From 2e170b9585fbbf57120598632cd7e13a11028c57 Mon Sep 17 00:00:00 2001 From: Rayyan oumlil Date: Thu, 5 Feb 2026 07:48:51 -0500 Subject: [PATCH 001/195] =?UTF-8?q?=F0=9F=93=9D=20Fix=20duplicate=20word?= =?UTF-8?q?=20in=20`advanced-dependencies.md`=20(#14815)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/advanced-dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/advanced/advanced-dependencies.md b/docs/en/docs/advanced/advanced-dependencies.md index 37f5c78f2..0d03507b7 100644 --- a/docs/en/docs/advanced/advanced-dependencies.md +++ b/docs/en/docs/advanced/advanced-dependencies.md @@ -120,7 +120,7 @@ The exit code, the automatic closing of the `Session` in: {* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} -...would be run after the the response finishes sending the slow data: +...would be run after the response finishes sending the slow data: {* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} From 64d0ee9104da25bfe11e0df5d560d3f989845b6f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 12:49:18 +0000 Subject: [PATCH 002/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 983e19fdb..6f3674911 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Docs + +* 📝 Fix duplicate word in `advanced-dependencies.md`. PR [#14815](https://github.com/fastapi/fastapi/pull/14815) by [@Rayyan-Oumlil](https://github.com/Rayyan-Oumlil). + ### Translations * 🔨 Update docs and translations scripts, enable Turkish. PR [#14824](https://github.com/fastapi/fastapi/pull/14824) by [@tiangolo](https://github.com/tiangolo). From 31faba8e41da15d3b71a46dadea8366f548e8892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 07:31:31 -0800 Subject: [PATCH 003/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20uk=20(update-outdated)=20(#14822)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Co-authored-by: Maksym Zavalniuk Co-authored-by: Roman Mashevskyi --- docs/uk/docs/index.md | 42 ++++++++----------- docs/uk/docs/tutorial/body-multiple-params.md | 13 +++--- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/docs/uk/docs/index.md b/docs/uk/docs/index.md index 526409c5c..4f089c4e1 100644 --- a/docs/uk/docs/index.md +++ b/docs/uk/docs/index.md @@ -8,7 +8,7 @@ FastAPI

- Фреймворк FastAPI: висока продуктивність, легко вивчати, швидко писати код, готовий до продакшину + Фреймворк FastAPI - це висока продуктивність, легко вивчати, швидко писати код, готовий до продакшину

@@ -33,14 +33,14 @@ --- -FastAPI — це сучасний, швидкий (високопродуктивний) вебфреймворк для створення API за допомогою Python, що базується на стандартних підказках типів Python. +FastAPI - це сучасний, швидкий (високопродуктивний) вебфреймворк для створення API за допомогою Python, що базується на стандартних підказках типів Python. Ключові особливості: * **Швидкий**: дуже висока продуктивність, на рівні з **NodeJS** та **Go** (завдяки Starlette та Pydantic). [Один із найшвидших Python-фреймворків](#performance). * **Швидке написання коду**: пришвидшує розробку функціоналу приблизно на 200%–300%. * * **Менше помилок**: зменшує приблизно на 40% кількість помилок, спричинених людиною (розробником). * -* **Інтуїтивний**: чудова підтримка редакторами коду. Автодоповнення всюди. Менше часу на налагодження. +* **Інтуїтивний**: чудова підтримка редакторами коду. Автодоповнення всюди. Менше часу на налагодження. * **Простий**: спроєктований так, щоб бути простим у використанні та вивченні. Менше часу на читання документації. * **Короткий**: мінімізує дублювання коду. Кілька можливостей з кожного оголошення параметра. Менше помилок. * **Надійний**: ви отримуєте код, готовий до продакшину. З автоматичною інтерактивною документацією. @@ -127,9 +127,9 @@ FastAPI — це сучасний, швидкий (високопродукти -Якщо ви створюєте застосунок CLI для використання в терміналі замість веб-API, зверніть увагу на **Typer**. +Якщо ви створюєте застосунок CLI для використання в терміналі замість веб-API, зверніть увагу на **Typer**. -**Typer** — молодший брат FastAPI. І його задумано як **FastAPI для CLI**. ⌨️ 🚀 +**Typer** - молодший брат FastAPI. І його задумано як **FastAPI для CLI**. ⌨️ 🚀 ## Вимоги { #requirements } @@ -161,8 +161,6 @@ $ pip install "fastapi[standard]" Створіть файл `main.py` з: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -174,7 +172,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -183,9 +181,7 @@ def read_item(item_id: int, q: Union[str, None] = None): Якщо ваш код використовує `async` / `await`, скористайтеся `async def`: -```Python hl_lines="9 14" -from typing import Union - +```Python hl_lines="7 12" from fastapi import FastAPI app = FastAPI() @@ -197,7 +193,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): +async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -288,9 +284,7 @@ INFO: Application startup complete. Оголосіть тіло, використовуючи стандартні типи Python, завдяки Pydantic. -```Python hl_lines="4 9-12 25-27" -from typing import Union - +```Python hl_lines="2 7-10 23-25" from fastapi import FastAPI from pydantic import BaseModel @@ -300,7 +294,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Union[bool, None] = None + is_offer: bool | None = None @app.get("/") @@ -309,7 +303,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} @@ -374,7 +368,7 @@ item: Item * Валідацію даних: * Автоматичні та зрозумілі помилки, коли дані некоректні. * Валідацію навіть для глибоко вкладених JSON-обʼєктів. -* Перетворення вхідних даних: з мережі до даних і типів Python. Читання з: +* Перетворення вхідних даних: з мережі до даних і типів Python. Читання з: * JSON. * Параметрів шляху. * Параметрів запиту. @@ -382,7 +376,7 @@ item: Item * Headers. * Forms. * Files. -* Перетворення вихідних даних: перетворення з даних і типів Python у мережеві дані (як JSON): +* Перетворення вихідних даних: перетворення з даних і типів Python у мережеві дані (як JSON): * Перетворення типів Python (`str`, `int`, `float`, `bool`, `list`, тощо). * Обʼєктів `datetime`. * Обʼєктів `UUID`. @@ -439,9 +433,9 @@ item: Item ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -Для більш повного прикладу, що включає більше можливостей, перегляньте Туторіал — Посібник користувача. +Для більш повного прикладу, що включає більше можливостей, перегляньте Навчальний посібник - Посібник користувача. -**Spoiler alert**: туторіал — посібник користувача містить: +**Попередження про спойлер**: навчальний посібник - посібник користувача містить: * Оголошення **параметрів** з інших різних місць, як-от: **headers**, **cookies**, **form fields** та **files**. * Як встановлювати **обмеження валідації** як `maximum_length` або `regex`. @@ -500,11 +494,11 @@ Deploying to FastAPI Cloud... Він забезпечує той самий **developer experience** створення застосунків на FastAPI під час їх **розгортання** у хмарі. 🎉 -FastAPI Cloud — основний спонсор і джерело фінансування open source проєктів *FastAPI and friends*. ✨ +FastAPI Cloud - основний спонсор і джерело фінансування open source проєктів *FastAPI and friends*. ✨ #### Розгортання в інших хмарних провайдерів { #deploy-to-other-cloud-providers } -FastAPI — open source і базується на стандартах. Ви можете розгортати застосунки FastAPI в будь-якому хмарному провайдері, який ви оберете. +FastAPI - open source проект і базується на стандартах. Ви можете розгортати застосунки FastAPI в будь-якому хмарному провайдері, який ви оберете. Дотримуйтеся інструкцій вашого хмарного провайдера, щоб розгорнути застосунки FastAPI у нього. 🤓 @@ -530,7 +524,7 @@ FastAPI залежить від Pydantic і Starlette. * httpx - потрібно, якщо ви хочете використовувати `TestClient`. * jinja2 - потрібно, якщо ви хочете використовувати конфігурацію шаблонів за замовчуванням. -* python-multipart - потрібно, якщо ви хочете підтримувати «parsing» форм за допомогою `request.form()`. +* python-multipart - потрібно, якщо ви хочете підтримувати «parsing» форм за допомогою `request.form()`. Використовується FastAPI: diff --git a/docs/uk/docs/tutorial/body-multiple-params.md b/docs/uk/docs/tutorial/body-multiple-params.md index dc9a768c3..f541beea7 100644 --- a/docs/uk/docs/tutorial/body-multiple-params.md +++ b/docs/uk/docs/tutorial/body-multiple-params.md @@ -102,15 +102,16 @@ Оскільки за замовчуванням одиничні значення інтерпретуються як параметри запиту, вам не потрібно явно додавати `Query`, ви можете просто зробити: +```Python +q: str | None = None +``` + +Або в Python 3.9: + ```Python q: Union[str, None] = None ``` -Або в Python 3.10 і вище: - -```Python -q: str | None = None -``` Наприклад: @@ -129,7 +130,7 @@ q: str | None = None За замовчуванням **FastAPI** очікуватиме його тіло безпосередньо. -Але якщо ви хочете, щоб він очікував JSON з ключем `item`, а всередині нього — вміст моделі, як це відбувається, коли ви оголошуєте додаткові параметри тіла, ви можете використати спеціальний параметр `Body` — `embed`: +Але якщо ви хочете, щоб він очікував JSON з ключем `item`, а всередині нього - вміст моделі, як це відбувається, коли ви оголошуєте додаткові параметри тіла, ви можете використати спеціальний параметр `Body` - `embed`: ```Python item: Item = Body(embed=True) From c00cebc5c68331046741f862359b83d0f428e518 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 15:31:55 +0000 Subject: [PATCH 004/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 6f3674911..f52be20d3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for uk (update-outdated). PR [#14822](https://github.com/fastapi/fastapi/pull/14822) by [@tiangolo](https://github.com/tiangolo). * 🔨 Update docs and translations scripts, enable Turkish. PR [#14824](https://github.com/fastapi/fastapi/pull/14824) by [@tiangolo](https://github.com/tiangolo). ## 0.128.1 From b4ed7e5b0644d180a2e437aa266f8696a0b89424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 07:32:56 -0800 Subject: [PATCH 005/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20zh-hant=20(update-outdated)=20(#14825)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/zh-hant/docs/about/index.md | 2 +- docs/zh-hant/docs/benchmarks.md | 6 +- docs/zh-hant/docs/deployment/cloud.md | 21 +- docs/zh-hant/docs/deployment/index.md | 8 +- docs/zh-hant/docs/environment-variables.md | 26 +- docs/zh-hant/docs/how-to/index.md | 2 +- docs/zh-hant/docs/index.md | 461 +++++++++++------- docs/zh-hant/docs/learn/index.md | 4 +- docs/zh-hant/docs/resources/index.md | 4 +- docs/zh-hant/docs/tutorial/first-steps.md | 205 +++++--- docs/zh-hant/docs/tutorial/index.md | 81 ++- .../docs/tutorial/query-param-models.md | 10 +- docs/zh-hant/docs/virtual-environments.md | 68 ++- 13 files changed, 532 insertions(+), 366 deletions(-) diff --git a/docs/zh-hant/docs/about/index.md b/docs/zh-hant/docs/about/index.md index 5dcee68f2..cf5b5742c 100644 --- a/docs/zh-hant/docs/about/index.md +++ b/docs/zh-hant/docs/about/index.md @@ -1,3 +1,3 @@ -# 關於 FastAPI +# 關於 { #about } 關於 FastAPI、其設計、靈感來源等更多資訊。 🤓 diff --git a/docs/zh-hant/docs/benchmarks.md b/docs/zh-hant/docs/benchmarks.md index c59e8e71c..df49621c5 100644 --- a/docs/zh-hant/docs/benchmarks.md +++ b/docs/zh-hant/docs/benchmarks.md @@ -1,10 +1,10 @@ -# 基準測試 +# 基準測試 { #benchmarks } 由第三方機構 TechEmpower 的基準測試表明在 Uvicorn 下運行的 **FastAPI** 應用程式是 最快的 Python 可用框架之一,僅次於 Starlette 和 Uvicorn 本身(於 FastAPI 內部使用)。 但是在查看基準得分和對比時,請注意以下幾點。 -## 基準測試和速度 +## 基準測試和速度 { #benchmarks-and-speed } 當你查看基準測試時,時常會見到幾個不同類型的工具被同時進行測試。 @@ -31,4 +31,4 @@ * FastAPI 在 Starlette 基礎之上提供了更多功能。包含建構 API 時所需要的功能,例如資料驗證和序列化。FastAPI 可以幫助你自動產生 API 文件,(應用程式啟動時將會自動生成文件,所以不會增加應用程式運行時的開銷)。 * 如果你沒有使用 FastAPI 而是直接使用 Starlette(或其他工具,如 Sanic、Flask、Responder 等),你將必須自行實現所有資料驗證和序列化。因此,你的最終應用程式仍然具有與使用 FastAPI 建置相同的開銷。在許多情況下,這種資料驗證和序列化是應用程式中編寫最大量的程式碼。 * 因此透過使用 FastAPI,你可以節省開發時間、錯誤與程式碼數量,並且相比不使用 FastAPI 你很大可能會獲得相同或更好的效能(因為那樣你必須在程式碼中實現所有相同的功能)。 - * 如果你要與 FastAPI 比較,請將其與能夠提供資料驗證、序列化和文件的網頁應用程式框架(或工具集)進行比較,例如 Flask-apispec、NestJS、Molten 等框架。 + * 如果你要與 FastAPI 比較,請將其與能夠提供資料驗證、序列化和文件的網頁應用程式框架(或工具集)進行比較,例如 Flask-apispec、NestJS、Molten 等框架。具備整合式自動資料驗證、序列化與文件的框架。 diff --git a/docs/zh-hant/docs/deployment/cloud.md b/docs/zh-hant/docs/deployment/cloud.md index 426937d3e..fffb2fcfe 100644 --- a/docs/zh-hant/docs/deployment/cloud.md +++ b/docs/zh-hant/docs/deployment/cloud.md @@ -1,13 +1,24 @@ -# 在雲端部署 FastAPI +# 在雲端供應商上部署 FastAPI { #deploy-fastapi-on-cloud-providers } 你幾乎可以使用**任何雲端供應商**來部署你的 FastAPI 應用程式。 在大多數情況下,主要的雲端供應商都有部署 FastAPI 的指南。 -## 雲端供應商 - 贊助商 +## FastAPI Cloud { #fastapi-cloud } -一些雲端供應商 ✨ [**贊助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨,這確保了 FastAPI 及其**生態系統**持續健康地**發展**。 +**FastAPI Cloud** 由 **FastAPI** 的同一位作者與團隊打造。 -這也展現了他們對 FastAPI 和其**社群**(包括你)的真正承諾,他們不僅希望為你提供**優質的服務**,還希望確保你擁有一個**良好且健康的框架**:FastAPI。🙇 +它讓你以最少的投入,簡化 **建置**、**部署** 與 **存取** API 的流程。 -你可能會想嘗試他們的服務,以下有他們的指南. +它把使用 FastAPI 開發應用的同樣**優秀的開發者體驗**,帶到將它們**部署**到雲端的過程中。🎉 + +FastAPI Cloud 是 *FastAPI and friends* 開源專案的主要贊助與資金提供者。✨ + +## 雲端供應商 - 贊助商 { #cloud-providers-sponsors } + +其他一些雲端供應商也會 ✨ [**贊助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨。🙇 + +你也可以參考他們的指南並試用其服務: + +* Render +* Railway diff --git a/docs/zh-hant/docs/deployment/index.md b/docs/zh-hant/docs/deployment/index.md index 1726562b4..9edd3368b 100644 --- a/docs/zh-hant/docs/deployment/index.md +++ b/docs/zh-hant/docs/deployment/index.md @@ -1,8 +1,8 @@ -# 部署 +# 部署 { #deployment } 部署 **FastAPI** 應用程式相對容易。 -## 部署是什麼意思 +## 部署是什麼意思 { #what-does-deployment-mean } **部署**應用程式指的是執行一系列必要的步驟,使其能夠**讓使用者存取和使用**。 @@ -10,12 +10,14 @@ 這與**開發**階段形成鮮明對比,在**開發**階段,你會不斷更改程式碼、破壞程式碼、修復程式碼,然後停止和重新啟動伺服器等。 -## 部署策略 +## 部署策略 { #deployment-strategies } 根據你的使用場景和使用工具,有多種方法可以實現此目的。 你可以使用一些工具自行**部署伺服器**,你也可以使用能為你完成部分工作的**雲端服務**,或其他可能的選項。 +例如,我們(FastAPI 的團隊)打造了 **FastAPI Cloud**,讓將 FastAPI 應用程式部署到雲端變得盡可能流暢,並保持與使用 FastAPI 開發時相同的開發者體驗。 + 我將向你展示在部署 **FastAPI** 應用程式時你可能應該記住的一些主要概念(儘管其中大部分適用於任何其他類型的 Web 應用程式)。 在接下來的部分中,你將看到更多需要記住的細節以及一些技巧。 ✨ diff --git a/docs/zh-hant/docs/environment-variables.md b/docs/zh-hant/docs/environment-variables.md index a1598fc01..5b684b9e6 100644 --- a/docs/zh-hant/docs/environment-variables.md +++ b/docs/zh-hant/docs/environment-variables.md @@ -1,4 +1,4 @@ -# 環境變數 +# 環境變數 { #environment-variables } /// tip @@ -10,7 +10,7 @@ 環境變數對於處理應用程式**設定**(作為 Python **安裝**的一部分等方面)非常有用。 -## 建立和使用環境變數 +## 建立和使用環境變數 { #create-and-use-env-vars } 你在 **shell(終端機)**中就可以**建立**和使用環境變數,並不需要用到 Python: @@ -50,7 +50,7 @@ Hello Wade Wilson //// -## 在 Python 中讀取環境變數 +## 在 Python 中讀取環境變數 { #read-env-vars-in-python } 你也可以在 Python **之外**的終端機中建立環境變數(或使用其他方法),然後在 Python 中**讀取**它們。 @@ -65,7 +65,7 @@ print(f"Hello {name} from Python") /// tip -第二個參數是 `os.getenv()` 的預設回傳值。 +第二個參數是 `os.getenv()` 的預設回傳值。 如果沒有提供,預設值為 `None`,這裡我們提供 `"World"` 作為預設值。 @@ -153,19 +153,19 @@ Hello World from Python /// tip -你可以在 The Twelve-Factor App: 配置中了解更多資訊。 +你可以在 The Twelve-Factor App: 配置中了解更多資訊。 /// -## 型別和驗證 +## 型別和驗證 { #types-and-validation } 這些環境變數只能處理**文字字串**,因為它們是位於 Python 範疇之外的,必須與其他程式和作業系統的其餘部分相容(甚至與不同的作業系統相容,如 Linux、Windows、macOS)。 這意味著從環境變數中讀取的**任何值**在 Python 中都將是一個 `str`,任何型別轉換或驗證都必須在程式碼中完成。 -你將在[進階使用者指南 - 設定和環境變數](./advanced/settings.md)中了解更多關於使用環境變數處理**應用程式設定**的資訊。 +你將在[進階使用者指南 - 設定和環境變數](./advanced/settings.md){.internal-link target=_blank}中了解更多關於使用環境變數處理**應用程式設定**的資訊。 -## `PATH` 環境變數 +## `PATH` 環境變數 { #path-environment-variable } 有一個**特殊的**環境變數稱為 **`PATH`**,作業系統(Linux、macOS、Windows)用它來查找要執行的程式。 @@ -209,7 +209,7 @@ C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System3 如果找到了,那麼作業系統將**使用它**;否則,作業系統會繼續在**其他目錄**中查找。 -### 安裝 Python 並更新 `PATH` +### 安裝 Python 並更新 `PATH` { #installing-python-and-updating-the-path } 安裝 Python 時,可能會詢問你是否要更新 `PATH` 環境變數。 @@ -233,7 +233,7 @@ C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System3 假設你安裝了 Python,並將其安裝在目錄 `C:\opt\custompython\bin` 中。 -如果你選擇更新 `PATH` 環境變數(在 Python 安裝程式中,這個選項是名為 `Add Python x.xx to PATH` 的勾選框——譯者註),那麼安裝程式會將 `C:\opt\custompython\bin` 加入到 `PATH` 環境變數中。 +如果你選擇更新 `PATH` 環境變數,那麼安裝程式會將 `C:\opt\custompython\bin` 加入到 `PATH` 環境變數中。 ```plaintext C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin @@ -285,13 +285,13 @@ $ C:\opt\custompython\bin\python //// -當學習[虛擬環境](virtual-environments.md)時,這些資訊將會很有用。 +當學習[虛擬環境](virtual-environments.md){.internal-link target=_blank}時,這些資訊將會很有用。 -## 結論 +## 結論 { #conclusion } 透過這個教學,你應該對**環境變數**是什麼以及如何在 Python 中使用它們有了基本的了解。 -你也可以在環境變數 - 維基百科 (Wikipedia for Environment Variable) 中了解更多關於它們的資訊。 +你也可以在 環境變數的維基百科條目 中閱讀更多。 在許多情況下,環境變數的用途和適用性可能不會立刻顯現。但是在開發過程中,它們會在許多不同的場景中出現,因此瞭解它們是非常必要的。 diff --git a/docs/zh-hant/docs/how-to/index.md b/docs/zh-hant/docs/how-to/index.md index db740140d..6c9a8202c 100644 --- a/docs/zh-hant/docs/how-to/index.md +++ b/docs/zh-hant/docs/how-to/index.md @@ -1,4 +1,4 @@ -# 使用指南 - 範例集 +# 使用指南 - 範例集 { #how-to-recipes } 在這裡,你將會看到**不同主題**的範例或「如何使用」的指南。 diff --git a/docs/zh-hant/docs/index.md b/docs/zh-hant/docs/index.md index 4390d9609..a31647f3c 100644 --- a/docs/zh-hant/docs/index.md +++ b/docs/zh-hant/docs/index.md @@ -1,5 +1,11 @@ +# FastAPI { #fastapi } + + +

- FastAPI + FastAPI

FastAPI 框架,高效能,易於學習,快速開發,適用於生產環境 @@ -21,138 +27,140 @@ --- -**文件**: https://fastapi.tiangolo.com +**文件**: https://fastapi.tiangolo.com/zh-hant **程式碼**: https://github.com/fastapi/fastapi --- -FastAPI 是一個現代、快速(高效能)的 web 框架,用於 Python 並採用標準 Python 型別提示。 +FastAPI 是一個現代、快速(高效能)的 Web 框架,用於以 Python 並基於標準的 Python 型別提示來構建 API。 主要特點包含: -- **快速**: 非常高的效能,可與 **NodeJS** 和 **Go** 效能相當 (歸功於 Starlette and Pydantic)。 [FastAPI 是最快的 Python web 框架之一](#performance)。 -- **極速開發**: 提高開發功能的速度約 200% 至 300%。 \* -- **更少的 Bug**: 減少約 40% 的人為(開發者)導致的錯誤。 \* -- **直覺**: 具有出色的編輯器支援,處處都有自動補全以減少偵錯時間。 -- **簡單**: 設計上易於使用和學習,大幅減少閱讀文件的時間。 -- **簡潔**: 最小化程式碼重複性。可以通過不同的參數聲明來實現更豐富的功能,和更少的錯誤。 -- **穩健**: 立即獲得生產級可用的程式碼,還有自動生成互動式文件。 -- **標準化**: 基於 (且完全相容於) OpenAPIs 的相關標準:OpenAPI(之前被稱為 Swagger)和JSON Schema。 +* **快速**:非常高的效能,可與 **NodeJS** 和 **Go** 相當(歸功於 Starlette 和 Pydantic)。[最快的 Python 框架之一](#performance)。 +* **極速開發**:開發功能的速度可提升約 200% 至 300%。* +* **更少的 Bug**:減少約 40% 的人為(開發者)錯誤。* +* **直覺**:具有出色的編輯器支援,處處都有 自動補全。更少的偵錯時間。 +* **簡單**:設計上易於使用與學習。更少的讀文件時間。 +* **簡潔**:最小化程式碼重複性。每個參數宣告可帶來多個功能。更少的錯誤。 +* **穩健**:立即獲得可投入生產的程式碼,並自動生成互動式文件。 +* **標準化**:基於(且完全相容於)API 的開放標準:OpenAPI(之前稱為 Swagger)和 JSON Schema。 -\* 基於內部開發團隊在建立生產應用程式時的測試預估。 +* 基於內部開發團隊在建立生產應用程式時的測試預估。 -## 贊助 +## 贊助 { #sponsors } -{% if sponsors %} +### 基石贊助商 { #keystone-sponsor } + +{% for sponsor in sponsors.keystone -%} + +{% endfor -%} + +### 金級與銀級贊助商 { #gold-and-silver-sponsors } + {% for sponsor in sponsors.gold -%} {% endfor -%} {%- for sponsor in sponsors.silver -%} {% endfor %} -{% endif %} -其他贊助商 +其他贊助商 -## 評價 +## 評價 { #opinions } -"_[...] 近期大量的使用 **FastAPI**。 [...] 目前正在計畫在**微軟**團隊的**機器學習**服務中導入。其中一些正在整合到核心的 **Windows** 產品和一些 **Office** 產品。_" +"_[...] 近期大量使用 **FastAPI**。[...] 我實際上打算在我在**微軟**團隊的所有**機器學習**服務上使用它。其中一些正在整合到核心的 **Windows** 產品,以及一些 **Office** 產品。_"

Kabir Khan - Microsoft (ref)
--- -"_我們使用 **FastAPI** 來建立產生**預測**結果的 **REST** 伺服器。 [for Ludwig]_" +"_我們採用了 **FastAPI** 函式庫來啟動一個 **REST** 伺服器,供查詢以取得**預測**。[for Ludwig]_"
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
--- -"_**Netflix** 很榮幸地宣布開源**危機管理**協調框架: **Dispatch**! [是使用 **FastAPI** 建構]_" +"_**Netflix** 很高興宣布我們的**危機管理**協調框架 **Dispatch** 開源![使用 **FastAPI** 建構]_"
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
--- -"_我對 **FastAPI** 興奮得不得了。它太有趣了!_" +"_我對 **FastAPI** 興奮得不得了。超好玩!_" -
Brian Okken - Python Bytes podcast host (ref)
+
Brian Okken - Python Bytes podcast 主持人 (ref)
--- -"_老實說,你建造的東西看起來非常堅固和精緻。在很多方面,這就是我想要的,看到有人建造它真的很鼓舞人心。_" +"_老實說,你們做的看起來非常穩健又精緻。很多方面都正是我希望 **Hug** 成為的樣子——看到有人把它建出來真的很鼓舞人心。_" -
Timothy Crosley - Hug creator (ref)
+
Timothy Crosley - Hug 創作者 (ref)
--- -"_如果您想學習一種用於構建 REST API 的**現代框架**,不能錯過 **FastAPI** [...] 它非常快速、且易於使用和學習 [...]_" +"_如果你想學一個用於構建 REST API 的**現代框架**,看看 **FastAPI** [...] 它很快、易用、也容易學習 [...]_" -"_我們的 **APIs** 已經改用 **FastAPI** [...] 我想你會喜歡它 [...]_" +"_我們的 **API** 已經改用 **FastAPI** [...] 我想你會喜歡它 [...]_" -
Ines Montani - Matthew Honnibal - Explosion AI 創辦人 - spaCy creators (ref) - (ref)
+
Ines Montani - Matthew Honnibal - Explosion AI 創辦人 - spaCy 創作者 (ref) - (ref)
--- -"_如果有人想要建立一個生產環境的 Python API,我強烈推薦 **FastAPI**,它**設計精美**,**使用簡單**且**高度可擴充**,它已成為我們 API 優先開發策略中的**關鍵組件**,並且驅動了許多自動化服務,例如我們的 Virtual TAC Engineer。_" +"_如果有人想要建立一個可投入生產的 Python API,我強烈推薦 **FastAPI**。它**設計精美**、**使用簡單**且**高度可擴充**,已成為我們 API 優先開發策略中的**關鍵組件**,推動了許多自動化與服務,例如我們的 Virtual TAC Engineer。_"
Deon Pillsbury - Cisco (ref)
--- -## **Typer**,命令列中的 FastAPI +## FastAPI 迷你紀錄片 { #fastapi-mini-documentary } + +在 2025 年底發布了一支 FastAPI 迷你紀錄片,你可以在線上觀看: + +FastAPI Mini Documentary + +## **Typer**,命令列的 FastAPI { #typer-the-fastapi-of-clis } -如果你不是在開發網頁 API,而是正在開發一個在終端機中運行的命令列應用程式,不妨嘗試 **Typer**。 +如果你不是在做 Web API,而是要建立一個在終端機中使用的 CLI 應用程式,可以看看 **Typer**。 -**Typer** 是 FastAPI 的小兄弟。他立志成為命令列的 **FastAPI**。 ⌨️ 🚀 +**Typer** 是 FastAPI 的小老弟。他立志成為命令列世界的 **FastAPI**。⌨️ 🚀 -## 安裝需求 +## 需求 { #requirements } FastAPI 是站在以下巨人的肩膀上: -- Starlette 負責網頁的部分 -- Pydantic 負責資料的部分 +* Starlette 負責 Web 部分。 +* Pydantic 負責資料部分。 -## 安裝 +## 安裝 { #installation } + +建立並啟用一個虛擬環境,然後安裝 FastAPI:
```console -$ pip install fastapi +$ pip install "fastapi[standard]" ---> 100% ```
-你同時也會需要 ASGI 伺服器用於生產環境,像是 UvicornHypercorn。 +**注意**:請務必將 `"fastapi[standard]"` 用引號包起來,以確保在所有終端機中都能正常運作。 -
+## 範例 { #example } -```console -$ pip install "uvicorn[standard]" +### 建立 { #create-it } ----> 100% -``` - -
- -## 範例 - -### 建立 - -- 建立一個 python 檔案 `main.py`,並寫入以下程式碼: +建立檔案 `main.py`,內容如下: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -164,18 +172,16 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ```
-或可以使用 async def... +或使用 async def... -如果你的程式使用 `async` / `await`,請使用 `async def`: - -```Python hl_lines="9 14" -from typing import Union +如果你的程式碼使用 `async` / `await`,請使用 `async def`: +```Python hl_lines="7 12" from fastapi import FastAPI app = FastAPI() @@ -187,28 +193,41 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): +async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` **注意**: -如果你不知道是否會用到,可以查看 _"In a hurry?"_ 章節中,關於 `async` 和 `await` 的部分。 +如果你不確定,請查看文件中 _"In a hurry?"_ 章節的 `async` 與 `await`
-### 運行 +### 運行 { #run-it } 使用以下指令運行伺服器:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py + ╭────────── 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 [28720] -INFO: Started server process [28722] +INFO: Started reloader process [2248755] using WatchFiles +INFO: Started server process [2248757] INFO: Waiting for application startup. INFO: Application startup complete. ``` @@ -216,21 +235,21 @@ INFO: Application startup complete.
-關於指令 uvicorn main:app --reload... +關於指令 fastapi dev main.py... -該指令 `uvicorn main:app` 指的是: +指令 `fastapi dev` 會讀取你的 `main.py`,偵測其中的 **FastAPI** 應用,並使用 Uvicorn 啟動伺服器。 -- `main`:`main.py` 檔案(一個 python 的 "模組")。 -- `app`:在 `main.py` 檔案中,使用 `app = FastAPI()` 建立的物件。 -- `--reload`:程式碼更改後會自動重新啟動,請僅在開發時使用此參數。 +預設情況下,`fastapi dev` 會在本機開發時啟用自動重新載入。 + +可在 FastAPI CLI 文件中閱讀更多資訊。
-### 檢查 +### 檢查 { #check-it } 使用瀏覽器開啟 http://127.0.0.1:8000/items/5?q=somequery。 -你將會看到以下的 JSON 回應: +你將會看到以下 JSON 回應: ```JSON {"item_id": 5, "q": "somequery"} @@ -238,36 +257,34 @@ INFO: Application startup complete. 你已經建立了一個具有以下功能的 API: -- 透過路徑 `/` 和 `/items/{item_id}` 接受 HTTP 請求。 -- 以上路經都接受 `GET` 請求(也被稱為 HTTP _方法_)。 -- 路徑 `/items/{item_id}` 有一個 `int` 型別的 `item_id` 參數。 -- 路徑 `/items/{item_id}` 有一個 `str` 型別的查詢參數 `q`。 +* 透過路徑 `/` 和 `/items/{item_id}` 接受 HTTP 請求。 +* 以上兩個路徑都接受 `GET` 操作(也被稱為 HTTP _方法_)。 +* 路徑 `/items/{item_id}` 有一個 `int` 型別的路徑參數 `item_id`。 +* 路徑 `/items/{item_id}` 有一個可選的 `str` 查詢參數 `q`。 -### 互動式 API 文件 +### 互動式 API 文件 { #interactive-api-docs } -使用瀏覽器開啟 http://127.0.0.1:8000/docs。 +接著前往 http://127.0.0.1:8000/docs。 -你會看到自動生成的互動式 API 文件(由 Swagger UI 生成): +你會看到自動生成的互動式 API 文件(由 Swagger UI 提供): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### ReDoc API 文件 +### 替代 API 文件 { #alternative-api-docs } -使用瀏覽器開啟 http://127.0.0.1:8000/redoc。 +現在前往 http://127.0.0.1:8000/redoc。 -你將看到 ReDoc 文件 (由 ReDoc 生成): +你會看到另一種自動文件(由 ReDoc 提供): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## 範例升級 +## 範例升級 { #example-upgrade } -現在繼續修改 `main.py` 檔案,來接收一個帶有 body 的 `PUT` 請求。 +現在修改 `main.py` 檔案,使其能從 `PUT` 請求接收 body。 -我們使用 Pydantic 來使用標準的 Python 型別聲明請求。 - -```Python hl_lines="4 9-12 25-27" -from typing import Union +多虧了 Pydantic,你可以用標準的 Python 型別來宣告 body。 +```Python hl_lines="2 7-10 23-25" from fastapi import FastAPI from pydantic import BaseModel @@ -277,7 +294,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Union[bool, None] = None + is_offer: bool | None = None @app.get("/") @@ -286,7 +303,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} @@ -295,43 +312,43 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -伺服器將自動重新載入(因為在上一步中,你向 `uvicorn` 指令添加了 `--reload` 的選項)。 +`fastapi dev` 伺服器應會自動重新載入。 -### 互動式 API 文件升級 +### 互動式 API 文件升級 { #interactive-api-docs-upgrade } -使用瀏覽器開啟 http://127.0.0.1:8000/docs。 +前往 http://127.0.0.1:8000/docs。 -- 互動式 API 文件會自動更新,並加入新的 body 請求: +* 互動式 API 文件會自動更新,包含新的 body: ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -- 點擊 "Try it out" 按鈕, 你可以填寫參數並直接與 API 互動: +* 點擊「Try it out」按鈕,你可以填寫參數並直接與 API 互動: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) -- 然後點擊 "Execute" 按鈕,使用者介面將會向 API 發送請求,並將結果顯示在螢幕上: +* 然後點擊「Execute」按鈕,使用者介面會與你的 API 溝通、送出參數、取得結果並顯示在螢幕上: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) -### ReDoc API 文件升級 +### 替代 API 文件升級 { #alternative-api-docs-upgrade } -使用瀏覽器開啟 http://127.0.0.1:8000/redoc。 +現在前往 http://127.0.0.1:8000/redoc。 -- ReDoc API 文件會自動更新,並加入新的參數和 body 請求: +* 替代文件也會反映新的查詢參數與 body: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### 總結 +### 總結 { #recap } -總結來說, 你就像宣告函式的參數型別一樣,只宣告了一次請求參數和請求主體參數等型別。 +總結來說,你只需在函式參數中**一次**宣告參數、body 等的型別。 -你使用 Python 標準型別來完成聲明。 +你使用的是現代標準的 Python 型別。 -你不需要學習新的語法、類別、方法或函式庫等等。 +你不需要學新的語法、特定函式庫的方法或類別,等等。 -只需要使用 **Python 以上的版本**。 +就用標準的 **Python**。 -舉個範例,比如宣告 int 的型別: +例如,對於一個 `int`: ```Python item_id: int @@ -343,126 +360,200 @@ item_id: int item: Item ``` -在進行一次宣告後,你將獲得: +…透過一次宣告,你將獲得: -- 編輯器支援: - - 自動補全 - - 型別檢查 -- 資料驗證: - - 驗證失敗時自動生成清楚的錯誤訊息 - - 可驗證多層巢狀的 JSON 物件 -- 轉換輸入的資料: 轉換來自網路請求到 Python 資料型別。包含以下數據: - - JSON - - 路徑參數 - - 查詢參數 - - Cookies - - 請求標頭 - - 表單 - - 文件 -- 轉換輸出的資料: 轉換 Python 資料型別到網路傳輸的 JSON: - - 轉換 Python 型別 (`str`、 `int`、 `float`、 `bool`、 `list` 等) - - `datetime` 物件 - - `UUID` 物件 - - 數據模型 - - ...還有其他更多 -- 自動生成的 API 文件,包含 2 種不同的使用介面: - - Swagger UI - - ReDoc +* 編輯器支援,包括: + * 自動補全。 + * 型別檢查。 +* 資料驗證: + * 當資料無效時,自動且清楚的錯誤。 + * 即使是深度巢狀的 JSON 物件也能驗證。 +* 輸入資料的 轉換:從網路讀入到 Python 資料與型別。包含: + * JSON。 + * 路徑參數。 + * 查詢參數。 + * Cookies。 + * 標頭。 + * 表單。 + * 檔案。 +* 輸出資料的 轉換:從 Python 資料與型別轉換為網路資料(JSON): + * 轉換 Python 型別(`str`、`int`、`float`、`bool`、`list` 等)。 + * `datetime` 物件。 + * `UUID` 物件。 + * 資料庫模型。 + * ...還有更多。 +* 自動生成的互動式 API 文件,包含 2 種替代的使用者介面: + * Swagger UI。 + * ReDoc。 --- -回到前面的的程式碼範例,**FastAPI** 還會: +回到前面的程式碼範例,**FastAPI** 會: -- 驗證 `GET` 和 `PUT` 請求路徑中是否包含 `item_id`。 -- 驗證 `GET` 和 `PUT` 請求中的 `item_id` 是否是 `int` 型別。 - - 如果驗證失敗,將會返回清楚有用的錯誤訊息。 -- 查看 `GET` 請求中是否有命名為 `q` 的查詢參數 (例如 `http://127.0.0.1:8000/items/foo?q=somequery`)。 - - 因為 `q` 參數被宣告為 `= None`,所以是選填的。 - - 如果沒有宣告 `None`,則此參數將會是必填 (例如 `PUT` 範例的請求 body)。 -- 對於 `PUT` 的請求 `/items/{item_id}`,將會讀取 body 為 JSON: - - 驗證是否有必填屬性 `name` 且型別是 `str`。 - - 驗證是否有必填屬性 `price` 且型別是 `float`。 - - 驗證是否有選填屬性 `is_offer` 且型別是 `bool`。 - - 以上驗證都適用於多層次巢狀 JSON 物件。 -- 自動轉換 JSON 格式。 -- 透過 OpenAPI 文件來記錄所有內容,可以被用於: - - 互動式文件系統。 - - 自動為多種程式語言生成用戶端的程式碼。 -- 提供兩種交互式文件介面。 +* 驗證 `GET` 與 `PUT` 請求的路徑中是否包含 `item_id`。 +* 驗證 `GET` 與 `PUT` 請求中的 `item_id` 是否為 `int` 型別。 + * 如果不是,客戶端會看到清楚有用的錯誤。 +* 在 `GET` 請求中檢查是否有名為 `q` 的可選查詢參數(如 `http://127.0.0.1:8000/items/foo?q=somequery`)。 + * 因為 `q` 參數被宣告為 `= None`,所以它是可選的。 + * 若沒有 `None`,則它會是必填(就像 `PUT` 時的 body)。 +* 對於 `/items/{item_id}` 的 `PUT` 請求,以 JSON 讀取 body: + * 檢查是否有必填屬性 `name`,且為 `str`。 + * 檢查是否有必填屬性 `price`,且為 `float`。 + * 檢查是否有可選屬性 `is_offer`,若存在則應為 `bool`。 + * 以上也適用於深度巢狀的 JSON 物件。 +* 自動在 JSON 與 Python 之間轉換。 +* 以 OpenAPI 記錄所有內容,可用於: + * 互動式文件系統。 + * 為多種語言自動產生用戶端程式碼的系統。 +* 直接提供兩種互動式文件網頁介面。 --- -雖然我們只敘述了表面的功能,但其實你已經理解了它是如何執行。 +我們只觸及了表面,但你已經了解它的運作方式了。 -試著修改以下程式碼: +試著把這一行: ```Python return {"item_name": item.name, "item_id": item_id} ``` -從: +…從: ```Python ... "item_name": item.name ... ``` -修改為: +…改為: ```Python ... "item_price": item.price ... ``` -然後觀察你的編輯器,會自動補全並且還知道他們的型別: +…然後看看你的編輯器如何自動補全屬性並知道它們的型別: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -有關更多功能的完整範例,可以參考 教學 - 使用者指南。 +若想看包含更多功能的完整範例,請參考 教學 - 使用者指南。 -**劇透警告**: 教學 - 使用者指南內容有: +**劇透警告**:教學 - 使用者指南包含: -- 對來自不同地方的**參數**進行宣告:像是 **headers**, **cookies**, **form 表單**以及**上傳檔案**。 -- 如何設定 **驗證限制** 像是 `maximum_length` or `regex`。 -- 簡單且非常容易使用的 **依賴注入** 系統。 -- 安全性和身份驗證,包含提供支援 **OAuth2**、**JWT tokens** 和 **HTTP Basic** 驗證。 -- 更進階 (但同樣簡單) 的宣告 **多層次的巢狀 JSON 格式** (感謝 Pydantic)。 -- **GraphQL** 與 Strawberry 以及其他的相關函式庫進行整合。 -- 更多其他的功能 (感謝 Starlette) 像是: - - **WebSockets** - - 於 HTTPX 和 `pytest` 的非常簡單測試 - - **CORS** - - **Cookie Sessions** - - ...以及更多 +* 來自不同來源的**參數**宣告:例如 **headers**、**cookies**、**form fields** 和 **files**。 +* 如何設定**驗證限制**,如 `maximum_length` 或 `regex`。 +* 一個非常強大且易用的 **依賴注入** 系統。 +* 安全與驗證,包含支援 **OAuth2** 搭配 **JWT tokens** 與 **HTTP Basic** 驗證。 +* 宣告**深度巢狀 JSON 模型**的進階(但同樣簡單)技巧(感謝 Pydantic)。 +* 與 Strawberry 及其他函式庫的 **GraphQL** 整合。 +* 許多額外功能(感謝 Starlette),例如: + * **WebSockets** + * 基於 HTTPX 與 `pytest` 的極其簡單的測試 + * **CORS** + * **Cookie Sessions** + * ...以及更多。 -## 效能 +### 部署你的應用(可選) { #deploy-your-app-optional } -來自獨立機構 TechEmpower 的測試結果,顯示在 Uvicorn 執行下的 **FastAPI** 是 最快的 Python 框架之一, 僅次於 Starlette 和 Uvicorn 本身 (兩者是 FastAPI 的底層)。 (\*) +你也可以選擇將 FastAPI 應用部署到 FastAPI Cloud,如果你還沒加入,去登記等候名單吧。🚀 -想了解更多訊息,可以參考 測試結果。 +如果你已經有 **FastAPI Cloud** 帳號(我們已從等候名單邀請你 😉),你可以用一個指令部署你的應用。 -## 可選的依賴套件 +部署前,先確認你已登入: -用於 Pydantic: +
-- email-validator - 用於電子郵件驗證。 -- pydantic-settings - 用於設定管理。 -- pydantic-extra-types - 用於與 Pydantic 一起使用的額外型別。 +```console +$ fastapi login -用於 Starlette: +You are logged in to FastAPI Cloud 🚀 +``` -- httpx - 使用 `TestClient`時必須安裝。 -- jinja2 - 使用預設的模板配置時必須安裝。 -- python-multipart - 需要使用 `request.form()` 對表單進行 "解析" 時安裝。 -- itsdangerous - 需要使用 `SessionMiddleware` 支援時安裝。 -- pyyaml - 用於支援 Starlette 的 `SchemaGenerator` (如果你使用 FastAPI,可能不需要它)。 +
-用於 FastAPI / Starlette: +接著部署你的應用: -- uvicorn - 用於加載和運行應用程式的服務器。 -- orjson - 使用 `ORJSONResponse`時必須安裝。 -- ujson - 使用 `UJSONResponse` 時必須安裝。 +
-你可以使用 `pip install "fastapi[all]"` 來安裝這些所有依賴套件。 +```console +$ fastapi deploy -## 授權 +Deploying to FastAPI Cloud... -該項目遵循 MIT 許可協議。 +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +就這樣!現在你可以在該 URL 造訪你的應用。✨ + +#### 關於 FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** 由 **FastAPI** 的同一位作者與團隊打造。 + +它讓你以最小的努力精簡地完成 API 的**建置**、**部署**與**存取**流程。 + +它把用 FastAPI 開發應用的**開發者體驗**帶到**部署**到雲端的流程中。🎉 + +FastAPI Cloud 是「FastAPI 與好朋友們」這些開源專案的主要贊助與資金來源。✨ + +#### 部署到其他雲端供應商 { #deploy-to-other-cloud-providers } + +FastAPI 是開源且基於標準。你可以把 FastAPI 應用部署到任何你選擇的雲端供應商。 + +依照你雲端供應商的指南來部署 FastAPI 應用吧。🤓 + +## 效能 { #performance } + +獨立的 TechEmpower 基準測試顯示,在 Uvicorn 下運行的 **FastAPI** 應用是最快的 Python 框架之一,僅次於 Starlette 與 Uvicorn 本身(FastAPI 內部使用它們)。(*) + +想了解更多,請參閱測試結果。 + +## 依賴套件 { #dependencies } + +FastAPI 依賴 Pydantic 與 Starlette。 + +### `standard` 依賴套件 { #standard-dependencies } + +當你以 `pip install "fastapi[standard]"` 安裝 FastAPI 時,會包含 `standard` 這組可選依賴套件: + +Pydantic 會使用: + +* email-validator - 用於電子郵件驗證。 + +Starlette 會使用: + +* httpx - 若要使用 `TestClient` 必須安裝。 +* jinja2 - 若要使用預設的模板設定必須安裝。 +* python-multipart - 若要支援表單 "解析",搭配 `request.form()`。 + +FastAPI 會使用: + +* uvicorn - 用於載入並服務你的應用的伺服器。這包含 `uvicorn[standard]`,其中含有一些高效能服務所需的依賴(例如 `uvloop`)。 +* `fastapi-cli[standard]` - 提供 `fastapi` 指令。 + * 其中包含 `fastapi-cloud-cli`,可讓你將 FastAPI 應用部署到 FastAPI Cloud。 + +### 不含 `standard` 依賴套件 { #without-standard-dependencies } + +如果你不想包含 `standard` 可選依賴,可以改用 `pip install fastapi`(而不是 `pip install "fastapi[standard]"`)。 + +### 不含 `fastapi-cloud-cli` { #without-fastapi-cloud-cli } + +如果你想安裝帶有 standard 依賴、但不包含 `fastapi-cloud-cli`,可以使用 `pip install "fastapi[standard-no-fastapi-cloud-cli]"`。 + +### 額外可選依賴套件 { #additional-optional-dependencies } + +有些額外依賴你可能也會想安裝。 + +Pydantic 的額外可選依賴: + +* pydantic-settings - 設定管理。 +* pydantic-extra-types - 與 Pydantic 一起使用的額外型別。 + +FastAPI 的額外可選依賴: + +* orjson - 若要使用 `ORJSONResponse` 必須安裝。 +* ujson - 若要使用 `UJSONResponse` 必須安裝。 + +## 授權 { #license } + +本專案以 MIT 授權條款釋出。 diff --git a/docs/zh-hant/docs/learn/index.md b/docs/zh-hant/docs/learn/index.md index eb7d7096a..43e4519a5 100644 --- a/docs/zh-hant/docs/learn/index.md +++ b/docs/zh-hant/docs/learn/index.md @@ -1,5 +1,5 @@ -# 學習 +# 學習 { #learn } -以下是學習 FastAPI 的入門介紹和教學。 +以下是學習 **FastAPI** 的入門介紹和教學。 你可以將其視為一本**書籍**或一門**課程**,這是**官方**認可並推薦的 FastAPI 學習方式。 😎 diff --git a/docs/zh-hant/docs/resources/index.md b/docs/zh-hant/docs/resources/index.md index f4c70a3a0..ea77cb728 100644 --- a/docs/zh-hant/docs/resources/index.md +++ b/docs/zh-hant/docs/resources/index.md @@ -1,3 +1,3 @@ -# 資源 +# 資源 { #resources } -額外的資源、外部連結、文章等。 ✈️ +額外的資源、外部連結等。 ✈️ diff --git a/docs/zh-hant/docs/tutorial/first-steps.md b/docs/zh-hant/docs/tutorial/first-steps.md index d6684bc51..6bcb453bf 100644 --- a/docs/zh-hant/docs/tutorial/first-steps.md +++ b/docs/zh-hant/docs/tutorial/first-steps.md @@ -1,8 +1,8 @@ -# 第一步 +# 第一步 { #first-steps } 最簡單的 FastAPI 檔案可能看起來像這樣: -{* ../../docs_src/first_steps/tutorial001.py *} +{* ../../docs_src/first_steps/tutorial001_py39.py *} 將其複製到一個名為 `main.py` 的文件中。 @@ -11,47 +11,39 @@
```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 +$ fastapi dev main.py - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ + FastAPI Starting development server 🚀 -INFO Importing module main -INFO Found importable FastAPI app + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ + module 🐍 main.py -INFO Using import string main:app + code Importing the FastAPI app object from the module with + the following code: - ╭────────── 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 - │ │ - ╰─────────────────────────────────────────────────────╯ + from main import app -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. + 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. ```
@@ -64,7 +56,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) 那列顯示了你的應用程式正在本地端機器上運行的 URL。 -### 查看它 +### 查看它 { #check-it } 在瀏覽器中打開 http://127.0.0.1:8000. @@ -74,7 +66,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) {"message": "Hello World"} ``` -### 互動式 API 文件 +### 互動式 API 文件 { #interactive-api-docs } 現在,前往 http://127.0.0.1:8000/docs. @@ -82,7 +74,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### 替代 API 文件 +### 替代 API 文件 { #alternative-api-docs } 現在,前往 http://127.0.0.1:8000/redoc. @@ -90,33 +82,33 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -### OpenAPI +### OpenAPI { #openapi } -**FastAPI** 使用定義 API 的 **OpenAPI** 標準來生成一個 「schema」 與你的所有 API。 +**FastAPI** 使用定義 API 的 **OpenAPI** 標準來生成一個「schema」,涵蓋你的全部 API。 -#### 「Schema」 +#### 「Schema」 { #schema } 「schema」是對某個事物的定義或描述。它並不是實作它的程式碼,而僅僅是一個抽象的描述。 -#### API 「schema」 +#### API 「schema」 { #api-schema } 在這種情況下,OpenAPI 是一個規範,它規定了如何定義 API 的 schema。 這個 schema 定義包含了你的 API 路徑、可能接收的參數等內容。 -#### 資料 「schema」 +#### 資料「schema」 { #data-schema } 「schema」這個術語也可能指某些資料的結構,比如 JSON 內容的結構。 在這種情況下,它指的是 JSON 的屬性、資料型別等。 -#### OpenAPI 和 JSON Schema +#### OpenAPI 和 JSON Schema { #openapi-and-json-schema } -OpenAPI 定義了 API 的 schema。這個 schema 包含了使用 **JSON Schema** 定義的資料,這是 JSON 資料 schema 的標準。 +OpenAPI 為你的 API 定義了 API 的 schema。而該 schema 會包含你的 API 所傳送與接收資料的定義(或稱「schemas」),使用 **JSON Schema**,這是 JSON 資料 schema 的標準。 -#### 檢查 `openapi.json` +#### 檢查 `openapi.json` { #check-the-openapi-json } -如果你好奇原始的 OpenAPI schema 長什麼樣子,FastAPI 會自動生成一個包含所有 API 描述的 JSON (schema)。 +如果你好奇原始的 OpenAPI schema 長什麼樣子,FastAPI 會自動生成一個包含所有 API 描述的 JSON(schema)。 你可以直接在 http://127.0.0.1:8000/openapi.json 查看它。 @@ -143,23 +135,59 @@ OpenAPI 定義了 API 的 schema。這個 schema 包含了使用 **JSON Schema** ... ``` -#### OpenAPI 的用途 +#### OpenAPI 的用途 { #what-is-openapi-for } OpenAPI schema 驅動了兩個互動式文件系統。 而且有許多替代方案,所有這些都是基於 OpenAPI。你可以輕鬆地將任何這些替代方案添加到使用 **FastAPI** 建置的應用程式中。 -你也可以用它自動生成程式碼,讓前端、手機應用程式或物聯網設備等與你的 API 進行通訊。 +你也可以用它自動生成程式碼,讓用戶端與你的 API 通訊。例如前端、手機或物聯網(IoT)應用程式。 -## 逐步回顧 +### 部署你的應用程式(可選) { #deploy-your-app-optional } -### 第一步:引入 `FastAPI` +你可以選擇將你的 FastAPI 應用程式部署到 FastAPI Cloud,如果還沒有,去加入候補名單吧。🚀 -{* ../../docs_src/first_steps/tutorial001.py h1[1] *} +如果你已經有 **FastAPI Cloud** 帳號(我們已從候補名單邀請你 😉),你可以用一個指令部署你的應用程式。 + +部署之前,先確保你已登入: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +接著部署你的應用程式: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +就這樣!現在你可以透過該 URL 存取你的應用程式了。✨ + +## 逐步回顧 { #recap-step-by-step } + +### 第一步:引入 `FastAPI` { #step-1-import-fastapi } + +{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} `FastAPI` 是一個 Python 類別,提供所有 API 的全部功能。 -/// note | Technical Details +/// note | 技術細節 `FastAPI` 是一個直接繼承自 `Starlette` 的類別。 @@ -167,17 +195,17 @@ OpenAPI schema 驅動了兩個互動式文件系統。 /// -### 第二步:建立一個 `FastAPI` 「實例」 +### 第二步:建立一個 `FastAPI`「實例」 { #step-2-create-a-fastapi-instance } -{* ../../docs_src/first_steps/tutorial001.py h1[3] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *} 這裡的 `app` 變數將會是 `FastAPI` 類別的「實例」。 這將是你建立所有 API 的主要互動點。 -### 第三步:建立一個 *路徑操作* +### 第三步:建立一個「路徑操作」 { #step-3-create-a-path-operation } -#### 路徑 +#### 路徑 { #path } 這裡的「路徑」指的是 URL 中自第一個 `/` 以後的部分。 @@ -201,7 +229,7 @@ https://example.com/items/foo 在建置 API 時,「路徑」是分離「關注點」和「資源」的主要方式。 -#### 操作 +#### 操作 { #operation } 這裡的「操作」指的是 HTTP 的「方法」之一。 @@ -236,16 +264,16 @@ https://example.com/items/foo 我們將會稱它們為「**操作**」。 -#### 定義一個 *路徑操作裝飾器* +#### 定義一個「路徑操作裝飾器」 { #define-a-path-operation-decorator } -{* ../../docs_src/first_steps/tutorial001.py h1[6] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *} `@app.get("/")` 告訴 **FastAPI** 那個函式負責處理請求: * 路徑 `/` * 使用 get操作 -/// info | `@decorator` Info +/// info | `@decorator` 說明 Python 中的 `@something` 語法被稱為「裝飾器」。 @@ -253,7 +281,7 @@ Python 中的 `@something` 語法被稱為「裝飾器」。 一個「裝飾器」會對下面的函式做一些事情。 -在這種情況下,這個裝飾器告訴 **FastAPI** 那個函式對應於 **路徑** `/` 和 **操作** `get`. +在這種情況下,這個裝飾器告訴 **FastAPI** 那個函式對應於 **路徑** `/` 和 **操作** `get`。 這就是「**路徑操作裝飾器**」。 @@ -284,27 +312,27 @@ Python 中的 `@something` 語法被稱為「裝飾器」。 /// -### 第四步:定義 **路徑操作函式** +### 第四步:定義「路徑操作函式」 { #step-4-define-the-path-operation-function } 這是我們的「**路徑操作函式**」: -* **path**: 是 `/`. -* **operation**: 是 `get`. -* **function**: 是裝飾器下面的函式(在 `@app.get("/")` 下面)。 +* **path**:是 `/`。 +* **operation**:是 `get`。 +* **function**:是裝飾器下面的函式(在 `@app.get("/")` 下面)。 -{* ../../docs_src/first_steps/tutorial001.py h1[7] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} 這就是一個 Python 函式。 -它將會在 **FastAPI** 收到一個請求時被呼叫,使用 `GET` 操作。 +它將會在 **FastAPI** 收到一個使用 `GET` 操作、網址為「`/`」的請求時被呼叫。 在這種情況下,它是一個 `async` 函式。 --- -你可以將它定義為一個正常的函式,而不是 `async def`: +你也可以將它定義為一般函式,而不是 `async def`: -{* ../../docs_src/first_steps/tutorial003.py h1[7] *} +{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} /// note @@ -312,9 +340,9 @@ Python 中的 `@something` 語法被稱為「裝飾器」。 /// -### 第五步:回傳內容 +### 第五步:回傳內容 { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001.py h1[8] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} 你可以返回一個 `dict`、`list`、單個值作為 `str`、`int` 等。 @@ -322,10 +350,31 @@ Python 中的 `@something` 語法被稱為「裝飾器」。 有很多其他物件和模型會自動轉換為 JSON(包括 ORMs,等等)。試用你最喜歡的,很有可能它們已經有支援。 -## 回顧 +### 第六步:部署 { #step-6-deploy-it } -* 引入 `FastAPI`. +用一行指令將你的應用程式部署到 **FastAPI Cloud**:`fastapi deploy`。🎉 + +#### 關於 FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** 由 **FastAPI** 的作者與團隊打造。 + +它讓你以最小的成本完成 API 的**建置**、**部署**與**存取**流程。 + +它把用 FastAPI 開發應用的同樣**開發者體驗**帶到將應用**部署**到雲端的流程中。🎉 + +FastAPI Cloud 也是「FastAPI 與其好友」這些開源專案的主要贊助與資金提供者。✨ + +#### 部署到其他雲端供應商 { #deploy-to-other-cloud-providers } + +FastAPI 是開源並基於標準的。你可以把 FastAPI 應用部署到你選擇的任何雲端供應商。 + +依照你的雲端供應商的指南部署 FastAPI 應用吧。🤓 + +## 回顧 { #recap } + +* 引入 `FastAPI`。 * 建立一個 `app` 實例。 -* 寫一個 **路徑操作裝飾器** 使用裝飾器像 `@app.get("/")`。 -* 定義一個 **路徑操作函式**;例如,`def root(): ...`。 +* 寫一個「路徑操作裝飾器」,像是 `@app.get("/")`。 +* 定義一個「路徑操作函式」;例如,`def root(): ...`。 * 使用命令 `fastapi dev` 執行開發伺服器。 +* 可選:使用 `fastapi deploy` 部署你的應用程式。 diff --git a/docs/zh-hant/docs/tutorial/index.md b/docs/zh-hant/docs/tutorial/index.md index ae0056f52..5e1961fd7 100644 --- a/docs/zh-hant/docs/tutorial/index.md +++ b/docs/zh-hant/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# 教學 - 使用者指南 +# 教學 - 使用者指南 { #tutorial-user-guide } 本教學將一步一步展示如何使用 **FastAPI** 及其大多數功能。 @@ -6,7 +6,7 @@ 它也被設計成可作為未來的參考,讓你隨時回來查看所需的內容。 -## 運行程式碼 +## 運行程式碼 { #run-the-code } 所有程式碼區塊都可以直接複製和使用(它們實際上是經過測試的 Python 檔案)。 @@ -15,48 +15,39 @@
```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 +$ fastapi dev main.py - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ + FastAPI Starting development server 🚀 -INFO Importing module main -INFO Found importable FastAPI app + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ + module 🐍 main.py -INFO Using import string main:app + code Importing the FastAPI app object from the module with + the following code: - ╭────────── 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 - │ │ - ╰─────────────────────────────────────────────────────╯ + from main import app -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. - + 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. ```
@@ -67,7 +58,7 @@ $ fastapi dev -/// note +/// note | 注意 -當你使用 `pip install "fastapi[standard]"` 安裝時,會包含一些預設的可選標準依賴項。 +當你使用 `pip install "fastapi[standard]"` 安裝時,會包含一些預設的可選標準依賴項,其中包括 `fastapi-cloud-cli`,它可以讓你部署到 FastAPI Cloud。 -如果你不想包含那些可選的依賴項,你可以使用 `pip install fastapi` 來安裝。 +如果你不想包含那些可選的依賴項,你可以改為安裝 `pip install fastapi`。 + +如果你想安裝標準依賴項,但不包含 `fastapi-cloud-cli`,可以使用 `pip install "fastapi[standard-no-fastapi-cloud-cli]"` 安裝。 /// -## 進階使用者指南 +## 進階使用者指南 { #advanced-user-guide } -還有一個**進階使用者指南**你可以稍後閱讀。 +還有一個**進階使用者指南**你可以在讀完這個**教學 - 使用者指南**後再閱讀。 **進階使用者指南**建立在這個教學之上,使用相同的概念,並教你一些額外的功能。 diff --git a/docs/zh-hant/docs/tutorial/query-param-models.md b/docs/zh-hant/docs/tutorial/query-param-models.md index 76ee74016..e36028199 100644 --- a/docs/zh-hant/docs/tutorial/query-param-models.md +++ b/docs/zh-hant/docs/tutorial/query-param-models.md @@ -1,4 +1,4 @@ -# 查詢參數模型 +# 查詢參數模型 { #query-parameter-models } 如果你有一組具有相關性的**查詢參數**,你可以建立一個 **Pydantic 模型**來聲明它們。 @@ -10,7 +10,7 @@ FastAPI 從 `0.115.0` 版本開始支援這個特性。🤓 /// -## 使用 Pydantic 模型的查詢參數 +## 使用 Pydantic 模型的查詢參數 { #query-parameters-with-a-pydantic-model } 在一個 **Pydantic 模型**中聲明你需要的**查詢參數**,然後將參數聲明為 `Query`: @@ -18,7 +18,7 @@ FastAPI 從 `0.115.0` 版本開始支援這個特性。🤓 **FastAPI** 將會從請求的**查詢參數**中**提取**出**每個欄位**的資料,並將其提供給你定義的 Pydantic 模型。 -## 查看文件 +## 查看文件 { #check-the-docs } 你可以在 `/docs` 頁面的 UI 中查看查詢參數: @@ -26,7 +26,7 @@ FastAPI 從 `0.115.0` 版本開始支援這個特性。🤓 -## 禁止額外的查詢參數 +## 禁止額外的查詢參數 { #forbid-extra-query-parameters } 在一些特殊的使用場景中(可能不是很常見),你可能希望**限制**你要收到的查詢參數。 @@ -57,7 +57,7 @@ https://example.com/items/?limit=10&tool=plumbus } ``` -## 總結 +## 總結 { #summary } 你可以使用 **Pydantic 模型**在 **FastAPI** 中聲明**查詢參數**。😎 diff --git a/docs/zh-hant/docs/virtual-environments.md b/docs/zh-hant/docs/virtual-environments.md index d8e31e08e..acd1d914e 100644 --- a/docs/zh-hant/docs/virtual-environments.md +++ b/docs/zh-hant/docs/virtual-environments.md @@ -1,4 +1,4 @@ -# 虛擬環境 +# 虛擬環境 { #virtual-environments } 當你在 Python 專案中工作時,你可能會需要使用一個**虛擬環境**(或類似的機制)來隔離你為每個專案安裝的套件。 @@ -26,7 +26,7 @@ /// -## 建立一個專案 +## 建立一個專案 { #create-a-project } 首先,為你的專案建立一個目錄。 @@ -51,7 +51,7 @@ $ cd awesome-project -## 建立一個虛擬環境 +## 建立一個虛擬環境 { #create-a-virtual-environment } 在開始一個 Python 專案的**第一時間**,**在你的專案內部**建立一個虛擬環境。 @@ -114,7 +114,7 @@ $ uv venv /// -## 啟動虛擬環境 +## 啟動虛擬環境 { #activate-the-virtual-environment } 啟動新的虛擬環境來確保你運行的任何 Python 指令或安裝的套件都能使用到它。 @@ -166,11 +166,11 @@ $ source .venv/Scripts/activate 每次你在這個環境中安裝一個**新的套件**時,都需要**重新啟動**這個環境。 -這麼做確保了當你使用一個由這個套件安裝的**終端(CLI)程式**時,你使用的是你的虛擬環境中的程式,而不是全域安裝、可能版本不同的程式。 +這麼做確保了當你使用一個由這個套件安裝的**終端(CLI)程式**時,你使用的是你的虛擬環境中的程式,而不是全域安裝、可能版本不同的程式。 /// -## 檢查虛擬環境是否啟動 +## 檢查虛擬環境是否啟動 { #check-the-virtual-environment-is-active } 檢查虛擬環境是否啟動(前面的指令是否生效)。 @@ -212,7 +212,7 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python //// -## 升級 `pip` +## 升級 `pip` { #upgrade-pip } /// tip @@ -242,7 +242,27 @@ $ python -m pip install --upgrade pip -## 加入 `.gitignore` +/// tip | 注意 + +有時你在嘗試升級 pip 時,可能會遇到 **`No module named pip`** 的錯誤。 + +如果發生這種情況,請用下面的指令安裝並升級 pip: + +
+ +```console +$ python -m ensurepip --upgrade + +---> 100% +``` + +
+ +此指令會在未安裝 pip 時為你安裝它,並確保安裝的 pip 版本至少與 `ensurepip` 所提供的版本一樣新。 + +/// + +## 加入 `.gitignore` { #add-gitignore } 如果你使用 **Git**(這是你應該使用的),加入一個 `.gitignore` 檔案來排除你的 `.venv` 中的所有內容。 @@ -282,7 +302,7 @@ $ echo "*" > .venv/.gitignore /// -## 安裝套件 +## 安裝套件 { #install-packages } 在啟用虛擬環境後,你可以在其中安裝套件。 @@ -294,7 +314,7 @@ $ echo "*" > .venv/.gitignore /// -### 直接安裝套件 +### 直接安裝套件 { #install-packages-directly } 如果你急於安裝,不想使用檔案來聲明專案的套件依賴,你可以直接安裝它們。 @@ -333,7 +353,7 @@ $ uv pip install "fastapi[standard]" //// -### 從 `requirements.txt` 安裝 +### 從 `requirements.txt` 安裝 { #install-from-requirements-txt } 如果你有一個 `requirements.txt` 檔案,你可以使用它來安裝其中的套件。 @@ -376,7 +396,7 @@ pydantic==2.8.0 /// -## 執行程式 +## 執行程式 { #run-your-program } 在啟用虛擬環境後,你可以執行你的程式,它將使用虛擬環境中的 Python 和你在其中安裝的套件。 @@ -390,7 +410,7 @@ Hello World -## 設定編輯器 +## 設定編輯器 { #configure-your-editor } 你可能會用到編輯器,請確保設定它使用你建立的相同虛擬環境(它可能會自動偵測到),以便你可以獲得自動完成和内嵌錯誤提示。 @@ -405,7 +425,7 @@ Hello World /// -## 退出虛擬環境 +## 退出虛擬環境 { #deactivate-the-virtual-environment } 當你完成工作後,你可以**退出**虛擬環境。 @@ -419,7 +439,7 @@ $ deactivate 這樣,當你執行 `python` 時它不會嘗試從已安裝套件的虛擬環境中執行。 -## 開始工作 +## 開始工作 { #ready-to-work } 現在你已經準備好開始你的工作了。 @@ -433,7 +453,7 @@ $ deactivate /// -## 為什麼要使用虛擬環境 +## 為什麼要使用虛擬環境 { #why-virtual-environments } 你需要安裝 Python 才能使用 FastAPI。 @@ -443,7 +463,7 @@ $ deactivate 然而,如果你直接使用 `pip`,套件將會安裝在你的**全域 Python 環境**中(即 Python 的全域安裝)。 -### 存在的問題 +### 存在的問題 { #the-problem } 那麼,在全域 Python 環境中安裝套件有什麼問題呢? @@ -526,7 +546,7 @@ Python 套件在推出**新版本**時通常會儘量**避免破壞性更改** 此外,取決於你的操作系統(例如 Linux、Windows、macOS),它可能已經預先安裝了 Python。在這種情況下,它可能已經有一些系統所需的套件和特定版本。如果你在全域 Python 環境中安裝套件,可能會**破壞**某些隨作業系統一起安裝的程式。 -## 套件安裝在哪裡 +## 套件安裝在哪裡 { #where-are-packages-installed } 當你安裝 Python 時,它會在你的電腦中建立一些目錄並放置一些檔案。 @@ -552,7 +572,7 @@ $ pip install "fastapi[standard]" 預設情況下,這些下載和解壓的檔案會放置於隨 Python 安裝的目錄中,即**全域環境**。 -## 什麼是虛擬環境 +## 什麼是虛擬環境 { #what-are-virtual-environments } 解決套件都安裝在全域環境中的問題方法是為你所做的每個專案使用一個**虛擬環境**。 @@ -577,7 +597,7 @@ flowchart TB stone-project ~~~ azkaban-project ``` -## 啟用虛擬環境意味著什麼 +## 啟用虛擬環境意味著什麼 { #what-does-activating-a-virtual-environment-mean } 當你啟用了虛擬環境,例如: @@ -714,7 +734,7 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python 啟用虛擬環境還會改變其他一些內容,但這是它所做的最重要的事情之一。 -## 檢查虛擬環境 +## 檢查虛擬環境 { #checking-a-virtual-environment } 當你檢查虛擬環境是否啟動時,例如: @@ -766,7 +786,7 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python /// -## 為什麼要停用虛擬環境 +## 為什麼要停用虛擬環境 { #why-deactivate-a-virtual-environment } 例如,你可能正在一個專案 `philosophers-stone` 上工作,**啟動了該虛擬環境**,安裝了套件並使用了該環境, @@ -820,7 +840,7 @@ I solemnly swear 🐺 -## 替代方案 +## 替代方案 { #alternatives } 這是一個簡單的指南,幫助你入門並教會你如何理解一切**底層**的原理。 @@ -837,7 +857,7 @@ I solemnly swear 🐺 * 確保你有一個**精確**的套件和版本集合來安裝,包括它們的依賴項,這樣你可以確保專案在生產環境中運行的狀態與開發時在你的電腦上運行的狀態完全相同,這被稱為**鎖定** * 還有很多其他功能 -## 結論 +## 結論 { #conclusion } 如果你讀過並理解了所有這些,現在**你對虛擬環境的了解已超過許多開發者**。🤓 From f23ea3bd957df9add546af378407b058473b453b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 15:33:24 +0000 Subject: [PATCH 006/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f52be20d3..9f70eccb5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for zh-hant (update-outdated). PR [#14825](https://github.com/fastapi/fastapi/pull/14825) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for uk (update-outdated). PR [#14822](https://github.com/fastapi/fastapi/pull/14822) by [@tiangolo](https://github.com/tiangolo). * 🔨 Update docs and translations scripts, enable Turkish. PR [#14824](https://github.com/fastapi/fastapi/pull/14824) by [@tiangolo](https://github.com/tiangolo). From 6ff8ff5b57bdbfb93030899091fe8219a722a74d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 07:34:19 -0800 Subject: [PATCH 007/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20fr=20(update-outdated)=20(#14826)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/fr/docs/advanced/additional-responses.md | 38 +- .../docs/advanced/additional-status-codes.md | 20 +- docs/fr/docs/advanced/index.md | 18 +- .../path-operation-advanced-configuration.md | 114 ++-- docs/fr/docs/advanced/response-directly.md | 30 +- docs/fr/docs/benchmarks.md | 36 +- docs/fr/docs/deployment/docker.md | 599 +++++++++++++++--- docs/fr/docs/deployment/https.md | 247 ++++++-- docs/fr/docs/deployment/index.md | 8 +- docs/fr/docs/deployment/versions.md | 76 +-- docs/fr/docs/index.md | 399 +++++++----- docs/fr/docs/learn/index.md | 4 +- docs/fr/docs/project-generation.md | 102 +-- docs/fr/docs/python-types.md | 413 ++++++++---- docs/fr/docs/tutorial/background-tasks.md | 50 +- docs/fr/docs/tutorial/body-multiple-params.md | 88 +-- docs/fr/docs/tutorial/body.md | 80 +-- docs/fr/docs/tutorial/debugging.md | 24 +- docs/fr/docs/tutorial/first-steps.md | 335 +++++----- docs/fr/docs/tutorial/index.md | 78 ++- .../path-params-numeric-validations.md | 49 +- docs/fr/docs/tutorial/path-params.md | 225 ++++--- .../tutorial/query-params-str-validations.md | 439 +++++++++---- docs/fr/docs/tutorial/query-params.md | 133 ++-- 24 files changed, 2303 insertions(+), 1302 deletions(-) diff --git a/docs/fr/docs/advanced/additional-responses.md b/docs/fr/docs/advanced/additional-responses.md index 38527aad3..dabcded52 100644 --- a/docs/fr/docs/advanced/additional-responses.md +++ b/docs/fr/docs/advanced/additional-responses.md @@ -1,6 +1,6 @@ -# Réponses supplémentaires dans OpenAPI +# Réponses supplémentaires dans OpenAPI { #additional-responses-in-openapi } -/// warning | Attention +/// warning | Alertes Ceci concerne un sujet plutôt avancé. @@ -14,9 +14,9 @@ Ces réponses supplémentaires seront incluses dans le schéma OpenAPI, elles ap Mais pour ces réponses supplémentaires, vous devez vous assurer de renvoyer directement une `Response` comme `JSONResponse`, avec votre code HTTP et votre contenu. -## Réponse supplémentaire avec `model` +## Réponse supplémentaire avec `model` { #additional-response-with-model } -Vous pouvez ajouter à votre décorateur de *paramètre de chemin* un paramètre `responses`. +Vous pouvez passer à vos décorateurs de *chemin d'accès* un paramètre `responses`. Il prend comme valeur un `dict` dont les clés sont des codes HTTP pour chaque réponse, comme `200`, et la valeur de ces clés sont d'autres `dict` avec des informations pour chacun d'eux. @@ -26,7 +26,7 @@ Chacun de ces `dict` de réponse peut avoir une clé `model`, contenant un modè Par exemple, pour déclarer une autre réponse avec un code HTTP `404` et un modèle Pydantic `Message`, vous pouvez écrire : -{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} /// note | Remarque @@ -49,7 +49,7 @@ Le bon endroit est : /// -Les réponses générées au format OpenAPI pour cette *opération de chemin* seront : +Les réponses générées au format OpenAPI pour ce *chemin d'accès* seront : ```JSON hl_lines="3-12" { @@ -169,13 +169,13 @@ Les schémas sont référencés à un autre endroit du modèle OpenAPI : } ``` -## Types de médias supplémentaires pour la réponse principale +## Types de médias supplémentaires pour la réponse principale { #additional-media-types-for-the-main-response } Vous pouvez utiliser ce même paramètre `responses` pour ajouter différents types de médias pour la même réponse principale. -Par exemple, vous pouvez ajouter un type de média supplémentaire `image/png`, en déclarant que votre *opération de chemin* peut renvoyer un objet JSON (avec le type de média `application/json`) ou une image PNG : +Par exemple, vous pouvez ajouter un type de média supplémentaire `image/png`, en déclarant que votre *chemin d'accès* peut renvoyer un objet JSON (avec le type de média `application/json`) ou une image PNG : -{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} /// note | Remarque @@ -191,7 +191,7 @@ Mais si vous avez spécifié une classe de réponse personnalisée avec `None` c /// -## Combinaison d'informations +## Combiner les informations { #combining-information } Vous pouvez également combiner des informations de réponse provenant de plusieurs endroits, y compris les paramètres `response_model`, `status_code` et `responses`. @@ -203,17 +203,17 @@ Par exemple, vous pouvez déclarer une réponse avec un code HTTP `404` qui util Et une réponse avec un code HTTP `200` qui utilise votre `response_model`, mais inclut un `example` personnalisé : -{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} Tout sera combiné et inclus dans votre OpenAPI, et affiché dans la documentation de l'API : -## Combinez les réponses prédéfinies et les réponses personnalisées +## Combinez les réponses prédéfinies et les réponses personnalisées { #combine-predefined-responses-and-custom-ones } -Vous voulez peut-être avoir des réponses prédéfinies qui s'appliquent à de nombreux *paramètre de chemin*, mais vous souhaitez les combiner avec des réponses personnalisées nécessaires à chaque *opération de chemin*. +Vous voulez peut-être avoir des réponses prédéfinies qui s'appliquent à de nombreux *chemins d'accès*, mais vous souhaitez les combiner avec des réponses personnalisées nécessaires à chaque *chemin d'accès*. -Dans ces cas, vous pouvez utiliser la technique Python "d'affection par décomposition" (appelé _unpacking_ en anglais) d'un `dict` avec `**dict_to_unpack` : +Dans ces cas, vous pouvez utiliser la technique Python « unpacking » d'un `dict` avec `**dict_to_unpack` : ```Python old_dict = { @@ -233,15 +233,15 @@ Ici, `new_dict` contiendra toutes les paires clé-valeur de `old_dict` plus la n } ``` -Vous pouvez utiliser cette technique pour réutiliser certaines réponses prédéfinies dans vos *paramètres de chemin* et les combiner avec des réponses personnalisées supplémentaires. +Vous pouvez utiliser cette technique pour réutiliser certaines réponses prédéfinies dans vos *chemins d'accès* et les combiner avec des réponses personnalisées supplémentaires. Par exemple: -{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} -## Plus d'informations sur les réponses OpenAPI +## Plus d'informations sur les réponses OpenAPI { #more-information-about-openapi-responses } Pour voir exactement ce que vous pouvez inclure dans les réponses, vous pouvez consulter ces sections dans la spécification OpenAPI : -* Objet Responses de OpenAPI , il inclut le `Response Object`. -* Objet Response de OpenAPI , vous pouvez inclure n'importe quoi directement dans chaque réponse à l'intérieur de votre paramètre `responses`. Y compris `description`, `headers`, `content` (à l'intérieur de cela, vous déclarez différents types de médias et schémas JSON) et `links`. +* Objet Responses de OpenAPI, il inclut le `Response Object`. +* Objet Response de OpenAPI, vous pouvez inclure n'importe quoi directement dans chaque réponse à l'intérieur de votre paramètre `responses`. Y compris `description`, `headers`, `content` (à l'intérieur de cela, vous déclarez différents types de médias et schémas JSON) et `links`. diff --git a/docs/fr/docs/advanced/additional-status-codes.md b/docs/fr/docs/advanced/additional-status-codes.md index dde6b9a63..b2befffa8 100644 --- a/docs/fr/docs/advanced/additional-status-codes.md +++ b/docs/fr/docs/advanced/additional-status-codes.md @@ -1,28 +1,28 @@ -# Codes HTTP supplémentaires +# Codes HTTP supplémentaires { #additional-status-codes } Par défaut, **FastAPI** renverra les réponses à l'aide d'une structure de données `JSONResponse`, en plaçant la réponse de votre *chemin d'accès* à l'intérieur de cette `JSONResponse`. Il utilisera le code HTTP par défaut ou celui que vous avez défini dans votre *chemin d'accès*. -## Codes HTTP supplémentaires +## Codes HTTP supplémentaires { #additional-status-codes_1 } Si vous souhaitez renvoyer des codes HTTP supplémentaires en plus du code principal, vous pouvez le faire en renvoyant directement une `Response`, comme une `JSONResponse`, et en définissant directement le code HTTP supplémentaire. -Par exemple, disons que vous voulez avoir un *chemin d'accès* qui permet de mettre à jour les éléments et renvoie les codes HTTP 200 "OK" en cas de succès. +Par exemple, disons que vous voulez avoir un *chemin d'accès* qui permet de mettre à jour les éléments et renvoie les codes HTTP 200 « OK » en cas de succès. -Mais vous voulez aussi qu'il accepte de nouveaux éléments. Et lorsque les éléments n'existaient pas auparavant, il les crée et renvoie un code HTTP de 201 "Créé". +Mais vous voulez aussi qu'il accepte de nouveaux éléments. Et lorsque les éléments n'existaient pas auparavant, il les crée et renvoie un code HTTP de 201 « Créé ». Pour y parvenir, importez `JSONResponse` et renvoyez-y directement votre contenu, en définissant le `status_code` que vous souhaitez : -{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *} +{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} -/// warning | Attention +/// warning | Alertes Lorsque vous renvoyez une `Response` directement, comme dans l'exemple ci-dessus, elle sera renvoyée directement. Elle ne sera pas sérialisée avec un modèle. -Assurez-vous qu'il contient les données souhaitées et que les valeurs soient dans un format JSON valides (si vous utilisez une `JSONResponse`). +Assurez-vous qu'il contient les données souhaitées et que les valeurs sont dans un format JSON valide (si vous utilisez une `JSONResponse`). /// @@ -30,12 +30,12 @@ Assurez-vous qu'il contient les données souhaitées et que les valeurs soient d Vous pouvez également utiliser `from starlette.responses import JSONResponse`. -Pour plus de commodités, **FastAPI** fournit les objets `starlette.responses` sous forme d'un alias accessible par `fastapi.responses`. Mais la plupart des réponses disponibles proviennent directement de Starlette. Il en est de même avec l'objet `statut`. +Pour plus de commodités, **FastAPI** fournit les objets `starlette.responses` sous forme d'un alias accessible par `fastapi.responses`. Mais la plupart des réponses disponibles proviennent directement de Starlette. Il en est de même avec `status`. /// -## Documents OpenAPI et API +## Documents OpenAPI et API { #openapi-and-api-docs } Si vous renvoyez directement des codes HTTP et des réponses supplémentaires, ils ne seront pas inclus dans le schéma OpenAPI (la documentation de l'API), car FastAPI n'a aucun moyen de savoir à l'avance ce que vous allez renvoyer. -Mais vous pouvez documenter cela dans votre code, en utilisant : [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}. +Mais vous pouvez documenter cela dans votre code, en utilisant : [Réponses supplémentaires](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/fr/docs/advanced/index.md b/docs/fr/docs/advanced/index.md index d9d8ad8e6..a2f9d3b1b 100644 --- a/docs/fr/docs/advanced/index.md +++ b/docs/fr/docs/advanced/index.md @@ -1,27 +1,21 @@ -# Guide de l'utilisateur avancé +# Guide de l'utilisateur avancé { #advanced-user-guide } -## Caractéristiques supplémentaires +## Caractéristiques supplémentaires { #additional-features } Le [Tutoriel - Guide de l'utilisateur](../tutorial/index.md){.internal-link target=_blank} devrait suffire à vous faire découvrir toutes les fonctionnalités principales de **FastAPI**. Dans les sections suivantes, vous verrez des options, configurations et fonctionnalités supplémentaires. -/// note | Remarque +/// tip | Astuce -Les sections de ce chapitre ne sont **pas nécessairement "avancées"**. +Les sections suivantes ne sont **pas nécessairement « avancées »**. -Et il est possible que pour votre cas d'utilisation, la solution se trouve dans l'un d'entre eux. +Et il est possible que, pour votre cas d'utilisation, la solution se trouve dans l'une d'entre elles. /// -## Lisez d'abord le didacticiel +## Lire d'abord le tutoriel { #read-the-tutorial-first } Vous pouvez utiliser la plupart des fonctionnalités de **FastAPI** grâce aux connaissances du [Tutoriel - Guide de l'utilisateur](../tutorial/index.md){.internal-link target=_blank}. Et les sections suivantes supposent que vous l'avez lu et que vous en connaissez les idées principales. - -## Cours TestDriven.io - -Si vous souhaitez suivre un cours pour débutants avancés pour compléter cette section de la documentation, vous pouvez consulter : Développement piloté par les tests avec FastAPI et Docker par **TestDriven.io**. - -10 % de tous les bénéfices de ce cours sont reversés au développement de **FastAPI**. 🎉 😄 diff --git a/docs/fr/docs/advanced/path-operation-advanced-configuration.md b/docs/fr/docs/advanced/path-operation-advanced-configuration.md index 7daf0fc65..fc88f3363 100644 --- a/docs/fr/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/fr/docs/advanced/path-operation-advanced-configuration.md @@ -1,106 +1,108 @@ -# Configuration avancée des paramètres de chemin +# Configuration avancée des chemins d'accès { #path-operation-advanced-configuration } -## ID d'opération OpenAPI +## ID d’opération OpenAPI { #openapi-operationid } -/// warning | Attention +/// warning | Alertes -Si vous n'êtes pas un "expert" en OpenAPI, vous n'en avez probablement pas besoin. +Si vous n’êtes pas un « expert » d’OpenAPI, vous n’en avez probablement pas besoin. /// -Dans OpenAPI, les chemins sont des ressources, tels que /users/ ou /items/, exposées par votre API, et les opérations sont les méthodes HTTP utilisées pour manipuler ces chemins, telles que GET, POST ou DELETE. Les operationId sont des chaînes uniques facultatives utilisées pour identifier une opération d'un chemin. Vous pouvez définir l'OpenAPI `operationId` à utiliser dans votre *opération de chemin* avec le paramètre `operation_id`. +Vous pouvez définir l’OpenAPI `operationId` à utiliser dans votre chemin d’accès avec le paramètre `operation_id`. -Vous devez vous assurer qu'il est unique pour chaque opération. +Vous devez vous assurer qu’il est unique pour chaque opération. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} -### Utilisation du nom *path operation function* comme operationId +### Utiliser le nom de la fonction de chemin d’accès comme operationId { #using-the-path-operation-function-name-as-the-operationid } -Si vous souhaitez utiliser les noms de fonction de vos API comme `operationId`, vous pouvez les parcourir tous et remplacer chaque `operation_id` de l'*opération de chemin* en utilisant leur `APIRoute.name`. +Si vous souhaitez utiliser les noms de fonction de vos API comme `operationId`, vous pouvez les parcourir tous et remplacer l’`operation_id` de chaque chemin d’accès en utilisant leur `APIRoute.name`. -Vous devriez le faire après avoir ajouté toutes vos *paramètres de chemin*. +Vous devez le faire après avoir ajouté tous vos chemins d’accès. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12:21,24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} /// tip | Astuce -Si vous appelez manuellement `app.openapi()`, vous devez mettre à jour les `operationId` avant. +Si vous appelez manuellement `app.openapi()`, vous devez mettre à jour les `operationId` avant cela. /// -/// warning | Attention +/// warning | Alertes -Pour faire cela, vous devez vous assurer que chacun de vos *chemin* ait un nom unique. +Si vous faites cela, vous devez vous assurer que chacune de vos fonctions de chemin d’accès a un nom unique. -Même s'ils se trouvent dans des modules différents (fichiers Python). +Même si elles se trouvent dans des modules différents (fichiers Python). /// -## Exclusion d'OpenAPI +## Exclusion d’OpenAPI { #exclude-from-openapi } -Pour exclure un *chemin* du schéma OpenAPI généré (et donc des systèmes de documentation automatiques), utilisez le paramètre `include_in_schema` et assignez-lui la valeur `False` : +Pour exclure un chemin d’accès du schéma OpenAPI généré (et donc des systèmes de documentation automatiques), utilisez le paramètre `include_in_schema` et définissez-le à `False` : -{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} -## Description avancée de docstring +## Description avancée depuis la docstring { #advanced-description-from-docstring } -Vous pouvez limiter le texte utilisé de la docstring d'une *fonction de chemin* qui sera affiché sur OpenAPI. +Vous pouvez limiter les lignes utilisées de la docstring d’une fonction de chemin d’accès pour OpenAPI. -L'ajout d'un `\f` (un caractère d'échappement "form feed") va permettre à **FastAPI** de tronquer la sortie utilisée pour OpenAPI à ce stade. +L’ajout d’un `\f` (un caractère « saut de page » échappé) amène **FastAPI** à tronquer la sortie utilisée pour OpenAPI à cet endroit. -Il n'apparaîtra pas dans la documentation, mais d'autres outils (tel que Sphinx) pourront utiliser le reste. +Cela n’apparaîtra pas dans la documentation, mais d’autres outils (comme Sphinx) pourront utiliser le reste. -{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} -## Réponses supplémentaires +## Réponses supplémentaires { #additional-responses } -Vous avez probablement vu comment déclarer le `response_model` et le `status_code` pour une *opération de chemin*. +Vous avez probablement vu comment déclarer le `response_model` et le `status_code` pour un chemin d’accès. -Cela définit les métadonnées sur la réponse principale d'une *opération de chemin*. +Cela définit les métadonnées sur la réponse principale d’un chemin d’accès. Vous pouvez également déclarer des réponses supplémentaires avec leurs modèles, codes de statut, etc. -Il y a un chapitre entier ici dans la documentation à ce sujet, vous pouvez le lire sur [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}. +Il y a un chapitre entier dans la documentation à ce sujet, vous pouvez le lire dans [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}. -## OpenAPI supplémentaire +## OpenAPI supplémentaire { #openapi-extra } -Lorsque vous déclarez un *chemin* dans votre application, **FastAPI** génère automatiquement les métadonnées concernant ce *chemin* à inclure dans le schéma OpenAPI. +Lorsque vous déclarez un chemin d’accès dans votre application, **FastAPI** génère automatiquement les métadonnées pertinentes à propos de ce chemin d’accès à inclure dans le schéma OpenAPI. /// note | Détails techniques -La spécification OpenAPI appelle ces métadonnées des Objets d'opération. +Dans la spécification OpenAPI, cela s’appelle l’objet Operation. /// -Il contient toutes les informations sur le *chemin* et est utilisé pour générer automatiquement la documentation. +Il contient toutes les informations sur le chemin d’accès et est utilisé pour générer la documentation automatique. Il inclut les `tags`, `parameters`, `requestBody`, `responses`, etc. -Ce schéma OpenAPI spécifique aux *operations* est normalement généré automatiquement par **FastAPI**, mais vous pouvez également l'étendre. +Ce schéma OpenAPI spécifique à un chemin d’accès est normalement généré automatiquement par **FastAPI**, mais vous pouvez également l’étendre. /// tip | Astuce -Si vous avez seulement besoin de déclarer des réponses supplémentaires, un moyen plus pratique de le faire est d'utiliser les [réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}. +Ceci est un point d’extension de bas niveau. + +Si vous avez seulement besoin de déclarer des réponses supplémentaires, un moyen plus pratique de le faire est d’utiliser [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}. /// -Vous pouvez étendre le schéma OpenAPI pour une *opération de chemin* en utilisant le paramètre `openapi_extra`. +Vous pouvez étendre le schéma OpenAPI pour un chemin d’accès en utilisant le paramètre `openapi_extra`. -### Extensions OpenAPI +### Extensions OpenAPI { #openapi-extensions } -Cet `openapi_extra` peut être utile, par exemple, pour déclarer [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) : +Cet `openapi_extra` peut être utile, par exemple, pour déclarer des [Extensions OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) : -{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} -Si vous ouvrez la documentation automatique de l'API, votre extension apparaîtra au bas du *chemin* spécifique. +Si vous ouvrez la documentation automatique de l’API, votre extension apparaîtra en bas du chemin d’accès spécifique. -Et dans le fichier openapi généré (`/openapi.json`), vous verrez également votre extension dans le cadre du *chemin* spécifique : +Et si vous consultez l’OpenAPI résultant (à `/openapi.json` dans votre API), vous verrez également votre extension comme partie du chemin d’accès spécifique : ```JSON hl_lines="22" { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": { "title": "FastAPI", "version": "0.1.0" @@ -127,44 +129,44 @@ Et dans le fichier openapi généré (`/openapi.json`), vous verrez également v } ``` -### Personnalisation du Schéma OpenAPI pour un chemin +### Personnaliser le schéma OpenAPI d’un chemin d’accès { #custom-openapi-path-operation-schema } -Le dictionnaire contenu dans la variable `openapi_extra` sera fusionné avec le schéma OpenAPI généré automatiquement pour l'*opération de chemin*. +Le dictionnaire dans `openapi_extra` sera fusionné en profondeur avec le schéma OpenAPI généré automatiquement pour le chemin d’accès. Ainsi, vous pouvez ajouter des données supplémentaires au schéma généré automatiquement. -Par exemple, vous pouvez décider de lire et de valider la requête avec votre propre code, sans utiliser les fonctionnalités automatiques de validation proposée par Pydantic, mais vous pouvez toujours définir la requête dans le schéma OpenAPI. +Par exemple, vous pourriez décider de lire et de valider la requête avec votre propre code, sans utiliser les fonctionnalités automatiques de FastAPI avec Pydantic, mais vous pourriez tout de même vouloir définir la requête dans le schéma OpenAPI. -Vous pouvez le faire avec `openapi_extra` : +Vous pourriez le faire avec `openapi_extra` : -{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[20:37,39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} -Dans cet exemple, nous n'avons déclaré aucun modèle Pydantic. En fait, le corps de la requête n'est même pas parsé en tant que JSON, il est lu directement en tant que `bytes`, et la fonction `magic_data_reader()` serait chargé de l'analyser d'une manière ou d'une autre. +Dans cet exemple, nous n’avons déclaré aucun modèle Pydantic. En fait, le corps de la requête n’est même pas parsé en JSON, il est lu directement en tant que `bytes`, et la fonction `magic_data_reader()` serait chargée de l’analyser d’une manière ou d’une autre. Néanmoins, nous pouvons déclarer le schéma attendu pour le corps de la requête. -### Type de contenu OpenAPI personnalisé +### Type de contenu OpenAPI personnalisé { #custom-openapi-content-type } -En utilisant cette même astuce, vous pouvez utiliser un modèle Pydantic pour définir le schéma JSON qui est ensuite inclus dans la section de schéma OpenAPI personnalisée pour le *chemin* concerné. +En utilisant cette même astuce, vous pourriez utiliser un modèle Pydantic pour définir le JSON Schema qui est ensuite inclus dans la section de schéma OpenAPI personnalisée pour le chemin d’accès. -Et vous pouvez le faire même si le type de données dans la requête n'est pas au format JSON. +Et vous pourriez le faire même si le type de données dans la requête n’est pas du JSON. -Dans cet exemple, nous n'utilisons pas les fonctionnalités de FastAPI pour extraire le schéma JSON des modèles Pydantic ni la validation automatique pour JSON. En fait, nous déclarons le type de contenu de la requête en tant que YAML, et non JSON : +Par exemple, dans cette application nous n’utilisons pas la fonctionnalité intégrée de FastAPI pour extraire le JSON Schema des modèles Pydantic ni la validation automatique pour le JSON. En fait, nous déclarons le type de contenu de la requête comme YAML, pas JSON : -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22,24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} -Néanmoins, bien que nous n'utilisions pas la fonctionnalité par défaut, nous utilisons toujours un modèle Pydantic pour générer manuellement le schéma JSON pour les données que nous souhaitons recevoir en YAML. +Néanmoins, bien que nous n’utilisions pas la fonctionnalité intégrée par défaut, nous utilisons toujours un modèle Pydantic pour générer manuellement le JSON Schema pour les données que nous souhaitons recevoir en YAML. -Ensuite, nous utilisons directement la requête et extrayons son contenu en tant qu'octets. Cela signifie que FastAPI n'essaiera même pas d'analyser le payload de la requête en tant que JSON. +Ensuite, nous utilisons directement la requête et extrayons le corps en tant que `bytes`. Cela signifie que FastAPI n’essaiera même pas d’analyser le payload de la requête en JSON. -Et nous analysons directement ce contenu YAML, puis nous utilisons à nouveau le même modèle Pydantic pour valider le contenu YAML : +Ensuite, dans notre code, nous analysons directement ce contenu YAML, puis nous utilisons à nouveau le même modèle Pydantic pour valider le contenu YAML : -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} /// tip | Astuce Ici, nous réutilisons le même modèle Pydantic. -Mais nous aurions pu tout aussi bien pu le valider d'une autre manière. +Mais de la même manière, nous aurions pu le valider autrement. /// diff --git a/docs/fr/docs/advanced/response-directly.md b/docs/fr/docs/advanced/response-directly.md index 4ff883c77..f35c39c06 100644 --- a/docs/fr/docs/advanced/response-directly.md +++ b/docs/fr/docs/advanced/response-directly.md @@ -1,20 +1,20 @@ -# Renvoyer directement une réponse +# Renvoyer directement une réponse { #return-a-response-directly } -Lorsque vous créez une *opération de chemins* **FastAPI**, vous pouvez normalement retourner n'importe quelle donnée : un `dict`, une `list`, un modèle Pydantic, un modèle de base de données, etc. +Lorsque vous créez un *chemin d'accès* **FastAPI**, vous pouvez normalement retourner n'importe quelle donnée : un `dict`, une `list`, un modèle Pydantic, un modèle de base de données, etc. -Par défaut, **FastAPI** convertirait automatiquement cette valeur de retour en JSON en utilisant le `jsonable_encoder` expliqué dans [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}. +Par défaut, **FastAPI** convertirait automatiquement cette valeur de retour en JSON en utilisant le `jsonable_encoder` expliqué dans [Encodeur compatible JSON](../tutorial/encoder.md){.internal-link target=_blank}. Ensuite, en arrière-plan, il mettra ces données JSON-compatible (par exemple un `dict`) à l'intérieur d'un `JSONResponse` qui sera utilisé pour envoyer la réponse au client. -Mais vous pouvez retourner une `JSONResponse` directement à partir de vos *opérations de chemin*. +Mais vous pouvez retourner une `JSONResponse` directement à partir de vos *chemins d'accès*. Cela peut être utile, par exemple, pour retourner des en-têtes personnalisés ou des cookies. -## Renvoyer une `Response` +## Renvoyer une `Response` { #return-a-response } En fait, vous pouvez retourner n'importe quelle `Response` ou n'importe quelle sous-classe de celle-ci. -/// note | Remarque +/// tip | Astuce `JSONResponse` est elle-même une sous-classe de `Response`. @@ -24,27 +24,27 @@ Et quand vous retournez une `Response`, **FastAPI** la transmet directement. Elle ne fera aucune conversion de données avec les modèles Pydantic, elle ne convertira pas le contenu en un type quelconque. -Cela vous donne beaucoup de flexibilité. Vous pouvez retourner n'importe quel type de données, surcharger n'importe quelle déclaration ou validation de données. +Cela vous donne beaucoup de flexibilité. Vous pouvez retourner n'importe quel type de données, surcharger n'importe quelle déclaration ou validation de données, etc. -## Utiliser le `jsonable_encoder` dans une `Response` +## Utiliser le `jsonable_encoder` dans une `Response` { #using-the-jsonable-encoder-in-a-response } -Parce que **FastAPI** n'apporte aucune modification à une `Response` que vous retournez, vous devez vous assurer que son contenu est prêt à être utilisé (sérialisable). +Parce que **FastAPI** n'apporte aucune modification à une `Response` que vous retournez, vous devez vous assurer que son contenu est prêt pour cela. Par exemple, vous ne pouvez pas mettre un modèle Pydantic dans une `JSONResponse` sans d'abord le convertir en un `dict` avec tous les types de données (comme `datetime`, `UUID`, etc.) convertis en types compatibles avec JSON. -Pour ces cas, vous pouvez spécifier un appel à `jsonable_encoder` pour convertir vos données avant de les passer à une réponse : +Pour ces cas, vous pouvez utiliser le `jsonable_encoder` pour convertir vos données avant de les passer à une réponse : -{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} /// note | Détails techniques Vous pouvez aussi utiliser `from starlette.responses import JSONResponse`. -**FastAPI** fournit le même objet `starlette.responses` que `fastapi.responses` juste par commodité pour le développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette. +**FastAPI** fournit le même `starlette.responses` que `fastapi.responses` juste par commodité pour vous, le développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette. /// -## Renvoyer une `Response` personnalisée +## Renvoyer une `Response` personnalisée { #returning-a-custom-response } L'exemple ci-dessus montre toutes les parties dont vous avez besoin, mais il n'est pas encore très utile, car vous auriez pu retourner l'`item` directement, et **FastAPI** l'aurait mis dans une `JSONResponse` pour vous, en le convertissant en `dict`, etc. Tout cela par défaut. @@ -54,9 +54,9 @@ Disons que vous voulez retourner une réponse étant l'un des frameworks Python les plus rapides disponibles, seulement inférieur à Starlette et Uvicorn (tous deux utilisés au cœur de FastAPI). (*) +Les benchmarks indépendants de TechEmpower montrent que les applications **FastAPI** s’exécutant avec Uvicorn sont parmi les frameworks Python les plus rapides disponibles, seulement en dessous de Starlette et Uvicorn eux‑mêmes (tous deux utilisés en interne par FastAPI). -Mais en prêtant attention aux tests de performance et aux comparaisons, il faut tenir compte de ce qu'il suit. +Mais en prêtant attention aux tests de performance et aux comparaisons, vous devez tenir compte de ce qui suit. -## Tests de performance et rapidité +## Tests de performance et rapidité { #benchmarks-and-speed } Lorsque vous vérifiez les tests de performance, il est commun de voir plusieurs outils de différents types comparés comme équivalents. En particulier, on voit Uvicorn, Starlette et FastAPI comparés (parmi de nombreux autres outils). -Plus le problème résolu par un outil est simple, mieux seront les performances obtenues. Et la plupart des tests de performance ne prennent pas en compte les fonctionnalités additionnelles fournies par les outils. +Plus le problème résolu par un outil est simple, meilleures seront les performances obtenues. Et la plupart des tests de performance ne prennent pas en compte les fonctionnalités additionnelles fournies par les outils. La hiérarchie est la suivante : * **Uvicorn** : un serveur ASGI - * **Starlette** : (utilise Uvicorn) un micro-framework web - * **FastAPI**: (utilise Starlette) un micro-framework pour API disposant de fonctionnalités additionnelles pour la création d'API, avec la validation des données, etc. + * **Starlette** : (utilise Uvicorn) un microframework web + * **FastAPI**: (utilise Starlette) un microframework pour API disposant de fonctionnalités additionnelles pour la création d'API, avec la validation des données, etc. * **Uvicorn** : - * A les meilleures performances, étant donné qu'il n'a pas beaucoup de code mis-à-part le serveur en lui-même. - * On n'écrit pas une application avec uniquement Uvicorn. Cela signifie que le code devrait inclure plus ou moins, au minimum, tout le code offert par Starlette (ou **FastAPI**). Et si on fait cela, l'application finale apportera les mêmes complications que si on avait utilisé un framework et que l'on avait minimisé la quantité de code et de bugs. - * Si on compare Uvicorn, il faut le comparer à d'autre applications de serveurs comme Daphne, Hypercorn, uWSGI, etc. + * A les meilleures performances, étant donné qu'il n'a pas beaucoup de code mis à part le serveur en lui‑même. + * On n'écrit pas une application directement avec Uvicorn. Cela signifie que le code devrait inclure, au minimum, plus ou moins tout le code offert par Starlette (ou **FastAPI**). Et si on fait cela, l'application finale aura la même surcharge que si on avait utilisé un framework, tout en minimisant la quantité de code et les bugs. + * Si on compare Uvicorn, il faut le comparer à d'autres serveurs d'applications comme Daphne, Hypercorn, uWSGI, etc. * **Starlette** : - * A les seconde meilleures performances après Uvicorn. Starlette utilise en réalité Uvicorn. De ce fait, il ne peut qu’être plus "lent" qu'Uvicorn car il requiert l'exécution de plus de code. - * Cependant il nous apporte les outils pour construire une application web simple, avec un routage basé sur des chemins, etc. - * Si on compare Starlette, il faut le comparer à d'autres frameworks web (ou micorframework) comme Sanic, Flask, Django, etc. + * A les secondes meilleures performances après Uvicorn. En réalité, Starlette utilise Uvicorn. De ce fait, il ne peut qu’être plus « lent » qu'Uvicorn car il requiert l'exécution de plus de code. + * Cependant, il apporte les outils pour construire une application web simple, avec un routage basé sur des chemins, etc. + * Si on compare Starlette, il faut le comparer à d'autres frameworks web (ou microframeworks) comme Sanic, Flask, Django, etc. * **FastAPI** : - * Comme Starlette, FastAPI utilise Uvicorn et ne peut donc pas être plus rapide que ce dernier. - * FastAPI apporte des fonctionnalités supplémentaires à Starlette. Des fonctionnalités qui sont nécessaires presque systématiquement lors de la création d'une API, comme la validation des données, la sérialisation. En utilisant FastAPI, on obtient une documentation automatiquement (qui ne requiert aucune manipulation pour être mise en place). - * Si on n'utilisait pas FastAPI mais directement Starlette (ou un outil équivalent comme Sanic, Flask, Responder, etc) il faudrait implémenter la validation des données et la sérialisation par nous-même. Le résultat serait donc le même dans les deux cas mais du travail supplémentaire serait à réaliser avec Starlette, surtout en considérant que la validation des données et la sérialisation représentent la plus grande quantité de code à écrire dans une application. - * De ce fait, en utilisant FastAPI on minimise le temps de développement, les bugs, le nombre de lignes de code, et on obtient les mêmes performances (si ce n'est de meilleurs performances) que l'on aurait pu avoir sans ce framework (en ayant à implémenter de nombreuses fonctionnalités importantes par nous-mêmes). - * Si on compare FastAPI, il faut le comparer à d'autres frameworks web (ou ensemble d'outils) qui fournissent la validation des données, la sérialisation et la documentation, comme Flask-apispec, NestJS, Molten, etc. + * Comme Starlette utilise Uvicorn et ne peut donc pas être plus rapide que lui, **FastAPI** utilise Starlette et ne peut donc pas être plus rapide que lui. + * FastAPI apporte des fonctionnalités supplémentaires à Starlette. Des fonctionnalités dont vous avez presque toujours besoin lors de la création d'une API, comme la validation des données et la sérialisation. En l'utilisant, vous obtenez une documentation automatique « gratuitement » (la documentation automatique n'ajoute même pas de surcharge à l’exécution, elle est générée au démarrage). + * Si on n'utilisait pas FastAPI mais directement Starlette (ou un autre outil comme Sanic, Flask, Responder, etc.), il faudrait implémenter toute la validation des données et la sérialisation soi‑même. L'application finale aurait donc la même surcharge que si elle avait été construite avec FastAPI. Et dans de nombreux cas, cette validation des données et cette sérialisation représentent la plus grande quantité de code écrite dans les applications. + * De ce fait, en utilisant FastAPI on minimise le temps de développement, les bugs, le nombre de lignes de code, et on obtient probablement les mêmes performances (voire de meilleures performances) que l'on aurait pu avoir sans ce framework (car il aurait fallu tout implémenter dans votre code). + * Si on compare FastAPI, il faut le comparer à d'autres frameworks d’application web (ou ensembles d'outils) qui fournissent la validation des données, la sérialisation et la documentation, comme Flask-apispec, NestJS, Molten, etc. Des frameworks avec validation des données, sérialisation et documentation automatiques intégrées. diff --git a/docs/fr/docs/deployment/docker.md b/docs/fr/docs/deployment/docker.md index 05b597a2d..ec30f9607 100644 --- a/docs/fr/docs/deployment/docker.md +++ b/docs/fr/docs/deployment/docker.md @@ -1,75 +1,150 @@ -# Déployer avec Docker +# FastAPI dans des conteneurs - Docker { #fastapi-in-containers-docker } -Dans cette section, vous verrez des instructions et des liens vers des guides pour savoir comment : +Lors du déploiement d'applications FastAPI, une approche courante consiste à construire une **image de conteneur Linux**. C'est généralement fait avec **Docker**. Vous pouvez ensuite déployer cette image de conteneur de plusieurs façons possibles. -* Faire de votre application **FastAPI** une image/conteneur Docker avec une performance maximale. En environ **5 min**. -* (Optionnellement) comprendre ce que vous, en tant que développeur, devez savoir sur HTTPS. -* Configurer un cluster en mode Docker Swarm avec HTTPS automatique, même sur un simple serveur à 5 dollars US/mois. En environ **20 min**. -* Générer et déployer une application **FastAPI** complète, en utilisant votre cluster Docker Swarm, avec HTTPS, etc. En environ **10 min**. - -Vous pouvez utiliser **Docker** pour le déploiement. Il présente plusieurs avantages comme la sécurité, la réplicabilité, la simplicité de développement, etc. - -Si vous utilisez Docker, vous pouvez utiliser l'image Docker officielle : - -## tiangolo/uvicorn-gunicorn-fastapi - -Cette image est dotée d'un mécanisme d'"auto-tuning", de sorte qu'il vous suffit d'ajouter votre code pour obtenir automatiquement des performances très élevées. Et sans faire de sacrifices. - -Mais vous pouvez toujours changer et mettre à jour toutes les configurations avec des variables d'environnement ou des fichiers de configuration. +L'utilisation de conteneurs Linux présente plusieurs avantages, notamment la **sécurité**, la **réplicabilité**, la **simplicité**, entre autres. /// tip | Astuce -Pour voir toutes les configurations et options, rendez-vous sur la page de l'image Docker : tiangolo/uvicorn-gunicorn-fastapi. +Vous êtes pressé et vous connaissez déjà tout ça ? Allez directement au [`Dockerfile` ci-dessous 👇](#build-a-docker-image-for-fastapi). /// -## Créer un `Dockerfile` - -* Allez dans le répertoire de votre projet. -* Créez un `Dockerfile` avec : +
+Aperçu du Dockerfile 👀 ```Dockerfile -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7 +FROM python:3.9 -COPY ./app /app +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +CMD ["fastapi", "run", "app/main.py", "--port", "80"] + +# Si vous exécutez derrière un proxy comme Nginx ou Traefik, ajoutez --proxy-headers +# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"] ``` -### Applications plus larges +
-Si vous avez suivi la section sur la création d' [Applications avec plusieurs fichiers](../tutorial/bigger-applications.md){.internal-link target=_blank}, votre `Dockerfile` pourrait ressembler à ceci : +## Qu'est-ce qu'un conteneur { #what-is-a-container } -```Dockerfile -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7 +Les conteneurs (principalement les conteneurs Linux) sont un moyen très **léger** d'empaqueter des applications, y compris toutes leurs dépendances et les fichiers nécessaires, tout en les isolant des autres conteneurs (autres applications ou composants) dans le même système. -COPY ./app /app/app +Les conteneurs Linux s'exécutent en utilisant le même noyau Linux que l'hôte (machine, machine virtuelle, serveur cloud, etc.). Cela signifie simplement qu'ils sont très légers (comparés à des machines virtuelles complètes émulant un système d'exploitation entier). + +Ainsi, les conteneurs consomment **peu de ressources**, une quantité comparable à l'exécution directe des processus (alors qu'une machine virtuelle consommerait beaucoup plus). + +Les conteneurs ont également leurs propres processus d'exécution **isolés** (généralement un seul processus), leur système de fichiers et leur réseau, ce qui simplifie le déploiement, la sécurité, le développement, etc. + +## Qu'est-ce qu'une image de conteneur { #what-is-a-container-image } + +Un **conteneur** s'exécute à partir d'une **image de conteneur**. + +Une image de conteneur est une version **statique** de tous les fichiers, des variables d'environnement et de la commande/le programme par défaut devant être présents dans un conteneur. Ici, **statique** signifie que l'**image** du conteneur ne s'exécute pas, elle n'est pas en cours d'exécution, ce ne sont que les fichiers et métadonnées empaquetés. + +Par opposition à une « **image de conteneur** » qui correspond aux contenus statiques stockés, un « **conteneur** » fait normalement référence à l'instance en cours d'exécution, la chose qui est **exécutée**. + +Lorsque le **conteneur** est démarré et en cours d'exécution (démarré à partir d'une **image de conteneur**), il peut créer ou modifier des fichiers, des variables d'environnement, etc. Ces changements n'existeront que dans ce conteneur, mais ne persisteront pas dans l'image de conteneur sous-jacente (ils ne seront pas enregistrés sur le disque). + +Une image de conteneur est comparable au **programme** et à ses contenus, par exemple `python` et un fichier `main.py`. + +Et le **conteneur** lui-même (par opposition à l'**image de conteneur**) est l'instance en cours d'exécution réelle de l'image, comparable à un **processus**. En fait, un conteneur ne fonctionne que lorsqu'il a un **processus en cours d'exécution** (et normalement, il s'agit d'un seul processus). Le conteneur s'arrête lorsqu'aucun processus n'y est en cours d'exécution. + +## Images de conteneur { #container-images } + +Docker a été l'un des principaux outils pour créer et gérer des **images de conteneur** et des **conteneurs**. + +Et il existe un Docker Hub public avec des **images de conteneur officielles** pré-construites pour de nombreux outils, environnements, bases de données et applications. + +Par exemple, il existe une image Python officielle. + +Et il existe beaucoup d'autres images pour différentes choses comme des bases de données, par exemple : + +* PostgreSQL +* MySQL +* MongoDB +* Redis, etc. + +En utilisant une image de conteneur pré-construite, il est très facile de **combiner** et d'utiliser différents outils. Par exemple, pour essayer une nouvelle base de données. Dans la plupart des cas, vous pouvez utiliser les **images officielles** et simplement les configurer avec des variables d'environnement. + +Ainsi, dans de nombreux cas, vous pouvez apprendre les conteneurs et Docker et réutiliser ces connaissances avec de nombreux outils et composants différents. + +Vous exécuteriez donc **plusieurs conteneurs** avec des éléments différents, comme une base de données, une application Python, un serveur web avec une application frontend React, et les connecter entre eux via leur réseau interne. + +Tous les systèmes de gestion de conteneurs (comme Docker ou Kubernetes) disposent de ces fonctionnalités réseau intégrées. + +## Conteneurs et processus { #containers-and-processes } + +Une **image de conteneur** inclut normalement dans ses métadonnées le programme/la commande par défaut à exécuter lorsque le **conteneur** est démarré et les paramètres à transmettre à ce programme. Très similaire à ce que vous utiliseriez en ligne de commande. + +Lorsqu'un **conteneur** est démarré, il exécutera cette commande/ce programme (bien que vous puissiez la/le remplacer et faire exécuter une autre commande/un autre programme). + +Un conteneur fonctionne tant que le **processus principal** (commande ou programme) est en cours d'exécution. + +Un conteneur a normalement un **seul processus**, mais il est aussi possible de démarrer des sous-processus à partir du processus principal, et ainsi vous aurez **plusieurs processus** dans le même conteneur. + +Mais il n'est pas possible d'avoir un conteneur en cours d'exécution sans **au moins un processus en cours**. Si le processus principal s'arrête, le conteneur s'arrête. + +## Construire une image Docker pour FastAPI { #build-a-docker-image-for-fastapi } + +Très bien, construisons quelque chose maintenant ! 🚀 + +Je vais vous montrer comment construire une **image Docker** pour FastAPI **à partir de zéro**, basée sur l'image **officielle Python**. + +C'est ce que vous voudrez faire dans **la plupart des cas**, par exemple : + +* Utiliser **Kubernetes** ou des outils similaires +* Exécuter sur un **Raspberry Pi** +* Utiliser un service cloud qui exécuterait une image de conteneur pour vous, etc. + +### Dépendances des paquets { #package-requirements } + +Vous aurez normalement les **dépendances des paquets** de votre application dans un fichier. + +Cela dépendra principalement de l'outil que vous utilisez pour **installer** ces dépendances. + +La manière la plus courante consiste à avoir un fichier `requirements.txt` avec les noms des paquets et leurs versions, un par ligne. + +Vous utiliserez bien sûr les mêmes idées que vous avez lues dans [À propos des versions de FastAPI](versions.md){.internal-link target=_blank} pour définir les plages de versions. + +Par exemple, votre `requirements.txt` pourrait ressembler à : + +``` +fastapi[standard]>=0.113.0,<0.114.0 +pydantic>=2.7.0,<3.0.0 ``` -### Raspberry Pi et autres architectures +Et vous installerez normalement ces dépendances de paquets avec `pip`, par exemple : -Si vous utilisez Docker sur un Raspberry Pi (qui a un processeur ARM) ou toute autre architecture, vous pouvez créer un `Dockerfile` à partir de zéro, basé sur une image de base Python (qui est multi-architecture) et utiliser Uvicorn seul. +
-Dans ce cas, votre `Dockerfile` pourrait ressembler à ceci : - -```Dockerfile -FROM python:3.7 - -RUN pip install fastapi uvicorn - -EXPOSE 80 - -COPY ./app /app - -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +```console +$ pip install -r requirements.txt +---> 100% +Successfully installed fastapi pydantic ``` -## Créer le code **FastAPI**. +
-* Créer un répertoire `app` et y entrer. -* Créez un fichier `main.py` avec : +/// info + +Il existe d'autres formats et outils pour définir et installer des dépendances de paquets. + +/// + +### Créer le code **FastAPI** { #create-the-fastapi-code } + +* Créez un répertoire `app` et entrez dedans. +* Créez un fichier vide `__init__.py`. +* Créez un fichier `main.py` avec : ```Python -from typing import Optional - from fastapi import FastAPI app = FastAPI() @@ -81,22 +156,168 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Optional[str] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` -* Vous devriez maintenant avoir une structure de répertoire telle que : +### Dockerfile { #dockerfile } + +Maintenant, dans le même répertoire de projet, créez un fichier `Dockerfile` avec : + +```{ .dockerfile .annotate } +# (1)! +FROM python:3.9 + +# (2)! +WORKDIR /code + +# (3)! +COPY ./requirements.txt /code/requirements.txt + +# (4)! +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (5)! +COPY ./app /code/app + +# (6)! +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +1. Démarrer à partir de l'image de base Python officielle. + +2. Définir le répertoire de travail courant sur `/code`. + + C'est là que nous placerons le fichier `requirements.txt` et le répertoire `app`. + +3. Copier le fichier des dépendances vers le répertoire `/code`. + + Copier **uniquement** le fichier des dépendances en premier, pas le reste du code. + + Comme ce fichier **ne change pas souvent**, Docker le détectera et utilisera le **cache** pour cette étape, ce qui activera le cache pour l'étape suivante aussi. + +4. Installer les dépendances listées dans le fichier des dépendances. + + L'option `--no-cache-dir` indique à `pip` de ne pas enregistrer localement les paquets téléchargés, car cela ne sert que si `pip` devait être relancé pour installer les mêmes paquets, mais ce n'est pas le cas lorsque l'on travaille avec des conteneurs. + + /// note | Remarque + + Le `--no-cache-dir` concerne uniquement `pip`, cela n'a rien à voir avec Docker ou les conteneurs. + + /// + + L'option `--upgrade` indique à `pip` de mettre à niveau les paquets s'ils sont déjà installés. + + Comme l'étape précédente de copie du fichier peut être détectée par le **cache Docker**, cette étape **utilisera également le cache Docker** lorsqu'il est disponible. + + L'utilisation du cache à cette étape vous **fera gagner** beaucoup de **temps** lors de la reconstruction de l'image encore et encore pendant le développement, au lieu de **télécharger et installer** toutes les dépendances **à chaque fois**. + +5. Copier le répertoire `./app` dans le répertoire `/code`. + + Comme cela contient tout le code qui est ce qui **change le plus fréquemment**, le **cache** Docker ne sera pas facilement utilisé pour cette étape ou pour les **étapes suivantes**. + + Il est donc important de placer cela **vers la fin** du `Dockerfile`, pour optimiser les temps de construction de l'image de conteneur. + +6. Définir la **commande** pour utiliser `fastapi run`, qui utilise Uvicorn sous le capot. + + `CMD` prend une liste de chaînes, chacune de ces chaînes correspond à ce que vous taperiez en ligne de commande séparé par des espaces. + + Cette commande sera exécutée à partir du **répertoire de travail courant**, le même répertoire `/code` que vous avez défini plus haut avec `WORKDIR /code`. + +/// tip | Astuce + +Passez en revue ce que fait chaque ligne en cliquant sur chaque bulle numérotée dans le code. 👆 + +/// + +/// warning | Alertes + +Vous devez vous assurer d'utiliser **toujours** la **forme exec** de l'instruction `CMD`, comme expliqué ci-dessous. + +/// + +#### Utiliser `CMD` - Forme Exec { #use-cmd-exec-form } + +L'instruction Docker `CMD` peut être écrite sous deux formes : + +✅ Forme **Exec** : + +```Dockerfile +# ✅ À faire +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +⛔️ Forme **Shell** : + +```Dockerfile +# ⛔️ À ne pas faire +CMD fastapi run app/main.py --port 80 +``` + +Assurez-vous d'utiliser toujours la forme **exec** pour garantir que FastAPI peut s'arrêter proprement et que les [événements de cycle de vie](../advanced/events.md){.internal-link target=_blank} sont déclenchés. + +Vous pouvez en lire davantage dans la documentation Docker sur les formes shell et exec. + +Cela peut être très visible lors de l'utilisation de `docker compose`. Voir cette section de la FAQ Docker Compose pour plus de détails techniques : Pourquoi mes services mettent-ils 10 secondes à se recréer ou à s'arrêter ?. + +#### Structure du répertoire { #directory-structure } + +Vous devriez maintenant avoir une structure de répertoire comme : ``` . ├── app +│   ├── __init__.py │ └── main.py -└── Dockerfile +├── Dockerfile +└── requirements.txt ``` -## Construire l'image Docker +#### Derrière un proxy de terminaison TLS { #behind-a-tls-termination-proxy } -* Allez dans le répertoire du projet (dans lequel se trouve votre `Dockerfile`, contenant votre répertoire `app`). +Si vous exécutez votre conteneur derrière un proxy de terminaison TLS (load balancer) comme Nginx ou Traefik, ajoutez l'option `--proxy-headers`, cela indiquera à Uvicorn (via la CLI FastAPI) de faire confiance aux en-têtes envoyés par ce proxy lui indiquant que l'application s'exécute derrière HTTPS, etc. + +```Dockerfile +CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] +``` + +#### Cache Docker { #docker-cache } + +Il y a une astuce importante dans ce `Dockerfile`, nous copions d'abord **le fichier des dépendances seul**, pas le reste du code. Laissez-moi vous expliquer pourquoi. + +```Dockerfile +COPY ./requirements.txt /code/requirements.txt +``` + +Docker et d'autres outils **construisent** ces images de conteneur **de manière incrémentale**, en ajoutant **une couche au-dessus de l'autre**, en commençant par le haut du `Dockerfile` et en ajoutant tous les fichiers créés par chacune des instructions du `Dockerfile`. + +Docker et des outils similaires utilisent également un **cache interne** lors de la construction de l'image : si un fichier n'a pas changé depuis la dernière construction de l'image de conteneur, alors il va **réutiliser la même couche** créée la dernière fois, au lieu de recopier le fichier et créer une nouvelle couche à partir de zéro. + +Éviter simplement la copie des fichiers n'améliore pas nécessairement les choses de manière significative, mais comme il a utilisé le cache pour cette étape, il peut **utiliser le cache pour l'étape suivante**. Par exemple, il peut utiliser le cache pour l'instruction qui installe les dépendances avec : + +```Dockerfile +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt +``` + +Le fichier des dépendances **ne changera pas fréquemment**. Ainsi, en copiant uniquement ce fichier, Docker pourra **utiliser le cache** pour cette étape. + +Et ensuite, Docker pourra **utiliser le cache pour l'étape suivante** qui télécharge et installe ces dépendances. Et c'est là que nous **gagnons beaucoup de temps**. ✨ ... et évitons l'ennui en attendant. 😪😆 + +Télécharger et installer les dépendances de paquets **peut prendre des minutes**, mais utiliser le **cache** ne **prendra que quelques secondes** au plus. + +Et comme vous reconstruirez l'image de conteneur encore et encore pendant le développement pour vérifier que vos modifications de code fonctionnent, cela vous fera gagner beaucoup de temps cumulé. + +Ensuite, vers la fin du `Dockerfile`, nous copions tout le code. Comme c'est ce qui **change le plus fréquemment**, nous le plaçons vers la fin, car presque toujours, tout ce qui suit cette étape ne pourra pas utiliser le cache. + +```Dockerfile +COPY ./app /code/app +``` + +### Construire l'image Docker { #build-the-docker-image } + +Maintenant que tous les fichiers sont en place, construisons l'image de conteneur. + +* Allez dans le répertoire du projet (là où se trouve votre `Dockerfile`, contenant votre répertoire `app`). * Construisez votre image FastAPI :
@@ -109,9 +330,17 @@ $ docker build -t myimage .
-## Démarrer le conteneur Docker +/// tip | Astuce -* Exécutez un conteneur basé sur votre image : +Remarquez le `.` à la fin, équivalent à `./`, il indique à Docker le répertoire à utiliser pour construire l'image de conteneur. + +Dans ce cas, c'est le même répertoire courant (`.`). + +/// + +### Démarrer le conteneur Docker { #start-the-docker-container } + +* Exécutez un conteneur basé sur votre image :
@@ -121,65 +350,269 @@ $ docker run -d --name mycontainer -p 80:80 myimage
-Vous disposez maintenant d'un serveur FastAPI optimisé dans un conteneur Docker. Configuré automatiquement pour votre -serveur actuel (et le nombre de cœurs du CPU). +## Vérifier { #check-it } -## Vérifier +Vous devriez pouvoir le vérifier via l'URL de votre conteneur Docker, par exemple : http://192.168.99.100/items/5?q=somequery ou http://127.0.0.1/items/5?q=somequery (ou équivalent, en utilisant votre hôte Docker). -Vous devriez pouvoir accéder à votre application via l'URL de votre conteneur Docker, par exemple : http://192.168.99.100/items/5?q=somequery ou http://127.0.0.1/items/5?q=somequery (ou équivalent, en utilisant votre hôte Docker). - -Vous verrez quelque chose comme : +Vous verrez quelque chose comme : ```JSON {"item_id": 5, "q": "somequery"} ``` -## Documentation interactive de l'API +## Documentation interactive de l'API { #interactive-api-docs } -Vous pouvez maintenant visiter http://192.168.99.100/docs ou http://127.0.0.1/docs (ou équivalent, en utilisant votre hôte Docker). +Vous pouvez maintenant aller sur http://192.168.99.100/docs ou http://127.0.0.1/docs (ou équivalent, en utilisant votre hôte Docker). -Vous verrez la documentation interactive automatique de l'API (fournie par Swagger UI) : +Vous verrez la documentation interactive automatique de l'API (fournie par Swagger UI) : ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -## Documentation de l'API alternative +## Documentation alternative de l'API { #alternative-api-docs } -Et vous pouvez également aller sur http://192.168.99.100/redoc ou http://127.0.0.1/redoc (ou équivalent, en utilisant votre hôte Docker). +Et vous pouvez aussi aller sur http://192.168.99.100/redoc ou http://127.0.0.1/redoc (ou équivalent, en utilisant votre hôte Docker). -Vous verrez la documentation automatique alternative (fournie par ReDoc) : +Vous verrez la documentation automatique alternative (fournie par ReDoc) : ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## Traefik +## Construire une image Docker avec un FastAPI mono-fichier { #build-a-docker-image-with-a-single-file-fastapi } -Traefik est un reverse proxy/load balancer -haute performance. Il peut faire office de "Proxy de terminaison TLS" (entre autres fonctionnalités). +Si votre FastAPI est un seul fichier, par exemple `main.py` sans répertoire `./app`, votre structure de fichiers pourrait ressembler à ceci : -Il est intégré à Let's Encrypt. Ainsi, il peut gérer toutes les parties HTTPS, y compris l'acquisition et le renouvellement des certificats. +``` +. +├── Dockerfile +├── main.py +└── requirements.txt +``` -Il est également intégré à Docker. Ainsi, vous pouvez déclarer vos domaines dans les configurations de chaque application et faire en sorte qu'elles lisent ces configurations, génèrent les certificats HTTPS et servent via HTTPS à votre application automatiquement, sans nécessiter aucune modification de leurs configurations. +Vous n'auriez alors qu'à changer les chemins correspondants pour copier le fichier dans le `Dockerfile` : + +```{ .dockerfile .annotate hl_lines="10 13" } +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (1)! +COPY ./main.py /code/ + +# (2)! +CMD ["fastapi", "run", "main.py", "--port", "80"] +``` + +1. Copier le fichier `main.py` directement dans le répertoire `/code` (sans répertoire `./app`). + +2. Utiliser `fastapi run` pour servir votre application dans le fichier unique `main.py`. + +Lorsque vous passez le fichier à `fastapi run`, il détectera automatiquement qu'il s'agit d'un fichier unique et non d'un package et saura comment l'importer et servir votre application FastAPI. 😎 + +## Concepts de déploiement { #deployment-concepts } + +Parlons à nouveau de certains des mêmes [Concepts de déploiement](concepts.md){.internal-link target=_blank} en termes de conteneurs. + +Les conteneurs sont principalement un outil pour simplifier le processus de **construction et de déploiement** d'une application, mais ils n'imposent pas une approche particulière pour gérer ces **concepts de déploiement**, et il existe plusieurs stratégies possibles. + +La **bonne nouvelle**, c'est qu'avec chaque stratégie différente, il existe un moyen de couvrir tous les concepts de déploiement. 🎉 + +Passons en revue ces **concepts de déploiement** en termes de conteneurs : + +* HTTPS +* Exécution au démarrage +* Redémarrages +* Réplication (le nombre de processus en cours d'exécution) +* Mémoire +* Étapes préalables au démarrage + +## HTTPS { #https } + +Si l'on se concentre uniquement sur l'**image de conteneur** pour une application FastAPI (et plus tard sur le **conteneur** en cours d'exécution), HTTPS serait normalement géré **à l'extérieur** par un autre outil. + +Cela pourrait être un autre conteneur, par exemple avec Traefik, gérant **HTTPS** et l'acquisition **automatique** des **certificats**. + +/// tip | Astuce + +Traefik s'intègre avec Docker, Kubernetes, et d'autres, donc il est très facile de configurer HTTPS pour vos conteneurs avec lui. + +/// + +Alternativement, HTTPS pourrait être géré par un fournisseur cloud comme l'un de leurs services (tout en exécutant l'application dans un conteneur). + +## Exécution au démarrage et redémarrages { #running-on-startup-and-restarts } + +Il y a normalement un autre outil chargé de **démarrer et exécuter** votre conteneur. + +Cela pourrait être **Docker** directement, **Docker Compose**, **Kubernetes**, un **service cloud**, etc. + +Dans la plupart (ou toutes) des situations, il existe une option simple pour activer l'exécution du conteneur au démarrage et activer les redémarrages en cas d'échec. Par exemple, dans Docker, c'est l'option de ligne de commande `--restart`. + +Sans utiliser de conteneurs, faire en sorte que les applications s'exécutent au démarrage et avec redémarrages peut être fastidieux et difficile. Mais en **travaillant avec des conteneurs**, dans la plupart des cas, cette fonctionnalité est incluse par défaut. ✨ + +## Réplication - Nombre de processus { #replication-number-of-processes } + +Si vous avez un cluster de machines avec **Kubernetes**, Docker Swarm Mode, Nomad, ou un autre système complexe similaire pour gérer des conteneurs distribués sur plusieurs machines, alors vous voudrez probablement **gérer la réplication** au **niveau du cluster** plutôt que d'utiliser un **gestionnaire de processus** (comme Uvicorn avec workers) dans chaque conteneur. + +L'un de ces systèmes de gestion de conteneurs distribués comme Kubernetes dispose normalement d'une manière intégrée de gérer la **réplication des conteneurs** tout en supportant l'**équilibrage de charge** des requêtes entrantes. Le tout au **niveau du cluster**. + +Dans ces cas, vous voudrez probablement construire une **image Docker à partir de zéro** comme [expliqué ci-dessus](#dockerfile), en installant vos dépendances et en exécutant **un seul processus Uvicorn** au lieu d'utiliser plusieurs workers Uvicorn. + +### Équilibreur de charge { #load-balancer } + +Lors de l'utilisation de conteneurs, vous aurez normalement un composant **à l'écoute sur le port principal**. Cela pourrait être un autre conteneur qui est également un **proxy de terminaison TLS** pour gérer **HTTPS** ou un outil similaire. + +Comme ce composant prend la **charge** des requêtes et la distribue entre les workers de façon (espérons-le) **équilibrée**, on l'appelle également communément un **équilibreur de charge**. + +/// tip | Astuce + +Le même composant de **proxy de terminaison TLS** utilisé pour HTTPS sera probablement aussi un **équilibreur de charge**. + +/// + +Et en travaillant avec des conteneurs, le même système que vous utilisez pour les démarrer et les gérer dispose déjà d'outils internes pour transmettre la **communication réseau** (par ex. les requêtes HTTP) depuis cet **équilibreur de charge** (qui peut aussi être un **proxy de terminaison TLS**) vers le ou les conteneurs avec votre application. + +### Un équilibreur de charge - Plusieurs conteneurs worker { #one-load-balancer-multiple-worker-containers } + +Lorsque vous travaillez avec **Kubernetes** ou des systèmes de gestion de conteneurs distribués similaires, l'utilisation de leurs mécanismes réseau internes permet au **seul équilibreur de charge** à l'écoute sur le **port** principal de transmettre la communication (les requêtes) vers potentiellement **plusieurs conteneurs** exécutant votre application. + +Chacun de ces conteneurs exécutant votre application aura normalement **un seul processus** (par ex. un processus Uvicorn exécutant votre application FastAPI). Ils seront tous des **conteneurs identiques**, exécutant la même chose, mais chacun avec son propre processus, sa mémoire, etc. De cette façon, vous profiterez de la **parallélisation** sur **différents cœurs** du CPU, voire sur **différentes machines**. + +Et le système de conteneurs distribués avec l'**équilibreur de charge** **distribuera les requêtes** à chacun des conteneurs exécutant votre application **à tour de rôle**. Ainsi, chaque requête pourrait être traitée par l'un des multiples **conteneurs répliqués** exécutant votre application. + +Et normalement cet **équilibreur de charge** pourra gérer des requêtes qui vont vers *d'autres* applications dans votre cluster (par ex. vers un autre domaine, ou sous un autre préfixe de chemin d'URL), et transmettra cette communication aux bons conteneurs pour *cette autre* application s'exécutant dans votre cluster. + +### Un processus par conteneur { #one-process-per-container } + +Dans ce type de scénario, vous voudrez probablement avoir **un seul processus (Uvicorn) par conteneur**, puisque vous gérez déjà la réplication au niveau du cluster. + +Donc, dans ce cas, vous **ne voudrez pas** avoir plusieurs workers dans le conteneur, par exemple avec l'option de ligne de commande `--workers`. Vous voudrez avoir **un seul processus Uvicorn** par conteneur (mais probablement plusieurs conteneurs). + +Avoir un autre gestionnaire de processus à l'intérieur du conteneur (comme ce serait le cas avec plusieurs workers) n'ajouterait que de la **complexité inutile** que vous gérez très probablement déjà avec votre système de cluster. + +### Conteneurs avec plusieurs processus et cas particuliers { #containers-with-multiple-processes-and-special-cases } + +Bien sûr, il existe des **cas particuliers** où vous pourriez vouloir avoir **un conteneur** avec plusieurs **processus worker Uvicorn** à l'intérieur. + +Dans ces cas, vous pouvez utiliser l'option de ligne de commande `--workers` pour définir le nombre de workers que vous souhaitez exécuter : + +```{ .dockerfile .annotate } +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +# (1)! +CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"] +``` + +1. Ici, nous utilisons l'option de ligne de commande `--workers` pour définir le nombre de workers à 4. + +Voici quelques exemples où cela pourrait avoir du sens : + +#### Une application simple { #a-simple-app } + +Vous pourriez vouloir un gestionnaire de processus dans le conteneur si votre application est **suffisamment simple** pour s'exécuter sur un **seul serveur**, pas un cluster. + +#### Docker Compose { #docker-compose } + +Vous pourriez déployer sur un **seul serveur** (pas un cluster) avec **Docker Compose**, donc vous n'auriez pas un moyen simple de gérer la réplication des conteneurs (avec Docker Compose) tout en préservant le réseau partagé et l'**équilibrage de charge**. + +Vous pourriez alors vouloir avoir **un seul conteneur** avec un **gestionnaire de processus** qui démarre **plusieurs processus worker** à l'intérieur. --- -Avec ces informations et ces outils, passez à la section suivante pour tout combiner. +L'idée principale est que **rien** de tout cela ne sont des **règles gravées dans la pierre** que vous devez suivre aveuglément. Vous pouvez utiliser ces idées pour **évaluer votre propre cas d'usage** et décider de la meilleure approche pour votre système, en vérifiant comment gérer les concepts suivants : -## Cluster en mode Docker Swarm avec Traefik et HTTPS +* Sécurité - HTTPS +* Exécution au démarrage +* Redémarrages +* Réplication (le nombre de processus en cours d'exécution) +* Mémoire +* Étapes préalables au démarrage -Vous pouvez avoir un cluster en mode Docker Swarm configuré en quelques minutes (environ 20 min) avec un processus Traefik principal gérant HTTPS (y compris l'acquisition et le renouvellement des certificats). +## Mémoire { #memory } -En utilisant le mode Docker Swarm, vous pouvez commencer par un "cluster" d'une seule machine (il peut même s'agir -d'un serveur à 5 USD/mois) et ensuite vous pouvez vous développer autant que vous le souhaitez en ajoutant d'autres serveurs. +Si vous exécutez **un seul processus par conteneur**, vous aurez une quantité de mémoire consommée plus ou moins bien définie, stable et limitée par chacun de ces conteneurs (plus d'un s'ils sont répliqués). -Pour configurer un cluster en mode Docker Swarm avec Traefik et la gestion de HTTPS, suivez ce guide : +Vous pouvez alors définir ces mêmes limites et exigences de mémoire dans vos configurations pour votre système de gestion de conteneurs (par exemple dans **Kubernetes**). De cette façon, il pourra **répliquer les conteneurs** sur les **machines disponibles** en tenant compte de la quantité de mémoire dont ils ont besoin et de la quantité disponible sur les machines du cluster. -### Docker Swarm Mode et Traefik pour un cluster HTTPS +Si votre application est **simple**, cela ne sera probablement **pas un problème**, et vous n'aurez peut-être pas besoin de spécifier des limites de mémoire strictes. Mais si vous **utilisez beaucoup de mémoire** (par exemple avec des modèles de **machine learning**), vous devez vérifier combien de mémoire vous consommez et ajuster le **nombre de conteneurs** qui s'exécutent sur **chaque machine** (et peut-être ajouter plus de machines à votre cluster). -### Déployer une application FastAPI +Si vous exécutez **plusieurs processus par conteneur**, vous devez vous assurer que le nombre de processus démarrés ne **consomme pas plus de mémoire** que ce qui est disponible. -La façon la plus simple de tout mettre en place, serait d'utiliser les [**Générateurs de projet FastAPI**](../project-generation.md){.internal-link target=_blank}. +## Étapes préalables au démarrage et conteneurs { #previous-steps-before-starting-and-containers } -Le génerateur de projet adéquat est conçu pour être intégré à ce cluster Docker Swarm avec Traefik et HTTPS décrit ci-dessus. +Si vous utilisez des conteneurs (par ex. Docker, Kubernetes), alors il existe deux approches principales que vous pouvez utiliser. -Vous pouvez générer un projet en 2 min environ. +### Plusieurs conteneurs { #multiple-containers } -Le projet généré a des instructions pour le déployer et le faire prend 2 min de plus. +Si vous avez **plusieurs conteneurs**, probablement chacun exécutant un **seul processus** (par exemple, dans un cluster **Kubernetes**), alors vous voudrez probablement avoir un **conteneur séparé** effectuant le travail des **étapes préalables** dans un seul conteneur, exécutant un seul processus, **avant** d'exécuter les conteneurs worker répliqués. + +/// info + +Si vous utilisez Kubernetes, ce sera probablement un Init Container. + +/// + +Si, dans votre cas d'usage, il n'y a pas de problème à exécuter ces étapes préalables **plusieurs fois en parallèle** (par exemple si vous n'exécutez pas de migrations de base de données, mais vérifiez simplement si la base de données est prête), alors vous pourriez aussi simplement les mettre dans chaque conteneur juste avant de démarrer le processus principal. + +### Un seul conteneur { #single-container } + +Si vous avez une configuration simple, avec **un seul conteneur** qui démarre ensuite plusieurs **processus worker** (ou un seul processus aussi), vous pouvez alors exécuter ces étapes préalables dans le même conteneur, juste avant de démarrer le processus avec l'application. + +### Image Docker de base { #base-docker-image } + +Il existait une image Docker officielle FastAPI : tiangolo/uvicorn-gunicorn-fastapi. Mais elle est désormais dépréciée. ⛔️ + +Vous ne devriez probablement **pas** utiliser cette image Docker de base (ni aucune autre similaire). + +Si vous utilisez **Kubernetes** (ou autres) et que vous définissez déjà la **réplication** au niveau du cluster, avec plusieurs **conteneurs**. Dans ces cas, il est préférable de **construire une image à partir de zéro** comme décrit ci-dessus : [Construire une image Docker pour FastAPI](#build-a-docker-image-for-fastapi). + +Et si vous devez avoir plusieurs workers, vous pouvez simplement utiliser l'option de ligne de commande `--workers`. + +/// note | Détails techniques + +L'image Docker a été créée à une époque où Uvicorn ne supportait pas la gestion et le redémarrage des workers morts, il fallait donc utiliser Gunicorn avec Uvicorn, ce qui ajoutait pas mal de complexité, uniquement pour que Gunicorn gère et redémarre les processus worker Uvicorn. + +Mais maintenant qu'Uvicorn (et la commande `fastapi`) supporte l'usage de `--workers`, il n'y a plus de raison d'utiliser une image Docker de base au lieu de construire la vôtre (c'est à peu près la même quantité de code 😅). + +/// + +## Déployer l'image de conteneur { #deploy-the-container-image } + +Après avoir une image de conteneur (Docker), il existe plusieurs façons de la déployer. + +Par exemple : + +* Avec **Docker Compose** sur un seul serveur +* Avec un cluster **Kubernetes** +* Avec un cluster Docker Swarm Mode +* Avec un autre outil comme Nomad +* Avec un service cloud qui prend votre image de conteneur et la déploie + +## Image Docker avec `uv` { #docker-image-with-uv } + +Si vous utilisez uv pour installer et gérer votre projet, vous pouvez suivre leur guide Docker pour uv. + +## Récapitulatif { #recap } + +Avec les systèmes de conteneurs (par ex. avec **Docker** et **Kubernetes**), il devient assez simple de gérer tous les **concepts de déploiement** : + +* HTTPS +* Exécution au démarrage +* Redémarrages +* Réplication (le nombre de processus en cours d'exécution) +* Mémoire +* Étapes préalables au démarrage + +Dans la plupart des cas, vous ne voudrez probablement pas utiliser d'image de base, et au contraire **construire une image de conteneur à partir de zéro** basée sur l'image Docker Python officielle. + +En prenant soin de l'**ordre** des instructions dans le `Dockerfile` et du **cache Docker**, vous pouvez **minimiser les temps de construction**, maximiser votre productivité (et éviter l'ennui). 😎 diff --git a/docs/fr/docs/deployment/https.md b/docs/fr/docs/deployment/https.md index 3f7068ff0..74d38cdb9 100644 --- a/docs/fr/docs/deployment/https.md +++ b/docs/fr/docs/deployment/https.md @@ -1,56 +1,231 @@ -# À propos de HTTPS +# À propos de HTTPS { #about-https } -Il est facile de penser que HTTPS peut simplement être "activé" ou non. +Il est facile de supposer que HTTPS est quelque chose qui est simplement « activé » ou non. Mais c'est beaucoup plus complexe que cela. -/// tip +/// tip | Astuce -Si vous êtes pressé ou si cela ne vous intéresse pas, passez aux sections suivantes pour obtenir des instructions étape par étape afin de tout configurer avec différentes techniques. +Si vous êtes pressé ou si cela ne vous intéresse pas, continuez avec les sections suivantes pour obtenir des instructions étape par étape afin de tout configurer avec différentes techniques. /// -Pour apprendre les bases du HTTPS, du point de vue d'un utilisateur, consultez https://howhttps.works/. +Pour apprendre les bases du HTTPS, du point de vue d'un utilisateur, consultez https://howhttps.works/. Maintenant, du point de vue d'un développeur, voici plusieurs choses à avoir en tête en pensant au HTTPS : -* Pour le HTTPS, le serveur a besoin de "certificats" générés par une tierce partie. - * Ces certificats sont en fait acquis auprès de la tierce partie, et non "générés". -* Les certificats ont une durée de vie. - * Ils expirent. - * Puis ils doivent être renouvelés et acquis à nouveau auprès de la tierce partie. -* Le cryptage de la connexion se fait au niveau du protocole TCP. - * C'est une couche en dessous de HTTP. - * Donc, le certificat et le traitement du cryptage sont faits avant HTTP. -* TCP ne connaît pas les "domaines", seulement les adresses IP. - * L'information sur le domaine spécifique demandé se trouve dans les données HTTP. -* Les certificats HTTPS "certifient" un certain domaine, mais le protocole et le cryptage se font au niveau TCP, avant de savoir quel domaine est traité. -* Par défaut, cela signifie que vous ne pouvez avoir qu'un seul certificat HTTPS par adresse IP. - * Quelle que soit la taille de votre serveur ou la taille de chacune des applications qu'il contient. - * Il existe cependant une solution à ce problème. -* Il existe une extension du protocole TLS (celui qui gère le cryptage au niveau TCP, avant HTTP) appelée SNI (indication du nom du serveur). - * Cette extension SNI permet à un seul serveur (avec une seule adresse IP) d'avoir plusieurs certificats HTTPS et de servir plusieurs domaines/applications HTTPS. - * Pour que cela fonctionne, un seul composant (programme) fonctionnant sur le serveur, écoutant sur l'adresse IP publique, doit avoir tous les certificats HTTPS du serveur. -* Après avoir obtenu une connexion sécurisée, le protocole de communication est toujours HTTP. - * Le contenu est crypté, même s'il est envoyé avec le protocole HTTP. +* Pour le HTTPS, **le serveur** doit **disposer de « certificats »** générés par une **tierce partie**. + * Ces certificats sont en réalité **acquis** auprès de la tierce partie, et non « générés ». +* Les certificats ont une **durée de vie**. + * Ils **expirent**. + * Puis ils doivent être **renouvelés**, **acquis à nouveau** auprès de la tierce partie. +* Le cryptage de la connexion se fait au **niveau TCP**. + * C'est une couche **en dessous de HTTP**. + * Donc, la gestion du **certificat et du cryptage** est effectuée **avant HTTP**. +* **TCP ne connaît pas les « domaines »**. Il ne connaît que les adresses IP. + * L'information sur le **domaine spécifique** demandé se trouve dans les **données HTTP**. +* Les **certificats HTTPS** « certifient » un **certain domaine**, mais le protocole et le cryptage se font au niveau TCP, **avant de savoir** quel domaine est traité. +* **Par défaut**, cela signifie que vous ne pouvez avoir qu'**un seul certificat HTTPS par adresse IP**. + * Quelle que soit la taille de votre serveur ou la petitesse de chacune des applications qu'il contient. + * Il existe cependant une **solution** à ce problème. +* Il existe une **extension** du protocole **TLS** (celui qui gère le cryptage au niveau TCP, avant HTTP) appelée **SNI**. + * Cette extension SNI permet à un seul serveur (avec une **seule adresse IP**) d'avoir **plusieurs certificats HTTPS** et de servir **plusieurs domaines/applications HTTPS**. + * Pour que cela fonctionne, un **seul** composant (programme) fonctionnant sur le serveur, écoutant sur l'**adresse IP publique**, doit avoir **tous les certificats HTTPS** du serveur. +* **Après** l'établissement d'une connexion sécurisée, le protocole de communication est **toujours HTTP**. + * Le contenu est **crypté**, même s'il est envoyé avec le **protocole HTTP**. -Il est courant d'avoir un seul programme/serveur HTTP fonctionnant sur le serveur (la machine, l'hôte, etc.) et -gérant toutes les parties HTTPS : envoyer les requêtes HTTP décryptées à l'application HTTP réelle fonctionnant sur -le même serveur (dans ce cas, l'application **FastAPI**), prendre la réponse HTTP de l'application, la crypter en utilisant le certificat approprié et la renvoyer au client en utilisant HTTPS. Ce serveur est souvent appelé un Proxy de terminaison TLS. +Il est courant d'avoir **un seul programme/serveur HTTP** fonctionnant sur le serveur (la machine, l'hôte, etc.) et **gérant toutes les parties HTTPS** : recevoir les **requêtes HTTPS chiffrées**, envoyer les **requêtes HTTP déchiffrées** à l'application HTTP réelle fonctionnant sur le même serveur (l'application **FastAPI**, dans ce cas), prendre la **réponse HTTP** de l'application, la **chiffrer** en utilisant le **certificat HTTPS** approprié et la renvoyer au client en utilisant **HTTPS**. Ce serveur est souvent appelé un **Proxy de terminaison TLS**. -## Let's Encrypt +Parmi les options que vous pourriez utiliser comme Proxy de terminaison TLS : -Avant Let's Encrypt, ces certificats HTTPS étaient vendus par des tiers de confiance. +* Traefik (qui peut également gérer les renouvellements de certificats) +* Caddy (qui peut également gérer les renouvellements de certificats) +* Nginx +* HAProxy -Le processus d'acquisition d'un de ces certificats était auparavant lourd, nécessitait pas mal de paperasses et les certificats étaient assez chers. +## Let's Encrypt { #lets-encrypt } -Mais ensuite, Let's Encrypt a été créé. +Avant Let's Encrypt, ces **certificats HTTPS** étaient vendus par des tiers de confiance. -Il s'agit d'un projet de la Fondation Linux. Il fournit des certificats HTTPS gratuitement. De manière automatisée. Ces certificats utilisent toutes les sécurités cryptographiques standard et ont une durée de vie courte (environ 3 mois), de sorte que la sécurité est en fait meilleure en raison de leur durée de vie réduite. +Le processus d'acquisition de l'un de ces certificats était auparavant lourd, nécessitait pas mal de paperasses et les certificats étaient assez chers. + +Mais ensuite, **Let's Encrypt** a été créé. + +Il s'agit d'un projet de la Fondation Linux. Il fournit **des certificats HTTPS gratuitement**, de manière automatisée. Ces certificats utilisent toutes les sécurités cryptographiques standard et ont une durée de vie courte (environ 3 mois), de sorte que la **sécurité est en fait meilleure** en raison de leur durée de vie réduite. Les domaines sont vérifiés de manière sécurisée et les certificats sont générés automatiquement. Cela permet également d'automatiser le renouvellement de ces certificats. -L'idée est d'automatiser l'acquisition et le renouvellement de ces certificats, afin que vous puissiez disposer d'un HTTPS sécurisé, gratuitement et pour toujours. +L'idée est d'automatiser l'acquisition et le renouvellement de ces certificats, afin que vous puissiez disposer d'un **HTTPS sécurisé, gratuitement et pour toujours**. + +## HTTPS pour les développeurs { #https-for-developers } + +Voici un exemple de ce à quoi pourrait ressembler une API HTTPS, étape par étape, en portant principalement attention aux idées importantes pour les développeurs. + +### Nom de domaine { #domain-name } + +Tout commencerait probablement par le fait que vous **acquériez** un **nom de domaine**. Ensuite, vous le configureriez dans un serveur DNS (possiblement le même que votre fournisseur cloud). + +Vous obtiendriez probablement un serveur cloud (une machine virtuelle) ou quelque chose de similaire, et il aurait une adresse IP **publique** fixe. + +Dans le ou les serveurs DNS, vous configureriez un enregistrement (un « `A record` ») pour faire pointer **votre domaine** vers l'**adresse IP publique de votre serveur**. + +Vous feriez probablement cela une seule fois, la première fois, lors de la mise en place de l'ensemble. + +/// tip | Astuce + +Cette partie relative au nom de domaine intervient bien avant HTTPS, mais comme tout dépend du domaine et de l'adresse IP, il vaut la peine de la mentionner ici. + +/// + +### DNS { #dns } + +Concentrons-nous maintenant sur toutes les parties réellement liées à HTTPS. + +D'abord, le navigateur vérifierait auprès des **serveurs DNS** quelle est l'**IP du domaine**, dans ce cas, `someapp.example.com`. + +Les serveurs DNS indiqueraient au navigateur d'utiliser une **adresse IP** spécifique. Ce serait l'adresse IP publique utilisée par votre serveur, celle que vous avez configurée dans les serveurs DNS. + + + +### Début de la négociation TLS (Handshake) { #tls-handshake-start } + +Le navigateur communiquerait ensuite avec cette adresse IP sur le **port 443** (le port HTTPS). + +La première partie de la communication consiste simplement à établir la connexion entre le client et le serveur et à décider des clés cryptographiques qu'ils utiliseront, etc. + + + +Cette interaction entre le client et le serveur pour établir la connexion TLS s'appelle la **négociation TLS (TLS handshake)**. + +### TLS avec l'extension SNI { #tls-with-sni-extension } + +**Un seul processus** sur le serveur peut écouter sur un **port** spécifique d'une **adresse IP** spécifique. Il pourrait y avoir d'autres processus écoutant sur d'autres ports de la même adresse IP, mais un seul pour chaque combinaison d'adresse IP et de port. + +TLS (HTTPS) utilise par défaut le port spécifique `443`. C'est donc le port dont nous aurions besoin. + +Comme un seul processus peut écouter sur ce port, le processus qui le ferait serait le **Proxy de terminaison TLS**. + +Le Proxy de terminaison TLS aurait accès à un ou plusieurs **certificats TLS** (certificats HTTPS). + +En utilisant l'**extension SNI** mentionnée plus haut, le Proxy de terminaison TLS vérifierait lequel des certificats TLS (HTTPS) disponibles il devrait utiliser pour cette connexion, en choisissant celui qui correspond au domaine attendu par le client. + +Dans ce cas, il utiliserait le certificat pour `someapp.example.com`. + + + +Le client **fait déjà confiance** à l'entité qui a généré ce certificat TLS (dans ce cas Let's Encrypt, mais nous y reviendrons plus tard), il peut donc **vérifier** que le certificat est valide. + +Ensuite, en utilisant le certificat, le client et le Proxy de terminaison TLS **décident comment chiffrer** le reste de la **communication TCP**. Cela termine la partie **négociation TLS**. + +Après cela, le client et le serveur disposent d'une **connexion TCP chiffrée**, c'est ce que fournit TLS. Ils peuvent alors utiliser cette connexion pour démarrer la **communication HTTP** proprement dite. + +Et c'est ce qu'est **HTTPS** : c'est simplement du **HTTP** à l'intérieur d'une **connexion TLS sécurisée** au lieu d'une connexion TCP pure (non chiffrée). + +/// tip | Astuce + +Remarquez que le cryptage de la communication se produit au **niveau TCP**, pas au niveau HTTP. + +/// + +### Requête HTTPS { #https-request } + +Maintenant que le client et le serveur (spécifiquement le navigateur et le Proxy de terminaison TLS) ont une **connexion TCP chiffrée**, ils peuvent démarrer la **communication HTTP**. + +Ainsi, le client envoie une **requête HTTPS**. Ce n'est qu'une requête HTTP à travers une connexion TLS chiffrée. + + + +### Déchiffrer la requête { #decrypt-the-request } + +Le Proxy de terminaison TLS utiliserait le chiffrement convenu pour **déchiffrer la requête**, et transmettrait la **requête HTTP en clair (déchiffrée)** au processus exécutant l'application (par exemple un processus avec Uvicorn exécutant l'application FastAPI). + + + +### Réponse HTTP { #http-response } + +L'application traiterait la requête et enverrait une **réponse HTTP en clair (non chiffrée)** au Proxy de terminaison TLS. + + + +### Réponse HTTPS { #https-response } + +Le Proxy de terminaison TLS **chiffrerait ensuite la réponse** en utilisant la cryptographie convenue auparavant (qui a commencé avec le certificat pour `someapp.example.com`), et la renverrait au navigateur. + +Ensuite, le navigateur vérifierait que la réponse est valide et chiffrée avec la bonne clé cryptographique, etc. Il **déchiffrerait la réponse** et la traiterait. + + + +Le client (navigateur) saura que la réponse provient du bon serveur parce qu'elle utilise la cryptographie convenue auparavant à l'aide du **certificat HTTPS**. + +### Applications multiples { #multiple-applications } + +Sur le même serveur (ou les mêmes serveurs), il pourrait y avoir **plusieurs applications**, par exemple d'autres programmes d'API ou une base de données. + +Un seul processus peut gérer l'adresse IP et le port spécifiques (le Proxy de terminaison TLS dans notre exemple), mais les autres applications/processus peuvent également s'exécuter sur le ou les serveurs, tant qu'ils n'essaient pas d'utiliser la même **combinaison d'adresse IP publique et de port**. + + + +De cette façon, le Proxy de terminaison TLS pourrait gérer HTTPS et les certificats pour **plusieurs domaines**, pour plusieurs applications, puis transmettre les requêtes à la bonne application dans chaque cas. + +### Renouvellement des certificats { #certificate-renewal } + +À un moment donné dans le futur, chaque certificat **expirerait** (environ 3 mois après son acquisition). + +Ensuite, il y aurait un autre programme (dans certains cas c'est un autre programme, dans d'autres cas cela pourrait être le même Proxy de terminaison TLS) qui communiquerait avec Let's Encrypt et renouvellerait le ou les certificats. + + + +Les **certificats TLS** sont **associés à un nom de domaine**, pas à une adresse IP. + +Ainsi, pour renouveler les certificats, le programme de renouvellement doit **prouver** à l'autorité (Let's Encrypt) qu'il **« possède » et contrôle ce domaine**. + +Pour ce faire, et pour s'adapter aux différents besoins des applications, il existe plusieurs façons de procéder. Parmi les plus courantes : + +* **Modifier certains enregistrements DNS**. + * Pour cela, le programme de renouvellement doit prendre en charge les API du fournisseur DNS ; ainsi, selon le fournisseur DNS que vous utilisez, cela peut être ou non une option. +* **S'exécuter comme un serveur** (au moins pendant le processus d'acquisition du certificat) sur l'adresse IP publique associée au domaine. + * Comme nous l'avons dit plus haut, un seul processus peut écouter sur une adresse IP et un port spécifiques. + * C'est l'une des raisons pour lesquelles il est très utile que le même Proxy de terminaison TLS prenne également en charge le processus de renouvellement des certificats. + * Sinon, vous pourriez avoir à arrêter le Proxy de terminaison TLS momentanément, démarrer le programme de renouvellement pour acquérir les certificats, puis les configurer avec le Proxy de terminaison TLS, et ensuite redémarrer le Proxy de terminaison TLS. Ce n'est pas idéal, car votre/vos application(s) ne seront pas disponibles pendant le temps où le Proxy de terminaison TLS est arrêté. + +Tout ce processus de renouvellement, tout en continuant à servir l'application, est l'une des principales raisons pour lesquelles vous voudriez avoir un **système séparé pour gérer HTTPS** avec un Proxy de terminaison TLS, au lieu d'utiliser directement les certificats TLS avec le serveur d'application (par exemple Uvicorn). + +## En-têtes Proxy Forwarded { #proxy-forwarded-headers } + +Lorsque vous utilisez un proxy pour gérer HTTPS, votre **serveur d'application** (par exemple Uvicorn via FastAPI CLI) ne connaît rien du processus HTTPS, il communique en HTTP en clair avec le **Proxy de terminaison TLS**. + +Ce **proxy** définirait normalement certains en-têtes HTTP à la volée avant de transmettre la requête au **serveur d'application**, pour informer le serveur d'application que la requête est **transmise** par le proxy. + +/// note | Détails techniques + +Les en-têtes du proxy sont : + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +Néanmoins, comme le **serveur d'application** ne sait pas qu'il se trouve derrière un **proxy** de confiance, par défaut, il ne ferait pas confiance à ces en-têtes. + +Mais vous pouvez configurer le **serveur d'application** pour qu'il fasse confiance aux en-têtes transmis (*forwarded*) envoyés par le **proxy**. Si vous utilisez FastAPI CLI, vous pouvez utiliser l'*option CLI* `--forwarded-allow-ips` pour lui indiquer à partir de quelles IP il doit faire confiance à ces en-têtes transmis. + +Par exemple, si le **serveur d'application** ne reçoit des communications que du **proxy** de confiance, vous pouvez définir `--forwarded-allow-ips="*"` pour lui faire faire confiance à toutes les IP entrantes, puisqu'il ne recevra des requêtes que depuis l'IP utilisée par le **proxy**. + +De cette façon, l'application sera en mesure de savoir quelle est sa propre URL publique, si elle utilise HTTPS, le domaine, etc. + +Cela serait utile, par exemple, pour gérer correctement les redirections. + +/// tip | Astuce + +Vous pouvez en savoir plus dans la documentation [Derrière un proxy - Activer les en-têtes transmis par le proxy](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} + +/// + +## Récapitulatif { #recap } + +Disposer de **HTTPS** est très important, et assez **critique** dans la plupart des cas. La majeure partie de l'effort que vous, en tant que développeur, devez fournir autour de HTTPS consiste simplement à **comprendre ces concepts** et leur fonctionnement. + +Mais une fois que vous connaissez les informations de base sur **HTTPS pour les développeurs**, vous pouvez facilement combiner et configurer différents outils pour vous aider à tout gérer simplement. + +Dans certains des prochains chapitres, je vous montrerai plusieurs exemples concrets de configuration de **HTTPS** pour des applications **FastAPI**. 🔒 diff --git a/docs/fr/docs/deployment/index.md b/docs/fr/docs/deployment/index.md index e2014afe9..3b08e9af7 100644 --- a/docs/fr/docs/deployment/index.md +++ b/docs/fr/docs/deployment/index.md @@ -1,8 +1,8 @@ -# Déploiement +# Déploiement { #deployment } Le déploiement d'une application **FastAPI** est relativement simple. -## Que signifie le déploiement +## Que signifie le déploiement { #what-does-deployment-mean } **Déployer** une application signifie effectuer les étapes nécessaires pour la rendre **disponible pour les utilisateurs**. @@ -14,7 +14,7 @@ l'application efficacement et sans interruption ni problème. Ceci contraste avec les étapes de **développement**, où vous êtes constamment en train de modifier le code, de le casser et de le réparer, d'arrêter et de redémarrer le serveur de développement, _etc._ -## Stratégies de déploiement +## Stratégies de déploiement { #deployment-strategies } Il existe plusieurs façons de procéder, en fonction de votre cas d'utilisation spécifique et des outils que vous utilisez. @@ -22,6 +22,8 @@ utilisez. Vous pouvez **déployer un serveur** vous-même en utilisant une combinaison d'outils, vous pouvez utiliser un **service cloud** qui fait une partie du travail pour vous, ou encore d'autres options possibles. +Par exemple, nous, l'équipe derrière FastAPI, avons créé **FastAPI Cloud**, pour rendre le déploiement d'applications FastAPI dans le cloud aussi fluide que possible, avec la même expérience développeur que lorsque vous travaillez avec FastAPI. + Je vais vous montrer certains des principaux concepts que vous devriez probablement avoir à l'esprit lors du déploiement d'une application **FastAPI** (bien que la plupart de ces concepts s'appliquent à tout autre type d'application web). diff --git a/docs/fr/docs/deployment/versions.md b/docs/fr/docs/deployment/versions.md index 9d84274e2..81794428f 100644 --- a/docs/fr/docs/deployment/versions.md +++ b/docs/fr/docs/deployment/versions.md @@ -1,101 +1,93 @@ -# À propos des versions de FastAPI +# À propos des versions de FastAPI { #about-fastapi-versions } -**FastAPI** est déjà utilisé en production dans de nombreuses applications et systèmes. Et la couverture de test est maintenue à 100 %. Mais son développement est toujours aussi rapide. +**FastAPI** est déjà utilisé en production dans de nombreuses applications et de nombreux systèmes. Et la couverture de tests est maintenue à 100 %. Mais son développement avance toujours rapidement. -De nouvelles fonctionnalités sont ajoutées fréquemment, des bogues sont corrigés régulièrement et le code est -amélioré continuellement. +De nouvelles fonctionnalités sont ajoutées fréquemment, des bogues sont corrigés régulièrement et le code s'améliore continuellement. -C'est pourquoi les versions actuelles sont toujours `0.x.x`, cela reflète que chaque version peut potentiellement -recevoir des changements non rétrocompatibles. Cela suit les conventions de versionnage sémantique. +C'est pourquoi les versions actuelles sont toujours `0.x.x`, cela reflète que chaque version pourrait potentiellement comporter des changements non rétrocompatibles. Cela suit les conventions de versionnage sémantique. Vous pouvez créer des applications de production avec **FastAPI** dès maintenant (et vous le faites probablement depuis un certain temps), vous devez juste vous assurer que vous utilisez une version qui fonctionne correctement avec le reste de votre code. -## Épinglez votre version de `fastapi` +## Épingler votre version de `fastapi` { #pin-your-fastapi-version } -Tout d'abord il faut "épingler" la version de **FastAPI** que vous utilisez à la dernière version dont vous savez -qu'elle fonctionne correctement pour votre application. +La première chose que vous devez faire est « épingler » la version de **FastAPI** que vous utilisez à la dernière version spécifique dont vous savez qu’elle fonctionne correctement pour votre application. -Par exemple, disons que vous utilisez la version `0.45.0` dans votre application. +Par exemple, disons que vous utilisez la version `0.112.0` dans votre application. -Si vous utilisez un fichier `requirements.txt`, vous pouvez spécifier la version avec : +Si vous utilisez un fichier `requirements.txt`, vous pouvez spécifier la version avec : ```txt -fastapi==0.45.0 +fastapi[standard]==0.112.0 ``` -ce qui signifierait que vous utiliseriez exactement la version `0.45.0`. +ce qui signifierait que vous utiliseriez exactement la version `0.112.0`. -Ou vous pourriez aussi l'épingler avec : +Ou vous pourriez aussi l'épingler avec : ```txt -fastapi>=0.45.0,<0.46.0 +fastapi[standard]>=0.112.0,<0.113.0 ``` -cela signifierait que vous utiliseriez les versions `0.45.0` ou supérieures, mais inférieures à `0.46.0`, par exemple, une version `0.45.2` serait toujours acceptée. +cela signifierait que vous utiliseriez les versions `0.112.0` ou supérieures, mais inférieures à `0.113.0`, par exemple, une version `0.112.2` serait toujours acceptée. -Si vous utilisez un autre outil pour gérer vos installations, comme Poetry, Pipenv, ou autres, ils ont tous un moyen que vous pouvez utiliser pour définir des versions spécifiques pour vos paquets. +Si vous utilisez un autre outil pour gérer vos installations, comme `uv`, Poetry, Pipenv, ou autres, ils ont tous un moyen que vous pouvez utiliser pour définir des versions spécifiques pour vos paquets. -## Versions disponibles +## Versions disponibles { #available-versions } Vous pouvez consulter les versions disponibles (par exemple, pour vérifier quelle est la dernière version en date) dans les [Notes de version](../release-notes.md){.internal-link target=_blank}. -## À propos des versions +## À propos des versions { #about-versions } -Suivant les conventions de versionnage sémantique, toute version inférieure à `1.0.0` peut potentiellement ajouter -des changements non rétrocompatibles. +Suivant les conventions de versionnage sémantique, toute version inférieure à `1.0.0` peut potentiellement ajouter des changements non rétrocompatibles. -FastAPI suit également la convention que tout changement de version "PATCH" est pour des corrections de bogues et -des changements rétrocompatibles. +FastAPI suit également la convention selon laquelle tout changement de version « PATCH » concerne des corrections de bogues et des changements rétrocompatibles. /// tip | Astuce -Le "PATCH" est le dernier chiffre, par exemple, dans `0.2.3`, la version PATCH est `3`. +Le « PATCH » est le dernier chiffre, par exemple, dans `0.2.3`, la version PATCH est `3`. /// -Donc, vous devriez être capable d'épingler une version comme suit : +Donc, vous devriez être en mesure d'épingler une version comme suit : ```txt fastapi>=0.45.0,<0.46.0 ``` -Les changements non rétrocompatibles et les nouvelles fonctionnalités sont ajoutés dans les versions "MINOR". +Les changements non rétrocompatibles et les nouvelles fonctionnalités sont ajoutés dans les versions « MINOR ». /// tip | Astuce -Le "MINOR" est le numéro au milieu, par exemple, dans `0.2.3`, la version MINOR est `2`. +Le « MINOR » est le numéro au milieu, par exemple, dans `0.2.3`, la version MINOR est `2`. /// -## Mise à jour des versions FastAPI +## Mettre à niveau les versions de FastAPI { #upgrading-the-fastapi-versions } -Vous devriez tester votre application. +Vous devez ajouter des tests pour votre application. -Avec **FastAPI** c'est très facile (merci à Starlette), consultez la documentation : [Testing](../tutorial/testing.md){.internal-link target=_blank} +Avec **FastAPI** c'est très facile (merci à Starlette), consultez les documents : [Tests](../tutorial/testing.md){.internal-link target=_blank} -Après avoir effectué des tests, vous pouvez mettre à jour la version **FastAPI** vers une version plus récente, et vous assurer que tout votre code fonctionne correctement en exécutant vos tests. +Après avoir des tests, vous pouvez mettre à niveau la version de **FastAPI** vers une version plus récente et vous assurer que tout votre code fonctionne correctement en exécutant vos tests. -Si tout fonctionne, ou après avoir fait les changements nécessaires, et que tous vos tests passent, vous pouvez -épingler votre version de `fastapi` à cette nouvelle version récente. +Si tout fonctionne, ou après avoir effectué les changements nécessaires, et que tous vos tests passent, vous pouvez alors épingler votre `fastapi` à cette nouvelle version récente. -## À propos de Starlette +## À propos de Starlette { #about-starlette } -Vous ne devriez pas épingler la version de `starlette`. +Vous ne devez pas épingler la version de `starlette`. Différentes versions de **FastAPI** utiliseront une version spécifique plus récente de Starlette. Ainsi, vous pouvez simplement laisser **FastAPI** utiliser la bonne version de Starlette. -## À propos de Pydantic +## À propos de Pydantic { #about-pydantic } -Pydantic inclut des tests pour **FastAPI** avec ses propres tests, ainsi les nouvelles versions de Pydantic (au-dessus -de `1.0.0`) sont toujours compatibles avec **FastAPI**. +Pydantic inclut les tests pour **FastAPI** avec ses propres tests, ainsi les nouvelles versions de Pydantic (au-dessus de `1.0.0`) sont toujours compatibles avec FastAPI. -Vous pouvez épingler Pydantic à toute version supérieure à `1.0.0` qui fonctionne pour vous et inférieure à `2.0.0`. +Vous pouvez épingler Pydantic à toute version supérieure à `1.0.0` qui fonctionne pour vous. -Par exemple : +Par exemple : ```txt -pydantic>=1.2.0,<2.0.0 +pydantic>=2.7.0,<3.0.0 ``` diff --git a/docs/fr/docs/index.md b/docs/fr/docs/index.md index 99ea8dda1..2aeaa1c69 100644 --- a/docs/fr/docs/index.md +++ b/docs/fr/docs/index.md @@ -1,11 +1,11 @@ -# FastAPI +# FastAPI { #fastapi }

- FastAPI + FastAPI

Framework FastAPI, haute performance, facile à apprendre, rapide à coder, prêt pour la production @@ -27,7 +27,7 @@ --- -**Documentation** : https://fastapi.tiangolo.com +**Documentation** : https://fastapi.tiangolo.com/fr **Code Source** : https://github.com/fastapi/fastapi @@ -37,128 +37,130 @@ FastAPI est un framework web moderne et rapide (haute performance) pour la créa Les principales fonctionnalités sont : -* **Rapidité** : De très hautes performances, au niveau de **NodeJS** et **Go** (grâce à Starlette et Pydantic). [L'un des frameworks Python les plus rapides](#performance). -* **Rapide à coder** : Augmente la vitesse de développement des fonctionnalités d'environ 200 % à 300 %. * -* **Moins de bugs** : Réduit d'environ 40 % les erreurs induites par le développeur. * -* **Intuitif** : Excellente compatibilité avec les IDE. Complétion complète. Moins de temps passé à déboguer. -* **Facile** : Conçu pour être facile à utiliser et à apprendre. Moins de temps passé à lire la documentation. -* **Concis** : Diminue la duplication de code. De nombreuses fonctionnalités liées à la déclaration de chaque paramètre. Moins de bugs. -* **Robuste** : Obtenez un code prêt pour la production. Avec une documentation interactive automatique. -* **Basé sur des normes** : Basé sur (et entièrement compatible avec) les standards ouverts pour les APIs : OpenAPI (précédemment connu sous le nom de Swagger) et JSON Schema. +* **Rapide** : très hautes performances, au niveau de **NodeJS** et **Go** (grâce à Starlette et Pydantic). [L'un des frameworks Python les plus rapides](#performance). +* **Rapide à coder** : augmente la vitesse de développement des fonctionnalités d'environ 200 % à 300 %. * +* **Moins de bugs** : réduit d'environ 40 % les erreurs induites par le développeur. * +* **Intuitif** : excellente compatibilité avec les éditeurs. Autocomplétion partout. Moins de temps passé à déboguer. +* **Facile** : conçu pour être facile à utiliser et à apprendre. Moins de temps passé à lire les documents. +* **Concis** : diminue la duplication de code. Plusieurs fonctionnalités à partir de chaque déclaration de paramètre. Moins de bugs. +* **Robuste** : obtenez un code prêt pour la production. Avec une documentation interactive automatique. +* **Basé sur des normes** : basé sur (et entièrement compatible avec) les standards ouverts pour les APIs : OpenAPI (précédemment connu sous le nom de Swagger) et JSON Schema. * estimation basée sur des tests d'une équipe de développement interne, construisant des applications de production. -## Sponsors +## Sponsors { #sponsors } -{% if sponsors %} +### Sponsor clé de voûte { #keystone-sponsor } + +{% for sponsor in sponsors.keystone -%} + +{% endfor -%} + +### Sponsors Or et Argent { #gold-and-silver-sponsors } + {% for sponsor in sponsors.gold -%} {% endfor -%} {%- for sponsor in sponsors.silver -%} {% endfor %} -{% endif %} -Other sponsors +Autres sponsors -## Opinions +## Opinions { #opinions } -"_[...] J'utilise beaucoup **FastAPI** ces derniers temps. [...] Je prévois de l'utiliser dans mon équipe pour tous les **services de ML chez Microsoft**. Certains d'entre eux seront intégrés dans le coeur de **Windows** et dans certains produits **Office**._" +« _[...] J'utilise beaucoup **FastAPI** ces derniers temps. [...] Je prévois de l'utiliser dans mon équipe pour tous les **services de ML chez Microsoft**. Certains d'entre eux sont intégrés au cœur de **Windows** et à certains produits **Office**._ »

Kabir Khan - Microsoft (ref)
--- -"_Nous avons adopté la bibliothèque **FastAPI** pour créer un serveur **REST** qui peut être interrogé pour obtenir des **prédictions**. [pour Ludwig]_" +« _Nous avons adopté la bibliothèque **FastAPI** pour créer un serveur **REST** qui peut être interrogé pour obtenir des **prédictions**. [pour Ludwig]_ » -
Piero Molino, Yaroslav Dudin et Sai Sumanth Miryala - Uber (ref)
+
Piero Molino, Yaroslav Dudin, et Sai Sumanth Miryala - Uber (ref)
--- -"_**Netflix** a le plaisir d'annoncer la sortie en open-source de notre framework d'orchestration de **gestion de crise** : **Dispatch** ! [construit avec **FastAPI**]_" +« _**Netflix** est heureux d'annoncer la publication en open source de notre framework d'orchestration de **gestion de crise** : **Dispatch** ! [construit avec **FastAPI**]_ »
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
--- -"_Je suis très enthousiaste à propos de **FastAPI**. C'est un bonheur !_" +« _Je suis plus qu'enthousiaste à propos de **FastAPI**. C'est tellement fun !_ » -
Brian Okken - Auteur du podcast Python Bytes (ref)
+
Brian Okken - Animateur du podcast Python Bytes (ref)
--- -"_Honnêtement, ce que vous avez construit a l'air super solide et élégant. A bien des égards, c'est comme ça que je voulais que **Hug** soit - c'est vraiment inspirant de voir quelqu'un construire ça._" +« _Honnêtement, ce que vous avez construit a l'air super solide et soigné. À bien des égards, c'est ce que je voulais que **Hug** soit — c'est vraiment inspirant de voir quelqu'un construire ça._ » -
Timothy Crosley - Créateur de Hug (ref)
+
Timothy Crosley - Créateur de Hug (ref)
--- -"_Si vous cherchez à apprendre un **framework moderne** pour créer des APIs REST, regardez **FastAPI** [...] C'est rapide, facile à utiliser et à apprendre [...]_" +« _Si vous cherchez à apprendre un **framework moderne** pour créer des APIs REST, regardez **FastAPI** [...] C'est rapide, facile à utiliser et facile à apprendre [...]_ » -"_Nous sommes passés à **FastAPI** pour nos **APIs** [...] Je pense que vous l'aimerez [...]_" +« _Nous sommes passés à **FastAPI** pour nos **APIs** [...] Je pense que vous l'aimerez [...]_ »
Ines Montani - Matthew Honnibal - Fondateurs de Explosion AI - Créateurs de spaCy (ref) - (ref)
--- -"_Si quelqu'un cherche à construire une API Python de production, je recommande vivement **FastAPI**. Il est **bien conçu**, **simple à utiliser** et **très évolutif**. Il est devenu un **composant clé** dans notre stratégie de développement API first et il est à l'origine de nombreux automatismes et services tels que notre ingénieur virtuel TAC._" +« _Si quelqu'un cherche à construire une API Python de production, je recommande vivement **FastAPI**. Il est **magnifiquement conçu**, **simple à utiliser** et **hautement scalable**. Il est devenu un **composant clé** de notre stratégie de développement API-first et alimente de nombreuses automatisations et services tels que notre ingénieur TAC virtuel._ »
Deon Pillsbury - Cisco (ref)
--- -## **Typer**, le FastAPI des CLI +## Mini documentaire FastAPI { #fastapi-mini-documentary } + +Un mini documentaire FastAPI est sorti fin 2025, vous pouvez le regarder en ligne : + +FastAPI Mini Documentary + +## **Typer**, le FastAPI des CLIs { #typer-the-fastapi-of-clis } -Si vous souhaitez construire une application CLI utilisable dans un terminal au lieu d'une API web, regardez **Typer**. +Si vous construisez une application CLI à utiliser dans un terminal au lieu d'une API web, regardez **Typer**. -**Typer** est le petit frère de FastAPI. Et il est destiné à être le **FastAPI des CLI**. ⌨️ 🚀 +**Typer** est le petit frère de FastAPI. Et il est destiné à être le **FastAPI des CLIs**. ⌨️ 🚀 -## Prérequis +## Prérequis { #requirements } FastAPI repose sur les épaules de géants : * Starlette pour les parties web. * Pydantic pour les parties données. -## Installation +## Installation { #installation } + +Créez et activez un environnement virtuel puis installez FastAPI :
```console -$ pip install fastapi +$ pip install "fastapi[standard]" ---> 100% ```
-Vous aurez également besoin d'un serveur ASGI pour la production tel que Uvicorn ou Hypercorn. +**Remarque** : Vous devez vous assurer de mettre « fastapi[standard] » entre guillemets pour garantir que cela fonctionne dans tous les terminaux. -
+## Exemple { #example } -```console -$ pip install "uvicorn[standard]" +### Créer { #create-it } ----> 100% -``` - -
- -## Exemple - -### Créez - -* Créez un fichier `main.py` avec : +Créez un fichier `main.py` avec : ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -170,18 +172,16 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ```
-Ou utilisez async def ... +Ou utilisez async def... Si votre code utilise `async` / `await`, utilisez `async def` : -```Python hl_lines="9 14" -from typing import Union - +```Python hl_lines="7 12" from fastapi import FastAPI app = FastAPI() @@ -193,28 +193,41 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): +async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` -**Note** +**Remarque** : -Si vous n'êtes pas familier avec cette notion, consultez la section _"Vous êtes pressés ?"_ à propos de `async` et `await` dans la documentation. +Si vous ne savez pas, consultez la section « Vous êtes pressés ? » à propos de `async` et `await` dans la documentation.
-### Lancez +### Lancer { #run-it } Lancez le serveur avec :
```console -$ uvicorn main:app --reload +$ fastapi dev main.py + ╭────────── 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 [28720] -INFO: Started server process [28722] +INFO: Started reloader process [2248755] using WatchFiles +INFO: Started server process [2248757] INFO: Waiting for application startup. INFO: Application startup complete. ``` @@ -222,34 +235,34 @@ INFO: Application startup complete.
-À propos de la commande uvicorn main:app --reload ... +À propos de la commande fastapi dev main.py... -La commande `uvicorn main:app` fait référence à : +La commande `fastapi dev` lit votre fichier `main.py`, détecte l'application **FastAPI** qu'il contient et lance un serveur avec Uvicorn. -* `main` : le fichier `main.py` (le "module" Python). -* `app` : l'objet créé à l'intérieur de `main.py` avec la ligne `app = FastAPI()`. -* `--reload` : fait redémarrer le serveur après des changements de code. À n'utiliser que pour le développement. +Par défaut, `fastapi dev` démarre avec le rechargement automatique activé pour le développement local. + +Vous pouvez en savoir plus dans la documentation de la CLI FastAPI.
-### Vérifiez +### Vérifier { #check-it } Ouvrez votre navigateur à l'adresse http://127.0.0.1:8000/items/5?q=somequery. -Vous obtenez alors cette réponse JSON : +Vous verrez la réponse JSON : ```JSON {"item_id": 5, "q": "somequery"} ``` -Vous venez de créer une API qui : +Vous avez déjà créé une API qui : -* Reçoit les requêtes HTTP pour les _chemins_ `/` et `/items/{item_id}`. -* Les deux _chemins_ acceptent des opérations `GET` (également connu sous le nom de _méthodes_ HTTP). -* Le _chemin_ `/items/{item_id}` a un _paramètre_ `item_id` qui doit être un `int`. -* Le _chemin_ `/items/{item_id}` a un _paramètre de requête_ optionnel `q` de type `str`. +* Reçoit des requêtes HTTP sur les _chemins_ `/` et `/items/{item_id}`. +* Les deux _chemins_ acceptent des opérations `GET` (également connues sous le nom de _méthodes_ HTTP). +* Le _chemin_ `/items/{item_id}` a un _paramètre de chemin_ `item_id` qui doit être un `int`. +* Le _chemin_ `/items/{item_id}` a un _paramètre de requête_ optionnel `q` de type `str`. -### Documentation API interactive +### Documentation API interactive { #interactive-api-docs } Maintenant, rendez-vous sur http://127.0.0.1:8000/docs. @@ -257,23 +270,21 @@ Vous verrez la documentation interactive automatique de l'API (fournie par http://127.0.0.1:8000/redoc. -Vous verrez la documentation interactive automatique de l'API (fournie par ReDoc) : +Vous verrez la documentation alternative automatique (fournie par ReDoc) : ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## Exemple plus poussé +## Mettre à niveau l'exemple { #example-upgrade } -Maintenant, modifiez le fichier `main.py` pour recevoir le corps d'une requête `PUT`. +Modifiez maintenant le fichier `main.py` pour recevoir un corps depuis une requête `PUT`. -Déclarez ce corps en utilisant les types Python standards, grâce à Pydantic. - -```Python hl_lines="4 9-12 25-27" -from typing import Union +Déclarez le corps en utilisant les types Python standard, grâce à Pydantic. +```Python hl_lines="2 7-10 23-25" from fastapi import FastAPI from pydantic import BaseModel @@ -283,7 +294,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Union[bool, None] = None + is_offer: bool | None = None @app.get("/") @@ -292,7 +303,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} @@ -301,35 +312,35 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -Le serveur se recharge normalement automatiquement (car vous avez pensé à `--reload` dans la commande `uvicorn` ci-dessus). +Le serveur `fastapi dev` devrait se recharger automatiquement. -### Plus loin avec la documentation API interactive +### Mettre à niveau la documentation API interactive { #interactive-api-docs-upgrade } Maintenant, rendez-vous sur http://127.0.0.1:8000/docs. -* La documentation interactive de l'API sera automatiquement mise à jour, y compris le nouveau corps de la requête : +* La documentation interactive de l'API sera automatiquement mise à jour, y compris le nouveau corps : ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* Cliquez sur le bouton "Try it out", il vous permet de renseigner les paramètres et d'interagir directement avec l'API : +* Cliquez sur le bouton « Try it out », il vous permet de renseigner les paramètres et d'interagir directement avec l'API : ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) -* Cliquez ensuite sur le bouton "Execute", l'interface utilisateur communiquera avec votre API, enverra les paramètres, obtiendra les résultats et les affichera à l'écran : +* Cliquez ensuite sur le bouton « Execute », l'interface utilisateur communiquera avec votre API, enverra les paramètres, obtiendra les résultats et les affichera à l'écran : ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) -### Plus loin avec la documentation API alternative +### Mettre à niveau la documentation API alternative { #alternative-api-docs-upgrade } Et maintenant, rendez-vous sur http://127.0.0.1:8000/redoc. -* La documentation alternative reflétera également le nouveau paramètre de requête et le nouveau corps : +* La documentation alternative reflètera également le nouveau paramètre de requête et le nouveau corps : ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### En résumé +### En résumé { #recap } -En résumé, vous déclarez **une fois** les types de paramètres, le corps de la requête, etc. en tant que paramètres de fonction. +En résumé, vous déclarez **une fois** les types de paramètres, le corps, etc. en tant que paramètres de fonction. Vous faites cela avec les types Python standard modernes. @@ -337,7 +348,7 @@ Vous n'avez pas à apprendre une nouvelle syntaxe, les méthodes ou les classes Juste du **Python** standard. -Par exemple, pour un `int`: +Par exemple, pour un `int` : ```Python item_id: int @@ -351,54 +362,54 @@ item: Item ... et avec cette déclaration unique, vous obtenez : -* Une assistance dans votre IDE, notamment : - * la complétion. +* Une assistance dans l'éditeur, notamment : + * l'autocomplétion. * la vérification des types. * La validation des données : * des erreurs automatiques et claires lorsque les données ne sont pas valides. - * une validation même pour les objets JSON profondément imbriqués. -* Une conversion des données d'entrée : venant du réseau et allant vers les données et types de Python, permettant de lire : - * le JSON. - * les paramètres du chemin. - * les paramètres de la requête. - * les cookies. - * les en-têtes. - * les formulaires. - * les fichiers. -* La conversion des données de sortie : conversion des données et types Python en données réseau (au format JSON), permettant de convertir : - * les types Python (`str`, `int`, `float`, `bool`, `list`, etc). - * les objets `datetime`. - * les objets `UUID`. - * les modèles de base de données. - * ... et beaucoup plus. -* La documentation API interactive automatique, avec 2 interfaces utilisateur au choix : + * une validation même pour les objets JSON profondément imbriqués. +* Conversion des données d'entrée : venant du réseau vers les données et types Python. Lecture depuis : + * JSON. + * Paramètres de chemin. + * Paramètres de requête. + * Cookies. + * En-têtes. + * Formulaires. + * Fichiers. +* Conversion des données de sortie : conversion des données et types Python en données réseau (au format JSON) : + * Conversion des types Python (`str`, `int`, `float`, `bool`, `list`, etc). + * Objets `datetime`. + * Objets `UUID`. + * Modèles de base de données. + * ... et bien plus. +* Documentation API interactive automatique, avec 2 interfaces utilisateur au choix : * Swagger UI. * ReDoc. --- -Pour revenir à l'exemple de code précédent, **FastAPI** permet de : +Pour revenir à l'exemple de code précédent, **FastAPI** va : -* Valider que `item_id` existe dans le chemin des requêtes `GET` et `PUT`. +* Valider la présence d'un `item_id` dans le chemin pour les requêtes `GET` et `PUT`. * Valider que `item_id` est de type `int` pour les requêtes `GET` et `PUT`. - * Si ce n'est pas le cas, le client voit une erreur utile et claire. -* Vérifier qu'il existe un paramètre de requête facultatif nommé `q` (comme dans `http://127.0.0.1:8000/items/foo?q=somequery`) pour les requêtes `GET`. - * Puisque le paramètre `q` est déclaré avec `= None`, il est facultatif. - * Sans le `None`, il serait nécessaire (comme l'est le corps de la requête dans le cas du `PUT`). -* Pour les requêtes `PUT` vers `/items/{item_id}`, de lire le corps en JSON : - * Vérifier qu'il a un attribut obligatoire `name` qui devrait être un `str`. - * Vérifier qu'il a un attribut obligatoire `prix` qui doit être un `float`. - * Vérifier qu'il a un attribut facultatif `is_offer`, qui devrait être un `bool`, s'il est présent. - * Tout cela fonctionnerait également pour les objets JSON profondément imbriqués. -* Convertir de et vers JSON automatiquement. -* Documenter tout avec OpenAPI, qui peut être utilisé par : - * Les systèmes de documentation interactifs. - * Les systèmes de génération automatique de code client, pour de nombreuses langues. + * Si ce n'est pas le cas, le client verra une erreur utile et claire. +* Vérifier s'il existe un paramètre de requête optionnel nommé `q` (comme dans `http://127.0.0.1:8000/items/foo?q=somequery`) pour les requêtes `GET`. + * Comme le paramètre `q` est déclaré avec `= None`, il est optionnel. + * Sans le `None`, il serait requis (comme l'est le corps dans le cas de `PUT`). +* Pour les requêtes `PUT` vers `/items/{item_id}`, lire le corps au format JSON : + * Vérifier qu'il a un attribut obligatoire `name` qui doit être un `str`. + * Vérifier qu'il a un attribut obligatoire `price` qui doit être un `float`. + * Vérifier qu'il a un attribut optionnel `is_offer`, qui doit être un `bool`, s'il est présent. + * Tout cela fonctionne également pour les objets JSON profondément imbriqués. +* Convertir automatiquement depuis et vers JSON. +* Tout documenter avec OpenAPI, qui peut être utilisé par : + * des systèmes de documentation interactive. + * des systèmes de génération automatique de clients, pour de nombreux langages. * Fournir directement 2 interfaces web de documentation interactive. --- -Nous n'avons fait qu'effleurer la surface, mais vous avez déjà une idée de la façon dont tout cela fonctionne. +Nous n'avons fait qu'effleurer la surface, mais vous avez déjà une idée de la façon dont tout fonctionne. Essayez de changer la ligne contenant : @@ -412,61 +423,137 @@ Essayez de changer la ligne contenant : ... "item_name": item.name ... ``` -... vers : +... à : ```Python ... "item_price": item.price ... ``` -... et voyez comment votre éditeur complétera automatiquement les attributs et connaîtra leurs types : +... et voyez comment votre éditeur complète automatiquement les attributs et connaît leurs types : -![compatibilité IDE](https://fastapi.tiangolo.com/img/vscode-completion.png) +![compatibilité éditeur](https://fastapi.tiangolo.com/img/vscode-completion.png) Pour un exemple plus complet comprenant plus de fonctionnalités, voir le Tutoriel - Guide utilisateur. -**Spoiler alert** : le tutoriel - guide utilisateur inclut : +**Alerte spoiler** : le tutoriel - guide utilisateur inclut : -* Déclaration de **paramètres** provenant d'autres endroits différents comme : **en-têtes.**, **cookies**, **champs de formulaire** et **fichiers**. -* L'utilisation de **contraintes de validation** comme `maximum_length` ou `regex`. -* Un **système d'injection de dépendance ** très puissant et facile à utiliser . -* Sécurité et authentification, y compris la prise en charge de **OAuth2** avec les **jetons JWT** et l'authentification **HTTP Basic**. -* Des techniques plus avancées (mais tout aussi faciles) pour déclarer les **modèles JSON profondément imbriqués** (grâce à Pydantic). -* Intégration de **GraphQL** avec Strawberry et d'autres bibliothèques. -* D'obtenir de nombreuses fonctionnalités supplémentaires (grâce à Starlette) comme : +* Déclaration de **paramètres** provenant d'autres emplacements comme : **en-têtes**, **cookies**, **champs de formulaire** et **fichiers**. +* Comment définir des **contraintes de validation** comme `maximum_length` ou `regex`. +* Un système **d'injection de dépendances** très puissant et facile à utiliser. +* Sécurité et authentification, y compris la prise en charge de **OAuth2** avec des **JWT tokens** et l'authentification **HTTP Basic**. +* Des techniques plus avancées (mais tout aussi faciles) pour déclarer des **modèles JSON profondément imbriqués** (grâce à Pydantic). +* Intégration **GraphQL** avec Strawberry et d'autres bibliothèques. +* De nombreuses fonctionnalités supplémentaires (grâce à Starlette) comme : * **WebSockets** - * de tester le code très facilement avec `requests` et `pytest` - * **CORS** + * des tests extrêmement faciles basés sur HTTPX et `pytest` + * **CORS** * **Cookie Sessions** * ... et plus encore. -## Performance +### Déployer votre application (optionnel) { #deploy-your-app-optional } -Les benchmarks TechEmpower indépendants montrent que les applications **FastAPI** s'exécutant sous Uvicorn sont parmi les frameworks existants en Python les plus rapides , juste derrière Starlette et Uvicorn (utilisés en interne par FastAPI). (*) +Vous pouvez, si vous le souhaitez, déployer votre application FastAPI sur FastAPI Cloud, allez vous inscrire sur la liste d'attente si ce n'est pas déjà fait. 🚀 + +Si vous avez déjà un compte **FastAPI Cloud** (nous vous avons invité depuis la liste d'attente 😉), vous pouvez déployer votre application avec une seule commande. + +Avant de déployer, assurez-vous d'être connecté : + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +Puis déployez votre application : + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +C'est tout ! Vous pouvez maintenant accéder à votre application à cette URL. ✨ + +#### À propos de FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** est construit par le même auteur et la même équipe derrière **FastAPI**. + +Il simplifie le processus de **construction**, de **déploiement** et **d'accès** à une API avec un effort minimal. + +Il apporte la même **expérience développeur** de la création d'applications avec FastAPI au **déploiement** dans le cloud. 🎉 + +FastAPI Cloud est le principal sponsor et financeur des projets open source *FastAPI and friends*. ✨ + +#### Déployer sur d'autres fournisseurs cloud { #deploy-to-other-cloud-providers } + +FastAPI est open source et basé sur des standards. Vous pouvez déployer des applications FastAPI sur n'importe quel fournisseur cloud de votre choix. + +Suivez les guides de votre fournisseur cloud pour y déployer des applications FastAPI. 🤓 + +## Performance { #performance } + +Les benchmarks TechEmpower indépendants montrent que les applications **FastAPI** s'exécutant sous Uvicorn sont parmi les frameworks Python les plus rapides, juste derrière Starlette et Uvicorn eux-mêmes (utilisés en interne par FastAPI). (*) Pour en savoir plus, consultez la section Benchmarks. -## Dépendances facultatives +## Dépendances { #dependencies } -Utilisées par Pydantic: +FastAPI dépend de Pydantic et Starlette. -* email-validator - pour la validation des adresses email. +### Dépendances `standard` { #standard-dependencies } + +Lorsque vous installez FastAPI avec `pip install "fastapi[standard]"`, il inclut le groupe `standard` de dépendances optionnelles : + +Utilisées par Pydantic : + +* email-validator - pour la validation des adresses e-mail. Utilisées par Starlette : -* requests - Obligatoire si vous souhaitez utiliser `TestClient`. +* httpx - Obligatoire si vous souhaitez utiliser le `TestClient`. * jinja2 - Obligatoire si vous souhaitez utiliser la configuration de template par défaut. -* python-multipart - Obligatoire si vous souhaitez supporter le "décodage" de formulaire avec `request.form()`. -* itsdangerous - Obligatoire pour la prise en charge de `SessionMiddleware`. -* pyyaml - Obligatoire pour le support `SchemaGenerator` de Starlette (vous n'en avez probablement pas besoin avec FastAPI). +* python-multipart - Obligatoire si vous souhaitez prendre en charge l’« parsing » de formulaires avec `request.form()`. -Utilisées par FastAPI / Starlette : +Utilisées par FastAPI : -* uvicorn - Pour le serveur qui charge et sert votre application. -* orjson - Obligatoire si vous voulez utiliser `ORJSONResponse`. +* uvicorn - pour le serveur qui charge et sert votre application. Cela inclut `uvicorn[standard]`, qui comprend certaines dépendances (par ex. `uvloop`) nécessaires pour une haute performance. +* `fastapi-cli[standard]` - pour fournir la commande `fastapi`. + * Cela inclut `fastapi-cloud-cli`, qui vous permet de déployer votre application FastAPI sur FastAPI Cloud. + +### Sans les dépendances `standard` { #without-standard-dependencies } + +Si vous ne souhaitez pas inclure les dépendances optionnelles `standard`, vous pouvez installer avec `pip install fastapi` au lieu de `pip install "fastapi[standard]"`. + +### Sans `fastapi-cloud-cli` { #without-fastapi-cloud-cli } + +Si vous souhaitez installer FastAPI avec les dépendances standard mais sans `fastapi-cloud-cli`, vous pouvez installer avec `pip install "fastapi[standard-no-fastapi-cloud-cli]"`. + +### Dépendances optionnelles supplémentaires { #additional-optional-dependencies } + +Il existe des dépendances supplémentaires que vous pourriez vouloir installer. + +Dépendances optionnelles supplémentaires pour Pydantic : + +* pydantic-settings - pour la gestion des paramètres. +* pydantic-extra-types - pour des types supplémentaires à utiliser avec Pydantic. + +Dépendances optionnelles supplémentaires pour FastAPI : + +* orjson - Obligatoire si vous souhaitez utiliser `ORJSONResponse`. * ujson - Obligatoire si vous souhaitez utiliser `UJSONResponse`. -Vous pouvez tout installer avec `pip install fastapi[all]`. - -## Licence +## Licence { #license } Ce projet est soumis aux termes de la licence MIT. diff --git a/docs/fr/docs/learn/index.md b/docs/fr/docs/learn/index.md index 46fc095dc..552687703 100644 --- a/docs/fr/docs/learn/index.md +++ b/docs/fr/docs/learn/index.md @@ -1,5 +1,5 @@ -# Apprendre +# Apprendre { #learn } Voici les sections introductives et les tutoriels pour apprendre **FastAPI**. -Vous pouvez considérer ceci comme un **manuel**, un **cours**, la **méthode officielle** et recommandée pour appréhender FastAPI. 😎 +Vous pouvez considérer ceci comme un **livre**, un **cours**, la **méthode officielle** et recommandée pour apprendre FastAPI. 😎 diff --git a/docs/fr/docs/project-generation.md b/docs/fr/docs/project-generation.md index 4c04dc167..f062ffecf 100644 --- a/docs/fr/docs/project-generation.md +++ b/docs/fr/docs/project-generation.md @@ -1,84 +1,28 @@ -# Génération de projets - Modèle +# Modèle Full Stack FastAPI { #full-stack-fastapi-template } -Vous pouvez utiliser un générateur de projet pour commencer, qui réalisera pour vous la mise en place de bases côté architecture globale, sécurité, base de données et premières routes d'API. +Les modèles, bien qu'ils soient généralement livrés avec une configuration spécifique, sont conçus pour être flexibles et personnalisables. Cela vous permet de les modifier et de les adapter aux exigences de votre projet, ce qui en fait un excellent point de départ. 🏁 -Un générateur de projet fera toujours une mise en place très subjective que vous devriez modifier et adapter suivant vos besoins, mais cela reste un bon point de départ pour vos projets. +Vous pouvez utiliser ce modèle pour démarrer, car il inclut une grande partie de la configuration initiale, la sécurité, la base de données et quelques endpoints d'API déjà prêts pour vous. -## Full Stack FastAPI PostgreSQL +Dépôt GitHub : Modèle Full Stack FastAPI -GitHub : https://github.com/tiangolo/full-stack-fastapi-postgresql +## Modèle Full Stack FastAPI - Pile technologique et fonctionnalités { #full-stack-fastapi-template-technology-stack-and-features } -### Full Stack FastAPI PostgreSQL - Fonctionnalités - -* Intégration **Docker** complète (basée sur Docker). -* Déploiement Docker en mode Swarm -* Intégration **Docker Compose** et optimisation pour développement local. -* Serveur web Python **prêt au déploiement** utilisant Uvicorn et Gunicorn. -* Backend Python **FastAPI** : - * **Rapide** : Très hautes performances, comparables à **NodeJS** ou **Go** (grâce à Starlette et Pydantic). - * **Intuitif** : Excellent support des éditeurs. Complétion partout. Moins de temps passé à déboguer. - * **Facile** : Fait pour être facile à utiliser et apprendre. Moins de temps passé à lire de la documentation. - * **Concis** : Minimise la duplication de code. Plusieurs fonctionnalités à chaque déclaration de paramètre. - * **Robuste** : Obtenez du code prêt pour être utilisé en production. Avec de la documentation automatique interactive. - * **Basé sur des normes** : Basé sur (et totalement compatible avec) les normes ouvertes pour les APIs : OpenAPI et JSON Schema. - * **Et bien d'autres fonctionnalités** comme la validation automatique, la sérialisation, l'authentification avec OAuth2 JWT tokens, etc. -* Hashage de **mots de passe sécurisé** par défaut. -* Authentification par **jetons JWT**. -* Modèles **SQLAlchemy** (indépendants des extensions Flask, afin qu'ils puissent être utilisés directement avec des *workers* Celery). -* Modèle de démarrages basiques pour les utilisateurs (à modifier et supprimer au besoin). -* Migrations **Alembic**. -* **CORS** (partage des ressources entre origines multiples, ou *Cross Origin Resource Sharing*). -* *Worker* **Celery** pouvant importer et utiliser les modèles et le code du reste du backend. -* Tests du backend REST basés sur **Pytest**, intégrés dans Docker, pour que vous puissiez tester toutes les interactions de l'API indépendamment de la base de données. Étant exécutés dans Docker, les tests peuvent utiliser un nouvel entrepôt de données créé de zéro à chaque fois (vous pouvez donc utiliser ElasticSearch, MongoDB, CouchDB, etc. et juste tester que l'API fonctionne). -* Intégration Python facile avec **Jupyter Kernels** pour le développement à distance ou intra-Docker avec des extensions comme Atom Hydrogen ou Visual Studio Code Jupyter. -* Frontend **Vue** : - * Généré avec Vue CLI. - * Gestion de l'**Authentification JWT**. - * Page de connexion. - * Après la connexion, page de tableau de bord principal. - * Tableau de bord principal avec création et modification d'utilisateurs. - * Modification de ses propres caractéristiques utilisateur. - * **Vuex**. - * **Vue-router**. - * **Vuetify** pour de magnifiques composants *material design*. - * **TypeScript**. - * Serveur Docker basé sur **Nginx** (configuré pour être facilement manipulé avec Vue-router). - * Utilisation de *Docker multi-stage building*, pour ne pas avoir besoin de sauvegarder ou *commit* du code compilé. - * Tests frontend exécutés à la compilation (pouvant être désactivés). - * Fait aussi modulable que possible, pour pouvoir fonctionner comme tel, tout en pouvant être utilisé qu'en partie grâce à Vue CLI. -* **PGAdmin** pour les bases de données PostgreSQL, facilement modifiable pour utiliser PHPMYAdmin ou MySQL. -* **Flower** pour la surveillance de tâches Celery. -* Équilibrage de charge entre le frontend et le backend avec **Traefik**, afin de pouvoir avoir les deux sur le même domaine, séparés par chemins, mais servis par différents conteneurs. -* Intégration Traefik, comprenant la génération automatique de certificat **HTTPS** Let's Encrypt. -* GitLab **CI** (intégration continue), comprenant des tests pour le frontend et le backend. - -## Full Stack FastAPI Couchbase - -GitHub : https://github.com/tiangolo/full-stack-fastapi-couchbase - -⚠️ **ATTENTION** ⚠️ - -Si vous démarrez un nouveau projet de zéro, allez voir les alternatives au début de cette page. - -Par exemple, le générateur de projet Full Stack FastAPI PostgreSQL peut être une meilleure alternative, étant activement maintenu et utilisé et comprenant toutes les nouvelles fonctionnalités et améliorations. - -Vous êtes toujours libre d'utiliser le générateur basé sur Couchbase si vous le voulez, cela devrait probablement fonctionner correctement, et si vous avez déjà un projet généré en utilisant ce dernier, cela devrait fonctionner aussi (et vous l'avez déjà probablement mis à jour suivant vos besoins). - -Vous pouvez en apprendre plus dans la documentation du dépôt GithHub. - -## Full Stack FastAPI MongoDB - -...viendra surement plus tard, suivant le temps que j'ai. 😅 🎉 - -## Modèles d'apprentissage automatique avec spaCy et FastAPI - -GitHub : https://github.com/microsoft/cookiecutter-spacy-fastapi - -## Modèles d'apprentissage automatique avec spaCy et FastAPI - Fonctionnalités - -* Intégration d'un modèle NER **spaCy**. -* Formatage de requête pour **Azure Cognitive Search**. -* Serveur Python web **prêt à utiliser en production** utilisant Uvicorn et Gunicorn. -* Déploiement CI/CD Kubernetes pour **Azure DevOps** (AKS). -* **Multilangues**. Choisissez facilement l'une des langues intégrées à spaCy durant la mise en place du projet. -* **Facilement généralisable** à d'autres bibliothèques similaires (Pytorch, Tensorflow), et non juste spaCy. +- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/fr) pour l'API backend Python. + - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) pour les interactions avec la base de données SQL en Python (ORM). + - 🔍 [Pydantic](https://docs.pydantic.dev), utilisé par FastAPI, pour la validation des données et la gestion des paramètres. + - 💾 [PostgreSQL](https://www.postgresql.org) comme base de données SQL. +- 🚀 [React](https://react.dev) pour le frontend. + - 💃 Utilisation de TypeScript, des hooks, de Vite et d'autres éléments d'un stack frontend moderne. + - 🎨 [Tailwind CSS](https://tailwindcss.com) et [shadcn/ui](https://ui.shadcn.com) pour les composants frontend. + - 🤖 Un client frontend généré automatiquement. + - 🧪 [Playwright](https://playwright.dev) pour les tests de bout en bout. + - 🦇 Prise en charge du mode sombre. +- 🐋 [Docker Compose](https://www.docker.com) pour le développement et la production. +- 🔒 Hachage sécurisé des mots de passe par défaut. +- 🔑 Authentification JWT (JSON Web Token). +- 📫 Récupération de mot de passe par e-mail. +- ✅ Tests avec [Pytest](https://pytest.org). +- 📞 [Traefik](https://traefik.io) comme proxy inverse / répartiteur de charge. +- 🚢 Instructions de déploiement avec Docker Compose, y compris la configuration d'un proxy Traefik frontal pour gérer les certificats HTTPS automatiques. +- 🏭 CI (intégration continue) et CD (déploiement continu) basés sur GitHub Actions. diff --git a/docs/fr/docs/python-types.md b/docs/fr/docs/python-types.md index 99ca90827..f393b0f5c 100644 --- a/docs/fr/docs/python-types.md +++ b/docs/fr/docs/python-types.md @@ -1,70 +1,68 @@ -# Introduction aux Types Python +# Introduction aux types Python { #python-types-intro } -Python supporte des annotations de type (ou *type hints*) optionnelles. +Python prend en charge des « type hints » (aussi appelées « annotations de type ») facultatives. -Ces annotations de type constituent une syntaxe spéciale qui permet de déclarer le type d'une variable. +Ces « type hints » ou annotations sont une syntaxe spéciale qui permet de déclarer le type d'une variable. -En déclarant les types de vos variables, cela permet aux différents outils comme les éditeurs de texte d'offrir un meilleur support. +En déclarant les types de vos variables, les éditeurs et outils peuvent vous offrir un meilleur support. -Ce chapitre n'est qu'un **tutoriel rapide / rappel** sur les annotations de type Python. -Seulement le minimum nécessaire pour les utiliser avec **FastAPI** sera couvert... ce qui est en réalité très peu. +Ceci est un **tutoriel rapide / rappel** à propos des annotations de type Python. Il couvre uniquement le minimum nécessaire pour les utiliser avec **FastAPI** ... ce qui est en réalité très peu. -**FastAPI** est totalement basé sur ces annotations de type, qui lui donnent de nombreux avantages. +**FastAPI** est totalement basé sur ces annotations de type, elles lui donnent de nombreux avantages et bénéfices. -Mais même si vous n'utilisez pas ou n'utiliserez jamais **FastAPI**, vous pourriez bénéficier d'apprendre quelques choses sur ces dernières. +Mais même si vous n'utilisez jamais **FastAPI**, vous auriez intérêt à en apprendre un peu à leur sujet. -/// note +/// note | Remarque -Si vous êtes un expert Python, et que vous savez déjà **tout** sur les annotations de type, passez au chapitre suivant. +Si vous êtes un expert Python, et que vous savez déjà tout sur les annotations de type, passez au chapitre suivant. /// -## Motivations +## Motivation { #motivation } -Prenons un exemple simple : +Commençons par un exemple simple : -{*../../docs_src/python_types/tutorial001.py*} +{* ../../docs_src/python_types/tutorial001_py39.py *} -Exécuter ce programe affiche : +Exécuter ce programme affiche : ``` John Doe ``` -La fonction : +La fonction fait ce qui suit : * Prend un `first_name` et un `last_name`. -* Convertit la première lettre de chaque paramètre en majuscules grâce à `title()`. -* Concatène les résultats avec un espace entre les deux. +* Convertit la première lettre de chacun en majuscule avec `title()`. +* Concatène ces deux valeurs avec un espace au milieu. -{*../../docs_src/python_types/tutorial001.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} -### Limitations +### Modifier le code { #edit-it } C'est un programme très simple. Mais maintenant imaginez que vous l'écriviez de zéro. -À un certain point vous auriez commencé la définition de la fonction, vous aviez les paramètres prêts. +À un certain moment, vous auriez commencé la définition de la fonction, vous aviez les paramètres prêts ... -Mais vous aviez besoin de "cette méthode qui convertit la première lettre en majuscule". +Mais ensuite vous devez appeler « cette méthode qui convertit la première lettre en majuscule ». -Était-ce `upper` ? `uppercase` ? `first_uppercase` ? `capitalize` ? +Était-ce `upper` ? Était-ce `uppercase` ? `first_uppercase` ? `capitalize` ? -Vous essayez donc d'utiliser le vieil ami du programmeur, l'auto-complétion de l'éditeur. +Vous essayez alors avec l'ami de toujours des programmeurs, l'autocomplétion de l'éditeur. -Vous écrivez le premier paramètre, `first_name`, puis un point (`.`) et appuyez sur `Ctrl+Espace` pour déclencher l'auto-complétion. +Vous tapez le premier paramètre de la fonction, `first_name`, puis un point (`.`) et appuyez sur `Ctrl+Espace` pour déclencher l'autocomplétion. -Mais malheureusement, rien d'utile n'en résulte : +Mais, malheureusement, vous n'obtenez rien d'utile : -### Ajouter des types +### Ajouter des types { #add-types } Modifions une seule ligne de la version précédente. -Nous allons changer seulement cet extrait, les paramètres de la fonction, de : - +Nous allons changer exactement ce fragment, les paramètres de la fonction, de : ```Python first_name, last_name @@ -78,222 +76,389 @@ Nous allons changer seulement cet extrait, les paramètres de la fonction, de : C'est tout. -Ce sont des annotations de types : +Ce sont les « annotations de type » : -{*../../docs_src/python_types/tutorial002.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} -À ne pas confondre avec la déclaration de valeurs par défaut comme ici : +Ce n'est pas la même chose que de déclarer des valeurs par défaut, ce qui serait : ```Python first_name="john", last_name="doe" ``` -C'est une chose différente. +C'est différent. -On utilise un deux-points (`:`), et pas un égal (`=`). +Nous utilisons des deux-points (`:`), pas des signes égal (`=`). -Et ajouter des annotations de types ne crée normalement pas de différence avec le comportement qui aurait eu lieu si elles n'étaient pas là. +Et ajouter des annotations de type ne change normalement pas ce qui se passe par rapport à ce qui se passerait sans elles. -Maintenant, imaginez que vous êtes en train de créer cette fonction, mais avec des annotations de type cette fois. +Mais maintenant, imaginez que vous êtes à nouveau en train de créer cette fonction, mais avec des annotations de type. -Au même moment que durant l'exemple précédent, vous essayez de déclencher l'auto-complétion et vous voyez : +Au même moment, vous essayez de déclencher l'autocomplétion avec `Ctrl+Espace` et vous voyez : -Vous pouvez donc dérouler les options jusqu'à trouver la méthode à laquelle vous pensiez. +Avec cela, vous pouvez faire défiler en voyant les options, jusqu'à trouver celle qui « vous dit quelque chose » : -## Plus de motivations +## Plus de motivation { #more-motivation } -Cette fonction possède déjà des annotations de type : +Regardez cette fonction, elle a déjà des annotations de type : -{*../../docs_src/python_types/tutorial003.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} -Comme l'éditeur connaît le type des variables, vous n'avez pas seulement l'auto-complétion, mais aussi de la détection d'erreurs : +Comme l'éditeur connaît les types des variables, vous n'obtenez pas seulement l'autocomplétion, vous obtenez aussi des vérifications d'erreurs : -Maintenant que vous avez connaissance du problème, convertissez `age` en chaîne de caractères grâce à `str(age)` : +Vous savez maintenant qu'il faut corriger, convertir `age` en chaîne avec `str(age)` : -{*../../docs_src/python_types/tutorial004.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} -## Déclarer des types +## Déclarer des types { #declaring-types } -Vous venez de voir là où les types sont généralement déclarés : dans les paramètres de fonctions. +Vous venez de voir l'endroit principal pour déclarer des annotations de type : dans les paramètres des fonctions. -C'est aussi ici que vous les utiliseriez avec **FastAPI**. +C'est aussi l'endroit principal où vous les utiliserez avec **FastAPI**. -### Types simples +### Types simples { #simple-types } -Vous pouvez déclarer tous les types de Python, pas seulement `str`. +Vous pouvez déclarer tous les types standards de Python, pas seulement `str`. -Comme par exemple : +Vous pouvez utiliser, par exemple : * `int` * `float` * `bool` * `bytes` -{*../../docs_src/python_types/tutorial005.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} -### Types génériques avec des paramètres de types +### Types génériques avec paramètres de type { #generic-types-with-type-parameters } -Il existe certaines structures de données qui contiennent d'autres valeurs, comme `dict`, `list`, `set` et `tuple`. Et les valeurs internes peuvent elles aussi avoir leurs propres types. +Il existe certaines structures de données qui peuvent contenir d'autres valeurs, comme `dict`, `list`, `set` et `tuple`. Et les valeurs internes peuvent aussi avoir leur propre type. -Pour déclarer ces types et les types internes, on utilise le module standard de Python `typing`. +Ces types qui ont des types internes sont appelés types « génériques ». Et il est possible de les déclarer, même avec leurs types internes. -Il existe spécialement pour supporter ces annotations de types. +Pour déclarer ces types et les types internes, vous pouvez utiliser le module standard Python `typing`. Il existe spécifiquement pour prendre en charge ces annotations de type. -#### `List` +#### Versions plus récentes de Python { #newer-versions-of-python } -Par exemple, définissons une variable comme `list` de `str`. +La syntaxe utilisant `typing` est compatible avec toutes les versions, de Python 3.6 aux plus récentes, y compris Python 3.9, Python 3.10, etc. -Importez `List` (avec un `L` majuscule) depuis `typing`. +Au fur et à mesure que Python évolue, les versions plus récentes apportent un meilleur support pour ces annotations de type et dans de nombreux cas vous n'aurez même pas besoin d'importer et d'utiliser le module `typing` pour les déclarer. -{*../../docs_src/python_types/tutorial006.py hl[1] *} +Si vous pouvez choisir une version plus récente de Python pour votre projet, vous pourrez profiter de cette simplicité supplémentaire. -Déclarez la variable, en utilisant la syntaxe des deux-points (`:`). +Dans toute la documentation, il y a des exemples compatibles avec chaque version de Python (lorsqu'il y a une différence). -Et comme type, mettez `List`. +Par exemple « Python 3.6+ » signifie que c'est compatible avec Python 3.6 ou supérieur (y compris 3.7, 3.8, 3.9, 3.10, etc.). Et « Python 3.9+ » signifie que c'est compatible avec Python 3.9 ou supérieur (y compris 3.10, etc). -Les listes étant un type contenant des types internes, mettez ces derniers entre crochets (`[`, `]`) : +Si vous pouvez utiliser les dernières versions de Python, utilisez les exemples pour la dernière version, ils auront la meilleure et la plus simple syntaxe, par exemple, « Python 3.10+ ». -{*../../docs_src/python_types/tutorial006.py hl[4] *} +#### Liste { #list } -/// tip | Astuce +Par exemple, définissons une variable comme une `list` de `str`. -Ces types internes entre crochets sont appelés des "paramètres de type". +Déclarez la variable, en utilisant la même syntaxe avec deux-points (`:`). -Ici, `str` est un paramètre de type passé à `List`. +Comme type, mettez `list`. + +Comme la liste est un type qui contient des types internes, mettez-les entre crochets : + +{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} + +/// info + +Ces types internes entre crochets sont appelés « paramètres de type ». + +Dans ce cas, `str` est le paramètre de type passé à `list`. /// -Ce qui signifie : "la variable `items` est une `list`, et chacun de ses éléments a pour type `str`. +Cela signifie : « la variable `items` est une `list`, et chacun des éléments de cette liste est un `str` ». -En faisant cela, votre éditeur pourra vous aider, même pendant que vous traitez des éléments de la liste. +En faisant cela, votre éditeur peut vous fournir de l'aide même pendant le traitement des éléments de la liste : Sans types, c'est presque impossible à réaliser. -Vous remarquerez que la variable `item` n'est qu'un des éléments de la list `items`. +Remarquez que la variable `item` est l'un des éléments de la liste `items`. -Et pourtant, l'éditeur sait qu'elle est de type `str` et pourra donc vous aider à l'utiliser. +Et pourtant, l'éditeur sait que c'est un `str` et fournit le support approprié. -#### `Tuple` et `Set` +#### Tuple et Set { #tuple-and-set } -C'est le même fonctionnement pour déclarer un `tuple` ou un `set` : +Vous feriez la même chose pour déclarer des `tuple` et des `set` : -{*../../docs_src/python_types/tutorial007.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} -Dans cet exemple : +Cela signifie : -* La variable `items_t` est un `tuple` avec 3 éléments, un `int`, un deuxième `int`, et un `str`. +* La variable `items_t` est un `tuple` avec 3 éléments, un `int`, un autre `int`, et un `str`. * La variable `items_s` est un `set`, et chacun de ses éléments est de type `bytes`. -#### `Dict` +#### Dict { #dict } -Pour définir un `dict`, il faut lui passer 2 paramètres, séparés par une virgule (`,`). +Pour définir un `dict`, vous passez 2 paramètres de type, séparés par des virgules. -Le premier paramètre de type est pour les clés et le second pour les valeurs du dictionnaire (`dict`). +Le premier paramètre de type est pour les clés du `dict`. -{*../../docs_src/python_types/tutorial008.py hl[1,4] *} +Le second paramètre de type est pour les valeurs du `dict` : -Dans cet exemple : +{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} -* La variable `prices` est de type `dict` : - * Les clés de ce dictionnaire sont de type `str`. - * Les valeurs de ce dictionnaire sont de type `float`. +Cela signifie : -#### `Optional` +* La variable `prices` est un `dict` : + * Les clés de ce `dict` sont de type `str` (disons, le nom de chaque article). + * Les valeurs de ce `dict` sont de type `float` (disons, le prix de chaque article). -Vous pouvez aussi utiliser `Optional` pour déclarer qu'une variable a un type, comme `str` mais qu'il est "optionnel" signifiant qu'il pourrait aussi être `None`. +#### Union { #union } -{*../../docs_src/python_types/tutorial009.py hl[1,4] *} +Vous pouvez déclarer qu'une variable peut être de plusieurs types, par exemple, un `int` ou un `str`. -Utiliser `Optional[str]` plutôt que `str` permettra à l'éditeur de vous aider à détecter les erreurs où vous supposeriez qu'une valeur est toujours de type `str`, alors qu'elle pourrait aussi être `None`. +Dans Python 3.6 et supérieur (y compris Python 3.10), vous pouvez utiliser le type `Union` de `typing` et mettre entre crochets les types possibles à accepter. -#### Types génériques +Dans Python 3.10, il existe aussi une nouvelle syntaxe où vous pouvez mettre les types possibles séparés par une barre verticale (`|`). -Les types qui peuvent contenir des paramètres de types entre crochets, comme : +//// tab | Python 3.10+ -* `List` -* `Tuple` -* `Set` -* `Dict` +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial008b_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial008b_py39.py!} +``` + +//// + +Dans les deux cas, cela signifie que `item` peut être un `int` ou un `str`. + +#### Possiblement `None` { #possibly-none } + +Vous pouvez déclarer qu'une valeur peut avoir un type, comme `str`, mais qu'elle peut aussi être `None`. + +Dans Python 3.6 et supérieur (y compris Python 3.10), vous pouvez le déclarer en important et en utilisant `Optional` depuis le module `typing`. + +```Python hl_lines="1 4" +{!../../docs_src/python_types/tutorial009_py39.py!} +``` + +Utiliser `Optional[str]` au lieu de simplement `str` permettra à l'éditeur de vous aider à détecter des erreurs où vous supposeriez qu'une valeur est toujours un `str`, alors qu'elle pourrait en fait aussi être `None`. + +`Optional[Something]` est en réalité un raccourci pour `Union[Something, None]`, ils sont équivalents. + +Cela signifie aussi que dans Python 3.10, vous pouvez utiliser `Something | None` : + +//// tab | Python 3.10+ + +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial009_py310.py!} +``` + +//// + +//// tab | Python 3.9+ + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial009_py39.py!} +``` + +//// + +//// tab | Python 3.9+ alternative + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial009b_py39.py!} +``` + +//// + +#### Utiliser `Union` ou `Optional` { #using-union-or-optional } + +Si vous utilisez une version de Python inférieure à 3.10, voici un conseil de mon point de vue très **subjectif** : + +* 🚨 Évitez d'utiliser `Optional[SomeType]` +* À la place ✨ **utilisez `Union[SomeType, None]`** ✨. + +Les deux sont équivalents et sous le capot ce sont les mêmes, mais je recommanderais `Union` plutôt que `Optional` parce que le mot « facultatif » semble impliquer que la valeur est optionnelle, alors que cela signifie en fait « elle peut être `None` », même si elle n'est pas facultative et est toujours requise. + +Je pense que `Union[SomeType, None]` est plus explicite sur ce que cela signifie. + +Il ne s'agit que des mots et des noms. Mais ces mots peuvent influencer la manière dont vous et vos coéquipiers pensez au code. + +Par exemple, prenons cette fonction : + +{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} + +Le paramètre `name` est défini comme `Optional[str]`, mais il n'est pas facultatif, vous ne pouvez pas appeler la fonction sans le paramètre : + +```Python +say_hi() # Oh non, cela lève une erreur ! 😱 +``` + +Le paramètre `name` est toujours requis (pas « optionnel ») parce qu'il n'a pas de valeur par défaut. Néanmoins, `name` accepte `None` comme valeur : + +```Python +say_hi(name=None) # Cela fonctionne, None est valide 🎉 +``` + +La bonne nouvelle est que, dès que vous êtes sur Python 3.10, vous n'avez plus à vous en soucier, car vous pourrez simplement utiliser `|` pour définir des unions de types : + +{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} + +Et alors vous n'aurez plus à vous soucier de noms comme `Optional` et `Union`. 😎 + +#### Types génériques { #generic-types } + +Ces types qui prennent des paramètres de type entre crochets sont appelés des **types génériques** ou **Generics**, par exemple : + +//// tab | Python 3.10+ + +Vous pouvez utiliser les mêmes types intégrés comme génériques (avec des crochets et des types à l'intérieur) : + +* `list` +* `tuple` +* `set` +* `dict` + +Et, comme avec les versions précédentes de Python, depuis le module `typing` : + +* `Union` * `Optional` -* ...et d'autres. +* ... et d'autres. -sont appelés des **types génériques** ou **Generics**. +Dans Python 3.10, comme alternative à l'utilisation des génériques `Union` et `Optional`, vous pouvez utiliser la barre verticale (`|`) pour déclarer des unions de types, c'est bien mieux et plus simple. -### Classes en tant que types +//// + +//// tab | Python 3.9+ + +Vous pouvez utiliser les mêmes types intégrés comme génériques (avec des crochets et des types à l'intérieur) : + +* `list` +* `tuple` +* `set` +* `dict` + +Et des génériques depuis le module `typing` : + +* `Union` +* `Optional` +* ... et d'autres. + +//// + +### Classes en tant que types { #classes-as-types } Vous pouvez aussi déclarer une classe comme type d'une variable. -Disons que vous avez une classe `Person`, avec une variable `name` : - -{*../../docs_src/python_types/tutorial010.py hl[1:3] *} +Disons que vous avez une classe `Person`, avec un nom : +{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} Vous pouvez ensuite déclarer une variable de type `Person` : -{*../../docs_src/python_types/tutorial010.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} -Et vous aurez accès, encore une fois, au support complet offert par l'éditeur : +Et là encore, vous obtenez tout le support de l'éditeur : -## Les modèles Pydantic +Remarquez que cela signifie « `one_person` est une instance de la classe `Person` ». + +Cela ne signifie pas « `one_person` est la classe appelée `Person` ». + +## Modèles Pydantic { #pydantic-models } Pydantic est une bibliothèque Python pour effectuer de la validation de données. -Vous déclarez la forme de la donnée avec des classes et des attributs. +Vous déclarez la « forme » de la donnée sous forme de classes avec des attributs. -Chaque attribut possède un type. +Et chaque attribut a un type. -Puis vous créez une instance de cette classe avec certaines valeurs et **Pydantic** validera les valeurs, les convertira dans le type adéquat (si c'est nécessaire et possible) et vous donnera un objet avec toute la donnée. +Ensuite, vous créez une instance de cette classe avec certaines valeurs et elle validera les valeurs, les convertira dans le type approprié (le cas échéant) et vous donnera un objet avec toutes les données. -Ainsi, votre éditeur vous offrira un support adapté pour l'objet résultant. +Et vous obtenez tout le support de l'éditeur avec cet objet résultant. -Extrait de la documentation officielle de **Pydantic** : +Un exemple tiré de la documentation officielle de Pydantic : -{*../../docs_src/python_types/tutorial011.py*} +{* ../../docs_src/python_types/tutorial011_py310.py *} /// info -Pour en savoir plus à propos de Pydantic, allez jeter un coup d'oeil à sa documentation. +Pour en savoir plus à propos de Pydantic, consultez sa documentation. /// -**FastAPI** est basé entièrement sur **Pydantic**. +**FastAPI** est entièrement basé sur Pydantic. -Vous verrez bien plus d'exemples de son utilisation dans [Tutoriel - Guide utilisateur](tutorial/index.md){.internal-link target=_blank}. +Vous verrez beaucoup plus de tout cela en pratique dans le [Tutoriel - Guide utilisateur](tutorial/index.md){.internal-link target=_blank}. -## Les annotations de type dans **FastAPI** +/// tip | Astuce -**FastAPI** utilise ces annotations pour faire différentes choses. +Pydantic a un comportement spécial lorsque vous utilisez `Optional` ou `Union[Something, None]` sans valeur par défaut, vous pouvez en lire davantage dans la documentation de Pydantic à propos des champs Optionals requis. -Avec **FastAPI**, vous déclarez des paramètres grâce aux annotations de types et vous obtenez : +/// -* **du support de l'éditeur** -* **de la vérification de types** +## Annotations de type avec métadonnées { #type-hints-with-metadata-annotations } -...et **FastAPI** utilise ces mêmes déclarations pour : +Python dispose également d'une fonctionnalité qui permet de mettre des **métadonnées supplémentaires** dans ces annotations de type en utilisant `Annotated`. -* **Définir les prérequis** : depuis les paramètres de chemins des requêtes, les entêtes, les corps, les dépendances, etc. -* **Convertir des données** : depuis la requête vers les types requis. -* **Valider des données** : venant de chaque requête : - * Générant automatiquement des **erreurs** renvoyées au client quand la donnée est invalide. +Depuis Python 3.9, `Annotated` fait partie de la bibliothèque standard, vous pouvez donc l'importer depuis `typing`. + +{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} + +Python lui-même ne fait rien avec ce `Annotated`. Et pour les éditeurs et autres outils, le type est toujours `str`. + +Mais vous pouvez utiliser cet espace dans `Annotated` pour fournir à **FastAPI** des métadonnées supplémentaires sur la façon dont vous voulez que votre application se comporte. + +L'important à retenir est que le premier paramètre de type que vous passez à `Annotated` est le type réel. Le reste n'est que des métadonnées pour d'autres outils. + +Pour l'instant, vous avez juste besoin de savoir que `Annotated` existe, et que c'est du Python standard. 😎 + +Plus tard, vous verrez à quel point cela peut être puissant. + +/// tip | Astuce + +Le fait que ce soit du Python standard signifie que vous bénéficierez toujours de la meilleure expérience développeur possible dans votre éditeur, avec les outils que vous utilisez pour analyser et refactoriser votre code, etc. ✨ + +Et aussi que votre code sera très compatible avec de nombreux autres outils et bibliothèques Python. 🚀 + +/// + +## Annotations de type dans **FastAPI** { #type-hints-in-fastapi } + +**FastAPI** tire parti de ces annotations de type pour faire plusieurs choses. + +Avec **FastAPI**, vous déclarez des paramètres avec des annotations de type et vous obtenez : + +* **Du support de l'éditeur**. +* **Des vérifications de types**. + +... et **FastAPI** utilise les mêmes déclarations pour : + +* **Définir des prérequis** : à partir des paramètres de chemin de la requête, des paramètres de requête, des en-têtes, des corps, des dépendances, etc. +* **Convertir des données** : de la requête vers le type requis. +* **Valider des données** : provenant de chaque requête : + * En générant des **erreurs automatiques** renvoyées au client lorsque la donnée est invalide. * **Documenter** l'API avec OpenAPI : - * ce qui ensuite utilisé par les interfaces utilisateur automatiques de documentation interactive. + * ce qui est ensuite utilisé par les interfaces utilisateur de documentation interactive automatiques. -Tout cela peut paraître bien abstrait, mais ne vous inquiétez pas, vous verrez tout ça en pratique dans [Tutoriel - Guide utilisateur](tutorial/index.md){.internal-link target=_blank}. +Tout cela peut sembler abstrait. Ne vous inquiétez pas. Vous verrez tout cela en action dans le [Tutoriel - Guide utilisateur](tutorial/index.md){.internal-link target=_blank}. -Ce qu'il faut retenir c'est qu'en utilisant les types standard de Python, à un seul endroit (plutôt que d'ajouter plus de classes, de décorateurs, etc.), **FastAPI** fera une grande partie du travail pour vous. +L'important est qu'en utilisant les types standards de Python, en un seul endroit (au lieu d'ajouter plus de classes, de décorateurs, etc.), **FastAPI** fera une grande partie du travail pour vous. /// info -Si vous avez déjà lu le tutoriel et êtes revenus ici pour voir plus sur les types, une bonne ressource est la "cheat sheet" de `mypy`. +Si vous avez déjà parcouru tout le tutoriel et êtes revenu pour en voir plus sur les types, une bonne ressource est l'« aide-mémoire » de `mypy`. /// diff --git a/docs/fr/docs/tutorial/background-tasks.md b/docs/fr/docs/tutorial/background-tasks.md index 6efd16e07..ed7494669 100644 --- a/docs/fr/docs/tutorial/background-tasks.md +++ b/docs/fr/docs/tutorial/background-tasks.md @@ -1,4 +1,4 @@ -# Tâches d'arrière-plan +# Tâches d'arrière-plan { #background-tasks } Vous pouvez définir des tâches d'arrière-plan qui seront exécutées après avoir retourné une réponse. @@ -7,20 +7,19 @@ Ceci est utile pour les opérations qui doivent avoir lieu après une requête, Cela comprend, par exemple : * Les notifications par email envoyées après l'exécution d'une action : - * Étant donné que se connecter à un serveur et envoyer un email a tendance à être «lent» (plusieurs secondes), vous pouvez retourner la réponse directement et envoyer la notification en arrière-plan. + * Étant donné que se connecter à un serveur et envoyer un email a tendance à être « lent » (plusieurs secondes), vous pouvez retourner la réponse directement et envoyer la notification en arrière-plan. * Traiter des données : - * Par exemple, si vous recevez un fichier qui doit passer par un traitement lent, vous pouvez retourner une réponse «Accepted» (HTTP 202) puis faire le traitement en arrière-plan. + * Par exemple, si vous recevez un fichier qui doit passer par un traitement lent, vous pouvez retourner une réponse « Accepted » (HTTP 202) puis faire le traitement en arrière-plan. +## Utiliser `BackgroundTasks` { #using-backgroundtasks } -## Utiliser `BackgroundTasks` +Pour commencer, importez `BackgroundTasks` et définissez un paramètre dans votre *fonction de chemin d'accès* avec `BackgroundTasks` comme type déclaré. -Pour commencer, importez `BackgroundTasks` et définissez un paramètre dans votre *fonction de chemin* avec `BackgroundTasks` comme type déclaré. - -{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} **FastAPI** créera l'objet de type `BackgroundTasks` pour vous et le passera comme paramètre. -## Créer une fonction de tâche +## Créer une fonction de tâche { #create-a-task-function } Une fonction à exécuter comme tâche d'arrière-plan est juste une fonction standard qui peut recevoir des paramètres. @@ -30,14 +29,13 @@ Dans cet exemple, la fonction de tâche écrira dans un fichier (afin de simuler L'opération d'écriture n'utilisant ni `async` ni `await`, on définit la fonction avec un `def` normal. -{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} -## Ajouter une tâche d'arrière-plan +## Ajouter une tâche d'arrière-plan { #add-the-background-task } -Dans votre *fonction de chemin*, passez votre fonction de tâche à l'objet de type `BackgroundTasks` (`background_tasks` ici) grâce à la méthode `.add_task()` : +Dans votre *fonction de chemin d'accès*, passez votre fonction de tâche à l'objet de type `BackgroundTasks` (`background_tasks` ici) grâce à la méthode `.add_task()` : - -{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} `.add_task()` reçoit comme arguments : @@ -45,40 +43,40 @@ Dans votre *fonction de chemin*, passez votre fonction de tâche à l'objet de t * Les arguments positionnels à passer à la fonction de tâche dans l'ordre (`email`). * Les arguments nommés à passer à la fonction de tâche (`message="some notification"`). -## Injection de dépendances +## Injection de dépendances { #dependency-injection } -Utiliser `BackgroundTasks` fonctionne aussi avec le système d'injection de dépendances. Vous pouvez déclarer un paramètre de type `BackgroundTasks` à différents niveaux : dans une *fonction de chemin*, dans une dépendance, dans une sous-dépendance... +Utiliser `BackgroundTasks` fonctionne aussi avec le système d'injection de dépendances. Vous pouvez déclarer un paramètre de type `BackgroundTasks` à différents niveaux : dans une *fonction de chemin d'accès*, dans une dépendance (dependable), dans une sous-dépendance, etc. -**FastAPI** sait quoi faire dans chaque cas et comment réutiliser le même objet, afin que tous les paramètres de type `BackgroundTasks` soient fusionnés et que les tâches soient exécutées en arrière-plan : +**FastAPI** sait quoi faire dans chaque cas et comment réutiliser le même objet, afin que toutes les tâches d'arrière-plan soient fusionnées et que les tâches soient ensuite exécutées en arrière-plan : -{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *} +{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *} Dans cet exemple, les messages seront écrits dans le fichier `log.txt` après que la réponse soit envoyée. -S'il y avait une `query` (paramètre nommé `q`) dans la requête, alors elle sera écrite dans `log.txt` via une tâche d'arrière-plan. +S'il y avait un paramètre de requête dans la requête, alors il sera écrit dans le journal via une tâche d'arrière-plan. -Et ensuite une autre tâche d'arrière-plan (générée dans les paramètres de la *la fonction de chemin*) écrira un message dans `log.txt` comprenant le paramètre de chemin `email`. +Et ensuite une autre tâche d'arrière-plan (générée dans la *fonction de chemin d'accès*) écrira un message comprenant le paramètre de chemin `email`. -## Détails techniques +## Détails techniques { #technical-details } La classe `BackgroundTasks` provient directement de `starlette.background`. Elle est importée/incluse directement dans **FastAPI** pour que vous puissiez l'importer depuis `fastapi` et éviter d'importer accidentellement `BackgroundTask` (sans `s` à la fin) depuis `starlette.background`. -En utilisant seulement `BackgroundTasks` (et non `BackgroundTask`), il est possible de l'utiliser en tant que paramètre de *fonction de chemin* et de laisser **FastAPI** gérer le reste pour vous, comme en utilisant l'objet `Request` directement. +En utilisant seulement `BackgroundTasks` (et non `BackgroundTask`), il est possible de l'utiliser en tant que paramètre de *fonction de chemin d'accès* et de laisser **FastAPI** gérer le reste pour vous, comme en utilisant l'objet `Request` directement. Il est tout de même possible d'utiliser `BackgroundTask` seul dans **FastAPI**, mais dans ce cas il faut créer l'objet dans le code et renvoyer une `Response` Starlette l'incluant. -Plus de détails sont disponibles dans la documentation officielle de Starlette sur les tâches d'arrière-plan (via leurs classes `BackgroundTasks`et `BackgroundTask`). +Plus de détails sont disponibles dans la documentation officielle de Starlette sur les tâches d'arrière-plan. -## Avertissement +## Avertissement { #caveat } Si vous avez besoin de réaliser des traitements lourds en tâche d'arrière-plan et que vous n'avez pas besoin que ces traitements aient lieu dans le même process (par exemple, pas besoin de partager la mémoire, les variables, etc.), il peut s'avérer profitable d'utiliser des outils plus importants tels que Celery. -Ces outils nécessitent généralement des configurations plus complexes ainsi qu'un gestionnaire de queue de message, comme RabbitMQ ou Redis, mais ils permettent d'exécuter des tâches d'arrière-plan dans différents process, et potentiellement, sur plusieurs serveurs. +Ces outils nécessitent généralement des configurations plus complexes ainsi qu'un gestionnaire de queue de message, comme RabbitMQ ou Redis, mais ils permettent d'exécuter des tâches d'arrière-plan dans différents process, et surtout, sur plusieurs serveurs. Mais si vous avez besoin d'accéder aux variables et objets de la même application **FastAPI**, ou si vous avez besoin d'effectuer de petites tâches d'arrière-plan (comme envoyer des notifications par email), vous pouvez simplement vous contenter d'utiliser `BackgroundTasks`. -## Résumé +## Résumé { #recap } -Importez et utilisez `BackgroundTasks` grâce aux paramètres de *fonction de chemin* et les dépendances pour ajouter des tâches d'arrière-plan. +Importez et utilisez `BackgroundTasks` grâce aux paramètres de *fonction de chemin d'accès* et les dépendances pour ajouter des tâches d'arrière-plan. diff --git a/docs/fr/docs/tutorial/body-multiple-params.md b/docs/fr/docs/tutorial/body-multiple-params.md index 0541acc74..9aed24e98 100644 --- a/docs/fr/docs/tutorial/body-multiple-params.md +++ b/docs/fr/docs/tutorial/body-multiple-params.md @@ -1,24 +1,24 @@ -# Body - Paramètres multiples +# Body - Paramètres multiples { #body-multiple-parameters } -Maintenant que nous avons vu comment manipuler `Path` et `Query`, voyons comment faire pour le corps d'une requête, communément désigné par le terme anglais "body". +Maintenant que nous avons vu comment utiliser `Path` et `Query`, voyons des usages plus avancés des déclarations de paramètres du corps de la requête. -## Mélanger les paramètres `Path`, `Query` et body +## Mélanger les paramètres `Path`, `Query` et body { #mix-path-query-and-body-parameters } -Tout d'abord, sachez que vous pouvez mélanger les déclarations des paramètres `Path`, `Query` et body, **FastAPI** saura quoi faire. +Tout d'abord, sachez que vous pouvez mélanger librement les déclarations des paramètres `Path`, `Query` et du body, **FastAPI** saura quoi faire. -Vous pouvez également déclarer des paramètres body comme étant optionnels, en leur assignant une valeur par défaut à `None` : +Et vous pouvez également déclarer des paramètres du body comme étant optionnels, en leur assignant une valeur par défaut à `None` : {* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} -/// note +/// note | Remarque -Notez que, dans ce cas, le paramètre `item` provenant du `Body` est optionnel (sa valeur par défaut est `None`). +Notez que, dans ce cas, l'élément `item` récupéré depuis le body est optionnel. Comme sa valeur par défaut est `None`. /// -## Paramètres multiples du body +## Paramètres multiples du body { #multiple-body-parameters } -Dans l'exemple précédent, les opérations de routage attendaient un body JSON avec les attributs d'un `Item`, par exemple : +Dans l'exemple précédent, les chemins d'accès attendraient un body JSON avec les attributs d'un `Item`, par exemple : ```JSON { @@ -29,13 +29,13 @@ Dans l'exemple précédent, les opérations de routage attendaient un body JSON } ``` -Mais vous pouvez également déclarer plusieurs paramètres provenant de body, par exemple `item` et `user` simultanément : +Mais vous pouvez également déclarer plusieurs paramètres provenant du body, par exemple `item` et `user` : {* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *} -Dans ce cas, **FastAPI** détectera qu'il y a plus d'un paramètre dans le body (chacun correspondant à un modèle Pydantic). +Dans ce cas, **FastAPI** détectera qu'il y a plus d'un paramètre du body dans la fonction (il y a deux paramètres qui sont des modèles Pydantic). -Il utilisera alors les noms des paramètres comme clés, et s'attendra à recevoir quelque chose de semblable à : +Il utilisera alors les noms des paramètres comme clés (noms de champs) dans le body, et s'attendra à recevoir un body semblable à : ```JSON { @@ -52,25 +52,25 @@ Il utilisera alors les noms des paramètres comme clés, et s'attendra à recevo } ``` -/// note +/// note | Remarque -"Notez que, bien que nous ayons déclaré le paramètre `item` de la même manière que précédemment, il est maintenant associé à la clé `item` dans le corps de la requête."`. +Notez que, bien que `item` ait été déclaré de la même manière qu'auparavant, il est désormais attendu à l'intérieur du body sous la clé `item`. /// -**FastAPI** effectue la conversion de la requête de façon transparente, de sorte que les objets `item` et `user` se trouvent correctement définis. +**FastAPI** effectuera la conversion automatique depuis la requête, de sorte que le paramètre `item` reçoive son contenu spécifique, et de même pour `user`. -Il effectue également la validation des données (même imbriquées les unes dans les autres), et permet de les documenter correctement (schéma OpenAPI et documentation auto-générée). +Il effectuera la validation des données composées, et les documentera ainsi pour le schéma OpenAPI et la documentation automatique. -## Valeurs scalaires dans le body +## Valeurs singulières dans le body { #singular-values-in-body } -De la même façon qu'il existe `Query` et `Path` pour définir des données supplémentaires pour les paramètres query et path, **FastAPI** fournit un équivalent `Body`. +De la même façon qu'il existe `Query` et `Path` pour définir des données supplémentaires pour les paramètres de requête et de chemin, **FastAPI** fournit un équivalent `Body`. -Par exemple, en étendant le modèle précédent, vous pouvez vouloir ajouter un paramètre `importance` dans le même body, en plus des paramètres `item` et `user`. +Par exemple, en étendant le modèle précédent, vous pourriez décider d'avoir une autre clé `importance` dans le même body, en plus de `item` et `user`. -Si vous le déclarez tel quel, comme c'est une valeur [scalaire](https://docs.github.com/fr/graphql/reference/scalars), **FastAPI** supposera qu'il s'agit d'un paramètre de requête (`Query`). +Si vous le déclarez tel quel, comme c'est une valeur singulière, **FastAPI** supposera qu'il s'agit d'un paramètre de requête. -Mais vous pouvez indiquer à **FastAPI** de la traiter comme une variable de body en utilisant `Body` : +Mais vous pouvez indiquer à **FastAPI** de la traiter comme une autre clé du body en utilisant `Body` : {* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *} @@ -92,51 +92,51 @@ Dans ce cas, **FastAPI** s'attendra à un body semblable à : } ``` -Encore une fois, cela convertira les types de données, les validera, permettra de générer la documentation, etc... +Encore une fois, il convertira les types de données, validera, documentera, etc. -## Paramètres multiples body et query +## Paramètres multiples du body et paramètres de requête { #multiple-body-params-and-query } -Bien entendu, vous pouvez déclarer autant de paramètres que vous le souhaitez, en plus des paramètres body déjà déclarés. +Bien entendu, vous pouvez également déclarer des paramètres de requête supplémentaires quand vous en avez besoin, en plus de tout paramètre du body. -Par défaut, les valeurs [scalaires](https://docs.github.com/fr/graphql/reference/scalars) sont interprétées comme des paramètres query, donc inutile d'ajouter explicitement `Query`. Vous pouvez juste écrire : - -```Python -q: Union[str, None] = None -``` - -Ou bien, en Python 3.10 et supérieur : +Comme, par défaut, les valeurs singulières sont interprétées comme des paramètres de requête, vous n'avez pas besoin d'ajouter explicitement `Query`, vous pouvez simplement écrire : ```Python q: str | None = None ``` +Ou en Python 3.9 : + +```Python +q: Union[str, None] = None +``` + Par exemple : -{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[27] *} +{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} /// info -`Body` possède les mêmes paramètres de validation additionnels et de gestion des métadonnées que `Query` et `Path`, ainsi que d'autres que nous verrons plus tard. +`Body` possède également les mêmes paramètres supplémentaires de validation et de métadonnées que `Query`, `Path` et d'autres que vous verrez plus tard. /// -## Inclure un paramètre imbriqué dans le body +## Intégrer un seul paramètre du body { #embed-a-single-body-parameter } -Disons que vous avez seulement un paramètre `item` dans le body, correspondant à un modèle Pydantic `Item`. +Supposons que vous n'ayez qu'un seul paramètre `item` dans le body, provenant d'un modèle Pydantic `Item`. -Par défaut, **FastAPI** attendra sa déclaration directement dans le body. +Par défaut, **FastAPI** attendra alors son contenu directement. -Cependant, si vous souhaitez qu'il interprête correctement un JSON avec une clé `item` associée au contenu du modèle, comme cela serait le cas si vous déclariez des paramètres body additionnels, vous pouvez utiliser le paramètre spécial `embed` de `Body` : +Mais si vous voulez qu'il attende un JSON avec une clé `item` contenant le contenu du modèle, comme lorsqu'on déclare des paramètres supplémentaires du body, vous pouvez utiliser le paramètre spécial `embed` de `Body` : ```Python item: Item = Body(embed=True) ``` -Voici un exemple complet : +comme dans : {* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *} -Dans ce cas **FastAPI** attendra un body semblable à : +Dans ce cas **FastAPI** s'attendra à un body semblable à : ```JSON hl_lines="2" { @@ -160,12 +160,12 @@ au lieu de : } ``` -## Pour résumer +## Récapitulatif { #recap } -Vous pouvez ajouter plusieurs paramètres body dans votre fonction de routage, même si une requête ne peut avoir qu'un seul body. +Vous pouvez ajouter plusieurs paramètres du body à votre fonction de chemin d'accès, même si une requête ne peut avoir qu'un seul body. -Cependant, **FastAPI** se chargera de faire opérer sa magie, afin de toujours fournir à votre fonction des données correctes, les validera et documentera le schéma associé. +Mais **FastAPI** s'en chargera, vous fournira les bonnes données dans votre fonction, et validera et documentera le schéma correct dans le chemin d'accès. -Vous pouvez également déclarer des valeurs [scalaires](https://docs.github.com/fr/graphql/reference/scalars) à recevoir dans le body. +Vous pouvez également déclarer des valeurs singulières à recevoir dans le body. -Et vous pouvez indiquer à **FastAPI** d'inclure le body dans une autre variable, même lorsqu'un seul paramètre est déclaré. +Et vous pouvez indiquer à **FastAPI** d'intégrer le body sous une clé même lorsqu'un seul paramètre est déclaré. diff --git a/docs/fr/docs/tutorial/body.md b/docs/fr/docs/tutorial/body.md index 760b6d80a..ca115fabc 100644 --- a/docs/fr/docs/tutorial/body.md +++ b/docs/fr/docs/tutorial/body.md @@ -1,10 +1,10 @@ -# Corps de la requête +# Corps de la requête { #request-body } Quand vous avez besoin d'envoyer de la donnée depuis un client (comme un navigateur) vers votre API, vous l'envoyez en tant que **corps de requête**. Le corps d'une **requête** est de la donnée envoyée par le client à votre API. Le corps d'une **réponse** est la donnée envoyée par votre API au client. -Votre API aura presque toujours à envoyer un corps de **réponse**. Mais un client n'a pas toujours à envoyer un corps de **requête**. +Votre API aura presque toujours à envoyer un corps de **réponse**. Mais un client n'a pas toujours à envoyer un **corps de requête** : parfois il demande seulement un chemin, peut-être avec quelques paramètres de requête, mais n'envoie pas de corps. Pour déclarer un corps de **requête**, on utilise les modèles de Pydantic en profitant de tous leurs avantages et fonctionnalités. @@ -18,23 +18,23 @@ Ceci étant découragé, la documentation interactive générée par Swagger UI /// -## Importez le `BaseModel` de Pydantic +## Importer le `BaseModel` de Pydantic { #import-pydantics-basemodel } Commencez par importer la classe `BaseModel` du module `pydantic` : -{* ../../docs_src/body/tutorial001.py hl[4] *} +{* ../../docs_src/body/tutorial001_py310.py hl[2] *} -## Créez votre modèle de données +## Créer votre modèle de données { #create-your-data-model } Déclarez ensuite votre modèle de données en tant que classe qui hérite de `BaseModel`. Utilisez les types Python standard pour tous les attributs : -{* ../../docs_src/body/tutorial001.py hl[7:11] *} +{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} -Tout comme pour la déclaration de paramètres de requête, quand un attribut de modèle a une valeur par défaut, il n'est pas nécessaire. Sinon, cet attribut doit être renseigné dans le corps de la requête. Pour rendre ce champ optionnel simplement, utilisez `None` comme valeur par défaut. +Tout comme pour la déclaration de paramètres de requête, quand un attribut de modèle a une valeur par défaut, il n'est pas nécessaire. Sinon, cet attribut doit être renseigné dans le corps de la requête. Utilisez `None` pour le rendre simplement optionnel. -Par exemple, le modèle ci-dessus déclare un "objet" JSON (ou `dict` Python) tel que : +Par exemple, le modèle ci-dessus déclare un JSON « `object` » (ou `dict` Python) tel que : ```JSON { @@ -45,7 +45,7 @@ Par exemple, le modèle ci-dessus déclare un "objet" JSON (ou `dict` Python) te } ``` -...`description` et `tax` étant des attributs optionnels (avec `None` comme valeur par défaut), cet "objet" JSON serait aussi valide : +... `description` et `tax` étant des attributs optionnels (avec `None` comme valeur par défaut), ce JSON « `object` » serait aussi valide : ```JSON { @@ -54,28 +54,28 @@ Par exemple, le modèle ci-dessus déclare un "objet" JSON (ou `dict` Python) te } ``` -## Déclarez-le comme paramètre +## Le déclarer comme paramètre { #declare-it-as-a-parameter } Pour l'ajouter à votre *opération de chemin*, déclarez-le comme vous déclareriez des paramètres de chemin ou de requête : -{* ../../docs_src/body/tutorial001.py hl[18] *} +{* ../../docs_src/body/tutorial001_py310.py hl[16] *} -...et déclarez que son type est le modèle que vous avez créé : `Item`. +... et déclarez que son type est le modèle que vous avez créé : `Item`. -## Résultats +## Résultats { #results } En utilisant uniquement les déclarations de type Python, **FastAPI** réussit à : * Lire le contenu de la requête en tant que JSON. * Convertir les types correspondants (si nécessaire). * Valider la donnée. - * Si la donnée est invalide, une erreur propre et claire sera renvoyée, indiquant exactement où était la donnée incorrecte. + * Si la donnée est invalide, une erreur propre et claire sera renvoyée, indiquant exactement où et quelle était la donnée incorrecte. * Passer la donnée reçue dans le paramètre `item`. - * Ce paramètre ayant été déclaré dans la fonction comme étant de type `Item`, vous aurez aussi tout le support offert par l'éditeur (auto-complétion, etc.) pour tous les attributs de ce paramètre et les types de ces attributs. -* Générer des définitions JSON Schema pour votre modèle, qui peuvent être utilisées où vous en avez besoin dans votre projet ensuite. -* Ces schémas participeront à la constitution du schéma généré OpenAPI, et seront donc utilisés par les documentations automatiquement générées. + * Ce paramètre ayant été déclaré dans la fonction comme étant de type `Item`, vous aurez aussi tout le support offert par l'éditeur (autocomplétion, etc.) pour tous les attributs de ce paramètre et les types de ces attributs. +* Générer des définitions JSON Schema pour votre modèle ; vous pouvez également les utiliser partout ailleurs si cela a du sens pour votre projet. +* Ces schémas participeront à la constitution du schéma généré OpenAPI, et seront utilisés par les documentations automatiques UIs. -## Documentation automatique +## Documentation automatique { #automatic-docs } Les schémas JSON de vos modèles seront intégrés au schéma OpenAPI global de votre application, et seront donc affichés dans la documentation interactive de l'API : @@ -85,63 +85,63 @@ Et seront aussi utilisés dans chaque *opération de chemin* de la documentation -## Support de l'éditeur +## Support de l'éditeur { #editor-support } -Dans votre éditeur, vous aurez des annotations de types et de l'auto-complétion partout dans votre fonction (ce qui n'aurait pas été le cas si vous aviez utilisé un classique `dict` plutôt qu'un modèle Pydantic) : +Dans votre éditeur, vous aurez des annotations de type et de l'autocomplétion partout dans votre fonction (ce qui n'aurait pas été le cas si vous aviez reçu un `dict` plutôt qu'un modèle Pydantic) : -Et vous obtenez aussi de la vérification d'erreur pour les opérations incorrectes de types : +Et vous obtenez aussi des vérifications d'erreurs pour les opérations de types incorrectes : Ce n'est pas un hasard, ce framework entier a été bâti avec ce design comme objectif. -Et cela a été rigoureusement testé durant la phase de design, avant toute implémentation, pour s'assurer que cela fonctionnerait avec tous les éditeurs. +Et cela a été rigoureusement testé durant la phase de design, avant toute implémentation, pour vous assurer que cela fonctionnerait avec tous les éditeurs. Des changements sur Pydantic ont même été faits pour supporter cela. -Les captures d'écrans précédentes ont été prises sur Visual Studio Code. +Les captures d'écran précédentes ont été prises sur Visual Studio Code. -Mais vous auriez le même support de l'éditeur avec PyCharm et la majorité des autres éditeurs de code Python. +Mais vous auriez le même support de l'éditeur avec PyCharm et la majorité des autres éditeurs de code Python : /// tip | Astuce -Si vous utilisez PyCharm comme éditeur, vous pouvez utiliser le Plugin Pydantic PyCharm Plugin. +Si vous utilisez PyCharm comme éditeur, vous pouvez utiliser le plug-in Pydantic PyCharm Plugin. Ce qui améliore le support pour les modèles Pydantic avec : -* de l'auto-complétion +* de l'autocomplétion * des vérifications de type -* du "refactoring" (ou remaniement de code) +* du « refactoring » (ou remaniement de code) * de la recherche -* de l'inspection +* des inspections /// -## Utilisez le modèle +## Utiliser le modèle { #use-the-model } Dans la fonction, vous pouvez accéder à tous les attributs de l'objet du modèle directement : -{* ../../docs_src/body/tutorial002.py hl[21] *} +{* ../../docs_src/body/tutorial002_py310.py *} -## Corps de la requête + paramètres de chemin +## Corps de la requête + paramètres de chemin { #request-body-path-parameters } Vous pouvez déclarer des paramètres de chemin et un corps de requête pour la même *opération de chemin*. **FastAPI** est capable de reconnaître que les paramètres de la fonction qui correspondent aux paramètres de chemin doivent être **récupérés depuis le chemin**, et que les paramètres de fonctions déclarés comme modèles Pydantic devraient être **récupérés depuis le corps de la requête**. -{* ../../docs_src/body/tutorial003.py hl[17:18] *} +{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} -## Corps de la requête + paramètres de chemin et de requête +## Corps de la requête + paramètres de chemin et de requête { #request-body-path-query-parameters } Vous pouvez aussi déclarer un **corps**, et des paramètres de **chemin** et de **requête** dans la même *opération de chemin*. **FastAPI** saura reconnaître chacun d'entre eux et récupérer la bonne donnée au bon endroit. -{* ../../docs_src/body/tutorial004.py hl[18] *} +{* ../../docs_src/body/tutorial004_py310.py hl[16] *} Les paramètres de la fonction seront reconnus comme tel : @@ -149,14 +149,16 @@ Les paramètres de la fonction seront reconnus comme tel : * Si le paramètre est d'un **type singulier** (comme `int`, `float`, `str`, `bool`, etc.), il sera interprété comme un paramètre de **requête**. * Si le paramètre est déclaré comme ayant pour type un **modèle Pydantic**, il sera interprété comme faisant partie du **corps** de la requête. -/// note +/// note | Remarque -**FastAPI** saura que la valeur de `q` n'est pas requise grâce à la valeur par défaut `=None`. +**FastAPI** saura que la valeur de `q` n'est pas requise grâce à la valeur par défaut `= None`. -Le type `Optional` dans `Optional[str]` n'est pas utilisé par **FastAPI**, mais sera utile à votre éditeur pour améliorer le support offert par ce dernier et détecter plus facilement des erreurs de type. +L'annotation de type `str | None` (Python 3.10+) ou `Union` dans `Union[str, None]` (Python 3.9+) n'est pas utilisée par **FastAPI** pour déterminer que la valeur n'est pas requise, il le saura parce qu'elle a une valeur par défaut `= None`. + +Mais ajouter ces annotations de type permettra à votre éditeur de vous offrir un meilleur support et de détecter des erreurs. /// -## Sans Pydantic +## Sans Pydantic { #without-pydantic } -Si vous ne voulez pas utiliser des modèles Pydantic, vous pouvez aussi utiliser des paramètres de **Corps**. Pour cela, allez voir la partie de la documentation sur [Corps de la requête - Paramètres multiples](body-multiple-params.md){.internal-link target=_blank}. +Si vous ne voulez pas utiliser des modèles Pydantic, vous pouvez aussi utiliser des paramètres de **Body**. Pour cela, allez voir la documentation sur [Corps de la requête - Paramètres multiples : Valeurs singulières dans le corps](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. diff --git a/docs/fr/docs/tutorial/debugging.md b/docs/fr/docs/tutorial/debugging.md index ab00fbdeb..a88fa2b23 100644 --- a/docs/fr/docs/tutorial/debugging.md +++ b/docs/fr/docs/tutorial/debugging.md @@ -1,14 +1,14 @@ -# Débogage +# Débogage { #debugging } Vous pouvez connecter le débogueur dans votre éditeur, par exemple avec Visual Studio Code ou PyCharm. -## Faites appel à `uvicorn` +## Appeler `uvicorn` { #call-uvicorn } Dans votre application FastAPI, importez et exécutez directement `uvicorn` : -{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} -### À propos de `__name__ == "__main__"` +### À propos de `__name__ == "__main__"` { #about-name-main } Le but principal de `__name__ == "__main__"` est d'avoir du code qui est exécuté lorsque votre fichier est appelé avec : @@ -26,7 +26,7 @@ mais qui n'est pas appelé lorsqu'un autre fichier l'importe, comme dans : from myapp import app ``` -#### Pour davantage de détails +#### Pour davantage de détails { #more-details } Imaginons que votre fichier s'appelle `myapp.py`. @@ -78,7 +78,7 @@ Pour plus d'informations, consultez débogueur +## Exécuter votre code avec votre débogueur { #run-your-code-with-your-debugger } Parce que vous exécutez le serveur Uvicorn directement depuis votre code, vous pouvez appeler votre programme Python (votre application FastAPI) directement depuis le débogueur. @@ -86,10 +86,10 @@ Parce que vous exécutez le serveur Uvicorn directement depuis votre code, vous Par exemple, dans Visual Studio Code, vous pouvez : -- Cliquer sur l'onglet "Debug" de la barre d'activités de Visual Studio Code. -- "Add configuration...". -- Sélectionnez "Python". -- Lancez le débogueur avec l'option "`Python: Current File (Integrated Terminal)`". +- Allez dans le panneau « Debug ». +- « Add configuration... ». +- Sélectionnez « Python ». +- Lancez le débogueur avec l'option « Python: Current File (Integrated Terminal) ». Il démarrera alors le serveur avec votre code **FastAPI**, s'arrêtera à vos points d'arrêt, etc. @@ -101,8 +101,8 @@ Voici à quoi cela pourrait ressembler : Si vous utilisez Pycharm, vous pouvez : -- Ouvrir le menu "Run". -- Sélectionnez l'option "Debug...". +- Ouvrez le menu « Run ». +- Sélectionnez l'option « Debug... ». - Un menu contextuel s'affiche alors. - Sélectionnez le fichier à déboguer (dans ce cas, `main.py`). diff --git a/docs/fr/docs/tutorial/first-steps.md b/docs/fr/docs/tutorial/first-steps.md index 96ea56e62..b2693b3e5 100644 --- a/docs/fr/docs/tutorial/first-steps.md +++ b/docs/fr/docs/tutorial/first-steps.md @@ -1,107 +1,122 @@ -# Démarrage +# Démarrage { #first-steps } -Le fichier **FastAPI** le plus simple possible pourrait ressembler à cela : +Le fichier **FastAPI** le plus simple possible pourrait ressembler à ceci : -{* ../../docs_src/first_steps/tutorial001.py *} +{* ../../docs_src/first_steps/tutorial001_py39.py *} -Copiez ce code dans un fichier nommé `main.py`. +Copiez cela dans un fichier `main.py`. -Démarrez le serveur : +Démarrez le serveur en direct :
```console -$ uvicorn main:app --reload +$ fastapi dev main.py -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. + FastAPI 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. ```
-/// note - -La commande `uvicorn main:app` fait référence à : - -* `main` : le fichier `main.py` (le module Python). -* `app` : l'objet créé dans `main.py` via la ligne `app = FastAPI()`. -* `--reload` : l'option disant à uvicorn de redémarrer le serveur à chaque changement du code. À ne pas utiliser en production ! - -/// - -Vous devriez voir dans la console, une ligne semblable à la suivante : +Dans la sortie, il y a une ligne semblable à : ```hl_lines="4" INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` -Cette ligne montre l'URL par laquelle l'app est actuellement accessible, sur votre machine locale. +Cette ligne montre l’URL où votre application est servie, sur votre machine locale. -### Allez voir le résultat +### Vérifiez { #check-it } -Ouvrez votre navigateur à l'adresse
http://127.0.0.1:8000. +Ouvrez votre navigateur à l’adresse http://127.0.0.1:8000. -Vous obtiendrez cette réponse JSON : +Vous verrez la réponse JSON suivante : ```JSON {"message": "Hello World"} ``` -### Documentation interactive de l'API +### Documentation interactive de l’API { #interactive-api-docs } -Rendez-vous sur http://127.0.0.1:8000/docs. +Allez maintenant sur http://127.0.0.1:8000/docs. -Vous verrez la documentation interactive de l'API générée automatiquement (via Swagger UI) : +Vous verrez la documentation interactive de l’API générée automatiquement (fournie par Swagger UI) : ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### Documentation alternative +### Documentation alternative de l’API { #alternative-api-docs } -Ensuite, rendez-vous sur http://127.0.0.1:8000/redoc. +Et maintenant, allez sur http://127.0.0.1:8000/redoc. -Vous y verrez la documentation alternative (via ReDoc) : +Vous verrez la documentation automatique alternative (fournie par ReDoc) : ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -### OpenAPI +### OpenAPI { #openapi } -**FastAPI** génère un "schéma" contenant toute votre API dans le standard de définition d'API **OpenAPI**. +**FastAPI** génère un « schéma » contenant toute votre API en utilisant le standard **OpenAPI** pour définir des API. -#### "Schéma" +#### « Schéma » { #schema } -Un "schéma" est une définition ou une description de quelque chose. Pas le code qui l'implémente, uniquement une description abstraite. +Un « schéma » est une définition ou une description de quelque chose. Pas le code qui l’implémente, mais uniquement une description abstraite. -#### "Schéma" d'API +#### « Schéma » d’API { #api-schema } Ici, OpenAPI est une spécification qui dicte comment définir le schéma de votre API. -Le schéma inclut les chemins de votre API, les paramètres potentiels de chaque chemin, etc. +Cette définition de schéma inclut les chemins de votre API, les paramètres possibles qu’ils prennent, etc. -#### "Schéma" de données +#### « Schéma » de données { #data-schema } -Le terme "schéma" peut aussi faire référence à la forme de la donnée, comme un contenu JSON. +Le terme « schéma » peut également faire référence à la forme d’une donnée, comme un contenu JSON. -Dans ce cas, cela signifierait les attributs JSON, ainsi que les types de ces attributs, etc. +Dans ce cas, cela désignerait les attributs JSON, ainsi que leurs types, etc. -#### OpenAPI et JSON Schema +#### OpenAPI et JSON Schema { #openapi-and-json-schema } -**OpenAPI** définit un schéma d'API pour votre API. Il inclut des définitions (ou "schémas") de la donnée envoyée et reçue par votre API en utilisant **JSON Schema**, le standard des schémas de données JSON. +OpenAPI définit un schéma d’API pour votre API. Et ce schéma inclut des définitions (ou « schémas ») des données envoyées et reçues par votre API en utilisant **JSON Schema**, le standard pour les schémas de données JSON. -#### Allez voir `openapi.json` +#### Voir le `openapi.json` { #check-the-openapi-json } -Si vous êtes curieux d'à quoi ressemble le schéma brut **OpenAPI**, **FastAPI** génère automatiquement un (schéma) JSON avec les descriptions de toute votre API. +Si vous êtes curieux de voir à quoi ressemble le schéma OpenAPI brut, FastAPI génère automatiquement un JSON (schéma) avec les descriptions de toute votre API. -Vous pouvez le voir directement à cette adresse : http://127.0.0.1:8000/openapi.json. - -Le schéma devrait ressembler à ceci : +Vous pouvez le voir directement à l’adresse : http://127.0.0.1:8000/openapi.json. +Il affichera un JSON commençant par quelque chose comme : ```JSON { - "openapi": "3.0.2", + "openapi": "3.1.0", "info": { "title": "FastAPI", "version": "0.1.0" @@ -120,79 +135,87 @@ Le schéma devrait ressembler à ceci : ... ``` -#### À quoi sert OpenAPI +#### À quoi sert OpenAPI { #what-is-openapi-for } -Le schéma **OpenAPI** est ce qui alimente les deux systèmes de documentation interactive. +Le schéma OpenAPI est ce qui alimente les deux systèmes de documentation interactive inclus. -Et il existe des dizaines d'alternatives, toutes basées sur **OpenAPI**. Vous pourriez facilement ajouter n'importe laquelle de ces alternatives à votre application **FastAPI**. +Et il existe des dizaines d’alternatives, toutes basées sur OpenAPI. Vous pourriez facilement ajouter n’importe laquelle de ces alternatives à votre application construite avec **FastAPI**. -Vous pourriez aussi l'utiliser pour générer du code automatiquement, pour les clients qui communiquent avec votre API. Comme par exemple, des applications frontend, mobiles ou IOT. +Vous pourriez également l’utiliser pour générer du code automatiquement, pour les clients qui communiquent avec votre API. Par exemple, des applications frontend, mobiles ou IoT. -## Récapitulatif, étape par étape +### Déployer votre application (optionnel) { #deploy-your-app-optional } -### Étape 1 : import `FastAPI` +Vous pouvez, si vous le souhaitez, déployer votre application FastAPI sur FastAPI Cloud, allez rejoindre la liste d’attente si ce n’est pas déjà fait. 🚀 -{* ../../docs_src/first_steps/tutorial001.py hl[1] *} +Si vous avez déjà un compte **FastAPI Cloud** (nous vous avons invité depuis la liste d’attente 😉), vous pouvez déployer votre application avec une seule commande. -`FastAPI` est une classe Python qui fournit toutes les fonctionnalités nécessaires au lancement de votre API. +Avant de déployer, vous devez vous assurer que vous êtes connecté : + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +Puis déployez votre application : + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +C’est tout ! Vous pouvez maintenant accéder à votre application à cette URL. ✨ + +## Récapitulatif, étape par étape { #recap-step-by-step } + +### Étape 1 : importer `FastAPI` { #step-1-import-fastapi } + +{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} + +`FastAPI` est une classe Python qui fournit toutes les fonctionnalités nécessaires à votre API. /// note | Détails techniques -`FastAPI` est une classe héritant directement de `Starlette`. +`FastAPI` est une classe qui hérite directement de `Starlette`. -Vous pouvez donc aussi utiliser toutes les fonctionnalités de Starlette depuis `FastAPI`. +Vous pouvez donc aussi utiliser toutes les fonctionnalités de Starlette avec `FastAPI`. /// -### Étape 2 : créer une "instance" `FastAPI` +### Étape 2 : créer une « instance » `FastAPI` { #step-2-create-a-fastapi-instance } -{* ../../docs_src/first_steps/tutorial001.py hl[3] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *} -Ici la variable `app` sera une "instance" de la classe `FastAPI`. +Ici, la variable `app` sera une « instance » de la classe `FastAPI`. -Ce sera le point principal d'interaction pour créer toute votre API. +Ce sera le point principal d’interaction pour créer toute votre API. -Cette `app` est la même que celle à laquelle fait référence `uvicorn` dans la commande : +### Étape 3 : créer un « chemin d’accès » { #step-3-create-a-path-operation } -
+#### Chemin { #path } -```console -$ uvicorn main:app --reload +« Chemin » fait ici référence à la dernière partie de l’URL à partir du premier `/`. -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -Si vous créez votre app avec : - -{* ../../docs_src/first_steps/tutorial002.py hl[3] *} - -Et la mettez dans un fichier `main.py`, alors vous appelleriez `uvicorn` avec : - -
- -```console -$ uvicorn main:my_awesome_api --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -### Étape 3: créer une *opération de chemin* - -#### Chemin - -Chemin, ou "path" fait référence ici à la dernière partie de l'URL démarrant au premier `/`. - -Donc, dans un URL tel que : +Donc, dans une URL telle que : ``` https://example.com/items/foo ``` -...le "path" serait : +... le chemin serait : ``` /items/foo @@ -200,66 +223,67 @@ https://example.com/items/foo /// info -Un chemin, ou "path" est aussi souvent appelé route ou "endpoint". +Un « chemin » est aussi couramment appelé « endpoint » ou « route ». /// -#### Opération +Lors de la création d’une API, le « chemin » est la manière principale de séparer les « préoccupations » et les « ressources ». -"Opération" fait référence à une des "méthodes" HTTP. +#### Opération { #operation } -Une de : +« Opération » fait ici référence à l’une des « méthodes » HTTP. + +L’une de : * `POST` * `GET` * `PUT` * `DELETE` -...ou une des plus exotiques : +... et les plus exotiques : * `OPTIONS` * `HEAD` * `PATCH` * `TRACE` -Dans le protocol HTTP, vous pouvez communiquer avec chaque chemin en utilisant une (ou plus) de ces "méthodes". +Dans le protocole HTTP, vous pouvez communiquer avec chaque chemin en utilisant une (ou plusieurs) de ces « méthodes ». --- -En construisant des APIs, vous utilisez généralement ces méthodes HTTP spécifiques pour effectuer une action précise. +En construisant des APIs, vous utilisez normalement ces méthodes HTTP spécifiques pour effectuer une action précise. -Généralement vous utilisez : +En général, vous utilisez : -* `POST` : pour créer de la donnée. -* `GET` : pour lire de la donnée. -* `PUT` : pour mettre à jour de la donnée. -* `DELETE` : pour supprimer de la donnée. +* `POST` : pour créer des données. +* `GET` : pour lire des données. +* `PUT` : pour mettre à jour des données. +* `DELETE` : pour supprimer des données. -Donc, dans **OpenAPI**, chaque méthode HTTP est appelée une "opération". +Donc, dans OpenAPI, chacune des méthodes HTTP est appelée une « opération ». -Nous allons donc aussi appeler ces dernières des "**opérations**". +Nous allons donc aussi les appeler « opérations ». +#### Définir un « décorateur de chemin d’accès » { #define-a-path-operation-decorator } -#### Définir un *décorateur d'opération de chemin* +{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *} -{* ../../docs_src/first_steps/tutorial001.py hl[6] *} - -Le `@app.get("/")` dit à **FastAPI** que la fonction en dessous est chargée de gérer les requêtes qui vont sur : +Le `@app.get("/")` indique à **FastAPI** que la fonction juste en dessous est chargée de gérer les requêtes qui vont vers : * le chemin `/` -* en utilisant une opération get +* en utilisant une get opération /// info | `@décorateur` Info -Cette syntaxe `@something` en Python est appelée un "décorateur". +Cette syntaxe `@something` en Python est appelée un « décorateur ». -Vous la mettez au dessus d'une fonction. Comme un joli chapeau décoratif (j'imagine que ce terme vient de là 🤷🏻‍♂). +Vous la mettez au-dessus d’une fonction. Comme un joli chapeau décoratif (j’imagine que c’est de là que vient le terme 🤷🏻‍♂). -Un "décorateur" prend la fonction en dessous et en fait quelque chose. +Un « décorateur » prend la fonction en dessous et fait quelque chose avec. -Dans notre cas, ce décorateur dit à **FastAPI** que la fonction en dessous correspond au **chemin** `/` avec l'**opération** `get`. +Dans notre cas, ce décorateur indique à **FastAPI** que la fonction en dessous correspond au **chemin** `/` avec une **opération** `get`. -C'est le "**décorateur d'opération de chemin**". +C’est le « décorateur de chemin d’accès ». /// @@ -269,7 +293,7 @@ Vous pouvez aussi utiliser les autres opérations : * `@app.put()` * `@app.delete()` -Tout comme celles les plus exotiques : +Ainsi que les plus exotiques : * `@app.options()` * `@app.head()` @@ -278,58 +302,79 @@ Tout comme celles les plus exotiques : /// tip | Astuce -Vous êtes libres d'utiliser chaque opération (méthode HTTP) comme vous le désirez. +Vous êtes libre d’utiliser chaque opération (méthode HTTP) comme vous le souhaitez. -**FastAPI** n'impose pas de sens spécifique à chacune d'elle. +**FastAPI** n’impose aucune signification spécifique. -Les informations qui sont présentées ici forment une directive générale, pas des obligations. +Les informations ici sont présentées comme des lignes directrices, pas comme une obligation. -Par exemple, quand l'on utilise **GraphQL**, toutes les actions sont effectuées en utilisant uniquement des opérations `POST`. +Par exemple, lorsque vous utilisez GraphQL, vous effectuez normalement toutes les actions en utilisant uniquement des opérations `POST`. /// -### Étape 4 : définir la **fonction de chemin**. +### Étape 4 : définir la **fonction de chemin d’accès** { #step-4-define-the-path-operation-function } -Voici notre "**fonction de chemin**" (ou fonction d'opération de chemin) : +Voici notre « fonction de chemin d’accès » : * **chemin** : `/`. * **opération** : `get`. -* **fonction** : la fonction sous le "décorateur" (sous `@app.get("/")`). +* **fonction** : la fonction sous le « décorateur » (sous `@app.get("/")`). -{* ../../docs_src/first_steps/tutorial001.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} -C'est une fonction Python. +C’est une fonction Python. -Elle sera appelée par **FastAPI** quand une requête sur l'URL `/` sera reçue via une opération `GET`. +Elle sera appelée par **FastAPI** chaque fois qu’il recevra une requête vers l’URL « / » en utilisant une opération `GET`. -Ici, c'est une fonction asynchrone (définie avec `async def`). +Dans ce cas, c’est une fonction `async`. --- -Vous pourriez aussi la définir comme une fonction classique plutôt qu'avec `async def` : +Vous pouvez aussi la définir comme une fonction normale au lieu de `async def` : -{* ../../docs_src/first_steps/tutorial003.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} /// note -Si vous ne connaissez pas la différence, allez voir la section [Concurrence : *"Vous êtes pressés ?"*](../async.md#vous-etes-presses){.internal-link target=_blank}. +Si vous ne connaissez pas la différence, consultez [Asynchrone : « Pressé ? »](../async.md#in-a-hurry){.internal-link target=_blank}. /// -### Étape 5 : retourner le contenu +### Étape 5 : retourner le contenu { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} -Vous pouvez retourner un dictionnaire (`dict`), une liste (`list`), des valeurs seules comme des chaines de caractères (`str`) et des entiers (`int`), etc. +Vous pouvez retourner un `dict`, une `list`, des valeurs uniques comme `str`, `int`, etc. -Vous pouvez aussi retourner des models **Pydantic** (qui seront détaillés plus tard). +Vous pouvez également retourner des modèles Pydantic (vous en verrez plus à ce sujet plus tard). -Il y a de nombreux autres objets et modèles qui seront automatiquement convertis en JSON. Essayez d'utiliser vos favoris, il est fort probable qu'ils soient déjà supportés. +Il existe de nombreux autres objets et modèles qui seront automatiquement convertis en JSON (y compris des ORM, etc.). Essayez d’utiliser vos favoris, il est fort probable qu’ils soient déjà pris en charge. -## Récapitulatif +### Étape 6 : le déployer { #step-6-deploy-it } + +Déployez votre application sur **FastAPI Cloud** avec une seule commande : `fastapi deploy`. 🎉 + +#### À propos de FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** est construit par le même auteur et l’équipe derrière **FastAPI**. + +Il simplifie le processus de **construction**, de **déploiement** et d’**accès** à une API avec un minimum d’effort. + +Il apporte la même **expérience développeur** de création d’applications avec FastAPI au **déploiement** dans le cloud. 🎉 + +FastAPI Cloud est le sponsor principal et le financeur des projets open source *FastAPI and friends*. ✨ + +#### Déployer sur d’autres fournisseurs cloud { #deploy-to-other-cloud-providers } + +FastAPI est open source et basé sur des standards. Vous pouvez déployer des applications FastAPI chez n’importe quel fournisseur cloud de votre choix. + +Suivez les guides de votre fournisseur cloud pour y déployer des applications FastAPI. 🤓 + +## Récapitulatif { #recap } * Importez `FastAPI`. -* Créez une instance d'`app`. -* Ajoutez une **décorateur d'opération de chemin** (tel que `@app.get("/")`). -* Ajoutez une **fonction de chemin** (telle que `def root(): ...` comme ci-dessus). -* Lancez le serveur de développement (avec `uvicorn main:app --reload`). +* Créez une instance `app`. +* Écrivez un **décorateur de chemin d’accès** avec des décorateurs comme `@app.get("/")`. +* Définissez une **fonction de chemin d’accès** ; par exemple, `def root(): ...`. +* Exécutez le serveur de développement avec la commande `fastapi dev`. +* Déployez éventuellement votre application avec `fastapi deploy`. diff --git a/docs/fr/docs/tutorial/index.md b/docs/fr/docs/tutorial/index.md index 83cc5f9e8..0251b9b4b 100644 --- a/docs/fr/docs/tutorial/index.md +++ b/docs/fr/docs/tutorial/index.md @@ -1,29 +1,53 @@ -# Tutoriel - Guide utilisateur - Introduction +# Tutoriel - Guide utilisateur { #tutorial-user-guide } Ce tutoriel vous montre comment utiliser **FastAPI** avec la plupart de ses fonctionnalités, étape par étape. -Chaque section s'appuie progressivement sur les précédentes, mais elle est structurée de manière à séparer les sujets, afin que vous puissiez aller directement à l'un d'entre eux pour résoudre vos besoins spécifiques en matière d'API. +Chaque section s'appuie progressivement sur les précédentes, mais elle est structurée de manière à séparer les sujets, afin que vous puissiez aller directement à l'un d'entre eux pour répondre à vos besoins spécifiques d'API. -Il est également conçu pour fonctionner comme une référence future. +Il est également conçu pour servir de référence ultérieure, afin que vous puissiez revenir voir exactement ce dont vous avez besoin. -Vous pouvez donc revenir et voir exactement ce dont vous avez besoin. - -## Exécuter le code +## Exécuter le code { #run-the-code } Tous les blocs de code peuvent être copiés et utilisés directement (il s'agit en fait de fichiers Python testés). -Pour exécuter l'un de ces exemples, copiez le code dans un fichier `main.py`, et commencez `uvicorn` avec : +Pour exécuter l'un de ces exemples, copiez le code dans un fichier `main.py`, et démarrez `fastapi dev` avec :
```console -$ uvicorn main:app --reload +$ fastapi dev main.py -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. + FastAPI 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. ```
@@ -34,45 +58,33 @@ L'utiliser dans votre éditeur est ce qui vous montre vraiment les avantages de --- -## Installer FastAPI +## Installer FastAPI { #install-fastapi } La première étape consiste à installer FastAPI. -Pour le tutoriel, vous voudrez peut-être l'installer avec toutes les dépendances et fonctionnalités optionnelles : +Assurez-vous de créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, de l'activer, puis **d'installer FastAPI** :
```console -$ pip install fastapi[all] +$ pip install "fastapi[standard]" ---> 100% ```
-... qui comprend également `uvicorn`, que vous pouvez utiliser comme serveur pour exécuter votre code. +/// note | Remarque -/// note +Lorsque vous installez avec `pip install "fastapi[standard]"` cela inclut des dépendances standard optionnelles par défaut, y compris `fastapi-cloud-cli`, qui vous permet de déployer sur FastAPI Cloud. -Vous pouvez également l'installer pièce par pièce. +Si vous ne souhaitez pas avoir ces dépendances optionnelles, vous pouvez à la place installer `pip install fastapi`. -C'est ce que vous feriez probablement une fois que vous voudrez déployer votre application en production : - -``` -pip install fastapi -``` - -Installez également `uvicorn` pour qu'il fonctionne comme serveur : - -``` -pip install uvicorn -``` - -Et la même chose pour chacune des dépendances facultatives que vous voulez utiliser. +Si vous souhaitez installer les dépendances standard mais sans `fastapi-cloud-cli`, vous pouvez installer avec `pip install "fastapi[standard-no-fastapi-cloud-cli]"`. /// -## Guide utilisateur avancé +## Guide d'utilisation avancé { #advanced-user-guide } Il existe également un **Guide d'utilisation avancé** que vous pouvez lire plus tard après ce **Tutoriel - Guide d'utilisation**. diff --git a/docs/fr/docs/tutorial/path-params-numeric-validations.md b/docs/fr/docs/tutorial/path-params-numeric-validations.md index 3f3280e64..c80710777 100644 --- a/docs/fr/docs/tutorial/path-params-numeric-validations.md +++ b/docs/fr/docs/tutorial/path-params-numeric-validations.md @@ -1,8 +1,8 @@ -# Paramètres de chemin et validations numériques +# Paramètres de chemin et validations numériques { #path-parameters-and-numeric-validations } De la même façon que vous pouvez déclarer plus de validations et de métadonnées pour les paramètres de requête avec `Query`, vous pouvez déclarer le même type de validations et de métadonnées pour les paramètres de chemin avec `Path`. -## Importer Path +## Importer `Path` { #import-path } Tout d'abord, importez `Path` de `fastapi`, et importez `Annotated` : @@ -14,11 +14,11 @@ FastAPI a ajouté le support pour `Annotated` (et a commencé à le recommander) Si vous avez une version plus ancienne, vous obtiendrez des erreurs en essayant d'utiliser `Annotated`. -Assurez-vous de [Mettre à jour la version de FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} à la version 0.95.1 à minima avant d'utiliser `Annotated`. +Assurez-vous de [Mettre à niveau la version de FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} à la version 0.95.1 à minima avant d'utiliser `Annotated`. /// -## Déclarer des métadonnées +## Déclarer des métadonnées { #declare-metadata } Vous pouvez déclarer les mêmes paramètres que pour `Query`. @@ -26,15 +26,15 @@ Par exemple, pour déclarer une valeur de métadonnée `title` pour le paramètr {* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *} -/// note +/// note | Remarque Un paramètre de chemin est toujours requis car il doit faire partie du chemin. Même si vous l'avez déclaré avec `None` ou défini une valeur par défaut, cela ne changerait rien, il serait toujours requis. /// -## Ordonnez les paramètres comme vous le souhaitez +## Ordonner les paramètres comme vous le souhaitez { #order-the-parameters-as-you-need } -/// tip +/// tip | Astuce Ce n'est probablement pas aussi important ou nécessaire si vous utilisez `Annotated`. @@ -46,7 +46,7 @@ Et vous n'avez pas besoin de déclarer autre chose pour ce paramètre, donc vous Mais vous avez toujours besoin d'utiliser `Path` pour le paramètre de chemin `item_id`. Et vous ne voulez pas utiliser `Annotated` pour une raison quelconque. -Python se plaindra si vous mettez une valeur avec une "défaut" avant une valeur qui n'a pas de "défaut". +Python se plaindra si vous mettez une valeur avec une « valeur par défaut » avant une valeur qui n'a pas de « valeur par défaut ». Mais vous pouvez les réorganiser, et avoir la valeur sans défaut (le paramètre de requête `q`) en premier. @@ -54,15 +54,15 @@ Cela n'a pas d'importance pour **FastAPI**. Il détectera les paramètres par le Ainsi, vous pouvez déclarer votre fonction comme suit : -{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} Mais gardez à l'esprit que si vous utilisez `Annotated`, vous n'aurez pas ce problème, cela n'aura pas d'importance car vous n'utilisez pas les valeurs par défaut des paramètres de fonction pour `Query()` ou `Path()`. -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} -## Ordonnez les paramètres comme vous le souhaitez (astuces) +## Ordonner les paramètres comme vous le souhaitez, astuces { #order-the-parameters-as-you-need-tricks } -/// tip +/// tip | Astuce Ce n'est probablement pas aussi important ou nécessaire si vous utilisez `Annotated`. @@ -77,38 +77,29 @@ Si vous voulez : * les avoir dans un ordre différent * ne pas utiliser `Annotated` -...Python a une petite syntaxe spéciale pour cela. +... Python a une petite syntaxe spéciale pour cela. Passez `*`, comme premier paramètre de la fonction. Python ne fera rien avec ce `*`, mais il saura que tous les paramètres suivants doivent être appelés comme arguments "mots-clés" (paires clé-valeur), également connus sous le nom de kwargs. Même s'ils n'ont pas de valeur par défaut. -{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} -# Avec `Annotated` +### Mieux avec `Annotated` { #better-with-annotated } Gardez à l'esprit que si vous utilisez `Annotated`, comme vous n'utilisez pas les valeurs par défaut des paramètres de fonction, vous n'aurez pas ce problème, et vous n'aurez probablement pas besoin d'utiliser `*`. {* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} -## Validations numériques : supérieur ou égal +## Validations numériques : supérieur ou égal { #number-validations-greater-than-or-equal } Avec `Query` et `Path` (et d'autres que vous verrez plus tard) vous pouvez déclarer des contraintes numériques. -Ici, avec `ge=1`, `item_id` devra être un nombre entier "`g`reater than or `e`qual" à `1`. +Ici, avec `ge=1`, `item_id` devra être un nombre entier « `g`reater than or `e`qual » à `1`. {* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} -## Validations numériques : supérieur ou égal et inférieur ou égal - -La même chose s'applique pour : - -* `gt` : `g`reater `t`han -* `le` : `l`ess than or `e`qual - -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} - -## Validations numériques : supérieur et inférieur ou égal +## Validations numériques : supérieur et inférieur ou égal { #number-validations-greater-than-and-less-than-or-equal } La même chose s'applique pour : @@ -117,7 +108,7 @@ La même chose s'applique pour : {* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} -## Validations numériques : flottants, supérieur et inférieur +## Validations numériques : flottants, supérieur et inférieur { #number-validations-floats-greater-than-and-less-than } Les validations numériques fonctionnent également pour les valeurs `float`. @@ -129,7 +120,7 @@ Et la même chose pour lt. {* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} -## Pour résumer +## Pour résumer { #recap } Avec `Query`, `Path` (et d'autres que vous verrez plus tard) vous pouvez déclarer des métadonnées et des validations de chaînes de la même manière qu'avec les [Paramètres de requête et validations de chaînes](query-params-str-validations.md){.internal-link target=_blank}. diff --git a/docs/fr/docs/tutorial/path-params.md b/docs/fr/docs/tutorial/path-params.md index 71c96b18e..3b2955a95 100644 --- a/docs/fr/docs/tutorial/path-params.md +++ b/docs/fr/docs/tutorial/path-params.md @@ -1,205 +1,196 @@ -# Paramètres de chemin +# Paramètres de chemin { #path-parameters } -Vous pouvez déclarer des "paramètres" ou "variables" de chemin avec la même syntaxe que celle utilisée par le -formatage de chaîne Python : +Vous pouvez déclarer des « paramètres » ou « variables » de chemin avec la même syntaxe utilisée par les chaînes de format Python : +{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} -{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} +La valeur du paramètre de chemin `item_id` sera transmise à votre fonction dans l'argument `item_id`. -La valeur du paramètre `item_id` sera transmise à la fonction dans l'argument `item_id`. - -Donc, si vous exécutez cet exemple et allez sur http://127.0.0.1:8000/items/foo, -vous verrez comme réponse : +Donc, si vous exécutez cet exemple et allez sur http://127.0.0.1:8000/items/foo, vous verrez comme réponse : ```JSON {"item_id":"foo"} ``` -## Paramètres de chemin typés +## Paramètres de chemin typés { #path-parameters-with-types } -Vous pouvez déclarer le type d'un paramètre de chemin dans la fonction, en utilisant les annotations de type Python : +Vous pouvez déclarer le type d'un paramètre de chemin dans la fonction, en utilisant les annotations de type Python standard : - -{* ../../docs_src/path_params/tutorial002.py hl[7] *} +{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *} Ici, `item_id` est déclaré comme `int`. -/// check | vérifier +/// check | Vérifications -Ceci vous permettra d'obtenir des fonctionnalités de l'éditeur dans votre fonction, telles -que des vérifications d'erreur, de l'auto-complétion, etc. +Cela vous apporte la prise en charge par l'éditeur dans votre fonction, avec vérifications d'erreurs, autocomplétion, etc. /// -## Conversion de données +## Conversion de données { #data-conversion } -Si vous exécutez cet exemple et allez sur http://127.0.0.1:8000/items/3, vous aurez comme réponse : +Si vous exécutez cet exemple et ouvrez votre navigateur sur http://127.0.0.1:8000/items/3, vous verrez comme réponse : ```JSON {"item_id":3} ``` -/// check | vérifier +/// check | Vérifications -Comme vous l'avez remarqué, la valeur reçue par la fonction (et renvoyée ensuite) est `3`, -en tant qu'entier (`int`) Python, pas la chaîne de caractères (`string`) `"3"`. +Remarquez que la valeur reçue par votre fonction (et renvoyée) est `3`, en tant qu'entier (`int`) Python, pas la chaîne de caractères « 3 ». -Grâce aux déclarations de types, **FastAPI** fournit du -"parsing" automatique. +Ainsi, avec cette déclaration de type, **FastAPI** vous fournit automatiquement le « parsing » de la requête. /// -## Validation de données +## Validation de données { #data-validation } -Si vous allez sur http://127.0.0.1:8000/items/foo, vous aurez une belle erreur HTTP : +Mais si vous allez dans le navigateur sur http://127.0.0.1:8000/items/foo, vous verrez une belle erreur HTTP : ```JSON { - "detail": [ - { - "loc": [ - "path", - "item_id" - ], - "msg": "value is not a valid integer", - "type": "type_error.integer" - } - ] + "detail": [ + { + "type": "int_parsing", + "loc": [ + "path", + "item_id" + ], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "foo" + } + ] } ``` -car le paramètre de chemin `item_id` possède comme valeur `"foo"`, qui ne peut pas être convertie en entier (`int`). +car le paramètre de chemin `item_id` a pour valeur « foo », qui n'est pas un `int`. -La même erreur se produira si vous passez un nombre flottant (`float`) et non un entier, comme ici -http://127.0.0.1:8000/items/4.2. +La même erreur apparaîtrait si vous fournissiez un `float` au lieu d'un `int`, comme ici : http://127.0.0.1:8000/items/4.2 +/// check | Vérifications -/// check | vérifier +Ainsi, avec la même déclaration de type Python, **FastAPI** vous fournit la validation de données. -Donc, avec ces mêmes déclarations de type Python, **FastAPI** vous fournit de la validation de données. +Remarquez que l'erreur indique clairement l'endroit exact où la validation n'a pas réussi. -Notez que l'erreur mentionne le point exact où la validation n'a pas réussi. - -Ce qui est incroyablement utile au moment de développer et débugger du code qui interagit avec votre API. +C'est incroyablement utile lors du développement et du débogage du code qui interagit avec votre API. /// -## Documentation +## Documentation { #documentation } -Et quand vous vous rendez sur http://127.0.0.1:8000/docs, vous verrez la -documentation générée automatiquement et interactive : +Et lorsque vous ouvrez votre navigateur sur http://127.0.0.1:8000/docs, vous verrez une documentation d'API automatique et interactive comme : -/// info +/// check | Vérifications -À nouveau, en utilisant uniquement les déclarations de type Python, **FastAPI** vous fournit automatiquement une documentation interactive (via Swagger UI). +À nouveau, simplement avec cette même déclaration de type Python, **FastAPI** vous fournit une documentation interactive automatique (intégrant Swagger UI). -On voit bien dans la documentation que `item_id` est déclaré comme entier. +Remarquez que le paramètre de chemin est déclaré comme entier. /// -## Les avantages d'avoir une documentation basée sur une norme, et la documentation alternative. +## Les avantages d'une norme, documentation alternative { #standards-based-benefits-alternative-documentation } -Le schéma généré suivant la norme OpenAPI, -il existe de nombreux outils compatibles. +Et comme le schéma généré suit la norme OpenAPI, il existe de nombreux outils compatibles. -Grâce à cela, **FastAPI** lui-même fournit une documentation alternative (utilisant ReDoc), qui peut être lue -sur http://127.0.0.1:8000/redoc : +Grâce à cela, **FastAPI** fournit lui-même une documentation d'API alternative (utilisant ReDoc), accessible sur http://127.0.0.1:8000/redoc : -De la même façon, il existe bien d'autres outils compatibles, y compris des outils de génération de code -pour de nombreux langages. +De la même façon, il existe de nombreux outils compatibles, y compris des outils de génération de code pour de nombreux langages. -## Pydantic +## Pydantic { #pydantic } -Toute la validation de données est effectué en arrière-plan avec Pydantic, -dont vous bénéficierez de tous les avantages. Vous savez donc que vous êtes entre de bonnes mains. +Toute la validation de données est effectuée sous le capot par Pydantic, vous en bénéficiez donc pleinement. Vous savez ainsi que vous êtes entre de bonnes mains. -## L'ordre importe +Vous pouvez utiliser les mêmes déclarations de type avec `str`, `float`, `bool` et de nombreux autres types de données complexes. -Quand vous créez des *fonctions de chemins*, vous pouvez vous retrouver dans une situation où vous avez un chemin fixe. +Plusieurs d'entre eux sont explorés dans les prochains chapitres du tutoriel. -Tel que `/users/me`, disons pour récupérer les données sur l'utilisateur actuel. +## L'ordre importe { #order-matters } -Et vous avez un second chemin : `/users/{user_id}` pour récupérer de la donnée sur un utilisateur spécifique grâce à son identifiant d'utilisateur +Quand vous créez des *chemins d'accès*, vous pouvez vous retrouver dans une situation avec un chemin fixe. -Les *fonctions de chemin* étant évaluées dans l'ordre, il faut s'assurer que la fonction correspondant à `/users/me` est déclarée avant celle de `/users/{user_id}` : +Par exemple `/users/me`, disons pour récupérer les données de l'utilisateur actuel. -{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} +Et vous pouvez aussi avoir un chemin `/users/{user_id}` pour récupérer des données sur un utilisateur spécifique grâce à un identifiant d'utilisateur. -Sinon, le chemin `/users/{user_id}` correspondrait aussi à `/users/me`, la fonction "croyant" qu'elle a reçu un paramètre `user_id` avec pour valeur `"me"`. +Comme les *chemins d'accès* sont évalués dans l'ordre, vous devez vous assurer que le chemin `/users/me` est déclaré avant celui de `/users/{user_id}` : -## Valeurs prédéfinies +{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} -Si vous avez une *fonction de chemin* qui reçoit un *paramètre de chemin*, mais que vous voulez que les valeurs possibles des paramètres soient prédéfinies, vous pouvez utiliser les `Enum` de Python. +Sinon, le chemin `/users/{user_id}` correspondrait aussi à `/users/me`, « pensant » qu'il reçoit un paramètre `user_id` avec la valeur « me ». -### Création d'un `Enum` +De même, vous ne pouvez pas redéfinir un chemin d'accès : -Importez `Enum` et créez une sous-classe qui hérite de `str` et `Enum`. +{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} -En héritant de `str` la documentation sera capable de savoir que les valeurs doivent être de type `string` et pourra donc afficher cette `Enum` correctement. +Le premier sera toujours utilisé puisque le chemin correspond en premier. -Créez ensuite des attributs de classe avec des valeurs fixes, qui seront les valeurs autorisées pour cette énumération. +## Valeurs prédéfinies { #predefined-values } -{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *} +Si vous avez un *chemin d'accès* qui reçoit un *paramètre de chemin*, mais que vous voulez que les valeurs possibles de ce *paramètre de chemin* soient prédéfinies, vous pouvez utiliser une `Enum` Python standard. -/// info +### Créer une classe `Enum` { #create-an-enum-class } -Les énumérations (ou enums) sont disponibles en Python depuis la version 3.4. +Importez `Enum` et créez une sous-classe qui hérite de `str` et de `Enum`. -/// +En héritant de `str`, la documentation de l'API saura que les valeurs doivent être de type `string` et pourra donc s'afficher correctement. + +Créez ensuite des attributs de classe avec des valeurs fixes, qui seront les valeurs valides disponibles : + +{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} /// tip | Astuce -Pour ceux qui se demandent, "AlexNet", "ResNet", et "LeNet" sont juste des noms de modèles de Machine Learning. +Si vous vous demandez, « AlexNet », « ResNet » et « LeNet » sont juste des noms de modèles de Machine Learning. /// -### Déclarer un paramètre de chemin +### Déclarer un paramètre de chemin { #declare-a-path-parameter } -Créez ensuite un *paramètre de chemin* avec une annotation de type désignant l'énumération créée précédemment (`ModelName`) : +Créez ensuite un *paramètre de chemin* avec une annotation de type utilisant la classe d'énumération que vous avez créée (`ModelName`) : -{* ../../docs_src/path_params/tutorial005.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} -### Documentation +### Consulter la documentation { #check-the-docs } -Les valeurs disponibles pour le *paramètre de chemin* sont bien prédéfinies, la documentation les affiche correctement : +Comme les valeurs disponibles pour le *paramètre de chemin* sont prédéfinies, la documentation interactive peut les afficher clairement : -### Manipuler les *énumérations* Python +### Travailler avec les *énumérations* Python { #working-with-python-enumerations } -La valeur du *paramètre de chemin* sera un des "membres" de l'énumération. +La valeur du *paramètre de chemin* sera un *membre d'énumération*. -#### Comparer les *membres d'énumération* +#### Comparer des *membres d'énumération* { #compare-enumeration-members } -Vous pouvez comparer ce paramètre avec les membres de votre énumération `ModelName` : +Vous pouvez le comparer avec le *membre d'énumération* dans votre enum `ModelName` : -{* ../../docs_src/path_params/tutorial005.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} -#### Récupérer la *valeur de l'énumération* +#### Obtenir la *valeur de l'énumération* { #get-the-enumeration-value } -Vous pouvez obtenir la valeur réel d'un membre (une chaîne de caractères ici), avec `model_name.value`, ou en général, `votre_membre_d'enum.value` : +Vous pouvez obtenir la valeur réelle (une `str` dans ce cas) avec `model_name.value`, ou en général, `votre_membre_d_enum.value` : -{* ../../docs_src/path_params/tutorial005.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} /// tip | Astuce -Vous pouvez aussi accéder la valeur `"lenet"` avec `ModelName.lenet.value`. +Vous pouvez aussi accéder à la valeur « lenet » avec `ModelName.lenet.value`. /// -#### Retourner des *membres d'énumération* +#### Retourner des *membres d'énumération* { #return-enumeration-members } -Vous pouvez retourner des *membres d'énumération* dans vos *fonctions de chemin*, même imbriquée dans un JSON (e.g. un `dict`). +Vous pouvez retourner des *membres d'énumération* depuis votre *chemin d'accès*, même imbriqués dans un corps JSON (par ex. un `dict`). -Ils seront convertis vers leurs valeurs correspondantes (chaînes de caractères ici) avant d'être transmis au client : +Ils seront convertis vers leurs valeurs correspondantes (des chaînes de caractères ici) avant d'être renvoyés au client : -{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} -Le client recevra une réponse JSON comme celle-ci : +Dans votre client, vous recevrez une réponse JSON comme : ```JSON { @@ -208,53 +199,53 @@ Le client recevra une réponse JSON comme celle-ci : } ``` -## Paramètres de chemin contenant des chemins +## Paramètres de chemin contenant des chemins { #path-parameters-containing-paths } -Disons que vous avez une *fonction de chemin* liée au chemin `/files/{file_path}`. +Disons que vous avez un *chemin d'accès* avec un chemin `/files/{file_path}`. -Mais que `file_path` lui-même doit contenir un *chemin*, comme `home/johndoe/myfile.txt` par exemple. +Mais vous avez besoin que `file_path` lui-même contienne un *chemin*, comme `home/johndoe/myfile.txt`. -Donc, l'URL pour ce fichier pourrait être : `/files/home/johndoe/myfile.txt`. +Ainsi, l'URL pour ce fichier serait : `/files/home/johndoe/myfile.txt`. -### Support d'OpenAPI +### Support d'OpenAPI { #openapi-support } -OpenAPI ne supporte pas de manière de déclarer un paramètre de chemin contenant un *chemin*, cela pouvant causer des scénarios difficiles à tester et définir. +OpenAPI ne prend pas en charge une manière de déclarer un *paramètre de chemin* contenant un *chemin* à l'intérieur, car cela peut conduire à des scénarios difficiles à tester et à définir. -Néanmoins, cela reste faisable dans **FastAPI**, via les outils internes de Starlette. +Néanmoins, vous pouvez toujours le faire dans **FastAPI**, en utilisant l'un des outils internes de Starlette. -Et la documentation fonctionne quand même, bien qu'aucune section ne soit ajoutée pour dire que la paramètre devrait contenir un *chemin*. +Et la documentation fonctionnera quand même, même si aucune indication supplémentaire ne sera ajoutée pour dire que le paramètre doit contenir un chemin. -### Convertisseur de *chemin* +### Convertisseur de chemin { #path-convertor } -En utilisant une option de Starlette directement, vous pouvez déclarer un *paramètre de chemin* contenant un *chemin* avec une URL comme : +En utilisant une option directement depuis Starlette, vous pouvez déclarer un *paramètre de chemin* contenant un *chemin* avec une URL comme : ``` /files/{file_path:path} ``` -Dans ce cas, le nom du paramètre est `file_path`, et la dernière partie, `:path`, indique à Starlette que le paramètre devrait correspondre à un *chemin*. +Dans ce cas, le nom du paramètre est `file_path`, et la dernière partie, `:path`, indique que le paramètre doit correspondre à n'importe quel *chemin*. -Vous pouvez donc l'utilisez comme tel : +Vous pouvez donc l'utiliser ainsi : -{* ../../docs_src/path_params/tutorial004.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} /// tip | Astuce -Vous pourriez avoir besoin que le paramètre contienne `/home/johndoe/myfile.txt`, avec un slash au début (`/`). +Vous pourriez avoir besoin que le paramètre contienne `/home/johndoe/myfile.txt`, avec un slash initial (`/`). Dans ce cas, l'URL serait : `/files//home/johndoe/myfile.txt`, avec un double slash (`//`) entre `files` et `home`. /// -## Récapitulatif +## Récapitulatif { #recap } -Avec **FastAPI**, en utilisant les déclarations de type rapides, intuitives et standards de Python, vous bénéficiez de : +Avec **FastAPI**, en utilisant des déclarations de type Python courtes, intuitives et standard, vous obtenez : -* Support de l'éditeur : vérification d'erreurs, auto-complétion, etc. -* "Parsing" de données. -* Validation de données. -* Annotations d'API et documentation automatique. +* Support de l'éditeur : vérifications d'erreurs, autocomplétion, etc. +* Données « parsing » +* Validation de données +* Annotations d'API et documentation automatique -Et vous n'avez besoin de le déclarer qu'une fois. +Et vous n'avez besoin de les déclarer qu'une seule fois. -C'est probablement l'avantage visible principal de **FastAPI** comparé aux autres *frameworks* (outre les performances pures). +C'est probablement l'avantage visible principal de **FastAPI** comparé aux autres frameworks (outre les performances pures). diff --git a/docs/fr/docs/tutorial/query-params-str-validations.md b/docs/fr/docs/tutorial/query-params-str-validations.md index c54c0c717..544d10328 100644 --- a/docs/fr/docs/tutorial/query-params-str-validations.md +++ b/docs/fr/docs/tutorial/query-params-str-validations.md @@ -1,166 +1,273 @@ -# Paramètres de requête et validations de chaînes de caractères +# Paramètres de requête et validations de chaînes de caractères { #query-parameters-and-string-validations } -**FastAPI** vous permet de déclarer des informations et des validateurs additionnels pour vos paramètres de requêtes. +**FastAPI** vous permet de déclarer des informations et des validations supplémentaires pour vos paramètres. -Commençons avec cette application pour exemple : +Prenons cette application comme exemple : -{* ../../docs_src/query_params_str_validations/tutorial001.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} -Le paramètre de requête `q` a pour type `Union[str, None]` (ou `str | None` en Python 3.10), signifiant qu'il est de type `str` mais pourrait aussi être égal à `None`, et bien sûr, la valeur par défaut est `None`, donc **FastAPI** saura qu'il n'est pas requis. +Le paramètre de requête `q` est de type `str | None`, cela signifie qu’il est de type `str` mais peut aussi être `None`, et en effet, la valeur par défaut est `None`, donc FastAPI saura qu’il n’est pas requis. -/// note +/// note | Remarque -**FastAPI** saura que la valeur de `q` n'est pas requise grâce à la valeur par défaut `= None`. +FastAPI saura que la valeur de `q` n’est pas requise grâce à la valeur par défaut `= None`. -Le `Union` dans `Union[str, None]` permettra à votre éditeur de vous offrir un meilleur support et de détecter les erreurs. +Avoir `str | None` permettra à votre éditeur de vous offrir un meilleur support et de détecter les erreurs. /// -## Validation additionnelle +## Validation additionnelle { #additional-validation } -Nous allons imposer que bien que `q` soit un paramètre optionnel, dès qu'il est fourni, **sa longueur n'excède pas 50 caractères**. +Nous allons imposer que, même si `q` est optionnel, dès qu’il est fourni, **sa longueur n’excède pas 50 caractères**. -## Importer `Query` +### Importer `Query` et `Annotated` { #import-query-and-annotated } -Pour cela, importez d'abord `Query` depuis `fastapi` : +Pour ce faire, importez d’abord : -{* ../../docs_src/query_params_str_validations/tutorial002.py hl[3] *} +- `Query` depuis `fastapi` +- `Annotated` depuis `typing` -## Utiliser `Query` comme valeur par défaut +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *} -Construisez ensuite la valeur par défaut de votre paramètre avec `Query`, en choisissant 50 comme `max_length` : +/// info -{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *} +FastAPI a ajouté la prise en charge de `Annotated` (et a commencé à le recommander) dans la version 0.95.0. -Comme nous devons remplacer la valeur par défaut `None` dans la fonction par `Query()`, nous pouvons maintenant définir la valeur par défaut avec le paramètre `Query(default=None)`, il sert le même objectif qui est de définir cette valeur par défaut. +Si vous avez une version plus ancienne, vous obtiendrez des erreurs en essayant d’utiliser `Annotated`. -Donc : +Assurez-vous de [mettre à niveau la version de FastAPI](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} vers au moins 0.95.1 avant d’utiliser `Annotated`. + +/// + +## Utiliser `Annotated` dans le type pour le paramètre `q` { #use-annotated-in-the-type-for-the-q-parameter } + +Vous vous souvenez que je vous ai dit plus tôt que `Annotated` peut être utilisé pour ajouter des métadonnées à vos paramètres dans l’[Introduction aux types Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank} ? + +C’est le moment de l’utiliser avec FastAPI. 🚀 + +Nous avions cette annotation de type : + +//// tab | Python 3.10+ ```Python -q: Union[str, None] = Query(default=None) +q: str | None = None ``` -... rend le paramètre optionnel, et est donc équivalent à : +//// + +//// tab | Python 3.9+ ```Python q: Union[str, None] = None ``` -Mais déclare explicitement `q` comme étant un paramètre de requête. +//// -/// info +Ce que nous allons faire, c’est l’englober avec `Annotated`, de sorte que cela devienne : -Gardez à l'esprit que la partie la plus importante pour rendre un paramètre optionnel est : +//// tab | Python 3.10+ ```Python -= None +q: Annotated[str | None] = None ``` -ou : +//// + +//// tab | Python 3.9+ ```Python -= Query(None) +q: Annotated[Union[str, None]] = None ``` -et utilisera ce `None` pour détecter que ce paramètre de requête **n'est pas requis**. +//// -Le `Union[str, None]` est uniquement là pour permettre à votre éditeur un meilleur support. +Les deux versions signifient la même chose, `q` est un paramètre qui peut être une `str` ou `None`, et par défaut, c’est `None`. + +Passons maintenant aux choses amusantes. 🎉 + +## Ajouter `Query` à `Annotated` dans le paramètre `q` { #add-query-to-annotated-in-the-q-parameter } + +Maintenant que nous avons cet `Annotated` dans lequel nous pouvons mettre plus d’informations (dans ce cas une validation supplémentaire), ajoutez `Query` à l’intérieur de `Annotated`, et définissez le paramètre `max_length` à `50` : + +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *} + +Remarquez que la valeur par défaut est toujours `None`, donc le paramètre est toujours optionnel. + +Mais maintenant, avec `Query(max_length=50)` à l’intérieur de `Annotated`, nous indiquons à FastAPI que nous voulons **une validation supplémentaire** pour cette valeur, nous voulons qu’elle ait au maximum 50 caractères. 😎 + +/// tip | Astuce + +Ici nous utilisons `Query()` parce qu’il s’agit d’un **paramètre de requête**. Plus tard nous verrons d’autres comme `Path()`, `Body()`, `Header()` et `Cookie()`, qui acceptent également les mêmes arguments que `Query()`. /// -Ensuite, nous pouvons passer d'autres paramètres à `Query`. Dans cet exemple, le paramètre `max_length` qui s'applique aux chaînes de caractères : +FastAPI va maintenant : -```Python -q: Union[str, None] = Query(default=None, max_length=50) -``` +- **Valider** les données en s’assurant que la longueur maximale est de 50 caractères +- Afficher une **erreur claire** au client quand les données ne sont pas valides +- **Documenter** le paramètre dans la *chemin d'accès* du schéma OpenAPI (il apparaîtra donc dans l’**interface de documentation automatique**) -Cela va valider les données, montrer une erreur claire si ces dernières ne sont pas valides, et documenter le paramètre dans le schéma `OpenAPI` de cette *path operation*. +## Alternative (ancienne) : `Query` comme valeur par défaut { #alternative-old-query-as-the-default-value } -## Rajouter plus de validation +Les versions précédentes de FastAPI (avant 0.95.0) exigeaient d’utiliser `Query` comme valeur par défaut de votre paramètre, au lieu de le mettre dans `Annotated`. Il y a de fortes chances que vous voyiez du code qui l’utilise encore, je vais donc vous l’expliquer. -Vous pouvez aussi rajouter un second paramètre `min_length` : +/// tip | Astuce -{* ../../docs_src/query_params_str_validations/tutorial003.py hl[9] *} - -## Ajouter des validations par expressions régulières - -On peut définir une expression régulière à laquelle le paramètre doit correspondre : - -{* ../../docs_src/query_params_str_validations/tutorial004.py hl[10] *} - -Cette expression régulière vérifie que la valeur passée comme paramètre : - -* `^` : commence avec les caractères qui suivent, avec aucun caractère avant ceux-là. -* `fixedquery` : a pour valeur exacte `fixedquery`. -* `$` : se termine directement ensuite, n'a pas d'autres caractères après `fixedquery`. - -Si vous vous sentez perdu avec le concept d'**expression régulière**, pas d'inquiétudes. Il s'agit d'une notion difficile pour beaucoup, et l'on peut déjà réussir à faire beaucoup sans jamais avoir à les manipuler. - -Mais si vous décidez d'apprendre à les utiliser, sachez qu'ensuite vous pouvez les utiliser directement dans **FastAPI**. - -## Valeurs par défaut - -De la même façon que vous pouvez passer `None` comme premier argument pour l'utiliser comme valeur par défaut, vous pouvez passer d'autres valeurs. - -Disons que vous déclarez le paramètre `q` comme ayant une longueur minimale de `3`, et une valeur par défaut étant `"fixedquery"` : - -{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *} - -/// note | Rappel - -Avoir une valeur par défaut rend le paramètre optionnel. +Pour du nouveau code et dès que possible, utilisez `Annotated` comme expliqué ci-dessus. Il y a de multiples avantages (expliqués ci-dessous) et aucun inconvénient. 🍰 /// -## Rendre ce paramètre requis +Voici comment vous utiliseriez `Query()` comme valeur par défaut du paramètre de votre fonction, en définissant le paramètre `max_length` à 50 : -Quand on ne déclare ni validation, ni métadonnée, on peut rendre le paramètre `q` requis en ne lui déclarant juste aucune valeur par défaut : +{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *} + +Comme, dans ce cas (sans utiliser `Annotated`), nous devons remplacer la valeur par défaut `None` dans la fonction par `Query()`, nous devons maintenant définir la valeur par défaut avec le paramètre `Query(default=None)`, cela sert le même objectif de définir cette valeur par défaut (au moins pour FastAPI). + +Donc : + +```Python +q: str | None = Query(default=None) +``` + +... rend le paramètre optionnel, avec une valeur par défaut de `None`, comme : + +```Python +q: str | None = None +``` + +Mais la version avec `Query` le déclare explicitement comme étant un paramètre de requête. + +Ensuite, nous pouvons passer plus de paramètres à `Query`. Dans ce cas, le paramètre `max_length` qui s’applique aux chaînes de caractères : + +```Python +q: str | None = Query(default=None, max_length=50) +``` + +Cela validera les données, affichera une erreur claire lorsque les données ne sont pas valides et documentera le paramètre dans la *chemin d'accès* du schéma OpenAPI. + +### `Query` comme valeur par défaut ou dans `Annotated` { #query-as-the-default-value-or-in-annotated } + +Gardez à l’esprit qu’en utilisant `Query` à l’intérieur de `Annotated`, vous ne pouvez pas utiliser le paramètre `default` de `Query`. + +Utilisez à la place la valeur par défaut réelle du paramètre de fonction. Sinon, ce serait incohérent. + +Par exemple, ceci n’est pas autorisé : + +```Python +q: Annotated[str, Query(default="rick")] = "morty" +``` + +... parce qu’il n’est pas clair si la valeur par défaut doit être « rick » ou « morty ». + +Donc, vous utiliseriez (de préférence) : + +```Python +q: Annotated[str, Query()] = "rick" +``` + +... ou dans des bases de code plus anciennes, vous trouverez : + +```Python +q: str = Query(default="rick") +``` + +### Avantages de `Annotated` { #advantages-of-annotated } + +**L’utilisation de `Annotated` est recommandée** plutôt que la valeur par défaut dans les paramètres de fonction, c’est **mieux** pour plusieurs raisons. 🤓 + +La valeur **par défaut** du **paramètre de fonction** est la **vraie valeur par défaut**, c’est plus intuitif en Python en général. 😌 + +Vous pouvez **appeler** cette même fonction dans **d’autres endroits** sans FastAPI, et elle **fonctionnera comme prévu**. S’il y a un paramètre **requis** (sans valeur par défaut), votre **éditeur** vous le signalera avec une erreur, **Python** se plaindra aussi si vous l’exécutez sans passer le paramètre requis. + +Quand vous n’utilisez pas `Annotated` et utilisez à la place l’**ancienne** méthode avec la **valeur par défaut**, si vous appelez cette fonction sans FastAPI dans **d’autres endroits**, vous devez **penser** à passer les arguments à la fonction pour qu’elle fonctionne correctement, sinon les valeurs seront différentes de ce que vous attendez (par ex. `QueryInfo` ou quelque chose de similaire au lieu d’une `str`). Et votre éditeur ne se plaindra pas, et Python ne se plaindra pas en exécutant cette fonction, seulement quand les opérations internes échoueront. + +Comme `Annotated` peut avoir plus d’une annotation de métadonnées, vous pouvez maintenant même utiliser la même fonction avec d’autres outils, comme Typer. 🚀 + +## Ajouter plus de validations { #add-more-validations } + +Vous pouvez également ajouter un paramètre `min_length` : + +{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *} + +## Ajouter des expressions régulières { #add-regular-expressions } + +Vous pouvez définir un `pattern` d’expression régulière auquel le paramètre doit correspondre : + +{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} + +Ce pattern d’expression régulière spécifique vérifie que la valeur reçue pour le paramètre : + +- `^` : commence avec les caractères qui suivent, n’a pas de caractères avant. +- `fixedquery` : a exactement la valeur `fixedquery`. +- `$` : se termine là, n’a pas d’autres caractères après `fixedquery`. + +Si vous vous sentez perdu avec toutes ces idées d’**« expression régulière »**, pas d’inquiétude. C’est un sujet difficile pour beaucoup. Vous pouvez déjà faire beaucoup de choses sans avoir besoin d’expressions régulières. + +Désormais, vous savez que, lorsque vous en aurez besoin, vous pourrez les utiliser dans **FastAPI**. + +## Valeurs par défaut { #default-values } + +Vous pouvez, bien sûr, utiliser des valeurs par défaut autres que `None`. + +Disons que vous voulez déclarer le paramètre de requête `q` avec un `min_length` de `3`, et avec une valeur par défaut de « fixedquery » : + +{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} + +/// note | Remarque + +Avoir une valeur par défaut de n’importe quel type, y compris `None`, rend le paramètre optionnel (non requis). + +/// + +## Paramètres requis { #required-parameters } + +Quand nous n’avons pas besoin de déclarer plus de validations ou de métadonnées, nous pouvons rendre le paramètre de requête `q` requis en n’indiquant simplement pas de valeur par défaut, comme : ```Python q: str ``` -à la place de : +au lieu de : ```Python -q: Union[str, None] = None +q: str | None = None ``` -Mais maintenant, on déclare `q` avec `Query`, comme ceci : +Mais maintenant nous le déclarons avec `Query`, par exemple ainsi : ```Python -q: Union[str, None] = Query(default=None, min_length=3) +q: Annotated[str | None, Query(min_length=3)] = None ``` -Donc pour déclarer une valeur comme requise tout en utilisant `Query`, il faut utiliser `...` comme premier argument : +Donc, lorsque vous avez besoin de déclarer une valeur comme requise tout en utilisant `Query`, vous pouvez simplement ne pas déclarer de valeur par défaut : -{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -/// info +### Requis, peut valoir `None` { #required-can-be-none } -Si vous n'avez jamais vu ce `...` auparavant : c'est une des constantes natives de Python appelée "Ellipsis". +Vous pouvez déclarer qu’un paramètre accepte `None`, mais qu’il est tout de même requis. Cela obligerait les clients à envoyer une valeur, même si la valeur est `None`. -/// +Pour ce faire, vous pouvez déclarer que `None` est un type valide tout en ne déclarant pas de valeur par défaut : -Cela indiquera à **FastAPI** que la présence de ce paramètre est obligatoire. +{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} -## Liste de paramètres / valeurs multiples via Query +## Liste de paramètres de requête / valeurs multiples { #query-parameter-list-multiple-values } -Quand on définit un paramètre de requête explicitement avec `Query` on peut aussi déclarer qu'il reçoit une liste de valeur, ou des "valeurs multiples". +Quand vous définissez un paramètre de requête explicitement avec `Query`, vous pouvez aussi déclarer qu’il reçoit une liste de valeurs, autrement dit, qu’il reçoit des valeurs multiples. -Par exemple, pour déclarer un paramètre de requête `q` qui peut apparaître plusieurs fois dans une URL, on écrit : +Par exemple, pour déclarer un paramètre de requête `q` qui peut apparaître plusieurs fois dans l’URL, vous pouvez écrire : -{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *} -Ce qui fait qu'avec une URL comme : +Ensuite, avec une URL comme : ``` http://localhost:8000/items/?q=foo&q=bar ``` -vous recevriez les valeurs des multiples paramètres de requête `q` (`foo` et `bar`) dans une `list` Python au sein de votre fonction de **path operation**, dans le paramètre de fonction `q`. +vous recevriez les valeurs des multiples paramètres de requête `q` (`foo` et `bar`) dans une `list` Python à l’intérieur de votre fonction de *chemin d'accès*, dans le *paramètre de fonction* `q`. -Donc la réponse de cette URL serait : +Donc, la réponse pour cette URL serait : ```JSON { @@ -173,19 +280,19 @@ Donc la réponse de cette URL serait : /// tip | Astuce -Pour déclarer un paramètre de requête de type `list`, comme dans l'exemple ci-dessus, il faut explicitement utiliser `Query`, sinon cela sera interprété comme faisant partie du corps de la requête. +Pour déclarer un paramètre de requête avec un type `list`, comme dans l’exemple ci-dessus, vous devez explicitement utiliser `Query`, sinon il serait interprété comme faisant partie du corps de la requête. /// -La documentation sera donc mise à jour automatiquement pour autoriser plusieurs valeurs : +L’interface de documentation interactive de l’API sera mise à jour en conséquence, pour autoriser plusieurs valeurs : -### Combiner liste de paramètres et valeurs par défaut +### Liste de paramètres de requête / valeurs multiples avec valeurs par défaut { #query-parameter-list-multiple-values-with-defaults } -Et l'on peut aussi définir une liste de valeurs par défaut si aucune n'est fournie : +Vous pouvez également définir une `list` de valeurs par défaut si aucune n’est fournie : -{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} Si vous allez à : @@ -193,9 +300,7 @@ Si vous allez à : http://localhost:8000/items/ ``` -la valeur par défaut de `q` sera : `["foo", "bar"]` - -et la réponse sera : +la valeur par défaut de `q` sera : `["foo", "bar"]` et votre réponse sera : ```JSON { @@ -206,93 +311,163 @@ et la réponse sera : } ``` -#### Utiliser `list` +#### Utiliser simplement `list` { #using-just-list } -Il est aussi possible d'utiliser directement `list` plutôt que `List[str]` : +Vous pouvez aussi utiliser `list` directement au lieu de `list[str]` : -{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} -/// note +/// note | Remarque -Dans ce cas-là, **FastAPI** ne vérifiera pas le contenu de la liste. +Gardez à l’esprit que dans ce cas, FastAPI ne vérifiera pas le contenu de la liste. -Par exemple, `List[int]` vérifiera (et documentera) que la liste est bien entièrement composée d'entiers. Alors qu'un simple `list` ne ferait pas cette vérification. +Par exemple, `list[int]` vérifierait (et documenterait) que le contenu de la liste est composé d’entiers. Mais un simple `list` ne le ferait pas. /// -## Déclarer des métadonnées supplémentaires +## Déclarer plus de métadonnées { #declare-more-metadata } -On peut aussi ajouter plus d'informations sur le paramètre. +Vous pouvez ajouter plus d’informations à propos du paramètre. -Ces informations seront incluses dans le schéma `OpenAPI` généré et utilisées par la documentation interactive ou les outils externes utilisés. +Ces informations seront incluses dans l’OpenAPI généré et utilisées par les interfaces de documentation et les outils externes. -/// note +/// note | Remarque -Gardez en tête que les outils externes utilisés ne supportent pas forcément tous parfaitement OpenAPI. +Gardez à l’esprit que différents outils peuvent avoir des niveaux de prise en charge d’OpenAPI différents. -Il se peut donc que certains d'entre eux n'utilisent pas toutes les métadonnées que vous avez déclarées pour le moment, bien que dans la plupart des cas, les fonctionnalités manquantes ont prévu d'être implémentées. +Certains d’entre eux pourraient ne pas encore afficher toutes les informations supplémentaires déclarées, bien que, dans la plupart des cas, la fonctionnalité manquante soit déjà prévue au développement. /// Vous pouvez ajouter un `title` : -{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *} +{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *} Et une `description` : -{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *} +{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *} -## Alias de paramètres +## Paramètres avec alias { #alias-parameters } -Imaginez que vous vouliez que votre paramètre se nomme `item-query`. +Imaginez que vous vouliez que le paramètre soit `item-query`. -Comme dans la requête : +Comme dans : ``` http://127.0.0.1:8000/items/?item-query=foobaritems ``` -Mais `item-query` n'est pas un nom de variable valide en Python. +Mais `item-query` n’est pas un nom de variable Python valide. -Le nom le plus proche serait `item_query`. +Le plus proche serait `item_query`. -Mais vous avez vraiment envie que ce soit exactement `item-query`... +Mais vous avez quand même besoin que ce soit exactement `item-query` ... -Pour cela vous pouvez déclarer un `alias`, et cet alias est ce qui sera utilisé pour trouver la valeur du paramètre : +Vous pouvez alors déclarer un `alias`, et cet alias sera utilisé pour trouver la valeur du paramètre : -{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *} -## Déprécier des paramètres +## Déprécier des paramètres { #deprecating-parameters } -Disons que vous ne vouliez plus utiliser ce paramètre désormais. +Disons que vous n’aimez plus ce paramètre. -Il faut qu'il continue à exister pendant un certain temps car vos clients l'utilisent, mais vous voulez que la documentation mentionne clairement que ce paramètre est déprécié. +Vous devez le laisser là quelque temps car des clients l’utilisent, mais vous voulez que les documents l’affichent clairement comme déprécié. -On utilise alors l'argument `deprecated=True` de `Query` : +Passez alors le paramètre `deprecated=True` à `Query` : -{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *} +{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *} -La documentation le présentera comme il suit : +Les documents l’afficheront ainsi : -## Pour résumer +## Exclure des paramètres d’OpenAPI { #exclude-parameters-from-openapi } -Il est possible d'ajouter des validateurs et métadonnées pour vos paramètres. +Pour exclure un paramètre de requête du schéma OpenAPI généré (et donc, des systèmes de documentation automatiques), définissez le paramètre `include_in_schema` de `Query` à `False` : -Validateurs et métadonnées génériques: +{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *} -* `alias` -* `title` -* `description` -* `deprecated` +## Validation personnalisée { #custom-validation } -Validateurs spécifiques aux chaînes de caractères : +Il peut y avoir des cas où vous devez faire une **validation personnalisée** qui ne peut pas être réalisée avec les paramètres montrés ci-dessus. -* `min_length` -* `max_length` -* `regex` +Dans ces cas, vous pouvez utiliser une **fonction de validation personnalisée** qui est appliquée après la validation normale (par ex. après avoir validé que la valeur est une `str`). -Parmi ces exemples, vous avez pu voir comment déclarer des validateurs pour les chaînes de caractères. +Vous pouvez y parvenir en utilisant `AfterValidator` de Pydantic à l’intérieur de `Annotated`. -Dans les prochains chapitres, vous verrez comment déclarer des validateurs pour d'autres types, comme les nombres. +/// tip | Astuce + +Pydantic a aussi `BeforeValidator` et d’autres. 🤓 + +/// + +Par exemple, ce validateur personnalisé vérifie que l’ID d’item commence par `isbn-` pour un numéro de livre ISBN ou par `imdb-` pour un ID d’URL de film IMDB : + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} + +/// info + +C’est disponible avec Pydantic version 2 ou supérieure. 😎 + +/// + +/// tip | Astuce + +Si vous devez faire un type de validation qui nécessite de communiquer avec un **composant externe**, comme une base de données ou une autre API, vous devez plutôt utiliser les **Dépendances de FastAPI**, vous en apprendrez davantage plus tard. + +Ces validateurs personnalisés sont destinés aux éléments qui peuvent être vérifiés **uniquement** avec les **mêmes données** fournies dans la requête. + +/// + +### Comprendre ce code { #understand-that-code } + +Le point important est simplement d’utiliser **`AfterValidator` avec une fonction à l’intérieur de `Annotated`**. N’hésitez pas à passer cette partie. 🤸 + +--- + +Mais si vous êtes curieux de cet exemple de code spécifique et que vous êtes toujours partant, voici quelques détails supplémentaires. + +#### Chaîne avec `value.startswith()` { #string-with-value-startswith } + +Avez-vous remarqué ? Une chaîne utilisant `value.startswith()` peut prendre un tuple, et elle vérifiera chaque valeur du tuple : + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *} + +#### Un élément aléatoire { #a-random-item } + +Avec `data.items()` nous obtenons un objet itérable avec des tuples contenant la clé et la valeur pour chaque élément du dictionnaire. + +Nous convertissons cet objet itérable en une `list` propre avec `list(data.items())`. + +Ensuite, avec `random.choice()` nous pouvons obtenir une **valeur aléatoire** depuis la liste, nous obtenons donc un tuple `(id, name)`. Ce sera quelque chose comme `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. + +Puis nous **affectons ces deux valeurs** du tuple aux variables `id` et `name`. + +Ainsi, si l’utilisateur n’a pas fourni d’ID d’item, il recevra quand même une suggestion aléatoire. + +... nous faisons tout cela en **une seule ligne simple**. 🤯 Vous n’adorez pas Python ? 🐍 + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} + +## Récapitulatif { #recap } + +Vous pouvez déclarer des validations et des métadonnées supplémentaires pour vos paramètres. + +Validations et métadonnées génériques : + +- `alias` +- `title` +- `description` +- `deprecated` + +Validations spécifiques aux chaînes : + +- `min_length` +- `max_length` +- `pattern` + +Validations personnalisées avec `AfterValidator`. + +Dans ces exemples, vous avez vu comment déclarer des validations pour des valeurs `str`. + +Voyez les prochains chapitres pour apprendre à déclarer des validations pour d’autres types, comme les nombres. diff --git a/docs/fr/docs/tutorial/query-params.md b/docs/fr/docs/tutorial/query-params.md index b87c26c78..1a4880ced 100644 --- a/docs/fr/docs/tutorial/query-params.md +++ b/docs/fr/docs/tutorial/query-params.md @@ -1,10 +1,10 @@ -# Paramètres de requête +# Paramètres de requête { #query-parameters } -Quand vous déclarez des paramètres dans votre fonction de chemin qui ne font pas partie des paramètres indiqués dans le chemin associé, ces paramètres sont automatiquement considérés comme des paramètres de "requête". +Quand vous déclarez d'autres paramètres de fonction qui ne font pas partie des paramètres de chemin, ils sont automatiquement interprétés comme des paramètres de « query ». -{* ../../docs_src/query_params/tutorial001.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} -La partie appelée requête (ou **query**) dans une URL est l'ensemble des paires clés-valeurs placées après le `?` , séparées par des `&`. +La query est l'ensemble des paires clé-valeur placées après le `?` dans une URL, séparées par des caractères `&`. Par exemple, dans l'URL : @@ -12,27 +12,27 @@ Par exemple, dans l'URL : http://127.0.0.1:8000/items/?skip=0&limit=10 ``` -...les paramètres de requête sont : +... les paramètres de requête sont : -* `skip` : avec une valeur de`0` +* `skip` : avec une valeur de `0` * `limit` : avec une valeur de `10` -Faisant partie de l'URL, ces valeurs sont des chaînes de caractères (`str`). +Comme ils font partie de l'URL, ce sont « naturellement » des chaînes de caractères. -Mais quand on les déclare avec des types Python (dans l'exemple précédent, en tant qu'`int`), elles sont converties dans les types renseignés. +Mais lorsque vous les déclarez avec des types Python (dans l'exemple ci-dessus, en tant que `int`), ils sont convertis vers ce type et validés par rapport à celui-ci. -Toutes les fonctionnalités qui s'appliquent aux paramètres de chemin s'appliquent aussi aux paramètres de requête : +Tous les mêmes processus qui s'appliquaient aux paramètres de chemin s'appliquent aussi aux paramètres de requête : -* Support de l'éditeur : vérification d'erreurs, auto-complétion, etc. -* "Parsing" de données. -* Validation de données. -* Annotations d'API et documentation automatique. +* Prise en charge de l'éditeur (évidemment) +* « parsing » des données +* Validation des données +* Documentation automatique -## Valeurs par défaut +## Valeurs par défaut { #defaults } -Les paramètres de requête ne sont pas une partie fixe d'un chemin, ils peuvent être optionnels et avoir des valeurs par défaut. +Comme les paramètres de requête ne sont pas une partie fixe d'un chemin, ils peuvent être optionnels et avoir des valeurs par défaut. -Dans l'exemple ci-dessus, ils ont des valeurs par défaut qui sont `skip=0` et `limit=10`. +Dans l'exemple ci-dessus, ils ont des valeurs par défaut `skip=0` et `limit=10`. Donc, accéder à l'URL : @@ -40,52 +40,44 @@ Donc, accéder à l'URL : http://127.0.0.1:8000/items/ ``` -serait équivalent à accéder à l'URL : +serait équivalent à accéder à : ``` http://127.0.0.1:8000/items/?skip=0&limit=10 ``` -Mais si vous accédez à, par exemple : +Mais si vous accédez, par exemple, à : ``` http://127.0.0.1:8000/items/?skip=20 ``` -Les valeurs des paramètres de votre fonction seront : +Les valeurs des paramètres dans votre fonction seront : -* `skip=20` : car c'est la valeur déclarée dans l'URL. -* `limit=10` : car `limit` n'a pas été déclaré dans l'URL, et que la valeur par défaut était `10`. +* `skip=20` : car vous l'avez défini dans l'URL +* `limit=10` : car c'était la valeur par défaut -## Paramètres optionnels +## Paramètres optionnels { #optional-parameters } -De la même façon, vous pouvez définir des paramètres de requête comme optionnels, en leur donnant comme valeur par défaut `None` : +De la même façon, vous pouvez déclarer des paramètres de requête optionnels, en définissant leur valeur par défaut à `None` : -{* ../../docs_src/query_params/tutorial002.py hl[9] *} +{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *} -Ici, le paramètre `q` sera optionnel, et aura `None` comme valeur par défaut. +Dans ce cas, le paramètre de fonction `q` sera optionnel et vaudra `None` par défaut. -/// check | Remarque +/// check | Vérifications -On peut voir que **FastAPI** est capable de détecter que le paramètre de chemin `item_id` est un paramètre de chemin et que `q` n'en est pas un, c'est donc un paramètre de requête. +Notez également que FastAPI est suffisamment intelligent pour remarquer que le paramètre de chemin `item_id` est un paramètre de chemin et que `q` ne l'est pas, c'est donc un paramètre de requête. /// -/// note +## Conversion des types des paramètres de requête { #query-parameter-type-conversion } -**FastAPI** saura que `q` est optionnel grâce au `=None`. +Vous pouvez aussi déclarer des types `bool`, ils seront convertis : -Le `Optional` dans `Optional[str]` n'est pas utilisé par **FastAPI** (**FastAPI** n'en utilisera que la partie `str`), mais il servira tout de même à votre éditeur de texte pour détecter des erreurs dans votre code. +{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *} -/// - -## Conversion des types des paramètres de requête - -Vous pouvez aussi déclarer des paramètres de requête comme booléens (`bool`), **FastAPI** les convertira : - -{* ../../docs_src/query_params/tutorial003.py hl[9] *} - -Avec ce code, en allant sur : +Dans ce cas, si vous allez sur : ``` http://127.0.0.1:8000/items/foo?short=1 @@ -115,60 +107,61 @@ ou http://127.0.0.1:8000/items/foo?short=yes ``` -ou n'importe quelle autre variation de casse (tout en majuscules, uniquement la première lettre en majuscule, etc.), votre fonction considérera le paramètre `short` comme ayant une valeur booléenne à `True`. Sinon la valeur sera à `False`. +ou n'importe quelle autre variation de casse (tout en majuscules, uniquement la première lettre en majuscule, etc.), votre fonction verra le paramètre `short` avec une valeur `bool` à `True`. Sinon la valeur sera à `False`. -## Multiples paramètres de chemin et de requête +## Multiples paramètres de chemin et de requête { #multiple-path-and-query-parameters } -Vous pouvez déclarer plusieurs paramètres de chemin et paramètres de requête dans la même fonction, **FastAPI** saura comment les gérer. +Vous pouvez déclarer plusieurs paramètres de chemin et paramètres de requête en même temps, FastAPI sait lequel est lequel. Et vous n'avez pas besoin de les déclarer dans un ordre spécifique. -Ils seront détectés par leurs noms : +Ils seront détectés par leur nom : -{* ../../docs_src/query_params/tutorial004.py hl[8,10] *} +{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *} -## Paramètres de requête requis +## Paramètres de requête requis { #required-query-parameters } -Quand vous déclarez une valeur par défaut pour un paramètre qui n'est pas un paramètre de chemin (actuellement, nous n'avons vu que les paramètres de requête), alors ce paramètre n'est pas requis. +Quand vous déclarez une valeur par défaut pour des paramètres qui ne sont pas des paramètres de chemin (pour l'instant, nous n'avons vu que les paramètres de requête), alors ils ne sont pas requis. -Si vous ne voulez pas leur donner de valeur par défaut mais juste les rendre optionnels, utilisez `None` comme valeur par défaut. +Si vous ne voulez pas leur donner de valeur spécifique mais simplement les rendre optionnels, définissez la valeur par défaut à `None`. -Mais si vous voulez rendre un paramètre de requête obligatoire, vous pouvez juste ne pas y affecter de valeur par défaut : +Mais si vous voulez rendre un paramètre de requête obligatoire, vous pouvez simplement ne déclarer aucune valeur par défaut : -{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} -Ici le paramètre `needy` est un paramètre requis (ou obligatoire) de type `str`. +Ici, le paramètre de requête `needy` est un paramètre de requête requis de type `str`. -Si vous ouvrez une URL comme : +Si vous ouvrez dans votre navigateur une URL comme : ``` http://127.0.0.1:8000/items/foo-item ``` -...sans ajouter le paramètre requis `needy`, vous aurez une erreur : +... sans ajouter le paramètre requis `needy`, vous verrez une erreur comme : ```JSON { - "detail": [ - { - "loc": [ - "query", - "needy" - ], - "msg": "field required", - "type": "value_error.missing" - } - ] + "detail": [ + { + "type": "missing", + "loc": [ + "query", + "needy" + ], + "msg": "Field required", + "input": null + } + ] } ``` -La présence de `needy` étant nécessaire, vous auriez besoin de l'insérer dans l'URL : +Comme `needy` est un paramètre requis, vous devez le définir dans l'URL : ``` http://127.0.0.1:8000/items/foo-item?needy=sooooneedy ``` -...ce qui fonctionnerait : +... cela fonctionnerait : ```JSON { @@ -177,18 +170,18 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy } ``` -Et bien sur, vous pouvez définir certains paramètres comme requis, certains avec des valeurs par défaut et certains entièrement optionnels : +Et bien sûr, vous pouvez définir certains paramètres comme requis, certains avec une valeur par défaut et certains entièrement optionnels : -{* ../../docs_src/query_params/tutorial006.py hl[10] *} +{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *} -Ici, on a donc 3 paramètres de requête : +Dans ce cas, il y a 3 paramètres de requête : -* `needy`, requis et de type `str`. -* `skip`, un `int` avec comme valeur par défaut `0`. +* `needy`, un `str` requis. +* `skip`, un `int` avec une valeur par défaut de `0`. * `limit`, un `int` optionnel. /// tip | Astuce -Vous pouvez utiliser les `Enum`s de la même façon qu'avec les [Paramètres de chemin](path-params.md#valeurs-predefinies){.internal-link target=_blank}. +Vous pourriez aussi utiliser des `Enum`s de la même façon qu'avec les [Paramètres de chemin](path-params.md#predefined-values){.internal-link target=_blank}. /// From 2c5a844c44c053251d557ce888811ea40dea0b01 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 15:34:47 +0000 Subject: [PATCH 008/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 9f70eccb5..c54875f06 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for fr (update-outdated). PR [#14826](https://github.com/fastapi/fastapi/pull/14826) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for zh-hant (update-outdated). PR [#14825](https://github.com/fastapi/fastapi/pull/14825) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for uk (update-outdated). PR [#14822](https://github.com/fastapi/fastapi/pull/14822) by [@tiangolo](https://github.com/tiangolo). * 🔨 Update docs and translations scripts, enable Turkish. PR [#14824](https://github.com/fastapi/fastapi/pull/14824) by [@tiangolo](https://github.com/tiangolo). From b5d276bda8d8f61e6df6ffe3472de2600588774c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 07:43:38 -0800 Subject: [PATCH 009/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20tr=20(add-missing)=20(#14790)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/tr/docs/_llm-test.md | 503 ++++++++++ docs/tr/docs/advanced/additional-responses.md | 247 +++++ .../docs/advanced/additional-status-codes.md | 41 + .../tr/docs/advanced/advanced-dependencies.md | 163 ++++ docs/tr/docs/advanced/async-tests.md | 99 ++ docs/tr/docs/advanced/behind-a-proxy.md | 466 ++++++++++ docs/tr/docs/advanced/custom-response.md | 312 +++++++ docs/tr/docs/advanced/dataclasses.md | 95 ++ docs/tr/docs/advanced/events.md | 165 ++++ docs/tr/docs/advanced/generate-clients.md | 208 +++++ docs/tr/docs/advanced/middleware.md | 97 ++ docs/tr/docs/advanced/openapi-callbacks.md | 186 ++++ docs/tr/docs/advanced/openapi-webhooks.md | 55 ++ .../path-operation-advanced-configuration.md | 172 ++++ .../advanced/response-change-status-code.md | 31 + docs/tr/docs/advanced/response-cookies.md | 51 ++ docs/tr/docs/advanced/response-directly.md | 65 ++ docs/tr/docs/advanced/response-headers.md | 41 + .../docs/advanced/security/http-basic-auth.md | 107 +++ .../docs/advanced/security/oauth2-scopes.md | 274 ++++++ docs/tr/docs/advanced/settings.md | 302 ++++++ docs/tr/docs/advanced/sub-applications.md | 67 ++ docs/tr/docs/advanced/templates.md | 126 +++ docs/tr/docs/advanced/testing-dependencies.md | 53 ++ docs/tr/docs/advanced/testing-events.md | 12 + .../docs/advanced/using-request-directly.md | 56 ++ docs/tr/docs/advanced/websockets.md | 186 ++++ docs/tr/docs/deployment/concepts.md | 321 +++++++ docs/tr/docs/deployment/docker.md | 620 +++++++++++++ docs/tr/docs/deployment/fastapicloud.md | 65 ++ docs/tr/docs/deployment/https.md | 231 +++++ docs/tr/docs/deployment/manually.md | 157 ++++ docs/tr/docs/deployment/server-workers.md | 139 +++ docs/tr/docs/deployment/versions.md | 93 ++ docs/tr/docs/environment-variables.md | 298 ++++++ docs/tr/docs/fastapi-cli.md | 75 ++ docs/tr/docs/help-fastapi.md | 256 ++++++ .../authentication-error-status-code.md | 17 + docs/tr/docs/how-to/conditional-openapi.md | 56 ++ docs/tr/docs/how-to/configure-swagger-ui.md | 70 ++ docs/tr/docs/how-to/custom-docs-ui-assets.md | 185 ++++ .../docs/how-to/custom-request-and-route.md | 109 +++ docs/tr/docs/how-to/extending-openapi.md | 80 ++ docs/tr/docs/how-to/graphql.md | 60 ++ ...migrate-from-pydantic-v1-to-pydantic-v2.md | 135 +++ .../docs/how-to/separate-openapi-schemas.md | 102 +++ docs/tr/docs/how-to/testing-database.md | 7 + docs/tr/docs/tutorial/background-tasks.md | 84 ++ docs/tr/docs/tutorial/bigger-applications.md | 504 ++++++++++ docs/tr/docs/tutorial/body-fields.md | 60 ++ docs/tr/docs/tutorial/body-multiple-params.md | 175 ++++ docs/tr/docs/tutorial/body-nested-models.md | 220 +++++ docs/tr/docs/tutorial/body-updates.md | 100 ++ docs/tr/docs/tutorial/body.md | 166 ++++ docs/tr/docs/tutorial/cookie-param-models.md | 76 ++ docs/tr/docs/tutorial/cors.md | 89 ++ docs/tr/docs/tutorial/debugging.md | 113 +++ .../dependencies/classes-as-dependencies.md | 288 ++++++ ...pendencies-in-path-operation-decorators.md | 69 ++ .../dependencies/dependencies-with-yield.md | 289 ++++++ .../dependencies/global-dependencies.md | 16 + docs/tr/docs/tutorial/dependencies/index.md | 250 +++++ .../tutorial/dependencies/sub-dependencies.md | 105 +++ docs/tr/docs/tutorial/encoder.md | 35 + docs/tr/docs/tutorial/extra-data-types.md | 62 ++ docs/tr/docs/tutorial/extra-models.md | 211 +++++ docs/tr/docs/tutorial/handling-errors.md | 244 +++++ docs/tr/docs/tutorial/header-param-models.md | 72 ++ docs/tr/docs/tutorial/header-params.md | 91 ++ docs/tr/docs/tutorial/index.md | 95 ++ docs/tr/docs/tutorial/metadata.md | 120 +++ docs/tr/docs/tutorial/middleware.md | 95 ++ .../tutorial/path-operation-configuration.md | 107 +++ .../path-params-numeric-validations.md | 154 ++++ docs/tr/docs/tutorial/query-param-models.md | 68 ++ .../tutorial/query-params-str-validations.md | 473 ++++++++++ docs/tr/docs/tutorial/request-files.md | 176 ++++ docs/tr/docs/tutorial/request-form-models.md | 78 ++ .../docs/tutorial/request-forms-and-files.md | 41 + docs/tr/docs/tutorial/response-model.md | 343 +++++++ docs/tr/docs/tutorial/response-status-code.md | 101 ++ docs/tr/docs/tutorial/schema-extra-example.md | 202 ++++ docs/tr/docs/tutorial/security/first-steps.md | 203 +++++ .../tutorial/security/get-current-user.md | 105 +++ docs/tr/docs/tutorial/security/index.md | 106 +++ docs/tr/docs/tutorial/security/oauth2-jwt.md | 273 ++++++ .../docs/tutorial/security/simple-oauth2.md | 289 ++++++ docs/tr/docs/tutorial/sql-databases.md | 357 ++++++++ docs/tr/docs/tutorial/testing.md | 190 ++++ docs/tr/docs/virtual-environments.md | 862 ++++++++++++++++++ 90 files changed, 15013 insertions(+) create mode 100644 docs/tr/docs/_llm-test.md create mode 100644 docs/tr/docs/advanced/additional-responses.md create mode 100644 docs/tr/docs/advanced/additional-status-codes.md create mode 100644 docs/tr/docs/advanced/advanced-dependencies.md create mode 100644 docs/tr/docs/advanced/async-tests.md create mode 100644 docs/tr/docs/advanced/behind-a-proxy.md create mode 100644 docs/tr/docs/advanced/custom-response.md create mode 100644 docs/tr/docs/advanced/dataclasses.md create mode 100644 docs/tr/docs/advanced/events.md create mode 100644 docs/tr/docs/advanced/generate-clients.md create mode 100644 docs/tr/docs/advanced/middleware.md create mode 100644 docs/tr/docs/advanced/openapi-callbacks.md create mode 100644 docs/tr/docs/advanced/openapi-webhooks.md create mode 100644 docs/tr/docs/advanced/path-operation-advanced-configuration.md create mode 100644 docs/tr/docs/advanced/response-change-status-code.md create mode 100644 docs/tr/docs/advanced/response-cookies.md create mode 100644 docs/tr/docs/advanced/response-directly.md create mode 100644 docs/tr/docs/advanced/response-headers.md create mode 100644 docs/tr/docs/advanced/security/http-basic-auth.md create mode 100644 docs/tr/docs/advanced/security/oauth2-scopes.md create mode 100644 docs/tr/docs/advanced/settings.md create mode 100644 docs/tr/docs/advanced/sub-applications.md create mode 100644 docs/tr/docs/advanced/templates.md create mode 100644 docs/tr/docs/advanced/testing-dependencies.md create mode 100644 docs/tr/docs/advanced/testing-events.md create mode 100644 docs/tr/docs/advanced/using-request-directly.md create mode 100644 docs/tr/docs/advanced/websockets.md create mode 100644 docs/tr/docs/deployment/concepts.md create mode 100644 docs/tr/docs/deployment/docker.md create mode 100644 docs/tr/docs/deployment/fastapicloud.md create mode 100644 docs/tr/docs/deployment/https.md create mode 100644 docs/tr/docs/deployment/manually.md create mode 100644 docs/tr/docs/deployment/server-workers.md create mode 100644 docs/tr/docs/deployment/versions.md create mode 100644 docs/tr/docs/environment-variables.md create mode 100644 docs/tr/docs/fastapi-cli.md create mode 100644 docs/tr/docs/help-fastapi.md create mode 100644 docs/tr/docs/how-to/authentication-error-status-code.md create mode 100644 docs/tr/docs/how-to/conditional-openapi.md create mode 100644 docs/tr/docs/how-to/configure-swagger-ui.md create mode 100644 docs/tr/docs/how-to/custom-docs-ui-assets.md create mode 100644 docs/tr/docs/how-to/custom-request-and-route.md create mode 100644 docs/tr/docs/how-to/extending-openapi.md create mode 100644 docs/tr/docs/how-to/graphql.md create mode 100644 docs/tr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md create mode 100644 docs/tr/docs/how-to/separate-openapi-schemas.md create mode 100644 docs/tr/docs/how-to/testing-database.md create mode 100644 docs/tr/docs/tutorial/background-tasks.md create mode 100644 docs/tr/docs/tutorial/bigger-applications.md create mode 100644 docs/tr/docs/tutorial/body-fields.md create mode 100644 docs/tr/docs/tutorial/body-multiple-params.md create mode 100644 docs/tr/docs/tutorial/body-nested-models.md create mode 100644 docs/tr/docs/tutorial/body-updates.md create mode 100644 docs/tr/docs/tutorial/body.md create mode 100644 docs/tr/docs/tutorial/cookie-param-models.md create mode 100644 docs/tr/docs/tutorial/cors.md create mode 100644 docs/tr/docs/tutorial/debugging.md create mode 100644 docs/tr/docs/tutorial/dependencies/classes-as-dependencies.md create mode 100644 docs/tr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md create mode 100644 docs/tr/docs/tutorial/dependencies/dependencies-with-yield.md create mode 100644 docs/tr/docs/tutorial/dependencies/global-dependencies.md create mode 100644 docs/tr/docs/tutorial/dependencies/index.md create mode 100644 docs/tr/docs/tutorial/dependencies/sub-dependencies.md create mode 100644 docs/tr/docs/tutorial/encoder.md create mode 100644 docs/tr/docs/tutorial/extra-data-types.md create mode 100644 docs/tr/docs/tutorial/extra-models.md create mode 100644 docs/tr/docs/tutorial/handling-errors.md create mode 100644 docs/tr/docs/tutorial/header-param-models.md create mode 100644 docs/tr/docs/tutorial/header-params.md create mode 100644 docs/tr/docs/tutorial/index.md create mode 100644 docs/tr/docs/tutorial/metadata.md create mode 100644 docs/tr/docs/tutorial/middleware.md create mode 100644 docs/tr/docs/tutorial/path-operation-configuration.md create mode 100644 docs/tr/docs/tutorial/path-params-numeric-validations.md create mode 100644 docs/tr/docs/tutorial/query-param-models.md create mode 100644 docs/tr/docs/tutorial/query-params-str-validations.md create mode 100644 docs/tr/docs/tutorial/request-files.md create mode 100644 docs/tr/docs/tutorial/request-form-models.md create mode 100644 docs/tr/docs/tutorial/request-forms-and-files.md create mode 100644 docs/tr/docs/tutorial/response-model.md create mode 100644 docs/tr/docs/tutorial/response-status-code.md create mode 100644 docs/tr/docs/tutorial/schema-extra-example.md create mode 100644 docs/tr/docs/tutorial/security/first-steps.md create mode 100644 docs/tr/docs/tutorial/security/get-current-user.md create mode 100644 docs/tr/docs/tutorial/security/index.md create mode 100644 docs/tr/docs/tutorial/security/oauth2-jwt.md create mode 100644 docs/tr/docs/tutorial/security/simple-oauth2.md create mode 100644 docs/tr/docs/tutorial/sql-databases.md create mode 100644 docs/tr/docs/tutorial/testing.md create mode 100644 docs/tr/docs/virtual-environments.md diff --git a/docs/tr/docs/_llm-test.md b/docs/tr/docs/_llm-test.md new file mode 100644 index 000000000..0ca218e6e --- /dev/null +++ b/docs/tr/docs/_llm-test.md @@ -0,0 +1,503 @@ +# LLM test dosyası { #llm-test-file } + +Bu doküman, dokümantasyonu çeviren LLM'nin `scripts/translate.py` içindeki `general_prompt`'u ve `docs/{language code}/llm-prompt.md` içindeki dile özel prompt'u anlayıp anlamadığını test eder. Dile özel prompt, `general_prompt`'a eklenir. + +Buraya eklenen testler, dile özel prompt'ları tasarlayan herkes tarafından görülecektir. + +Şu şekilde kullanın: + +* Dile özel bir prompt bulundurun: `docs/{language code}/llm-prompt.md`. +* Bu dokümanın hedeflediğiniz dile sıfırdan yeni bir çevirisini yapın (örneğin `translate.py` içindeki `translate-page` komutu). Bu, çeviriyi `docs/{language code}/docs/_llm-test.md` altında oluşturur. +* Çeviride her şeyin yolunda olup olmadığını kontrol edin. +* Gerekirse dile özel prompt'u, genel prompt'u veya İngilizce dokümanı iyileştirin. +* Ardından çeviride kalan sorunları elle düzeltin; böylece iyi bir çeviri elde edin. +* İyi çeviri yerindeyken yeniden çeviri yapın. İdeal sonuç, LLM'nin artık çeviride hiçbir değişiklik yapmamasıdır. Bu da genel prompt'un ve dile özel prompt'un olabilecek en iyi hâle geldiği anlamına gelir (bazen rastgele gibi görünen birkaç değişiklik yapabilir; çünkü LLM'ler deterministik algoritmalar değildir). + +Testler: + +## Code snippets { #code-snippets } + +//// tab | Test + +Bu bir code snippet: `foo`. Bu da başka bir code snippet: `bar`. Bir tane daha: `baz quux`. + +//// + +//// tab | Bilgi + +Code snippet'lerin içeriği olduğu gibi bırakılmalıdır. + +`script/translate.py` içindeki genel prompt'ta `### Content of code snippets` bölümüne bakın. + +//// + +## Alıntılar { #quotes } + +//// tab | Test + +Dün bir arkadaşım şunu yazdı: "If you spell incorrectly correctly, you have spelled it incorrectly". Ben de şunu yanıtladım: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'". + +/// note | Not + +LLM muhtemelen bunu yanlış çevirecektir. Yeniden çeviri yapıldığında düzeltilmiş çeviriyi koruyup korumadığı önemlidir. + +/// + +//// + +//// tab | Bilgi + +Prompt tasarlayan kişi, düz tırnakları tipografik tırnaklara dönüştürüp dönüştürmemeyi seçebilir. Olduğu gibi bırakmak da uygundur. + +Örneğin `docs/de/llm-prompt.md` içindeki `### Quotes` bölümüne bakın. + +//// + +## Code snippet'lerde alıntılar { #quotes-in-code-snippets } + +//// tab | Test + +`pip install "foo[bar]"` + +Code snippet'lerde string literal örnekleri: `"this"`, `'that'`. + +Code snippet'lerde string literal için zor bir örnek: `f"I like {'oranges' if orange else "apples"}"` + +Hardcore: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` + +//// + +//// tab | Bilgi + +... Ancak code snippet'lerin içindeki tırnaklar olduğu gibi kalmalıdır. + +//// + +## Code block'lar { #code-blocks } + +//// tab | Test + +Bir Bash code örneği... + +```bash +# Evrene bir selam yazdır +echo "Hello universe" +``` + +...ve bir console code örneği... + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +...ve bir başka console code örneği... + +```console +// "Code" adında bir dizin oluştur +$ mkdir code +// O dizine geç +$ cd code +``` + +...ve bir Python code örneği... + +```Python +wont_work() # This won't work 😱 +works(foo="bar") # This works 🎉 +``` + +...ve hepsi bu. + +//// + +//// tab | Bilgi + +Code block'ların içindeki code değiştirilmemelidir; tek istisna yorumlardır (comments). + +`script/translate.py` içindeki genel prompt'ta `### Content of code blocks` bölümüne bakın. + +//// + +## Sekmeler ve renkli kutular { #tabs-and-colored-boxes } + +//// tab | Test + +/// info | Bilgi +Some text +/// + +/// note | Not +Some text +/// + +/// note | Teknik Detaylar +Some text +/// + +/// check | Ek bilgi +Some text +/// + +/// tip | İpucu +Some text +/// + +/// warning | Uyarı +Some text +/// + +/// danger | Tehlike +Some text +/// + +//// + +//// tab | Bilgi + +Sekmelerin ve `Info`/`Note`/`Warning`/vb. blokların başlığı, dikey çizgiden (`|`) sonra çeviri olarak eklenmelidir. + +`script/translate.py` içindeki genel prompt'ta `### Special blocks` ve `### Tab blocks` bölümlerine bakın. + +//// + +## Web ve internal link'ler { #web-and-internal-links } + +//// tab | Test + +Link metni çevrilmelidir, link adresi değişmeden kalmalıdır: + +* [Yukarıdaki başlığa link](#code-snippets) +* [Internal link](index.md#installation){.internal-link target=_blank} +* External link +* Link to a style +* Link to a script +* Link to an image + +Link metni çevrilmelidir, link adresi çeviriye işaret etmelidir: + +* FastAPI link + +//// + +//// tab | Bilgi + +Link'ler çevrilmelidir, ancak adresleri değişmeden kalmalıdır. Bir istisna, FastAPI dokümantasyonunun sayfalarına verilen mutlak link'lerdir. Bu durumda link, çeviriye işaret etmelidir. + +`script/translate.py` içindeki genel prompt'ta `### Links` bölümüne bakın. + +//// + +## HTML "abbr" öğeleri { #html-abbr-elements } + +//// tab | Test + +Burada HTML "abbr" öğeleriyle sarılmış bazı şeyler var (bazıları uydurma): + +### abbr tam bir ifade verir { #the-abbr-gives-a-full-phrase } + +* GTD +* lt +* XWT +* PSGI + +### abbr bir açıklama verir { #the-abbr-gives-an-explanation } + +* cluster +* Deep Learning + +### abbr tam bir ifade ve bir açıklama verir { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* MDN +* I/O. + +//// + +//// tab | Bilgi + +"abbr" öğelerinin "title" attribute'ları belirli talimatlara göre çevrilir. + +Çeviriler, LLM'nin kaldırmaması gereken kendi "abbr" öğelerini ekleyebilir. Örneğin İngilizce kelimeleri açıklamak için. + +`script/translate.py` içindeki genel prompt'ta `### HTML abbr elements` bölümüne bakın. + +//// + +## Başlıklar { #headings } + +//// tab | Test + +### Bir web uygulaması geliştirin - bir öğretici { #develop-a-webapp-a-tutorial } + +Merhaba. + +### Type hint'ler ve -annotation'lar { #type-hints-and-annotations } + +Tekrar merhaba. + +### Super- ve subclass'lar { #super-and-subclasses } + +Tekrar merhaba. + +//// + +//// tab | Bilgi + +Başlıklarla ilgili tek katı kural, LLM'nin süslü parantezler içindeki hash kısmını değiştirmemesidir; böylece link'ler bozulmaz. + +`script/translate.py` içindeki genel prompt'ta `### Headings` bölümüne bakın. + +Dile özel bazı talimatlar için örneğin `docs/de/llm-prompt.md` içindeki `### Headings` bölümüne bakın. + +//// + +## Dokümanlarda kullanılan terimler { #terms-used-in-the-docs } + +//// tab | Test + +* siz +* sizin + +* örn. +* vb. + +* `foo` bir `int` olarak +* `bar` bir `str` olarak +* `baz` bir `list` olarak + +* Tutorial - Kullanıcı kılavuzu +* İleri Düzey Kullanıcı Kılavuzu +* SQLModel dokümanları +* API dokümanları +* otomatik dokümanlar + +* Veri Bilimi +* Deep Learning +* Machine Learning +* Dependency Injection +* HTTP Basic authentication +* HTTP Digest +* ISO formatı +* JSON Schema standardı +* JSON schema +* schema tanımı +* Password Flow +* Mobil + +* deprecated +* designed +* invalid +* on the fly +* standard +* default +* case-sensitive +* case-insensitive + +* uygulamayı serve etmek +* sayfayı serve etmek + +* app +* application + +* request +* response +* error response + +* path operation +* path operation decorator +* path operation function + +* body +* request body +* response body +* JSON body +* form body +* file body +* function body + +* parameter +* body parameter +* path parameter +* query parameter +* cookie parameter +* header parameter +* form parameter +* function parameter + +* event +* startup event +* server'ın startup'ı +* shutdown event +* lifespan event + +* handler +* event handler +* exception handler +* handle etmek + +* model +* Pydantic model +* data model +* database model +* form model +* model object + +* class +* base class +* parent class +* subclass +* child class +* sibling class +* class method + +* header +* headers +* authorization header +* `Authorization` header +* forwarded header + +* dependency injection system +* dependency +* dependable +* dependant + +* I/O bound +* CPU bound +* concurrency +* parallelism +* multiprocessing + +* env var +* environment variable +* `PATH` +* `PATH` variable + +* authentication +* authentication provider +* authorization +* authorization form +* authorization provider +* kullanıcı authenticate olur +* sistem kullanıcıyı authenticate eder + +* CLI +* command line interface + +* server +* client + +* cloud provider +* cloud service + +* geliştirme +* geliştirme aşamaları + +* dict +* dictionary +* enumeration +* enum +* enum member + +* encoder +* decoder +* encode etmek +* decode etmek + +* exception +* raise etmek + +* expression +* statement + +* frontend +* backend + +* GitHub discussion +* GitHub issue + +* performance +* performance optimization + +* return type +* return value + +* security +* security scheme + +* task +* background task +* task function + +* template +* template engine + +* type annotation +* type hint + +* server worker +* Uvicorn worker +* Gunicorn Worker +* worker process +* worker class +* workload + +* deployment +* deploy etmek + +* SDK +* software development kit + +* `APIRouter` +* `requirements.txt` +* Bearer Token +* breaking change +* bug +* button +* callable +* code +* commit +* context manager +* coroutine +* database session +* disk +* domain +* engine +* fake X +* HTTP GET method +* item +* library +* lifespan +* lock +* middleware +* mobile application +* module +* mounting +* network +* origin +* override +* payload +* processor +* property +* proxy +* pull request +* query +* RAM +* remote machine +* status code +* string +* tag +* web framework +* wildcard +* return etmek +* validate etmek + +//// + +//// tab | Bilgi + +Bu, dokümanlarda görülen (çoğunlukla) teknik terimlerin eksiksiz ve normatif olmayan bir listesidir. Prompt tasarlayan kişi için, LLM'nin hangi terimlerde desteğe ihtiyaç duyduğunu anlamada yardımcı olabilir. Örneğin iyi bir çeviriyi sürekli daha zayıf bir çeviriye geri alıyorsa. Ya da sizin dilinizde bir terimi çekimlemekte (conjugating/declinating) zorlanıyorsa. + +Örneğin `docs/de/llm-prompt.md` içindeki `### List of English terms and their preferred German translations` bölümüne bakın. + +//// diff --git a/docs/tr/docs/advanced/additional-responses.md b/docs/tr/docs/advanced/additional-responses.md new file mode 100644 index 000000000..c8e372775 --- /dev/null +++ b/docs/tr/docs/advanced/additional-responses.md @@ -0,0 +1,247 @@ +# OpenAPI'de Ek Response'lar { #additional-responses-in-openapi } + +/// warning | Uyarı + +Bu konu oldukça ileri seviye bir konudur. + +**FastAPI**'ye yeni başlıyorsanız buna ihtiyaç duymayabilirsiniz. + +/// + +Ek status code'lar, media type'lar, açıklamalar vb. ile ek response'lar tanımlayabilirsiniz. + +Bu ek response'lar OpenAPI şemasına dahil edilir; dolayısıyla API dokümanlarında da görünürler. + +Ancak bu ek response'lar için, status code'unuzu ve içeriğinizi vererek `JSONResponse` gibi bir `Response`'u doğrudan döndürdüğünüzden emin olmanız gerekir. + +## `model` ile Ek Response { #additional-response-with-model } + +*Path operation decorator*'larınıza `responses` adlı bir parametre geçebilirsiniz. + +Bu parametre bir `dict` alır: anahtarlar her response için status code'lardır (`200` gibi), değerler ise her birine ait bilgileri içeren başka `dict`'lerdir. + +Bu response `dict`'lerinin her birinde, `response_model`'e benzer şekilde bir Pydantic model içeren `model` anahtarı olabilir. + +**FastAPI** bu modeli alır, JSON Schema'sını üretir ve OpenAPI'de doğru yere ekler. + +Örneğin, `404` status code'u ve `Message` Pydantic model'i ile başka bir response tanımlamak için şunu yazabilirsiniz: + +{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} + +/// note | Not + +`JSONResponse`'u doğrudan döndürmeniz gerektiğini unutmayın. + +/// + +/// info | Bilgi + +`model` anahtarı OpenAPI'nin bir parçası değildir. + +**FastAPI** buradaki Pydantic model'i alır, JSON Schema'yı üretir ve doğru yere yerleştirir. + +Doğru yer şurasıdır: + +* Değeri başka bir JSON nesnesi (`dict`) olan `content` anahtarının içinde: + * Media type anahtarı (örn. `application/json`) bulunur; bunun değeri başka bir JSON nesnesidir ve onun içinde: + * Değeri model'den gelen JSON Schema olan `schema` anahtarı vardır; doğru yer burasıdır. + * **FastAPI** bunu doğrudan gömmek yerine OpenAPI'deki başka bir yerde bulunan global JSON Schema'lara bir referans ekler. Böylece diğer uygulamalar ve client'lar bu JSON Schema'ları doğrudan kullanabilir, daha iyi code generation araçları sağlayabilir, vb. + +/// + +Bu *path operation* için OpenAPI'de üretilen response'lar şöyle olur: + +```JSON hl_lines="3-12" +{ + "responses": { + "404": { + "description": "Additional Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } +} +``` + +Schema'lar OpenAPI şemasının içinde başka bir yere referanslanır: + +```JSON hl_lines="4-16" +{ + "components": { + "schemas": { + "Message": { + "title": "Message", + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "title": "Message", + "type": "string" + } + } + }, + "Item": { + "title": "Item", + "required": [ + "id", + "value" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + } + }, + "ValidationError": { + "title": "ValidationError", + "required": [ + "loc", + "msg", + "type" + ], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } +} +``` + +## Ana Response İçin Ek Media Type'lar { #additional-media-types-for-the-main-response } + +Aynı `responses` parametresini, aynı ana response için farklı media type'lar eklemek amacıyla da kullanabilirsiniz. + +Örneğin, `image/png` için ek bir media type ekleyerek, *path operation*'ınızın bir JSON nesnesi (media type `application/json`) ya da bir PNG görseli döndürebildiğini belirtebilirsiniz: + +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} + +/// note | Not + +Görseli `FileResponse` kullanarak doğrudan döndürmeniz gerektiğine dikkat edin. + +/// + +/// info | Bilgi + +`responses` parametrenizde açıkça farklı bir media type belirtmediğiniz sürece FastAPI, response'un ana response class'ı ile aynı media type'a sahip olduğunu varsayar (varsayılan `application/json`). + +Ancak media type'ı `None` olan özel bir response class belirttiyseniz, FastAPI ilişkili bir model'i olan tüm ek response'lar için `application/json` kullanır. + +/// + +## Bilgileri Birleştirme { #combining-information } + +`response_model`, `status_code` ve `responses` parametreleri dahil olmak üzere, response bilgilerini birden fazla yerden birleştirebilirsiniz. + +Varsayılan `200` status code'unu (ya da gerekiyorsa özel bir tane) kullanarak bir `response_model` tanımlayabilir, ardından aynı response için ek bilgileri `responses` içinde, doğrudan OpenAPI şemasına ekleyebilirsiniz. + +**FastAPI**, `responses` içindeki ek bilgileri korur ve model'inizin JSON Schema'sı ile birleştirir. + +Örneğin, Pydantic model kullanan ve özel bir `description` içeren `404` status code'lu bir response tanımlayabilirsiniz. + +Ayrıca `response_model`'inizi kullanan, ancak özel bir `example` içeren `200` status code'lu bir response da tanımlayabilirsiniz: + +{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} + +Bunların hepsi OpenAPI'nize birleştirilerek dahil edilir ve API dokümanlarında gösterilir: + + + +## Ön Tanımlı Response'ları Özel Olanlarla Birleştirme { #combine-predefined-responses-and-custom-ones } + +Birçok *path operation* için geçerli olacak bazı ön tanımlı response'larınız olabilir; ancak bunları her *path operation*'ın ihtiyaç duyduğu özel response'larla birleştirmek isteyebilirsiniz. + +Bu durumlarda, Python'daki bir `dict`'i `**dict_to_unpack` ile "unpacking" tekniğini kullanabilirsiniz: + +```Python +old_dict = { + "old key": "old value", + "second old key": "second old value", +} +new_dict = {**old_dict, "new key": "new value"} +``` + +Burada `new_dict`, `old_dict` içindeki tüm key-value çiftlerini ve buna ek olarak yeni key-value çiftini içerir: + +```Python +{ + "old key": "old value", + "second old key": "second old value", + "new key": "new value", +} +``` + +Bu tekniği, *path operation*'larınızda bazı ön tanımlı response'ları yeniden kullanmak ve bunları ek özel response'larla birleştirmek için kullanabilirsiniz. + +Örneğin: + +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} + +## OpenAPI Response'ları Hakkında Daha Fazla Bilgi { #more-information-about-openapi-responses } + +Response'ların içine tam olarak neleri dahil edebileceğinizi görmek için OpenAPI spesifikasyonundaki şu bölümlere bakabilirsiniz: + +* OpenAPI Responses Object, `Response Object`'i içerir. +* OpenAPI Response Object, buradaki her şeyi `responses` parametreniz içinde, her bir response'un içine doğrudan ekleyebilirsiniz. Buna `description`, `headers`, `content` (bunun içinde farklı media type'lar ve JSON Schema'lar tanımlarsınız) ve `links` dahildir. diff --git a/docs/tr/docs/advanced/additional-status-codes.md b/docs/tr/docs/advanced/additional-status-codes.md new file mode 100644 index 000000000..710a6459f --- /dev/null +++ b/docs/tr/docs/advanced/additional-status-codes.md @@ -0,0 +1,41 @@ +# Ek Status Code'ları { #additional-status-codes } + +Varsayılan olarak **FastAPI**, response'ları bir `JSONResponse` kullanarak döndürür; *path operation*'ınızdan döndürdüğünüz içeriği bu `JSONResponse`'un içine yerleştirir. + +Varsayılan status code'u veya *path operation* içinde sizin belirlediğiniz status code'u kullanır. + +## Ek status code'ları { #additional-status-codes_1 } + +Ana status code'a ek olarak başka status code'lar da döndürmek istiyorsanız, `JSONResponse` gibi bir `Response`'u doğrudan döndürerek bunu yapabilirsiniz ve ek status code'u doğrudan orada ayarlarsınız. + +Örneğin, item'ları güncellemeye izin veren bir *path operation*'ınız olduğunu düşünelim; başarılı olduğunda 200 "OK" HTTP status code'unu döndürüyor olsun. + +Ancak yeni item'ları da kabul etmesini istiyorsunuz. Ve item daha önce yoksa, onu oluşturup 201 "Created" HTTP status code'unu döndürsün. + +Bunu yapmak için `JSONResponse` import edin ve içeriğinizi doğrudan onunla döndürün; istediğiniz `status_code`'u da ayarlayın: + +{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} + +/// warning | Uyarı + +Yukarıdaki örnekte olduğu gibi bir `Response`'u doğrudan döndürdüğünüzde, response aynen olduğu gibi döndürülür. + +Bir model ile serialize edilmez, vb. + +İçinde olmasını istediğiniz veriyi taşıdığından emin olun ve değerlerin geçerli JSON olduğundan emin olun (eğer `JSONResponse` kullanıyorsanız). + +/// + +/// note | Teknik Detaylar + +`from starlette.responses import JSONResponse` da kullanabilirsiniz. + +**FastAPI**, geliştirici olarak size kolaylık olsun diye `starlette.responses` içindekileri `fastapi.responses` altında da sunar. Ancak mevcut response'ların çoğu doğrudan Starlette'ten gelir. `status` için de aynı durum geçerlidir. + +/// + +## OpenAPI ve API docs { #openapi-and-api-docs } + +Ek status code'ları ve response'ları doğrudan döndürürseniz, FastAPI sizin ne döndüreceğinizi önceden bilemeyeceği için bunlar OpenAPI şemasına (API docs) dahil edilmez. + +Ancak bunu kodunuzda şu şekilde dokümante edebilirsiniz: [Ek Response'lar](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/tr/docs/advanced/advanced-dependencies.md b/docs/tr/docs/advanced/advanced-dependencies.md new file mode 100644 index 000000000..48c0b6211 --- /dev/null +++ b/docs/tr/docs/advanced/advanced-dependencies.md @@ -0,0 +1,163 @@ +# Gelişmiş Dependency'ler { #advanced-dependencies } + +## Parametreli dependency'ler { #parameterized-dependencies } + +Şimdiye kadar gördüğümüz tüm dependency'ler sabit bir function ya da class idi. + +Ancak, birçok farklı function veya class tanımlamak zorunda kalmadan, dependency üzerinde bazı parametreler ayarlamak isteyebileceğiniz durumlar olabilir. + +Örneğin, query parametresi `q`'nun belirli bir sabit içeriği barındırıp barındırmadığını kontrol eden bir dependency istediğimizi düşünelim. + +Ama bu sabit içeriği parametreleştirebilmek istiyoruz. + +## "Callable" bir instance { #a-callable-instance } + +Python'da bir class'ın instance'ını "callable" yapmanın bir yolu vardır. + +Class'ın kendisini değil (zaten callable'dır), o class'ın bir instance'ını. + +Bunu yapmak için `__call__` adında bir method tanımlarız: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} + +Bu durumda, ek parametreleri ve alt-dependency'leri kontrol etmek için **FastAPI**'nin kullanacağı şey bu `__call__` olacaktır; ayrıca daha sonra *path operation function* içindeki parametreye bir değer geçmek için çağrılacak olan da budur. + +## Instance'ı parametreleştirme { #parameterize-the-instance } + +Ve şimdi, dependency'yi "parametreleştirmek" için kullanacağımız instance parametrelerini tanımlamak üzere `__init__` kullanabiliriz: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} + +Bu durumda **FastAPI**, `__init__` ile asla uğraşmaz veya onu önemsemez; onu doğrudan kendi kodumuzda kullanırız. + +## Bir instance oluşturma { #create-an-instance } + +Bu class'tan bir instance'ı şöyle oluşturabiliriz: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} + +Böylece dependency'mizi "parametreleştirmiş" oluruz; artık içinde `"bar"` vardır ve bu değer `checker.fixed_content` attribute'u olarak durur. + +## Instance'ı dependency olarak kullanma { #use-the-instance-as-a-dependency } + +Sonra `Depends(FixedContentQueryChecker)` yerine `Depends(checker)` içinde bu `checker`'ı kullanabiliriz. Çünkü dependency, class'ın kendisi değil, `checker` instance'ıdır. + +Ve dependency çözülürken **FastAPI** bu `checker`'ı şöyle çağırır: + +```Python +checker(q="somequery") +``` + +...ve bunun döndürdüğü her şeyi, *path operation function* içinde `fixed_content_included` parametresine dependency değeri olarak geçirir: + +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} + +/// tip | İpucu + +Bunların tamamı biraz yapay görünebilir. Ayrıca bunun nasıl faydalı olduğu da henüz çok net olmayabilir. + +Bu örnekler bilerek basit tutuldu; ama mekanizmanın nasıl çalıştığını gösteriyor. + +Security bölümlerinde, aynı şekilde implement edilmiş yardımcı function'lar bulunuyor. + +Buradaki her şeyi anladıysanız, security için kullanılan bu yardımcı araçların arka planda nasıl çalıştığını da zaten biliyorsunuz demektir. + +/// + +## `yield`, `HTTPException`, `except` ve Background Tasks ile Dependency'ler { #dependencies-with-yield-httpexception-except-and-background-tasks } + +/// warning | Uyarı + +Büyük ihtimalle bu teknik detaylara ihtiyacınız yok. + +Bu detaylar, özellikle 0.121.0'dan eski bir FastAPI uygulamanız varsa ve `yield` kullanan dependency'lerle ilgili sorunlar yaşıyorsanız faydalıdır. + +/// + +`yield` kullanan dependency'ler; farklı kullanım senaryolarını kapsamak ve bazı sorunları düzeltmek için zaman içinde evrildi. Aşağıda nelerin değiştiğinin bir özeti var. + +### `yield` ve `scope` ile dependency'ler { #dependencies-with-yield-and-scope } + +0.121.0 sürümünde FastAPI, `yield` kullanan dependency'ler için `Depends(scope="function")` desteğini ekledi. + +`Depends(scope="function")` kullanıldığında, `yield` sonrasındaki çıkış kodu, *path operation function* biter bitmez, response client'a geri gönderilmeden önce çalıştırılır. + +`Depends(scope="request")` (varsayılan) kullanıldığında ise `yield` sonrasındaki çıkış kodu, response gönderildikten sonra çalıştırılır. + +Daha fazlasını [Dependencies with `yield` - Early exit and `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope) dokümantasyonunda okuyabilirsiniz. + +### `yield` ve `StreamingResponse` ile dependency'ler, Teknik Detaylar { #dependencies-with-yield-and-streamingresponse-technical-details } + +FastAPI 0.118.0 öncesinde, `yield` kullanan bir dependency kullanırsanız, *path operation function* döndükten sonra ama response gönderilmeden hemen önce `yield` sonrasındaki çıkış kodu çalıştırılırdı. + +Amaç, response'un ağ üzerinden taşınmasını beklerken gereğinden uzun süre resource tutmaktan kaçınmaktı. + +Bu değişiklik aynı zamanda şunu da ifade ediyordu: `StreamingResponse` döndürürseniz, `yield` kullanan dependency'nin çıkış kodu zaten çalışmış olurdu. + +Örneğin, `yield` kullanan bir dependency içinde bir veritabanı session'ınız varsa, `StreamingResponse` veri stream ederken bu session'ı kullanamazdı; çünkü `yield` sonrasındaki çıkış kodunda session zaten kapatılmış olurdu. + +Bu davranış 0.118.0'da geri alındı ve `yield` sonrasındaki çıkış kodunun, response gönderildikten sonra çalıştırılması sağlandı. + +/// info | Bilgi + +Aşağıda göreceğiniz gibi, bu davranış 0.106.0 sürümünden önceki davranışa oldukça benzer; ancak köşe durumlar için çeşitli iyileştirmeler ve bug fix'ler içerir. + +/// + +#### Erken Çıkış Kodu için Kullanım Senaryoları { #use-cases-with-early-exit-code } + +Bazı özel koşullardaki kullanım senaryoları, response gönderilmeden önce `yield` kullanan dependency'lerin çıkış kodunun çalıştırıldığı eski davranıştan fayda görebilir. + +Örneğin, `yield` kullanan bir dependency içinde yalnızca bir kullanıcıyı doğrulamak için veritabanı session'ı kullanan bir kodunuz olduğunu düşünün; ama bu session *path operation function* içinde bir daha hiç kullanılmıyor, yalnızca dependency içinde kullanılıyor **ve** response'un gönderilmesi uzun sürüyor. Mesela veriyi yavaş gönderen bir `StreamingResponse` var, ama herhangi bir nedenle veritabanını kullanmıyor. + +Bu durumda veritabanı session'ı, response tamamen gönderilene kadar elde tutulur. Ancak session kullanılmıyorsa, bunu elde tutmak gerekli değildir. + +Şöyle görünebilir: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py *} + +`Session`'ın otomatik kapatılması olan çıkış kodu şurada: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +...yavaş veri gönderen response'un gönderimi bittikten sonra çalıştırılır: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +Ama `generate_stream()` veritabanı session'ını kullanmadığı için, response gönderilirken session'ı açık tutmak aslında gerekli değildir. + +SQLModel (veya SQLAlchemy) kullanarak bu spesifik senaryoya sahipseniz, session'a artık ihtiyacınız kalmadıktan sonra session'ı açıkça kapatabilirsiniz: + +{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *} + +Böylece session veritabanı bağlantısını serbest bırakır ve diğer request'ler bunu kullanabilir. + +`yield` kullanan bir dependency'den erken çıkış gerektiren farklı bir kullanım senaryonuz varsa, lütfen kullanım senaryonuzla birlikte ve `yield` kullanan dependency'ler için erken kapatmadan neden fayda göreceğinizi açıklayarak bir GitHub Discussion Question oluşturun. + +`yield` kullanan dependency'lerde erken kapatma için ikna edici kullanım senaryoları varsa, erken kapatmayı seçmeli (opt-in) hale getiren yeni bir yöntem eklemeyi düşünebilirim. + +### `yield` ve `except` ile dependency'ler, Teknik Detaylar { #dependencies-with-yield-and-except-technical-details } + +FastAPI 0.110.0 öncesinde, `yield` kullanan bir dependency kullanır, sonra o dependency içinde `except` ile bir exception yakalar ve exception'ı tekrar raise etmezseniz; exception otomatik olarak herhangi bir exception handler'a veya internal server error handler'a raise/forward edilirdi. + +Bu davranış 0.110.0 sürümünde değiştirildi. Amaç, handler olmayan (internal server errors) forward edilmiş exception'ların yönetilmemesinden kaynaklanan bellek tüketimini düzeltmek ve bunu normal Python kodunun davranışıyla tutarlı hale getirmekti. + +### Background Tasks ve `yield` ile dependency'ler, Teknik Detaylar { #background-tasks-and-dependencies-with-yield-technical-details } + +FastAPI 0.106.0 öncesinde, `yield` sonrasında exception raise etmek mümkün değildi; çünkü `yield` kullanan dependency'lerdeki çıkış kodu response gönderildikten *sonra* çalıştırılıyordu. Bu nedenle [Exception Handlers](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} zaten çalışmış olurdu. + +Bu tasarımın ana sebeplerinden biri, background task'lerin içinde dependency'lerin "yield ettiği" aynı objeleri kullanmaya izin vermekti; çünkü çıkış kodu, background task'ler bittikten sonra çalıştırılıyordu. + +Bu davranış FastAPI 0.106.0'da, response'un ağ üzerinde taşınmasını beklerken resource tutmamak amacıyla değiştirildi. + +/// tip | İpucu + +Ek olarak, bir background task normalde ayrı ele alınması gereken bağımsız bir mantık setidir ve kendi resource'larına sahip olmalıdır (ör. kendi veritabanı bağlantısı). + +Bu şekilde muhtemelen daha temiz bir kod elde edersiniz. + +/// + +Bu davranışa güvenerek kod yazdıysanız, artık background task'ler için resource'ları background task'in içinde oluşturmalı ve içeride yalnızca `yield` kullanan dependency'lerin resource'larına bağlı olmayan verileri kullanmalısınız. + +Örneğin, aynı veritabanı session'ını kullanmak yerine background task içinde yeni bir veritabanı session'ı oluşturur ve veritabanındaki objeleri bu yeni session ile alırsınız. Ardından, background task function'ına veritabanından gelen objeyi parametre olarak geçirmek yerine, o objenin ID'sini geçirir ve objeyi background task function'ı içinde yeniden elde edersiniz. diff --git a/docs/tr/docs/advanced/async-tests.md b/docs/tr/docs/advanced/async-tests.md new file mode 100644 index 000000000..82349bbec --- /dev/null +++ b/docs/tr/docs/advanced/async-tests.md @@ -0,0 +1,99 @@ +# Async Testler { #async-tests } + +Sağlanan `TestClient` ile **FastAPI** uygulamalarınızı nasıl test edeceğinizi zaten gördünüz. Şimdiye kadar yalnızca senkron testler yazdık, yani `async` fonksiyonlar kullanmadan. + +Testlerinizde asenkron fonksiyonlar kullanabilmek faydalı olabilir; örneğin veritabanınızı asenkron olarak sorguluyorsanız. Diyelim ki FastAPI uygulamanıza request gönderilmesini test etmek ve ardından async bir veritabanı kütüphanesi kullanırken backend'in doğru veriyi veritabanına başarıyla yazdığını doğrulamak istiyorsunuz. + +Bunu nasıl çalıştırabileceğimize bir bakalım. + +## pytest.mark.anyio { #pytest-mark-anyio } + +Testlerimizde asenkron fonksiyonlar çağırmak istiyorsak, test fonksiyonlarımızın da asenkron olması gerekir. AnyIO bunun için güzel bir plugin sağlar; böylece bazı test fonksiyonlarının asenkron olarak çağrılacağını belirtebiliriz. + +## HTTPX { #httpx } + +**FastAPI** uygulamanız `async def` yerine normal `def` fonksiyonları kullanıyor olsa bile, altta yatan yapı hâlâ bir `async` uygulamadır. + +`TestClient`, standart pytest kullanarak normal `def` test fonksiyonlarınızın içinden asenkron FastAPI uygulamasını çağırmak için içeride bazı “sihirli” işlemler yapar. Ancak bu sihir, onu asenkron fonksiyonların içinde kullandığımızda artık çalışmaz. Testlerimizi asenkron çalıştırdığımızda, test fonksiyonlarımızın içinde `TestClient` kullanamayız. + +`TestClient`, HTTPX tabanlıdır ve neyse ki API'yi test etmek için HTTPX'i doğrudan kullanabiliriz. + +## Örnek { #example } + +Basit bir örnek için, [Bigger Applications](../tutorial/bigger-applications.md){.internal-link target=_blank} ve [Testing](../tutorial/testing.md){.internal-link target=_blank} bölümlerinde anlatılana benzer bir dosya yapısı düşünelim: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +`main.py` dosyası şöyle olur: + +{* ../../docs_src/async_tests/app_a_py39/main.py *} + +`test_main.py` dosyasında `main.py` için testler yer alır, artık şöyle görünebilir: + +{* ../../docs_src/async_tests/app_a_py39/test_main.py *} + +## Çalıştırma { #run-it } + +Testlerinizi her zamanki gibi şu şekilde çalıştırabilirsiniz: + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## Detaylı Anlatım { #in-detail } + +`@pytest.mark.anyio` marker'ı, pytest'e bu test fonksiyonunun asenkron olarak çağrılması gerektiğini söyler: + +{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} + +/// tip | İpucu + +Test fonksiyonu artık `TestClient` kullanırken eskiden olduğu gibi sadece `def` değil, `async def`. + +/// + +Ardından app ile bir `AsyncClient` oluşturup `await` kullanarak ona async request'ler gönderebiliriz. + +{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} + +Bu, şu kullanıma denktir: + +```Python +response = client.get('/') +``` + +...ki daha önce request'leri `TestClient` ile bu şekilde gönderiyorduk. + +/// tip | İpucu + +Yeni `AsyncClient` ile async/await kullandığımızı unutmayın; request asenkron çalışır. + +/// + +/// warning | Uyarı + +Uygulamanız lifespan event'lerine dayanıyorsa, `AsyncClient` bu event'leri tetiklemez. Tetiklendiklerinden emin olmak için florimondmanca/asgi-lifespan paketindeki `LifespanManager`'ı kullanın. + +/// + +## Diğer Asenkron Fonksiyon Çağrıları { #other-asynchronous-function-calls } + +Test fonksiyonu artık asenkron olduğundan, testlerinizde FastAPI uygulamanıza request göndermenin yanında başka `async` fonksiyonları da (çağırıp `await` ederek) kodunuzun başka yerlerinde yaptığınız gibi aynı şekilde kullanabilirsiniz. + +/// tip | İpucu + +Testlerinize asenkron fonksiyon çağrıları entegre ederken `RuntimeError: Task attached to a different loop` hatasıyla karşılaşırsanız (ör. MongoDB'nin MotorClient kullanımı), event loop gerektiren nesneleri yalnızca async fonksiyonların içinde oluşturmanız gerektiğini unutmayın; örneğin bir `@app.on_event("startup")` callback'i içinde. + +/// diff --git a/docs/tr/docs/advanced/behind-a-proxy.md b/docs/tr/docs/advanced/behind-a-proxy.md new file mode 100644 index 000000000..e70b16960 --- /dev/null +++ b/docs/tr/docs/advanced/behind-a-proxy.md @@ -0,0 +1,466 @@ +# Proxy Arkasında Çalıştırma { #behind-a-proxy } + +Birçok durumda, FastAPI uygulamanızın önünde Traefik veya Nginx gibi bir **proxy** kullanırsınız. + +Bu proxy'ler HTTPS sertifikalarını ve diğer bazı işleri üstlenebilir. + +## Proxy Forwarded Header'ları { #proxy-forwarded-headers } + +Uygulamanızın önündeki bir **proxy**, request'leri **server**'ınıza göndermeden önce genelde bazı header'ları dinamik olarak ayarlar. Böylece server, request'in proxy tarafından **forward** edildiğini; domain dahil orijinal (public) URL'yi, HTTPS kullanıldığını vb. bilgileri anlayabilir. + +**Server** programı (örneğin **FastAPI CLI** üzerinden **Uvicorn**) bu header'ları yorumlayabilir ve ardından bu bilgiyi uygulamanıza aktarabilir. + +Ancak güvenlik nedeniyle, server güvenilir bir proxy arkasında olduğunu bilmediği için bu header'ları yorumlamaz. + +/// note | Teknik Detaylar + +Proxy header'ları şunlardır: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +### Proxy Forwarded Header'larını Etkinleştirme { #enable-proxy-forwarded-headers } + +FastAPI CLI'yi `--forwarded-allow-ips` *CLI Option*'ı ile başlatıp, bu forwarded header'ları okumada güvenilecek IP adreslerini verebilirsiniz. + +Bunu `--forwarded-allow-ips="*"` olarak ayarlarsanız, gelen tüm IP'lere güvenir. + +**Server**'ınız güvenilir bir **proxy** arkasındaysa ve onunla sadece proxy konuşuyorsa, bu ayar server'ın o **proxy**'nin IP'si her neyse onu kabul etmesini sağlar. + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### HTTPS ile Redirect'ler { #redirects-with-https } + +Örneğin `/items/` adında bir *path operation* tanımladığınızı düşünelim: + +{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} + +Client `/items`'a gitmeye çalışırsa, varsayılan olarak `/items/`'a redirect edilir. + +Ancak *CLI Option* `--forwarded-allow-ips` ayarlanmadan önce, `http://localhost:8000/items/`'a redirect edebilir. + +Oysa uygulamanız `https://mysuperapp.com` üzerinde host ediliyor olabilir ve redirect'in `https://mysuperapp.com/items/` olması gerekir. + +Artık `--proxy-headers` ayarını yaparak FastAPI'nin doğru adrese redirect edebilmesini sağlarsınız. 😎 + +``` +https://mysuperapp.com/items/ +``` + +/// tip | İpucu + +HTTPS hakkında daha fazla bilgi için [HTTPS Hakkında](../deployment/https.md){.internal-link target=_blank} rehberine bakın. + +/// + +### Proxy Forwarded Header'ları Nasıl Çalışır { #how-proxy-forwarded-headers-work } + +**Proxy**'nin, client ile **application server** arasında forwarded header'ları nasıl eklediğini gösteren görsel bir temsil: + +```mermaid +sequenceDiagram + participant Client + participant Proxy as Proxy/Load Balancer + participant Server as FastAPI Server + + Client->>Proxy: HTTPS Request
Host: mysuperapp.com
Path: /items + + Note over Proxy: Proxy adds forwarded headers + + Proxy->>Server: HTTP Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items + + Note over Server: Server interprets headers
(if --forwarded-allow-ips is set) + + Server->>Proxy: HTTP Response
with correct HTTPS URLs + + Proxy->>Client: HTTPS Response +``` + +**Proxy**, orijinal client request'ini araya girerek (intercept) alır ve request'i **application server**'a iletmeden önce özel *forwarded* header'ları (`X-Forwarded-*`) ekler. + +Bu header'lar, aksi halde kaybolacak olan orijinal request bilgilerini korur: + +* **X-Forwarded-For**: Orijinal client'ın IP adresi +* **X-Forwarded-Proto**: Orijinal protokol (`https`) +* **X-Forwarded-Host**: Orijinal host (`mysuperapp.com`) + +**FastAPI CLI** `--forwarded-allow-ips` ile yapılandırıldığında bu header'lara güvenir ve örneğin redirect'lerde doğru URL'leri üretmek için bunları kullanır. + +## Path Prefix'i Kırpılan (Stripped) Bir Proxy { #proxy-with-a-stripped-path-prefix } + +Uygulamanıza bir path prefix ekleyen bir proxy'niz olabilir. + +Bu durumlarda uygulamanızı yapılandırmak için `root_path` kullanabilirsiniz. + +`root_path`, FastAPI'nin (Starlette üzerinden) üzerine kurulduğu ASGI spesifikasyonunun sağladığı bir mekanizmadır. + +`root_path` bu özel senaryoları yönetmek için kullanılır. + +Ayrıca sub-application mount ederken de içeride kullanılır. + +Path prefix'i kırpılan bir proxy kullanmak, şu anlama gelir: Kodunuzda `/app` altında bir path tanımlarsınız; ancak üstte bir katman (proxy) ekleyip **FastAPI** uygulamanızı `/api/v1` gibi bir path'in altına koyarsınız. + +Bu durumda, orijinal `/app` path'i aslında `/api/v1/app` altında servis edilir. + +Kodunuzun tamamı sadece `/app` varmış gibi yazılmış olsa bile. + +{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} + +Proxy, request'i app server'a (muhtemelen FastAPI CLI üzerinden Uvicorn) iletmeden önce **path prefix**'i anlık olarak **"kırpar"** (strip). Böylece uygulamanız hâlâ `/app` altında servis ediliyormuş gibi davranır ve tüm kodunuzu `/api/v1` prefix'ini içerecek şekilde güncellemeniz gerekmez. + +Buraya kadar her şey normal çalışır. + +Ancak entegre doküman arayüzünü (frontend) açtığınızda, OpenAPI şemasını `/api/v1/openapi.json` yerine `/openapi.json` üzerinden almayı bekler. + +Dolayısıyla tarayıcıda çalışan frontend `/openapi.json`'a erişmeye çalışır ve OpenAPI şemasını alamaz. + +Çünkü uygulamamız proxy arkasında `/api/v1` path prefix'i ile çalışmaktadır; frontend'in OpenAPI şemasını `/api/v1/openapi.json` üzerinden çekmesi gerekir. + +```mermaid +graph LR + +browser("Browser") +proxy["Proxy on http://0.0.0.0:9999/api/v1/app"] +server["Server on http://127.0.0.1:8000/app"] + +browser --> proxy +proxy --> server +``` + +/// tip | İpucu + +`0.0.0.0` IP'si, genelde programın ilgili makine/server üzerindeki tüm kullanılabilir IP'lerde dinlediği anlamına gelir. + +/// + +Docs UI'nin, bu API `server`'ının (proxy arkasında) `/api/v1` altında bulunduğunu belirtmek için OpenAPI şemasına da ihtiyacı olur. Örneğin: + +```JSON hl_lines="4-8" +{ + "openapi": "3.1.0", + // More stuff here + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + // More stuff here + } +} +``` + +Bu örnekte "Proxy", **Traefik** gibi bir şey olabilir. Server da FastAPI uygulamanızı çalıştıran (Uvicorn'lu) FastAPI CLI olabilir. + +### `root_path` Sağlama { #providing-the-root-path } + +Bunu yapmak için `--root-path` komut satırı seçeneğini şöyle kullanabilirsiniz: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Hypercorn kullanıyorsanız, onda da `--root-path` seçeneği vardır. + +/// note | Teknik Detaylar + +ASGI spesifikasyonu bu kullanım senaryosu için bir `root_path` tanımlar. + +`--root-path` komut satırı seçeneği de bu `root_path`'i sağlar. + +/// + +### Mevcut `root_path`'i Kontrol Etme { #checking-the-current-root-path } + +Uygulamanızın her request için kullandığı mevcut `root_path` değerini alabilirsiniz; bu değer ASGI spesifikasyonunun bir parçası olan `scope` dict'inin içindedir. + +Burada sadece göstermek için bunu mesaja dahil ediyoruz. + +{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} + +Ardından Uvicorn'u şu şekilde başlatırsanız: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Response şöyle bir şey olur: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +### FastAPI Uygulamasında `root_path` Ayarlama { #setting-the-root-path-in-the-fastapi-app } + +Alternatif olarak, `--root-path` gibi bir komut satırı seçeneği (veya muadili) sağlayamıyorsanız, FastAPI uygulamanızı oluştururken `root_path` parametresini ayarlayabilirsiniz: + +{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} + +`FastAPI`'ye `root_path` vermek, Uvicorn veya Hypercorn'a `--root-path` komut satırı seçeneğini vermekle eşdeğerdir. + +### `root_path` Hakkında { #about-root-path } + +Şunu unutmayın: Server (Uvicorn) bu `root_path`'i, uygulamaya iletmek dışında başka bir amaçla kullanmaz. + +Ancak tarayıcınızla http://127.0.0.1:8000/app adresine giderseniz normal response'u görürsünüz: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +Yani `http://127.0.0.1:8000/api/v1/app` üzerinden erişilmeyi beklemez. + +Uvicorn, proxy'nin Uvicorn'a `http://127.0.0.1:8000/app` üzerinden erişmesini bekler; bunun üstüne ekstra `/api/v1` prefix'ini eklemek proxy'nin sorumluluğudur. + +## Stripped Path Prefix Kullanan Proxy'ler Hakkında { #about-proxies-with-a-stripped-path-prefix } + +Stripped path prefix kullanan bir proxy, yapılandırma yöntemlerinden yalnızca biridir. + +Birçok durumda varsayılan davranış, proxy'nin stripped path prefix kullanmaması olacaktır. + +Böyle bir durumda (stripped path prefix olmadan), proxy `https://myawesomeapp.com` gibi bir yerde dinler; tarayıcı `https://myawesomeapp.com/api/v1/app`'e giderse ve sizin server'ınız (ör. Uvicorn) `http://127.0.0.1:8000` üzerinde dinliyorsa, proxy (stripped path prefix olmadan) Uvicorn'a aynı path ile erişir: `http://127.0.0.1:8000/api/v1/app`. + +## Traefik ile Local Olarak Test Etme { #testing-locally-with-traefik } + +Traefik kullanarak, stripped path prefix'li deneyi local'de kolayca çalıştırabilirsiniz. + +Traefik'i indirin; tek bir binary'dir, sıkıştırılmış dosyayı çıkarıp doğrudan terminalden çalıştırabilirsiniz. + +Ardından `traefik.toml` adında bir dosya oluşturup şunu yazın: + +```TOML hl_lines="3" +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +Bu, Traefik'e 9999 portunda dinlemesini ve `routes.toml` adlı başka bir dosyayı kullanmasını söyler. + +/// tip | İpucu + +Standart HTTP portu 80 yerine 9999 portunu kullanıyoruz; böylece admin (`sudo`) yetkileriyle çalıştırmanız gerekmez. + +/// + +Şimdi diğer dosyayı, `routes.toml`'u oluşturun: + +```TOML hl_lines="5 12 20" +[http] + [http.middlewares] + + [http.middlewares.api-stripprefix.stripPrefix] + prefixes = ["/api/v1"] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + middlewares = ["api-stripprefix"] + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +Bu dosya, Traefik'i `/api/v1` path prefix'ini kullanacak şekilde yapılandırır. + +Ardından Traefik, request'leri `http://127.0.0.1:8000` üzerinde çalışan Uvicorn'unuza yönlendirir. + +Şimdi Traefik'i başlatın: + +
+ +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml +``` + +
+ +Ve şimdi uygulamanızı `--root-path` seçeneğiyle başlatın: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Response'ları Kontrol Edin { #check-the-responses } + +Şimdi Uvicorn'un portundaki URL'ye giderseniz: http://127.0.0.1:8000/app, normal response'u görürsünüz: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +/// tip | İpucu + +`http://127.0.0.1:8000/app` üzerinden erişiyor olsanız bile, `root_path` değerinin `--root-path` seçeneğinden alınıp `/api/v1` olarak gösterildiğine dikkat edin. + +/// + +Şimdi de Traefik'in portundaki URL'yi, path prefix ile birlikte açın: http://127.0.0.1:9999/api/v1/app. + +Aynı response'u alırız: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +ama bu sefer proxy'nin sağladığı prefix path olan `/api/v1` ile gelen URL'de. + +Elbette buradaki fikir, herkesin uygulamaya proxy üzerinden erişmesidir; dolayısıyla `/api/v1` path prefix'li sürüm "doğru" olandır. + +Uvicorn'un doğrudan sunduğu, path prefix olmayan sürüm (`http://127.0.0.1:8000/app`) ise sadece _proxy_'nin (Traefik) erişmesi için kullanılmalıdır. + +Bu da Proxy'nin (Traefik) path prefix'i nasıl kullandığını ve server'ın (Uvicorn) `--root-path` seçeneğinden gelen `root_path`'i nasıl kullandığını gösterir. + +### Docs UI'yi Kontrol Edin { #check-the-docs-ui } + +Şimdi işin eğlenceli kısmı. ✨ + +Uygulamaya erişmenin "resmi" yolu, tanımladığımız path prefix ile proxy üzerinden erişmektir. Bu yüzden beklendiği gibi, Uvicorn'un doğrudan servis ettiği docs UI'yi URL'de path prefix olmadan açarsanız çalışmaz; çünkü proxy üzerinden erişileceğini varsayar. + +Şuradan kontrol edebilirsiniz: http://127.0.0.1:8000/docs: + + + +Ancak docs UI'yi proxy üzerinden, `9999` portuyla, `/api/v1/docs` altında "resmi" URL'den açarsak doğru çalışır! 🎉 + +Şuradan kontrol edebilirsiniz: http://127.0.0.1:9999/api/v1/docs: + + + +Tam istediğimiz gibi. ✔️ + +Bunun nedeni, FastAPI'nin OpenAPI içinde varsayılan `server`'ı, `root_path` tarafından verilen URL ile oluşturmak için bu `root_path`'i kullanmasıdır. + +## Ek `server`'lar { #additional-servers } + +/// warning | Uyarı + +Bu daha ileri seviye bir kullanım senaryosudur. İsterseniz atlayabilirsiniz. + +/// + +Varsayılan olarak **FastAPI**, OpenAPI şemasında `root_path` için bir `server` oluşturur. + +Ancak başka alternatif `servers` da sağlayabilirsiniz; örneğin *aynı* docs UI'nin hem staging hem de production ortamıyla etkileşime girmesini istiyorsanız. + +Özel bir `servers` listesi verirseniz ve bir `root_path` varsa (çünkü API'niz proxy arkasındadır), **FastAPI** bu `root_path` ile bir "server"ı listenin başına ekler. + +Örneğin: + +{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} + +Şöyle bir OpenAPI şeması üretir: + +```JSON hl_lines="5-7" +{ + "openapi": "3.1.0", + // More stuff here + "servers": [ + { + "url": "/api/v1" + }, + { + "url": "https://stag.example.com", + "description": "Staging environment" + }, + { + "url": "https://prod.example.com", + "description": "Production environment" + } + ], + "paths": { + // More stuff here + } +} +``` + +/// tip | İpucu + +`url` değeri `/api/v1` olan, `root_path`'ten alınmış otomatik üretilen server'a dikkat edin. + +/// + +Docs UI'de, http://127.0.0.1:9999/api/v1/docs adresinde şöyle görünür: + + + +/// tip | İpucu + +Docs UI, seçtiğiniz server ile etkileşime girer. + +/// + +/// note | Teknik Detaylar + +OpenAPI spesifikasyonunda `servers` özelliği opsiyoneldir. + +`servers` parametresini belirtmezseniz ve `root_path` `/` ile aynıysa, üretilen OpenAPI şemasında `servers` özelliği varsayılan olarak tamamen çıkarılır; bu da `url` değeri `/` olan tek bir server ile eşdeğerdir. + +/// + +### `root_path`'ten Otomatik `server` Eklenmesini Kapatma { #disable-automatic-server-from-root-path } + +**FastAPI**'nin `root_path` kullanarak otomatik bir server eklemesini istemiyorsanız, `root_path_in_servers=False` parametresini kullanabilirsiniz: + +{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} + +Böylece OpenAPI şemasına dahil etmez. + +## Bir Sub-Application Mount Etme { #mounting-a-sub-application } + +Bir sub-application'ı ( [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank} bölümünde anlatıldığı gibi) mount etmeniz gerekiyorsa ve aynı zamanda `root_path` ile bir proxy kullanıyorsanız, bunu beklendiği gibi normal şekilde yapabilirsiniz. + +FastAPI içeride `root_path`'i akıllıca kullanır; dolayısıyla doğrudan çalışır. ✨ diff --git a/docs/tr/docs/advanced/custom-response.md b/docs/tr/docs/advanced/custom-response.md new file mode 100644 index 000000000..c5148f428 --- /dev/null +++ b/docs/tr/docs/advanced/custom-response.md @@ -0,0 +1,312 @@ +# Özel Response - HTML, Stream, File ve Diğerleri { #custom-response-html-stream-file-others } + +Varsayılan olarak **FastAPI**, response'ları `JSONResponse` kullanarak döndürür. + +Bunu, [Doğrudan bir Response döndür](response-directly.md){.internal-link target=_blank} bölümünde gördüğünüz gibi doğrudan bir `Response` döndürerek geçersiz kılabilirsiniz. + +Ancak doğrudan bir `Response` döndürürseniz (veya `JSONResponse` gibi herhangi bir alt sınıfını), veri otomatik olarak dönüştürülmez (bir `response_model` tanımlamış olsanız bile) ve dokümantasyon da otomatik üretilmez (örneğin, üretilen OpenAPI’nin parçası olarak HTTP header `Content-Type` içindeki ilgili "media type" dahil edilmez). + +Bununla birlikte, *path operation decorator* içinde `response_class` parametresini kullanarak hangi `Response`’un (örn. herhangi bir `Response` alt sınıfı) kullanılacağını da ilan edebilirsiniz. + +*path operation function*’ınızdan döndürdüğünüz içerik, o `Response`’un içine yerleştirilir. + +Ve eğer bu `Response` ( `JSONResponse` ve `UJSONResponse`’ta olduğu gibi) bir JSON media type’a (`application/json`) sahipse, döndürdüğünüz veri; *path operation decorator* içinde tanımladığınız herhangi bir Pydantic `response_model` ile otomatik olarak dönüştürülür (ve filtrelenir). + +/// note | Not + +Media type’ı olmayan bir response class kullanırsanız, FastAPI response’unuzun content içermediğini varsayar; bu yüzden ürettiği OpenAPI dokümanında response formatını dokümante etmez. + +/// + +## `ORJSONResponse` Kullan { #use-orjsonresponse } + +Örneğin performansı sıkıştırmaya çalışıyorsanız, `orjson` kurup kullanabilir ve response’u `ORJSONResponse` olarak ayarlayabilirsiniz. + +Kullanmak istediğiniz `Response` class’ını (alt sınıfını) import edin ve *path operation decorator* içinde tanımlayın. + +Büyük response'larda, doğrudan bir `Response` döndürmek bir dictionary döndürmekten çok daha hızlıdır. + +Çünkü varsayılan olarak FastAPI, içindeki her item’ı inceleyip JSON olarak serialize edilebilir olduğundan emin olur; tutorial’da anlatılan aynı [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} mekanizmasını kullanır. Bu da örneğin veritabanı modelleri gibi **keyfi objeleri** döndürebilmenizi sağlar. + +Ancak döndürdüğünüz içeriğin **JSON ile serialize edilebilir** olduğundan eminseniz, onu doğrudan response class’ına verebilir ve FastAPI’nin response class’ına vermeden önce dönüş içeriğinizi `jsonable_encoder` içinden geçirirken oluşturacağı ek yükten kaçınabilirsiniz. + +{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} + +/// info | Bilgi + +`response_class` parametresi, response’un "media type"’ını tanımlamak için de kullanılır. + +Bu durumda HTTP header `Content-Type`, `application/json` olarak ayarlanır. + +Ve OpenAPI’de de bu şekilde dokümante edilir. + +/// + +/// tip | İpucu + +`ORJSONResponse` yalnızca FastAPI’de vardır, Starlette’te yoktur. + +/// + +## HTML Response { #html-response } + +**FastAPI**’den doğrudan HTML içeren bir response döndürmek için `HTMLResponse` kullanın. + +* `HTMLResponse` import edin. +* *path operation decorator*’ınızın `response_class` parametresi olarak `HTMLResponse` verin. + +{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} + +/// info | Bilgi + +`response_class` parametresi, response’un "media type"’ını tanımlamak için de kullanılır. + +Bu durumda HTTP header `Content-Type`, `text/html` olarak ayarlanır. + +Ve OpenAPI’de de bu şekilde dokümante edilir. + +/// + +### Bir `Response` Döndür { #return-a-response } + +[Doğrudan bir Response döndür](response-directly.md){.internal-link target=_blank} bölümünde görüldüğü gibi, *path operation* içinde doğrudan bir response döndürerek response’u override edebilirsiniz. + +Yukarıdaki örneğin aynısı, bu sefer bir `HTMLResponse` döndürerek, şöyle görünebilir: + +{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} + +/// warning | Uyarı + +*path operation function*’ınızın doğrudan döndürdüğü bir `Response`, OpenAPI’de dokümante edilmez (örneğin `Content-Type` dokümante edilmez) ve otomatik interaktif dokümanlarda görünmez. + +/// + +/// info | Bilgi + +Elbette gerçek `Content-Type` header’ı, status code vb. değerler, döndürdüğünüz `Response` objesinden gelir. + +/// + +### OpenAPI’de Dokümante Et ve `Response`’u Override Et { #document-in-openapi-and-override-response } + +Response’u fonksiyonun içinden override etmek ama aynı zamanda OpenAPI’de "media type"’ı dokümante etmek istiyorsanız, `response_class` parametresini kullanıp ayrıca bir `Response` objesi döndürebilirsiniz. + +Bu durumda `response_class` sadece OpenAPI *path operation*’ını dokümante etmek için kullanılır; sizin `Response`’unuz ise olduğu gibi kullanılır. + +#### Doğrudan bir `HTMLResponse` Döndür { #return-an-htmlresponse-directly } + +Örneğin şöyle bir şey olabilir: + +{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} + +Bu örnekte `generate_html_response()` fonksiyonu, HTML’i bir `str` olarak döndürmek yerine zaten bir `Response` üretip döndürmektedir. + +`generate_html_response()` çağrısının sonucunu döndürerek, varsayılan **FastAPI** davranışını override edecek bir `Response` döndürmüş olursunuz. + +Ama `response_class` içinde `HTMLResponse` da verdiğiniz için **FastAPI**, bunu OpenAPI’de ve interaktif dokümanlarda `text/html` ile HTML olarak nasıl dokümante edeceğini bilir: + + + +## Mevcut Response'lar { #available-responses } + +Mevcut response'lardan bazıları aşağıdadır. + +Unutmayın: `Response` ile başka herhangi bir şeyi döndürebilir, hatta özel bir alt sınıf da oluşturabilirsiniz. + +/// note | Teknik Detaylar + +`from starlette.responses import HTMLResponse` da kullanabilirsiniz. + +**FastAPI**, geliştirici için kolaylık olsun diye `starlette.responses` içindekileri `fastapi.responses` olarak da sağlar. Ancak mevcut response'ların çoğu doğrudan Starlette’ten gelir. + +/// + +### `Response` { #response } + +Ana `Response` class’ıdır; diğer tüm response'lar bundan türetilir. + +Bunu doğrudan döndürebilirsiniz. + +Şu parametreleri kabul eder: + +* `content` - Bir `str` veya `bytes`. +* `status_code` - Bir `int` HTTP status code. +* `headers` - String’lerden oluşan bir `dict`. +* `media_type` - Media type’ı veren bir `str`. Örn. `"text/html"`. + +FastAPI (aslında Starlette) otomatik olarak bir Content-Length header’ı ekler. Ayrıca `media_type`’a göre bir Content-Type header’ı ekler ve text türleri için sona bir charset ekler. + +{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} + +### `HTMLResponse` { #htmlresponse } + +Yukarıda okuduğunuz gibi, bir miktar text veya bytes alır ve HTML response döndürür. + +### `PlainTextResponse` { #plaintextresponse } + +Bir miktar text veya bytes alır ve düz metin response döndürür. + +{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} + +### `JSONResponse` { #jsonresponse } + +Bir miktar veri alır ve `application/json` olarak encode edilmiş bir response döndürür. + +Yukarıda okuduğunuz gibi, **FastAPI**’de varsayılan response budur. + +### `ORJSONResponse` { #orjsonresponse } + +Yukarıda okuduğunuz gibi `orjson` kullanan hızlı bir alternatif JSON response. + +/// info | Bilgi + +Bunun için `orjson` kurulmalıdır; örneğin `pip install orjson`. + +/// + +### `UJSONResponse` { #ujsonresponse } + +`ujson` kullanan alternatif bir JSON response. + +/// info | Bilgi + +Bunun için `ujson` kurulmalıdır; örneğin `pip install ujson`. + +/// + +/// warning | Uyarı + +`ujson`, bazı edge-case’leri ele alma konusunda Python’un built-in implementasyonu kadar dikkatli değildir. + +/// + +{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} + +/// tip | İpucu + +`ORJSONResponse` daha hızlı bir alternatif olabilir. + +/// + +### `RedirectResponse` { #redirectresponse } + +HTTP redirect döndürür. Varsayılan olarak 307 status code (Temporary Redirect) kullanır. + +`RedirectResponse`’u doğrudan döndürebilirsiniz: + +{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} + +--- + +Veya `response_class` parametresi içinde kullanabilirsiniz: + +{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} + +Bunu yaparsanız, *path operation* function’ınızdan doğrudan URL döndürebilirsiniz. + +Bu durumda kullanılan `status_code`, `RedirectResponse` için varsayılan olan `307` olur. + +--- + +Ayrıca `status_code` parametresini `response_class` parametresiyle birlikte kullanabilirsiniz: + +{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} + +### `StreamingResponse` { #streamingresponse } + +Bir async generator veya normal generator/iterator alır ve response body’yi stream eder. + +{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} + +#### `StreamingResponse`’u file-like objelerle kullanma { #using-streamingresponse-with-file-like-objects } + +Bir file-like objeniz varsa (örn. `open()`’ın döndürdüğü obje), o file-like obje üzerinde iterate eden bir generator function oluşturabilirsiniz. + +Böylece önce hepsini memory’ye okumak zorunda kalmazsınız; bu generator function’ı `StreamingResponse`’a verip döndürebilirsiniz. + +Buna cloud storage ile etkileşime giren, video işleyen ve benzeri birçok kütüphane dahildir. + +{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} + +1. Bu generator function’dır. İçinde `yield` ifadeleri olduğu için "generator function" denir. +2. Bir `with` bloğu kullanarak, generator function bittiğinde file-like objenin kapandığından emin oluruz. Yani response göndermeyi bitirdikten sonra kapanır. +3. Bu `yield from`, fonksiyona `file_like` isimli şeyi iterate etmesini söyler. Ardından iterate edilen her parça için, o parçayı bu generator function’dan (`iterfile`) geliyormuş gibi yield eder. + + Yani, içerdeki "üretme" (generating) işini başka bir şeye devreden bir generator function’dır. + + Bunu bu şekilde yaptığımızda `with` bloğu içinde tutabilir ve böylece iş bitince file-like objenin kapanmasını garanti edebiliriz. + +/// tip | İpucu + +Burada `async` ve `await` desteklemeyen standart `open()` kullandığımız için path operation’ı normal `def` ile tanımlarız. + +/// + +### `FileResponse` { #fileresponse } + +Asenkron olarak bir dosyayı response olarak stream eder. + +Diğer response türlerine göre instantiate ederken farklı argümanlar alır: + +* `path` - Stream edilecek dosyanın dosya path'i. +* `headers` - Eklenecek özel header’lar; dictionary olarak. +* `media_type` - Media type’ı veren string. Ayarlanmazsa, dosya adı veya path kullanılarak media type tahmin edilir. +* `filename` - Ayarlanırsa response içindeki `Content-Disposition`’a dahil edilir. + +File response'ları uygun `Content-Length`, `Last-Modified` ve `ETag` header’larını içerir. + +{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} + +`response_class` parametresini de kullanabilirsiniz: + +{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} + +Bu durumda *path operation* function’ınızdan doğrudan dosya path'ini döndürebilirsiniz. + +## Özel response class { #custom-response-class } + +`Response`’dan türeterek kendi özel response class’ınızı oluşturabilir ve kullanabilirsiniz. + +Örneğin, dahil gelen `ORJSONResponse` class’ında kullanılmayan bazı özel ayarlarla `orjson` kullanmak istediğinizi varsayalım. + +Diyelim ki girintili ve biçimlendirilmiş JSON döndürmek istiyorsunuz; bunun için `orjson.OPT_INDENT_2` seçeneğini kullanmak istiyorsunuz. + +Bir `CustomORJSONResponse` oluşturabilirsiniz. Burada yapmanız gereken temel şey, content’i `bytes` olarak döndüren bir `Response.render(content)` metodu yazmaktır: + +{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} + +Artık şunu döndürmek yerine: + +```json +{"message": "Hello World"} +``` + +...bu response şunu döndürür: + +```json +{ + "message": "Hello World" +} +``` + +Elbette JSON’u formatlamaktan çok daha iyi şekillerde bundan faydalanabilirsiniz. 😉 + +## Varsayılan response class { #default-response-class } + +Bir **FastAPI** class instance’ı veya bir `APIRouter` oluştururken, varsayılan olarak hangi response class’ının kullanılacağını belirtebilirsiniz. + +Bunu tanımlayan parametre `default_response_class`’tır. + +Aşağıdaki örnekte **FastAPI**, tüm *path operations* için varsayılan olarak `JSONResponse` yerine `ORJSONResponse` kullanır. + +{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} + +/// tip | İpucu + +Daha önce olduğu gibi, *path operations* içinde `response_class`’ı yine override edebilirsiniz. + +/// + +## Ek dokümantasyon { #additional-documentation } + +OpenAPI’de media type’ı ve daha birçok detayı `responses` kullanarak da tanımlayabilirsiniz: [OpenAPI’de Ek Response'lar](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/tr/docs/advanced/dataclasses.md b/docs/tr/docs/advanced/dataclasses.md new file mode 100644 index 000000000..263976007 --- /dev/null +++ b/docs/tr/docs/advanced/dataclasses.md @@ -0,0 +1,95 @@ +# Dataclass Kullanımı { #using-dataclasses } + +FastAPI, **Pydantic** üzerine inşa edilmiştir ve request/response tanımlamak için Pydantic model'lerini nasıl kullanacağınızı gösteriyordum. + +Ancak FastAPI, `dataclasses` kullanmayı da aynı şekilde destekler: + +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} + +Bu destek hâlâ **Pydantic** sayesinde vardır; çünkü Pydantic, `dataclasses` için dahili destek sunar. + +Yani yukarıdaki kod Pydantic'i doğrudan kullanmasa bile, FastAPI bu standart dataclass'ları Pydantic'in kendi dataclass biçimine dönüştürmek için Pydantic'i kullanmaktadır. + +Ve elbette aynı özellikleri destekler: + +* veri doğrulama (data validation) +* veri serileştirme (data serialization) +* veri dokümantasyonu (data documentation), vb. + +Bu, Pydantic model'lerinde olduğu gibi çalışır. Aslında arka planda da aynı şekilde, Pydantic kullanılarak yapılır. + +/// info | Bilgi + +Dataclass'ların, Pydantic model'lerinin yapabildiği her şeyi yapamadığını unutmayın. + +Bu yüzden yine de Pydantic model'lerini kullanmanız gerekebilir. + +Ancak elinizde zaten bir sürü dataclass varsa, bunları FastAPI ile bir web API'yi beslemek için kullanmak güzel bir numaradır. 🤓 + +/// + +## `response_model` İçinde Dataclass'lar { #dataclasses-in-response-model } + +`response_model` parametresinde `dataclasses` da kullanabilirsiniz: + +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} + +Dataclass otomatik olarak bir Pydantic dataclass'ına dönüştürülür. + +Bu sayede şeması API docs kullanıcı arayüzünde görünür: + + + +## İç İçe Veri Yapılarında Dataclass'lar { #dataclasses-in-nested-data-structures } + +İç içe veri yapıları oluşturmak için `dataclasses` ile diğer type annotation'ları da birleştirebilirsiniz. + +Bazı durumlarda yine de Pydantic'in `dataclasses` sürümünü kullanmanız gerekebilir. Örneğin, otomatik oluşturulan API dokümantasyonunda hata alıyorsanız. + +Bu durumda standart `dataclasses` yerine, drop-in replacement olan `pydantic.dataclasses` kullanabilirsiniz: + +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} + +1. `field` hâlâ standart `dataclasses` içinden import edilir. + +2. `pydantic.dataclasses`, `dataclasses` için bir drop-in replacement'tır. + +3. `Author` dataclass'ı, `Item` dataclass'larından oluşan bir liste içerir. + +4. `Author` dataclass'ı, `response_model` parametresi olarak kullanılır. + +5. Request body olarak dataclass'larla birlikte diğer standart type annotation'ları da kullanabilirsiniz. + + Bu örnekte, `Item` dataclass'larından oluşan bir listedir. + +6. Burada `items` içeren bir dictionary döndürüyoruz; `items` bir dataclass listesi. + + FastAPI, veriyi JSON'a serializing etmeyi yine başarır. + +7. Burada `response_model`, `Author` dataclass'larından oluşan bir listenin type annotation'ını kullanıyor. + + Yine `dataclasses` ile standart type annotation'ları birleştirebilirsiniz. + +8. Bu *path operation function*, `async def` yerine normal `def` kullanıyor. + + Her zaman olduğu gibi, FastAPI'de ihtiyaca göre `def` ve `async def`’i birlikte kullanabilirsiniz. + + Hangisini ne zaman kullanmanız gerektiğine dair hızlı bir hatırlatma isterseniz, [`async` ve `await`](../async.md#in-a-hurry){.internal-link target=_blank} dokümanındaki _"In a hurry?"_ bölümüne bakın. + +9. Bu *path operation function* dataclass döndürmüyor (isterse döndürebilir), onun yerine dahili verilerle bir dictionary listesi döndürüyor. + + FastAPI, response'u dönüştürmek için (dataclass'ları içeren) `response_model` parametresini kullanacaktır. + +Karmaşık veri yapıları oluşturmak için `dataclasses` ile diğer type annotation'ları pek çok farklı kombinasyonda birleştirebilirsiniz. + +Daha spesifik ayrıntılar için yukarıdaki kod içi annotation ipuçlarına bakın. + +## Daha Fazla Öğrenin { #learn-more } + +`dataclasses`'ı diğer Pydantic model'leriyle de birleştirebilir, onlardan kalıtım alabilir, kendi model'lerinize dahil edebilirsiniz, vb. + +Daha fazlası için Pydantic'in dataclasses dokümantasyonuna bakın. + +## Sürüm { #version } + +Bu özellik FastAPI `0.67.0` sürümünden beri mevcuttur. 🔖 diff --git a/docs/tr/docs/advanced/events.md b/docs/tr/docs/advanced/events.md new file mode 100644 index 000000000..257b952f9 --- /dev/null +++ b/docs/tr/docs/advanced/events.md @@ -0,0 +1,165 @@ +# Lifespan Olayları { #lifespan-events } + +Uygulama **başlamadan** önce çalıştırılması gereken mantığı (kodu) tanımlayabilirsiniz. Bu, bu kodun **bir kez**, uygulama **request almaya başlamadan önce** çalıştırılacağı anlamına gelir. + +Benzer şekilde, uygulama **kapanırken** çalıştırılması gereken mantığı (kodu) da tanımlayabilirsiniz. Bu durumda bu kod, muhtemelen **çok sayıda request** işlendi **sonra**, **bir kez** çalıştırılır. + +Bu kod, uygulama request almaya **başlamadan** önce ve request’leri işlemeyi **bitirdikten** hemen sonra çalıştığı için, uygulamanın tüm **lifespan**’ını (birazdan "lifespan" kelimesi önemli olacak 😉) kapsar. + +Bu yaklaşım, tüm uygulama boyunca kullanacağınız ve request’ler arasında **paylaşılan** **resource**’ları kurmak ve/veya sonrasında bunları **temizlemek** için çok faydalıdır. Örneğin bir veritabanı connection pool’u ya da paylaşılan bir machine learning modelini yüklemek gibi. + +## Kullanım Senaryosu { #use-case } + +Önce bir **kullanım senaryosu** örneğiyle başlayalım, sonra bunu bununla nasıl çözeceğimize bakalım. + +Request’leri işlemek için kullanmak istediğiniz bazı **machine learning modelleriniz** olduğunu hayal edelim. 🤖 + +Aynı modeller request’ler arasında paylaşılır; yani request başına bir model, kullanıcı başına bir model vb. gibi değil. + +Modeli yüklemenin, diskten çok fazla **data** okunması gerektiği için **oldukça uzun sürebildiğini** düşünelim. Dolayısıyla bunu her request için yapmak istemezsiniz. + +Modeli modülün/dosyanın en üst seviyesinde yükleyebilirdiniz; ancak bu, basit bir otomatik test çalıştırdığınızda bile **modelin yükleneceği** anlamına gelir. Böyle olunca test, kodun bağımsız bir kısmını çalıştırabilmek için önce modelin yüklenmesini beklemek zorunda kalır ve **yavaş** olur. + +Burada çözeceğimiz şey bu: modeli request’ler işlenmeden önce yükleyelim, ama kod yüklenirken değil; yalnızca uygulama request almaya başlamadan hemen önce. + +## Lifespan { #lifespan } + +Bu *startup* ve *shutdown* mantığını, `FastAPI` uygulamasının `lifespan` parametresi ve bir "context manager" kullanarak tanımlayabilirsiniz (bunun ne olduğunu birazdan göstereceğim). + +Önce bir örnekle başlayıp sonra ayrıntılarına bakalım. + +Aşağıdaki gibi `yield` kullanan async bir `lifespan()` fonksiyonu oluşturuyoruz: + +{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} + +Burada, `yield` öncesinde (sahte) model fonksiyonunu machine learning modellerini içeren dictionary’e koyarak, modeli yükleme gibi maliyetli bir *startup* işlemini simüle ediyoruz. Bu kod, *startup* sırasında, uygulama **request almaya başlamadan önce** çalıştırılır. + +Ardından `yield`’den hemen sonra modeli bellekten kaldırıyoruz (unload). Bu kod, uygulama **request’leri işlemeyi bitirdikten sonra**, *shutdown*’dan hemen önce çalıştırılır. Örneğin memory veya GPU gibi resource’ları serbest bırakabilir. + +/// tip | İpucu + +`shutdown`, uygulamayı **durdurduğunuzda** gerçekleşir. + +Belki yeni bir sürüm başlatmanız gerekiyordur, ya da çalıştırmaktan sıkılmışsınızdır. 🤷 + +/// + +### Lifespan fonksiyonu { #lifespan-function } + +Dikkat edilmesi gereken ilk şey, `yield` içeren async bir fonksiyon tanımlıyor olmamız. Bu, `yield` kullanan Dependencies’e oldukça benzer. + +{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} + +Fonksiyonun `yield`’den önceki kısmı, uygulama başlamadan **önce** çalışır. + +`yield`’den sonraki kısım ise, uygulama işini bitirdikten **sonra** çalışır. + +### Async Context Manager { #async-context-manager } + +Bakarsanız, fonksiyon `@asynccontextmanager` ile dekore edilmiş. + +Bu da fonksiyonu "**async context manager**" denen şeye dönüştürür. + +{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} + +Python’da **context manager**, `with` ifadesi içinde kullanabildiğiniz bir yapıdır. Örneğin `open()` bir context manager olarak kullanılabilir: + +```Python +with open("file.txt") as file: + file.read() +``` + +Python’ın güncel sürümlerinde bir de **async context manager** vardır. Bunu `async with` ile kullanırsınız: + +```Python +async with lifespan(app): + await do_stuff() +``` + +Yukarıdaki gibi bir context manager veya async context manager oluşturduğunuzda, yaptığı şey şudur: `with` bloğuna girmeden önce `yield`’den önceki kodu çalıştırır, `with` bloğundan çıktıktan sonra da `yield`’den sonraki kodu çalıştırır. + +Yukarıdaki kod örneğimizde bunu doğrudan kullanmıyoruz; bunun yerine FastAPI’ye veriyoruz ki o kullansın. + +`FastAPI` uygulamasının `lifespan` parametresi bir **async context manager** alır; dolayısıyla oluşturduğumuz yeni `lifespan` async context manager’ını buraya geçebiliriz. + +{* ../../docs_src/events/tutorial003_py39.py hl[22] *} + +## Alternatif Events (kullanımdan kaldırıldı) { #alternative-events-deprecated } + +/// warning | Uyarı + +*startup* ve *shutdown* işlemlerini yönetmenin önerilen yolu, yukarıda anlatıldığı gibi `FastAPI` uygulamasının `lifespan` parametresini kullanmaktır. Bir `lifespan` parametresi sağlarsanız, `startup` ve `shutdown` event handler’ları artık çağrılmaz. Ya tamamen `lifespan` ya da tamamen events; ikisi birden değil. + +Muhtemelen bu bölümü atlayabilirsiniz. + +/// + +*startup* ve *shutdown* sırasında çalıştırılacak bu mantığı tanımlamanın alternatif bir yolu daha vardır. + +Uygulama başlamadan önce veya uygulama kapanırken çalıştırılması gereken event handler’ları (fonksiyonları) tanımlayabilirsiniz. + +Bu fonksiyonlar `async def` ile veya normal `def` ile tanımlanabilir. + +### `startup` eventi { #startup-event } + +Uygulama başlamadan önce çalıştırılacak bir fonksiyon eklemek için, `"startup"` event’i ile tanımlayın: + +{* ../../docs_src/events/tutorial001_py39.py hl[8] *} + +Bu durumda `startup` event handler fonksiyonu, "database" öğesini (sadece bir `dict`) bazı değerlerle başlatır. + +Birden fazla event handler fonksiyonu ekleyebilirsiniz. + +Ve tüm `startup` event handler’ları tamamlanmadan uygulamanız request almaya başlamaz. + +### `shutdown` eventi { #shutdown-event } + +Uygulama kapanırken çalıştırılacak bir fonksiyon eklemek için, `"shutdown"` event’i ile tanımlayın: + +{* ../../docs_src/events/tutorial002_py39.py hl[6] *} + +Burada `shutdown` event handler fonksiyonu, `log.txt` dosyasına `"Application shutdown"` satırını yazar. + +/// info | Bilgi + +`open()` fonksiyonunda `mode="a"` "append" anlamına gelir; yani satır, önceki içeriği silmeden dosyada ne varsa onun sonuna eklenir. + +/// + +/// tip | İpucu + +Dikkat edin, bu örnekte bir dosyayla etkileşen standart Python `open()` fonksiyonunu kullanıyoruz. + +Dolayısıyla disk’e yazılmasını beklemeyi gerektiren I/O (input/output) söz konusu. + +Ancak `open()` `async` ve `await` kullanmaz. + +Bu yüzden event handler fonksiyonunu `async def` yerine standart `def` ile tanımlarız. + +/// + +### `startup` ve `shutdown` birlikte { #startup-and-shutdown-together } + +*startup* ve *shutdown* mantığınızın birbiriyle bağlantılı olma ihtimali yüksektir; bir şeyi başlatıp sonra bitirmek, bir resource edinip sonra serbest bırakmak vb. isteyebilirsiniz. + +Bunu, ortak mantık veya değişken paylaşmayan ayrı fonksiyonlarda yapmak daha zordur; çünkü değerleri global değişkenlerde tutmanız veya benzer numaralar yapmanız gerekir. + +Bu nedenle artık bunun yerine, yukarıda açıklandığı gibi `lifespan` kullanmanız önerilmektedir. + +## Teknik Detaylar { #technical-details } + +Meraklı nerd’ler için küçük bir teknik detay. 🤓 + +Altta, ASGI teknik spesifikasyonunda bu, Lifespan Protocol’ün bir parçasıdır ve `startup` ile `shutdown` adında event’ler tanımlar. + +/// info | Bilgi + +Starlette `lifespan` handler’ları hakkında daha fazlasını Starlette's Lifespan docs içinde okuyabilirsiniz. + +Ayrıca kodunuzun başka bölgelerinde de kullanılabilecek lifespan state’i nasıl yöneteceğinizi de kapsar. + +/// + +## Alt Uygulamalar { #sub-applications } + +🚨 Unutmayın: Bu lifespan event’leri (`startup` ve `shutdown`) yalnızca ana uygulama için çalıştırılır; [Alt Uygulamalar - Mounts](sub-applications.md){.internal-link target=_blank} için çalıştırılmaz. diff --git a/docs/tr/docs/advanced/generate-clients.md b/docs/tr/docs/advanced/generate-clients.md new file mode 100644 index 000000000..af278f2fe --- /dev/null +++ b/docs/tr/docs/advanced/generate-clients.md @@ -0,0 +1,208 @@ +# SDK Üretme { #generating-sdks } + +**FastAPI**, **OpenAPI** spesifikasyonunu temel aldığı için API'leri birçok aracın anlayabildiği standart bir formatta tanımlanabilir. + +Bu sayede güncel **dokümantasyon**, birden fazla dilde istemci kütüphaneleri (**SDKs**) ve kodunuzla senkron kalan **test** veya **otomasyon iş akışları** üretmek kolaylaşır. + +Bu rehberde, FastAPI backend'iniz için bir **TypeScript SDK** üretmeyi öğreneceksiniz. + +## Açık Kaynak SDK Üreteçleri { #open-source-sdk-generators } + +Esnek bir seçenek olan OpenAPI Generator, **birçok programlama dilini** destekler ve OpenAPI spesifikasyonunuzdan SDK üretebilir. + +**TypeScript client**'lar için Hey API, TypeScript ekosistemi için özel olarak tasarlanmış, optimize bir deneyim sunan bir çözümdür. + +Daha fazla SDK üretecini OpenAPI.Tools üzerinde keşfedebilirsiniz. + +/// tip | İpucu + +FastAPI otomatik olarak **OpenAPI 3.1** spesifikasyonları üretir; bu yüzden kullanacağınız aracın bu sürümü desteklemesi gerekir. + +/// + +## FastAPI Sponsorlarından SDK Üreteçleri { #sdk-generators-from-fastapi-sponsors } + +Bu bölüm, FastAPI'yi sponsorlayan şirketlerin sunduğu **yatırım destekli** ve **şirket destekli** çözümleri öne çıkarır. Bu ürünler, yüksek kaliteli üretilen SDK'ların üzerine **ek özellikler** ve **entegrasyonlar** sağlar. + +✨ [**FastAPI'ye sponsor olarak**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ bu şirketler, framework'ün ve **ekosisteminin** sağlıklı ve **sürdürülebilir** kalmasına yardımcı olur. + +Sponsor olmaları aynı zamanda FastAPI **topluluğuna** (size) güçlü bir bağlılığı da gösterir; yalnızca **iyi bir hizmet** sunmayı değil, aynı zamanda **güçlü ve gelişen bir framework** olan FastAPI'yi desteklemeyi de önemsediklerini gösterir. 🙇 + +Örneğin şunları deneyebilirsiniz: + +* Speakeasy +* Stainless +* liblab + +Bu çözümlerin bazıları açık kaynak olabilir veya ücretsiz katman sunabilir; yani finansal bir taahhüt olmadan deneyebilirsiniz. Başka ticari SDK üreteçleri de vardır ve internette bulunabilir. 🤓 + +## TypeScript SDK Oluşturma { #create-a-typescript-sdk } + +Basit bir FastAPI uygulamasıyla başlayalım: + +{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} + +*Path operation*'ların, request payload ve response payload için kullandıkları modelleri `Item` ve `ResponseMessage` modelleriyle tanımladıklarına dikkat edin. + +### API Dokümanları { #api-docs } + +`/docs` adresine giderseniz, request'lerde gönderilecek ve response'larda alınacak veriler için **schema**'ları içerdiğini görürsünüz: + + + +Bu schema'ları görebilirsiniz, çünkü uygulamada modellerle birlikte tanımlandılar. + +Bu bilgi uygulamanın **OpenAPI schema**'sında bulunur ve sonrasında API dokümanlarında gösterilir. + +OpenAPI'ye dahil edilen, modellerden gelen bu bilginin aynısı **client code üretmek** için kullanılabilir. + +### Hey API { #hey-api } + +Modelleri olan bir FastAPI uygulamamız olduğunda, Hey API ile bir TypeScript client üretebiliriz. Bunu yapmanın en hızlı yolu npx kullanmaktır. + +```sh +npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client +``` + +Bu komut `./src/client` içine bir TypeScript SDK üretecektir. + +Web sitelerinde `@hey-api/openapi-ts` kurulumunu öğrenebilir ve üretilen çıktıyı inceleyebilirsiniz. + +### SDK'yı Kullanma { #using-the-sdk } + +Artık client code'u import edip kullanabilirsiniz. Şuna benzer görünebilir; method'lar için otomatik tamamlama aldığınıza dikkat edin: + + + +Ayrıca gönderilecek payload için de otomatik tamamlama alırsınız: + + + +/// tip | İpucu + +`name` ve `price` için otomatik tamamlamaya dikkat edin; bunlar FastAPI uygulamasında, `Item` modelinde tanımlanmıştı. + +/// + +Gönderdiğiniz veriler için satır içi hatalar (inline errors) da alırsınız: + + + +Response objesi de otomatik tamamlama sunacaktır: + + + +## Tag'lerle FastAPI Uygulaması { #fastapi-app-with-tags } + +Birçok durumda FastAPI uygulamanız daha büyük olacaktır ve farklı *path operation* gruplarını ayırmak için muhtemelen tag'leri kullanacaksınız. + +Örneğin **items** için bir bölüm, **users** için başka bir bölüm olabilir ve bunları tag'lerle ayırabilirsiniz: + +{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} + +### Tag'lerle TypeScript Client Üretme { #generate-a-typescript-client-with-tags } + +Tag'leri kullanan bir FastAPI uygulaması için client ürettiğinizde, genelde client code da tag'lere göre ayrılır. + +Bu sayede client code tarafında her şey doğru şekilde sıralanır ve gruplandırılır: + + + +Bu örnekte şunlar var: + +* `ItemsService` +* `UsersService` + +### Client Method İsimleri { #client-method-names } + +Şu an üretilen `createItemItemsPost` gibi method isimleri çok temiz görünmüyor: + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +...çünkü client üreteci, her *path operation* için OpenAPI'nin dahili **operation ID** değerini kullanır. + +OpenAPI, her operation ID'nin tüm *path operation*'lar arasında benzersiz olmasını ister. Bu yüzden FastAPI; operation ID'yi benzersiz tutabilmek için **function adı**, **path** ve **HTTP method/operation** bilgilerini birleştirerek üretir. + +Ancak bunu bir sonraki adımda nasıl iyileştirebileceğinizi göstereceğim. 🤓 + +## Özel Operation ID'ler ve Daha İyi Method İsimleri { #custom-operation-ids-and-better-method-names } + +Bu operation ID'lerin **üretilme** şeklini **değiştirerek**, client'larda daha basit **method isimleri** elde edebilirsiniz. + +Bu durumda, her operation ID'nin **benzersiz** olduğundan başka bir şekilde emin olmanız gerekir. + +Örneğin, her *path operation*'ın bir tag'i olmasını sağlayabilir ve operation ID'yi **tag** ve *path operation* **adı**na (function adı) göre üretebilirsiniz. + +### Benzersiz ID Üreten Özel Fonksiyon { #custom-generate-unique-id-function } + +FastAPI, her *path operation* için bir **unique ID** kullanır. Bu ID, **operation ID** için ve ayrıca request/response'lar için gerekebilecek özel model isimleri için de kullanılır. + +Bu fonksiyonu özelleştirebilirsiniz. Bir `APIRoute` alır ve string döndürür. + +Örneğin burada ilk tag'i (muhtemelen tek tag'iniz olur) ve *path operation* adını (function adı) kullanıyor. + +Sonrasında bu özel fonksiyonu `generate_unique_id_function` parametresiyle **FastAPI**'ye geçebilirsiniz: + +{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} + +### Özel Operation ID'lerle TypeScript Client Üretme { #generate-a-typescript-client-with-custom-operation-ids } + +Artık client'ı tekrar üretirseniz, geliştirilmiş method isimlerini göreceksiniz: + + + +Gördüğünüz gibi method isimleri artık önce tag'i, sonra function adını içeriyor; URL path'i ve HTTP operation bilgisini artık taşımıyor. + +### Client Üretecine Vermeden Önce OpenAPI Spesifikasyonunu Ön İşlemek { #preprocess-the-openapi-specification-for-the-client-generator } + +Üretilen kodda hâlâ bazı **tekrarlanan bilgiler** var. + +Bu method'un **items** ile ilişkili olduğunu zaten biliyoruz; çünkü bu kelime `ItemsService` içinde var (tag'den geliyor). Ama method adında da tag adı önek olarak duruyor. 😕 + +OpenAPI genelinde muhtemelen bunu korumak isteriz; çünkü operation ID'lerin **benzersiz** olmasını sağlar. + +Ancak üretilen client için, client'ları üretmeden hemen önce OpenAPI operation ID'lerini **değiştirip**, method isimlerini daha hoş ve **temiz** hale getirebiliriz. + +OpenAPI JSON'u `openapi.json` diye bir dosyaya indirip, şu tarz bir script ile **öndeki tag'i kaldırabiliriz**: + +{* ../../docs_src/generate_clients/tutorial004_py39.py *} + +//// tab | Node.js + +```Javascript +{!> ../../docs_src/generate_clients/tutorial004.js!} +``` + +//// + +Bununla operation ID'ler `items-get_items` gibi değerlerden sadece `get_items` olacak şekilde yeniden adlandırılır; böylece client üreteci daha basit method isimleri üretebilir. + +### Ön İşlenmiş OpenAPI ile TypeScript Client Üretme { #generate-a-typescript-client-with-the-preprocessed-openapi } + +Sonuç artık bir `openapi.json` dosyasında olduğuna göre, input konumunu güncellemeniz gerekir: + +```sh +npx @hey-api/openapi-ts -i ./openapi.json -o src/client +``` + +Yeni client'ı ürettikten sonra, tüm **otomatik tamamlama**, **satır içi hatalar**, vb. ile birlikte **temiz method isimleri** elde edersiniz: + + + +## Faydalar { #benefits } + +Otomatik üretilen client'ları kullanınca şu alanlarda **otomatik tamamlama** elde edersiniz: + +* Method'lar. +* Body'deki request payload'ları, query parametreleri, vb. +* Response payload'ları. + +Ayrıca her şey için **satır içi hatalar** (inline errors) da olur. + +Backend kodunu her güncellediğinizde ve frontend'i **yeniden ürettiğinizde**, yeni *path operation*'lar method olarak eklenir, eskileri kaldırılır ve diğer değişiklikler de üretilen koda yansır. 🤓 + +Bu, bir şey değiştiğinde client code'a otomatik olarak **yansıyacağı** anlamına gelir. Ayrıca client'ı **build** ettiğinizde, kullanılan verilerde bir **uyuşmazlık** (mismatch) varsa hata alırsınız. + +Böylece üretimde son kullanıcılara hata yansımasını beklemek ve sonra sorunun nerede olduğunu debug etmeye çalışmak yerine, geliştirme sürecinin çok erken aşamalarında **birçok hatayı tespit edersiniz**. ✨ diff --git a/docs/tr/docs/advanced/middleware.md b/docs/tr/docs/advanced/middleware.md new file mode 100644 index 000000000..a22644a09 --- /dev/null +++ b/docs/tr/docs/advanced/middleware.md @@ -0,0 +1,97 @@ +# İleri Seviye Middleware { #advanced-middleware } + +Ana tutorial'da uygulamanıza [Özel Middleware](../tutorial/middleware.md){.internal-link target=_blank} eklemeyi gördünüz. + +Ardından [`CORSMiddleware` ile CORS'u yönetmeyi](../tutorial/cors.md){.internal-link target=_blank} de okudunuz. + +Bu bölümde diğer middleware'leri nasıl kullanacağımıza bakacağız. + +## ASGI middleware'leri ekleme { #adding-asgi-middlewares } + +**FastAPI**, Starlette üzerine kurulu olduğu ve ASGI spesifikasyonunu uyguladığı için, herhangi bir ASGI middleware'ini kullanabilirsiniz. + +Bir middleware'in çalışması için özellikle FastAPI ya da Starlette için yazılmış olması gerekmez; ASGI spec'ine uyduğu sürece yeterlidir. + +Genel olarak ASGI middleware'leri, ilk argüman olarak bir ASGI app almayı bekleyen class'lar olur. + +Dolayısıyla üçüncü taraf ASGI middleware'lerinin dokümantasyonunda muhtemelen şöyle bir şey yapmanızı söylerler: + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +Ancak FastAPI (aslında Starlette) bunu yapmanın daha basit bir yolunu sunar; böylece dahili middleware'ler server hatalarını doğru şekilde ele alır ve özel exception handler'lar düzgün çalışır. + +Bunun için `app.add_middleware()` kullanırsınız (CORS örneğindeki gibi). + +```Python +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +`app.add_middleware()` ilk argüman olarak bir middleware class'ı alır ve middleware'e aktarılacak ek argümanları da kabul eder. + +## Entegre middleware'ler { #integrated-middlewares } + +**FastAPI**, yaygın kullanım senaryoları için birkaç middleware içerir; şimdi bunları nasıl kullanacağımıza bakacağız. + +/// note | Teknik Detaylar + +Bir sonraki örneklerde `from starlette.middleware.something import SomethingMiddleware` kullanmanız da mümkündür. + +**FastAPI**, size (geliştirici olarak) kolaylık olsun diye `fastapi.middleware` içinde bazı middleware'leri sağlar. Ancak mevcut middleware'lerin çoğu doğrudan Starlette'ten gelir. + +/// + +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } + +Gelen tüm request'lerin `https` veya `wss` olmasını zorunlu kılar. + +`http` veya `ws` olarak gelen herhangi bir request, bunun yerine güvenli şemaya redirect edilir. + +{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} + +## `TrustedHostMiddleware` { #trustedhostmiddleware } + +HTTP Host Header saldırılarına karşı korunmak için, gelen tüm request'lerde `Host` header'ının doğru ayarlanmış olmasını zorunlu kılar. + +{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} + +Aşağıdaki argümanlar desteklenir: + +* `allowed_hosts` - Hostname olarak izin verilmesi gereken domain adlarının listesi. `*.example.com` gibi wildcard domain'ler subdomain eşleştirmesi için desteklenir. Herhangi bir hostname'e izin vermek için `allowed_hosts=["*"]` kullanın veya middleware'i hiç eklemeyin. +* `www_redirect` - True olarak ayarlanırsa, izin verilen host'ların www olmayan sürümlerine gelen request'ler www sürümlerine redirect edilir. Varsayılanı `True`'dur. + +Gelen bir request doğru şekilde doğrulanmazsa `400` response gönderilir. + +## `GZipMiddleware` { #gzipmiddleware } + +`Accept-Encoding` header'ında `"gzip"` içeren herhangi bir request için GZip response'larını yönetir. + +Middleware hem standart hem de streaming response'ları ele alır. + +{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} + +Aşağıdaki argümanlar desteklenir: + +* `minimum_size` - Bayt cinsinden bu minimum boyuttan küçük response'lara GZip uygulama. Varsayılanı `500`'dür. +* `compresslevel` - GZip sıkıştırması sırasında kullanılır. 1 ile 9 arasında bir tamsayıdır. Varsayılanı `9`'dur. Daha düşük değer daha hızlı sıkıştırma ama daha büyük dosya boyutları üretir; daha yüksek değer daha yavaş sıkıştırma ama daha küçük dosya boyutları üretir. + +## Diğer middleware'ler { #other-middlewares } + +Başka birçok ASGI middleware'i vardır. + +Örneğin: + +* Uvicorn'un `ProxyHeadersMiddleware`'i +* MessagePack + +Diğer mevcut middleware'leri görmek için Starlette'in Middleware dokümanlarına ve ASGI Awesome List listesine bakın. diff --git a/docs/tr/docs/advanced/openapi-callbacks.md b/docs/tr/docs/advanced/openapi-callbacks.md new file mode 100644 index 000000000..61135b7e0 --- /dev/null +++ b/docs/tr/docs/advanced/openapi-callbacks.md @@ -0,0 +1,186 @@ +# OpenAPI Callback'leri { #openapi-callbacks } + +Başka biri tarafından (muhtemelen API'nizi *kullanacak* olan aynı geliştirici tarafından) oluşturulmuş bir *external API*'ye request tetikleyebilen bir *path operation* ile bir API oluşturabilirsiniz. + +API uygulamanızın *external API*'yi çağırdığı sırada gerçekleşen sürece "callback" denir. Çünkü dış geliştiricinin yazdığı yazılım API'nize bir request gönderir ve ardından API'niz *geri çağrı* yaparak (*call back*), bir *external API*'ye request gönderir (muhtemelen aynı geliştiricinin oluşturduğu). + +Bu durumda, o external API'nin nasıl görünmesi *gerektiğini* dokümante etmek isteyebilirsiniz. Hangi *path operation*'a sahip olmalı, hangi body'yi beklemeli, hangi response'u döndürmeli, vb. + +## Callback'leri olan bir uygulama { #an-app-with-callbacks } + +Bunların hepsine bir örnekle bakalım. + +Fatura oluşturmayı sağlayan bir uygulama geliştirdiğinizi düşünün. + +Bu faturaların `id`, `title` (opsiyonel), `customer` ve `total` alanları olacak. + +API'nizin kullanıcısı (external bir geliştirici) API'nizde bir POST request ile fatura oluşturacak. + +Sonra API'niz (varsayalım ki): + +* Faturayı external geliştiricinin bir müşterisine gönderir. +* Parayı tahsil eder. +* API kullanıcısına (external geliştiriciye) tekrar bir bildirim gönderir. + * Bu, external geliştiricinin sağladığı bir *external API*'ye (*sizin API'nizden*) bir POST request gönderilerek yapılır (işte bu "callback"tir). + +## Normal **FastAPI** uygulaması { #the-normal-fastapi-app } + +Önce callback eklemeden önce normal API uygulamasının nasıl görüneceğine bakalım. + +Bir `Invoice` body alacak bir *path operation*'ı ve callback için URL'yi taşıyacak `callback_url` adlı bir query parametresi olacak. + +Bu kısım oldukça standart; kodun çoğu muhtemelen size zaten tanıdık gelecektir: + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} + +/// tip | İpucu + +`callback_url` query parametresi, Pydantic'in Url tipini kullanır. + +/// + +Tek yeni şey, *path operation decorator*'ına argüman olarak verilen `callbacks=invoices_callback_router.routes`. Bunun ne olduğuna şimdi bakacağız. + +## Callback'i dokümante etmek { #documenting-the-callback } + +Callback'in gerçek kodu, büyük ölçüde sizin API uygulamanıza bağlıdır. + +Ve bir uygulamadan diğerine oldukça değişebilir. + +Sadece bir-iki satır kod bile olabilir, örneğin: + +```Python +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +Ancak callback'in belki de en önemli kısmı, API'nizin kullanıcısının (external geliştiricinin) *external API*'yi doğru şekilde uyguladığından emin olmaktır; çünkü *sizin API'niz* callback'in request body'sinde belirli veriler gönderecektir, vb. + +Dolayısıyla sıradaki adım olarak, *sizin API'nizden* callback almak için o *external API*'nin nasıl görünmesi gerektiğini dokümante eden kodu ekleyeceğiz. + +Bu dokümantasyon, API'nizde `/docs` altındaki Swagger UI'da görünecek ve external geliştiricilere *external API*'yi nasıl inşa edeceklerini gösterecek. + +Bu örnek callback'in kendisini implemente etmiyor (o zaten tek satır kod olabilir), sadece dokümantasyon kısmını ekliyor. + +/// tip | İpucu + +Gerçek callback, sadece bir HTTP request'tir. + +Callback'i kendiniz implemente ederken HTTPX veya Requests gibi bir şey kullanabilirsiniz. + +/// + +## Callback dokümantasyon kodunu yazın { #write-the-callback-documentation-code } + +Bu kod uygulamanızda çalıştırılmayacak; sadece o *external API*'nin nasıl görünmesi gerektiğini *dokümante etmek* için gerekiyor. + +Ancak **FastAPI** ile bir API için otomatik dokümantasyonu kolayca nasıl üreteceğinizi zaten biliyorsunuz. + +O halde aynı bilgiyi kullanarak, *external API*'nin nasıl görünmesi gerektiğini dokümante edeceğiz... external API'nin implemente etmesi gereken *path operation*'ları oluşturarak (API'nizin çağıracağı olanlar). + +/// tip | İpucu + +Bir callback'i dokümante eden kodu yazarken, kendinizi *external geliştirici* olarak hayal etmek faydalı olabilir. Ve şu anda *sizin API'nizi* değil, *external API*'yi implemente ettiğinizi düşünün. + +Bu bakış açısını (external geliştiricinin bakış açısını) geçici olarak benimsemek; parametreleri nereye koyacağınızı, body için Pydantic modelini, response için modelini vb. external API tarafında nasıl tasarlayacağınızı daha net hale getirebilir. + +/// + +### Bir callback `APIRouter` oluşturun { #create-a-callback-apirouter } + +Önce bir veya daha fazla callback içerecek yeni bir `APIRouter` oluşturun. + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *} + +### Callback *path operation*'ını oluşturun { #create-the-callback-path-operation } + +Callback *path operation*'ını oluşturmak için, yukarıda oluşturduğunuz aynı `APIRouter`'ı kullanın. + +Normal bir FastAPI *path operation*'ı gibi görünmelidir: + +* Muhtemelen alması gereken body'nin bir deklarasyonu olmalı, örn. `body: InvoiceEvent`. +* Ayrıca döndürmesi gereken response'un deklarasyonu da olabilir, örn. `response_model=InvoiceEventReceived`. + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} + +Normal bir *path operation*'dan 2 temel farkı vardır: + +* Gerçek bir koda ihtiyaç duymaz; çünkü uygulamanız bu kodu asla çağırmayacak. Bu yalnızca *external API*'yi dokümante etmek için kullanılır. Yani fonksiyon sadece `pass` içerebilir. +* *path*, bir OpenAPI 3 expression (aşağıda daha fazlası) içerebilir; böylece parametreler ve *sizin API'nize* gönderilen orijinal request'in bazı parçalarıyla değişkenler kullanılabilir. + +### Callback path ifadesi { #the-callback-path-expression } + +Callback *path*'i, *sizin API'nize* gönderilen orijinal request'in bazı parçalarını içerebilen bir OpenAPI 3 expression barındırabilir. + +Bu örnekte, bu bir `str`: + +```Python +"{$callback_url}/invoices/{$request.body.id}" +``` + +Yani API'nizin kullanıcısı (external geliştirici) *sizin API'nize* şu adrese bir request gönderirse: + +``` +https://yourapi.com/invoices/?callback_url=https://www.external.org/events +``` + +ve JSON body şu şekilde olursa: + +```JSON +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +o zaman *sizin API'niz* faturayı işleyecek ve daha sonra bir noktada `callback_url`'ye (yani *external API*'ye) bir callback request gönderecek: + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +ve JSON body yaklaşık şöyle bir şey içerecek: + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +ve o *external API*'den şu gibi bir JSON body içeren response bekleyecek: + +```JSON +{ + "ok": true +} +``` + +/// tip | İpucu + +Callback URL'sinin, `callback_url` içindeki query parametresi olarak alınan URL'yi (`https://www.external.org/events`) ve ayrıca JSON body'nin içindeki fatura `id`'sini (`2expen51ve`) birlikte kullandığına dikkat edin. + +/// + +### Callback router'ını ekleyin { #add-the-callback-router } + +Bu noktada, yukarıda oluşturduğunuz callback router'ında gerekli callback *path operation*'ları (external geliştiricinin *external API*'de implemente etmesi gerekenler) hazır. + +Şimdi *sizin API'nizin path operation decorator*'ında `callbacks` parametresini kullanarak, callback router'ının `.routes` attribute'unu (bu aslında route/*path operation*'lardan oluşan bir `list`) geçin: + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} + +/// tip | İpucu + +`callback=` içine router'ın kendisini (`invoices_callback_router`) değil, `invoices_callback_router.routes` şeklinde `.routes` attribute'unu verdiğinize dikkat edin. + +/// + +### Dokümanları kontrol edin { #check-the-docs } + +Artık uygulamanızı başlatıp http://127.0.0.1:8000/docs adresine gidebilirsiniz. + +*Path operation*'ınız için, *external API*'nin nasıl görünmesi gerektiğini gösteren bir "Callbacks" bölümünü içeren dokümanları göreceksiniz: + + diff --git a/docs/tr/docs/advanced/openapi-webhooks.md b/docs/tr/docs/advanced/openapi-webhooks.md new file mode 100644 index 000000000..dd9e9bbe7 --- /dev/null +++ b/docs/tr/docs/advanced/openapi-webhooks.md @@ -0,0 +1,55 @@ +# OpenAPI Webhook'lar { #openapi-webhooks } + +Bazı durumlarda, API'nizi kullanan **kullanıcılara** uygulamanızın *onların* uygulamasını (request göndererek) bazı verilerle çağırabileceğini; genellikle bir tür **event** hakkında **bildirim** yapmak için kullanacağını söylemek istersiniz. + +Bu da şunu ifade eder: Kullanıcılarınızın API'nize request göndermesi şeklindeki normal akış yerine, request'i **sizin API'niz** (veya uygulamanız) **onların sistemine** (onların API'sine, onların uygulamasına) **gönderebilir**. + +Buna genellikle **webhook** denir. + +## Webhook adımları { #webhooks-steps } + +Süreç genellikle şöyledir: Kodunuzda göndereceğiniz mesajın ne olduğunu, yani request'in **body**'sini **siz tanımlarsınız**. + +Ayrıca uygulamanızın bu request'leri veya event'leri hangi **anlarda** göndereceğini de bir şekilde tanımlarsınız. + +Ve **kullanıcılarınız** da bir şekilde (örneğin bir web dashboard üzerinden) uygulamanızın bu request'leri göndermesi gereken **URL**'yi tanımlar. + +Webhook'lar için URL'lerin nasıl kaydedileceğine dair tüm **mantık** ve bu request'leri gerçekten gönderen kod tamamen size bağlıdır. Bunu **kendi kodunuzda** istediğiniz gibi yazarsınız. + +## **FastAPI** ve OpenAPI ile webhook'ları dokümante etmek { #documenting-webhooks-with-fastapi-and-openapi } + +**FastAPI** ile OpenAPI kullanarak bu webhook'ların adlarını, uygulamanızın gönderebileceği HTTP operation türlerini (örn. `POST`, `PUT`, vb.) ve uygulamanızın göndereceği request **body**'lerini tanımlayabilirsiniz. + +Bu, kullanıcılarınızın **webhook** request'lerinizi alacak şekilde **API'lerini implement etmesini** çok daha kolaylaştırabilir; hatta kendi API kodlarının bir kısmını otomatik üretebilirler. + +/// info | Bilgi + +Webhook'lar OpenAPI 3.1.0 ve üzeri sürümlerde mevcuttur; FastAPI `0.99.0` ve üzeri tarafından desteklenir. + +/// + +## Webhook'ları olan bir uygulama { #an-app-with-webhooks } + +Bir **FastAPI** uygulaması oluşturduğunuzda, *webhook*'ları tanımlamak için kullanabileceğiniz bir `webhooks` attribute'u vardır; *path operation* tanımlar gibi, örneğin `@app.webhooks.post()` ile. + +{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} + +Tanımladığınız webhook'lar **OpenAPI** şemasında ve otomatik **docs UI**'da yer alır. + +/// info | Bilgi + +`app.webhooks` nesnesi aslında sadece bir `APIRouter`'dır; uygulamanızı birden fazla dosya ile yapılandırırken kullanacağınız türün aynısıdır. + +/// + +Dikkat edin: Webhook'larda aslında bir *path* (ör. `/items/`) deklare etmiyorsunuz; oraya verdiğiniz metin sadece webhook'un bir **identifier**'ıdır (event'in adı). Örneğin `@app.webhooks.post("new-subscription")` içinde webhook adı `new-subscription`'dır. + +Bunun nedeni, webhook request'ini almak istedikleri gerçek **URL path**'i **kullanıcılarınızın** başka bir şekilde (örn. bir web dashboard üzerinden) tanımlamasının beklenmesidir. + +### Dokümanları kontrol edin { #check-the-docs } + +Şimdi uygulamanızı başlatıp http://127.0.0.1:8000/docs adresine gidin. + +Dokümanlarınızda normal *path operation*'ları ve artık bazı **webhook**'ları da göreceksiniz: + + diff --git a/docs/tr/docs/advanced/path-operation-advanced-configuration.md b/docs/tr/docs/advanced/path-operation-advanced-configuration.md new file mode 100644 index 000000000..e326842d6 --- /dev/null +++ b/docs/tr/docs/advanced/path-operation-advanced-configuration.md @@ -0,0 +1,172 @@ +# Path Operation İleri Düzey Yapılandırma { #path-operation-advanced-configuration } + +## OpenAPI operationId { #openapi-operationid } + +/// warning | Uyarı + +OpenAPI konusunda "uzman" değilseniz, muhtemelen buna ihtiyacınız yok. + +/// + +*path operation*’ınızda kullanılacak OpenAPI `operationId` değerini `operation_id` parametresiyle ayarlayabilirsiniz. + +Bunun her operation için benzersiz olduğundan emin olmanız gerekir. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} + +### operationId olarak *path operation function* adını kullanma { #using-the-path-operation-function-name-as-the-operationid } + +API’lerinizin function adlarını `operationId` olarak kullanmak istiyorsanız, hepsini dolaşıp her *path operation*’ın `operation_id` değerini `APIRoute.name` ile override edebilirsiniz. + +Bunu, tüm *path operation*’ları ekledikten sonra yapmalısınız. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} + +/// tip | İpucu + +`app.openapi()` fonksiyonunu manuel olarak çağırıyorsanız, bunu yapmadan önce `operationId`’leri güncellemelisiniz. + +/// + +/// warning | Uyarı + +Bunu yaparsanız, her bir *path operation function*’ın adının benzersiz olduğundan emin olmanız gerekir. + +Farklı modüllerde (Python dosyalarında) olsalar bile. + +/// + +## OpenAPI’den Hariç Tutma { #exclude-from-openapi } + +Bir *path operation*’ı üretilen OpenAPI şemasından (dolayısıyla otomatik dokümantasyon sistemlerinden) hariç tutmak için `include_in_schema` parametresini kullanın ve `False` yapın: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} + +## Docstring’den İleri Düzey Açıklama { #advanced-description-from-docstring } + +OpenAPI için, bir *path operation function*’ın docstring’inden kullanılacak satırları sınırlandırabilirsiniz. + +Bir `\f` (escape edilmiş "form feed" karakteri) eklerseniz, **FastAPI** OpenAPI için kullanılan çıktıyı bu noktada **keser**. + +Dokümantasyonda görünmez, ancak diğer araçlar (Sphinx gibi) geri kalan kısmı kullanabilir. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} + +## Ek Responses { #additional-responses } + +Muhtemelen bir *path operation* için `response_model` ve `status_code` tanımlamayı görmüşsünüzdür. + +Bu, bir *path operation*’ın ana response’u ile ilgili metadata’yı tanımlar. + +Ek response’ları; modelleri, status code’ları vb. ile birlikte ayrıca da tanımlayabilirsiniz. + +Dokümantasyonda bununla ilgili ayrı bir bölüm var; [OpenAPI’de Ek Responses](additional-responses.md){.internal-link target=_blank} sayfasından okuyabilirsiniz. + +## OpenAPI Extra { #openapi-extra } + +Uygulamanızda bir *path operation* tanımladığınızda, **FastAPI** OpenAPI şemasına dahil edilmek üzere o *path operation* ile ilgili metadata’yı otomatik olarak üretir. + +/// note | Teknik Detaylar + +OpenAPI spesifikasyonunda buna Operation Object denir. + +/// + +Bu, *path operation* hakkında tüm bilgileri içerir ve otomatik dokümantasyonu üretmek için kullanılır. + +`tags`, `parameters`, `requestBody`, `responses` vb. alanları içerir. + +Bu *path operation*’a özel OpenAPI şeması normalde **FastAPI** tarafından otomatik üretilir; ancak siz bunu genişletebilirsiniz. + +/// tip | İpucu + +Bu, düşük seviyeli bir genişletme noktasıdır. + +Yalnızca ek response’lar tanımlamanız gerekiyorsa, bunu yapmanın daha pratik yolu [OpenAPI’de Ek Responses](additional-responses.md){.internal-link target=_blank} kullanmaktır. + +/// + +Bir *path operation* için OpenAPI şemasını `openapi_extra` parametresiyle genişletebilirsiniz. + +### OpenAPI Extensions { #openapi-extensions } + +Örneğin bu `openapi_extra`, [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) tanımlamak için faydalı olabilir: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} + +Otomatik API dokümanlarını açtığınızda, extension’ınız ilgili *path operation*’ın en altında görünür. + + + +Ayrıca ortaya çıkan OpenAPI’yi (API’nizde `/openapi.json`) görüntülerseniz, extension’ınızı ilgili *path operation*’ın bir parçası olarak orada da görürsünüz: + +```JSON hl_lines="22" +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "x-aperture-labs-portal": "blue" + } + } + } +} +``` + +### Özel OpenAPI *path operation* şeması { #custom-openapi-path-operation-schema } + +`openapi_extra` içindeki dictionary, *path operation* için otomatik üretilen OpenAPI şemasıyla derinlemesine (deep) birleştirilir. + +Böylece otomatik üretilen şemaya ek veri ekleyebilirsiniz. + +Örneğin, Pydantic ile FastAPI’nin otomatik özelliklerini kullanmadan request’i kendi kodunuzla okuyup doğrulamaya karar verebilirsiniz; ancak yine de OpenAPI şemasında request’i tanımlamak isteyebilirsiniz. + +Bunu `openapi_extra` ile yapabilirsiniz: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} + +Bu örnekte herhangi bir Pydantic model tanımlamadık. Hatta request body JSON olarak parsed bile edilmiyor; doğrudan `bytes` olarak okunuyor ve `magic_data_reader()` fonksiyonu bunu bir şekilde parse etmekten sorumlu oluyor. + +Buna rağmen, request body için beklenen şemayı tanımlayabiliriz. + +### Özel OpenAPI content type { #custom-openapi-content-type } + +Aynı yöntemi kullanarak, Pydantic model ile JSON Schema’yı tanımlayıp bunu *path operation* için özel OpenAPI şeması bölümüne dahil edebilirsiniz. + +Ve bunu, request içindeki veri tipi JSON olmasa bile yapabilirsiniz. + +Örneğin bu uygulamada, FastAPI’nin Pydantic modellerinden JSON Schema çıkarmaya yönelik entegre işlevselliğini ve JSON için otomatik doğrulamayı kullanmıyoruz. Hatta request content type’ını JSON değil, YAML olarak tanımlıyoruz: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} + +Buna rağmen, varsayılan entegre işlevselliği kullanmasak da, YAML olarak almak istediğimiz veri için JSON Schema’yı manuel üretmek üzere bir Pydantic model kullanmaya devam ediyoruz. + +Ardından request’i doğrudan kullanıp body’yi `bytes` olarak çıkarıyoruz. Bu da FastAPI’nin request payload’ını JSON olarak parse etmeye çalışmayacağı anlamına gelir. + +Sonrasında kodumuzda bu YAML içeriğini doğrudan parse ediyor, ardından YAML içeriğini doğrulamak için yine aynı Pydantic modeli kullanıyoruz: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} + +/// tip | İpucu + +Burada aynı Pydantic modeli tekrar kullanıyoruz. + +Aynı şekilde, başka bir yöntemle de doğrulama yapabilirdik. + +/// diff --git a/docs/tr/docs/advanced/response-change-status-code.md b/docs/tr/docs/advanced/response-change-status-code.md new file mode 100644 index 000000000..239c0dddd --- /dev/null +++ b/docs/tr/docs/advanced/response-change-status-code.md @@ -0,0 +1,31 @@ +# Response - Status Code Değiştirme { #response-change-status-code } + +Muhtemelen daha önce varsayılan bir [Response Status Code](../tutorial/response-status-code.md){.internal-link target=_blank} ayarlayabileceğinizi okumuşsunuzdur. + +Ancak bazı durumlarda, varsayılandan farklı bir status code döndürmeniz gerekir. + +## Kullanım senaryosu { #use-case } + +Örneğin, varsayılan olarak "OK" `200` HTTP status code'u döndürmek istediğinizi düşünün. + +Ama veri mevcut değilse onu oluşturmak ve "CREATED" `201` HTTP status code'u döndürmek istiyorsunuz. + +Aynı zamanda, döndürdüğünüz veriyi bir `response_model` ile filtreleyip dönüştürebilmeyi de sürdürmek istiyorsunuz. + +Bu tür durumlarda bir `Response` parametresi kullanabilirsiniz. + +## Bir `Response` parametresi kullanın { #use-a-response-parameter } + +*Path operation function* içinde `Response` tipinde bir parametre tanımlayabilirsiniz (cookie ve header'lar için yapabildiğiniz gibi). + +Ardından bu *geçici (temporal)* `Response` nesnesi üzerinde `status_code` değerini ayarlayabilirsiniz. + +{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} + +Sonrasında, normalde yaptığınız gibi ihtiyacınız olan herhangi bir nesneyi döndürebilirsiniz (`dict`, bir veritabanı modeli, vb.). + +Ve eğer bir `response_model` tanımladıysanız, döndürdüğünüz nesneyi filtrelemek ve dönüştürmek için yine kullanılacaktır. + +**FastAPI**, status code'u (ayrıca cookie ve header'ları) bu *geçici (temporal)* response'tan alır ve `response_model` ile filtrelenmiş, sizin döndürdüğünüz değeri içeren nihai response'a yerleştirir. + +Ayrıca `Response` parametresini dependency'lerde de tanımlayıp status code'u orada ayarlayabilirsiniz. Ancak unutmayın, en son ayarlanan değer geçerli olur. diff --git a/docs/tr/docs/advanced/response-cookies.md b/docs/tr/docs/advanced/response-cookies.md new file mode 100644 index 000000000..d00bfc4cd --- /dev/null +++ b/docs/tr/docs/advanced/response-cookies.md @@ -0,0 +1,51 @@ +# Response Cookie'leri { #response-cookies } + +## Bir `Response` parametresi kullanın { #use-a-response-parameter } + +*Path operation function* içinde `Response` tipinde bir parametre tanımlayabilirsiniz. + +Ardından bu *geçici* response nesnesi üzerinde cookie'leri set edebilirsiniz. + +{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} + +Sonrasında normalde yaptığınız gibi ihtiyaç duyduğunuz herhangi bir nesneyi döndürebilirsiniz (bir `dict`, bir veritabanı modeli vb.). + +Ayrıca bir `response_model` tanımladıysanız, döndürdüğünüz nesneyi filtrelemek ve dönüştürmek için yine kullanılacaktır. + +**FastAPI**, bu *geçici* response'u cookie'leri (ayrıca header'ları ve status code'u) çıkarmak için kullanır ve bunları, döndürdüğünüz değeri içeren nihai response'a ekler. Döndürdüğünüz değer, varsa `response_model` ile filtrelenmiş olur. + +`Response` parametresini dependency'lerde de tanımlayıp, onların içinde cookie (ve header) set edebilirsiniz. + +## Doğrudan bir `Response` döndürün { #return-a-response-directly } + +Kodunuzda doğrudan bir `Response` döndürürken de cookie oluşturabilirsiniz. + +Bunu yapmak için, [Doğrudan Response Döndürme](response-directly.md){.internal-link target=_blank} bölümünde anlatıldığı gibi bir response oluşturabilirsiniz. + +Sonra bunun içinde Cookie'leri set edin ve response'u döndürün: + +{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} + +/// tip | İpucu + +`Response` parametresini kullanmak yerine doğrudan bir response döndürürseniz, FastAPI onu olduğu gibi (doğrudan) döndürür. + +Bu yüzden, verinizin doğru tipte olduğundan emin olmanız gerekir. Örneğin `JSONResponse` döndürüyorsanız, verinin JSON ile uyumlu olması gerekir. + +Ayrıca `response_model` tarafından filtrelenmesi gereken bir veriyi göndermediğinizden de emin olun. + +/// + +### Daha fazla bilgi { #more-info } + +/// note | Teknik Detaylar + +`from starlette.responses import Response` veya `from starlette.responses import JSONResponse` da kullanabilirsiniz. + +**FastAPI**, geliştirici olarak size kolaylık olması için `fastapi.responses` içinde `starlette.responses` ile aynı response sınıflarını sunar. Ancak mevcut response'ların büyük kısmı doğrudan Starlette'ten gelir. + +Ve `Response`, header ve cookie set etmek için sık kullanıldığından, **FastAPI** bunu `fastapi.Response` olarak da sağlar. + +/// + +Mevcut tüm parametreleri ve seçenekleri görmek için Starlette dokümantasyonuna bakın. diff --git a/docs/tr/docs/advanced/response-directly.md b/docs/tr/docs/advanced/response-directly.md new file mode 100644 index 000000000..332f1224f --- /dev/null +++ b/docs/tr/docs/advanced/response-directly.md @@ -0,0 +1,65 @@ +# Doğrudan Bir Response Döndürme { #return-a-response-directly } + +**FastAPI** ile bir *path operation* oluşturduğunuzda, normalde ondan herhangi bir veri döndürebilirsiniz: bir `dict`, bir `list`, bir Pydantic model, bir veritabanı modeli vb. + +Varsayılan olarak **FastAPI**, döndürdüğünüz bu değeri [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} bölümünde anlatılan `jsonable_encoder` ile otomatik olarak JSON'a çevirir. + +Ardından perde arkasında, JSON-uyumlu bu veriyi (ör. bir `dict`) client'a response göndermek için kullanılacak bir `JSONResponse` içine yerleştirir. + +Ancak *path operation*'larınızdan doğrudan bir `JSONResponse` döndürebilirsiniz. + +Bu, örneğin özel header'lar veya cookie'ler döndürmek istediğinizde faydalı olabilir. + +## Bir `Response` Döndürme { #return-a-response } + +Aslında herhangi bir `Response` veya onun herhangi bir alt sınıfını döndürebilirsiniz. + +/// tip | İpucu + +`JSONResponse` zaten `Response`'un bir alt sınıfıdır. + +/// + +Bir `Response` döndürdüğünüzde, **FastAPI** bunu olduğu gibi doğrudan iletir. + +Pydantic model'leriyle herhangi bir veri dönüşümü yapmaz, içeriği başka bir tipe çevirmez vb. + +Bu size ciddi bir esneklik sağlar. Herhangi bir veri türü döndürebilir, herhangi bir veri deklarasyonunu veya validasyonunu override edebilirsiniz. + +## Bir `Response` İçinde `jsonable_encoder` Kullanma { #using-the-jsonable-encoder-in-a-response } + +**FastAPI**, sizin döndürdüğünüz `Response` üzerinde hiçbir değişiklik yapmadığı için, içeriğinin gönderilmeye hazır olduğundan emin olmanız gerekir. + +Örneğin, bir Pydantic model'i, önce JSON-uyumlu tiplere çevrilmeden (`datetime`, `UUID` vb.) doğrudan bir `JSONResponse` içine koyamazsınız. Önce tüm veri tipleri JSON-uyumlu hale gelecek şekilde `dict`'e çevrilmesi gerekir. + +Bu gibi durumlarda, response'a vermeden önce verinizi dönüştürmek için `jsonable_encoder` kullanabilirsiniz: + +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} + +/// note | Teknik Detaylar + +`from starlette.responses import JSONResponse` da kullanabilirsiniz. + +**FastAPI**, geliştirici olarak size kolaylık olması için `starlette.responses` içeriğini `fastapi.responses` üzerinden de sunar. Ancak mevcut response'ların çoğu doğrudan Starlette'tan gelir. + +/// + +## Özel Bir `Response` Döndürme { #returning-a-custom-response } + +Yukarıdaki örnek ihtiyaç duyduğunuz tüm parçaları gösteriyor, ancak henüz çok kullanışlı değil. Çünkü `item`'ı zaten doğrudan döndürebilirdiniz ve **FastAPI** varsayılan olarak onu sizin için bir `JSONResponse` içine koyup `dict`'e çevirirdi vb. + +Şimdi bunu kullanarak nasıl özel bir response döndürebileceğinize bakalım. + +Diyelim ki XML response döndürmek istiyorsunuz. + +XML içeriğinizi bir string içine koyabilir, onu bir `Response` içine yerleştirip döndürebilirsiniz: + +{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} + +## Notlar { #notes } + +Bir `Response`'u doğrudan döndürdüğünüzde, verisi otomatik olarak validate edilmez, dönüştürülmez (serialize edilmez) veya dokümante edilmez. + +Ancak yine de [Additional Responses in OpenAPI](additional-responses.md){.internal-link target=_blank} bölümünde anlatıldığı şekilde dokümante edebilirsiniz. + +İlerleyen bölümlerde, otomatik veri dönüşümü, dokümantasyon vb. özellikleri korurken bu özel `Response`'ları nasıl kullanıp declare edebileceğinizi göreceksiniz. diff --git a/docs/tr/docs/advanced/response-headers.md b/docs/tr/docs/advanced/response-headers.md new file mode 100644 index 000000000..85b0799d3 --- /dev/null +++ b/docs/tr/docs/advanced/response-headers.md @@ -0,0 +1,41 @@ +# Response Header'ları { #response-headers } + +## Bir `Response` parametresi kullanın { #use-a-response-parameter } + +*Path operation function* içinde (cookie'lerde yapabildiğiniz gibi) tipi `Response` olan bir parametre tanımlayabilirsiniz. + +Sonra da bu *geçici* response nesnesi üzerinde header'ları ayarlayabilirsiniz. + +{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *} + +Ardından normalde yaptığınız gibi ihtiyacınız olan herhangi bir nesneyi döndürebilirsiniz (bir `dict`, bir veritabanı modeli vb.). + +Eğer bir `response_model` tanımladıysanız, döndürdüğünüz nesneyi filtrelemek ve dönüştürmek için yine kullanılacaktır. + +**FastAPI**, header'ları (aynı şekilde cookie'leri ve status code'u) bu *geçici* response'dan alır ve döndürdüğünüz değeri (varsa bir `response_model` ile filtrelenmiş hâliyle) içeren nihai response'a ekler. + +`Response` parametresini dependency'lerde de tanımlayıp, onların içinde header (ve cookie) ayarlayabilirsiniz. + +## Doğrudan bir `Response` döndürün { #return-a-response-directly } + +Doğrudan bir `Response` döndürdüğünüzde de header ekleyebilirsiniz. + +[Bir Response'u Doğrudan Döndürün](response-directly.md){.internal-link target=_blank} bölümünde anlatıldığı gibi bir response oluşturun ve header'ları ek bir parametre olarak geçin: + +{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *} + +/// note | Teknik Detaylar + +`from starlette.responses import Response` veya `from starlette.responses import JSONResponse` da kullanabilirsiniz. + +**FastAPI**, geliştirici olarak size kolaylık olsun diye `starlette.responses` içeriğini `fastapi.responses` olarak da sunar. Ancak mevcut response'ların çoğu doğrudan Starlette'ten gelir. + +Ayrıca `Response` header ve cookie ayarlamak için sık kullanıldığından, **FastAPI** bunu `fastapi.Response` altında da sağlar. + +/// + +## Özel Header'lar { #custom-headers } + +Özel/proprietary header'ların `X-` prefix'i kullanılarak eklenebileceğini unutmayın. + +Ancak tarayıcıdaki bir client'ın görebilmesini istediğiniz özel header'larınız varsa, bunları CORS ayarlarınıza eklemeniz gerekir ([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank} bölümünde daha fazla bilgi), bunun için Starlette'in CORS dokümanında açıklanan `expose_headers` parametresini kullanın. diff --git a/docs/tr/docs/advanced/security/http-basic-auth.md b/docs/tr/docs/advanced/security/http-basic-auth.md new file mode 100644 index 000000000..b194c763e --- /dev/null +++ b/docs/tr/docs/advanced/security/http-basic-auth.md @@ -0,0 +1,107 @@ +# HTTP Basic Auth { #http-basic-auth } + +En basit senaryolarda HTTP Basic Auth kullanabilirsiniz. + +HTTP Basic Auth’ta uygulama, içinde kullanıcı adı ve şifre bulunan bir header bekler. + +Eğer bunu almazsa HTTP 401 "Unauthorized" hatası döndürür. + +Ayrıca değeri `Basic` olan ve isteğe bağlı `realm` parametresi içerebilen `WWW-Authenticate` header’ını da döndürür. + +Bu da tarayıcıya, kullanıcı adı ve şifre için entegre giriş penceresini göstermesini söyler. + +Ardından kullanıcı adı ve şifreyi yazdığınızda tarayıcı bunları otomatik olarak header içinde gönderir. + +## Basit HTTP Basic Auth { #simple-http-basic-auth } + +* `HTTPBasic` ve `HTTPBasicCredentials` import edin. +* `HTTPBasic` kullanarak bir "`security` scheme" oluşturun. +* *path operation*’ınızda bir dependency ile bu `security`’yi kullanın. +* Bu, `HTTPBasicCredentials` tipinde bir nesne döndürür: + * İçinde gönderilen `username` ve `password` bulunur. + +{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} + +URL’yi ilk kez açmaya çalıştığınızda (veya dokümanlardaki "Execute" butonuna tıkladığınızda) tarayıcı sizden kullanıcı adınızı ve şifrenizi ister: + + + +## Kullanıcı adını kontrol edin { #check-the-username } + +Daha kapsamlı bir örneğe bakalım. + +Kullanıcı adı ve şifrenin doğru olup olmadığını kontrol etmek için bir dependency kullanın. + +Bunun için kullanıcı adı ve şifreyi kontrol ederken Python standart modülü olan `secrets`’i kullanın. + +`secrets.compare_digest()`; `bytes` ya da yalnızca ASCII karakterleri (İngilizce’deki karakterler) içeren bir `str` almalıdır. Bu da `Sebastián` içindeki `á` gibi karakterlerle çalışmayacağı anlamına gelir. + +Bunu yönetmek için önce `username` ve `password` değerlerini UTF-8 ile encode ederek `bytes`’a dönüştürürüz. + +Sonra `secrets.compare_digest()` kullanarak `credentials.username`’in `"stanleyjobson"` ve `credentials.password`’ün `"swordfish"` olduğundan emin olabiliriz. + +{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} + +Bu, kabaca şuna benzer olurdu: + +```Python +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # Return some error + ... +``` + +Ancak `secrets.compare_digest()` kullanarak, "timing attacks" denilen bir saldırı türüne karşı güvenli olursunuz. + +### Timing Attacks { #timing-attacks } + +Peki "timing attack" nedir? + +Bazı saldırganların kullanıcı adı ve şifreyi tahmin etmeye çalıştığını düşünelim. + +Ve `johndoe` kullanıcı adı ve `love123` şifresi ile bir request gönderiyorlar. + +Uygulamanızdaki Python kodu o zaman kabaca şuna denk olur: + +```Python +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Ancak Python, `johndoe` içindeki ilk `j` ile `stanleyjobson` içindeki ilk `s`’i karşılaştırdığı anda `False` döndürür; çünkü iki string’in aynı olmadığını zaten anlar ve "kalan harfleri karşılaştırmak için daha fazla hesaplama yapmaya gerek yok" diye düşünür. Uygulamanız da "Incorrect username or password" der. + +Sonra saldırganlar bu sefer `stanleyjobsox` kullanıcı adı ve `love123` şifresi ile dener. + +Uygulama kodunuz da şuna benzer bir şey yapar: + +```Python +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Bu kez Python, iki string’in aynı olmadığını fark etmeden önce hem `stanleyjobsox` hem de `stanleyjobson` içinde `stanleyjobso` kısmının tamamını karşılaştırmak zorunda kalır. Bu nedenle "Incorrect username or password" yanıtını vermesi birkaç mikro saniye daha uzun sürer. + +#### Yanıt süresi saldırganlara yardımcı olur { #the-time-to-answer-helps-the-attackers } + +Bu noktada saldırganlar, server’ın "Incorrect username or password" response’unu göndermesinin birkaç mikro saniye daha uzun sürdüğünü fark ederek _bir şeyleri_ doğru yaptıklarını anlar; yani başlangıçtaki bazı harfler doğrudur. + +Sonra tekrar denerken, bunun `johndoe`’dan ziyade `stanleyjobsox`’a daha yakın bir şey olması gerektiğini bilerek devam edebilirler. + +#### "Profesyonel" bir saldırı { #a-professional-attack } + +Elbette saldırganlar bunu elle tek tek denemez; bunu yapan bir program yazarlar. Muhtemelen saniyede binlerce ya da milyonlarca test yaparlar ve her seferinde yalnızca bir doğru harf daha elde ederler. + +Böylece birkaç dakika ya da birkaç saat içinde doğru kullanıcı adı ve şifreyi, yanıt süresini kullanarak ve uygulamamızın "yardımıyla" tahmin etmiş olurlar. + +#### `secrets.compare_digest()` ile düzeltin { #fix-it-with-secrets-compare-digest } + +Ancak bizim kodumuzda `secrets.compare_digest()` kullanıyoruz. + +Kısacası, `stanleyjobsox` ile `stanleyjobson`’u karşılaştırmak için geçen süre, `johndoe` ile `stanleyjobson`’u karşılaştırmak için geçen süreyle aynı olur. Şifre için de aynı şekilde. + +Bu sayede uygulama kodunuzda `secrets.compare_digest()` kullanarak bu güvenlik saldırıları ailesine karşı güvenli olursunuz. + +### Hatayı döndürün { #return-the-error } + +Credential’ların hatalı olduğunu tespit ettikten sonra, 401 status code ile (credential verilmediğinde dönenle aynı) bir `HTTPException` döndürün ve tarayıcının giriş penceresini yeniden göstermesi için `WWW-Authenticate` header’ını ekleyin: + +{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} diff --git a/docs/tr/docs/advanced/security/oauth2-scopes.md b/docs/tr/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 000000000..ecba7851b --- /dev/null +++ b/docs/tr/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,274 @@ +# OAuth2 scope'ları { #oauth2-scopes } + +OAuth2 scope'larını **FastAPI** ile doğrudan kullanabilirsiniz; sorunsuz çalışacak şekilde entegre edilmiştir. + +Bu sayede OAuth2 standardını takip eden, daha ince taneli bir izin sistemini OpenAPI uygulamanıza (ve API dokümanlarınıza) entegre edebilirsiniz. + +Scope'lu OAuth2; Facebook, Google, GitHub, Microsoft, X (Twitter) vb. birçok büyük kimlik doğrulama sağlayıcısının kullandığı mekanizmadır. Kullanıcı ve uygulamalara belirli izinler vermek için bunu kullanırlar. + +Facebook, Google, GitHub, Microsoft, X (Twitter) ile "giriş yaptığınızda", o uygulama scope'lu OAuth2 kullanıyor demektir. + +Bu bölümde, **FastAPI** uygulamanızda aynı scope'lu OAuth2 ile authentication ve authorization'ı nasıl yöneteceğinizi göreceksiniz. + +/// warning | Uyarı + +Bu bölüm az çok ileri seviye sayılır. Yeni başlıyorsanız atlayabilirsiniz. + +OAuth2 scope'larına mutlaka ihtiyacınız yok; authentication ve authorization'ı istediğiniz şekilde ele alabilirsiniz. + +Ancak scope'lu OAuth2, API'nize (OpenAPI ile) ve API dokümanlarınıza güzel biçimde entegre edilebilir. + +Buna rağmen, bu scope'ları (veya başka herhangi bir security/authorization gereksinimini) kodunuzda ihtiyaç duyduğunuz şekilde yine siz zorunlu kılarsınız. + +Birçok durumda scope'lu OAuth2 gereğinden fazla (overkill) olabilir. + +Ama ihtiyacınız olduğunu biliyorsanız ya da merak ediyorsanız okumaya devam edin. + +/// + +## OAuth2 scope'ları ve OpenAPI { #oauth2-scopes-and-openapi } + +OAuth2 spesifikasyonu, "scope"ları boşluklarla ayrılmış string'lerden oluşan bir liste olarak tanımlar. + +Bu string'lerin her birinin içeriği herhangi bir formatta olabilir, ancak boşluk içermemelidir. + +Bu scope'lar "izinleri" temsil eder. + +OpenAPI'de (ör. API dokümanlarında) "security scheme" tanımlayabilirsiniz. + +Bu security scheme'lerden biri OAuth2 kullanıyorsa, scope'ları da tanımlayıp kullanabilirsiniz. + +Her bir "scope" sadece bir string'dir (boşluksuz). + +Genellikle belirli güvenlik izinlerini tanımlamak için kullanılır, örneğin: + +* `users:read` veya `users:write` sık görülen örneklerdir. +* `instagram_basic` Facebook / Instagram tarafından kullanılır. +* `https://www.googleapis.com/auth/drive` Google tarafından kullanılır. + +/// info | Bilgi + +OAuth2'de "scope", gereken belirli bir izni bildiren bir string'den ibarettir. + +`:` gibi başka karakterler içermesi ya da bir URL olması önemli değildir. + +Bu detaylar implementasyon'a bağlıdır. + +OAuth2 için bunlar sadece string'dir. + +/// + +## Genel görünüm { #global-view } + +Önce, ana **Tutorial - User Guide** içindeki [Password (ve hashing) ile OAuth2, JWT token'lı Bearer](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank} örneklerinden, OAuth2 scope'larına geçince hangi kısımların değiştiğine hızlıca bakalım: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} + +Şimdi bu değişiklikleri adım adım inceleyelim. + +## OAuth2 Security scheme { #oauth2-security-scheme } + +İlk değişiklik, artık OAuth2 security scheme'ini iki adet kullanılabilir scope ile tanımlamamız: `me` ve `items`. + +`scopes` parametresi; her scope'un key, açıklamasının ise value olduğu bir `dict` alır: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} + +Bu scope'ları tanımladığımız için, login/authorize yaptığınızda API dokümanlarında görünecekler. + +Ve hangi scope'lara erişim vermek istediğinizi seçebileceksiniz: `me` ve `items`. + +Bu, Facebook/Google/GitHub vb. ile giriş yaparken izin verdiğinizde kullanılan mekanizmanın aynısıdır: + + + +## Scope'lu JWT token { #jwt-token-with-scopes } + +Şimdi token *path operation*'ını, istenen scope'ları döndürecek şekilde değiştirin. + +Hâlâ aynı `OAuth2PasswordRequestForm` kullanılıyor. Bu form, request'te aldığı her scope için `str`'lerden oluşan bir `list` içeren `scopes` özelliğine sahiptir. + +Ve scope'ları JWT token'ın bir parçası olarak döndürüyoruz. + +/// danger | Uyarı + +Basitlik için burada, gelen scope'ları doğrudan token'a ekliyoruz. + +Ama uygulamanızda güvenlik açısından, yalnızca kullanıcının gerçekten sahip olabileceği scope'ları (veya sizin önceden tanımladıklarınızı) eklediğinizden emin olmalısınız. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} + +## *Path operation*'larda ve dependency'lerde scope tanımlama { #declare-scopes-in-path-operations-and-dependencies } + +Artık `/users/me/items/` için olan *path operation*'ın `items` scope'unu gerektirdiğini tanımlıyoruz. + +Bunun için `fastapi` içinden `Security` import edip kullanıyoruz. + +Dependency'leri (`Depends` gibi) tanımlamak için `Security` kullanabilirsiniz; fakat `Security`, ayrıca string'lerden oluşan bir scope listesi alan `scopes` parametresini de alır. + +Bu durumda `Security`'ye dependency fonksiyonu olarak `get_current_active_user` veriyoruz (`Depends` ile yaptığımız gibi). + +Ama ayrıca bir `list` olarak scope'ları da veriyoruz; burada tek bir scope var: `items` (daha fazla da olabilir). + +Ve `get_current_active_user` dependency fonksiyonu, sadece `Depends` ile değil `Security` ile de alt-dependency'ler tanımlayabilir. Kendi alt-dependency fonksiyonunu (`get_current_user`) ve daha fazla scope gereksinimini tanımlar. + +Bu örnekte `me` scope'unu gerektiriyor (birden fazla scope da isteyebilirdi). + +/// note | Not + +Farklı yerlerde farklı scope'lar eklemek zorunda değilsiniz. + +Burada, **FastAPI**'nin farklı seviyelerde tanımlanan scope'ları nasıl ele aldığını göstermek için böyle yapıyoruz. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} + +/// info | Teknik Detaylar + +`Security` aslında `Depends`'in bir alt sınıfıdır ve sadece birazdan göreceğimiz bir ek parametreye sahiptir. + +Ancak `Depends` yerine `Security` kullanınca **FastAPI**, security scope'larının tanımlanabileceğini bilir, bunları içeride kullanır ve API'yi OpenAPI ile dokümante eder. + +Fakat `fastapi` içinden `Query`, `Path`, `Depends`, `Security` vb. import ettiğiniz şeyler, aslında özel sınıflar döndüren fonksiyonlardır. + +/// + +## `SecurityScopes` kullanımı { #use-securityscopes } + +Şimdi `get_current_user` dependency'sini güncelleyelim. + +Bu fonksiyon, yukarıdaki dependency'ler tarafından kullanılıyor. + +Burada, daha önce oluşturduğumuz aynı OAuth2 scheme'i dependency olarak tanımlıyoruz: `oauth2_scheme`. + +Bu dependency fonksiyonunun kendi içinde bir scope gereksinimi olmadığı için, `oauth2_scheme` ile `Depends` kullanabiliriz; security scope'larını belirtmemiz gerekmiyorsa `Security` kullanmak zorunda değiliz. + +Ayrıca `fastapi.security` içinden import edilen, `SecurityScopes` tipinde özel bir parametre tanımlıyoruz. + +Bu `SecurityScopes` sınıfı, `Request`'e benzer (`Request`, request nesnesini doğrudan almak için kullanılmıştı). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} + +## `scopes`'ları kullanma { #use-the-scopes } + +`security_scopes` parametresi `SecurityScopes` tipinde olacaktır. + +Bu nesnenin `scopes` adlı bir özelliği vardır; bu liste, kendisinin ve bunu alt-dependency olarak kullanan tüm dependency'lerin gerektirdiği tüm scope'ları içerir. Yani tüm "dependant"lar... kafa karıştırıcı gelebilir; aşağıda tekrar açıklanıyor. + +`security_scopes` nesnesi (`SecurityScopes` sınıfından) ayrıca, bu scope'ları boşluklarla ayrılmış tek bir string olarak veren `scope_str` attribute'una sahiptir (bunu kullanacağız). + +Sonrasında birkaç farklı noktada tekrar kullanabileceğimiz (`raise` edebileceğimiz) bir `HTTPException` oluşturuyoruz. + +Bu exception içinde, gerekiyorsa, gerekli scope'ları boşlukla ayrılmış bir string olarak (`scope_str` ile) ekliyoruz. Bu scope'ları içeren string'i `WWW-Authenticate` header'ına koyuyoruz (spesifikasyonun bir parçası). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} + +## `username` ve veri şeklinin doğrulanması { #verify-the-username-and-data-shape } + +Bir `username` aldığımızı doğruluyoruz ve scope'ları çıkarıyoruz. + +Ardından bu veriyi Pydantic model'i ile doğruluyoruz (`ValidationError` exception'ını yakalayarak). JWT token'ı okurken veya Pydantic ile veriyi doğrularken bir hata olursa, daha önce oluşturduğumuz `HTTPException`'ı fırlatıyoruz. + +Bunun için Pydantic model'i `TokenData`'yı, `scopes` adlı yeni bir özellik ekleyerek güncelliyoruz. + +Veriyi Pydantic ile doğrulayarak örneğin scope'ların tam olarak `str`'lerden oluşan bir `list` olduğunu ve `username`'in bir `str` olduğunu garanti edebiliriz. + +Aksi halde, örneğin bir `dict` veya başka bir şey gelebilir; bu da daha sonra uygulamanın bir yerinde kırılmaya yol açıp güvenlik riski oluşturabilir. + +Ayrıca bu `username` ile bir kullanıcı olduğunu doğruluyoruz; yoksa yine aynı exception'ı fırlatıyoruz. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} + +## `scopes`'ların doğrulanması { #verify-the-scopes } + +Şimdi bu dependency'nin ve tüm dependant'ların ( *path operation*'lar dahil) gerektirdiği tüm scope'ların, alınan token'da sağlanan scope'lar içinde olup olmadığını doğruluyoruz; değilse `HTTPException` fırlatıyoruz. + +Bunun için, tüm bu scope'ları `str` olarak içeren bir `list` olan `security_scopes.scopes` kullanılır. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} + +## Dependency ağacı ve scope'lar { #dependency-tree-and-scopes } + +Bu dependency ağacını ve scope'ları tekrar gözden geçirelim. + +`get_current_active_user` dependency'si, alt-dependency olarak `get_current_user`'ı kullandığı için, `get_current_active_user` üzerinde tanımlanan `"me"` scope'u, `get_current_user`'a geçirilen `security_scopes.scopes` içindeki gerekli scope listesine dahil edilir. + +*Path operation*'ın kendisi de `"items"` scope'unu tanımlar; bu da `get_current_user`'a geçirilen `security_scopes.scopes` listesinde yer alır. + +Dependency'lerin ve scope'ların hiyerarşisi şöyle görünür: + +* *Path operation* `read_own_items` şunlara sahiptir: + * Dependency ile gerekli scope'lar `["items"]`: + * `get_current_active_user`: + * `get_current_active_user` dependency fonksiyonu şunlara sahiptir: + * Dependency ile gerekli scope'lar `["me"]`: + * `get_current_user`: + * `get_current_user` dependency fonksiyonu şunlara sahiptir: + * Kendisinin gerektirdiği scope yok. + * `oauth2_scheme` kullanan bir dependency. + * `SecurityScopes` tipinde bir `security_scopes` parametresi: + * Bu `security_scopes` parametresinin `scopes` adlı bir özelliği vardır ve yukarıda tanımlanan tüm scope'ları içeren bir `list` taşır, yani: + * *Path operation* `read_own_items` için `security_scopes.scopes` `["me", "items"]` içerir. + * *Path operation* `read_users_me` için `security_scopes.scopes` `["me"]` içerir; çünkü bu scope `get_current_active_user` dependency'sinde tanımlanmıştır. + * *Path operation* `read_system_status` için `security_scopes.scopes` `[]` (boş) olur; çünkü herhangi bir `Security` ile `scopes` tanımlamamıştır ve dependency'si olan `get_current_user` da `scopes` tanımlamaz. + +/// tip | İpucu + +Buradaki önemli ve "sihirli" nokta şu: `get_current_user`, her *path operation* için kontrol etmesi gereken farklı bir `scopes` listesi alır. + +Bu, belirli bir *path operation* için dependency ağacındaki her *path operation* ve her dependency üzerinde tanımlanan `scopes`'lara bağlıdır. + +/// + +## `SecurityScopes` hakkında daha fazla detay { #more-details-about-securityscopes } + +`SecurityScopes`'u herhangi bir noktada ve birden fazla yerde kullanabilirsiniz; mutlaka "kök" dependency'de olmak zorunda değildir. + +Her zaman, **o spesifik** *path operation* ve **o spesifik** dependency ağacı için, mevcut `Security` dependency'lerinde ve tüm dependant'larda tanımlanan security scope'larını içerir. + +`SecurityScopes`, dependant'ların tanımladığı tüm scope'ları barındırdığı için, gereken scope'ların token'da olup olmadığını merkezi bir dependency fonksiyonunda doğrulayıp, farklı *path operation*'larda farklı scope gereksinimleri tanımlayabilirsiniz. + +Bu kontroller her *path operation* için bağımsız yapılır. + +## Deneyin { #check-it } + +API dokümanlarını açarsanız, authenticate olup hangi scope'ları authorize etmek istediğinizi seçebilirsiniz. + + + +Hiç scope seçmezseniz "authenticated" olursunuz; ancak `/users/me/` veya `/users/me/items/`'e erişmeye çalıştığınızda, yeterli izniniz olmadığını söyleyen bir hata alırsınız. Yine de `/status/`'a erişebilirsiniz. + +`me` scope'unu seçip `items` scope'unu seçmezseniz `/users/me/`'a erişebilirsiniz ama `/users/me/items/`'e erişemezsiniz. + +Bu, bir üçüncü taraf uygulamanın, bir kullanıcı tarafından sağlanan token ile bu *path operation*'lardan birine erişmeye çalıştığında; kullanıcının uygulamaya kaç izin verdiğine bağlı olarak yaşayacağı durumdur. + +## Üçüncü taraf entegrasyonları hakkında { #about-third-party-integrations } + +Bu örnekte OAuth2 "password" flow'unu kullanıyoruz. + +Bu, kendi uygulamamıza giriş yaptığımız durumlar için uygundur; muhtemelen kendi frontend'imiz vardır. + +Çünkü `username` ve `password` alacağını bildiğimiz frontend'i biz kontrol ediyoruz, dolayısıyla güvenebiliriz. + +Ancak başkalarının bağlanacağı bir OAuth2 uygulaması geliştiriyorsanız (yani Facebook, Google, GitHub vb. gibi bir authentication provider muadili geliştiriyorsanız) diğer flow'lardan birini kullanmalısınız. + +En yaygını implicit flow'dur. + +En güvenlisi code flow'dur; ancak daha fazla adım gerektirdiği için implementasyonu daha karmaşıktır. Daha karmaşık olduğundan, birçok sağlayıcı implicit flow'yu önermeye yönelir. + +/// note | Not + +Her authentication provider'ın flow'ları markasının bir parçası yapmak için farklı şekilde adlandırması yaygındır. + +Ama sonuçta aynı OAuth2 standardını implement ediyorlar. + +/// + +**FastAPI**, bu OAuth2 authentication flow'larının tamamı için `fastapi.security.oauth2` içinde yardımcı araçlar sunar. + +## Decorator `dependencies` içinde `Security` { #security-in-decorator-dependencies } + +Decorator'ın `dependencies` parametresinde bir `list` `Depends` tanımlayabildiğiniz gibi ( [Path operation decorator'larında Dependencies](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} bölümünde açıklandığı üzere), burada `scopes` ile birlikte `Security` de kullanabilirsiniz. diff --git a/docs/tr/docs/advanced/settings.md b/docs/tr/docs/advanced/settings.md new file mode 100644 index 000000000..e3bcaac61 --- /dev/null +++ b/docs/tr/docs/advanced/settings.md @@ -0,0 +1,302 @@ +# Ayarlar ve Ortam Değişkenleri { #settings-and-environment-variables } + +Birçok durumda uygulamanızın bazı harici ayarlara veya konfigürasyonlara ihtiyacı olabilir; örneğin secret key'ler, veritabanı kimlik bilgileri, e-posta servisleri için kimlik bilgileri vb. + +Bu ayarların çoğu değişkendir (değişebilir); örneğin veritabanı URL'leri. Ayrıca birçoğu hassas olabilir; örneğin secret'lar. + +Bu nedenle bunları, uygulama tarafından okunan environment variable'lar ile sağlamak yaygındır. + +/// tip | İpucu + +Environment variable'ları anlamak için [Environment Variables](../environment-variables.md){.internal-link target=_blank} dokümanını okuyabilirsiniz. + +/// + +## Tipler ve doğrulama { #types-and-validation } + +Bu environment variable'lar yalnızca metin (string) taşıyabilir; çünkü Python'ın dışındadırlar ve diğer programlarla ve sistemin geri kalanıyla uyumlu olmaları gerekir (hatta Linux, Windows, macOS gibi farklı işletim sistemleriyle de). + +Bu da, Python içinde bir environment variable'dan okunan herhangi bir değerin `str` olacağı anlamına gelir; farklı bir tipe dönüştürme veya herhangi bir doğrulama işlemi kod içinde yapılmalıdır. + +## Pydantic `Settings` { #pydantic-settings } + +Neyse ki Pydantic, environment variable'lardan gelen bu ayarları yönetmek için Pydantic: Settings management ile çok iyi bir yardımcı araç sunar. + +### `pydantic-settings`'i kurun { #install-pydantic-settings } + +Önce, [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, aktive ettiğinizden emin olun ve ardından `pydantic-settings` paketini kurun: + +
+ +```console +$ pip install pydantic-settings +---> 100% +``` + +
+ +Ayrıca `all` extras'ını şu şekilde kurduğunuzda da dahil gelir: + +
+ +```console +$ pip install "fastapi[all]" +---> 100% +``` + +
+ +### `Settings` nesnesini oluşturun { #create-the-settings-object } + +Pydantic'ten `BaseSettings` import edin ve bir alt sınıf (sub-class) oluşturun; tıpkı bir Pydantic model'inde olduğu gibi. + +Pydantic model'lerinde olduğu gibi, type annotation'larla (ve gerekirse default değerlerle) class attribute'ları tanımlarsınız. + +Pydantic model'lerinde kullandığınız aynı doğrulama özelliklerini ve araçlarını burada da kullanabilirsiniz; örneğin farklı veri tipleri ve `Field()` ile ek doğrulamalar. + +{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} + +/// tip | İpucu + +Hızlıca kopyalayıp yapıştırmak istiyorsanız bu örneği kullanmayın; aşağıdaki son örneği kullanın. + +/// + +Ardından, bu `Settings` sınıfının bir instance'ını oluşturduğunuzda (bu örnekte `settings` nesnesi), Pydantic environment variable'ları büyük/küçük harfe duyarsız şekilde okur; yani büyük harfli `APP_NAME` değişkeni, yine de `app_name` attribute'u için okunur. + +Sonrasında veriyi dönüştürür ve doğrular. Böylece `settings` nesnesini kullandığınızda, tanımladığınız tiplerde verilere sahip olursunuz (örn. `items_per_user` bir `int` olur). + +### `settings`'i kullanın { #use-the-settings } + +Daha sonra uygulamanızda yeni `settings` nesnesini kullanabilirsiniz: + +{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *} + +### Server'ı çalıştırın { #run-the-server } + +Sonraki adımda server'ı çalıştırırken konfigürasyonları environment variable olarak geçersiniz; örneğin `ADMIN_EMAIL` ve `APP_NAME` şu şekilde ayarlanabilir: + +
+ +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +/// tip | İpucu + +Tek bir komut için birden fazla env var ayarlamak istiyorsanız aralarına boşluk koyun ve hepsini komuttan önce yazın. + +/// + +Böylece `admin_email` ayarı `"deadpool@example.com"` olur. + +`app_name` `"ChimichangApp"` olur. + +`items_per_user` ise default değeri olan `50` olarak kalır. + +## Ayarları başka bir module'de tutma { #settings-in-another-module } + +[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank} bölümünde gördüğünüz gibi, bu ayarları başka bir module dosyasına koyabilirsiniz. + +Örneğin `config.py` adında bir dosyanız şu şekilde olabilir: + +{* ../../docs_src/settings/app01_py39/config.py *} + +Ve ardından bunu `main.py` dosyasında kullanabilirsiniz: + +{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *} + +/// tip | İpucu + +[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank} bölümünde gördüğünüz gibi, ayrıca bir `__init__.py` dosyasına da ihtiyacınız olacak. + +/// + +## Dependency içinde ayarlar { #settings-in-a-dependency } + +Bazı durumlarda, her yerde kullanılan global bir `settings` nesnesi yerine ayarları bir dependency üzerinden sağlamak faydalı olabilir. + +Bu özellikle test sırasında çok işe yarar; çünkü bir dependency'yi kendi özel ayarlarınızla override etmek çok kolaydır. + +### Config dosyası { #the-config-file } + +Bir önceki örnekten devam edersek, `config.py` dosyanız şöyle görünebilir: + +{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} + +Dikkat edin, artık default bir instance `settings = Settings()` oluşturmuyoruz. + +### Ana uygulama dosyası { #the-main-app-file } + +Şimdi, yeni bir `config.Settings()` döndüren bir dependency oluşturuyoruz. + +{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} + +/// tip | İpucu + +`@lru_cache` konusunu birazdan ele alacağız. + +Şimdilik `get_settings()`'in normal bir fonksiyon olduğunu varsayabilirsiniz. + +/// + +Sonra bunu dependency olarak *path operation function*'dan talep edebilir ve ihtiyaç duyduğumuz her yerde kullanabiliriz. + +{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} + +### Ayarlar ve test { #settings-and-testing } + +Ardından, `get_settings` için bir dependency override oluşturarak test sırasında farklı bir settings nesnesi sağlamak çok kolay olur: + +{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} + +Dependency override içinde, yeni `Settings` nesnesini oluştururken `admin_email` için yeni bir değer ayarlarız ve sonra bu yeni nesneyi döndürürüz. + +Sonrasında bunun kullanıldığını test edebiliriz. + +## `.env` dosyası okuma { #reading-a-env-file } + +Çok sayıda ayarınız varsa ve bunlar farklı ortamlarda sık sık değişiyorsa, bunları bir dosyaya koyup, sanki environment variable'mış gibi o dosyadan okumak faydalı olabilir. + +Bu yaklaşım oldukça yaygındır ve bir adı vardır: Bu environment variable'lar genellikle `.env` adlı bir dosyaya konur ve bu dosyaya "dotenv" denir. + +/// tip | İpucu + +Nokta (`.`) ile başlayan dosyalar, Linux ve macOS gibi Unix-benzeri sistemlerde gizli dosyadır. + +Ancak dotenv dosyasının mutlaka bu dosya adına sahip olması gerekmez. + +/// + +Pydantic, harici bir kütüphane kullanarak bu tür dosyalardan okuma desteğine sahiptir. Daha fazlası için: Pydantic Settings: Dotenv (.env) support. + +/// tip | İpucu + +Bunun çalışması için `pip install python-dotenv` yapmanız gerekir. + +/// + +### `.env` dosyası { #the-env-file } + +Şöyle bir `.env` dosyanız olabilir: + +```bash +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +### Ayarları `.env`'den okuyun { #read-settings-from-env } + +Ardından `config.py` dosyanızı şöyle güncelleyin: + +{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} + +/// tip | İpucu + +`model_config` attribute'u yalnızca Pydantic konfigürasyonu içindir. Daha fazlası için Pydantic: Concepts: Configuration. + +/// + +Burada, Pydantic `Settings` sınıfınızın içinde `env_file` konfigürasyonunu tanımlar ve değer olarak kullanmak istediğimiz dotenv dosyasının dosya adını veririz. + +### `lru_cache` ile `Settings`'i yalnızca bir kez oluşturma { #creating-the-settings-only-once-with-lru-cache } + +Diskten dosya okumak normalde maliyetli (yavaş) bir işlemdir; bu yüzden muhtemelen bunu yalnızca bir kez yapıp aynı settings nesnesini tekrar kullanmak istersiniz. Her request için yeniden okumak istemezsiniz. + +Ancak her seferinde şunu yaptığımızda: + +```Python +Settings() +``` + +yeni bir `Settings` nesnesi oluşturulur ve oluşturulurken `.env` dosyasını yeniden okur. + +Dependency fonksiyonu sadece şöyle olsaydı: + +```Python +def get_settings(): + return Settings() +``` + +bu nesneyi her request için oluştururduk ve `.env` dosyasını her request'te okurduk. ⚠️ + +Fakat en üstte `@lru_cache` decorator'ünü kullandığımız için `Settings` nesnesi yalnızca bir kez, ilk çağrıldığı anda oluşturulur. ✔️ + +{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} + +Sonraki request'lerde dependency'ler içinden `get_settings()` çağrıldığında, `get_settings()`'in iç kodu tekrar çalıştırılıp yeni bir `Settings` nesnesi yaratılmak yerine, ilk çağrıda döndürülen aynı nesne tekrar tekrar döndürülür. + +#### `lru_cache` Teknik Detayları { #lru-cache-technical-details } + +`@lru_cache`, decorator olarak uygulandığı fonksiyonu, her seferinde tekrar hesaplamak yerine ilk seferde döndürdüğü değeri döndürecek şekilde değiştirir; yani fonksiyon kodunu her çağrıda yeniden çalıştırmaz. + +Bu nedenle altındaki fonksiyon, argüman kombinasyonlarının her biri için bir kez çalıştırılır. Sonra bu argüman kombinasyonlarının her biri için döndürülmüş değerler, fonksiyon aynı argüman kombinasyonuyla çağrıldıkça tekrar tekrar kullanılır. + +Örneğin, şöyle bir fonksiyonunuz varsa: + +```Python +@lru_cache +def say_hi(name: str, salutation: str = "Ms."): + return f"Hello {salutation} {name}" +``` + +programınız şu şekilde çalışabilir: + +```mermaid +sequenceDiagram + +participant code as Code +participant function as say_hi() +participant execute as Execute function + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Camila") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick", salutation="Mr.") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: return stored result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end +``` + +Bizim `get_settings()` dependency'miz özelinde ise fonksiyon hiç argüman almaz; dolayısıyla her zaman aynı değeri döndürür. + +Bu şekilde, neredeyse global bir değişken gibi davranır. Ancak bir dependency fonksiyonu kullandığı için testte kolayca override edebiliriz. + +`@lru_cache`, Python standart kütüphanesinin bir parçası olan `functools` içindedir. Daha fazla bilgi için: Python docs for `@lru_cache`. + +## Özet { #recap } + +Uygulamanızın ayarlarını veya konfigürasyonlarını yönetmek için, Pydantic model'lerinin tüm gücüyle birlikte Pydantic Settings'i kullanabilirsiniz. + +* Dependency kullanarak test etmeyi basitleştirebilirsiniz. +* Bununla `.env` dosyalarını kullanabilirsiniz. +* `@lru_cache` kullanmak, dotenv dosyasını her request için tekrar tekrar okumayı engellerken, test sırasında override etmenize de izin verir. diff --git a/docs/tr/docs/advanced/sub-applications.md b/docs/tr/docs/advanced/sub-applications.md new file mode 100644 index 000000000..4773ba200 --- /dev/null +++ b/docs/tr/docs/advanced/sub-applications.md @@ -0,0 +1,67 @@ +# Alt Uygulamalar - Mount İşlemi { #sub-applications-mounts } + +Kendi bağımsız OpenAPI şemaları ve kendi dokümantasyon arayüzleri olan iki bağımsız FastAPI uygulamasına ihtiyacınız varsa, bir ana uygulama oluşturup bir (veya daha fazla) alt uygulamayı "mount" edebilirsiniz. + +## Bir **FastAPI** uygulamasını mount etmek { #mounting-a-fastapi-application } + +"Mount" etmek, belirli bir path altında tamamen "bağımsız" bir uygulamayı eklemek anlamına gelir. Ardından o path’in altındaki her şeyi, alt uygulamada tanımlanan _path operation_’lar ile o alt uygulama yönetir. + +### Üst seviye uygulama { #top-level-application } + +Önce ana, üst seviye **FastAPI** uygulamasını ve onun *path operation*’larını oluşturun: + +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *} + +### Alt uygulama { #sub-application } + +Sonra alt uygulamanızı ve onun *path operation*’larını oluşturun. + +Bu alt uygulama da standart bir FastAPI uygulamasıdır; ancak "mount" edilecek olan budur: + +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *} + +### Alt uygulamayı mount edin { #mount-the-sub-application } + +Üst seviye uygulamanızda (`app`), alt uygulama `subapi`’yi mount edin. + +Bu örnekte `/subapi` path’ine mount edilecektir: + +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *} + +### Otomatik API dokümanlarını kontrol edin { #check-the-automatic-api-docs } + +Şimdi dosyanızla birlikte `fastapi` komutunu çalıştırın: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Ardından http://127.0.0.1:8000/docs adresinden dokümanları açın. + +Ana uygulama için otomatik API dokümanlarını göreceksiniz; yalnızca onun kendi _path operation_’larını içerir: + + + +Sonra alt uygulamanın dokümanlarını http://127.0.0.1:8000/subapi/docs adresinden açın. + +Alt uygulama için otomatik API dokümanlarını göreceksiniz; yalnızca onun kendi _path operation_’larını içerir ve hepsi doğru alt-path öneki `/subapi` altında yer alır: + + + +İki arayüzden herhangi biriyle etkileşime girmeyi denerseniz doğru şekilde çalıştıklarını görürsünüz; çünkü tarayıcı her bir uygulama ya da alt uygulama ile ayrı ayrı iletişim kurabilir. + +### Teknik Detaylar: `root_path` { #technical-details-root-path } + +Yukarıda anlatıldığı gibi bir alt uygulamayı mount ettiğinizde FastAPI, ASGI spesifikasyonundaki `root_path` adlı bir mekanizmayı kullanarak alt uygulamaya mount path’ini iletmeyi otomatik olarak yönetir. + +Bu sayede alt uygulama, dokümantasyon arayüzü için o path önekini kullanması gerektiğini bilir. + +Ayrıca alt uygulamanın kendi mount edilmiş alt uygulamaları da olabilir; FastAPI tüm bu `root_path`’leri otomatik olarak yönettiği için her şey doğru şekilde çalışır. + +`root_path` hakkında daha fazlasını ve bunu açıkça nasıl kullanacağınızı [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank} bölümünde öğreneceksiniz. diff --git a/docs/tr/docs/advanced/templates.md b/docs/tr/docs/advanced/templates.md new file mode 100644 index 000000000..b91e0a2a8 --- /dev/null +++ b/docs/tr/docs/advanced/templates.md @@ -0,0 +1,126 @@ +# Şablonlar { #templates } + +**FastAPI** ile istediğiniz herhangi bir template engine'i kullanabilirsiniz. + +Yaygın bir tercih, Flask ve diğer araçların da kullandığı Jinja2'dir. + +Bunu kolayca yapılandırmak için, doğrudan **FastAPI** uygulamanızda kullanabileceğiniz yardımcı araçlar vardır (Starlette tarafından sağlanır). + +## Bağımlılıkları Yükleme { #install-dependencies } + +Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, etkinleştirdiğinizden ve `jinja2`'yi yüklediğinizden emin olun: + +
+ +```console +$ pip install jinja2 + +---> 100% +``` + +
+ +## `Jinja2Templates` Kullanımı { #using-jinja2templates } + +* `Jinja2Templates`'ı içe aktarın. +* Daha sonra tekrar kullanabileceğiniz bir `templates` nesnesi oluşturun. +* Template döndürecek *path operation* içinde bir `Request` parametresi tanımlayın. +* Oluşturduğunuz `templates` nesnesini kullanarak bir `TemplateResponse` render edip döndürün; template'in adını, request nesnesini ve Jinja2 template'i içinde kullanılacak anahtar-değer çiftlerini içeren bir "context" sözlüğünü (dict) iletin. + +{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *} + +/// note | Not + +FastAPI 0.108.0 ve Starlette 0.29.0 öncesinde, ilk parametre `name` idi. + +Ayrıca, daha önceki sürümlerde `request` nesnesi, Jinja2 için context içindeki anahtar-değer çiftlerinin bir parçası olarak geçirilirdi. + +/// + +/// tip | İpucu + +`response_class=HTMLResponse` olarak tanımlarsanız doküman arayüzü (docs UI) response'un HTML olacağını anlayabilir. + +/// + +/// note | Teknik Detaylar + +`from starlette.templating import Jinja2Templates` da kullanabilirsiniz. + +**FastAPI**, geliştirici için kolaylık olması adına `starlette.templating` içeriğini `fastapi.templating` olarak da sunar. Ancak mevcut response'ların çoğu doğrudan Starlette'ten gelir. `Request` ve `StaticFiles` için de aynı durum geçerlidir. + +/// + +## Template Yazma { #writing-templates } + +Ardından örneğin `templates/item.html` konumunda bir template yazabilirsiniz: + +```jinja hl_lines="7" +{!../../docs_src/templates/templates/item.html!} +``` + +### Template Context Değerleri { #template-context-values } + +Şu HTML içeriğinde: + +{% raw %} + +```jinja +Item ID: {{ id }} +``` + +{% endraw %} + +...gösterilecek olan `id`, sizin "context" olarak ilettiğiniz `dict` içinden alınır: + +```Python +{"id": id} +``` + +Örneğin ID değeri `42` ise, şu şekilde render edilir: + +```html +Item ID: 42 +``` + +### Template `url_for` Argümanları { #template-url-for-arguments } + +Template içinde `url_for()` da kullanabilirsiniz; argüman olarak, *path operation function*'ınızın kullandığı argümanların aynısını alır. + +Dolayısıyla şu bölüm: + +{% raw %} + +```jinja + +``` + +{% endraw %} + +...*path operation function* olan `read_item(id=id)` tarafından handle edilecek URL'nin aynısına bir link üretir. + +Örneğin ID değeri `42` ise, şu şekilde render edilir: + +```html + +``` + +## Template'ler ve statik dosyalar { #templates-and-static-files } + +Template içinde `url_for()` kullanabilir ve örneğin `name="static"` ile mount ettiğiniz `StaticFiles` ile birlikte kullanabilirsiniz. + +```jinja hl_lines="4" +{!../../docs_src/templates/templates/item.html!} +``` + +Bu örnekte, şu şekilde `static/styles.css` konumundaki bir CSS dosyasına link verir: + +```CSS hl_lines="4" +{!../../docs_src/templates/static/styles.css!} +``` + +Ve `StaticFiles` kullandığınız için, bu CSS dosyası **FastAPI** uygulamanız tarafından `/static/styles.css` URL'sinde otomatik olarak servis edilir. + +## Daha fazla detay { #more-details } + +Template'leri nasıl test edeceğiniz dahil daha fazla detay için Starlette'in template dokümantasyonuna bakın. diff --git a/docs/tr/docs/advanced/testing-dependencies.md b/docs/tr/docs/advanced/testing-dependencies.md new file mode 100644 index 000000000..3cad63776 --- /dev/null +++ b/docs/tr/docs/advanced/testing-dependencies.md @@ -0,0 +1,53 @@ +# Override Kullanarak Dependency'leri Test Etme { #testing-dependencies-with-overrides } + +## Test Sırasında Dependency Override Etme { #overriding-dependencies-during-testing } + +Test yazarken bazı durumlarda bir dependency'yi override etmek isteyebilirsiniz. + +Orijinal dependency'nin (ve varsa tüm alt dependency'lerinin) çalışmasını istemezsiniz. + +Bunun yerine, yalnızca testler sırasında (hatta belki sadece belirli bazı testlerde) kullanılacak farklı bir dependency sağlarsınız; böylece orijinal dependency'nin ürettiği değerin kullanıldığı yerde, test için üretilen değeri kullanabilirsiniz. + +### Kullanım Senaryoları: Harici Servis { #use-cases-external-service } + +Örneğin, çağırmanız gereken harici bir authentication provider'ınız olabilir. + +Ona bir token gönderirsiniz ve o da authenticated bir user döndürür. + +Bu provider request başına ücret alıyor olabilir ve onu çağırmak, testlerde sabit bir mock user kullanmaya kıyasla daha fazla zaman alabilir. + +Muhtemelen harici provider'ı bir kez test etmek istersiniz; ancak çalışan her testte onu çağırmanız şart değildir. + +Bu durumda, o provider'ı çağıran dependency'yi override edebilir ve yalnızca testleriniz için mock user döndüren özel bir dependency kullanabilirsiniz. + +### `app.dependency_overrides` Attribute'ünü Kullanın { #use-the-app-dependency-overrides-attribute } + +Bu tür durumlar için **FastAPI** uygulamanızda `app.dependency_overrides` adında bir attribute bulunur; bu basit bir `dict`'tir. + +Test için bir dependency'yi override etmek istediğinizde, key olarak orijinal dependency'yi (bir function), value olarak da override edecek dependency'nizi (başka bir function) verirsiniz. + +Böylece **FastAPI**, orijinal dependency yerine bu override'ı çağırır. + +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} + +/// tip | İpucu + +**FastAPI** uygulamanızın herhangi bir yerinde kullanılan bir dependency için override tanımlayabilirsiniz. + +Orijinal dependency bir *path operation function* içinde, bir *path operation decorator* içinde (return value kullanmadığınız durumlarda), bir `.include_router()` çağrısında, vb. kullanılıyor olabilir. + +FastAPI yine de onu override edebilir. + +/// + +Sonrasında override'larınızı (yani kaldırıp sıfırlamayı) `app.dependency_overrides` değerini boş bir `dict` yaparak gerçekleştirebilirsiniz: + +```Python +app.dependency_overrides = {} +``` + +/// tip | İpucu + +Bir dependency'yi yalnızca bazı testler sırasında override etmek istiyorsanız, override'ı testin başında (test function'ının içinde) ayarlayıp testin sonunda (yine test function'ının sonunda) sıfırlayabilirsiniz. + +/// diff --git a/docs/tr/docs/advanced/testing-events.md b/docs/tr/docs/advanced/testing-events.md new file mode 100644 index 000000000..f12aef988 --- /dev/null +++ b/docs/tr/docs/advanced/testing-events.md @@ -0,0 +1,12 @@ +# Event'leri Test Etme: lifespan ve startup - shutdown { #testing-events-lifespan-and-startup-shutdown } + +Test'lerinizde `lifespan`'ın çalışması gerektiğinde, `TestClient`'ı bir `with` ifadesiyle kullanabilirsiniz: + +{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *} + + +Bu konuda daha fazla ayrıntıyı resmi Starlette dokümantasyon sitesindeki ["Running lifespan in tests in the official Starlette documentation site."](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) bölümünde okuyabilirsiniz. + +Kullanımdan kaldırılmış `startup` ve `shutdown` event'leri için ise `TestClient`'ı aşağıdaki gibi kullanabilirsiniz: + +{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *} diff --git a/docs/tr/docs/advanced/using-request-directly.md b/docs/tr/docs/advanced/using-request-directly.md new file mode 100644 index 000000000..3efdafb03 --- /dev/null +++ b/docs/tr/docs/advanced/using-request-directly.md @@ -0,0 +1,56 @@ +# Request'i Doğrudan Kullanmak { #using-the-request-directly } + +Şu ana kadar, ihtiyacınız olan request parçalarını tipleriyle birlikte tanımlıyordunuz. + +Verileri şuradan alarak: + +* path'ten parameter olarak. +* Header'lardan. +* Cookie'lerden. +* vb. + +Bunu yaptığınızda **FastAPI**, bu verileri doğrular (validate eder), dönüştürür ve API'niz için dokümantasyonu otomatik olarak üretir. + +Ancak bazı durumlarda `Request` nesnesine doğrudan erişmeniz gerekebilir. + +## `Request` nesnesi hakkında detaylar { #details-about-the-request-object } + +**FastAPI** aslında altta **Starlette** çalıştırır ve üstüne çeşitli araçlardan oluşan bir katman ekler. Bu yüzden gerektiğinde Starlette'in `Request` nesnesini doğrudan kullanabilirsiniz. + +Bu ayrıca şu anlama gelir: `Request` nesnesinden veriyi doğrudan alırsanız (örneğin body'yi okursanız) FastAPI bu veriyi doğrulamaz, dönüştürmez veya dokümante etmez (otomatik API arayüzü için OpenAPI ile). + +Buna rağmen normal şekilde tanımladığınız diğer herhangi bir parameter (örneğin Pydantic model ile body) yine doğrulanır, dönüştürülür, annotate edilir, vb. + +Ama bazı özel durumlarda `Request` nesnesini almak faydalıdır. + +## `Request` nesnesini doğrudan kullanın { #use-the-request-object-directly } + +*Path operation function* içinde client'ın IP adresini/host'unu almak istediğinizi düşünelim. + +Bunun için request'e doğrudan erişmeniz gerekir. + +{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *} + +Tipi `Request` olan bir *path operation function* parameter'ı tanımladığınızda **FastAPI**, o parameter'a `Request` nesnesini geçmesi gerektiğini anlar. + +/// tip | İpucu + +Bu örnekte, request parameter'ının yanında bir path parameter'ı da tanımladığımıza dikkat edin. + +Dolayısıyla path parameter'ı çıkarılır, doğrulanır, belirtilen tipe dönüştürülür ve OpenAPI ile annotate edilir. + +Aynı şekilde, diğer parameter'ları normal biçimde tanımlamaya devam edip buna ek olarak `Request` de alabilirsiniz. + +/// + +## `Request` dokümantasyonu { #request-documentation } + +Resmi Starlette dokümantasyon sitesinde `Request` nesnesiyle ilgili daha fazla detayı okuyabilirsiniz. + +/// note | Teknik Detaylar + +`from starlette.requests import Request` de kullanabilirsiniz. + +**FastAPI** bunu size (geliştiriciye) kolaylık olsun diye doğrudan sunar. Ancak kendisi doğrudan Starlette'ten gelir. + +/// diff --git a/docs/tr/docs/advanced/websockets.md b/docs/tr/docs/advanced/websockets.md new file mode 100644 index 000000000..775d7cc25 --- /dev/null +++ b/docs/tr/docs/advanced/websockets.md @@ -0,0 +1,186 @@ +# WebSockets { #websockets } + +**FastAPI** ile WebSockets kullanabilirsiniz. + +## `websockets` Kurulumu { #install-websockets } + +Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu aktive ettiğinizden ve `websockets`'i ("WebSocket" protokolünü kullanmayı kolaylaştıran bir Python kütüphanesi) kurduğunuzdan emin olun: + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## WebSockets client { #websockets-client } + +### Production'da { #in-production } + +Production sisteminizde muhtemelen React, Vue.js veya Angular gibi modern bir framework ile oluşturulmuş bir frontend vardır. + +WebSockets kullanarak backend'inizle iletişim kurmak için de büyük ihtimalle frontend'inizin sağladığı yardımcı araçları kullanırsınız. + +Ya da native kod ile doğrudan WebSocket backend'inizle iletişim kuran native bir mobil uygulamanız olabilir. + +Veya WebSocket endpoint'i ile iletişim kurmak için başka herhangi bir yönteminizi de kullanıyor olabilirsiniz. + +--- + +Ancak bu örnek için, tamamı uzun bir string içinde olacak şekilde biraz JavaScript içeren çok basit bir HTML dokümanı kullanacağız. + +Elbette bu optimal değil ve production için kullanmazsınız. + +Production'da yukarıdaki seçeneklerden birini kullanırsınız. + +Ama WebSockets'in server tarafına odaklanmak ve çalışan bir örnek görmek için en basit yol bu: + +{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *} + +## Bir `websocket` Oluşturun { #create-a-websocket } + +**FastAPI** uygulamanızda bir `websocket` oluşturun: + +{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *} + +/// note | Teknik Detaylar + +`from starlette.websockets import WebSocket` da kullanabilirsiniz. + +**FastAPI**, geliştirici olarak işinizi kolaylaştırmak için aynı `WebSocket`'i doğrudan sağlar. Ancak aslında doğrudan Starlette'ten gelir. + +/// + +## Mesajları `await` Edin ve Mesaj Gönderin { #await-for-messages-and-send-messages } + +WebSocket route'unuzda mesajları `await` edebilir ve mesaj gönderebilirsiniz. + +{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *} + +Binary, text ve JSON verisi alıp gönderebilirsiniz. + +## Deneyin { #try-it } + +Dosyanızın adı `main.py` ise uygulamanızı şu şekilde çalıştırın: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Tarayıcınızda http://127.0.0.1:8000 adresini açın. + +Şuna benzer basit bir sayfa göreceksiniz: + + + +Input kutusuna mesaj yazıp gönderebilirsiniz: + + + +Ve WebSockets kullanan **FastAPI** uygulamanız yanıt döndürecektir: + + + +Birçok mesaj gönderebilir (ve alabilirsiniz): + + + +Ve hepsinde aynı WebSocket bağlantısı kullanılacaktır. + +## `Depends` ve Diğerlerini Kullanma { #using-depends-and-others } + +WebSocket endpoint'lerinde `fastapi` içinden import edip şunları kullanabilirsiniz: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +Diğer FastAPI endpoint'leri/*path operations* ile aynı şekilde çalışırlar: + +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} + +/// info | Bilgi + +Bu bir WebSocket olduğu için `HTTPException` raise etmek pek anlamlı değildir; bunun yerine `WebSocketException` raise ederiz. + +Spesifikasyonda tanımlanan geçerli kodlar arasından bir kapatma kodu kullanabilirsiniz. + +/// + +### Dependency'lerle WebSockets'i Deneyin { #try-the-websockets-with-dependencies } + +Dosyanızın adı `main.py` ise uygulamanızı şu şekilde çalıştırın: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Tarayıcınızda http://127.0.0.1:8000 adresini açın. + +Burada şunları ayarlayabilirsiniz: + +* path'te kullanılan "Item ID". +* query parametresi olarak kullanılan "Token". + +/// tip | İpucu + +query'deki `token` değerinin bir dependency tarafından ele alınacağına dikkat edin. + +/// + +Bununla WebSocket'e bağlanabilir, ardından mesaj gönderip alabilirsiniz: + + + +## Bağlantı Kopmalarını ve Birden Fazla Client'ı Yönetme { #handling-disconnections-and-multiple-clients } + +Bir WebSocket bağlantısı kapandığında, `await websocket.receive_text()` bir `WebSocketDisconnect` exception'ı raise eder; ardından bunu bu örnekteki gibi yakalayıp (catch) yönetebilirsiniz. + +{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} + +Denemek için: + +* Uygulamayı birden fazla tarayıcı sekmesiyle açın. +* Bu sekmelerden mesaj yazın. +* Sonra sekmelerden birini kapatın. + +Bu, `WebSocketDisconnect` exception'ını raise eder ve diğer tüm client'lar şuna benzer bir mesaj alır: + +``` +Client #1596980209979 left the chat +``` + +/// tip | İpucu + +Yukarıdaki uygulama, birden fazla WebSocket bağlantısına mesajları nasıl yönetip broadcast edeceğinizi göstermek için minimal ve basit bir örnektir. + +Ancak her şey memory'de, tek bir list içinde yönetildiği için yalnızca process çalıştığı sürece ve yalnızca tek bir process ile çalışacaktır. + +FastAPI ile kolay entegre olan ama Redis, PostgreSQL vb. tarafından desteklenen daha sağlam bir şeye ihtiyacınız varsa encode/broadcaster'a göz atın. + +/// + +## Daha Fazla Bilgi { #more-info } + +Seçenekler hakkında daha fazlasını öğrenmek için Starlette dokümantasyonunda şunlara bakın: + +* `WebSocket` class'ı. +* Class tabanlı WebSocket yönetimi. diff --git a/docs/tr/docs/deployment/concepts.md b/docs/tr/docs/deployment/concepts.md new file mode 100644 index 000000000..d0f568146 --- /dev/null +++ b/docs/tr/docs/deployment/concepts.md @@ -0,0 +1,321 @@ +# Deployment Kavramları { #deployments-concepts } + +Bir **FastAPI** uygulamasını (hatta genel olarak herhangi bir web API'yi) deploy ederken, muhtemelen önemseyeceğiniz bazı kavramlar vardır. Bu kavramları kullanarak, **uygulamanızı deploy etmek** için **en uygun** yöntemi bulabilirsiniz. + +Önemli kavramlardan bazıları şunlardır: + +* Güvenlik - HTTPS +* Startup'ta çalıştırma +* Yeniden başlatmalar +* Replikasyon (çalışan process sayısı) +* Bellek +* Başlatmadan önceki adımlar + +Bunların **deployment**'ları nasıl etkilediğine bakalım. + +Nihai hedef, **API client**'larınıza **güvenli** bir şekilde hizmet verebilmek, **kesintileri** önlemek ve **hesaplama kaynaklarını** (ör. uzak server'lar/sanal makineler) olabildiğince verimli kullanmaktır. + +Burada bu **kavramlar** hakkında biraz daha bilgi vereceğim. Böylece, çok farklı ortamlarda—hatta bugün var olmayan **gelecekteki** ortamlarda bile—API'nizi nasıl deploy edeceğinize karar verirken ihtiyaç duyacağınız **sezgiyi** kazanmış olursunuz. + +Bu kavramları dikkate alarak, **kendi API**'leriniz için en iyi deployment yaklaşımını **değerlendirebilir ve tasarlayabilirsiniz**. + +Sonraki bölümlerde, FastAPI uygulamalarını deploy etmek için daha **somut tarifler** (recipes) paylaşacağım. + +Ama şimdilik, bu önemli **kavramsal fikirleri** inceleyelim. Bu kavramlar diğer tüm web API türleri için de geçerlidir. + +## Güvenlik - HTTPS { #security-https } + +[HTTPS hakkındaki önceki bölümde](https.md){.internal-link target=_blank} HTTPS'in API'niz için nasıl şifreleme sağladığını öğrenmiştik. + +Ayrıca HTTPS'in genellikle uygulama server'ınızın **dışında** yer alan bir bileşen tarafından sağlandığını, yani bir **TLS Termination Proxy** ile yapıldığını da görmüştük. + +Ve **HTTPS sertifikalarını yenilemekten** sorumlu bir şey olmalıdır; bu aynı bileşen olabileceği gibi farklı bir bileşen de olabilir. + +### HTTPS için Örnek Araçlar { #example-tools-for-https } + +TLS Termination Proxy olarak kullanabileceğiniz bazı araçlar: + +* Traefik + * Sertifika yenilemelerini otomatik yönetir +* Caddy + * Sertifika yenilemelerini otomatik yönetir +* Nginx + * Sertifika yenilemeleri için Certbot gibi harici bir bileşenle +* HAProxy + * Sertifika yenilemeleri için Certbot gibi harici bir bileşenle +* Nginx gibi bir Ingress Controller ile Kubernetes + * Sertifika yenilemeleri için cert-manager gibi harici bir bileşenle +* Bir cloud provider tarafından servislerinin parçası olarak içeride yönetilmesi (aşağıyı okuyun) + +Bir diğer seçenek de, HTTPS kurulumunu da dahil olmak üzere işin daha büyük kısmını yapan bir **cloud service** kullanmaktır. Bunun bazı kısıtları olabilir veya daha pahalı olabilir vb. Ancak bu durumda TLS Termination Proxy'yi kendiniz kurmak zorunda kalmazsınız. + +Sonraki bölümlerde bazı somut örnekler göstereceğim. + +--- + +Sonraki kavramlar, gerçek API'nizi çalıştıran programla (ör. Uvicorn) ilgilidir. + +## Program ve Process { #program-and-process } + +Çalışan "**process**" hakkında çok konuşacağız. Bu yüzden ne anlama geldiğini ve "**program**" kelimesinden farkının ne olduğunu netleştirmek faydalı. + +### Program Nedir { #what-is-a-program } + +**Program** kelimesi günlük kullanımda birçok şeyi anlatmak için kullanılır: + +* Yazdığınız **code**, yani **Python dosyaları**. +* İşletim sistemi tarafından **çalıştırılabilen** **dosya**, örn: `python`, `python.exe` veya `uvicorn`. +* İşletim sistemi üzerinde **çalışır durumdayken** CPU kullanan ve bellekte veri tutan belirli bir program. Buna **process** de denir. + +### Process Nedir { #what-is-a-process } + +**Process** kelimesi genellikle daha spesifik kullanılır; yalnızca işletim sistemi üzerinde çalışan şeye (yukarıdaki son madde gibi) işaret eder: + +* İşletim sistemi üzerinde **çalışır durumda** olan belirli bir program. + * Bu; dosyayı ya da code'u değil, işletim sistemi tarafından **çalıştırılan** ve yönetilen şeyi ifade eder. +* Herhangi bir program, herhangi bir code, **yalnızca çalıştırılırken** bir şey yapabilir. Yani bir **process çalışıyorken**. +* Process siz tarafından veya işletim sistemi tarafından **sonlandırılabilir** (ya da "killed" edilebilir). O anda çalışması/çalıştırılması durur ve artık **hiçbir şey yapamaz**. +* Bilgisayarınızda çalışan her uygulamanın arkasında bir process vardır; çalışan her program, her pencere vb. Bilgisayar açıkken normalde **aynı anda** birçok process çalışır. +* Aynı anda **aynı programın birden fazla process**'i çalışabilir. + +İşletim sisteminizdeki "task manager" veya "system monitor" (ya da benzeri araçlar) ile bu process'lerin birçoğunu çalışır halde görebilirsiniz. + +Örneğin muhtemelen aynı browser programını (Firefox, Chrome, Edge vb.) çalıştıran birden fazla process göreceksiniz. Genelde her tab için bir process, üstüne bazı ek process'ler çalıştırırlar. + + + +--- + +Artık **process** ve **program** arasındaki farkı bildiğimize göre, deployment konusuna devam edelim. + +## Startup'ta Çalıştırma { #running-on-startup } + +Çoğu durumda bir web API oluşturduğunuzda, client'larınızın her zaman erişebilmesi için API'nizin kesintisiz şekilde **sürekli çalışıyor** olmasını istersiniz. Elbette sadece belirli durumlarda çalışmasını istemenizin özel bir sebebi olabilir; ancak çoğunlukla onu sürekli açık ve **kullanılabilir** halde tutarsınız. + +### Uzak Bir Server'da { #in-a-remote-server } + +Uzak bir server (cloud server, sanal makine vb.) kurduğunuzda, yapabileceğiniz en basit şey; local geliştirme sırasında yaptığınız gibi, manuel olarak `fastapi run` (Uvicorn'u kullanır) veya benzeri bir komutla çalıştırmaktır. + +Bu yöntem çalışır ve **geliştirme sırasında** faydalıdır. + +Ancak server'a olan bağlantınız koparsa, **çalışan process** muhtemelen ölür. + +Ve server yeniden başlatılırsa (örneğin update'lerden sonra ya da cloud provider'ın migration'larından sonra) bunu muhtemelen **fark etmezsiniz**. Dolayısıyla process'i manuel yeniden başlatmanız gerektiğini de bilmezsiniz. Sonuçta API'niz ölü kalır. + +### Startup'ta Otomatik Çalıştırma { #run-automatically-on-startup } + +Genellikle server programının (ör. Uvicorn) server açılışında otomatik başlamasını ve herhangi bir **insan müdahalesi** gerektirmeden API'nizi çalıştıran bir process'in sürekli ayakta olmasını istersiniz (ör. FastAPI uygulamanızı çalıştıran Uvicorn). + +### Ayrı Bir Program { #separate-program } + +Bunu sağlamak için genellikle startup'ta uygulamanızın çalıştığından emin olacak **ayrı bir program** kullanırsınız. Pek çok durumda bu program, örneğin bir veritabanı gibi diğer bileşenlerin/uygulamaların da çalıştığından emin olur. + +### Startup'ta Çalıştırmak için Örnek Araçlar { #example-tools-to-run-at-startup } + +Bu işi yapabilen araçlara örnekler: + +* Docker +* Kubernetes +* Docker Compose +* Docker in Swarm Mode +* Systemd +* Supervisor +* Bir cloud provider tarafından servislerinin parçası olarak içeride yönetilmesi +* Diğerleri... + +Sonraki bölümlerde daha somut örnekler vereceğim. + +## Yeniden Başlatmalar { #restarts } + +Uygulamanızın startup'ta çalıştığından emin olmaya benzer şekilde, hatalardan sonra **yeniden başlatıldığından** da emin olmak istersiniz. + +### Hata Yaparız { #we-make-mistakes } + +Biz insanlar sürekli **hata** yaparız. Yazılımın neredeyse *her zaman* farklı yerlerinde gizli **bug**'lar vardır. + +Ve biz geliştiriciler bu bug'ları buldukça ve yeni özellikler ekledikçe code'u iyileştiririz (muhtemelen yeni bug'lar da ekleyerek). + +### Küçük Hatalar Otomatik Yönetilir { #small-errors-automatically-handled } + +FastAPI ile web API geliştirirken, code'umuzda bir hata olursa FastAPI genellikle bunu hatayı tetikleyen tek request ile sınırlar. + +Client o request için **500 Internal Server Error** alır; ancak uygulama tamamen çöküp durmak yerine sonraki request'ler için çalışmaya devam eder. + +### Daha Büyük Hatalar - Çökmeler { #bigger-errors-crashes } + +Yine de bazı durumlarda, yazdığımız bir code **tüm uygulamayı çökertip** Uvicorn ve Python'ın crash olmasına neden olabilir. + +Böyle bir durumda, tek bir noktadaki hata yüzünden uygulamanın ölü kalmasını istemezsiniz; bozuk olmayan *path operations* en azından çalışmaya devam etsin istersiniz. + +### Crash Sonrası Yeniden Başlatma { #restart-after-crash } + +Ancak çalışan **process**'i çökerten gerçekten kötü hatalarda, process'i **yeniden başlatmaktan** sorumlu harici bir bileşen istersiniz; en azından birkaç kez... + +/// tip | İpucu + +...Yine de uygulama **hemen crash oluyorsa**, onu sonsuza kadar yeniden başlatmaya çalışmanın pek anlamı yoktur. Böyle durumları büyük ihtimalle geliştirme sırasında ya da en geç deploy'dan hemen sonra fark edersiniz. + +O yüzden ana senaryoya odaklanalım: Gelecekte bazı özel durumlarda tamamen çökebilir ve yine de yeniden başlatmak mantıklıdır. + +/// + +Uygulamanızı yeniden başlatmakla görevli bileşenin **harici bir bileşen** olmasını istersiniz. Çünkü o noktada Uvicorn ve Python ile birlikte aynı uygulama zaten crash olmuştur; aynı app'in içindeki aynı code'un bunu düzeltmek için yapabileceği bir şey kalmaz. + +### Otomatik Yeniden Başlatma için Örnek Araçlar { #example-tools-to-restart-automatically } + +Çoğu durumda, **startup'ta programı çalıştırmak** için kullanılan aracın aynısı otomatik **restart**'ları yönetmek için de kullanılır. + +Örneğin bu şunlarla yönetilebilir: + +* Docker +* Kubernetes +* Docker Compose +* Docker in Swarm Mode +* Systemd +* Supervisor +* Bir cloud provider tarafından servislerinin parçası olarak içeride yönetilmesi +* Diğerleri... + +## Replikasyon - Process'ler ve Bellek { #replication-processes-and-memory } + +FastAPI uygulamasında, Uvicorn'u çalıştıran `fastapi` komutu gibi bir server programı kullanırken, uygulamayı **tek bir process** içinde bir kez çalıştırmak bile aynı anda birden fazla client'a hizmet verebilir. + +Ancak birçok durumda, aynı anda birden fazla worker process çalıştırmak istersiniz. + +### Birden Fazla Process - Worker'lar { #multiple-processes-workers } + +Tek bir process'in karşılayabileceğinden daha fazla client'ınız varsa (örneğin sanal makine çok büyük değilse) ve server CPU'sunda **birden fazla core** varsa, aynı uygulamayla **birden fazla process** çalıştırıp tüm request'leri bunlara dağıtabilirsiniz. + +Aynı API programının **birden fazla process**'ini çalıştırdığınızda, bunlara genellikle **worker** denir. + +### Worker Process'ler ve Port'lar { #worker-processes-and-ports } + +[HTTPS hakkındaki dokümanda](https.md){.internal-link target=_blank} bir server'da aynı port ve IP adresi kombinasyonunu yalnızca tek bir process'in dinleyebileceğini hatırlıyor musunuz? + +Bu hâlâ geçerli. + +Dolayısıyla **aynı anda birden fazla process** çalıştırabilmek için, **port** üzerinde dinleyen **tek bir process** olmalı ve bu process iletişimi bir şekilde worker process'lere aktarmalıdır. + +### Process Başına Bellek { #memory-per-process } + +Program belleğe bir şeyler yüklediğinde—örneğin bir değişkende bir machine learning modelini veya büyük bir dosyanın içeriğini tutmak gibi—bunların hepsi server'ın **belleğini (RAM)** tüketir. + +Ve birden fazla process normalde **belleği paylaşmaz**. Yani her çalışan process'in kendi verileri, değişkenleri ve belleği vardır. Code'unuz çok bellek tüketiyorsa, **her process** buna denk bir miktar bellek tüketir. + +### Server Belleği { #server-memory } + +Örneğin code'unuz **1 GB** boyutunda bir Machine Learning modelini yüklüyorsa, API'niz tek process ile çalışırken en az 1 GB RAM tüketir. **4 process** (4 worker) başlatırsanız her biri 1 GB RAM tüketir. Yani toplamda API'niz **4 GB RAM** tüketir. + +Uzak server'ınız veya sanal makineniz yalnızca 3 GB RAM'e sahipse, 4 GB'tan fazla RAM yüklemeye çalışmak sorun çıkarır. + +### Birden Fazla Process - Bir Örnek { #multiple-processes-an-example } + +Bu örnekte, iki adet **Worker Process** başlatıp kontrol eden bir **Manager Process** vardır. + +Bu Manager Process büyük ihtimalle IP üzerindeki **port**'u dinleyen süreçtir ve tüm iletişimi worker process'lere aktarır. + +Worker process'ler uygulamanızı çalıştıran process'lerdir; bir **request** alıp bir **response** döndürmek için asıl hesaplamaları yaparlar ve sizin RAM'de değişkenlere koyduğunuz her şeyi yüklerler. + + + +Elbette aynı makinede, uygulamanız dışında da muhtemelen **başka process**'ler çalışır. + +İlginç bir detay: Her process'in kullandığı **CPU** yüzdesi zaman içinde çok **değişken** olabilir; ancak **bellek (RAM)** genellikle az çok **stabil** kalır. + +Eğer API'niz her seferinde benzer miktarda hesaplama yapıyorsa ve çok sayıda client'ınız varsa, **CPU kullanımı** da muhtemelen *stabil olur* (hızlı hızlı sürekli yükselip alçalmak yerine). + +### Replikasyon Araçları ve Stratejileri Örnekleri { #examples-of-replication-tools-and-strategies } + +Bunu başarmak için farklı yaklaşımlar olabilir. Sonraki bölümlerde, örneğin Docker ve container'lar konuşurken, belirli stratejileri daha detaylı anlatacağım. + +Dikkate almanız gereken ana kısıt şudur: **public IP** üzerindeki **port**'u yöneten **tek** bir bileşen olmalı. Sonrasında bu bileşenin, replikasyonla çoğaltılmış **process/worker**'lara iletişimi **aktarmanın** bir yoluna sahip olması gerekir. + +Olası kombinasyonlar ve stratejiler: + +* `--workers` ile **Uvicorn** + * Bir Uvicorn **process manager** **IP** ve **port** üzerinde dinler ve **birden fazla Uvicorn worker process** başlatır. +* **Kubernetes** ve diğer dağıtık **container sistemleri** + * **Kubernetes** katmanında bir şey **IP** ve **port** üzerinde dinler. Replikasyon, her birinde **tek bir Uvicorn process** çalışan **birden fazla container** ile yapılır. +* Bunu sizin yerinize yapan **cloud service**'ler + * Cloud service muhtemelen **replikasyonu sizin yerinize yönetir**. Size çalıştırılacak **bir process** veya kullanılacak bir **container image** tanımlama imkânı verebilir; her durumda büyük ihtimalle **tek bir Uvicorn process** olur ve bunu çoğaltmaktan cloud service sorumlu olur. + +/// tip | İpucu + +**Container**, Docker veya Kubernetes ile ilgili bazı maddeler şimdilik çok anlamlı gelmiyorsa dert etmeyin. + +Container image'ları, Docker, Kubernetes vb. konuları ilerideki bir bölümde daha detaylı anlatacağım: [Container'larda FastAPI - Docker](docker.md){.internal-link target=_blank}. + +/// + +## Başlatmadan Önceki Adımlar { #previous-steps-before-starting } + +Uygulamanızı **başlatmadan önce** bazı adımlar yapmak isteyeceğiniz birçok durum vardır. + +Örneğin **database migrations** çalıştırmak isteyebilirsiniz. + +Ancak çoğu durumda, bu adımları yalnızca **bir kez** çalıştırmak istersiniz. + +Bu yüzden, uygulamayı başlatmadan önce bu **ön adımları** çalıştıracak **tek bir process** olmasını istersiniz. + +Ve daha sonra uygulamanın kendisi için **birden fazla process** (birden fazla worker) başlatsanız bile, bu ön adımları çalıştıranın *yine* tek process olduğundan emin olmalısınız. Bu adımlar **birden fazla process** tarafından çalıştırılsaydı, işi **paralel** şekilde tekrarlarlardı. Adımlar database migration gibi hassas bir şeyse, birbirleriyle çakışıp çatışma çıkarabilirler. + +Elbette bazı durumlarda ön adımları birden fazla kez çalıştırmak sorun değildir; bu durumda yönetmesi çok daha kolay olur. + +/// tip | İpucu + +Ayrıca, kurulumunuza bağlı olarak bazı durumlarda uygulamanızı başlatmadan önce **hiç ön adıma ihtiyaç duymayabilirsiniz**. + +Bu durumda bunların hiçbirini düşünmeniz gerekmez. + +/// + +### Ön Adımlar için Strateji Örnekleri { #examples-of-previous-steps-strategies } + +Bu konu, **sisteminizi nasıl deploy ettiğinize** çok bağlıdır ve muhtemelen programları nasıl başlattığınız, restart'ları nasıl yönettiğiniz vb. ile bağlantılıdır. + +Bazı olası fikirler: + +* Kubernetes'te, app container'ınızdan önce çalışan bir "Init Container" +* Ön adımları çalıştırıp sonra uygulamanızı başlatan bir bash script + * Yine de o bash script'i başlatmak/restart etmek, hataları tespit etmek vb. için bir mekanizmaya ihtiyacınız olur. + +/// tip | İpucu + +Bunu container'larla nasıl yapabileceğinize dair daha somut örnekleri ilerideki bir bölümde anlatacağım: [Container'larda FastAPI - Docker](docker.md){.internal-link target=_blank}. + +/// + +## Kaynak Kullanımı { #resource-utilization } + +Server(lar)ınız bir **kaynaktır**. Programlarınızla CPU'lardaki hesaplama zamanını ve mevcut RAM belleğini tüketebilir veya **kullanabilirsiniz**. + +Sistem kaynaklarının ne kadarını tüketmek/kullanmak istersiniz? "Az" demek kolaydır; ancak pratikte hedef genellikle **çökmeden mümkün olduğunca fazla** kullanmaktır. + +3 server için para ödüyor ama onların RAM ve CPU'sunun yalnızca küçük bir kısmını kullanıyorsanız, muhtemelen **para israf ediyorsunuz** ve muhtemelen **elektrik tüketimini** de gereksiz yere artırıyorsunuz vb. + +Bu durumda 2 server ile devam edip onların kaynaklarını (CPU, bellek, disk, ağ bant genişliği vb.) daha yüksek oranlarda kullanmak daha iyi olabilir. + +Öte yandan, 2 server'ınız var ve CPU ile RAM'in **%100**'ünü kullanıyorsanız, bir noktada bir process daha fazla bellek ister; server diski "bellek" gibi kullanmak zorunda kalır (binlerce kat daha yavaş olabilir) ya da hatta **crash** edebilir. Ya da bir process bir hesaplama yapmak ister ve CPU tekrar boşalana kadar beklemek zorunda kalır. + +Bu senaryoda **bir server daha** eklemek ve bazı process'leri orada çalıştırmak daha iyi olur; böylece hepsinin **yeterli RAM'i ve CPU zamanı** olur. + +Ayrıca, herhangi bir sebeple API'nizde bir kullanım **spike**'ı olma ihtimali de vardır. Belki viral olur, belki başka servisler veya bot'lar kullanmaya başlar. Bu durumlarda güvende olmak için ekstra kaynak isteyebilirsiniz. + +Hedef olarak **keyfi bir sayı** belirleyebilirsiniz; örneğin kaynak kullanımını **%50 ile %90 arasında** tutmak gibi. Önemli olan, bunların muhtemelen ölçmek isteyeceğiniz ve deployment'larınızı ayarlamak için kullanacağınız ana metrikler olmasıdır. + +Server'ınızda CPU ve RAM kullanımını veya her process'in ne kadar kullandığını görmek için `htop` gibi basit araçları kullanabilirsiniz. Ya da server'lar arasında dağıtık çalışan daha karmaşık monitoring araçları kullanabilirsiniz. + +## Özet { #recap } + +Uygulamanızı nasıl deploy edeceğinize karar verirken aklınızda tutmanız gereken ana kavramların bazılarını okudunuz: + +* Güvenlik - HTTPS +* Startup'ta çalıştırma +* Yeniden başlatmalar +* Replikasyon (çalışan process sayısı) +* Bellek +* Başlatmadan önceki adımlar + +Bu fikirleri ve nasıl uygulayacağınızı anlamak, deployment'larınızı yapılandırırken ve ince ayar yaparken ihtiyaç duyacağınız sezgiyi kazanmanızı sağlamalıdır. + +Sonraki bölümlerde, izleyebileceğiniz stratejilere dair daha somut örnekler paylaşacağım. diff --git a/docs/tr/docs/deployment/docker.md b/docs/tr/docs/deployment/docker.md new file mode 100644 index 000000000..b48d2ddf5 --- /dev/null +++ b/docs/tr/docs/deployment/docker.md @@ -0,0 +1,620 @@ +# Container'larda FastAPI - Docker { #fastapi-in-containers-docker } + +FastAPI uygulamalarını deploy ederken yaygın bir yaklaşım, bir **Linux container image** oluşturmaktır. Bu genellikle **Docker** kullanılarak yapılır. Ardından bu container image'ı birkaç farklı yöntemden biriyle deploy edebilirsiniz. + +Linux container'ları kullanmanın **güvenlik**, **tekrarlanabilirlik**, **basitlik** gibi birçok avantajı vardır. + +/// tip | İpucu + +Aceleniz var ve bunları zaten biliyor musunuz? Aşağıdaki [`Dockerfile`'a atlayın 👇](#build-a-docker-image-for-fastapi). + +/// + +
+Dockerfile Önizleme 👀 + +```Dockerfile +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +CMD ["fastapi", "run", "app/main.py", "--port", "80"] + +# If running behind a proxy like Nginx or Traefik add --proxy-headers +# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"] +``` + +
+ +## Container Nedir { #what-is-a-container } + +Container'lar (özellikle Linux container'ları), bir uygulamayı tüm bağımlılıkları ve gerekli dosyalarıyla birlikte paketlemenin, aynı sistemdeki diğer container'lardan (diğer uygulama ya da bileşenlerden) izole tutarken yapılan, çok **hafif** bir yoludur. + +Linux container'ları, host'un (makine, sanal makine, cloud server vb.) aynı Linux kernel'ini kullanarak çalışır. Bu da, tüm bir işletim sistemini emüle eden tam sanal makinelere kıyasla çok daha hafif oldukları anlamına gelir. + +Bu sayede container'lar **az kaynak** tüketir; süreçleri doğrudan çalıştırmaya benzer bir seviyede (bir sanal makine çok daha fazla tüketirdi). + +Container'ların ayrıca kendi **izole** çalışan process'leri (çoğunlukla tek bir process), dosya sistemi ve ağı vardır. Bu da deployment, güvenlik, geliştirme vb. süreçleri kolaylaştırır. + +## Container Image Nedir { #what-is-a-container-image } + +Bir **container**, bir **container image**'dan çalıştırılır. + +Container image; bir container içinde bulunması gereken tüm dosyaların, environment variable'ların ve varsayılan komut/programın **statik** bir sürümüdür. Buradaki **statik**, container **image**'ının çalışmadığı, execute edilmediği; sadece paketlenmiş dosyalar ve metadata olduğu anlamına gelir. + +Depolanmış statik içerik olan "**container image**"ın aksine, "**container**" normalde çalışan instance'ı, yani **execute edilen** şeyi ifade eder. + +**Container** başlatılıp çalıştığında (bir **container image**'dan başlatılır), dosyalar oluşturabilir/değiştirebilir, environment variable'ları değiştirebilir vb. Bu değişiklikler sadece o container içinde kalır; alttaki container image'da kalıcı olmaz (diske kaydedilmez). + +Bir container image, **program** dosyası ve içeriklerine benzetilebilir; örn. `python` ve `main.py` gibi bir dosya. + +Ve **container**'ın kendisi (container image'a karşıt olarak) image'ın gerçek çalışan instance'ıdır; bir **process**'e benzer. Hatta bir container, yalnızca içinde **çalışan bir process** varken çalışır (ve genelde tek process olur). İçinde çalışan process kalmayınca container durur. + +## Container Image'lar { #container-images } + +Docker, **container image** ve **container** oluşturup yönetmek için kullanılan başlıca araçlardan biri olmuştur. + +Ayrıca birçok araç, ortam, veritabanı ve uygulama için önceden hazırlanmış **resmi container image**'ların bulunduğu herkese açık bir Docker Hub vardır. + +Örneğin, resmi bir Python Image bulunur. + +Ve veritabanları gibi farklı şeyler için de birçok image vardır; örneğin: + +* PostgreSQL +* MySQL +* MongoDB +* Redis, vb. + +Hazır bir container image kullanarak farklı araçları **birleştirmek** ve birlikte kullanmak çok kolaydır. Örneğin yeni bir veritabanını denemek için. Çoğu durumda **resmi image**'ları kullanıp sadece environment variable'lar ile yapılandırmanız yeterlidir. + +Bu şekilde, çoğu zaman container'lar ve Docker hakkında öğrendiklerinizi farklı araç ve bileşenlerde tekrar kullanabilirsiniz. + +Dolayısıyla; veritabanı, Python uygulaması, React frontend uygulaması olan bir web server gibi farklı şeyler için **birden fazla container** çalıştırır ve bunları internal network üzerinden birbirine bağlarsınız. + +Docker veya Kubernetes gibi tüm container yönetim sistemlerinde bu ağ özellikleri entegre olarak bulunur. + +## Container'lar ve Process'ler { #containers-and-processes } + +Bir **container image** normalde metadata içinde, **container** başlatıldığında çalıştırılacak varsayılan program/komutu ve o programa geçirilecek parametreleri içerir. Bu, komut satırında yazacağınız şeye çok benzer. + +Bir **container** başlatıldığında bu komutu/programı çalıştırır (ancak isterseniz bunu override edip başka bir komut/program çalıştırabilirsiniz). + +Bir container, **ana process** (komut/program) çalıştığı sürece çalışır. + +Container'larda normalde **tek bir process** olur. Ancak ana process içinden subprocess'ler başlatmak da mümkündür; böylece aynı container içinde **birden fazla process** olur. + +Ama **en az bir çalışan process olmadan** çalışan bir container olamaz. Ana process durursa container da durur. + +## FastAPI için Docker Image Oluşturalım { #build-a-docker-image-for-fastapi } + +Tamam, şimdi bir şeyler inşa edelim! 🚀 + +Resmi **Python** image'ını temel alarak, FastAPI için **sıfırdan** bir **Docker image** nasıl oluşturulur göstereceğim. + +Bu, örneğin şu durumlarda **çoğu zaman** yapmak isteyeceğiniz şeydir: + +* **Kubernetes** veya benzeri araçlar kullanırken +* **Raspberry Pi** üzerinde çalıştırırken +* Container image'ınızı sizin için çalıştıran bir cloud servisi kullanırken, vb. + +### Paket Gereksinimleri { #package-requirements } + +Uygulamanızın **paket gereksinimleri** genelde bir dosyada yer alır. + +Bu, gereksinimleri **yüklemek** için kullandığınız araca göre değişir. + +En yaygın yöntem, paket adları ve versiyonlarının satır satır yazıldığı bir `requirements.txt` dosyasına sahip olmaktır. + +Versiyon aralıklarını belirlemek için elbette [FastAPI sürümleri hakkında](versions.md){.internal-link target=_blank} bölümünde okuduğunuz fikirleri kullanırsınız. + +Örneğin `requirements.txt` şöyle görünebilir: + +``` +fastapi[standard]>=0.113.0,<0.114.0 +pydantic>=2.7.0,<3.0.0 +``` + +Ve bu bağımlılıkları normalde `pip` ile yüklersiniz, örneğin: + +
+ +```console +$ pip install -r requirements.txt +---> 100% +Successfully installed fastapi pydantic +``` + +
+ +/// info | Bilgi + +Paket bağımlılıklarını tanımlamak ve yüklemek için başka formatlar ve araçlar da vardır. + +/// + +### **FastAPI** Kodunu Oluşturun { #create-the-fastapi-code } + +* Bir `app` dizini oluşturun ve içine girin. +* Boş bir `__init__.py` dosyası oluşturun. +* Aşağıdakilerle bir `main.py` dosyası oluşturun: + +```Python +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +### Dockerfile { #dockerfile } + +Şimdi aynı proje dizininde `Dockerfile` adlı bir dosya oluşturun ve içine şunları yazın: + +```{ .dockerfile .annotate } +# (1)! +FROM python:3.9 + +# (2)! +WORKDIR /code + +# (3)! +COPY ./requirements.txt /code/requirements.txt + +# (4)! +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (5)! +COPY ./app /code/app + +# (6)! +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +1. Resmi Python base image'ından başlayın. + +2. Geçerli çalışma dizinini `/code` olarak ayarlayın. + + `requirements.txt` dosyasını ve `app` dizinini buraya koyacağız. + +3. Gereksinimleri içeren dosyayı `/code` dizinine kopyalayın. + + Önce kodun tamamını değil, **sadece** gereksinim dosyasını kopyalayın. + + Bu dosya **çok sık değişmediği** için Docker bunu tespit eder ve bu adımda **cache** kullanır; böylece bir sonraki adım için de cache devreye girer. + +4. Gereksinim dosyasındaki paket bağımlılıklarını yükleyin. + + `--no-cache-dir` seçeneği, indirilen paketlerin yerel olarak kaydedilmemesini `pip`'e söyler. Bu kayıt, `pip` aynı paketleri tekrar yüklemek için yeniden çalıştırılacaksa işe yarar; ancak container'larla çalışırken genelde bu durum geçerli değildir. + + /// note | Not + + `--no-cache-dir` yalnızca `pip` ile ilgilidir; Docker veya container'larla ilgili değildir. + + /// + + `--upgrade` seçeneği, paketler zaten yüklüyse `pip`'e onları yükseltmesini söyler. + + Bir önceki adım (dosyayı kopyalama) **Docker cache** tarafından tespit edilebildiği için, bu adım da uygun olduğunda **Docker cache'i kullanır**. + + Bu adımda cache kullanmak, geliştirme sırasında image'ı tekrar tekrar build ederken size çok **zaman** kazandırır; her seferinde bağımlılıkları **indirip yüklemek** zorunda kalmazsınız. + +5. `./app` dizinini `/code` dizininin içine kopyalayın. + + Burada en sık değişen şey olan kodun tamamı bulunduğundan, bu adım (ve genelde bundan sonraki adımlar) için Docker **cache**'i kolay kolay kullanılamaz. + + Bu yüzden, container image build sürelerini optimize etmek için bunu `Dockerfile`'ın **sonlarına yakın** koymak önemlidir. + +6. Altta Uvicorn kullanan `fastapi run` komutunu **command** olarak ayarlayın. + + `CMD` bir string listesi alır; bu string'lerin her biri komut satırında boşlukla ayrılmış şekilde yazacağınız parçaları temsil eder. + + Bu komut, yukarıda `WORKDIR /code` ile ayarladığınız `/code` dizininden çalıştırılır. + +/// tip | İpucu + +Kod içindeki her numara balonuna tıklayarak her satırın ne yaptığını gözden geçirin. 👆 + +/// + +/// warning | Uyarı + +Aşağıda açıklandığı gibi `CMD` talimatının **her zaman** **exec form**'unu kullandığınızdan emin olun. + +/// + +#### `CMD` Kullanımı - Exec Form { #use-cmd-exec-form } + +`CMD` Docker talimatı iki formda yazılabilir: + +✅ **Exec** form: + +```Dockerfile +# ✅ Do this +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +⛔️ **Shell** form: + +```Dockerfile +# ⛔️ Don't do this +CMD fastapi run app/main.py --port 80 +``` + +FastAPI'nin düzgün şekilde kapanabilmesi ve [lifespan event](../advanced/events.md){.internal-link target=_blank}'lerinin tetiklenmesi için her zaman **exec** formunu kullanın. + +Detaylar için shell ve exec form için Docker dokümanlarına bakabilirsiniz. + +Bu durum `docker compose` kullanırken oldukça belirgin olabilir. Daha teknik detaylar için şu Docker Compose FAQ bölümüne bakın: Why do my services take 10 seconds to recreate or stop?. + +#### Dizin Yapısı { #directory-structure } + +Artık dizin yapınız şöyle olmalı: + +``` +. +├── app +│   ├── __init__.py +│ └── main.py +├── Dockerfile +└── requirements.txt +``` + +#### TLS Termination Proxy Arkasında { #behind-a-tls-termination-proxy } + +Container'ınızı Nginx veya Traefik gibi bir TLS Termination Proxy (load balancer) arkasında çalıştırıyorsanız `--proxy-headers` seçeneğini ekleyin. Bu, Uvicorn'a (FastAPI CLI üzerinden) uygulamanın HTTPS arkasında çalıştığını söyleyen proxy header'larına güvenmesini söyler. + +```Dockerfile +CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] +``` + +#### Docker Cache { #docker-cache } + +Bu `Dockerfile` içinde önemli bir numara var: önce kodun geri kalanını değil, **sadece bağımlılık dosyasını** kopyalıyoruz. Nedenini anlatayım. + +```Dockerfile +COPY ./requirements.txt /code/requirements.txt +``` + +Docker ve benzeri araçlar bu container image'larını **artımlı (incremental)** olarak **build** eder; `Dockerfile`'ın en üstünden başlayıp her talimatın oluşturduğu dosyaları ekleyerek **katman katman (layer)** ilerler. + +Docker ve benzeri araçlar image build ederken ayrıca bir **internal cache** kullanır. Son build'den beri bir dosya değişmediyse, dosyayı tekrar kopyalayıp sıfırdan yeni bir layer oluşturmak yerine, daha önce oluşturulan **aynı layer**'ı yeniden kullanır. + +Sadece dosya kopyalamayı azaltmak her zaman büyük fark yaratmaz. Ancak o adımda cache kullanıldığı için, **bir sonraki adımda da cache kullanılabilir**. Örneğin bağımlılıkları yükleyen şu talimat için: + +```Dockerfile +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt +``` + +Paket gereksinimleri dosyası **sık sık değişmez**. Bu yüzden sadece bu dosyayı kopyalayınca, Docker bu adımda **cache** kullanabilir. + +Sonra Docker, bağımlılıkları indirip yükleyen **bir sonraki adımda** da cache kullanabilir. Asıl **çok zaman kazandığımız** yer de burasıdır. ✨ ...ve beklerken sıkılmayı engeller. 😪😆 + +Bağımlılıkları indirip yüklemek **dakikalar sürebilir**, fakat **cache** kullanmak en fazla **saniyeler** alır. + +Geliştirme sırasında kod değişikliklerinizin çalıştığını kontrol etmek için container image'ı tekrar tekrar build edeceğinizden, bu ciddi birikimli zaman kazancı sağlar. + +Sonra `Dockerfile`'ın sonlarına doğru tüm kodu kopyalarız. En sık değişen kısım bu olduğu için sona koyarız; çünkü neredeyse her zaman bu adımdan sonra gelen adımlar cache kullanamaz. + +```Dockerfile +COPY ./app /code/app +``` + +### Docker Image'ını Build Edin { #build-the-docker-image } + +Tüm dosyalar hazır olduğuna göre container image'ı build edelim. + +* Proje dizinine gidin (`Dockerfile`'ınızın olduğu ve `app` dizininizi içeren dizin). +* FastAPI image'ınızı build edin: + +
+ +```console +$ docker build -t myimage . + +---> 100% +``` + +
+ +/// tip | İpucu + +Sondaki `.` ifadesine dikkat edin; `./` ile aynı anlama gelir ve Docker'a container image build etmek için hangi dizini kullanacağını söyler. + +Bu örnekte, mevcut dizindir (`.`). + +/// + +### Docker Container'ını Başlatın { #start-the-docker-container } + +* Image'ınızdan bir container çalıştırın: + +
+ +```console +$ docker run -d --name mycontainer -p 80:80 myimage +``` + +
+ +## Kontrol Edin { #check-it } + +Docker container'ınızın URL'inden kontrol edebilmelisiniz. Örneğin: http://192.168.99.100/items/5?q=somequery veya http://127.0.0.1/items/5?q=somequery (ya da Docker host'unuzu kullanarak eşdeğeri). + +Şuna benzer bir şey görürsünüz: + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +## Etkileşimli API Dokümanları { #interactive-api-docs } + +Şimdi http://192.168.99.100/docs veya http://127.0.0.1/docs adresine gidebilirsiniz (ya da Docker host'unuzla eşdeğeri). + +Otomatik etkileşimli API dokümantasyonunu görürsünüz ( Swagger UI tarafından sağlanır): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +## Alternatif API Dokümanları { #alternative-api-docs } + +Ayrıca http://192.168.99.100/redoc veya http://127.0.0.1/redoc adresine de gidebilirsiniz (ya da Docker host'unuzla eşdeğeri). + +Alternatif otomatik dokümantasyonu görürsünüz (ReDoc tarafından sağlanır): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## Tek Dosyalık FastAPI ile Docker Image Oluşturma { #build-a-docker-image-with-a-single-file-fastapi } + +FastAPI uygulamanız tek bir dosyaysa; örneğin `./app` dizini olmadan sadece `main.py` varsa, dosya yapınız şöyle olabilir: + +``` +. +├── Dockerfile +├── main.py +└── requirements.txt +``` + +Bu durumda `Dockerfile` içinde dosyayı kopyaladığınız path'leri buna göre değiştirmeniz yeterlidir: + +```{ .dockerfile .annotate hl_lines="10 13" } +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (1)! +COPY ./main.py /code/ + +# (2)! +CMD ["fastapi", "run", "main.py", "--port", "80"] +``` + +1. `main.py` dosyasını doğrudan `/code` dizinine kopyalayın (herhangi bir `./app` dizini olmadan). + +2. Tek dosya olan `main.py` içindeki uygulamanızı sunmak için `fastapi run` kullanın. + +Dosyayı `fastapi run`'a verdiğinizde, bunun bir package'ın parçası değil tek bir dosya olduğunu otomatik olarak algılar; nasıl import edip FastAPI uygulamanızı nasıl serve edeceğini bilir. 😎 + +## Deployment Kavramları { #deployment-concepts } + +Aynı [Deployment Kavramları](concepts.md){.internal-link target=_blank}nı bu kez container'lar açısından tekrar konuşalım. + +Container'lar, bir uygulamayı **build etme ve deploy etme** sürecini basitleştiren bir araçtır. Ancak bu **deployment kavramları**nı ele almak için belirli bir yaklaşımı zorunlu kılmazlar; birkaç farklı strateji mümkündür. + +**İyi haber** şu: Hangi stratejiyi seçerseniz seçin, deployment kavramlarının tamamını kapsayacak bir yol vardır. 🎉 + +Bu **deployment kavramları**nı container'lar açısından gözden geçirelim: + +* HTTPS +* Startup'ta çalıştırma +* Restart'lar +* Replication (çalışan process sayısı) +* Memory +* Başlatmadan önceki adımlar + +## HTTPS { #https } + +Bir FastAPI uygulamasının sadece **container image**'ına (ve sonra çalışan **container**'a) odaklanırsak, HTTPS genellikle **haricen** başka bir araçla ele alınır. + +Örneğin Traefik kullanan başka bir container olabilir; **HTTPS** ve **sertifika**ların **otomatik** alınmasını o yönetebilir. + +/// tip | İpucu + +Traefik; Docker, Kubernetes ve diğerleriyle entegre çalışır. Bu sayede container'larınız için HTTPS'i kurup yapılandırmak oldukça kolaydır. + +/// + +Alternatif olarak HTTPS, bir cloud provider'ın sunduğu servislerden biri tarafından da yönetilebilir (uygulama yine container içinde çalışırken). + +## Startup'ta Çalıştırma ve Restart'lar { #running-on-startup-and-restarts } + +Container'ınızı **başlatıp çalıştırmaktan** sorumlu genellikle başka bir araç olur. + +Bu; doğrudan **Docker**, **Docker Compose**, **Kubernetes**, bir **cloud service** vb. olabilir. + +Çoğu (veya tüm) durumda, container'ı startup'ta çalıştırmayı ve hata durumlarında restart'ları etkinleştirmeyi sağlayan basit bir seçenek vardır. Örneğin Docker'da bu, `--restart` komut satırı seçeneğidir. + +Container kullanmadan, uygulamaları startup'ta çalıştırmak ve restart mekanizması eklemek zahmetli ve zor olabilir. Ancak **container'larla çalışırken** çoğu zaman bu işlevler varsayılan olarak hazır gelir. ✨ + +## Replication - Process Sayısı { #replication-number-of-processes } + +Kubernetes, Docker Swarm Mode, Nomad veya benzeri, birden fazla makinede dağıtık container'ları yöneten karmaşık bir sistemle kurulmuş bir cluster'ınız varsa, replication'ı her container içinde bir **process manager** (ör. worker'lı Uvicorn) kullanarak yönetmek yerine, muhtemelen **cluster seviyesinde** ele almak istersiniz. + +Kubernetes gibi dağıtık container yönetim sistemleri, gelen request'ler için **load balancing** desteği sunarken aynı zamanda **container replication**'ını yönetmek için entegre mekanizmalara sahiptir. Hepsi **cluster seviyesinde**. + +Bu tür durumlarda, yukarıda [anlatıldığı gibi](#dockerfile) bağımlılıkları yükleyip **sıfırdan bir Docker image** build etmek ve birden fazla Uvicorn worker kullanmak yerine **tek bir Uvicorn process** çalıştırmak istersiniz. + +### Load Balancer { #load-balancer } + +Container'lar kullanırken, genellikle ana port'ta dinleyen bir bileşen olur. Bu, **HTTPS**'i ele almak için bir **TLS Termination Proxy** olan başka bir container da olabilir ya da benzeri bir araç olabilir. + +Bu bileşen request'lerin **yükünü** alıp worker'lar arasında (umarım) **dengeli** şekilde dağıttığı için yaygın olarak **Load Balancer** diye de adlandırılır. + +/// tip | İpucu + +HTTPS için kullanılan aynı **TLS Termination Proxy** bileşeni muhtemelen bir **Load Balancer** olarak da çalışır. + +/// + +Container'larla çalışırken, onları başlatıp yönettiğiniz sistem; bu **load balancer**'dan (aynı zamanda **TLS Termination Proxy** de olabilir) uygulamanızın bulunduğu container(lar)a **network communication**'ı (ör. HTTP request'leri) iletmek için zaten dahili araçlar sunar. + +### Tek Load Balancer - Çoklu Worker Container { #one-load-balancer-multiple-worker-containers } + +**Kubernetes** veya benzeri dağıtık container yönetim sistemleriyle çalışırken, dahili ağ mekanizmaları sayesinde ana **port**'u dinleyen tek bir **load balancer**, uygulamanızı çalıştıran muhtemelen **birden fazla container**'a request'leri iletebilir. + +Uygulamanızı çalıştıran bu container'ların her birinde normalde **tek bir process** olur (ör. FastAPI uygulamanızı çalıştıran bir Uvicorn process). Hepsi aynı şeyi çalıştıran **özdeş container**'lardır; ancak her birinin kendi process'i, memory'si vb. vardır. Böylece CPU'nun **farklı core**'larında, hatta **farklı makinelerde** paralelleştirmeden yararlanırsınız. + +Load balancer'lı dağıtık sistem, request'leri uygulamanızın bulunduğu container'ların her birine sırayla **dağıtır**. Böylece her request, uygulamanızın birden fazla **replicated container**'ından biri tarafından işlenebilir. + +Ve bu **load balancer** normalde cluster'ınızdaki *diğer* uygulamalara giden request'leri de (ör. farklı bir domain ya da farklı bir URL path prefix altında) yönetebilir ve iletişimi o *diğer* uygulamanın doğru container'larına iletir. + +### Container Başına Tek Process { #one-process-per-container } + +Bu senaryoda, replication'ı zaten cluster seviyesinde yaptığınız için, muhtemelen **container başına tek bir (Uvicorn) process** istersiniz. + +Dolayısıyla bu durumda container içinde `--workers` gibi bir komut satırı seçeneğiyle çoklu worker istemezsiniz. Container başına sadece **tek bir Uvicorn process** istersiniz (ama muhtemelen birden fazla container). + +Container içine ekstra bir process manager koymak (çoklu worker gibi) çoğu zaman zaten cluster sisteminizle çözdüğünüz şeye ek **gereksiz karmaşıklık** katar. + +### Birden Fazla Process'li Container'lar ve Özel Durumlar { #containers-with-multiple-processes-and-special-cases } + +Elbette bazı **özel durumlarda** bir container içinde birden fazla **Uvicorn worker process** çalıştırmak isteyebilirsiniz. + +Bu durumlarda çalıştırmak istediğiniz worker sayısını `--workers` komut satırı seçeneğiyle ayarlayabilirsiniz: + +```{ .dockerfile .annotate } +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +# (1)! +CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"] +``` + +1. Burada worker sayısını 4 yapmak için `--workers` komut satırı seçeneğini kullanıyoruz. + +Bunun mantıklı olabileceği birkaç örnek: + +#### Basit Bir Uygulama { #a-simple-app } + +Uygulamanız tek bir server üzerinde (cluster değil) çalışacak kadar **basitse**, container içinde bir process manager isteyebilirsiniz. + +#### Docker Compose { #docker-compose } + +**Docker Compose** ile **tek bir server**'a (cluster değil) deploy ediyor olabilirsiniz. Bu durumda, paylaşılan ağı ve **load balancing**'i koruyarak container replication'ını (Docker Compose ile) yönetmenin kolay bir yolu olmayabilir. + +Bu durumda, tek bir container içinde **bir process manager** ile **birden fazla worker process** başlatmak isteyebilirsiniz. + +--- + +Ana fikir şu: Bunların **hiçbiri** körü körüne uymanız gereken **değişmez kurallar** değildir. Bu fikirleri, kendi kullanım senaryonuzu **değerlendirmek** ve sisteminiz için en iyi yaklaşımı seçmek için kullanabilirsiniz. Şu kavramları nasıl yöneteceğinize bakarak karar verin: + +* Güvenlik - HTTPS +* Startup'ta çalıştırma +* Restart'lar +* Replication (çalışan process sayısı) +* Memory +* Başlatmadan önceki adımlar + +## Memory { #memory } + +**Container başına tek process** çalıştırırsanız, her container'ın tüketeceği memory miktarı aşağı yukarı tanımlı, stabil ve sınırlı olur (replication varsa birden fazla container için). + +Sonra aynı memory limit ve gereksinimlerini container yönetim sisteminizin (ör. **Kubernetes**) konfigürasyonlarında belirleyebilirsiniz. Böylece sistem; ihtiyaç duyulan memory miktarını ve cluster'daki makinelerde mevcut memory'yi dikkate alarak **uygun makinelerde container'ları replicate edebilir**. + +Uygulamanız **basitse**, muhtemelen bu **bir sorun olmaz** ve katı memory limitleri belirlemeniz gerekmeyebilir. Ancak **çok memory kullanıyorsanız** (ör. **machine learning** modelleriyle), ne kadar memory tükettiğinizi kontrol edip **her makinede** çalışacak **container sayısını** ayarlamalısınız (ve gerekirse cluster'a daha fazla makine eklemelisiniz). + +**Container başına birden fazla process** çalıştırırsanız, başlatılan process sayısının mevcut olandan **fazla memory tüketmediğinden** emin olmanız gerekir. + +## Başlatmadan Önceki Adımlar ve Container'lar { #previous-steps-before-starting-and-containers } + +Container kullanıyorsanız (örn. Docker, Kubernetes), temelde iki yaklaşım vardır. + +### Birden Fazla Container { #multiple-containers } + +**Birden fazla container**'ınız varsa ve muhtemelen her biri **tek process** çalıştırıyorsa (ör. bir **Kubernetes** cluster'ında), replication yapılan worker container'lar çalışmadan **önce**, **başlatmadan önceki adımlar**ın işini yapan **ayrı bir container** kullanmak isteyebilirsiniz (tek container, tek process). + +/// info | Bilgi + +Kubernetes kullanıyorsanız, bu muhtemelen bir Init Container olur. + +/// + +Kullanım senaryonuzda bu adımları **paralel olarak birden fazla kez** çalıştırmak sorun değilse (örneğin veritabanı migration çalıştırmıyor, sadece veritabanı hazır mı diye kontrol ediyorsanız), o zaman her container'da ana process başlamadan hemen önce de çalıştırabilirsiniz. + +### Tek Container { #single-container } + +Basit bir kurulumda; **tek bir container** olup onun içinde birden fazla **worker process** (ya da sadece bir process) başlatıyorsanız, bu adımları aynı container içinde, uygulama process'ini başlatmadan hemen önce çalıştırabilirsiniz. + +### Base Docker Image { #base-docker-image } + +Eskiden resmi bir FastAPI Docker image'ı vardı: tiangolo/uvicorn-gunicorn-fastapi. Ancak artık kullanımdan kaldırıldı (deprecated). ⛔️ + +Muhtemelen bu base Docker image'ını (veya benzeri başka bir image'ı) kullanmamalısınız. + +**Kubernetes** (veya diğerleri) kullanıyor ve cluster seviyesinde birden fazla **container** ile **replication** ayarlıyorsanız, bu durumda yukarıda anlatıldığı gibi **sıfırdan bir image build etmek** daha iyi olur: [FastAPI için Docker Image Oluşturalım](#build-a-docker-image-for-fastapi). + +Ve birden fazla worker gerekiyorsa, sadece `--workers` komut satırı seçeneğini kullanabilirsiniz. + +/// note | Teknik Detaylar + +Bu Docker image, Uvicorn dead worker'ları yönetmeyi ve yeniden başlatmayı desteklemediği dönemde oluşturulmuştu. Bu yüzden Uvicorn ile birlikte Gunicorn kullanmak gerekiyordu; sırf Gunicorn, Uvicorn worker process'lerini yönetip yeniden başlatsın diye oldukça fazla karmaşıklık ekleniyordu. + +Ancak artık Uvicorn (ve `fastapi` komutu) `--workers` kullanımını desteklediğine göre, kendi image'ınızı build etmek yerine bir base Docker image kullanmanın bir nedeni kalmadı (kod miktarı da hemen hemen aynı 😅). + +/// + +## Container Image'ı Deploy Etme { #deploy-the-container-image } + +Bir Container (Docker) Image'ınız olduktan sonra bunu deploy etmenin birkaç yolu vardır. + +Örneğin: + +* Tek bir server'da **Docker Compose** ile +* Bir **Kubernetes** cluster'ı ile +* Docker Swarm Mode cluster'ı ile +* Nomad gibi başka bir araçla +* Container image'ınızı alıp deploy eden bir cloud servisiyle + +## `uv` ile Docker Image { #docker-image-with-uv } + +Projenizi yüklemek ve yönetmek için uv kullanıyorsanız, onların uv Docker rehberini takip edebilirsiniz. + +## Özet { #recap } + +Container sistemleri (örn. **Docker** ve **Kubernetes** ile) kullanınca tüm **deployment kavramları**nı ele almak oldukça kolaylaşır: + +* HTTPS +* Startup'ta çalıştırma +* Restart'lar +* Replication (çalışan process sayısı) +* Memory +* Başlatmadan önceki adımlar + +Çoğu durumda bir base image kullanmak istemezsiniz; bunun yerine resmi Python Docker image'ını temel alarak **sıfırdan bir container image** build edersiniz. + +`Dockerfile` içindeki talimatların **sırasına** ve **Docker cache**'ine dikkat ederek **build sürelerini minimize edebilir**, üretkenliğinizi artırabilirsiniz (ve beklerken sıkılmayı önlersiniz). 😎 diff --git a/docs/tr/docs/deployment/fastapicloud.md b/docs/tr/docs/deployment/fastapicloud.md new file mode 100644 index 000000000..bb861273b --- /dev/null +++ b/docs/tr/docs/deployment/fastapicloud.md @@ -0,0 +1,65 @@ +# FastAPI Cloud { #fastapi-cloud } + +FastAPI uygulamanızı FastAPI Cloud'a **tek bir komutla** deploy edebilirsiniz. Henüz yapmadıysanız gidip bekleme listesine katılın. 🚀 + +## Giriş Yapma { #login } + +Önceden bir **FastAPI Cloud** hesabınız olduğundan emin olun (sizi bekleme listesinden davet ettik 😉). + +Ardından giriş yapın: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +## Deploy { #deploy } + +Şimdi uygulamanızı **tek bir komutla** deploy edin: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +Hepsi bu! Artık uygulamanıza o URL üzerinden erişebilirsiniz. ✨ + +## FastAPI Cloud Hakkında { #about-fastapi-cloud } + +**FastAPI Cloud**, **FastAPI**'nin arkasındaki aynı yazar ve ekip tarafından geliştirilmiştir. + +Bir API'yi minimum eforla **geliştirme**, **deploy etme** ve **erişilebilir kılma** sürecini sadeleştirir. + +FastAPI ile uygulama geliştirirken elde ettiğiniz aynı **developer experience**'ı, onları buluta **deploy etmeye** de taşır. 🎉 + +Ayrıca bir uygulamayı deploy ederken ihtiyaç duyacağınız pek çok şeyi de sizin için halleder; örneğin: + +* HTTPS +* Replication (çoğaltma), request'lere göre autoscaling ile +* vb. + +FastAPI Cloud, *FastAPI and friends* açık kaynak projelerinin birincil sponsoru ve finansman sağlayıcısıdır. ✨ + +## Diğer cloud sağlayıcılarına deploy etme { #deploy-to-other-cloud-providers } + +FastAPI açık kaynaklıdır ve standartlara dayanır. FastAPI uygulamalarını seçtiğiniz herhangi bir cloud sağlayıcısına deploy edebilirsiniz. + +FastAPI uygulamalarını deploy etmek için cloud sağlayıcınızın kendi kılavuzlarını takip edin. 🤓 + +## Kendi server'ınıza deploy etme { #deploy-your-own-server } + +Bu **Deployment** kılavuzunun ilerleyen bölümlerinde tüm detayları da ele alacağız; böylece neler olduğunu, nelerin gerçekleşmesi gerektiğini ve FastAPI uygulamalarını kendi başınıza (kendi server'larınızla da) nasıl deploy edebileceğinizi anlayacaksınız. 🤓 diff --git a/docs/tr/docs/deployment/https.md b/docs/tr/docs/deployment/https.md new file mode 100644 index 000000000..bb70883aa --- /dev/null +++ b/docs/tr/docs/deployment/https.md @@ -0,0 +1,231 @@ +# HTTPS Hakkında { #about-https } + +HTTPS’in sadece "açık" ya da "kapalı" olan bir şey olduğunu düşünmek kolaydır. + +Ancak bundan çok daha karmaşıktır. + +/// tip | İpucu + +Aceleniz varsa veya çok da önemsemiyorsanız, her şeyi farklı tekniklerle adım adım kurmak için sonraki bölümlere geçin. + +/// + +Bir kullanıcı gözüyle **HTTPS’in temellerini öğrenmek** için https://howhttps.works/ adresine bakın. + +Şimdi de **geliştirici perspektifinden**, HTTPS hakkında düşünürken akılda tutulması gereken birkaç nokta: + +* HTTPS için **server**’ın, **üçüncü bir taraf** tarafından verilen **"sertifikalara"** sahip olması gerekir. + * Bu sertifikalar aslında üçüncü tarafça "üretilmez", üçüncü taraftan **temin edilir**. +* Sertifikaların bir **geçerlilik süresi** vardır. + * Süresi **dolar**. + * Sonrasında **yenilenmeleri**, üçüncü taraftan **yeniden temin edilmeleri** gerekir. +* Bağlantının şifrelenmesi **TCP seviyesinde** gerçekleşir. + * Bu, **HTTP’nin bir katman altıdır**. + * Dolayısıyla **sertifika ve şifreleme** işlemleri **HTTP’den önce** yapılır. +* **TCP "domain"leri bilmez**. Yalnızca IP adreslerini bilir. + * İstenen **spesifik domain** bilgisi **HTTP verisinin** içindedir. +* **HTTPS sertifikaları** belirli bir **domain**’i "sertifikalandırır"; ancak protokol ve şifreleme TCP seviyesinde, hangi domain ile çalışıldığı **henüz bilinmeden** gerçekleşir. +* **Varsayılan olarak** bu, IP adresi başına yalnızca **bir HTTPS sertifikası** olabileceği anlamına gelir. + * Server’ınız ne kadar büyük olursa olsun ya da üzerindeki her uygulama ne kadar küçük olursa olsun. + * Ancak bunun bir **çözümü** vardır. +* **TLS** protokolüne (TCP seviyesinde, HTTP’den önce şifrelemeyi yapan) eklenen **SNI** adlı bir **extension** vardır. + * Bu SNI extension’ı, tek bir server’ın (tek bir **IP adresiyle**) **birden fazla HTTPS sertifikası** kullanmasına ve **birden fazla HTTPS domain/uygulama** sunmasına izin verir. + * Bunun çalışması için server üzerinde, **public IP adresini** dinleyen tek bir bileşenin (programın) server’daki **tüm HTTPS sertifikalarına** sahip olması gerekir. +* Güvenli bir bağlantı elde edildikten **sonra**, iletişim protokolü **hâlâ HTTP**’dir. + * İçerikler, **HTTP protokolü** ile gönderiliyor olsa bile **şifrelenmiştir**. + +Yaygın yaklaşım, server’da (makine, host vb.) çalışan **tek bir program/HTTP server** bulundurup **HTTPS ile ilgili tüm kısımları** yönetmektir: **şifreli HTTPS request**’leri almak, aynı server’da çalışan gerçek HTTP uygulamasına (bu örnekte **FastAPI** uygulaması) **şifresi çözülmüş HTTP request**’leri iletmek, uygulamadan gelen **HTTP response**’u almak, uygun **HTTPS sertifikası** ile **şifrelemek** ve **HTTPS** ile client’a geri göndermek. Bu server’a çoğu zaman **TLS Termination Proxy** denir. + +TLS Termination Proxy olarak kullanabileceğiniz seçeneklerden bazıları: + +* Traefik (sertifika yenilemelerini de yönetebilir) +* Caddy (sertifika yenilemelerini de yönetebilir) +* Nginx +* HAProxy + +## Let's Encrypt { #lets-encrypt } + +Let's Encrypt’ten önce bu **HTTPS sertifikaları**, güvenilen üçüncü taraflar tarafından satılırdı. + +Bu sertifikalardan birini temin etme süreci zahmetliydi, epey evrak işi gerektirirdi ve sertifikalar oldukça pahalıydı. + +Sonra **Let's Encrypt** ortaya çıktı. + +Linux Foundation’ın bir projesidir. **HTTPS sertifikalarını ücretsiz** ve otomatik bir şekilde sağlar. Bu sertifikalar tüm standart kriptografik güvenliği kullanır ve kısa ömürlüdür (yaklaşık 3 ay). Bu yüzden, ömürleri kısa olduğu için **güvenlik aslında daha iyidir**. + +Domain’ler güvenli şekilde doğrulanır ve sertifikalar otomatik üretilir. Bu sayede sertifikaların yenilenmesini otomatikleştirmek de mümkün olur. + +Amaç, bu sertifikaların temin edilmesi ve yenilenmesini otomatikleştirerek **ücretsiz, kalıcı olarak güvenli HTTPS** sağlamaktır. + +## Geliştiriciler İçin HTTPS { #https-for-developers } + +Burada, bir HTTPS API’nin adım adım nasıl görünebileceğine dair, özellikle geliştiriciler için önemli fikirlere odaklanan bir örnek var. + +### Domain Adı { #domain-name } + +Muhtemelen her şey, bir **domain adı** **temin etmenizle** başlar. Sonra bunu bir DNS server’ında (muhtemelen aynı cloud provider’ınızda) yapılandırırsınız. + +Muhtemelen bir cloud server (virtual machine) ya da benzeri bir şey alırsınız ve bunun fixed bir **public IP adresi** olur. + +DNS server(lar)ında, bir kaydı ("`A record`") **domain**’inizi server’ınızın **public IP adresine** yönlendirecek şekilde yapılandırırsınız. + +Bunu büyük olasılıkla ilk kurulumda, sadece bir kez yaparsınız. + +/// tip | İpucu + +Bu Domain Adı kısmı HTTPS’ten çok daha önce gelir. Ancak her şey domain ve IP adresine bağlı olduğu için burada bahsetmeye değer. + +/// + +### DNS { #dns } + +Şimdi gerçek HTTPS parçalarına odaklanalım. + +Önce tarayıcı, bu örnekte `someapp.example.com` olan domain için **IP**’nin ne olduğunu **DNS server**’larına sorar. + +DNS server’ları tarayıcıya belirli bir **IP adresini** kullanmasını söyler. Bu, DNS server’larında yapılandırdığınız ve server’ınızın kullandığı public IP adresidir. + + + +### TLS Handshake Başlangıcı { #tls-handshake-start } + +Tarayıcı daha sonra bu IP adresiyle **443 portu** (HTTPS portu) üzerinden iletişim kurar. + +İletişimin ilk kısmı, client ile server arasında bağlantıyı kurmak ve hangi kriptografik anahtarların kullanılacağına karar vermek vb. içindir. + + + +Client ile server arasındaki, TLS bağlantısını kurmaya yönelik bu etkileşime **TLS handshake** denir. + +### SNI Extension’ı ile TLS { #tls-with-sni-extension } + +Server’da, belirli bir **IP adresindeki** belirli bir **portu** dinleyen **yalnızca bir process** olabilir. Aynı IP adresinde başka portları dinleyen başka process’ler olabilir, ancak IP+port kombinasyonu başına yalnızca bir tane olur. + +TLS (HTTPS) varsayılan olarak `443` portunu kullanır. Yani ihtiyaç duyacağımız port budur. + +Bu portu yalnızca bir process dinleyebileceği için, bunu yapacak process **TLS Termination Proxy** olur. + +TLS Termination Proxy, bir ya da daha fazla **TLS sertifikasına** (HTTPS sertifikası) erişebilir. + +Yukarıda bahsettiğimiz **SNI extension**’ını kullanarak TLS Termination Proxy, bu bağlantı için elindeki TLS (HTTPS) sertifikalarından hangisini kullanacağını kontrol eder; client’ın beklediği domain ile eşleşen sertifikayı seçer. + +Bu örnekte `someapp.example.com` sertifikasını kullanır. + + + +Client, bu TLS sertifikasını üreten kuruluşa zaten **güvenir** (bu örnekte Let's Encrypt; birazdan ona da geleceğiz). Bu sayede sertifikanın geçerli olduğunu **doğrulayabilir**. + +Ardından client ve TLS Termination Proxy, sertifikayı kullanarak **TCP iletişiminin geri kalanını nasıl şifreleyeceklerine** karar verir. Böylece **TLS Handshake** kısmı tamamlanır. + +Bundan sonra client ve server arasında **şifreli bir TCP bağlantısı** vardır; TLS’in sağladığı şey budur. Sonra bu bağlantıyı kullanarak gerçek **HTTP iletişimini** başlatabilirler. + +Ve **HTTPS** de tam olarak budur: şifrelenmemiş bir TCP bağlantısı yerine, **güvenli bir TLS bağlantısının içinde** düz **HTTP**’dir. + +/// tip | İpucu + +Şifrelemenin HTTP seviyesinde değil, **TCP seviyesinde** gerçekleştiğine dikkat edin. + +/// + +### HTTPS Request { #https-request } + +Artık client ile server (özellikle tarayıcı ile TLS Termination Proxy) arasında **şifreli bir TCP bağlantısı** olduğuna göre, **HTTP iletişimi** başlayabilir. + +Dolayısıyla client bir **HTTPS request** gönderir. Bu, şifreli bir TLS bağlantısı üzerinden giden bir HTTP request’tir. + + + +### Request’in Şifresini Çözme { #decrypt-the-request } + +TLS Termination Proxy, üzerinde anlaşılan şifrelemeyi kullanarak **request’in şifresini çözer** ve **düz (şifresi çözülmüş) HTTP request**’i uygulamayı çalıştıran process’e iletir (ör. FastAPI uygulamasını çalıştıran Uvicorn process’i). + + + +### HTTP Response { #http-response } + +Uygulama request’i işler ve TLS Termination Proxy’ye **düz (şifrelenmemiş) bir HTTP response** gönderir. + + + +### HTTPS Response { #https-response } + +TLS Termination Proxy daha sonra response’u, daha önce üzerinde anlaşılan kriptografi ile (başlangıcı `someapp.example.com` sertifikasına dayanan) **şifreler** ve tarayıcıya geri gönderir. + +Sonrasında tarayıcı response’un geçerli olduğunu ve doğru kriptografik anahtarla şifrelendiğini doğrular vb. Ardından **response’un şifresini çözer** ve işler. + + + +Client (tarayıcı), response’un doğru server’dan geldiğini bilir; çünkü daha önce **HTTPS sertifikası** ile üzerinde anlaştıkları kriptografiyi kullanmaktadır. + +### Birden Fazla Uygulama { #multiple-applications } + +Aynı server’da (veya server’larda) örneğin başka API programları ya da bir veritabanı gibi **birden fazla uygulama** olabilir. + +Belirli IP ve port kombinasyonunu yalnızca bir process yönetebilir (örneğimizde TLS Termination Proxy). Ancak diğer uygulamalar/process’ler, aynı **public IP + port kombinasyonunu** kullanmaya çalışmadıkları sürece server(lar)da çalışabilir. + + + +Bu şekilde TLS Termination Proxy, birden fazla uygulama için **birden fazla domain**’in HTTPS ve sertifika işlerini yönetebilir ve her durumda request’leri doğru uygulamaya iletebilir. + +### Sertifika Yenileme { #certificate-renewal } + +Gelecekte bir noktada, her sertifikanın süresi **dolar** (temin edildikten yaklaşık 3 ay sonra). + +Ardından başka bir program (bazı durumlarda ayrı bir programdır, bazı durumlarda aynı TLS Termination Proxy olabilir) Let's Encrypt ile konuşup sertifika(ları) yeniler. + + + +**TLS sertifikaları** bir IP adresiyle değil, **domain adıyla ilişkilidir**. + +Bu yüzden sertifikaları yenilemek için, yenileme programı otoriteye (Let's Encrypt) gerçekten o domain’i **"sahiplendiğini" ve kontrol ettiğini** **kanıtlamalıdır**. + +Bunu yapmak ve farklı uygulama ihtiyaçlarını karşılamak için birden fazla yöntem vardır. Yaygın yöntemlerden bazıları: + +* Bazı **DNS kayıtlarını değiştirmek**. + * Bunun için yenileme programının DNS provider API’lerini desteklemesi gerekir. Dolayısıyla kullandığınız DNS provider’a bağlı olarak bu seçenek mümkün de olabilir, olmayabilir de. +* Domain ile ilişkili public IP adresinde **server olarak çalışmak** (en azından sertifika temin sürecinde). + * Yukarıda söylediğimiz gibi, belirli bir IP ve portu yalnızca bir process dinleyebilir. + * Bu, aynı TLS Termination Proxy’nin sertifika yenileme sürecini de yönetmesinin neden çok faydalı olduğunun sebeplerinden biridir. + * Aksi halde TLS Termination Proxy’yi kısa süreliğine durdurmanız, sertifikaları temin etmek için yenileme programını başlatmanız, sonra bunları TLS Termination Proxy ile yapılandırmanız ve ardından TLS Termination Proxy’yi tekrar başlatmanız gerekebilir. Bu ideal değildir; çünkü TLS Termination Proxy kapalıyken uygulama(lar)ınıza erişilemez. + +Uygulamayı servis etmeye devam ederken tüm bu yenileme sürecini yönetebilmek, TLS sertifikalarını doğrudan uygulama server’ıyla (örn. Uvicorn) kullanmak yerine, TLS Termination Proxy ile HTTPS’i yönetecek **ayrı bir sistem** istemenizin başlıca nedenlerinden biridir. + +## Proxy Forwarded Headers { #proxy-forwarded-headers } + +HTTPS’i bir proxy ile yönetirken, **application server**’ınız (örneğin FastAPI CLI üzerinden Uvicorn) HTTPS süreci hakkında hiçbir şey bilmez; **TLS Termination Proxy** ile düz HTTP üzerinden iletişim kurar. + +Bu **proxy** normalde request’i **application server**’a iletmeden önce, request’in proxy tarafından **forward** edildiğini application server’a bildirmek için bazı HTTP header’larını anlık olarak ekler. + +/// note | Teknik Detaylar + +Proxy header’ları şunlardır: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +Buna rağmen **application server**, güvenilen bir **proxy** arkasında olduğunu bilmediği için varsayılan olarak bu header’lara güvenmez. + +Ancak **application server**’ı, **proxy**’nin gönderdiği *forwarded* header’larına güvenecek şekilde yapılandırabilirsiniz. FastAPI CLI kullanıyorsanız, hangi IP’lerden gelen *forwarded* header’lara güvenmesi gerektiğini söylemek için *CLI Option* `--forwarded-allow-ips` seçeneğini kullanabilirsiniz. + +Örneğin **application server** yalnızca güvenilen **proxy**’den iletişim alıyorsa, yalnızca **proxy**’nin kullandığı IP’den request alacağı için `--forwarded-allow-ips="*"` ayarlayıp gelen tüm IP’lere güvenmesini sağlayabilirsiniz. + +Bu sayede uygulama kendi public URL’inin ne olduğunu, HTTPS kullanıp kullanmadığını, domain’i vb. bilebilir. + +Bu, örneğin redirect’leri doğru şekilde yönetmek için faydalıdır. + +/// tip | İpucu + +Bununla ilgili daha fazlasını [Behind a Proxy - Enable Proxy Forwarded Headers](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} dokümantasyonunda öğrenebilirsiniz. + +/// + +## Özet { #recap } + +**HTTPS** kullanmak çok önemlidir ve çoğu durumda oldukça **kritiktir**. Geliştirici olarak HTTPS etrafında harcadığınız çabanın büyük kısmı, aslında **bu kavramları** ve nasıl çalıştıklarını **anlamaktır**. + +Ancak **geliştiriciler için HTTPS**’in temel bilgilerini öğrendikten sonra, her şeyi basitçe yönetmek için farklı araçları kolayca birleştirip yapılandırabilirsiniz. + +Sonraki bölümlerin bazılarında, **FastAPI** uygulamaları için **HTTPS**’i nasıl kuracağınıza dair birkaç somut örnek göstereceğim. 🔒 diff --git a/docs/tr/docs/deployment/manually.md b/docs/tr/docs/deployment/manually.md new file mode 100644 index 000000000..561ba8677 --- /dev/null +++ b/docs/tr/docs/deployment/manually.md @@ -0,0 +1,157 @@ +# Bir Sunucuyu Manuel Olarak Çalıştırın { #run-a-server-manually } + +## `fastapi run` Komutunu Kullanın { #use-the-fastapi-run-command } + +Kısacası, FastAPI uygulamanızı sunmak için `fastapi run` kullanın: + +
+ +```console +$ fastapi run main.py + + FastAPI Starting production 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://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) +``` + +
+ +Bu, çoğu durumda işinizi görür. 😎 + +Örneğin bu komutu, **FastAPI** app'inizi bir container içinde, bir sunucuda vb. başlatmak için kullanabilirsiniz. + +## ASGI Sunucuları { #asgi-servers } + +Şimdi biraz daha detaya inelim. + +FastAPI, Python web framework'leri ve sunucularını inşa etmek için kullanılan ASGI adlı bir standardı kullanır. FastAPI bir ASGI web framework'üdür. + +Uzak bir sunucu makinesinde **FastAPI** uygulamasını (veya herhangi bir ASGI uygulamasını) çalıştırmak için gereken ana şey, **Uvicorn** gibi bir ASGI server programıdır. `fastapi` komutuyla varsayılan olarak gelen de budur. + +Buna alternatif birkaç seçenek daha vardır, örneğin: + +* Uvicorn: yüksek performanslı bir ASGI server. +* Hypercorn: diğer özelliklerin yanında HTTP/2 ve Trio ile uyumlu bir ASGI server. +* Daphne: Django Channels için geliştirilmiş ASGI server. +* Granian: Python uygulamaları için bir Rust HTTP server. +* NGINX Unit: NGINX Unit, hafif ve çok yönlü bir web uygulaması runtime'ıdır. + +## Sunucu Makinesi ve Sunucu Programı { #server-machine-and-server-program } + +İsimlendirme konusunda akılda tutulması gereken küçük bir detay var. 💡 + +"**server**" kelimesi yaygın olarak hem uzak/bulut bilgisayarı (fiziksel veya sanal makine) hem de o makinede çalışan programı (ör. Uvicorn) ifade etmek için kullanılır. + +Dolayısıyla genel olarak "server" dendiğinde, bu iki şeyden birini kast ediyor olabilir. + +Uzak makineden bahsederken genelde **server** denir; ayrıca **machine**, **VM** (virtual machine), **node** ifadeleri de kullanılır. Bunların hepsi, genellikle Linux çalıştıran ve üzerinde programlarınızı çalıştırdığınız bir tür uzak makineyi ifade eder. + +## Sunucu Programını Yükleyin { #install-the-server-program } + +FastAPI'yi kurduğunuzda, production sunucusu olarak Uvicorn da beraberinde gelir ve bunu `fastapi run` komutuyla başlatabilirsiniz. + +Ancak bir ASGI server'ı manuel olarak da kurabilirsiniz. + +Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, etkinleştirdiğinizden emin olun; ardından server uygulamasını kurabilirsiniz. + +Örneğin Uvicorn'u kurmak için: + +
+ +```console +$ pip install "uvicorn[standard]" + +---> 100% +``` + +
+ +Benzer bir süreç, diğer ASGI server programlarının tamamı için de geçerlidir. + +/// tip | İpucu + +`standard` eklediğinizde Uvicorn, önerilen bazı ek bağımlılıkları kurar ve kullanır. + +Bunlara, `asyncio` için yüksek performanslı bir drop-in replacement olan ve concurrency performansını ciddi şekilde artıran `uvloop` da dahildir. + +FastAPI'yi `pip install "fastapi[standard]"` gibi bir şekilde kurduğunuzda `uvicorn[standard]` da zaten kurulmuş olur. + +/// + +## Sunucu Programını Çalıştırın { #run-the-server-program } + +Bir ASGI server'ı manuel olarak kurduysanız, FastAPI uygulamanızı import edebilmesi için genellikle özel bir formatta bir import string geçirmeniz gerekir: + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 80 + +INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) +``` + +
+ +/// note | Not + +`uvicorn main:app` komutu şunları ifade eder: + +* `main`: `main.py` dosyası (Python "module"). +* `app`: `main.py` içinde `app = FastAPI()` satırıyla oluşturulan nesne. + +Şununla eşdeğerdir: + +```Python +from main import app +``` + +/// + +Her alternatif ASGI server programı için benzer bir komut bulunur; daha fazlası için ilgili dokümantasyonlarına bakabilirsiniz. + +/// warning | Uyarı + +Uvicorn ve diğer sunucular, geliştirme sırasında faydalı olan `--reload` seçeneğini destekler. + +`--reload` seçeneği çok daha fazla kaynak tüketir, daha kararsızdır vb. + +**Geliştirme** sırasında çok yardımcı olur, ancak **production** ortamında kullanmamalısınız. + +/// + +## Deployment Kavramları { #deployment-concepts } + +Bu örnekler server programını (ör. Uvicorn) çalıştırır; **tek bir process** başlatır, tüm IP'lerde (`0.0.0.0`) ve önceden belirlenmiş bir port'ta (ör. `80`) dinler. + +Temel fikir budur. Ancak muhtemelen şunlar gibi bazı ek konularla da ilgilenmek isteyeceksiniz: + +* Güvenlik - HTTPS +* Açılışta çalıştırma +* Yeniden başlatmalar +* Replikasyon (çalışan process sayısı) +* Bellek +* Başlatmadan önceki adımlar + +Sonraki bölümlerde bu kavramların her birini nasıl düşünmeniz gerektiğini ve bunlarla başa çıkmak için kullanabileceğiniz somut örnekleri/stratejileri anlatacağım. 🚀 diff --git a/docs/tr/docs/deployment/server-workers.md b/docs/tr/docs/deployment/server-workers.md new file mode 100644 index 000000000..faae4ef92 --- /dev/null +++ b/docs/tr/docs/deployment/server-workers.md @@ -0,0 +1,139 @@ +# Server Workers - Worker'larla Uvicorn { #server-workers-uvicorn-with-workers } + +Önceki bölümlerde bahsettiğimiz deployment kavramlarına tekrar bakalım: + +* Güvenlik - HTTPS +* Başlangıçta çalıştırma +* Yeniden başlatmalar +* **Replikasyon (çalışan process sayısı)** +* Bellek +* Başlatmadan önceki adımlar + +Bu noktaya kadar, dokümantasyondaki tüm tutorial'larla muhtemelen bir **server programı** çalıştırıyordunuz; örneğin Uvicorn'u çalıştıran `fastapi` komutunu kullanarak ve **tek bir process** ile. + +Uygulamaları deploy ederken, **çok çekirdekten (multiple cores)** faydalanmak ve daha fazla request'i karşılayabilmek için büyük olasılıkla **process replikasyonu** (birden fazla process) isteyeceksiniz. + +[Daha önceki Deployment Concepts](concepts.md){.internal-link target=_blank} bölümünde gördüğünüz gibi, kullanabileceğiniz birden fazla strateji var. + +Burada, `fastapi` komutunu kullanarak ya da `uvicorn` komutunu doğrudan çalıştırarak **worker process**'lerle **Uvicorn**'u nasıl kullanacağınızı göstereceğim. + +/// info | Bilgi + +Container kullanıyorsanız (örneğin Docker veya Kubernetes ile), bununla ilgili daha fazlasını bir sonraki bölümde anlatacağım: [Container'larda FastAPI - Docker](docker.md){.internal-link target=_blank}. + +Özellikle **Kubernetes** üzerinde çalıştırırken, büyük olasılıkla worker kullanmak **istemeyeceksiniz**; bunun yerine **container başına tek bir Uvicorn process** çalıştırmak daha uygundur. Ancak bunu da o bölümde detaylandıracağım. + +/// + +## Birden Fazla Worker { #multiple-workers } + +Komut satırında `--workers` seçeneğiyle birden fazla worker başlatabilirsiniz: + +//// tab | `fastapi` + +`fastapi` komutunu kullanıyorsanız: + +
+ +```console +$ fastapi run --workers 4 main.py + + FastAPI Starting production 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://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. +``` + +
+ +//// + +//// tab | `uvicorn` + +`uvicorn` komutunu doğrudan kullanmayı tercih ederseniz: + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 +INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit) +INFO: Started parent process [27365] +INFO: Started server process [27368] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27369] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27370] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27367] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +//// + +Buradaki tek yeni seçenek `--workers`; bu seçenek Uvicorn'a 4 adet worker process başlatmasını söyler. + +Ayrıca her process'in **PID**'inin gösterildiğini de görebilirsiniz: parent process için `27365` (bu **process manager**), her worker process için de bir PID: `27368`, `27369`, `27370` ve `27367`. + +## Deployment Kavramları { #deployment-concepts } + +Burada, uygulamanın çalışmasını **paralelleştirmek**, CPU'daki **çok çekirdekten** yararlanmak ve **daha fazla request** karşılayabilmek için birden fazla **worker**'ı nasıl kullanacağınızı gördünüz. + +Yukarıdaki deployment kavramları listesinden, worker kullanımı ağırlıklı olarak **replikasyon** kısmına yardımcı olur, ayrıca **yeniden başlatmalar** konusunda da az da olsa katkı sağlar. Ancak diğerlerini yine sizin yönetmeniz gerekir: + +* **Güvenlik - HTTPS** +* **Başlangıçta çalıştırma** +* ***Yeniden başlatmalar*** +* Replikasyon (çalışan process sayısı) +* **Bellek** +* **Başlatmadan önceki adımlar** + +## Container'lar ve Docker { #containers-and-docker } + +Bir sonraki bölümde, [Container'larda FastAPI - Docker](docker.md){.internal-link target=_blank} üzerinden diğer **deployment kavramlarını** ele almak için kullanabileceğiniz bazı stratejileri anlatacağım. + +Tek bir Uvicorn process çalıştıracak şekilde **sıfırdan kendi image'ınızı oluşturmayı** göstereceğim. Bu oldukça basit bir süreçtir ve **Kubernetes** gibi dağıtık bir container yönetim sistemi kullanırken büyük olasılıkla yapmak isteyeceğiniz şey de budur. + +## Özet { #recap } + +**Çok çekirdekli CPU**'lardan faydalanmak ve **birden fazla process'i paralel** çalıştırmak için `fastapi` veya `uvicorn` komutlarıyla `--workers` CLI seçeneğini kullanarak birden fazla worker process çalıştırabilirsiniz. + +Diğer deployment kavramlarını da kendiniz ele alarak **kendi deployment sisteminizi** kuruyorsanız, bu araçları ve fikirleri kullanabilirsiniz. + +Container'larla (örn. Docker ve Kubernetes) **FastAPI**'yi öğrenmek için bir sonraki bölüme göz atın. Bu araçların, diğer **deployment kavramlarını** çözmek için de basit yöntemleri olduğunu göreceksiniz. ✨ diff --git a/docs/tr/docs/deployment/versions.md b/docs/tr/docs/deployment/versions.md new file mode 100644 index 000000000..c3fb5d9bd --- /dev/null +++ b/docs/tr/docs/deployment/versions.md @@ -0,0 +1,93 @@ +# FastAPI Sürümleri Hakkında { #about-fastapi-versions } + +**FastAPI** hâlihazırda birçok uygulama ve sistemde production ortamında kullanılmaktadır. Ayrıca test kapsamı %100 seviyesinde tutulmaktadır. Ancak geliştirme süreci hâlâ hızlı şekilde ilerlemektedir. + +Yeni özellikler sık sık eklenir, bug'lar düzenli olarak düzeltilir ve kod sürekli iyileştirilmektedir. + +Bu yüzden mevcut sürümler hâlâ `0.x.x` şeklindedir; bu da her sürümde breaking change olma ihtimalini yansıtır. Bu yaklaşım Semantic Versioning kurallarını takip eder. + +Şu anda **FastAPI** ile production uygulamaları geliştirebilirsiniz (muhtemelen bir süredir yapıyorsunuz da); sadece kodunuzun geri kalanıyla doğru çalışan bir sürüm kullandığınızdan emin olmanız gerekir. + +## `fastapi` sürümünü sabitleyin { #pin-your-fastapi-version } + +İlk yapmanız gereken, kullandığınız **FastAPI** sürümünü uygulamanızla doğru çalıştığını bildiğiniz belirli bir güncel sürüme "sabitlemek" (pinlemek) olmalı. + +Örneğin, uygulamanızda `0.112.0` sürümünü kullandığınızı varsayalım. + +`requirements.txt` dosyası kullanıyorsanız sürümü şöyle belirtebilirsiniz: + +```txt +fastapi[standard]==0.112.0 +``` + +Bu, tam olarak `0.112.0` sürümünü kullanacağınız anlamına gelir. + +Ya da şu şekilde de sabitleyebilirsiniz: + +```txt +fastapi[standard]>=0.112.0,<0.113.0 +``` + +Bu da `0.112.0` ve üzeri, ama `0.113.0` altındaki sürümleri kullanacağınız anlamına gelir; örneğin `0.112.2` gibi bir sürüm de kabul edilir. + +Kurulumları yönetmek için `uv`, Poetry, Pipenv gibi başka bir araç (veya benzerleri) kullanıyorsanız, bunların hepsinde paketler için belirli sürümler tanımlamanın bir yolu vardır. + +## Mevcut sürümler { #available-versions } + +Mevcut sürümleri (ör. en güncel son sürümün hangisi olduğunu kontrol etmek için) [Release Notes](../release-notes.md){.internal-link target=_blank} sayfasında görebilirsiniz. + +## Sürümler Hakkında { #about-versions } + +Semantic Versioning kurallarına göre, `1.0.0` altındaki herhangi bir sürüm breaking change içerebilir. + +FastAPI ayrıca "PATCH" sürüm değişikliklerinin bug fix'ler ve breaking olmayan değişiklikler için kullanılması kuralını da takip eder. + +/// tip | İpucu + +"PATCH" son sayıdır. Örneğin `0.2.3` içinde PATCH sürümü `3`'tür. + +/// + +Dolayısıyla şu şekilde bir sürüme sabitleyebilmelisiniz: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +Breaking change'ler ve yeni özellikler "MINOR" sürümlerde eklenir. + +/// tip | İpucu + +"MINOR" ortadaki sayıdır. Örneğin `0.2.3` içinde MINOR sürümü `2`'dir. + +/// + +## FastAPI Sürümlerini Yükseltme { #upgrading-the-fastapi-versions } + +Uygulamanız için test'ler eklemelisiniz. + +**FastAPI** ile bu çok kolaydır (Starlette sayesinde). Dokümantasyona bakın: [Testing](../tutorial/testing.md){.internal-link target=_blank} + +Test'leriniz olduktan sonra **FastAPI** sürümünü daha yeni bir sürüme yükseltebilir ve test'lerinizi çalıştırarak tüm kodunuzun doğru çalıştığından emin olabilirsiniz. + +Her şey çalışıyorsa (ya da gerekli değişiklikleri yaptıktan sonra) ve tüm test'leriniz geçiyorsa, `fastapi` sürümünü o yeni sürüme sabitleyebilirsiniz. + +## Starlette Hakkında { #about-starlette } + +`starlette` sürümünü sabitlememelisiniz. + +**FastAPI**'nin farklı sürümleri, Starlette'in belirli (daha yeni) bir sürümünü kullanır. + +Bu yüzden **FastAPI**'nin doğru Starlette sürümünü kullanmasına izin verebilirsiniz. + +## Pydantic Hakkında { #about-pydantic } + +Pydantic, **FastAPI** için olan test'leri kendi test'lerinin içine dahil eder; bu yüzden Pydantic'in yeni sürümleri (`1.0.0` üzeri) her zaman FastAPI ile uyumludur. + +Pydantic'i sizin için çalışan `1.0.0` üzerindeki herhangi bir sürüme sabitleyebilirsiniz. + +Örneğin: + +```txt +pydantic>=2.7.0,<3.0.0 +``` diff --git a/docs/tr/docs/environment-variables.md b/docs/tr/docs/environment-variables.md new file mode 100644 index 000000000..e4f769a16 --- /dev/null +++ b/docs/tr/docs/environment-variables.md @@ -0,0 +1,298 @@ +# Ortam Değişkenleri { #environment-variables } + +/// tip | İpucu + +"Ortam değişkenleri"nin ne olduğunu ve nasıl kullanılacağını zaten biliyorsanız, bu bölümü atlayabilirsiniz. + +/// + +Ortam değişkeni (genelde "**env var**" olarak da anılır), Python kodunun **dışında**, **işletim sistemi** seviyesinde bulunan ve Python kodunuz (veya diğer programlar) tarafından okunabilen bir değişkendir. + +Ortam değişkenleri; uygulama **ayarları**nı yönetmek, Python’un **kurulumu**nun bir parçası olarak konfigürasyon yapmak vb. durumlarda işe yarar. + +## Env Var Oluşturma ve Kullanma { #create-and-use-env-vars } + +Python’a ihtiyaç duymadan, **shell (terminal)** içinde ortam değişkenleri **oluşturabilir** ve kullanabilirsiniz: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// You could create an env var MY_NAME with +$ export MY_NAME="Wade Wilson" + +// Then you could use it with other programs, like +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Create an env var MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Use it with other programs, like +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## Python’da env var Okuma { #read-env-vars-in-python } + +Ortam değişkenlerini Python’un **dışında** (terminalde veya başka bir yöntemle) oluşturup daha sonra **Python’da okuyabilirsiniz**. + +Örneğin `main.py` adında bir dosyanız şöyle olabilir: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip | İpucu + +`os.getenv()` fonksiyonunun ikinci argümanı, bulunamadığında döndürülecek varsayılan (default) değerdir. + +Verilmezse varsayılan olarak `None` olur; burada varsayılan değer olarak `"World"` verdik. + +/// + +Sonrasında bu Python programını çalıştırabilirsiniz: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ export MY_NAME="Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ $Env:MY_NAME = "Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +Ortam değişkenleri kodun dışında ayarlanabildiği, ama kod tarafından okunabildiği ve dosyalarla birlikte saklanmasının (ör. `git`’e commit edilmesinin) gerekmediği için, konfigürasyon veya **ayarlar** için sıkça kullanılır. + +Ayrıca, bir ortam değişkenini yalnızca **belirli bir program çalıştırımı** için oluşturabilirsiniz; bu değişken sadece o program tarafından, sadece o çalıştırma süresince kullanılabilir. + +Bunu yapmak için, program komutunun hemen öncesinde ve aynı satırda tanımlayın: + +
+ +```console +// Create an env var MY_NAME in line for this program call +$ MY_NAME="Wade Wilson" python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python + +// The env var no longer exists afterwards +$ python main.py + +Hello World from Python +``` + +
+ +/// tip | İpucu + +Bu konuyla ilgili daha fazlasını The Twelve-Factor App: Config bölümünde okuyabilirsiniz. + +/// + +## Tipler ve Doğrulama { #types-and-validation } + +Bu ortam değişkenleri yalnızca **metin string**’lerini taşıyabilir. Çünkü Python’un dışındadırlar ve diğer programlarla, sistemin geri kalanıyla (hatta Linux, Windows, macOS gibi farklı işletim sistemleriyle) uyumlu olmak zorundadırlar. + +Bu, Python’da bir ortam değişkeninden okunan **her değerin `str` olacağı** anlamına gelir. Farklı bir tipe dönüştürme veya doğrulama işlemleri kod içinde yapılmalıdır. + +Uygulama **ayarları**nı yönetmek için ortam değişkenlerini kullanmayı, [İleri Seviye Kullanıcı Rehberi - Ayarlar ve Ortam Değişkenleri](./advanced/settings.md){.internal-link target=_blank} bölümünde daha detaylı öğreneceksiniz. + +## `PATH` Ortam Değişkeni { #path-environment-variable } + +İşletim sistemlerinin (Linux, macOS, Windows) çalıştırılacak programları bulmak için kullandığı **özel** bir ortam değişkeni vardır: **`PATH`**. + +`PATH` değişkeninin değeri uzun bir string’dir; Linux ve macOS’te dizinler iki nokta üst üste `:` ile, Windows’ta ise noktalı virgül `;` ile ayrılır. + +Örneğin `PATH` ortam değişkeni şöyle görünebilir: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Bu, sistemin şu dizinlerde program araması gerektiği anlamına gelir: + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +Bu, sistemin şu dizinlerde program araması gerektiği anlamına gelir: + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +Terminalde bir **komut** yazdığınızda, işletim sistemi `PATH` ortam değişkeninde listelenen **bu dizinlerin her birinde** programı **arar**. + +Örneğin terminalde `python` yazdığınızda, işletim sistemi bu listedeki **ilk dizinde** `python` adlı bir program arar. + +Bulursa **onu kullanır**. Bulamazsa **diğer dizinlerde** aramaya devam eder. + +### Python Kurulumu ve `PATH`’in Güncellenmesi { #installing-python-and-updating-the-path } + +Python’u kurarken, `PATH` ortam değişkenini güncellemek isteyip istemediğiniz sorulabilir. + +//// tab | Linux, macOS + +Diyelim ki Python’u kurdunuz ve `/opt/custompython/bin` dizinine yüklendi. + +`PATH` ortam değişkenini güncellemeyi seçerseniz, kurulum aracı `/opt/custompython/bin` yolunu `PATH` ortam değişkenine ekler. + +Şöyle görünebilir: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +Böylece terminalde `python` yazdığınızda, sistem `/opt/custompython/bin` (son dizin) içindeki Python programını bulur ve onu kullanır. + +//// + +//// tab | Windows + +Diyelim ki Python’u kurdunuz ve `C:\opt\custompython\bin` dizinine yüklendi. + +`PATH` ortam değişkenini güncellemeyi seçerseniz, kurulum aracı `C:\opt\custompython\bin` yolunu `PATH` ortam değişkenine ekler. + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +Böylece terminalde `python` yazdığınızda, sistem `C:\opt\custompython\bin` (son dizin) içindeki Python programını bulur ve onu kullanır. + +//// + +Yani şunu yazarsanız: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +Sistem `python` programını `/opt/custompython/bin` içinde **bulur** ve çalıştırır. + +Bu, kabaca şunu yazmaya denktir: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +Sistem `python` programını `C:\opt\custompython\bin\python` içinde **bulur** ve çalıştırır. + +Bu, kabaca şunu yazmaya denktir: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +Bu bilgiler, [Virtual Environments](virtual-environments.md){.internal-link target=_blank} konusunu öğrenirken işinize yarayacak. + +## Sonuç { #conclusion } + +Buraya kadar **ortam değişkenleri**nin ne olduğuna ve Python’da nasıl kullanılacağına dair temel bir fikir edinmiş olmalısınız. + +Ayrıca Wikipedia for Environment Variable sayfasından daha fazlasını da okuyabilirsiniz. + +Çoğu zaman ortam değişkenlerinin hemen nasıl işe yarayacağı ilk bakışta çok net olmayabilir. Ancak geliştirme yaparken birçok farklı senaryoda tekrar tekrar karşınıza çıkarlar; bu yüzden bunları bilmek faydalıdır. + +Örneğin bir sonraki bölümde, [Virtual Environments](virtual-environments.md) konusunda bu bilgilere ihtiyaç duyacaksınız. diff --git a/docs/tr/docs/fastapi-cli.md b/docs/tr/docs/fastapi-cli.md new file mode 100644 index 000000000..4680d4bb6 --- /dev/null +++ b/docs/tr/docs/fastapi-cli.md @@ -0,0 +1,75 @@ +# FastAPI CLI { #fastapi-cli } + +**FastAPI CLI**, FastAPI uygulamanızı servis etmek, FastAPI projenizi yönetmek ve daha fazlası için kullanabileceğiniz bir komut satırı programıdır. + +FastAPI'yi kurduğunuzda (ör. `pip install "fastapi[standard]"`), beraberinde `fastapi-cli` adlı bir paket de gelir; bu paket terminalde `fastapi` komutunu sağlar. + +FastAPI uygulamanızı geliştirme için çalıştırmak üzere `fastapi dev` komutunu kullanabilirsiniz: + +
+ +```console +$ 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` adlı bu komut satırı programı, **FastAPI CLI**'dır. + +FastAPI CLI, Python programınızın path'ini (ör. `main.py`) alır; `FastAPI` instance'ını (genellikle `app` olarak adlandırılır) otomatik olarak tespit eder, doğru import sürecini belirler ve ardından uygulamayı servis eder. + +Production için bunun yerine `fastapi run` kullanırsınız. 🚀 + +İçeride **FastAPI CLI**, yüksek performanslı, production'a hazır bir ASGI server olan Uvicorn'u kullanır. 😎 + +## `fastapi dev` { #fastapi-dev } + +`fastapi dev` çalıştırmak, geliştirme modunu başlatır. + +Varsayılan olarak **auto-reload** etkindir; kodunuzda değişiklik yaptığınızda server'ı otomatik olarak yeniden yükler. Bu, kaynak tüketimi yüksek bir özelliktir ve kapalı olduğuna kıyasla daha az stabil olabilir. Sadece geliştirme sırasında kullanmalısınız. Ayrıca yalnızca `127.0.0.1` IP adresini dinler; bu, makinenizin sadece kendisiyle iletişim kurması için kullanılan IP'dir (`localhost`). + +## `fastapi run` { #fastapi-run } + +`fastapi run` çalıştırmak, varsayılan olarak FastAPI'yi production modunda başlatır. + +Varsayılan olarak **auto-reload** kapalıdır. Ayrıca `0.0.0.0` IP adresini dinler; bu, kullanılabilir tüm IP adresleri anlamına gelir. Böylece makineyle iletişim kurabilen herkes tarafından genel erişime açık olur. Bu, normalde production'da çalıştırma şeklidir; örneğin bir container içinde. + +Çoğu durumda (ve genellikle yapmanız gereken şekilde) üst tarafta sizin yerinize HTTPS'i yöneten bir "termination proxy" bulunur. Bu, uygulamanızı nasıl deploy ettiğinize bağlıdır; sağlayıcınız bunu sizin için yapabilir ya da sizin ayrıca kurmanız gerekebilir. + +/// tip | İpucu + +Bununla ilgili daha fazla bilgiyi [deployment dokümantasyonunda](deployment/index.md){.internal-link target=_blank} bulabilirsiniz. + +/// diff --git a/docs/tr/docs/help-fastapi.md b/docs/tr/docs/help-fastapi.md new file mode 100644 index 000000000..785c0ae0d --- /dev/null +++ b/docs/tr/docs/help-fastapi.md @@ -0,0 +1,256 @@ +# FastAPI'ye Yardım Et - Yardım Al { #help-fastapi-get-help } + +**FastAPI**'yi seviyor musunuz? + +FastAPI'ye, diğer kullanıcılara ve yazara yardım etmek ister misiniz? + +Yoksa **FastAPI** ile ilgili yardım mı almak istiyorsunuz? + +Yardım etmenin çok basit yolları var (bazıları sadece bir-iki tıklama gerektirir). + +Yardım almanın da birkaç yolu var. + +## Bültene abone olun { #subscribe-to-the-newsletter } + +Şunlardan haberdar olmak için (seyrek yayımlanan) [**FastAPI and friends** bültenine](newsletter.md){.internal-link target=_blank} abone olabilirsiniz: + +* FastAPI ve friends ile ilgili haberler 🚀 +* Rehberler 📝 +* Özellikler ✨ +* Geriye dönük uyumsuz değişiklikler 🚨 +* İpuçları ve püf noktaları ✅ + +## X (Twitter) üzerinden FastAPI'yi takip edin { #follow-fastapi-on-x-twitter } + +**FastAPI** ile ilgili en güncel haberleri almak için @fastapi hesabını **X (Twitter)** üzerinde takip edin. 🐦 + +## GitHub'da **FastAPI**'ye yıldız verin { #star-fastapi-in-github } + +GitHub'da FastAPI'ye "star" verebilirsiniz (sağ üstteki yıldız butonuna tıklayarak): https://github.com/fastapi/fastapi. ⭐️ + +Yıldız verince, diğer kullanıcılar projeyi daha kolay bulabilir ve başkaları için de faydalı olduğunu görebilir. + +## GitHub repository'sini release'ler için izleyin { #watch-the-github-repository-for-releases } + +GitHub'da FastAPI'yi "watch" edebilirsiniz (sağ üstteki "watch" butonuna tıklayarak): https://github.com/fastapi/fastapi. 👀 + +Orada "Releases only" seçebilirsiniz. + +Böylece **FastAPI**'nin bug fix'ler ve yeni özelliklerle gelen her yeni release'inde (yeni versiyonunda) email ile bildirim alırsınız. + +## Yazarla bağlantı kurun { #connect-with-the-author } + +Yazar olan benimle (Sebastián Ramírez / `tiangolo`) bağlantı kurabilirsiniz. + +Şunları yapabilirsiniz: + +* Beni **GitHub**'da takip edin. + * Size yardımcı olabilecek oluşturduğum diğer Open Source projelere göz atın. + * Yeni bir Open Source proje oluşturduğumda haberdar olmak için beni takip edin. +* Beni **X (Twitter)** üzerinde veya Mastodon'da takip edin. + * FastAPI'yi nasıl kullandığınızı anlatın (bunu duymayı seviyorum). + * Duyuru yaptığımda veya yeni araçlar yayınladığımda haberdar olun. + * Ayrıca (ayrı bir hesap olan) X (Twitter) üzerinde @fastapi hesabını da takip edebilirsiniz. +* Beni **LinkedIn**'de takip edin. + * Duyuru yaptığımda veya yeni araçlar yayınladığımda haberdar olun (gerçi X (Twitter)'ı daha sık kullanıyorum 🤷‍♂). +* **Dev.to** veya **Medium** üzerinde yazdıklarımı okuyun (ya da beni takip edin). + * Diğer fikirleri, yazıları ve oluşturduğum araçlarla ilgili içerikleri okuyun. + * Yeni bir şey yayınladığımda görmek için beni takip edin. + +## **FastAPI** hakkında tweet atın { #tweet-about-fastapi } + +**FastAPI** hakkında tweet atın ve neden sevdiğinizi bana ve diğerlerine söyleyin. 🎉 + +**FastAPI**'nin nasıl kullanıldığını, nelerini sevdiğinizi, hangi projede/şirkette kullandığınızı vb. duymayı seviyorum. + +## FastAPI için oy verin { #vote-for-fastapi } + +* Slant'ta **FastAPI** için oy verin. +* AlternativeTo'da **FastAPI** için oy verin. +* StackShare'de **FastAPI** kullandığınızı belirtin. + +## GitHub'da sorularla başkalarına yardım edin { #help-others-with-questions-in-github } + +Şuralarda insanların sorularına yardımcı olmayı deneyebilirsiniz: + +* GitHub Discussions +* GitHub Issues + +Birçok durumda bu soruların cevabını zaten biliyor olabilirsiniz. 🤓 + +Eğer insanların sorularına çok yardım ederseniz, resmi bir [FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank} olabilirsiniz. 🎉 + +Şunu unutmayın: en önemli nokta, nazik olmaya çalışmak. İnsanlar çoğu zaman biriken stresle geliyor ve birçok durumda soruyu en iyi şekilde sormuyor; yine de elinizden geldiğince nazik olmaya çalışın. 🤗 + +Amaç, **FastAPI** topluluğunun nazik ve kapsayıcı olması. Aynı zamanda başkalarına zorbalık ya da saygısız davranışları da kabul etmeyin. Birbirimizi kollamalıyız. + +--- + +Sorularda (discussions veya issues içinde) başkalarına yardım etmek için şunları yapabilirsiniz: + +### Soruyu anlayın { #understand-the-question } + +* Soru soran kişinin **amacının** ve kullanım senaryosunun ne olduğunu anlayabiliyor musunuz, kontrol edin. + +* Sonra sorunun (büyük çoğunluğu soru olur) **net** olup olmadığına bakın. + +* Birçok durumda kullanıcı kafasında hayali bir çözüm kurup onu sorar; ancak **daha iyi** bir çözüm olabilir. Problemi ve kullanım senaryosunu daha iyi anladıysanız daha iyi bir **alternatif çözüm** önerebilirsiniz. + +* Soruyu anlayamıyorsanız daha fazla **detay** isteyin. + +### Problemi yeniden üretin { #reproduce-the-problem } + +Çoğu durumda ve çoğu soruda, kişinin **orijinal kodu** ile ilgili bir şey vardır. + +Birçok kişi sadece kodun bir parçasını kopyalar, ama bu **problemi yeniden üretmek** için yeterli olmaz. + +* Çalıştırıp aynı hatayı/davranışı görebileceğiniz veya kullanım senaryosunu daha iyi anlayabileceğiniz, yerelde **kopyala-yapıştır** yaparak çalıştırılabilen bir minimal, reproducible, example paylaşmalarını isteyebilirsiniz. + +* Çok cömert hissediyorsanız, problemi anlatan açıklamadan yola çıkarak kendiniz de böyle bir **örnek oluşturmayı** deneyebilirsiniz. Ancak bunun çok zaman alabileceğini unutmayın; çoğu zaman önce problemi netleştirmelerini istemek daha iyidir. + +### Çözüm önerin { #suggest-solutions } + +* Soruyu anlayabildikten sonra olası bir **cevap** verebilirsiniz. + +* Çoğu durumda, yapmak istediklerinden ziyade alttaki **asıl problemi veya kullanım senaryosunu** anlamak daha iyidir; çünkü denedikleri yöntemden daha iyi bir çözüm yolu olabilir. + +### Kapatılmasını isteyin { #ask-to-close } + +Eğer yanıt verirlerse, büyük ihtimalle problemi çözmüşsünüzdür, tebrikler, **kahramansınız**! 🦸 + +* Eğer çözüm işe yaradıysa şunları yapmalarını isteyebilirsiniz: + + * GitHub Discussions'ta: ilgili yorumu **answer** olarak işaretlemeleri. + * GitHub Issues'ta: issue'yu **close** etmeleri. + +## GitHub repository'sini izleyin { #watch-the-github-repository } + +GitHub'da FastAPI'yi "watch" edebilirsiniz (sağ üstteki "watch" butonuna tıklayarak): https://github.com/fastapi/fastapi. 👀 + +"Releases only" yerine "Watching" seçerseniz biri yeni bir issue veya soru oluşturduğunda bildirim alırsınız. Ayrıca sadece yeni issue'lar, ya da discussions, ya da PR'lar vb. için bildirim almak istediğinizi belirtebilirsiniz. + +Sonra da bu soruları çözmelerine yardımcı olmayı deneyebilirsiniz. + +## Soru Sorun { #ask-questions } + +GitHub repository'sinde örneğin şunlar için yeni bir soru oluşturabilirsiniz: + +* Bir **soru** sorun veya bir **problem** hakkında danışın. +* Yeni bir **feature** önerin. + +**Not**: Bunu yaparsanız, ben de sizden başkalarına yardım etmenizi isteyeceğim. 😉 + +## Pull Request'leri İnceleyin { #review-pull-requests } + +Başkalarının gönderdiği pull request'leri incelememde bana yardımcı olabilirsiniz. + +Yine, lütfen elinizden geldiğince nazik olmaya çalışın. 🤗 + +--- + +Bir pull request'i incelerken akılda tutmanız gerekenler: + +### Problemi anlayın { #understand-the-problem } + +* Önce, pull request'in çözmeye çalıştığı **problemi anladığınızdan** emin olun. GitHub Discussion veya issue içinde daha uzun bir tartışması olabilir. + +* Pull request'in aslında hiç gerekmiyor olma ihtimali de yüksektir; çünkü problem **farklı bir şekilde** çözülebilir. Bu durumda bunu önerebilir veya bununla ilgili soru sorabilirsiniz. + +### Style konusunda çok dert etmeyin { #dont-worry-about-style } + +* Commit message tarzı gibi şeyleri çok dert etmeyin; ben commit'leri manuel olarak düzenleyerek squash and merge yapacağım. + +* Style kuralları için de endişelenmeyin; bunları kontrol eden otomatik araçlar zaten var. + +Ek bir style veya tutarlılık ihtiyacı olursa, bunu doğrudan isterim ya da gerekli değişikliklerle üstüne commit eklerim. + +### Kodu kontrol edin { #check-the-code } + +* Kodu okuyup kontrol edin; mantıklı mı bakın, **yerelde çalıştırın** ve gerçekten problemi çözüyor mu görün. + +* Ardından bunu yaptığınızı belirten bir **yorum** yazın; böylece gerçekten kontrol ettiğinizi anlarım. + +/// info | Bilgi + +Ne yazık ki sadece birkaç onayı olan PR'lara körü körüne güvenemem. + +Defalarca, 3, 5 veya daha fazla onayı olan PR'lar oldu; muhtemelen açıklaması çekici olduğu için onay aldılar. Ama PR'lara baktığımda aslında bozuk olduklarını, bug içerdiğini veya iddia ettikleri problemi çözmediklerini gördüm. 😅 + +Bu yüzden kodu gerçekten okuyup çalıştırmanız ve bunu yorumlarda bana bildirmeniz çok önemli. 🤓 + +/// + +* PR bir şekilde basitleştirilebiliyorsa bunu isteyebilirsiniz. Ancak çok didik didik etmeye gerek yok; konuya göre birçok öznel bakış açısı olabilir (benim de olacaktır 🙈). Bu yüzden temel noktalara odaklanmak daha iyi. + +### Testler { #tests } + +* PR'da **testler** olduğunu kontrol etmemde bana yardımcı olun. + +* PR'dan önce testlerin **fail** ettiğini kontrol edin. 🚨 + +* PR'dan sonra testlerin **pass** ettiğini kontrol edin. ✅ + +* Birçok PR test içermez; test eklemelerini **hatırlatabilirsiniz** veya hatta kendiniz bazı testler **önerebilirsiniz**. Bu, en çok zaman alan işlerden biridir ve burada çok yardımcı olabilirsiniz. + +* Ayrıca neleri denediğinizi yorumlara yazın; böylece kontrol ettiğinizi anlarım. 🤓 + +## Pull Request Oluşturun { #create-a-pull-request } + +Örneğin şunlar için Pull Request'lerle kaynak koda [katkıda bulunabilirsiniz](contributing.md){.internal-link target=_blank}: + +* Dokümantasyonda bulduğunuz bir yazım hatasını düzeltmek. +* FastAPI hakkında oluşturduğunuz veya bulduğunuz bir makaleyi, videoyu ya da podcast'i bu dosyayı düzenleyerek paylaşmak. + * Link'inizi ilgili bölümün başına eklediğinizden emin olun. +* Dokümantasyonu kendi dilinize [çevirmeye yardımcı olmak](contributing.md#translations){.internal-link target=_blank}. + * Başkalarının yaptığı çevirileri gözden geçirmeye de yardımcı olabilirsiniz. +* Yeni dokümantasyon bölümleri önermek. +* Mevcut bir issue/bug'ı düzeltmek. + * Test eklediğinizden emin olun. +* Yeni bir feature eklemek. + * Test eklediğinizden emin olun. + * İlgiliyse dokümantasyon da eklediğinizden emin olun. + +## FastAPI'nin Bakımına Yardım Edin { #help-maintain-fastapi } + +**FastAPI**'nin bakımını yapmama yardımcı olun! 🤓 + +Yapılacak çok iş var ve bunların çoğunu **SİZ** yapabilirsiniz. + +Şu anda yapabileceğiniz ana işler: + +* [GitHub'da sorularla başkalarına yardım edin](#help-others-with-questions-in-github){.internal-link target=_blank} (yukarıdaki bölüme bakın). +* [Pull Request'leri inceleyin](#review-pull-requests){.internal-link target=_blank} (yukarıdaki bölüme bakın). + +Bu iki iş, **en çok zamanı alan** işlerdir. FastAPI bakımının ana yükü buradadır. + +Burada yardımcı olursanız, **FastAPI'nin bakımını yapmama yardım etmiş** ve daha **hızlı ve daha iyi ilerlemesini** sağlamış olursunuz. 🚀 + +## Sohbete katılın { #join-the-chat } + +FastAPI topluluğundan diğer kişilerle takılmak için 👥 Discord chat server 👥 sohbetine katılın. + +/// tip | İpucu + +Sorular için GitHub Discussions'a sorun; [FastAPI Experts](fastapi-people.md#fastapi-experts){.internal-link target=_blank} tarafından yardım alma ihtimaliniz çok daha yüksektir. + +Chat'i sadece genel sohbetler için kullanın. + +/// + +### Sorular için chat'i kullanmayın { #dont-use-the-chat-for-questions } + +Chat sistemleri daha "serbest sohbet"e izin verdiği için, çok genel ve yanıtlaması daha zor sorular sormak kolaylaşır; bu nedenle cevap alamayabilirsiniz. + +GitHub'da ise şablon (template) doğru soruyu yazmanız için sizi yönlendirir; böylece daha kolay iyi bir cevap alabilir, hatta bazen sormadan önce problemi kendiniz çözebilirsiniz. Ayrıca GitHub'da (zaman alsa bile) her şeye mutlaka cevap verdiğimden emin olabilirim. Chat sistemlerinde bunu kişisel olarak yapamam. 😅 + +Chat sistemlerindeki konuşmalar GitHub kadar kolay aranabilir değildir; bu yüzden soru ve cevaplar sohbet içinde kaybolabilir. Ayrıca [FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank} olmak için sadece GitHub'daki katkılar sayılır; dolayısıyla büyük olasılıkla GitHub'da daha fazla ilgi görürsünüz. + +Öte yandan chat sistemlerinde binlerce kullanıcı vardır; bu yüzden neredeyse her zaman konuşacak birini bulma ihtimaliniz yüksektir. 😄 + +## Yazara sponsor olun { #sponsor-the-author } + +Eğer **ürününüz/şirketiniz** **FastAPI**'ye bağlıysa veya onunla ilişkiliyse ve FastAPI kullanıcılarına ulaşmak istiyorsanız, GitHub sponsors üzerinden yazara (bana) sponsor olabilirsiniz. Tier'a göre dokümantasyonda bir rozet gibi ek faydalar elde edebilirsiniz. 🎁 + +--- + +Teşekkürler! 🚀 diff --git a/docs/tr/docs/how-to/authentication-error-status-code.md b/docs/tr/docs/how-to/authentication-error-status-code.md new file mode 100644 index 000000000..579673624 --- /dev/null +++ b/docs/tr/docs/how-to/authentication-error-status-code.md @@ -0,0 +1,17 @@ +# Eski 403 Kimlik Doğrulama Hata Durum Kodlarını Kullanma { #use-old-403-authentication-error-status-codes } + +FastAPI `0.122.0` sürümünden önce, entegre security yardımcı araçları başarısız bir kimlik doğrulama (authentication) sonrasında client'a bir hata döndüğünde HTTP durum kodu olarak `403 Forbidden` kullanıyordu. + +FastAPI `0.122.0` sürümünden itibaren ise daha uygun olan HTTP durum kodu `401 Unauthorized` kullanılmakta ve HTTP spesifikasyonlarına uygun olarak response içinde anlamlı bir `WWW-Authenticate` header'ı döndürülmektedir: RFC 7235, RFC 9110. + +Ancak herhangi bir nedenle client'larınız eski davranışa bağlıysa, security class'larınızda `make_not_authenticated_error` metodunu override ederek eski davranışa geri dönebilirsiniz. + +Örneğin, varsayılan `401 Unauthorized` hatası yerine `403 Forbidden` hatası döndüren bir `HTTPBearer` alt sınıfı oluşturabilirsiniz: + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} + +/// tip | İpucu + +Fonksiyonun exception instance'ını döndürdüğüne dikkat edin; exception'ı raise etmiyor. Raise işlemi internal kodun geri kalan kısmında yapılıyor. + +/// diff --git a/docs/tr/docs/how-to/conditional-openapi.md b/docs/tr/docs/how-to/conditional-openapi.md new file mode 100644 index 000000000..9562637c4 --- /dev/null +++ b/docs/tr/docs/how-to/conditional-openapi.md @@ -0,0 +1,56 @@ +# Koşullu OpenAPI { #conditional-openapi } + +Gerekirse, ayarlar ve environment variable'ları kullanarak OpenAPI'yi ortama göre koşullu şekilde yapılandırabilir, hatta tamamen devre dışı bırakabilirsiniz. + +## Güvenlik, API'ler ve dokümantasyon hakkında { #about-security-apis-and-docs } + +Production ortamında dokümantasyon arayüzlerini gizlemek, API'nizi korumanın yolu *olmamalıdır*. + +Bu, API'nize ekstra bir güvenlik katmanı eklemez; *path operation*'lar bulundukları yerde yine erişilebilir olacaktır. + +Kodunuzda bir güvenlik açığı varsa, o açık yine var olmaya devam eder. + +Dokümantasyonu gizlemek, API'nizle nasıl etkileşime geçileceğini anlamayı zorlaştırır ve production'da debug etmeyi de daha zor hale getirebilir. Bu yaklaşım, basitçe Security through obscurity olarak değerlendirilebilir. + +API'nizi güvence altına almak istiyorsanız, yapabileceğiniz daha iyi birçok şey var; örneğin: + +* request body'leriniz ve response'larınız için iyi tanımlanmış Pydantic model'larına sahip olduğunuzdan emin olun. +* dependencies kullanarak gerekli izinleri ve rolleri yapılandırın. +* Asla düz metin (plaintext) şifre saklamayın, yalnızca password hash'leri saklayın. +* pwdlib ve JWT token'ları gibi, iyi bilinen kriptografik araçları uygulayın ve kullanın. +* Gerektiğinde OAuth2 scope'ları ile daha ayrıntılı izin kontrolleri ekleyin. +* ...vb. + +Yine de, bazı ortamlarda (örn. production) veya environment variable'lardan gelen konfigürasyonlara bağlı olarak API docs'u gerçekten devre dışı bırakmanız gereken çok spesifik bir use case'iniz olabilir. + +## Ayarlar ve env var'lar ile koşullu OpenAPI { #conditional-openapi-from-settings-and-env-vars } + +Üretilen OpenAPI'yi ve docs UI'larını yapılandırmak için aynı Pydantic settings'i kolayca kullanabilirsiniz. + +Örneğin: + +{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} + +Burada `openapi_url` ayarını, varsayılanı `"/openapi.json"` olacak şekilde tanımlıyoruz. + +Ardından `FastAPI` app'ini oluştururken bunu kullanıyoruz. + +Sonrasında, environment variable `OPENAPI_URL`'i boş string olarak ayarlayarak OpenAPI'yi (UI docs dahil) devre dışı bırakabilirsiniz; örneğin: + +
+ +```console +$ OPENAPI_URL= uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Böylece `/openapi.json`, `/docs` veya `/redoc` URL'lerine giderseniz, aşağıdaki gibi bir `404 Not Found` hatası alırsınız: + +```JSON +{ + "detail": "Not Found" +} +``` diff --git a/docs/tr/docs/how-to/configure-swagger-ui.md b/docs/tr/docs/how-to/configure-swagger-ui.md new file mode 100644 index 000000000..6c051a121 --- /dev/null +++ b/docs/tr/docs/how-to/configure-swagger-ui.md @@ -0,0 +1,70 @@ +# Swagger UI'yi Yapılandırın { #configure-swagger-ui } + +Bazı ek Swagger UI parametrelerini yapılandırabilirsiniz. + +Bunları yapılandırmak için, `FastAPI()` uygulama nesnesini oluştururken ya da `get_swagger_ui_html()` fonksiyonuna `swagger_ui_parameters` argümanını verin. + +`swagger_ui_parameters`, Swagger UI'ye doğrudan iletilecek yapılandırmaları içeren bir `dict` alır. + +FastAPI, Swagger UI'nin ihtiyaç duyduğu şekilde JavaScript ile uyumlu olsun diye bu yapılandırmaları **JSON**'a dönüştürür. + +## Syntax Highlighting'i Devre Dışı Bırakın { #disable-syntax-highlighting } + +Örneğin, Swagger UI'de syntax highlighting'i devre dışı bırakabilirsiniz. + +Ayarları değiştirmeden bırakırsanız, syntax highlighting varsayılan olarak etkindir: + + + +Ancak `syntaxHighlight` değerini `False` yaparak devre dışı bırakabilirsiniz: + +{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} + +...ve ardından Swagger UI artık syntax highlighting'i göstermeyecektir: + + + +## Temayı Değiştirin { #change-the-theme } + +Aynı şekilde, `"syntaxHighlight.theme"` anahtarıyla (ortasında bir nokta olduğuna dikkat edin) syntax highlighting temasını ayarlayabilirsiniz: + +{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} + +Bu yapılandırma, syntax highlighting renk temasını değiştirir: + + + +## Varsayılan Swagger UI Parametrelerini Değiştirin { #change-default-swagger-ui-parameters } + +FastAPI, çoğu kullanım senaryosu için uygun bazı varsayılan yapılandırma parametreleriyle gelir. + +Şu varsayılan yapılandırmaları içerir: + +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} + +`swagger_ui_parameters` argümanında farklı bir değer vererek bunların herhangi birini ezebilirsiniz (override). + +Örneğin `deepLinking`'i devre dışı bırakmak için `swagger_ui_parameters`'a şu ayarları geçebilirsiniz: + +{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} + +## Diğer Swagger UI Parametreleri { #other-swagger-ui-parameters } + +Kullanabileceğiniz diğer tüm olası yapılandırmaları görmek için, resmi Swagger UI parametreleri dokümantasyonunu okuyun. + +## Yalnızca JavaScript ayarları { #javascript-only-settings } + +Swagger UI ayrıca bazı yapılandırmaların **yalnızca JavaScript** nesneleri olmasına izin verir (örneğin JavaScript fonksiyonları). + +FastAPI, bu yalnızca JavaScript olan `presets` ayarlarını da içerir: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +Bunlar string değil, **JavaScript** nesneleridir; dolayısıyla bunları Python kodundan doğrudan geçemezsiniz. + +Böyle yalnızca JavaScript yapılandırmalarına ihtiyacınız varsa, yukarıdaki yöntemlerden birini kullanabilirsiniz: Swagger UI'nin tüm *path operation*'larını override edin ve ihtiyaç duyduğunuz JavaScript'i elle yazın. diff --git a/docs/tr/docs/how-to/custom-docs-ui-assets.md b/docs/tr/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 000000000..bdd2d0244 --- /dev/null +++ b/docs/tr/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,185 @@ +# Özel Docs UI Statik Varlıkları (Self-Hosting) { #custom-docs-ui-static-assets-self-hosting } + +API dokümanları **Swagger UI** ve **ReDoc** kullanır ve bunların her biri bazı JavaScript ve CSS dosyalarına ihtiyaç duyar. + +Varsayılan olarak bu dosyalar bir CDN üzerinden servis edilir. + +Ancak bunu özelleştirmek mümkündür; belirli bir CDN ayarlayabilir veya dosyaları kendiniz servis edebilirsiniz. + +## JavaScript ve CSS için Özel CDN { #custom-cdn-for-javascript-and-css } + +Diyelim ki farklı bir CDN kullanmak istiyorsunuz; örneğin `https://unpkg.com/` kullanmak istiyorsunuz. + +Bu, örneğin bazı URL'leri kısıtlayan bir ülkede yaşıyorsanız faydalı olabilir. + +### Otomatik dokümanları devre dışı bırakın { #disable-the-automatic-docs } + +İlk adım, otomatik dokümanları devre dışı bırakmaktır; çünkü varsayılan olarak bunlar varsayılan CDN'i kullanır. + +Bunları devre dışı bırakmak için `FastAPI` uygulamanızı oluştururken URL'lerini `None` olarak ayarlayın: + +{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *} + +### Özel dokümanları ekleyin { #include-the-custom-docs } + +Şimdi özel dokümanlar için *path operation*'ları oluşturabilirsiniz. + +Dokümanlar için HTML sayfalarını üretmek üzere FastAPI'nin dahili fonksiyonlarını yeniden kullanabilir ve gerekli argümanları iletebilirsiniz: + +* `openapi_url`: Dokümanların HTML sayfasının API'niz için OpenAPI şemasını alacağı URL. Burada `app.openapi_url` niteliğini kullanabilirsiniz. +* `title`: API'nizin başlığı. +* `oauth2_redirect_url`: varsayılanı kullanmak için burada `app.swagger_ui_oauth2_redirect_url` kullanabilirsiniz. +* `swagger_js_url`: Swagger UI dokümanlarınızın HTML'inin **JavaScript** dosyasını alacağı URL. Bu, özel CDN URL'idir. +* `swagger_css_url`: Swagger UI dokümanlarınızın HTML'inin **CSS** dosyasını alacağı URL. Bu, özel CDN URL'idir. + +ReDoc için de benzer şekilde... + +{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *} + +/// tip | İpucu + +`swagger_ui_redirect` için olan *path operation*, OAuth2 kullandığınızda yardımcı olması için vardır. + +API'nizi bir OAuth2 sağlayıcısıyla entegre ederseniz kimlik doğrulaması yapabilir, aldığınız kimlik bilgileriyle API dokümanlarına geri dönebilir ve gerçek OAuth2 kimlik doğrulamasını kullanarak onunla etkileşime geçebilirsiniz. + +Swagger UI bunu arka planda sizin için yönetir, ancak bu "redirect" yardımcısına ihtiyaç duyar. + +/// + +### Test etmek için bir *path operation* oluşturun { #create-a-path-operation-to-test-it } + +Şimdi her şeyin çalıştığını test edebilmek için bir *path operation* oluşturun: + +{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *} + +### Test edin { #test-it } + +Artık http://127.0.0.1:8000/docs adresinden dokümanlarınıza gidebilmeli ve sayfayı yenilediğinizde bu varlıkların yeni CDN'den yüklendiğini görebilmelisiniz. + +## Dokümanlar için JavaScript ve CSS'i Self-Hosting ile barındırma { #self-hosting-javascript-and-css-for-docs } + +JavaScript ve CSS'i self-hosting ile barındırmak, örneğin uygulamanızın İnternet erişimi olmadan (offline), açık İnternet olmadan veya bir lokal ağ içinde bile çalışmaya devam etmesi gerekiyorsa faydalı olabilir. + +Burada bu dosyaları aynı FastAPI uygulamasında nasıl kendiniz servis edeceğinizi ve dokümanların bunları kullanacak şekilde nasıl yapılandırılacağını göreceksiniz. + +### Proje dosya yapısı { #project-file-structure } + +Diyelim ki projenizin dosya yapısı şöyle: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +Şimdi bu statik dosyaları saklamak için bir dizin oluşturun. + +Yeni dosya yapınız şöyle olabilir: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### Dosyaları indirin { #download-the-files } + +Dokümanlar için gereken statik dosyaları indirin ve `static/` dizinine koyun. + +Muhtemelen her bir linke sağ tıklayıp "Save link as..." benzeri bir seçenek seçebilirsiniz. + +**Swagger UI** şu dosyaları kullanır: + +* `swagger-ui-bundle.js` +* `swagger-ui.css` + +**ReDoc** ise şu dosyayı kullanır: + +* `redoc.standalone.js` + +Bundan sonra dosya yapınız şöyle görünebilir: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### Statik dosyaları servis edin { #serve-the-static-files } + +* `StaticFiles` içe aktarın. +* Belirli bir path'te bir `StaticFiles()` instance'ını "mount" edin. + +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *} + +### Statik dosyaları test edin { #test-the-static-files } + +Uygulamanızı başlatın ve http://127.0.0.1:8000/static/redoc.standalone.js adresine gidin. + +**ReDoc** için çok uzun bir JavaScript dosyası görmelisiniz. + +Şuna benzer bir şekilde başlayabilir: + +```JavaScript +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): +... +``` + +Bu, uygulamanızdan statik dosyaları servis edebildiğinizi ve dokümanlar için statik dosyaları doğru yere koyduğunuzu doğrular. + +Şimdi uygulamayı, dokümanlar için bu statik dosyaları kullanacak şekilde yapılandırabiliriz. + +### Statik dosyalar için otomatik dokümanları devre dışı bırakın { #disable-the-automatic-docs-for-static-files } + +Özel CDN kullanırken olduğu gibi, ilk adım otomatik dokümanları devre dışı bırakmaktır; çünkü bunlar varsayılan olarak CDN kullanır. + +Bunları devre dışı bırakmak için `FastAPI` uygulamanızı oluştururken URL'lerini `None` olarak ayarlayın: + +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *} + +### Statik dosyalar için özel dokümanları ekleyin { #include-the-custom-docs-for-static-files } + +Özel CDN'de olduğu gibi, artık özel dokümanlar için *path operation*'ları oluşturabilirsiniz. + +Yine FastAPI'nin dahili fonksiyonlarını kullanarak dokümanlar için HTML sayfalarını oluşturabilir ve gerekli argümanları geçebilirsiniz: + +* `openapi_url`: Dokümanların HTML sayfasının API'niz için OpenAPI şemasını alacağı URL. Burada `app.openapi_url` niteliğini kullanabilirsiniz. +* `title`: API'nizin başlığı. +* `oauth2_redirect_url`: varsayılanı kullanmak için burada `app.swagger_ui_oauth2_redirect_url` kullanabilirsiniz. +* `swagger_js_url`: Swagger UI dokümanlarınızın HTML'inin **JavaScript** dosyasını alacağı URL. **Artık bunu sizin kendi uygulamanız servis ediyor**. +* `swagger_css_url`: Swagger UI dokümanlarınızın HTML'inin **CSS** dosyasını alacağı URL. **Artık bunu sizin kendi uygulamanız servis ediyor**. + +ReDoc için de benzer şekilde... + +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *} + +/// tip | İpucu + +`swagger_ui_redirect` için olan *path operation*, OAuth2 kullandığınızda yardımcı olması için vardır. + +API'nizi bir OAuth2 sağlayıcısıyla entegre ederseniz kimlik doğrulaması yapabilir, aldığınız kimlik bilgileriyle API dokümanlarına geri dönebilir ve gerçek OAuth2 kimlik doğrulamasını kullanarak onunla etkileşime geçebilirsiniz. + +Swagger UI bunu arka planda sizin için yönetir, ancak bu "redirect" yardımcısına ihtiyaç duyar. + +/// + +### Statik dosyaları test etmek için bir *path operation* oluşturun { #create-a-path-operation-to-test-static-files } + +Şimdi her şeyin çalıştığını test edebilmek için bir *path operation* oluşturun: + +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *} + +### Statik Dosyalar UI'ını Test Edin { #test-static-files-ui } + +Artık WiFi bağlantınızı kesip http://127.0.0.1:8000/docs adresindeki dokümanlarınıza gidebilmeli ve sayfayı yenileyebilmelisiniz. + +Ve İnternet olmasa bile API dokümanlarınızı görebilir ve onunla etkileşime geçebilirsiniz. diff --git a/docs/tr/docs/how-to/custom-request-and-route.md b/docs/tr/docs/how-to/custom-request-and-route.md new file mode 100644 index 000000000..a4419373f --- /dev/null +++ b/docs/tr/docs/how-to/custom-request-and-route.md @@ -0,0 +1,109 @@ +# Özel Request ve APIRoute sınıfı { #custom-request-and-apiroute-class } + +Bazı durumlarda, `Request` ve `APIRoute` sınıflarının kullandığı mantığı override etmek isteyebilirsiniz. + +Özellikle bu yaklaşım, bir middleware içindeki mantığa iyi bir alternatif olabilir. + +Örneğin, request body uygulamanız tarafından işlenmeden önce okumak veya üzerinde değişiklik yapmak istiyorsanız. + +/// danger | Uyarı + +Bu "ileri seviye" bir özelliktir. + +**FastAPI**'ye yeni başlıyorsanız bu bölümü atlamak isteyebilirsiniz. + +/// + +## Kullanım senaryoları { #use-cases } + +Bazı kullanım senaryoları: + +* JSON olmayan request body'leri JSON'a dönüştürmek (örn. `msgpack`). +* gzip ile sıkıştırılmış request body'leri açmak (decompress). +* Tüm request body'lerini otomatik olarak loglamak. + +## Özel request body encoding'lerini ele alma { #handling-custom-request-body-encodings } + +Gzip request'lerini açmak için özel bir `Request` alt sınıfını nasıl kullanabileceğimize bakalım. + +Ayrıca, o özel request sınıfını kullanmak için bir `APIRoute` alt sınıfı da oluşturacağız. + +### Özel bir `GzipRequest` sınıfı oluşturun { #create-a-custom-gziprequest-class } + +/// tip | İpucu + +Bu, nasıl çalıştığını göstermek için hazırlanmış basit bir örnektir; Gzip desteğine ihtiyacınız varsa sağlanan [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank} bileşenini kullanabilirsiniz. + +/// + +Önce, uygun bir header mevcut olduğunda body'yi açmak için `Request.body()` metodunu overwrite edecek bir `GzipRequest` sınıfı oluşturuyoruz. + +Header'da `gzip` yoksa body'yi açmayı denemez. + +Böylece aynı route sınıfı, gzip ile sıkıştırılmış veya sıkıştırılmamış request'leri handle edebilir. + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} + +### Özel bir `GzipRoute` sınıfı oluşturun { #create-a-custom-gziproute-class } + +Sonra, `GzipRequest`'i kullanacak `fastapi.routing.APIRoute` için özel bir alt sınıf oluşturuyoruz. + +Bu kez `APIRoute.get_route_handler()` metodunu overwrite edeceğiz. + +Bu metot bir fonksiyon döndürür. Bu fonksiyon da request'i alır ve response döndürür. + +Burada bu fonksiyonu, orijinal request'ten bir `GzipRequest` oluşturmak için kullanıyoruz. + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} + +/// note | Teknik Detaylar + +Bir `Request`'in, request ile ilgili metadata'yı içeren bir Python `dict` olan `request.scope` attribute'u vardır. + +Bir `Request` ayrıca `request.receive` içerir; bu, request'in body'sini "almak" (receive etmek) için kullanılan bir fonksiyondur. + +`scope` `dict`'i ve `receive` fonksiyonu, ASGI spesifikasyonunun parçalarıdır. + +Ve bu iki şey, `scope` ve `receive`, yeni bir `Request` instance'ı oluşturmak için gerekenlerdir. + +`Request` hakkında daha fazla bilgi için Starlette'ın Request dokümantasyonuna bakın. + +/// + +`GzipRequest.get_route_handler` tarafından döndürülen fonksiyonun farklı yaptığı tek şey, `Request`'i bir `GzipRequest`'e dönüştürmektir. + +Bunu yaptığımızda `GzipRequest`, veriyi (gerekliyse) *path operations*'larımıza geçirmeden önce açma (decompress) işini üstlenir. + +Bundan sonra tüm işleme mantığı aynıdır. + +Ancak `GzipRequest.body` içindeki değişikliklerimiz sayesinde, request body gerektiğinde **FastAPI** tarafından yüklendiğinde otomatik olarak decompress edilir. + +## Bir exception handler içinde request body'ye erişme { #accessing-the-request-body-in-an-exception-handler } + +/// tip | İpucu + +Aynı problemi çözmek için, muhtemelen `RequestValidationError` için özel bir handler içinde `body` kullanmak çok daha kolaydır ([Hataları Ele Alma](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). + +Yine de bu örnek geçerlidir ve dahili bileşenlerle nasıl etkileşime geçileceğini gösterir. + +/// + +Aynı yaklaşımı bir exception handler içinde request body'ye erişmek için de kullanabiliriz. + +Tek yapmamız gereken, request'i bir `try`/`except` bloğu içinde handle etmek: + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} + +Bir exception oluşursa, `Request` instance'ı hâlâ scope içinde olacağı için, hatayı handle ederken request body'yi okuyup kullanabiliriz: + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} + +## Bir router içinde özel `APIRoute` sınıfı { #custom-apiroute-class-in-a-router } + +Bir `APIRouter` için `route_class` parametresini de ayarlayabilirsiniz: + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} + +Bu örnekte, `router` altındaki *path operations*'lar özel `TimedRoute` sınıfını kullanır ve response'u üretmek için geçen süreyi içeren ekstra bir `X-Response-Time` header'ı response'ta bulunur: + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} diff --git a/docs/tr/docs/how-to/extending-openapi.md b/docs/tr/docs/how-to/extending-openapi.md new file mode 100644 index 000000000..99691946c --- /dev/null +++ b/docs/tr/docs/how-to/extending-openapi.md @@ -0,0 +1,80 @@ +# OpenAPI'yi Genişletme { #extending-openapi } + +Oluşturulan OpenAPI şemasını değiştirmeniz gereken bazı durumlar olabilir. + +Bu bölümde bunun nasıl yapılacağını göreceksiniz. + +## Normal süreç { #the-normal-process } + +Normal (varsayılan) süreç şöyledir. + +Bir `FastAPI` uygulamasının (instance) OpenAPI şemasını döndürmesi beklenen bir `.openapi()` metodu vardır. + +Uygulama nesnesi oluşturulurken, `/openapi.json` (ya da `openapi_url` için ne ayarladıysanız o) için bir *path operation* kaydedilir. + +Bu path operation, uygulamanın `.openapi()` metodunun sonucunu içeren bir JSON response döndürür. + +Varsayılan olarak `.openapi()` metodunun yaptığı şey, `.openapi_schema` özelliğinde içerik olup olmadığını kontrol etmek ve varsa onu döndürmektir. + +Eğer yoksa, `fastapi.openapi.utils.get_openapi` konumundaki yardımcı (utility) fonksiyonu kullanarak şemayı üretir. + +Ve `get_openapi()` fonksiyonu şu parametreleri alır: + +* `title`: Dokümanlarda gösterilen OpenAPI başlığı. +* `version`: API'nizin sürümü, örn. `2.5.0`. +* `openapi_version`: Kullanılan OpenAPI specification sürümü. Varsayılan olarak en günceli: `3.1.0`. +* `summary`: API'nin kısa özeti. +* `description`: API'nizin açıklaması; markdown içerebilir ve dokümanlarda gösterilir. +* `routes`: route'ların listesi; bunların her biri kayıtlı *path operations*'lardır. `app.routes` içinden alınırlar. + +/// info | Bilgi + +`summary` parametresi OpenAPI 3.1.0 ve üzeri sürümlerde vardır; FastAPI 0.99.0 ve üzeri tarafından desteklenmektedir. + +/// + +## Varsayılanları ezme { #overriding-the-defaults } + +Yukarıdaki bilgileri kullanarak aynı yardımcı fonksiyonla OpenAPI şemasını üretebilir ve ihtiyacınız olan her parçayı override edebilirsiniz. + +Örneğin, özel bir logo eklemek için ReDoc'un OpenAPI extension'ını ekleyelim. + +### Normal **FastAPI** { #normal-fastapi } + +Önce, tüm **FastAPI** uygulamanızı her zamanki gibi yazın: + +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[1,4,7:9] *} + +### OpenAPI şemasını üretme { #generate-the-openapi-schema } + +Ardından, bir `custom_openapi()` fonksiyonunun içinde aynı yardımcı fonksiyonu kullanarak OpenAPI şemasını üretin: + +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *} + +### OpenAPI şemasını değiştirme { #modify-the-openapi-schema } + +Artık OpenAPI şemasındaki `info` "object"'ine özel bir `x-logo` ekleyerek ReDoc extension'ını ekleyebilirsiniz: + +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *} + +### OpenAPI şemasını cache'leme { #cache-the-openapi-schema } + +Ürettiğiniz şemayı saklamak için `.openapi_schema` özelliğini bir "cache" gibi kullanabilirsiniz. + +Böylece bir kullanıcı API docs'larınızı her açtığında uygulamanız şemayı tekrar tekrar üretmek zorunda kalmaz. + +Şema yalnızca bir kez üretilecektir; sonraki request'ler için de aynı cache'lenmiş şema kullanılacaktır. + +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *} + +### Metodu override etme { #override-the-method } + +Şimdi `.openapi()` metodunu yeni fonksiyonunuzla değiştirebilirsiniz. + +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *} + +### Kontrol edin { #check-it } + +http://127.0.0.1:8000/redoc adresine gittiğinizde, özel logonuzu kullandığınızı göreceksiniz (bu örnekte **FastAPI**'nin logosu): + + diff --git a/docs/tr/docs/how-to/graphql.md b/docs/tr/docs/how-to/graphql.md new file mode 100644 index 000000000..fbf018874 --- /dev/null +++ b/docs/tr/docs/how-to/graphql.md @@ -0,0 +1,60 @@ +# GraphQL { #graphql } + +**FastAPI**, **ASGI** standardını temel aldığı için ASGI ile uyumlu herhangi bir **GraphQL** kütüphanesini entegre etmek oldukça kolaydır. + +Aynı uygulama içinde normal FastAPI *path operation*'larını GraphQL ile birlikte kullanabilirsiniz. + +/// tip | İpucu + +**GraphQL** bazı çok özel kullanım senaryolarını çözer. + +Yaygın **web API**'lerle karşılaştırıldığında **avantajları** ve **dezavantajları** vardır. + +Kendi senaryonuz için **faydaların**, **olumsuz yönleri** telafi edip etmediğini mutlaka değerlendirin. 🤓 + +/// + +## GraphQL Kütüphaneleri { #graphql-libraries } + +Aşağıda **ASGI** desteği olan bazı **GraphQL** kütüphaneleri var. Bunları **FastAPI** ile kullanabilirsiniz: + +* Strawberry 🍓 + * FastAPI dokümantasyonu ile +* Ariadne + * FastAPI dokümantasyonu ile +* Tartiflette + * ASGI entegrasyonu sağlamak için Tartiflette ASGI ile +* Graphene + * starlette-graphene3 ile + +## Strawberry ile GraphQL { #graphql-with-strawberry } + +**GraphQL** ile çalışmanız gerekiyorsa ya da bunu istiyorsanız, **Strawberry** önerilen kütüphanedir; çünkü tasarımı **FastAPI**'nin tasarımına en yakındır ve her şey **type annotation**'lar üzerine kuruludur. + +Kullanım senaryonuza göre farklı bir kütüphaneyi tercih edebilirsiniz; ancak bana sorarsanız muhtemelen **Strawberry**'yi denemenizi önerirdim. + +Strawberry'yi FastAPI ile nasıl entegre edebileceğinize dair küçük bir ön izleme: + +{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *} + +Strawberry hakkında daha fazlasını Strawberry dokümantasyonunda öğrenebilirsiniz. + +Ayrıca FastAPI ile Strawberry dokümanlarına da göz atın. + +## Starlette'teki Eski `GraphQLApp` { #older-graphqlapp-from-starlette } + +Starlette'in önceki sürümlerinde Graphene ile entegrasyon için bir `GraphQLApp` sınıfı vardı. + +Bu sınıf Starlette'te kullanımdan kaldırıldı (deprecated). Ancak bunu kullanan bir kodunuz varsa, aynı kullanım senaryosunu kapsayan ve **neredeyse aynı bir interface** sağlayan starlette-graphene3'e kolayca **migrate** edebilirsiniz. + +/// tip | İpucu + +GraphQL'e ihtiyacınız varsa, custom class ve type'lar yerine type annotation'lara dayandığı için yine de Strawberry'yi incelemenizi öneririm. + +/// + +## Daha Fazlasını Öğrenin { #learn-more } + +**GraphQL** hakkında daha fazlasını resmi GraphQL dokümantasyonunda öğrenebilirsiniz. + +Ayrıca yukarıda bahsedilen kütüphanelerin her biri hakkında, kendi bağlantılarından daha fazla bilgi okuyabilirsiniz. diff --git a/docs/tr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/tr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md new file mode 100644 index 000000000..275ac5fd1 --- /dev/null +++ b/docs/tr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -0,0 +1,135 @@ +# Pydantic v1'den Pydantic v2'ye Geçiş { #migrate-from-pydantic-v1-to-pydantic-v2 } + +Eski bir FastAPI uygulamanız varsa, Pydantic'in 1. sürümünü kullanıyor olabilirsiniz. + +FastAPI 0.100.0 sürümü, Pydantic v1 veya v2 ile çalışmayı destekliyordu. Hangisi kuruluysa onu kullanıyordu. + +FastAPI 0.119.0 sürümü, v2'ye geçişi kolaylaştırmak için, Pydantic v2’nin içinden Pydantic v1’e (`pydantic.v1` olarak) kısmi destek ekledi. + +FastAPI 0.126.0 sürümü Pydantic v1 desteğini kaldırdı, ancak bir süre daha `pydantic.v1` desteğini sürdürdü. + +/// warning | Uyarı + +Pydantic ekibi, **Python 3.14** ile başlayarak Python'ın en yeni sürümleri için Pydantic v1 desteğini sonlandırdı. + +Buna `pydantic.v1` de dahildir; Python 3.14 ve üzeri sürümlerde artık desteklenmemektedir. + +Python'ın en yeni özelliklerini kullanmak istiyorsanız, Pydantic v2 kullandığınızdan emin olmanız gerekir. + +/// + +Pydantic v1 kullanan eski bir FastAPI uygulamanız varsa, burada onu Pydantic v2'ye nasıl taşıyacağınızı ve kademeli geçişi kolaylaştıran **FastAPI 0.119.0 özelliklerini** göstereceğim. + +## Resmi Kılavuz { #official-guide } + +Pydantic'in v1'den v2'ye resmi bir Migration Guide'ı vardır. + +Ayrıca nelerin değiştiğini, validasyonların artık nasıl daha doğru ve katı olduğunu, olası dikkat edilmesi gereken noktaları (caveat) vb. de içerir. + +Nelerin değiştiğini daha iyi anlamak için okuyabilirsiniz. + +## Testler { #tests } + +Uygulamanız için [testlerinizin](../tutorial/testing.md){.internal-link target=_blank} olduğundan ve bunları continuous integration (CI) üzerinde çalıştırdığınızdan emin olun. + +Bu şekilde yükseltmeyi yapabilir ve her şeyin hâlâ beklendiği gibi çalıştığını doğrulayabilirsiniz. + +## `bump-pydantic` { #bump-pydantic } + +Birçok durumda, özel özelleştirmeler olmadan standart Pydantic modelleri kullanıyorsanız, Pydantic v1'den Pydantic v2'ye geçiş sürecinin büyük kısmını otomatikleştirebilirsiniz. + +Aynı Pydantic ekibinin geliştirdiği `bump-pydantic` aracını kullanabilirsiniz. + +Bu araç, değişmesi gereken kodun büyük bir kısmını otomatik olarak dönüştürmenize yardımcı olur. + +Bundan sonra testleri çalıştırıp her şeyin çalışıp çalışmadığını kontrol edebilirsiniz. Çalışıyorsa işiniz biter. 😎 + +## v2 İçinde Pydantic v1 { #pydantic-v1-in-v2 } + +Pydantic v2, `pydantic.v1` adlı bir alt modül olarak Pydantic v1'in tamamını içerir. Ancak bu yapı, Python 3.13'ün üzerindeki sürümlerde artık desteklenmemektedir. + +Bu da şu anlama gelir: Pydantic v2'nin en güncel sürümünü kurup, bu alt modülden eski Pydantic v1 bileşenlerini import ederek, sanki eski Pydantic v1 kuruluymuş gibi kullanabilirsiniz. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} + +### v2 İçinde Pydantic v1 için FastAPI Desteği { #fastapi-support-for-pydantic-v1-in-v2 } + +FastAPI 0.119.0'dan itibaren, v2'ye geçişi kolaylaştırmak için Pydantic v2’nin içinden Pydantic v1 kullanımına yönelik kısmi destek de vardır. + +Dolayısıyla Pydantic'i en güncel 2 sürümüne yükseltip import'ları `pydantic.v1` alt modülünü kullanacak şekilde değiştirebilirsiniz; çoğu durumda bu doğrudan çalışır. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} + +/// warning | Uyarı + +Pydantic ekibi Python 3.14'ten itibaren yeni Python sürümlerinde Pydantic v1'i artık desteklemediği için, `pydantic.v1` kullanımı da Python 3.14 ve üzeri sürümlerde desteklenmez. + +/// + +### Aynı Uygulamada Pydantic v1 ve v2 { #pydantic-v1-and-v2-on-the-same-app } + +Pydantic açısından, alanları (field) Pydantic v1 modelleriyle tanımlanmış bir Pydantic v2 modeli (ya da tersi) kullanmak **desteklenmez**. + +```mermaid +graph TB + subgraph "❌ Not Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V1Field["Pydantic v1 Model"] + end + subgraph V1["Pydantic v1 Model"] + V2Field["Pydantic v2 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +...ancak aynı uygulamada Pydantic v1 ve v2 kullanarak **ayrı (separated)** modeller tanımlayabilirsiniz. + +```mermaid +graph TB + subgraph "✅ Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V2Field["Pydantic v2 Model"] + end + subgraph V1["Pydantic v1 Model"] + V1Field["Pydantic v1 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +Bazı durumlarda, FastAPI uygulamanızda aynı **path operation** içinde hem Pydantic v1 hem de v2 modellerini kullanmak bile mümkündür: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} + +Yukarıdaki örnekte input modeli bir Pydantic v1 modelidir; output modeli ( `response_model=ItemV2` ile tanımlanan) ise bir Pydantic v2 modelidir. + +### Pydantic v1 Parametreleri { #pydantic-v1-parameters } + +Pydantic v1 modelleriyle `Body`, `Query`, `Form` vb. parametreler için FastAPI'ye özgü bazı araçları kullanmanız gerekiyorsa, Pydantic v2'ye geçişi tamamlayana kadar bunları `fastapi.temp_pydantic_v1_params` içinden import edebilirsiniz: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} + +### Adım Adım Geçiş { #migrate-in-steps } + +/// tip | İpucu + +Önce `bump-pydantic` ile deneyin; testleriniz geçerse ve bu yol çalışırsa tek komutla işi bitirmiş olursunuz. ✨ + +/// + +`bump-pydantic` sizin senaryonuz için uygun değilse, aynı uygulamada hem Pydantic v1 hem de v2 modellerini birlikte kullanma desteğinden yararlanarak Pydantic v2'ye kademeli şekilde geçebilirsiniz. + +Önce Pydantic'i en güncel 2 sürümüne yükseltip tüm modelleriniz için import'ları `pydantic.v1` kullanacak şekilde değiştirebilirsiniz. + +Ardından modellerinizi Pydantic v1'den v2'ye gruplar hâlinde, adım adım taşımaya başlayabilirsiniz. 🚶 diff --git a/docs/tr/docs/how-to/separate-openapi-schemas.md b/docs/tr/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 000000000..c26411d29 --- /dev/null +++ b/docs/tr/docs/how-to/separate-openapi-schemas.md @@ -0,0 +1,102 @@ +# Input ve Output için Ayrı OpenAPI Schema'ları (Ya da Değil) { #separate-openapi-schemas-for-input-and-output-or-not } + +**Pydantic v2** yayınlandığından beri, üretilen OpenAPI eskisine göre biraz daha net ve **doğru**. 😎 + +Hatta bazı durumlarda, aynı Pydantic model için OpenAPI içinde input ve output tarafında, **default değerler** olup olmamasına bağlı olarak **iki farklı JSON Schema** bile görebilirsiniz. + +Bunun nasıl çalıştığına ve gerekirse nasıl değiştirebileceğinize bir bakalım. + +## Input ve Output için Pydantic Modelleri { #pydantic-models-for-input-and-output } + +Default değerleri olan bir Pydantic modeliniz olduğunu varsayalım; örneğin şöyle: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} + +### Input için Model { #model-for-input } + +Bu modeli şöyle input olarak kullanırsanız: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} + +...`description` alanı **zorunlu olmaz**. Çünkü `None` default değerine sahiptir. + +### Dokümanlarda Input Modeli { #input-model-in-docs } + +Bunu dokümanlarda da doğrulayabilirsiniz; `description` alanında **kırmızı yıldız** yoktur, yani required olarak işaretlenmemiştir: + +
+ +
+ +### Output için Model { #model-for-output } + +Ancak aynı modeli output olarak şöyle kullanırsanız: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} + +...`description` default değere sahip olduğu için, o alan için **hiçbir şey döndürmeseniz** bile yine de **o default değeri** alır. + +### Output Response Verisi için Model { #model-for-output-response-data } + +Dokümanlarla etkileşip response'u kontrol ederseniz, kod `description` alanlarından birine bir şey eklememiş olsa bile, JSON response default değeri (`null`) içerir: + +
+ +
+ +Bu, alanın **her zaman bir değeri olacağı** anlamına gelir; sadece bazen bu değer `None` olabilir (JSON'da `null`). + +Dolayısıyla API'nizi kullanan client'ların bu değerin var olup olmadığını kontrol etmesine gerek yoktur; **alanın her zaman mevcut olacağını varsayabilirler**, sadece bazı durumlarda default değer olan `None` gelecektir. + +Bunu OpenAPI'de ifade etmenin yolu, bu alanı **required** olarak işaretlemektir; çünkü her zaman yer alacaktır. + +Bu nedenle, bir modelin JSON Schema'sı **input mu output mu** kullanıldığına göre farklı olabilir: + +* **input** için `description` **required olmaz** +* **output** için **required olur** (ve `None` olabilir; JSON açısından `null`) + +### Dokümanlarda Output Modeli { #model-for-output-in-docs } + +Dokümanlarda output modelini de kontrol edebilirsiniz; **hem** `name` **hem de** `description` alanları **kırmızı yıldız** ile **required** olarak işaretlenmiştir: + +
+ +
+ +### Dokümanlarda Input ve Output Modelleri { #model-for-input-and-output-in-docs } + +OpenAPI içindeki tüm kullanılabilir Schema'lara (JSON Schema'lara) bakarsanız, iki tane olduğunu göreceksiniz: biri `Item-Input`, diğeri `Item-Output`. + +`Item-Input` için `description` **required değildir**, kırmızı yıldız yoktur. + +Ama `Item-Output` için `description` **required**'dır, kırmızı yıldız vardır. + +
+ +
+ +**Pydantic v2**'nin bu özelliğiyle API dokümantasyonunuz daha **hassas** olur; ayrıca autogenerated client'lar ve SDK'lar kullanıyorsanız, onlar da daha tutarlı ve daha iyi bir **developer experience** ile daha doğru üretilir. 🎉 + +## Schema'ları Ayırma { #do-not-separate-schemas } + +Bazı durumlarda **input ve output için aynı schema'yı** kullanmak isteyebilirsiniz. + +Bunun muhtemelen en yaygın nedeni, halihazırda autogenerated client kodlarınız/SDK'larınızın olması ve henüz bunların hepsini güncellemek istememenizdir. Büyük ihtimalle bir noktada güncellemek isteyeceksiniz, ama belki şu an değil. + +Bu durumda **FastAPI**'de bu özelliği `separate_input_output_schemas=False` parametresiyle kapatabilirsiniz. + +/// info | Bilgi + +`separate_input_output_schemas` desteği FastAPI `0.102.0` sürümünde eklendi. 🤓 + +/// + +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} + +### Dokümanlarda Input ve Output Modelleri için Aynı Schema { #same-schema-for-input-and-output-models-in-docs } + +Artık model için input ve output tarafında tek bir schema olur: sadece `Item`. Ayrıca `description` alanı **required değildir**: + +
+ +
diff --git a/docs/tr/docs/how-to/testing-database.md b/docs/tr/docs/how-to/testing-database.md new file mode 100644 index 000000000..a2062f6c2 --- /dev/null +++ b/docs/tr/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# Bir Veritabanını Test Etmek { #testing-a-database } + +Veritabanları, SQL ve SQLModel hakkında SQLModel dokümantasyonundan öğrenebilirsiniz. 🤓 + +Ayrıca SQLModel'i FastAPI ile kullanmaya dair mini bir tutorial da var. ✨ + +Bu tutorial içinde SQL veritabanlarını test etme hakkında bir bölüm de bulunuyor. 😎 diff --git a/docs/tr/docs/tutorial/background-tasks.md b/docs/tr/docs/tutorial/background-tasks.md new file mode 100644 index 000000000..4cb67d822 --- /dev/null +++ b/docs/tr/docs/tutorial/background-tasks.md @@ -0,0 +1,84 @@ +# Arka Plan Görevleri { #background-tasks } + +Response döndürüldükten *sonra* çalıştırılacak arka plan görevleri tanımlayabilirsiniz. + +Bu, request’ten sonra yapılması gereken; ancak client’ın response’u almadan önce tamamlanmasını beklemesine gerek olmayan işlemler için kullanışlıdır. + +Örneğin: + +* Bir işlem gerçekleştirdikten sonra gönderilen email bildirimleri: + * Bir email server’a bağlanmak ve email göndermek genellikle "yavaş" olduğundan (birkaç saniye), response’u hemen döndürüp email bildirimini arka planda gönderebilirsiniz. +* Veri işleme: + * Örneğin, yavaş bir süreçten geçmesi gereken bir dosya aldığınızı düşünün; "Accepted" (HTTP 202) response’u döndürüp dosyayı arka planda işleyebilirsiniz. + +## `BackgroundTasks` Kullanımı { #using-backgroundtasks } + +Önce `BackgroundTasks`’i import edin ve *path operation function*’ınızda `BackgroundTasks` tip bildirimi olan bir parametre tanımlayın: + +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} + +**FastAPI**, sizin için `BackgroundTasks` tipinde bir obje oluşturur ve onu ilgili parametre olarak geçirir. + +## Bir Görev Fonksiyonu Oluşturun { #create-a-task-function } + +Arka plan görevi olarak çalıştırılacak bir fonksiyon oluşturun. + +Bu, parametre alabilen standart bir fonksiyondur. + +`async def` de olabilir, normal `def` de olabilir; **FastAPI** bunu doğru şekilde nasıl ele alacağını bilir. + +Bu örnekte görev fonksiyonu bir dosyaya yazacaktır (email göndermeyi simüle ediyor). + +Ve yazma işlemi `async` ve `await` kullanmadığı için fonksiyonu normal `def` ile tanımlarız: + +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} + +## Arka Plan Görevini Ekleyin { #add-the-background-task } + +*Path operation function*’ınızın içinde, görev fonksiyonunuzu `.add_task()` metodu ile *background tasks* objesine ekleyin: + +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} + +`.add_task()` şu argümanları alır: + +* Arka planda çalıştırılacak bir görev fonksiyonu (`write_notification`). +* Görev fonksiyonuna sırayla geçirilecek argümanlar (`email`). +* Görev fonksiyonuna geçirilecek keyword argümanlar (`message="some notification"`). + +## Dependency Injection { #dependency-injection } + +`BackgroundTasks` kullanımı dependency injection sistemiyle de çalışır; `BackgroundTasks` tipinde bir parametreyi birden fazla seviyede tanımlayabilirsiniz: bir *path operation function* içinde, bir dependency’de (dependable), bir sub-dependency’de, vb. + +**FastAPI** her durumda ne yapılacağını ve aynı objenin nasıl yeniden kullanılacağını bilir; böylece tüm arka plan görevleri birleştirilir ve sonrasında arka planda çalıştırılır: + +{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *} + +Bu örnekte, response gönderildikten *sonra* mesajlar `log.txt` dosyasına yazılacaktır. + +Request’te bir query varsa, log’a bir arka plan göreviyle yazılır. + +Ardından *path operation function* içinde oluşturulan başka bir arka plan görevi, `email` path parametresini kullanarak bir mesaj yazar. + +## Teknik Detaylar { #technical-details } + +`BackgroundTasks` sınıfı doğrudan `starlette.background`’dan gelir. + +`fastapi` üzerinden import edebilmeniz ve yanlışlıkla `starlette.background` içindeki alternatif `BackgroundTask`’i (sonunda `s` olmadan) import etmemeniz için FastAPI’nin içine doğrudan import/eklenmiştir. + +Sadece `BackgroundTasks` (ve `BackgroundTask` değil) kullanarak, bunu bir *path operation function* parametresi olarak kullanmak ve gerisini **FastAPI**’nin sizin için halletmesini sağlamak mümkündür; tıpkı `Request` objesini doğrudan kullanırken olduğu gibi. + +FastAPI’de `BackgroundTask`’i tek başına kullanmak hâlâ mümkündür; ancak bu durumda objeyi kendi kodunuzda oluşturmanız ve onu içeren bir Starlette `Response` döndürmeniz gerekir. + +Daha fazla detayı Starlette’in Background Tasks için resmi dokümantasyonunda görebilirsiniz. + +## Dikkat Edilmesi Gerekenler { #caveat } + +Yoğun arka plan hesaplamaları yapmanız gerekiyorsa ve bunun aynı process tarafından çalıştırılmasına şart yoksa (örneğin memory, değişkenler vb. paylaşmanız gerekmiyorsa), Celery gibi daha büyük araçları kullanmak size fayda sağlayabilir. + +Bunlar genellikle daha karmaşık konfigurasyonlar ve RabbitMQ veya Redis gibi bir mesaj/iş kuyruğu yöneticisi gerektirir; ancak arka plan görevlerini birden fazla process’te ve özellikle birden fazla server’da çalıştırmanıza olanak tanırlar. + +Ancak aynı **FastAPI** app’i içindeki değişkenlere ve objelere erişmeniz gerekiyorsa veya küçük arka plan görevleri (email bildirimi göndermek gibi) yapacaksanız, doğrudan `BackgroundTasks` kullanabilirsiniz. + +## Özet { #recap } + +Arka plan görevleri eklemek için *path operation function*’larda ve dependency’lerde parametre olarak `BackgroundTasks`’i import edip kullanın. diff --git a/docs/tr/docs/tutorial/bigger-applications.md b/docs/tr/docs/tutorial/bigger-applications.md new file mode 100644 index 000000000..d8a4b8208 --- /dev/null +++ b/docs/tr/docs/tutorial/bigger-applications.md @@ -0,0 +1,504 @@ +# Daha Büyük Uygulamalar - Birden Fazla Dosya { #bigger-applications-multiple-files } + +Bir uygulama veya web API geliştirirken, her şeyi tek bir dosyaya sığdırabilmek nadirdir. + +**FastAPI**, tüm esnekliği korurken uygulamanızı yapılandırmanıza yardımcı olan pratik bir araç sunar. + +/// info | Bilgi + +Flask'ten geliyorsanız, bu yapı Flask'in Blueprints'ine denk gelir. + +/// + +## Örnek Bir Dosya Yapısı { #an-example-file-structure } + +Diyelim ki şöyle bir dosya yapınız var: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   ├── dependencies.py +│   └── routers +│   │ ├── __init__.py +│   │ ├── items.py +│   │ └── users.py +│   └── internal +│   ├── __init__.py +│   └── admin.py +``` + +/// tip | İpucu + +Birden fazla `__init__.py` dosyası var: her dizinde veya alt dizinde bir tane. + +Bu sayede bir dosyadaki kodu diğerine import edebilirsiniz. + +Örneğin `app/main.py` içinde şöyle bir satırınız olabilir: + +``` +from app.routers import items +``` + +/// + +* `app` dizini her şeyi içerir. Ayrıca boş bir `app/__init__.py` dosyası olduğu için bir "Python package" (bir "Python module" koleksiyonu) olur: `app`. +* İçinde bir `app/main.py` dosyası vardır. Bir Python package'in (içinde `__init__.py` dosyası olan bir dizinin) içinde olduğundan, o package'in bir "module"’üdür: `app.main`. +* Benzer şekilde `app/dependencies.py` dosyası da bir "module"’dür: `app.dependencies`. +* `app/routers/` adında bir alt dizin vardır ve içinde başka bir `__init__.py` dosyası bulunur; dolayısıyla bu bir "Python subpackage"’dir: `app.routers`. +* `app/routers/items.py` dosyası `app/routers/` package’i içinde olduğundan bir submodule’dür: `app.routers.items`. +* `app/routers/users.py` için de aynı şekilde, başka bir submodule’dür: `app.routers.users`. +* `app/internal/` adında bir alt dizin daha vardır ve içinde başka bir `__init__.py` dosyası bulunur; dolayısıyla bu da bir "Python subpackage"’dir: `app.internal`. +* Ve `app/internal/admin.py` dosyası başka bir submodule’dür: `app.internal.admin`. + + + +Aynı dosya yapısı, yorumlarla birlikte: + +```bash +. +├── app # "app" bir Python package'idir +│   ├── __init__.py # bu dosya, "app"i bir "Python package" yapar +│   ├── main.py # "main" module'ü, örn. import app.main +│   ├── dependencies.py # "dependencies" module'ü, örn. import app.dependencies +│   └── routers # "routers" bir "Python subpackage"idir +│   │ ├── __init__.py # "routers"ı bir "Python subpackage" yapar +│   │ ├── items.py # "items" submodule'ü, örn. import app.routers.items +│   │ └── users.py # "users" submodule'ü, örn. import app.routers.users +│   └── internal # "internal" bir "Python subpackage"idir +│   ├── __init__.py # "internal"ı bir "Python subpackage" yapar +│   └── admin.py # "admin" submodule'ü, örn. import app.internal.admin +``` + +## `APIRouter` { #apirouter } + +Diyelim ki sadece kullanıcıları yönetmeye ayrılmış dosyanız `/app/routers/users.py` içindeki submodule olsun. + +Kullanıcılarla ilgili *path operation*’ları, kodun geri kalanından ayrı tutmak istiyorsunuz; böylece düzenli kalır. + +Ancak bu hâlâ aynı **FastAPI** uygulaması/web API’sinin bir parçasıdır (aynı "Python Package" içinde). + +Bu module için *path operation*’ları `APIRouter` kullanarak oluşturabilirsiniz. + +### `APIRouter` Import Edin { #import-apirouter } + +`FastAPI` class’ında yaptığınız gibi import edip bir "instance" oluşturursunuz: + +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} + +### `APIRouter` ile *Path Operations* { #path-operations-with-apirouter } + +Sonra bunu kullanarak *path operation*’larınızı tanımlarsınız. + +`FastAPI` class’ını nasıl kullanıyorsanız aynı şekilde kullanın: + +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} + +`APIRouter`’ı "mini bir `FastAPI`" class’ı gibi düşünebilirsiniz. + +Aynı seçeneklerin hepsi desteklenir. + +Aynı `parameters`, `responses`, `dependencies`, `tags`, vb. + +/// tip | İpucu + +Bu örnekte değişkenin adı `router`. Ancak istediğiniz gibi adlandırabilirsiniz. + +/// + +Bu `APIRouter`’ı ana `FastAPI` uygulamasına ekleyeceğiz; ama önce dependency’lere ve bir diğer `APIRouter`’a bakalım. + +## Dependencies { #dependencies } + +Uygulamanın birden fazla yerinde kullanılacak bazı dependency’lere ihtiyacımız olacağını görüyoruz. + +Bu yüzden onları ayrı bir `dependencies` module’üne koyuyoruz (`app/dependencies.py`). + +Şimdi, özel bir `X-Token` header'ını okumak için basit bir dependency kullanalım: + +{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} + +/// tip | İpucu + +Örneği basit tutmak için uydurma bir header kullanıyoruz. + +Ancak gerçek senaryolarda, entegre [Security yardımcı araçlarını](security/index.md){.internal-link target=_blank} kullanarak daha iyi sonuç alırsınız. + +/// + +## `APIRouter` ile Başka Bir Module { #another-module-with-apirouter } + +Diyelim ki uygulamanızdaki "items" ile ilgili endpoint'ler de `app/routers/items.py` module’ünde olsun. + +Şunlar için *path operation*’larınız var: + +* `/items/` +* `/items/{item_id}` + +Bu, `app/routers/users.py` ile aynı yapıdadır. + +Ancak biraz daha akıllı davranıp kodu sadeleştirmek istiyoruz. + +Bu module’deki tüm *path operation*’ların şu ortak özelliklere sahip olduğunu biliyoruz: + +* Path `prefix`: `/items`. +* `tags`: (tek bir tag: `items`). +* Ek `responses`. +* `dependencies`: hepsinin, oluşturduğumuz `X-Token` dependency’sine ihtiyacı var. + +Dolayısıyla bunları her *path operation*’a tek tek eklemek yerine `APIRouter`’a ekleyebiliriz. + +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} + +Her *path operation*’ın path’i aşağıdaki gibi `/` ile başlamak zorunda olduğundan: + +```Python hl_lines="1" +@router.get("/{item_id}") +async def read_item(item_id: str): + ... +``` + +...prefix’in sonunda `/` olmamalıdır. + +Yani bu örnekte prefix `/items` olur. + +Ayrıca, bu router içindeki tüm *path operation*’lara uygulanacak bir `tags` listesi ve ek `responses` da ekleyebiliriz. + +Ve router’daki tüm *path operation*’lara eklenecek, her request için çalıştırılıp çözülecek bir `dependencies` listesi de ekleyebiliriz. + +/// tip | İpucu + +[ *path operation decorator*’larındaki dependency’lerde](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} olduğu gibi, *path operation function*’ınıza herhangi bir değer aktarılmayacağını unutmayın. + +/// + +Sonuç olarak item path’leri artık: + +* `/items/` +* `/items/{item_id}` + +...tam da istediğimiz gibi olur. + +* Hepsi, içinde tek bir string `"items"` bulunan bir tag listesiyle işaretlenir. + * Bu "tags", özellikle otomatik interaktif dokümantasyon sistemleri (OpenAPI) için çok faydalıdır. +* Hepsi önceden tanımlı `responses`’ları içerir. +* Bu *path operation*’ların hepsinde, öncesinde `dependencies` listesi değerlendirilip çalıştırılır. + * Ayrıca belirli bir *path operation* içinde dependency tanımlarsanız, **onlar da çalıştırılır**. + * Önce router dependency’leri, sonra decorator’daki [`dependencies`](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, sonra da normal parametre dependency’leri çalışır. + * Ayrıca [`scopes` ile `Security` dependency’leri](../advanced/security/oauth2-scopes.md){.internal-link target=_blank} de ekleyebilirsiniz. + +/// tip | İpucu + +`APIRouter` içinde `dependencies` kullanmak, örneğin bir grup *path operation* için kimlik doğrulamayı zorunlu kılmakta kullanılabilir. Dependency’leri tek tek her birine eklemeseniz bile. + +/// + +/// check | Ek bilgi + +`prefix`, `tags`, `responses` ve `dependencies` parametreleri (çoğu başka örnekte olduğu gibi) kod tekrarını önlemenize yardımcı olan, **FastAPI**’nin bir özelliğidir. + +/// + +### Dependency'leri Import Edin { #import-the-dependencies } + +Bu kod `app.routers.items` module’ünde, yani `app/routers/items.py` dosyasında duruyor. + +Dependency function’ını ise `app.dependencies` module’ünden, yani `app/dependencies.py` dosyasından almamız gerekiyor. + +Bu yüzden dependency’ler için `..` ile relative import kullanıyoruz: + +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} + +#### Relative Import Nasıl Çalışır { #how-relative-imports-work } + +/// tip | İpucu + +Import’ların nasıl çalıştığını çok iyi biliyorsanız, bir sonraki bölüme geçin. + +/// + +Tek bir nokta `.`, örneğin: + +```Python +from .dependencies import get_token_header +``` + +şu anlama gelir: + +* Bu module’ün (yani `app/routers/items.py` dosyasının) bulunduğu package içinden başla ( `app/routers/` dizini)... +* `dependencies` module’ünü bul (`app/routers/dependencies.py` gibi hayali bir dosya)... +* ve oradan `get_token_header` function’ını import et. + +Ama o dosya yok; bizim dependency’lerimiz `app/dependencies.py` dosyasında. + +Uygulama/dosya yapımızın nasıl göründüğünü hatırlayın: + + + +--- + +İki nokta `..`, örneğin: + +```Python +from ..dependencies import get_token_header +``` + +şu anlama gelir: + +* Bu module’ün bulunduğu package içinden başla (`app/routers/` dizini)... +* üst (parent) package’e çık (`app/` dizini)... +* burada `dependencies` module’ünü bul (`app/dependencies.py` dosyası)... +* ve oradan `get_token_header` function’ını import et. + +Bu doğru şekilde çalışır! 🎉 + +--- + +Aynı şekilde, üç nokta `...` kullansaydık: + +```Python +from ...dependencies import get_token_header +``` + +şu anlama gelirdi: + +* Bu module’ün bulunduğu package içinden başla (`app/routers/` dizini)... +* üst package’e çık (`app/` dizini)... +* sonra bir üstüne daha çık (orada bir üst package yok; `app` en üst seviye 😱)... +* ve orada `dependencies` module’ünü bul (`app/dependencies.py` dosyası)... +* ve oradan `get_token_header` function’ını import et. + +Bu, `app/` dizininin üstünde, kendi `__init__.py` dosyası olan başka bir package’e işaret ederdi. Ama bizde böyle bir şey yok. Dolayısıyla bu örnekte hata verirdi. 🚨 + +Artık nasıl çalıştığını bildiğinize göre, uygulamalarınız ne kadar karmaşık olursa olsun relative import’ları kullanabilirsiniz. 🤓 + +### Özel `tags`, `responses` ve `dependencies` Ekleyin { #add-some-custom-tags-responses-and-dependencies } + +`/items` prefix’ini ya da `tags=["items"]` değerini her *path operation*’a tek tek eklemiyoruz; çünkü bunları `APIRouter`’a ekledik. + +Ama yine de belirli bir *path operation*’a uygulanacak _ek_ `tags` tanımlayabilir, ayrıca o *path operation*’a özel `responses` ekleyebiliriz: + +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} + +/// tip | İpucu + +Bu son *path operation*’da tag kombinasyonu şöyle olur: `["items", "custom"]`. + +Ayrıca dokümantasyonda iki response da görünür: biri `404`, diğeri `403`. + +/// + +## Ana `FastAPI` { #the-main-fastapi } + +Şimdi `app/main.py` module’üne bakalım. + +Burada `FastAPI` class’ını import edip kullanırsınız. + +Bu dosya, uygulamanızda her şeyi bir araya getiren ana dosya olacak. + +Mantığın büyük kısmı artık kendi module’lerinde yaşayacağı için ana dosya oldukça basit kalır. + +### `FastAPI` Import Edin { #import-fastapi } + +Normal şekilde bir `FastAPI` class’ı oluşturursunuz. + +Hatta her `APIRouter` için olan dependency’lerle birleştirilecek [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank} bile tanımlayabilirsiniz: + +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} + +### `APIRouter` Import Edin { #import-the-apirouter } + +Şimdi `APIRouter` içeren diğer submodule’leri import ediyoruz: + +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} + +`app/routers/users.py` ve `app/routers/items.py` dosyaları aynı Python package’i olan `app`’in parçası olan submodule’ler olduğu için, onları "relative import" ile tek bir nokta `.` kullanarak import edebiliriz. + +### Import Nasıl Çalışır { #how-the-importing-works } + +Şu bölüm: + +```Python +from .routers import items, users +``` + +şu anlama gelir: + +* Bu module’ün (yani `app/main.py` dosyasının) bulunduğu package içinden başla (`app/` dizini)... +* `routers` subpackage’ini bul (`app/routers/` dizini)... +* ve buradan `items` submodule’ünü (`app/routers/items.py`) ve `users` submodule’ünü (`app/routers/users.py`) import et... + +`items` module’ünün içinde `router` adında bir değişken vardır (`items.router`). Bu, `app/routers/items.py` dosyasında oluşturduğumuz aynı değişkendir; bir `APIRouter` nesnesidir. + +Sonra aynı işlemi `users` module’ü için de yaparız. + +Ayrıca şöyle de import edebilirdik: + +```Python +from app.routers import items, users +``` + +/// info | Bilgi + +İlk sürüm "relative import"tur: + +```Python +from .routers import items, users +``` + +İkinci sürüm "absolute import"tur: + +```Python +from app.routers import items, users +``` + +Python Packages ve Modules hakkında daha fazlası için, Python'ın Modules ile ilgili resmi dokümantasyonunu okuyun. + +/// + +### İsim Çakışmalarını Önleyin { #avoid-name-collisions } + +`items` submodule’ünü doğrudan import ediyoruz; sadece içindeki `router` değişkenini import etmiyoruz. + +Çünkü `users` submodule’ünde de `router` adlı başka bir değişken var. + +Eğer şöyle sırayla import etseydik: + +```Python +from .routers.items import router +from .routers.users import router +``` + +`users` içindeki `router`, `items` içindeki `router`’ın üstüne yazardı ve ikisini aynı anda kullanamazdık. + +Bu yüzden ikisini de aynı dosyada kullanabilmek için submodule’leri doğrudan import ediyoruz: + +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} + +### `users` ve `items` için `APIRouter`’ları Dahil Edin { #include-the-apirouters-for-users-and-items } + +Şimdi `users` ve `items` submodule’lerindeki `router`’ları dahil edelim: + +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} + +/// info | Bilgi + +`users.router`, `app/routers/users.py` dosyasının içindeki `APIRouter`’ı içerir. + +`items.router` ise `app/routers/items.py` dosyasının içindeki `APIRouter`’ı içerir. + +/// + +`app.include_router()` ile her bir `APIRouter`’ı ana `FastAPI` uygulamasına ekleyebiliriz. + +Böylece o router içindeki tüm route’lar uygulamanın bir parçası olarak dahil edilir. + +/// note | Teknik Detaylar + +Aslında içeride, `APIRouter` içinde tanımlanan her *path operation* için bir *path operation* oluşturur. + +Yani perde arkasında, her şey tek bir uygulamaymış gibi çalışır. + +/// + +/// check | Ek bilgi + +Router’ları dahil ederken performans konusunda endişelenmeniz gerekmez. + +Bu işlem mikrosaniyeler sürer ve sadece startup sırasında olur. + +Dolayısıyla performansı etkilemez. ⚡ + +/// + +### Özel `prefix`, `tags`, `responses` ve `dependencies` ile Bir `APIRouter` Dahil Edin { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies } + +Şimdi, kurumunuzun size `app/internal/admin.py` dosyasını verdiğini düşünelim. + +Bu dosyada, kurumunuzun birden fazla proje arasında paylaştığı bazı admin *path operation*’larını içeren bir `APIRouter` var. + +Bu örnekte çok basit olacak. Ancak kurum içinde başka projelerle paylaşıldığı için, bunu değiştirip `prefix`, `dependencies`, `tags` vs. doğrudan `APIRouter`’a ekleyemediğimizi varsayalım: + +{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} + +Yine de bu `APIRouter`’ı dahil ederken özel bir `prefix` ayarlamak istiyoruz ki tüm *path operation*’ları `/admin` ile başlasın; ayrıca bu projede hâlihazırda kullandığımız `dependencies` ile güvene almak, `tags` ve `responses` eklemek istiyoruz. + +Orijinal `APIRouter`’ı değiştirmeden, bu parametreleri `app.include_router()`’a vererek hepsini tanımlayabiliriz: + +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} + +Böylece orijinal `APIRouter` değişmeden kalır; yani aynı `app/internal/admin.py` dosyasını kurum içindeki diğer projelerle de paylaşmaya devam edebiliriz. + +Sonuç olarak, uygulamamızda `admin` module’ündeki her bir *path operation* şunlara sahip olur: + +* `/admin` prefix’i. +* `admin` tag’i. +* `get_token_header` dependency’si. +* `418` response’u. 🍵 + +Ancak bu sadece bizim uygulamamızdaki o `APIRouter` için geçerlidir; onu kullanan diğer kodlar için değil. + +Dolayısıyla örneğin diğer projeler aynı `APIRouter`’ı farklı bir authentication yöntemiyle kullanabilir. + +### Bir *Path Operation* Dahil Edin { #include-a-path-operation } + +*Path operation*’ları doğrudan `FastAPI` uygulamasına da ekleyebiliriz. + +Burada bunu yapıyoruz... sadece yapabildiğimizi göstermek için 🤷: + +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} + +ve `app.include_router()` ile eklenen diğer tüm *path operation*’larla birlikte doğru şekilde çalışır. + +/// info | Çok Teknik Detaylar + +**Not**: Bu oldukça teknik bir detay; büyük ihtimalle **direkt geçebilirsiniz**. + +--- + +`APIRouter`’lar "mount" edilmez; uygulamanın geri kalanından izole değildir. + +Çünkü *path operation*’larını OpenAPI şemasına ve kullanıcı arayüzlerine dahil etmek istiyoruz. + +Onları tamamen izole edip bağımsız şekilde "mount" edemediğimiz için, *path operation*’lar doğrudan eklenmek yerine "klonlanır" (yeniden oluşturulur). + +/// + +## Otomatik API Dokümanını Kontrol Edin { #check-the-automatic-api-docs } + +Şimdi uygulamanızı çalıştırın: + +
+ +```console +$ fastapi dev app/main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Ve dokümanları http://127.0.0.1:8000/docs adresinde açın. + +Tüm submodule’lerdeki path’leri, doğru path’ler (ve prefix’ler) ve doğru tag’lerle birlikte içeren otomatik API dokümanını göreceksiniz: + + + +## Aynı Router'ı Farklı `prefix` ile Birden Fazla Kez Dahil Edin { #include-the-same-router-multiple-times-with-different-prefix } + +`.include_router()` ile aynı router’ı farklı prefix’ler kullanarak birden fazla kez de dahil edebilirsiniz. + +Örneğin aynı API’yi `/api/v1` ve `/api/latest` gibi farklı prefix’ler altında sunmak için faydalı olabilir. + +Bu, muhtemelen ihtiyacınız olmayan ileri seviye bir kullanımdır; ancak gerekirse diye mevcut. + +## Bir `APIRouter`’ı Başka Birine Dahil Edin { #include-an-apirouter-in-another } + +Bir `APIRouter`’ı `FastAPI` uygulamasına dahil ettiğiniz gibi, bir `APIRouter`’ı başka bir `APIRouter`’a da şu şekilde dahil edebilirsiniz: + +```Python +router.include_router(other_router) +``` + +`router`’ı `FastAPI` uygulamasına dahil etmeden önce bunu yaptığınızdan emin olun; böylece `other_router` içindeki *path operation*’lar da dahil edilmiş olur. diff --git a/docs/tr/docs/tutorial/body-fields.md b/docs/tr/docs/tutorial/body-fields.md new file mode 100644 index 000000000..6a0f3314a --- /dev/null +++ b/docs/tr/docs/tutorial/body-fields.md @@ -0,0 +1,60 @@ +# Body - Alanlar { #body-fields } + +`Query`, `Path` ve `Body` ile *path operation function* parametrelerinde ek doğrulama ve metadata tanımlayabildiğiniz gibi, Pydantic modellerinin içinde de Pydantic'in `Field`'ını kullanarak doğrulama ve metadata tanımlayabilirsiniz. + +## `Field`'ı import edin { #import-field } + +Önce import etmeniz gerekir: + +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *} + +/// warning | Uyarı + +`Field`'ın, diğerlerinin (`Query`, `Path`, `Body` vb.) aksine `fastapi`'den değil doğrudan `pydantic`'den import edildiğine dikkat edin. + +/// + +## Model attribute'larını tanımlayın { #declare-model-attributes } + +Ardından `Field`'ı model attribute'larıyla birlikte kullanabilirsiniz: + +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *} + +`Field`, `Query`, `Path` ve `Body` ile aynı şekilde çalışır; aynı parametrelerin tamamına sahiptir, vb. + +/// note | Teknik Detaylar + +Aslında, `Query`, `Path` ve birazdan göreceğiniz diğerleri, ortak bir `Param` sınıfının alt sınıflarından nesneler oluşturur; `Param` sınıfı da Pydantic'in `FieldInfo` sınıfının bir alt sınıfıdır. + +Pydantic'in `Field`'ı da `FieldInfo`'nun bir instance'ını döndürür. + +`Body` ayrıca doğrudan `FieldInfo`'nun bir alt sınıfından nesneler döndürür. Daha sonra göreceğiniz başka bazıları da `Body` sınıfının alt sınıflarıdır. + +`fastapi`'den `Query`, `Path` ve diğerlerini import ettiğinizde, bunların aslında özel sınıflar döndüren fonksiyonlar olduğunu unutmayın. + +/// + +/// tip | İpucu + +Type, varsayılan değer ve `Field` ile tanımlanan her model attribute'unun yapısının, *path operation function* parametresiyle aynı olduğuna dikkat edin; sadece `Path`, `Query` ve `Body` yerine `Field` kullanılmıştır. + +/// + +## Ek bilgi ekleyin { #add-extra-information } + +`Field`, `Query`, `Body` vb. içinde ek bilgi tanımlayabilirsiniz. Bu bilgiler oluşturulan JSON Schema'ya dahil edilir. + +Örnek (examples) tanımlamayı öğrenirken, dokümanların ilerleyen kısımlarında ek bilgi ekleme konusunu daha ayrıntılı göreceksiniz. + +/// warning | Uyarı + +`Field`'a geçirilen ekstra key'ler, uygulamanız için üretilen OpenAPI schema'sında da yer alır. +Bu key'ler OpenAPI spesifikasyonunun bir parçası olmak zorunda olmadığından, örneğin [OpenAPI validator](https://validator.swagger.io/) gibi bazı OpenAPI araçları üretilen schema'nızla çalışmayabilir. + +/// + +## Özet { #recap } + +Model attribute'ları için ek doğrulamalar ve metadata tanımlamak üzere Pydantic'in `Field`'ını kullanabilirsiniz. + +Ayrıca, ek keyword argument'ları kullanarak JSON Schema'ya ekstra metadata da iletebilirsiniz. diff --git a/docs/tr/docs/tutorial/body-multiple-params.md b/docs/tr/docs/tutorial/body-multiple-params.md new file mode 100644 index 000000000..3bac80d6a --- /dev/null +++ b/docs/tr/docs/tutorial/body-multiple-params.md @@ -0,0 +1,175 @@ +# Body - Birden Fazla Parametre { #body-multiple-parameters } + +Artık `Path` ve `Query` kullanmayı gördüğümüze göre, request body bildirimlerinin daha ileri kullanım senaryolarına bakalım. + +## `Path`, `Query` ve body parametrelerini karıştırma { #mix-path-query-and-body-parameters } + +Öncelikle, elbette `Path`, `Query` ve request body parametre bildirimlerini serbestçe karıştırabilirsiniz ve **FastAPI** ne yapacağını bilir. + +Ayrıca, varsayılan değeri `None` yaparak body parametrelerini opsiyonel olarak da tanımlayabilirsiniz: + +{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} + +/// note | Not + +Bu durumda body'den alınacak `item` opsiyoneldir. Çünkü varsayılan değeri `None` olarak ayarlanmıştır. + +/// + +## Birden fazla body parametresi { #multiple-body-parameters } + +Önceki örnekte, *path operation*'lar `Item`'ın özelliklerini içeren bir JSON body beklerdi, örneğin: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +Ancak birden fazla body parametresi de tanımlayabilirsiniz; örneğin `item` ve `user`: + +{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *} + + +Bu durumda **FastAPI**, fonksiyonda birden fazla body parametresi olduğunu fark eder (iki parametre de Pydantic modelidir). + +Bunun üzerine, body içinde anahtar (field name) olarak parametre adlarını kullanır ve şu şekilde bir body bekler: + +```JSON +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + } +} +``` + +/// note | Not + +`item` daha öncekiyle aynı şekilde tanımlanmış olsa bile, artık body içinde `item` anahtarı altında gelmesi beklenir. + +/// + +**FastAPI**, request'ten otomatik dönüşümü yapar; böylece `item` parametresi kendi içeriğini alır, `user` için de aynı şekilde olur. + +Birleşik verinin validasyonunu yapar ve OpenAPI şeması ile otomatik dokümantasyonda da bunu bu şekilde dokümante eder. + +## Body içinde tekil değerler { #singular-values-in-body } + +Query ve path parametreleri için ek veri tanımlamak üzere `Query` ve `Path` olduğu gibi, **FastAPI** bunların karşılığı olarak `Body` de sağlar. + +Örneğin, önceki modeli genişleterek, aynı body içinde `item` ve `user` dışında bir de `importance` anahtarı olmasını isteyebilirsiniz. + +Bunu olduğu gibi tanımlarsanız, tekil bir değer olduğu için **FastAPI** bunun bir query parametresi olduğunu varsayar. + +Ama `Body` kullanarak, **FastAPI**'ye bunu body içinde başka bir anahtar olarak ele almasını söyleyebilirsiniz: + +{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *} + + +Bu durumda **FastAPI** şu şekilde bir body bekler: + +```JSON +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + }, + "importance": 5 +} +``` + +Yine veri tiplerini dönüştürür, validate eder, dokümante eder, vb. + +## Birden fazla body parametresi ve query { #multiple-body-params-and-query } + +Elbette ihtiyaç duyduğunuzda, body parametrelerine ek olarak query parametreleri de tanımlayabilirsiniz. + +Varsayılan olarak tekil değerler query parametresi olarak yorumlandığı için, ayrıca `Query` eklemeniz gerekmez; şöyle yazmanız yeterlidir: + +```Python +q: Union[str, None] = None +``` + +Ya da Python 3.10 ve üzeri için: + +```Python +q: str | None = None +``` + +Örneğin: + +{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} + + +/// info | Bilgi + +`Body`, `Query`, `Path` ve daha sonra göreceğiniz diğerleriyle aynı ek validasyon ve metadata parametrelerine de sahiptir. + +/// + +## Tek bir body parametresini gömme { #embed-a-single-body-parameter } + +Diyelim ki Pydantic'teki `Item` modelinden gelen yalnızca tek bir `item` body parametreniz var. + +Varsayılan olarak **FastAPI**, body'nin doğrudan bu modelin içeriği olmasını bekler. + +Ancak, ek body parametreleri tanımladığınızda olduğu gibi, `item` anahtarı olan bir JSON ve onun içinde modelin içeriğini beklemesini istiyorsanız, `Body`'nin özel parametresi olan `embed`'i kullanabilirsiniz: + +```Python +item: Item = Body(embed=True) +``` + +yani şöyle: + +{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *} + + +Bu durumda **FastAPI** şu şekilde bir body bekler: + +```JSON hl_lines="2" +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + } +} +``` + +şunun yerine: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +## Özet { #recap } + +Bir request yalnızca tek bir body içerebilse de, *path operation function*'ınıza birden fazla body parametresi ekleyebilirsiniz. + +Ancak **FastAPI** bunu yönetir; fonksiyonunuza doğru veriyi verir ve *path operation* içinde doğru şemayı validate edip dokümante eder. + +Ayrıca tekil değerlerin body'nin bir parçası olarak alınmasını da tanımlayabilirsiniz. + +Ve yalnızca tek bir parametre tanımlanmış olsa bile, **FastAPI**'ye body'yi bir anahtarın içine gömmesini söyleyebilirsiniz. diff --git a/docs/tr/docs/tutorial/body-nested-models.md b/docs/tr/docs/tutorial/body-nested-models.md new file mode 100644 index 000000000..b4ffef3f1 --- /dev/null +++ b/docs/tr/docs/tutorial/body-nested-models.md @@ -0,0 +1,220 @@ +# Body - İç İçe Modeller { #body-nested-models } + +**FastAPI** ile (Pydantic sayesinde) istediğiniz kadar derin iç içe geçmiş modelleri tanımlayabilir, doğrulayabilir, dokümante edebilir ve kullanabilirsiniz. + +## List alanları { #list-fields } + +Bir attribute’u bir alt tipe sahip olacak şekilde tanımlayabilirsiniz. Örneğin, bir Python `list`: + +{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *} + +Bu, `tags`’in bir list olmasını sağlar; ancak list’in elemanlarının tipini belirtmez. + +## Tip parametresi olan list alanları { #list-fields-with-type-parameter } + +Ancak Python’da, iç tipleri olan list’leri (ya da "type parameter" içeren tipleri) tanımlamanın belirli bir yolu vardır: + +### Tip parametresiyle bir `list` tanımlayın { #declare-a-list-with-a-type-parameter } + +`list`, `dict`, `tuple` gibi type parameter (iç tip) alan tipleri tanımlamak için, iç tipi(leri) köşeli parantezlerle "type parameter" olarak verin: `[` ve `]` + +```Python +my_list: list[str] +``` + +Bu, tip tanımları için standart Python sözdizimidir. + +İç tipleri olan model attribute’ları için de aynı standart sözdizimini kullanın. + +Dolayısıyla örneğimizde, `tags`’i özel olarak bir "string list’i" yapabiliriz: + +{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *} + +## Set tipleri { #set-types } + +Sonra bunu düşününce, tag’lerin tekrar etmemesi gerektiğini fark ederiz; büyük ihtimalle benzersiz string’ler olmalıdır. + +Python’da benzersiz öğelerden oluşan kümeler için özel bir veri tipi vardır: `set`. + +O zaman `tags`’i string’lerden oluşan bir set olarak tanımlayabiliriz: + +{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *} + +Böylece duplicate veri içeren bir request alsanız bile, bu veri benzersiz öğelerden oluşan bir set’e dönüştürülür. + +Ve bu veriyi ne zaman output etseniz, kaynakta duplicate olsa bile, benzersiz öğelerden oluşan bir set olarak output edilir. + +Ayrıca buna göre annotate / dokümante edilir. + +## İç İçe Modeller { #nested-models } + +Bir Pydantic modelinin her attribute’unun bir tipi vardır. + +Ancak bu tip, kendi başına başka bir Pydantic modeli de olabilir. + +Yani belirli attribute adları, tipleri ve validation kurallarıyla derin iç içe JSON "object"leri tanımlayabilirsiniz. + +Hem de istediğiniz kadar iç içe. + +### Bir alt model tanımlayın { #define-a-submodel } + +Örneğin bir `Image` modeli tanımlayabiliriz: + +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *} + +### Alt modeli tip olarak kullanın { #use-the-submodel-as-a-type } + +Ardından bunu bir attribute’un tipi olarak kullanabiliriz: + +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *} + +Bu da **FastAPI**’nin aşağıdakine benzer bir body bekleyeceği anlamına gelir: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": ["rock", "metal", "bar"], + "image": { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + } +} +``` + +Yine, sadece bu tanımı yaparak **FastAPI** ile şunları elde edersiniz: + +* Editör desteği (tamamlama vb.), iç içe modeller için bile +* Veri dönüştürme +* Veri doğrulama (validation) +* Otomatik dokümantasyon + +## Özel tipler ve doğrulama { #special-types-and-validation } + +`str`, `int`, `float` vb. normal tekil tiplerin yanında, `str`’den türeyen daha karmaşık tekil tipleri de kullanabilirsiniz. + +Tüm seçenekleri görmek için Pydantic Type Overview sayfasına göz atın. Sonraki bölümde bazı örnekleri göreceksiniz. + +Örneğin `Image` modelinde bir `url` alanımız olduğuna göre, bunu `str` yerine Pydantic’in `HttpUrl` tipinden bir instance olacak şekilde tanımlayabiliriz: + +{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *} + +String’in geçerli bir URL olup olmadığı kontrol edilir ve JSON Schema / OpenAPI’de de buna göre dokümante edilir. + +## Alt modellerden oluşan list’lere sahip attribute’lar { #attributes-with-lists-of-submodels } + +Pydantic modellerini `list`, `set` vb. tiplerin alt tipi olarak da kullanabilirsiniz: + +{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *} + +Bu, aşağıdaki gibi bir JSON body bekler (dönüştürür, doğrular, dokümante eder vb.): + +```JSON hl_lines="11" +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": [ + "rock", + "metal", + "bar" + ], + "images": [ + { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + }, + { + "url": "http://example.com/dave.jpg", + "name": "The Baz" + } + ] +} +``` + +/// info | Bilgi + +`images` key’inin artık image object’lerinden oluşan bir list içerdiğine dikkat edin. + +/// + +## Çok derin iç içe modeller { #deeply-nested-models } + +İstediğiniz kadar derin iç içe modeller tanımlayabilirsiniz: + +{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *} + +/// info | Bilgi + +`Offer`’ın bir `Item` list’i olduğuna, `Item`’ların da opsiyonel bir `Image` list’ine sahip olduğuna dikkat edin. + +/// + +## Sadece list olan body’ler { #bodies-of-pure-lists } + +Beklediğiniz JSON body’nin en üst seviye değeri bir JSON `array` (Python’da `list`) ise, tipi Pydantic modellerinde olduğu gibi fonksiyonun parametresinde tanımlayabilirsiniz: + +```Python +images: list[Image] +``` + +şu örnekte olduğu gibi: + +{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} + +## Her yerde editör desteği { #editor-support-everywhere } + +Ve her yerde editör desteği alırsınız. + +List içindeki öğeler için bile: + + + +Pydantic modelleri yerine doğrudan `dict` ile çalışsaydınız bu tür bir editör desteğini alamazdınız. + +Ancak bunlarla uğraşmanız da gerekmez; gelen dict’ler otomatik olarak dönüştürülür ve output’unuz da otomatik olarak JSON’a çevrilir. + +## Rastgele `dict` body’leri { #bodies-of-arbitrary-dicts } + +Body’yi, key’leri bir tipte ve value’ları başka bir tipte olan bir `dict` olarak da tanımlayabilirsiniz. + +Bu şekilde (Pydantic modellerinde olduğu gibi) geçerli field/attribute adlarının önceden ne olduğunu bilmeniz gerekmez. + +Bu, önceden bilmediğiniz key’leri almak istediğiniz durumlarda faydalıdır. + +--- + +Bir diğer faydalı durum da key’lerin başka bir tipte olmasını istediğiniz zamandır (ör. `int`). + +Burada göreceğimiz şey de bu. + +Bu durumda, `int` key’lere ve `float` value’lara sahip olduğu sürece herhangi bir `dict` kabul edersiniz: + +{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} + +/// tip | İpucu + +JSON key olarak yalnızca `str` destekler, bunu unutmayın. + +Ancak Pydantic otomatik veri dönüştürme yapar. + +Yani API client’larınız key’leri sadece string olarak gönderebilse bile, bu string’ler saf tamsayı içeriyorsa Pydantic bunları dönüştürür ve doğrular. + +Ve `weights` olarak aldığınız `dict`, gerçekte `int` key’lere ve `float` value’lara sahip olur. + +/// + +## Özet { #recap } + +**FastAPI** ile Pydantic modellerinin sağladığı en yüksek esnekliği elde ederken, kodunuzu da basit, kısa ve şık tutarsınız. + +Üstelik tüm avantajlarla birlikte: + +* Editör desteği (her yerde tamamlama!) +* Veri dönüştürme (diğer adıyla parsing / serialization) +* Veri doğrulama (validation) +* Schema dokümantasyonu +* Otomatik dokümanlar diff --git a/docs/tr/docs/tutorial/body-updates.md b/docs/tr/docs/tutorial/body-updates.md new file mode 100644 index 000000000..a9ad13d2e --- /dev/null +++ b/docs/tr/docs/tutorial/body-updates.md @@ -0,0 +1,100 @@ +# Body - Güncellemeler { #body-updates } + +## `PUT` ile değiştirerek güncelleme { #update-replacing-with-put } + +Bir öğeyi güncellemek için HTTP `PUT` operasyonunu kullanabilirsiniz. + +Girdi verisini JSON olarak saklanabilecek bir formata (ör. bir NoSQL veritabanı ile) dönüştürmek için `jsonable_encoder` kullanabilirsiniz. Örneğin, `datetime` değerlerini `str`'ye çevirmek gibi. + +{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *} + +`PUT`, mevcut verinin yerine geçmesi gereken veriyi almak için kullanılır. + +### Değiştirerek güncelleme uyarısı { #warning-about-replacing } + +Bu, `bar` öğesini `PUT` ile, body içinde şu verilerle güncellemek isterseniz: + +```Python +{ + "name": "Barz", + "price": 3, + "description": None, +} +``` + +zaten kayıtlı olan `"tax": 20.2` alanını içermediği için, input model `"tax": 10.5` varsayılan değerini kullanacaktır. + +Ve veri, bu "yeni" `tax` değeri olan `10.5` ile kaydedilecektir. + +## `PATCH` ile kısmi güncellemeler { #partial-updates-with-patch } + +Veriyi *kısmen* güncellemek için HTTP `PATCH` operasyonunu da kullanabilirsiniz. + +Bu, yalnızca güncellemek istediğiniz veriyi gönderip, geri kalanını olduğu gibi bırakabileceğiniz anlamına gelir. + +/// note | Not + +`PATCH`, `PUT`'a göre daha az yaygın kullanılır ve daha az bilinir. + +Hatta birçok ekip, kısmi güncellemeler için bile yalnızca `PUT` kullanır. + +Bunları nasıl isterseniz öyle kullanmakta **özgürsünüz**; **FastAPI** herhangi bir kısıtlama dayatmaz. + +Ancak bu kılavuz, aşağı yukarı, bunların nasıl kullanılması amaçlandığını gösterir. + +/// + +### Pydantic'in `exclude_unset` parametresini kullanma { #using-pydantics-exclude-unset-parameter } + +Kısmi güncellemeler almak istiyorsanız, Pydantic modelinin `.model_dump()` metodundaki `exclude_unset` parametresini kullanmak çok faydalıdır. + +Örneğin: `item.model_dump(exclude_unset=True)`. + +Bu, `item` modeli oluşturulurken set edilmiş verileri içeren; varsayılan değerleri hariç tutan bir `dict` üretir. + +Sonrasında bunu, yalnızca set edilmiş (request'te gönderilmiş) veriyi içeren; varsayılan değerleri atlayan bir `dict` üretmek için kullanabilirsiniz: + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *} + +### Pydantic'in `update` parametresini kullanma { #using-pydantics-update-parameter } + +Artık `.model_copy()` ile mevcut modelin bir kopyasını oluşturup, güncellenecek verileri içeren bir `dict` ile `update` parametresini geçebilirsiniz. + +Örneğin: `stored_item_model.model_copy(update=update_data)`: + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} + +### Kısmi güncellemeler özeti { #partial-updates-recap } + +Özetle, kısmi güncelleme uygulamak için şunları yaparsınız: + +* (İsteğe bağlı olarak) `PUT` yerine `PATCH` kullanın. +* Kayıtlı veriyi alın. +* Bu veriyi bir Pydantic modeline koyun. +* Input modelinden, varsayılan değerler olmadan bir `dict` üretin (`exclude_unset` kullanarak). + * Bu şekilde, modelinizdeki varsayılan değerlerle daha önce saklanmış değerlerin üzerine yazmak yerine, yalnızca kullanıcının gerçekten set ettiği değerleri güncellersiniz. +* Kayıtlı modelin bir kopyasını oluşturun ve alınan kısmi güncellemeleri kullanarak attribute'larını güncelleyin (`update` parametresini kullanarak). +* Kopyalanan modeli DB'nizde saklanabilecek bir şeye dönüştürün (ör. `jsonable_encoder` kullanarak). + * Bu, modelin `.model_dump()` metodunu yeniden kullanmaya benzer; ancak değerlerin JSON'a dönüştürülebilecek veri tiplerine çevrilmesini garanti eder (ör. `datetime` -> `str`). +* Veriyi DB'nize kaydedin. +* Güncellenmiş modeli döndürün. + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *} + +/// tip | İpucu + +Aynı tekniği HTTP `PUT` operasyonu ile de kullanabilirsiniz. + +Ancak buradaki örnek `PATCH` kullanıyor, çünkü bu kullanım senaryoları için tasarlanmıştır. + +/// + +/// note | Not + +Input modelin yine de doğrulandığına dikkat edin. + +Dolayısıyla, tüm attribute'ların atlanabildiği kısmi güncellemeler almak istiyorsanız, tüm attribute'ları optional olarak işaretlenmiş (varsayılan değerlerle veya `None` ile) bir modele ihtiyacınız vardır. + +**Güncelleme** için tüm değerleri optional olan modeller ile **oluşturma** için zorunlu değerlere sahip modelleri ayırmak için, [Extra Models](extra-models.md){.internal-link target=_blank} bölümünde anlatılan fikirleri kullanabilirsiniz. + +/// diff --git a/docs/tr/docs/tutorial/body.md b/docs/tr/docs/tutorial/body.md new file mode 100644 index 000000000..0557ef737 --- /dev/null +++ b/docs/tr/docs/tutorial/body.md @@ -0,0 +1,166 @@ +# Request Body { #request-body } + +Bir client'ten (örneğin bir tarayıcıdan) API'nize veri göndermeniz gerektiğinde, bunu **request body** olarak gönderirsiniz. + +Bir **request** body, client'in API'nize gönderdiği veridir. Bir **response** body ise API'nizin client'e gönderdiği veridir. + +API'niz neredeyse her zaman bir **response** body göndermek zorundadır. Ancak client'lerin her zaman **request body** göndermesi gerekmez; bazen sadece bir path isterler, belki birkaç query parametresiyle birlikte, ama body göndermezler. + +Bir **request** body tanımlamak için, tüm gücü ve avantajlarıyla Pydantic modellerini kullanırsınız. + +/// info | Bilgi + +Veri göndermek için şunlardan birini kullanmalısınız: `POST` (en yaygını), `PUT`, `DELETE` veya `PATCH`. + +`GET` request'i ile body göndermek, spesifikasyonlarda tanımsız bir davranıştır; yine de FastAPI bunu yalnızca çok karmaşık/uç kullanım senaryoları için destekler. + +Önerilmediği için Swagger UI ile etkileşimli dokümanlar, `GET` kullanırken body için dokümantasyonu göstermez ve aradaki proxy'ler bunu desteklemeyebilir. + +/// + +## Pydantic'in `BaseModel`'ini import edin { #import-pydantics-basemodel } + +Önce, `pydantic` içinden `BaseModel`'i import etmeniz gerekir: + +{* ../../docs_src/body/tutorial001_py310.py hl[2] *} + +## Veri modelinizi oluşturun { #create-your-data-model } + +Sonra veri modelinizi, `BaseModel`'den kalıtım alan bir class olarak tanımlarsınız. + +Tüm attribute'lar için standart Python type'larını kullanın: + +{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} + + +Query parametrelerini tanımlarken olduğu gibi, bir model attribute'ü default bir değere sahipse zorunlu değildir. Aksi halde zorunludur. Sadece opsiyonel yapmak için `None` kullanın. + +Örneğin, yukarıdaki model şu şekilde bir JSON "`object`" (veya Python `dict`) tanımlar: + +```JSON +{ + "name": "Foo", + "description": "An optional description", + "price": 45.2, + "tax": 3.5 +} +``` + +...`description` ve `tax` opsiyonel olduğu için (default değerleri `None`), şu JSON "`object`" da geçerli olur: + +```JSON +{ + "name": "Foo", + "price": 45.2 +} +``` + +## Parametre olarak tanımlayın { #declare-it-as-a-parameter } + +Bunu *path operation*'ınıza eklemek için, path ve query parametrelerini tanımladığınız şekilde tanımlayın: + +{* ../../docs_src/body/tutorial001_py310.py hl[16] *} + +...ve type'ını, oluşturduğunuz model olan `Item` olarak belirtin. + +## Sonuçlar { #results } + +Sadece bu Python type tanımıyla, **FastAPI** şunları yapar: + +* Request'in body'sini JSON olarak okur. +* İlgili type'lara dönüştürür (gerekirse). +* Veriyi doğrular (validate eder). + * Veri geçersizse, tam olarak nerede ve hangi verinin hatalı olduğunu söyleyen, anlaşılır bir hata döndürür. +* Aldığı veriyi `item` parametresi içinde size verir. + * Fonksiyonda bunun type'ını `Item` olarak tanımladığınız için, tüm attribute'lar ve type'ları için editor desteğini (tamamlama vb.) de alırsınız. +* Modeliniz için JSON Schema tanımları üretir; projeniz için anlamlıysa bunları başka yerlerde de kullanabilirsiniz. +* Bu şemalar üretilen OpenAPI şemasının bir parçası olur ve otomatik dokümantasyon UIs tarafından kullanılır. + +## Otomatik dokümanlar { #automatic-docs } + +Modellerinizin JSON Schema'ları, OpenAPI tarafından üretilen şemanın bir parçası olur ve etkileşimli API dokümanlarında gösterilir: + + + +Ayrıca, ihtiyaç duyan her *path operation* içindeki API dokümanlarında da kullanılır: + + + +## Editor desteği { #editor-support } + +Editor'ünüzde, fonksiyonunuzun içinde her yerde type hint'leri ve tamamlama (completion) alırsınız (Pydantic modeli yerine `dict` alsaydınız bu olmazdı): + + + +Yanlış type işlemleri için hata kontrolleri de alırsınız: + + + +Bu bir tesadüf değil; tüm framework bu tasarımın etrafında inşa edildi. + +Ayrıca, bunun tüm editor'lerle çalışacağından emin olmak için herhangi bir implementasyon yapılmadan önce tasarım aşamasında kapsamlı şekilde test edildi. + +Hatta bunu desteklemek için Pydantic'in kendisinde bile bazı değişiklikler yapıldı. + +Önceki ekran görüntüleri Visual Studio Code ile alınmıştır. + +Ancak PyCharm ve diğer Python editor'lerinin çoğunda da aynı editor desteğini alırsınız: + + + +/// tip | İpucu + +Editor olarak PyCharm kullanıyorsanız, Pydantic PyCharm Plugin kullanabilirsiniz. + +Pydantic modelleri için editor desteğini şu açılardan iyileştirir: + +* auto-completion +* type checks +* refactoring +* searching +* inspections + +/// + +## Modeli kullanın { #use-the-model } + +Fonksiyonun içinde model nesnesinin tüm attribute'larına doğrudan erişebilirsiniz: + +{* ../../docs_src/body/tutorial002_py310.py *} + +## Request body + path parametreleri { #request-body-path-parameters } + +Path parametrelerini ve request body'yi aynı anda tanımlayabilirsiniz. + +**FastAPI**, path parametreleriyle eşleşen fonksiyon parametrelerinin **path'ten alınması** gerektiğini ve Pydantic model olarak tanımlanan fonksiyon parametrelerinin **request body'den alınması** gerektiğini anlayacaktır. + +{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} + + +## Request body + path + query parametreleri { #request-body-path-query-parameters } + +**body**, **path** ve **query** parametrelerini aynı anda da tanımlayabilirsiniz. + +**FastAPI** bunların her birini tanır ve veriyi doğru yerden alır. + +{* ../../docs_src/body/tutorial004_py310.py hl[16] *} + +Fonksiyon parametreleri şu şekilde tanınır: + +* Parametre, **path** içinde de tanımlıysa path parametresi olarak kullanılır. +* Parametre **tekil bir type**'taysa (`int`, `float`, `str`, `bool` vb.), **query** parametresi olarak yorumlanır. +* Parametre bir **Pydantic model** type'ı olarak tanımlandıysa, request **body** olarak yorumlanır. + +/// note | Not + +FastAPI, `q` değerinin zorunlu olmadığını `= None` default değerinden anlayacaktır. + +`str | None` (Python 3.10+) veya `Union[str, None]` (Python 3.9+) içindeki `Union`, FastAPI tarafından bu değerin zorunlu olmadığını belirlemek için kullanılmaz; FastAPI bunun zorunlu olmadığını `= None` default değeri olduğu için bilir. + +Ancak type annotation'larını eklemek, editor'ünüzün size daha iyi destek vermesini ve hataları yakalamasını sağlar. + +/// + +## Pydantic olmadan { #without-pydantic } + +Pydantic modellerini kullanmak istemiyorsanız, **Body** parametrelerini de kullanabilirsiniz. [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank} dokümanına bakın. diff --git a/docs/tr/docs/tutorial/cookie-param-models.md b/docs/tr/docs/tutorial/cookie-param-models.md new file mode 100644 index 000000000..a5bf51560 --- /dev/null +++ b/docs/tr/docs/tutorial/cookie-param-models.md @@ -0,0 +1,76 @@ +# Cookie Parameter Models { #cookie-parameter-models } + +Birbirleriyle ilişkili bir **cookie** grubunuz varsa, bunları tanımlamak için bir **Pydantic model** oluşturabilirsiniz. + +Bu sayede **model'i yeniden kullanabilir**, **birden fazla yerde** tekrar tekrar kullanabilir ve tüm parametreler için validation ve metadata'yı tek seferde tanımlayabilirsiniz. + +/// note | Not + +Bu özellik FastAPI `0.115.0` sürümünden beri desteklenmektedir. + +/// + +/// tip | İpucu + +Aynı teknik `Query`, `Cookie` ve `Header` için de geçerlidir. + +/// + +## Pydantic Model ile Cookies { #cookies-with-a-pydantic-model } + +İhtiyacınız olan **cookie** parametrelerini bir **Pydantic model** içinde tanımlayın ve ardından parametreyi `Cookie` olarak bildirin: + +{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} + +**FastAPI**, request ile gelen **cookies** içinden **her bir field** için veriyi **extract** eder ve size tanımladığınız Pydantic model'i verir. + +## Dokümanları Kontrol Edin { #check-the-docs } + +Tanımlanan cookie'leri `/docs` altındaki docs UI'da görebilirsiniz: + +
+ +
+ +/// info | Bilgi + +Tarayıcıların cookie'leri özel biçimlerde ve arka planda yönetmesi nedeniyle, **JavaScript**'in cookie'lere erişmesine kolayca izin vermediğini aklınızda bulundurun. + +`/docs` altındaki **API docs UI**'a giderseniz, *path operation*'larınız için cookie'lerin **dokümantasyonunu** görebilirsiniz. + +Ancak verileri **doldurup** "Execute" düğmesine tıklasanız bile, docs UI **JavaScript** ile çalıştığı için cookie'ler gönderilmez; dolayısıyla hiç değer girmemişsiniz gibi bir **error** mesajı görürsünüz. + +/// + +## Fazladan Cookies'leri Yasaklayın { #forbid-extra-cookies } + +Bazı özel kullanım senaryolarında (muhtemelen çok yaygın değildir) almak istediğiniz cookie'leri **kısıtlamak** isteyebilirsiniz. + +API'niz artık kendi cookie consent'ını kontrol etme gücüne sahip. + +Pydantic'in model configuration'ını kullanarak `extra` olan herhangi bir field'ı `forbid` edebilirsiniz: + +{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *} + +Bir client **fazladan cookie** göndermeye çalışırsa, bir **error** response alır. + +Onayınızı almak için bunca çaba harcayan zavallı cookie banner'ları... API'nin bunu reddetmesi için. + +Örneğin client, değeri `good-list-please` olan bir `santa_tracker` cookie'si göndermeye çalışırsa, client `santa_tracker` cookie is not allowed diyen bir **error** response alır: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "santa_tracker"], + "msg": "Extra inputs are not permitted", + "input": "good-list-please", + } + ] +} +``` + +## Özet { #summary } + +**FastAPI**'de **cookies** tanımlamak için **Pydantic model**'lerini kullanabilirsiniz. 😎 diff --git a/docs/tr/docs/tutorial/cors.md b/docs/tr/docs/tutorial/cors.md new file mode 100644 index 000000000..aae560022 --- /dev/null +++ b/docs/tr/docs/tutorial/cors.md @@ -0,0 +1,89 @@ +# CORS (Cross-Origin Resource Sharing) { #cors-cross-origin-resource-sharing } + +CORS veya "Cross-Origin Resource Sharing", tarayıcıda çalışan bir frontend’in JavaScript kodunun bir backend ile iletişim kurduğu ve backend’in frontend’den farklı bir "origin"de olduğu durumları ifade eder. + +## Origin { #origin } + +Origin; protocol (`http`, `https`), domain (`myapp.com`, `localhost`, `localhost.tiangolo.com`) ve port’un (`80`, `443`, `8080`) birleşimidir. + +Dolayısıyla şunların hepsi farklı origin’lerdir: + +* `http://localhost` +* `https://localhost` +* `http://localhost:8080` + +Hepsi `localhost` üzerinde olsa bile, farklı protocol veya port kullandıkları için farklı "origin" sayılırlar. + +## Adımlar { #steps } + +Diyelim ki tarayıcınızda `http://localhost:8080` adresinde çalışan bir frontend’iniz var ve JavaScript’i, `http://localhost` adresinde çalışan bir backend ile iletişim kurmaya çalışıyor (port belirtmediğimiz için tarayıcı varsayılan port olan `80`’i kullanacaktır). + +Bu durumda tarayıcı, `:80`-backend’e bir HTTP `OPTIONS` request’i gönderir. Eğer backend, bu farklı origin’den (`http://localhost:8080`) gelen iletişimi yetkilendiren uygun header’ları gönderirse, `:8080`-tarayıcı frontend’deki JavaScript’in `:80`-backend’e request göndermesine izin verir. + +Bunu sağlayabilmek için `:80`-backend’in bir "allowed origins" listesi olmalıdır. + +Bu örnekte `:8080`-frontend’in doğru çalışması için listede `http://localhost:8080` bulunmalıdır. + +## Wildcard'lar { #wildcards } + +Listeyi `"*"` (bir "wildcard") olarak tanımlayıp, hepsine izin verildiğini söylemek de mümkündür. + +Ancak bu, credentials içeren her şeyi hariç tutarak yalnızca belirli iletişim türlerine izin verir: Cookie’ler, Bearer Token’larla kullanılanlar gibi Authorization header’ları, vb. + +Bu yüzden her şeyin düzgün çalışması için, izin verilen origin’leri açıkça belirtmek daha iyidir. + +## `CORSMiddleware` Kullanımı { #use-corsmiddleware } + +Bunu **FastAPI** uygulamanızda `CORSMiddleware` ile yapılandırabilirsiniz. + +* `CORSMiddleware`’i import edin. +* İzin verilen origin’lerden (string) oluşan bir liste oluşturun. +* Bunu **FastAPI** uygulamanıza bir "middleware" olarak ekleyin. + +Ayrıca backend’in şunlara izin verip vermediğini de belirtebilirsiniz: + +* Credentials (Authorization header’ları, Cookie’ler, vb). +* Belirli HTTP method’ları (`POST`, `PUT`) veya wildcard `"*"` ile hepsini. +* Belirli HTTP header’ları veya wildcard `"*"` ile hepsini. + +{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} + + +`CORSMiddleware` implementasyonu tarafından kullanılan varsayılan parametreler kısıtlayıcıdır; bu nedenle tarayıcıların Cross-Domain bağlamında kullanmasına izin vermek için belirli origin’leri, method’ları veya header’ları açıkça etkinleştirmeniz gerekir. + +Aşağıdaki argümanlar desteklenir: + +* `allow_origins` - Cross-origin request yapmasına izin verilmesi gereken origin’lerin listesi. Örn. `['https://example.org', 'https://www.example.org']`. Herhangi bir origin’e izin vermek için `['*']` kullanabilirsiniz. +* `allow_origin_regex` - Cross-origin request yapmasına izin verilmesi gereken origin’lerle eşleşecek bir regex string’i. Örn. `'https://.*\.example\.org'`. +* `allow_methods` - Cross-origin request’lerde izin verilmesi gereken HTTP method’larının listesi. Varsayılanı `['GET']`. Tüm standart method’lara izin vermek için `['*']` kullanabilirsiniz. +* `allow_headers` - Cross-origin request’lerde desteklenmesi gereken HTTP request header’larının listesi. Varsayılanı `[]`. Tüm header’lara izin vermek için `['*']` kullanabilirsiniz. `Accept`, `Accept-Language`, `Content-Language` ve `Content-Type` header’larına basit CORS request'leri için her zaman izin verilir. +* `allow_credentials` - Cross-origin request’ler için cookie desteği olup olmayacağını belirtir. Varsayılanı `False`. + + `allow_credentials` `True` olarak ayarlanmışsa, `allow_origins`, `allow_methods` ve `allow_headers` değerlerinin hiçbiri `['*']` olamaz. Hepsinin açıkça belirtilmesi gerekir. + +* `expose_headers` - Tarayıcının erişebilmesi gereken response header’larını belirtir. Varsayılanı `[]`. +* `max_age` - Tarayıcıların CORS response’larını cache’lemesi için saniye cinsinden azami süreyi ayarlar. Varsayılanı `600`. + +Middleware iki özel HTTP request türüne yanıt verir... + +### CORS preflight request'leri { #cors-preflight-requests } + +Bunlar, `Origin` ve `Access-Control-Request-Method` header’larına sahip herhangi bir `OPTIONS` request’idir. + +Bu durumda middleware gelen request’i intercept eder ve uygun CORS header’larıyla yanıt verir; bilgilendirme amaçlı olarak da `200` veya `400` response döndürür. + +### Basit request'ler { #simple-requests } + +`Origin` header’ı olan herhangi bir request. Bu durumda middleware request’i normal şekilde geçirir, ancak response’a uygun CORS header’larını ekler. + +## Daha Fazla Bilgi { #more-info } + +CORS hakkında daha fazla bilgi için Mozilla CORS dokümantasyonuna bakın. + +/// note | Teknik Detaylar + +`from starlette.middleware.cors import CORSMiddleware` şeklinde de kullanabilirsiniz. + +**FastAPI**, geliştirici olarak size kolaylık olması için `fastapi.middleware` içinde bazı middleware’ler sağlar. Ancak mevcut middleware’lerin çoğu doğrudan Starlette’ten gelir. + +/// diff --git a/docs/tr/docs/tutorial/debugging.md b/docs/tr/docs/tutorial/debugging.md new file mode 100644 index 000000000..54d5c9252 --- /dev/null +++ b/docs/tr/docs/tutorial/debugging.md @@ -0,0 +1,113 @@ +# Debugging { #debugging } + +Visual Studio Code veya PyCharm gibi editörünüzde debugger'ı bağlayabilirsiniz. + +## `uvicorn`'ı Çağırma { #call-uvicorn } + +FastAPI uygulamanızda `uvicorn`'ı import edip doğrudan çalıştırın: + +{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} + +### `__name__ == "__main__"` Hakkında { #about-name-main } + +`__name__ == "__main__"` ifadesinin temel amacı, dosyanız şu şekilde çağrıldığında çalışacak: + +
+ +```console +$ python myapp.py +``` + +
+ +ancak başka bir dosya onu import ettiğinde çalışmayacak bir kod bölümüne sahip olmaktır, örneğin: + +```Python +from myapp import app +``` + +#### Daha fazla detay { #more-details } + +Dosyanızın adının `myapp.py` olduğunu varsayalım. + +Şu şekilde çalıştırırsanız: + +
+ +```console +$ python myapp.py +``` + +
+ +Python tarafından otomatik oluşturulan, dosyanızın içindeki `__name__` adlı dahili değişkenin değeri `"__main__"` string'i olur. + +Dolayısıyla şu bölüm: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +çalışır. + +--- + +Ancak modülü (dosyayı) import ederseniz bu gerçekleşmez. + +Yani örneğin `importer.py` adında başka bir dosyanız var ve içinde şunlar bulunuyorsa: + +```Python +from myapp import app + +# Some more code +``` + +bu durumda `myapp.py` içindeki otomatik oluşturulan `__name__` değişkeni `"__main__"` değerine sahip olmaz. + +Bu yüzden şu satır: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +çalıştırılmaz. + +/// info | Bilgi + +Daha fazla bilgi için resmi Python dokümantasyonuna bakın. + +/// + +## Kodunuzu Debugger ile Çalıştırma { #run-your-code-with-your-debugger } + +Uvicorn server'ını doğrudan kodunuzdan çalıştırdığınız için, Python programınızı (FastAPI uygulamanızı) debugger'dan doğrudan başlatabilirsiniz. + +--- + +Örneğin Visual Studio Code'da şunları yapabilirsiniz: + +* "Debug" paneline gidin. +* "Add configuration..." seçin. +* "Python" seçin +* "`Python: Current File (Integrated Terminal)`" seçeneğiyle debugger'ı çalıştırın. + +Böylece server, **FastAPI** kodunuzla başlar; breakpoint'lerinizde durur vb. + +Aşağıdaki gibi görünebilir: + + + +--- + +PyCharm kullanıyorsanız şunları yapabilirsiniz: + +* "Run" menüsünü açın. +* "Debug..." seçeneğini seçin. +* Bir context menü açılır. +* Debug edilecek dosyayı seçin (bu örnekte `main.py`). + +Böylece server, **FastAPI** kodunuzla başlar; breakpoint'lerinizde durur vb. + +Aşağıdaki gibi görünebilir: + + diff --git a/docs/tr/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/tr/docs/tutorial/dependencies/classes-as-dependencies.md new file mode 100644 index 000000000..9ee57cb29 --- /dev/null +++ b/docs/tr/docs/tutorial/dependencies/classes-as-dependencies.md @@ -0,0 +1,288 @@ +# Dependency Olarak Class'lar { #classes-as-dependencies } + +**Dependency Injection** sistemine daha derinlemesine geçmeden önce, bir önceki örneği geliştirelim. + +## Önceki Örnekten Bir `dict` { #a-dict-from-the-previous-example } + +Önceki örnekte, dependency'mizden ("dependable") bir `dict` döndürüyorduk: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *} + +Ama sonra *path operation function* içindeki `commons` parametresinde bir `dict` alıyoruz. + +Ve biliyoruz ki editor'ler `dict`'ler için çok fazla destek (ör. completion) veremez; çünkü key'lerini ve value type'larını bilemezler. + +Daha iyisini yapabiliriz... + +## Bir Şeyi Dependency Yapan Nedir { #what-makes-a-dependency } + +Şimdiye kadar dependency'leri function olarak tanımlanmış şekilde gördünüz. + +Ancak dependency tanımlamanın tek yolu bu değil (muhtemelen en yaygını bu olsa da). + +Buradaki kritik nokta, bir dependency'nin "callable" olması gerektiğidir. + +Python'da "**callable**", Python'ın bir function gibi "çağırabildiği" her şeydir. + +Yani elinizde `something` adlı bir nesne varsa (function _olmak zorunda değil_) ve onu şöyle "çağırabiliyorsanız" (çalıştırabiliyorsanız): + +```Python +something() +``` + +veya + +```Python +something(some_argument, some_keyword_argument="foo") +``` + +o zaman bu bir "callable" demektir. + +## Dependency Olarak Class'lar { #classes-as-dependencies_1 } + +Python'da bir class'tan instance oluştururken de aynı söz dizimini kullandığınızı fark etmiş olabilirsiniz. + +Örneğin: + +```Python +class Cat: + def __init__(self, name: str): + self.name = name + + +fluffy = Cat(name="Mr Fluffy") +``` + +Bu durumda `fluffy`, `Cat` class'ının bir instance'ıdır. + +Ve `fluffy` oluşturmak için `Cat`'i "çağırmış" olursunuz. + +Dolayısıyla bir Python class'ı da bir **callable**'dır. + +O zaman **FastAPI** içinde bir Python class'ını dependency olarak kullanabilirsiniz. + +FastAPI'nin aslında kontrol ettiği şey, bunun bir "callable" olması (function, class ya da başka bir şey) ve tanımlı parametreleridir. + +Eğer **FastAPI**'de bir dependency olarak bir "callable" verirseniz, FastAPI o "callable" için parametreleri analiz eder ve bunları *path operation function* parametreleriyle aynı şekilde işler. Sub-dependency'ler dahil. + +Bu, hiç parametresi olmayan callable'lar için de geçerlidir. Tıpkı hiç parametresi olmayan *path operation function*'larda olduğu gibi. + +O zaman yukarıdaki `common_parameters` adlı "dependable" dependency'sini `CommonQueryParams` class'ına çevirebiliriz: + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *} + +Class instance'ını oluşturmak için kullanılan `__init__` metoduna dikkat edin: + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *} + +...bizim önceki `common_parameters` ile aynı parametrelere sahip: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *} + +Bu parametreler, dependency'yi "çözmek" için **FastAPI**'nin kullanacağı şeylerdir. + +Her iki durumda da şunlar olacak: + +* `str` olan opsiyonel bir `q` query parametresi. +* Default değeri `0` olan `int` tipinde bir `skip` query parametresi. +* Default değeri `100` olan `int` tipinde bir `limit` query parametresi. + +Her iki durumda da veriler dönüştürülecek, doğrulanacak, OpenAPI şemasında dokümante edilecek, vb. + +## Kullanalım { #use-it } + +Artık bu class'ı kullanarak dependency'nizi tanımlayabilirsiniz. + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *} + +**FastAPI**, `CommonQueryParams` class'ını çağırır. Bu, o class'ın bir "instance"ını oluşturur ve bu instance, sizin function'ınıza `commons` parametresi olarak geçirilir. + +## Type Annotation vs `Depends` { #type-annotation-vs-depends } + +Yukarıdaki kodda `CommonQueryParams`'ı iki kez yazdığımıza dikkat edin: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ Annotated Olmadan + +/// tip | İpucu + +Mümkünse `Annotated` sürümünü kullanmayı tercih edin. + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +Şuradaki son `CommonQueryParams`: + +```Python +... Depends(CommonQueryParams) +``` + +...FastAPI'nin dependency'nin ne olduğunu anlamak için gerçekten kullandığı şeydir. + +FastAPI tanımlanan parametreleri buradan çıkarır ve aslında çağıracağı şey de budur. + +--- + +Bu durumda, şuradaki ilk `CommonQueryParams`: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, ... +``` + +//// + +//// tab | Python 3.9+ Annotated Olmadan + +/// tip | İpucu + +Mümkünse `Annotated` sürümünü kullanmayı tercih edin. + +/// + +```Python +commons: CommonQueryParams ... +``` + +//// + +...**FastAPI** için özel bir anlam taşımaz. FastAPI bunu veri dönüştürme, doğrulama vb. için kullanmaz (çünkü bunlar için `Depends(CommonQueryParams)` kullanıyor). + +Hatta şunu bile yazabilirsiniz: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[Any, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ Annotated Olmadan + +/// tip | İpucu + +Mümkünse `Annotated` sürümünü kullanmayı tercih edin. + +/// + +```Python +commons = Depends(CommonQueryParams) +``` + +//// + +...şu örnekte olduğu gibi: + +{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *} + +Ancak type'ı belirtmeniz önerilir; böylece editor'ünüz `commons` parametresine ne geçirileceğini bilir ve size code completion, type check'leri vb. konularda yardımcı olur: + + + +## Kısayol { #shortcut } + +Ama burada bir miktar kod tekrarımız olduğunu görüyorsunuz; `CommonQueryParams`'ı iki kez yazıyoruz: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ Annotated Olmadan + +/// tip | İpucu + +Mümkünse `Annotated` sürümünü kullanmayı tercih edin. + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +**FastAPI**, bu durumlar için bir kısayol sağlar: dependency'nin *özellikle* FastAPI'nin bir instance oluşturmak için "çağıracağı" bir class olduğu durumlar. + +Bu özel durumlarda şunu yapabilirsiniz: + +Şunu yazmak yerine: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ Annotated Olmadan + +/// tip | İpucu + +Mümkünse `Annotated` sürümünü kullanmayı tercih edin. + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +...şunu yazarsınız: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends()] +``` + +//// + +//// tab | Python 3.9+ Annotated Olmadan + +/// tip | İpucu + +Mümkünse `Annotated` sürümünü kullanmayı tercih edin. + +/// + +```Python +commons: CommonQueryParams = Depends() +``` + +//// + +Dependency'yi parametrenin type'ı olarak tanımlarsınız ve `Depends(CommonQueryParams)` içinde class'ı *yeniden* yazmak yerine, parametre vermeden `Depends()` kullanırsınız. + +Aynı örnek şu hale gelir: + +{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *} + +...ve **FastAPI** ne yapması gerektiğini bilir. + +/// tip | İpucu + +Bu size faydalı olmaktan çok kafa karıştırıcı geliyorsa, kullanmayın; buna *ihtiyacınız* yok. + +Bu sadece bir kısayoldur. Çünkü **FastAPI** kod tekrarını en aza indirmenize yardımcı olmayı önemser. + +/// diff --git a/docs/tr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/tr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md new file mode 100644 index 000000000..4903aec4a --- /dev/null +++ b/docs/tr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -0,0 +1,69 @@ +# Path Operation Decorator'lerinde Dependency'ler { #dependencies-in-path-operation-decorators } + +Bazı durumlarda bir dependency'nin döndürdüğü değere *path operation function* içinde gerçekten ihtiyacınız olmaz. + +Ya da dependency zaten bir değer döndürmüyordur. + +Ancak yine de çalıştırılmasını/çözülmesini istersiniz. + +Bu gibi durumlarda, `Depends` ile bir *path operation function* parametresi tanımlamak yerine, *path operation decorator*'üne `dependencies` adında bir `list` ekleyebilirsiniz. + +## *Path Operation Decorator*'üne `dependencies` Ekleyin { #add-dependencies-to-the-path-operation-decorator } + +*Path operation decorator*, opsiyonel bir `dependencies` argümanı alır. + +Bu, `Depends()` öğelerinden oluşan bir `list` olmalıdır: + +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} + +Bu dependency'ler normal dependency'lerle aynı şekilde çalıştırılır/çözülür. Ancak (eğer bir değer döndürüyorlarsa) bu değer *path operation function*'ınıza aktarılmaz. + +/// tip | İpucu + +Bazı editörler, kullanılmayan function parametrelerini kontrol eder ve bunları hata olarak gösterebilir. + +Bu `dependencies` yaklaşımıyla, editör/araç hatalarına takılmadan dependency'lerin çalıştırılmasını sağlayabilirsiniz. + +Ayrıca kodunuzda kullanılmayan bir parametreyi gören yeni geliştiricilerin bunun gereksiz olduğunu düşünmesi gibi bir kafa karışıklığını da azaltabilir. + +/// + +/// info | Bilgi + +Bu örnekte uydurma özel header'lar olan `X-Key` ve `X-Token` kullanıyoruz. + +Ancak gerçek senaryolarda, security uygularken, entegre [Security yardımcı araçlarını (bir sonraki bölüm)](../security/index.md){.internal-link target=_blank} kullanmak size daha fazla fayda sağlar. + +/// + +## Dependency Hataları ve Return Değerleri { #dependencies-errors-and-return-values } + +Normalde kullandığınız aynı dependency *function*'larını burada da kullanabilirsiniz. + +### Dependency Gereksinimleri { #dependency-requirements } + +Request gereksinimleri (header'lar gibi) veya başka alt dependency'ler tanımlayabilirler: + +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} + +### Exception Fırlatmak { #raise-exceptions } + +Bu dependency'ler, normal dependency'lerde olduğu gibi `raise` ile exception fırlatabilir: + +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} + +### Return Değerleri { #return-values } + +Ayrıca değer döndürebilirler ya da döndürmeyebilirler; dönen değer kullanılmayacaktır. + +Yani başka bir yerde zaten kullandığınız, değer döndüren normal bir dependency'yi tekrar kullanabilirsiniz; değer kullanılmasa bile dependency çalıştırılacaktır: + +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} + +## Bir *Path Operation* Grubu İçin Dependency'ler { #dependencies-for-a-group-of-path-operations } + +Daha sonra, muhtemelen birden fazla dosya kullanarak daha büyük uygulamaları nasıl yapılandıracağınızı okurken ([Bigger Applications - Multiple Files](../../tutorial/bigger-applications.md){.internal-link target=_blank}), bir *path operation* grubu için tek bir `dependencies` parametresini nasıl tanımlayacağınızı öğreneceksiniz. + +## Global Dependency'ler { #global-dependencies } + +Sırada, dependency'leri tüm `FastAPI` uygulamasına nasıl ekleyeceğimizi göreceğiz; böylece her *path operation* için geçerli olacaklar. diff --git a/docs/tr/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/tr/docs/tutorial/dependencies/dependencies-with-yield.md new file mode 100644 index 000000000..bd025f799 --- /dev/null +++ b/docs/tr/docs/tutorial/dependencies/dependencies-with-yield.md @@ -0,0 +1,289 @@ +# `yield` ile Dependency'ler { #dependencies-with-yield } + +FastAPI, işini bitirdikten sonra ek adımlar çalıştıran dependency'leri destekler. + +Bunu yapmak için `return` yerine `yield` kullanın ve ek adımları (kodu) `yield` satırından sonra yazın. + +/// tip | İpucu + +Her dependency için yalnızca **bir kez** `yield` kullandığınızdan emin olun. + +/// + +/// note | Teknik Detaylar + +Şunlarla kullanılabilen herhangi bir fonksiyon: + +* `@contextlib.contextmanager` veya +* `@contextlib.asynccontextmanager` + +bir **FastAPI** dependency'si olarak kullanılabilir. + +Hatta FastAPI bu iki decorator'ı içeride (internally) kullanır. + +/// + +## `yield` ile Bir Veritabanı Dependency'si { #a-database-dependency-with-yield } + +Örneğin bunu, bir veritabanı session'ı oluşturmak ve iş bittikten sonra kapatmak için kullanabilirsiniz. + +Response oluşturulmadan önce yalnızca `yield` satırına kadar olan (ve `yield` dahil) kod çalıştırılır: + +{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} + +`yield` edilen değer, *path operation*'lara ve diğer dependency'lere enjekte edilen (injected) değerdir: + +{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} + +Response'dan sonra `yield` satırını takip eden kod çalıştırılır: + +{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} + +/// tip | İpucu + +`async` ya da normal fonksiyonlar kullanabilirsiniz. + +**FastAPI**, normal dependency'lerde olduğu gibi her ikisinde de doğru şekilde davranır. + +/// + +## `yield` ve `try` ile Bir Dependency { #a-dependency-with-yield-and-try } + +`yield` kullanan bir dependency içinde bir `try` bloğu kullanırsanız, dependency kullanılırken fırlatılan (raised) herhangi bir exception'ı alırsınız. + +Örneğin, başka bir dependency'de veya bir *path operation* içinde çalışan bir kod, bir veritabanı transaction'ını "rollback" yaptıysa ya da başka bir exception oluşturduysa, o exception dependency'nizde size gelir. + +Dolayısıyla `except SomeException` ile dependency içinde o spesifik exception'ı yakalayabilirsiniz. + +Aynı şekilde, exception olsun ya da olmasın çıkış (exit) adımlarının çalıştırılmasını garanti etmek için `finally` kullanabilirsiniz. + +{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} + +## `yield` ile Alt Dependency'ler { #sub-dependencies-with-yield } + +Her boyutta ve şekilde alt dependency'ler ve alt dependency "ağaçları" (trees) oluşturabilirsiniz; bunların herhangi biri veya hepsi `yield` kullanabilir. + +**FastAPI**, `yield` kullanan her dependency'deki "exit code"'un doğru sırayla çalıştırılmasını sağlar. + +Örneğin, `dependency_c`, `dependency_b`'ye; `dependency_b` de `dependency_a`'ya bağlı olabilir: + +{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} + +Ve hepsi `yield` kullanabilir. + +Bu durumda `dependency_c`, exit code'unu çalıştırabilmek için `dependency_b`'den gelen değerin (burada `dep_b`) hâlâ erişilebilir olmasına ihtiyaç duyar. + +Aynı şekilde `dependency_b` de exit code'u için `dependency_a`'dan gelen değerin (burada `dep_a`) erişilebilir olmasına ihtiyaç duyar. + +{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *} + +Benzer şekilde, bazı dependency'ler `yield`, bazıları `return` kullanabilir ve bunların bazıları diğerlerine bağlı olabilir. + +Ayrıca birden fazla `yield` kullanan dependency gerektiren tek bir dependency'niz de olabilir, vb. + +İstediğiniz herhangi bir dependency kombinasyonunu kullanabilirsiniz. + +**FastAPI** her şeyin doğru sırada çalışmasını sağlar. + +/// note | Teknik Detaylar + +Bu, Python'un Context Managers yapısı sayesinde çalışır. + +**FastAPI** bunu sağlamak için içeride onları kullanır. + +/// + +## `yield` ve `HTTPException` ile Dependency'ler { #dependencies-with-yield-and-httpexception } + +`yield` kullanan dependency'lerde `try` bloklarıyla bazı kodları çalıştırıp ardından `finally` sonrasında exit code çalıştırabileceğinizi gördünüz. + +Ayrıca `except` ile fırlatılan exception'ı yakalayıp onunla bir şey yapabilirsiniz. + +Örneğin `HTTPException` gibi farklı bir exception fırlatabilirsiniz. + +/// tip | İpucu + +Bu biraz ileri seviye bir tekniktir ve çoğu durumda gerçekten ihtiyaç duymazsınız; çünkü exception'ları (`HTTPException` dahil) uygulamanızın geri kalan kodundan, örneğin *path operation function* içinden fırlatabilirsiniz. + +Ama ihtiyaç duyarsanız diye burada. 🤓 + +/// + +{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *} + +Exception yakalayıp buna göre özel bir response oluşturmak istiyorsanız bir [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} oluşturun. + +## `yield` ve `except` ile Dependency'ler { #dependencies-with-yield-and-except } + +`yield` kullanan bir dependency içinde `except` ile bir exception yakalar ve bunu tekrar fırlatmazsanız (ya da yeni bir exception fırlatmazsanız), FastAPI normal Python'da olduğu gibi bir exception olduğunu fark edemez: + +{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} + +Bu durumda client, biz `HTTPException` veya benzeri bir şey fırlatmadığımız için olması gerektiği gibi *HTTP 500 Internal Server Error* response'u görür; ancak server **hiç log üretmez** ve hatanın ne olduğuna dair başka bir işaret de olmaz. 😱 + +### `yield` ve `except` Kullanan Dependency'lerde Her Zaman `raise` Edin { #always-raise-in-dependencies-with-yield-and-except } + +`yield` kullanan bir dependency içinde bir exception yakalarsanız, başka bir `HTTPException` veya benzeri bir şey fırlatmıyorsanız, **orijinal exception'ı tekrar raise etmelisiniz**. + +Aynı exception'ı `raise` ile tekrar fırlatabilirsiniz: + +{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *} + +Artık client yine aynı *HTTP 500 Internal Server Error* response'unu alır, ama server log'larda bizim özel `InternalError`'ımızı görür. 😎 + +## `yield` Kullanan Dependency'lerin Çalıştırılması { #execution-of-dependencies-with-yield } + +Çalıştırma sırası kabaca aşağıdaki diyagramdaki gibidir. Zaman yukarıdan aşağı akar. Her sütun, etkileşime giren veya kod çalıştıran parçalardan birini temsil eder. + +```mermaid +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,operation: Can raise exceptions, including HTTPException + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise Exception + dep -->> handler: Raise Exception + handler -->> client: HTTP error response + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise Exception (e.g. HTTPException) + opt handle + dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception + end + handler -->> client: HTTP error response + end + + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> tasks: Handle exceptions in the background task code + end +``` + +/// info | Bilgi + +Client'a yalnızca **tek bir response** gönderilir. Bu, error response'lardan biri olabilir ya da *path operation*'dan dönen response olabilir. + +Bu response'lardan biri gönderildikten sonra başka bir response gönderilemez. + +/// + +/// tip | İpucu + +*Path operation function* içindeki koddan herhangi bir exception raise ederseniz, `HTTPException` dahil olmak üzere bu exception `yield` kullanan dependency'lere aktarılır. Çoğu durumda, doğru şekilde ele alındığından emin olmak için `yield` kullanan dependency'den aynı exception'ı (veya yeni bir tanesini) yeniden raise etmek istersiniz. + +/// + +## Erken Çıkış ve `scope` { #early-exit-and-scope } + +Normalde `yield` kullanan dependency'lerin exit code'u, client'a response gönderildikten **sonra** çalıştırılır. + +Ama *path operation function*'dan döndükten sonra dependency'yi kullanmayacağınızı biliyorsanız, `Depends(scope="function")` kullanarak FastAPI'ye dependency'yi *path operation function* döndükten sonra kapatmasını, ancak **response gönderilmeden önce** kapatmasını söyleyebilirsiniz. + +{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *} + +`Depends()` şu değerleri alabilen bir `scope` parametresi alır: + +* `"function"`: dependency'yi request'i işleyen *path operation function* çalışmadan önce başlat, *path operation function* bittikten sonra bitir, ancak response client'a geri gönderilmeden **önce** sonlandır. Yani dependency fonksiyonu, *path operation **function***'ın **etrafında** çalıştırılır. +* `"request"`: dependency'yi request'i işleyen *path operation function* çalışmadan önce başlat (`"function"` kullanımına benzer), ancak response client'a geri gönderildikten **sonra** sonlandır. Yani dependency fonksiyonu, **request** ve response döngüsünün **etrafında** çalıştırılır. + +Belirtilmezse ve dependency `yield` kullanıyorsa, varsayılan olarak `scope` `"request"` olur. + +### Alt dependency'ler için `scope` { #scope-for-sub-dependencies } + +`scope="request"` (varsayılan) ile bir dependency tanımladığınızda, herhangi bir alt dependency'nin `scope` değeri de `"request"` olmalıdır. + +Ancak `scope` değeri `"function"` olan bir dependency, hem `"function"` hem de `"request"` scope'una sahip dependency'lere bağlı olabilir. + +Bunun nedeni, bir dependency'nin exit code'unu alt dependency'lerden önce çalıştırabilmesi gerekmesidir; çünkü exit code sırasında hâlâ onları kullanması gerekebilir. + +```mermaid +sequenceDiagram + +participant client as Client +participant dep_req as Dep scope="request" +participant dep_func as Dep scope="function" +participant operation as Path Operation + + client ->> dep_req: Start request + Note over dep_req: Run code up to yield + dep_req ->> dep_func: Pass dependency + Note over dep_func: Run code up to yield + dep_func ->> operation: Run path operation with dependency + operation ->> dep_func: Return from path operation + Note over dep_func: Run code after yield + Note over dep_func: ✅ Dependency closed + dep_func ->> client: Send response to client + Note over client: Response sent + Note over dep_req: Run code after yield + Note over dep_req: ✅ Dependency closed +``` + +## `yield`, `HTTPException`, `except` ve Background Tasks ile Dependency'ler { #dependencies-with-yield-httpexception-except-and-background-tasks } + +`yield` kullanan dependency'ler, zaman içinde farklı kullanım senaryolarını kapsamak ve bazı sorunları düzeltmek için gelişti. + +FastAPI'nin farklı sürümlerinde nelerin değiştiğini görmek isterseniz, advanced guide'da şu bölümü okuyabilirsiniz: [Advanced Dependencies - Dependencies with `yield`, `HTTPException`, `except` and Background Tasks](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}. + +## Context Managers { #context-managers } + +### "Context Managers" Nedir? { #what-are-context-managers } + +"Context Managers", `with` ifadesiyle kullanabildiğiniz Python nesneleridir. + +Örneğin, bir dosyayı okumak için `with` kullanabilirsiniz: + +```Python +with open("./somefile.txt") as f: + contents = f.read() + print(contents) +``` + +Temelde `open("./somefile.txt")`, "Context Manager" olarak adlandırılan bir nesne oluşturur. + +`with` bloğu bittiğinde, exception olsa bile dosyanın kapatılmasını garanti eder. + +`yield` ile bir dependency oluşturduğunuzda, **FastAPI** içeride bunun için bir context manager oluşturur ve bazı ilgili başka araçlarla birleştirir. + +### `yield` kullanan dependency'lerde context manager kullanma { #using-context-managers-in-dependencies-with-yield } + +/// warning | Uyarı + +Bu, az çok "ileri seviye" bir fikirdir. + +**FastAPI**'ye yeni başlıyorsanız şimdilik bunu atlamak isteyebilirsiniz. + +/// + +Python'da Context Manager'ları, iki method'a sahip bir class oluşturarak: `__enter__()` ve `__exit__()` yaratabilirsiniz. + +Ayrıca dependency fonksiyonunun içinde `with` veya `async with` ifadeleri kullanarak **FastAPI**'de `yield` kullanan dependency'lerin içinde de kullanabilirsiniz: + +{* ../../docs_src/dependencies/tutorial010_py39.py hl[1:9,13] *} + +/// tip | İpucu + +Bir context manager oluşturmanın başka bir yolu da şunlardır: + +* `@contextlib.contextmanager` veya +* `@contextlib.asynccontextmanager` + +Bunları, tek bir `yield` içeren bir fonksiyonu decorate etmek için kullanabilirsiniz. + +FastAPI, `yield` kullanan dependency'ler için içeride bunu yapar. + +Ancak FastAPI dependency'leri için bu decorator'ları kullanmak zorunda değilsiniz (hatta kullanmamalısınız). + +FastAPI bunu sizin yerinize içeride yapar. + +/// diff --git a/docs/tr/docs/tutorial/dependencies/global-dependencies.md b/docs/tr/docs/tutorial/dependencies/global-dependencies.md new file mode 100644 index 000000000..7f0025eaf --- /dev/null +++ b/docs/tr/docs/tutorial/dependencies/global-dependencies.md @@ -0,0 +1,16 @@ +# Global Dependencies { #global-dependencies } + +Bazı uygulama türlerinde, tüm uygulama için dependency eklemek isteyebilirsiniz. + +[`dependencies`'i *path operation decorator*'larına ekleyebildiğiniz](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} gibi, `FastAPI` uygulamasına da ekleyebilirsiniz. + +Bu durumda, uygulamadaki tüm *path operation*'lara uygulanırlar: + +{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *} + + +Ve [*path operation decorator*'larına `dependencies` ekleme](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} bölümündeki tüm fikirler hâlâ geçerlidir; ancak bu sefer, uygulamadaki tüm *path operation*'lar için geçerli olur. + +## *Path operations* grupları için Dependencies { #dependencies-for-groups-of-path-operations } + +İleride, daha büyük uygulamaları nasıl yapılandıracağınızı ([Bigger Applications - Multiple Files](../../tutorial/bigger-applications.md){.internal-link target=_blank}) okurken, muhtemelen birden fazla dosyayla birlikte, bir *path operations* grubu için tek bir `dependencies` parametresini nasıl tanımlayacağınızı öğreneceksiniz. diff --git a/docs/tr/docs/tutorial/dependencies/index.md b/docs/tr/docs/tutorial/dependencies/index.md new file mode 100644 index 000000000..f1e446d67 --- /dev/null +++ b/docs/tr/docs/tutorial/dependencies/index.md @@ -0,0 +1,250 @@ +# Bağımlılıklar { #dependencies } + +**FastAPI**, çok güçlü ama aynı zamanda sezgisel bir **Dependency Injection** sistemine sahiptir. + +Kullanımı çok basit olacak şekilde tasarlanmıştır ve herhangi bir geliştiricinin diğer bileşenleri **FastAPI** ile entegre etmesini kolaylaştırır. + +## "Dependency Injection" Nedir? { #what-is-dependency-injection } + +Programlamada **"Dependency Injection"**, kodunuzun (bu örnekte *path operation function*'larınızın) çalışmak ve kullanmak için ihtiyaç duyduğu şeyleri: "dependencies" (bağımlılıklar) olarak beyan edebilmesi anlamına gelir. + +Ardından bu sistem (bu örnekte **FastAPI**), kodunuza gerekli bağımlılıkları sağlamak ("inject" etmek) için gereken her şeyi sizin yerinize halleder. + +Bu yaklaşım, şunlara ihtiyaç duyduğunuzda özellikle faydalıdır: + +* Paylaşılan bir mantığa sahip olmak (aynı kod mantığını tekrar tekrar kullanmak). +* Veritabanı bağlantılarını paylaşmak. +* Güvenlik, authentication, rol gereksinimleri vb. kuralları zorunlu kılmak. +* Ve daha birçok şey... + +Tüm bunları, kod tekrarını minimumda tutarak yaparsınız. + +## İlk Adımlar { #first-steps } + +Çok basit bir örneğe bakalım. Şimdilik o kadar basit olacak ki pek işe yaramayacak. + +Ama bu sayede **Dependency Injection** sisteminin nasıl çalıştığına odaklanabiliriz. + +### Bir dependency (bağımlılık) veya "dependable" Oluşturun { #create-a-dependency-or-dependable } + +Önce dependency'e odaklanalım. + +Bu, bir *path operation function*'ın alabileceği parametrelerin aynısını alabilen basit bir fonksiyondur: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *} + +Bu kadar. + +**2 satır**. + +Ve tüm *path operation function*'larınızla aynı şekle ve yapıya sahiptir. + +Bunu, "decorator" olmadan (yani `@app.get("/some-path")` olmadan) yazılmış bir *path operation function* gibi düşünebilirsiniz. + +Ayrıca istediğiniz herhangi bir şeyi döndürebilir. + +Bu örnekte, bu dependency şunları bekler: + +* `str` olan, opsiyonel bir query parametresi `q`. +* `int` olan, opsiyonel bir query parametresi `skip` ve varsayılanı `0`. +* `int` olan, opsiyonel bir query parametresi `limit` ve varsayılanı `100`. + +Sonra da bu değerleri içeren bir `dict` döndürür. + +/// info | Bilgi + +FastAPI, `Annotated` desteğini 0.95.0 sürümünde ekledi (ve önermeye başladı). + +Daha eski bir sürüm kullanıyorsanız `Annotated` kullanmaya çalıştığınızda hata alırsınız. + +`Annotated` kullanmadan önce **FastAPI** sürümünü en az 0.95.1'e yükseltmek için [FastAPI sürümünü yükseltin](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}. + +/// + +### `Depends`'i Import Edin { #import-depends } + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *} + +### "Dependant" İçinde Dependency'yi Tanımlayın { #declare-the-dependency-in-the-dependant } + +*Path operation function* parametrelerinizde `Body`, `Query` vb. kullandığınız gibi, yeni bir parametreyle `Depends` kullanın: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *} + +Fonksiyon parametrelerinde `Depends`'i `Body`, `Query` vb. ile aynı şekilde kullansanız da `Depends` biraz farklı çalışır. + +`Depends`'e yalnızca tek bir parametre verirsiniz. + +Bu parametre, bir fonksiyon gibi bir şey olmalıdır. + +Onu doğrudan **çağırmazsınız** (sonuna parantez eklemezsiniz), sadece `Depends()`'e parametre olarak verirsiniz. + +Ve bu fonksiyon da, *path operation function*'lar gibi parametre alır. + +/// tip | İpucu + +Fonksiyonların dışında başka hangi "şeylerin" dependency olarak kullanılabildiğini bir sonraki bölümde göreceksiniz. + +/// + +Yeni bir request geldiğinde, **FastAPI** şunları sizin yerinize yapar: + +* Dependency ("dependable") fonksiyonunuzu doğru parametrelerle çağırır. +* Fonksiyonunuzun sonucunu alır. +* Bu sonucu *path operation function*'ınızdaki parametreye atar. + +```mermaid +graph TB + +common_parameters(["common_parameters"]) +read_items["/items/"] +read_users["/users/"] + +common_parameters --> read_items +common_parameters --> read_users +``` + +Bu şekilde paylaşılan kodu bir kez yazarsınız ve onu *path operation*'larda çağırma işini **FastAPI** halleder. + +/// check | Ek bilgi + +Dikkat edin: Bunu "register" etmek ya da benzeri bir şey yapmak için özel bir class oluşturup **FastAPI**'ye bir yere geçirmeniz gerekmez. + +Sadece `Depends`'e verirsiniz ve gerisini **FastAPI** nasıl yapacağını bilir. + +/// + +## `Annotated` Dependency'lerini Paylaşın { #share-annotated-dependencies } + +Yukarıdaki örneklerde, ufak bir **kod tekrarı** olduğunu görüyorsunuz. + +`common_parameters()` dependency'sini kullanmanız gerektiğinde, type annotation ve `Depends()` içeren parametrenin tamamını yazmanız gerekir: + +```Python +commons: Annotated[dict, Depends(common_parameters)] +``` + +Ancak `Annotated` kullandığımız için bu `Annotated` değerini bir değişkende saklayıp birden fazla yerde kullanabiliriz: + +{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *} + +/// tip | İpucu + +Bu aslında standart Python'dır; buna "type alias" denir ve **FastAPI**'ye özel bir şey değildir. + +Ama **FastAPI**, `Annotated` dahil Python standartları üzerine kurulu olduğu için bu tekniği kodunuzda kullanabilirsiniz. 😎 + +/// + +Dependency'ler beklediğiniz gibi çalışmaya devam eder ve **en güzel kısmı** da şudur: **type bilgisi korunur**. Bu da editörünüzün size **autocompletion**, **inline errors** vb. sağlamaya devam edeceği anlamına gelir. `mypy` gibi diğer araçlar için de aynısı geçerlidir. + +Bu özellikle, **büyük bir kod tabanında**, aynı dependency'leri **birçok *path operation*** içinde tekrar tekrar kullandığınızda çok faydalı olacaktır. + +## `async` Olsa da Olmasa da { #to-async-or-not-to-async } + +Dependency'ler de **FastAPI** tarafından çağrılacağı için (tıpkı *path operation function*'larınız gibi), fonksiyonları tanımlarken aynı kurallar geçerlidir. + +`async def` ya da normal `def` kullanabilirsiniz. + +Ayrıca normal `def` *path operation function*'ları içinde `async def` dependency tanımlayabilir veya `async def` *path operation function*'ları içinde `def` dependency kullanabilirsiniz vb. + +Fark etmez. **FastAPI** ne yapacağını bilir. + +/// note | Not + +Eğer bilmiyorsanız, dokümanlarda `async` ve `await` için [Async: *"In a hurry?"*](../../async.md#in-a-hurry){.internal-link target=_blank} bölümüne bakın. + +/// + +## OpenAPI ile Entegre { #integrated-with-openapi } + +Dependency'lerinizin (ve alt dependency'lerin) tüm request tanımları, doğrulamaları ve gereksinimleri aynı OpenAPI şemasına entegre edilir. + +Bu nedenle interaktif dokümanlar, bu dependency'lerden gelen tüm bilgileri de içerir: + + + +## Basit Kullanım { #simple-usage } + +Şöyle düşünürseniz: *Path operation function*'lar, bir *path* ve *operation* eşleştiğinde kullanılacak şekilde tanımlanır; ardından **FastAPI** fonksiyonu doğru parametrelerle çağırır ve request'ten veriyi çıkarır. + +Aslında tüm (veya çoğu) web framework'ü de aynı şekilde çalışır. + +Bu fonksiyonları hiçbir zaman doğrudan çağırmazsınız. Onları framework'ünüz (bu örnekte **FastAPI**) çağırır. + +Dependency Injection sistemiyle, *path operation function*'ınızın, ondan önce çalıştırılması gereken başka bir şeye de "bağlı" olduğunu **FastAPI**'ye söyleyebilirsiniz; **FastAPI** bunu çalıştırır ve sonuçları "inject" eder. + +Aynı "dependency injection" fikri için kullanılan diğer yaygın terimler: + +* resources +* providers +* services +* injectables +* components + +## **FastAPI** Plug-in'leri { #fastapi-plug-ins } + +Entegrasyonlar ve "plug-in"ler **Dependency Injection** sistemi kullanılarak inşa edilebilir. Ancak aslında **"plug-in" oluşturmanıza gerek yoktur**; çünkü dependency'leri kullanarak *path operation function*'larınıza sunulabilecek sınırsız sayıda entegrasyon ve etkileşim tanımlayabilirsiniz. + +Dependency'ler, çok basit ve sezgisel bir şekilde oluşturulabilir. Böylece ihtiyacınız olan Python package'larını import edip, API fonksiyonlarınızla birkaç satır kodla *kelimenin tam anlamıyla* entegre edebilirsiniz. + +İlerleyen bölümlerde ilişkisel ve NoSQL veritabanları, güvenlik vb. konularda bunun örneklerini göreceksiniz. + +## **FastAPI** Uyumluluğu { #fastapi-compatibility } + +Dependency injection sisteminin sadeliği, **FastAPI**'yi şunlarla uyumlu hale getirir: + +* tüm ilişkisel veritabanları +* NoSQL veritabanları +* harici paketler +* harici API'ler +* authentication ve authorization sistemleri +* API kullanım izleme (monitoring) sistemleri +* response verisi injection sistemleri +* vb. + +## Basit ve Güçlü { #simple-and-powerful } + +Hiyerarşik dependency injection sistemi tanımlamak ve kullanmak çok basit olsa da, hâlâ oldukça güçlüdür. + +Kendileri de dependency tanımlayabilen dependency'ler tanımlayabilirsiniz. + +Sonuçta hiyerarşik bir dependency ağacı oluşur ve **Dependency Injection** sistemi tüm bu dependency'leri (ve alt dependency'lerini) sizin için çözer ve her adımda sonuçları sağlar ("inject" eder). + +Örneğin, 4 API endpoint'iniz (*path operation*) olduğunu varsayalım: + +* `/items/public/` +* `/items/private/` +* `/users/{user_id}/activate` +* `/items/pro/` + +O zaman her biri için farklı izin gereksinimlerini yalnızca dependency'ler ve alt dependency'lerle ekleyebilirsiniz: + +```mermaid +graph TB + +current_user(["current_user"]) +active_user(["active_user"]) +admin_user(["admin_user"]) +paying_user(["paying_user"]) + +public["/items/public/"] +private["/items/private/"] +activate_user["/users/{user_id}/activate"] +pro_items["/items/pro/"] + +current_user --> active_user +active_user --> admin_user +active_user --> paying_user + +current_user --> public +active_user --> private +admin_user --> activate_user +paying_user --> pro_items +``` + +## **OpenAPI** ile Entegre { #integrated-with-openapi_1 } + +Bu dependency'lerin tümü, gereksinimlerini beyan ederken aynı zamanda *path operation*'larınıza parametreler, doğrulamalar vb. da ekler. + +**FastAPI**, bunların hepsini OpenAPI şemasına eklemekle ilgilenir; böylece interaktif dokümantasyon sistemlerinde gösterilir. diff --git a/docs/tr/docs/tutorial/dependencies/sub-dependencies.md b/docs/tr/docs/tutorial/dependencies/sub-dependencies.md new file mode 100644 index 000000000..184db839b --- /dev/null +++ b/docs/tr/docs/tutorial/dependencies/sub-dependencies.md @@ -0,0 +1,105 @@ +# Alt Bağımlılıklar { #sub-dependencies } + +**Alt bağımlılıkları** olan bağımlılıklar oluşturabilirsiniz. + +İhtiyacınız olduğu kadar **derine** gidebilirler. + +Bunları çözme işini **FastAPI** üstlenir. + +## İlk bağımlılık "dependable" { #first-dependency-dependable } + +Şöyle bir ilk bağımlılık ("dependable") oluşturabilirsiniz: + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *} + +Burada `q` adında opsiyonel bir query parametresi `str` olarak tanımlanır ve sonra doğrudan geri döndürülür. + +Bu oldukça basit (pek faydalı değil), ama alt bağımlılıkların nasıl çalıştığına odaklanmamıza yardımcı olacak. + +## İkinci bağımlılık: "dependable" ve "dependant" { #second-dependency-dependable-and-dependant } + +Ardından, aynı zamanda kendi içinde bir bağımlılık da tanımlayan başka bir bağımlılık fonksiyonu (bir "dependable") oluşturabilirsiniz (yani o da bir "dependant" olur): + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *} + +Tanımlanan parametrelere odaklanalım: + +* Bu fonksiyonun kendisi bir bağımlılık ("dependable") olmasına rağmen, ayrıca başka bir bağımlılık daha tanımlar (başka bir şeye "depends" olur). + * `query_extractor`'a bağlıdır ve ondan dönen değeri `q` parametresine atar. +* Ayrıca `last_query` adında opsiyonel bir cookie'yi `str` olarak tanımlar. + * Kullanıcı herhangi bir query `q` sağlamadıysa, daha önce cookie'ye kaydettiğimiz en son kullanılan query'yi kullanırız. + +## Bağımlılığı Kullanma { #use-the-dependency } + +Sonra bu bağımlılığı şöyle kullanabiliriz: + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[23] *} + +/// info | Bilgi + +Dikkat edin, *path operation function* içinde yalnızca tek bir bağımlılık tanımlıyoruz: `query_or_cookie_extractor`. + +Ancak **FastAPI**, `query_or_cookie_extractor`'ı çağırmadan önce `query_extractor`'ı önce çözmesi gerektiğini bilir ve onun sonucunu `query_or_cookie_extractor`'a aktarır. + +/// + +```mermaid +graph TB + +query_extractor(["query_extractor"]) +query_or_cookie_extractor(["query_or_cookie_extractor"]) + +read_query["/items/"] + +query_extractor --> query_or_cookie_extractor --> read_query +``` + +## Aynı Bağımlılığı Birden Fazla Kez Kullanma { #using-the-same-dependency-multiple-times } + +Bağımlılıklarınızdan biri aynı *path operation* için birden fazla kez tanımlanırsa (örneğin birden fazla bağımlılığın ortak bir alt bağımlılığı varsa), **FastAPI** o alt bağımlılığı request başına yalnızca bir kez çağıracağını bilir. + +Dönen değeri bir "cache" içinde saklar ve aynı request içinde buna ihtiyaç duyan tüm "dependant"lara aktarır; böylece aynı request için bağımlılığı tekrar tekrar çağırmaz. + +Daha ileri bir senaryoda, "cached" değeri kullanmak yerine aynı request içinde her adımda (muhtemelen birden fazla kez) bağımlılığın çağrılması gerektiğini biliyorsanız, `Depends` kullanırken `use_cache=False` parametresini ayarlayabilirsiniz: + +//// tab | Python 3.9+ + +```Python hl_lines="1" +async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): + return {"fresh_value": fresh_value} +``` + +//// + +//// tab | Python 3.9+ Annotated olmayan + +/// tip | İpucu + +Mümkünse `Annotated` sürümünü tercih edin. + +/// + +```Python hl_lines="1" +async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)): + return {"fresh_value": fresh_value} +``` + +//// + +## Özet { #recap } + +Burada kullanılan havalı terimlerin ötesinde, **Dependency Injection** sistemi aslında oldukça basittir. + +*Path operation function*'lara benzeyen fonksiyonlardan ibarettir. + +Yine de çok güçlüdür ve keyfi derecede derin iç içe geçmiş bağımlılık "graph"ları (ağaçları) tanımlamanıza izin verir. + +/// tip | İpucu + +Bu basit örneklerle çok faydalı görünmeyebilir. + +Ancak **security** ile ilgili bölümlerde bunun ne kadar işe yaradığını göreceksiniz. + +Ayrıca size ne kadar kod kazandırdığını da göreceksiniz. + +/// diff --git a/docs/tr/docs/tutorial/encoder.md b/docs/tr/docs/tutorial/encoder.md new file mode 100644 index 000000000..e4790a032 --- /dev/null +++ b/docs/tr/docs/tutorial/encoder.md @@ -0,0 +1,35 @@ +# JSON Uyumlu Encoder { #json-compatible-encoder } + +Bazı durumlarda, bir veri tipini (örneğin bir Pydantic model) JSON ile uyumlu bir şeye (örneğin `dict`, `list` vb.) dönüştürmeniz gerekebilir. + +Örneğin, bunu bir veritabanında saklamanız gerekiyorsa. + +Bunun için **FastAPI**, `jsonable_encoder()` fonksiyonunu sağlar. + +## `jsonable_encoder` Kullanımı { #using-the-jsonable-encoder } + +Yalnızca JSON ile uyumlu veri kabul eden bir veritabanınız olduğunu düşünelim: `fake_db`. + +Örneğin bu veritabanı, JSON ile uyumlu olmadıkları için `datetime` objelerini kabul etmez. + +Dolayısıyla bir `datetime` objesinin, ISO formatında veriyi içeren bir `str`'e dönüştürülmesi gerekir. + +Aynı şekilde bu veritabanı bir Pydantic model'i (attribute'lara sahip bir obje) de kabul etmez; yalnızca bir `dict` kabul eder. + +Bunun için `jsonable_encoder` kullanabilirsiniz. + +Bir Pydantic model gibi bir obje alır ve JSON ile uyumlu bir versiyonunu döndürür: + +{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *} + +Bu örnekte, Pydantic model'i bir `dict`'e, `datetime`'ı da bir `str`'e dönüştürür. + +Bu fonksiyonun çağrılmasıyla elde edilen sonuç, Python standardındaki `json.dumps()` ile encode edilebilecek bir şeydir. + +JSON formatında (string olarak) veriyi içeren büyük bir `str` döndürmez. Bunun yerine, tüm değerleri ve alt değerleri JSON ile uyumlu olacak şekilde, Python’un standart bir veri yapısını (örneğin bir `dict`) döndürür. + +/// note | Not + +`jsonable_encoder`, aslında **FastAPI** tarafından veriyi dönüştürmek için internal olarak kullanılır. Ancak birçok farklı senaryoda da oldukça faydalıdır. + +/// diff --git a/docs/tr/docs/tutorial/extra-data-types.md b/docs/tr/docs/tutorial/extra-data-types.md new file mode 100644 index 000000000..464d3a82a --- /dev/null +++ b/docs/tr/docs/tutorial/extra-data-types.md @@ -0,0 +1,62 @@ +# Ek Veri Tipleri { #extra-data-types } + +Şimdiye kadar şunlar gibi yaygın veri tiplerini kullanıyordunuz: + +* `int` +* `float` +* `str` +* `bool` + +Ancak daha karmaşık veri tiplerini de kullanabilirsiniz. + +Ve yine, şimdiye kadar gördüğünüz özelliklerin aynısına sahip olursunuz: + +* Harika editör desteği. +* Gelen request'lerden veri dönüştürme. +* response verileri için veri dönüştürme. +* Veri doğrulama. +* Otomatik annotation ve dokümantasyon. + +## Diğer veri tipleri { #other-data-types } + +Kullanabileceğiniz ek veri tiplerinden bazıları şunlardır: + +* `UUID`: + * Birçok veritabanı ve sistemde ID olarak yaygın kullanılan, standart bir "Universally Unique Identifier". + * request ve response'larda `str` olarak temsil edilir. +* `datetime.datetime`: + * Python `datetime.datetime`. + * request ve response'larda ISO 8601 formatında bir `str` olarak temsil edilir, örn: `2008-09-15T15:53:00+05:00`. +* `datetime.date`: + * Python `datetime.date`. + * request ve response'larda ISO 8601 formatında bir `str` olarak temsil edilir, örn: `2008-09-15`. +* `datetime.time`: + * Python `datetime.time`. + * request ve response'larda ISO 8601 formatında bir `str` olarak temsil edilir, örn: `14:23:55.003`. +* `datetime.timedelta`: + * Python `datetime.timedelta`. + * request ve response'larda toplam saniye sayısını ifade eden bir `float` olarak temsil edilir. + * Pydantic, bunu ayrıca bir "ISO 8601 time diff encoding" olarak temsil etmeye de izin verir, daha fazla bilgi için dokümanlara bakın. +* `frozenset`: + * request ve response'larda, `set` ile aynı şekilde ele alınır: + * request'lerde bir list okunur, tekrarlar kaldırılır ve `set`'e dönüştürülür. + * response'larda `set`, `list`'e dönüştürülür. + * Üretilen schema, `set` değerlerinin benzersiz olduğunu belirtir (JSON Schema'nın `uniqueItems` özelliğini kullanarak). +* `bytes`: + * Standart Python `bytes`. + * request ve response'larda `str` gibi ele alınır. + * Üretilen schema, bunun `binary` "format"ına sahip bir `str` olduğunu belirtir. +* `Decimal`: + * Standart Python `Decimal`. + * request ve response'larda `float` ile aynı şekilde işlenir. +* Geçerli tüm Pydantic veri tiplerini burada görebilirsiniz: Pydantic data types. + +## Örnek { #example } + +Yukarıdaki tiplerden bazılarını kullanan parametrelere sahip bir örnek *path operation* şöyle: + +{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *} + +Fonksiyonun içindeki parametrelerin doğal veri tiplerinde olduğunu unutmayın; örneğin normal tarih işlemleri yapabilirsiniz: + +{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *} diff --git a/docs/tr/docs/tutorial/extra-models.md b/docs/tr/docs/tutorial/extra-models.md new file mode 100644 index 000000000..9aae28e05 --- /dev/null +++ b/docs/tr/docs/tutorial/extra-models.md @@ -0,0 +1,211 @@ +# Ek Modeller { #extra-models } + +Önceki örnekten devam edersek, birbiriyle ilişkili birden fazla modelin olması oldukça yaygındır. + +Bu durum özellikle kullanıcı modellerinde sık görülür, çünkü: + +* **input modeli** bir `password` içerebilmelidir. +* **output modeli** `password` içermemelidir. +* **database modeli** büyük ihtimalle hash'lenmiş bir `password` tutmalıdır. + +/// danger + +Kullanıcının düz metin (plaintext) `password`'ünü asla saklamayın. Her zaman sonradan doğrulayabileceğiniz "güvenli bir hash" saklayın. + +Eğer bilmiyorsanız, "password hash" nedir konusunu [güvenlik bölümlerinde](security/simple-oauth2.md#password-hashing){.internal-link target=_blank} öğreneceksiniz. + +/// + +## Birden Çok Model { #multiple-models } + +`password` alanlarıyla birlikte modellerin genel olarak nasıl görünebileceğine ve nerelerde kullanılacaklarına dair bir fikir: + +{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} + +### `**user_in.model_dump()` Hakkında { #about-user-in-model-dump } + +#### Pydantic'in `.model_dump()` Metodu { #pydantics-model-dump } + +`user_in`, `UserIn` sınıfına ait bir Pydantic modelidir. + +Pydantic modellerinde, model verilerini içeren bir `dict` döndüren `.model_dump()` metodu bulunur. + +Yani, şöyle bir Pydantic nesnesi `user_in` oluşturursak: + +```Python +user_in = UserIn(username="john", password="secret", email="john.doe@example.com") +``` + +ve sonra şunu çağırırsak: + +```Python +user_dict = user_in.model_dump() +``` + +artık `user_dict` değişkeninde modelin verilerini içeren bir `dict` vardır (Pydantic model nesnesi yerine bir `dict` elde etmiş oluruz). + +Ve eğer şunu çağırırsak: + +```Python +print(user_dict) +``` + +şöyle bir Python `dict` elde ederiz: + +```Python +{ + 'username': 'john', + 'password': 'secret', + 'email': 'john.doe@example.com', + 'full_name': None, +} +``` + +#### Bir `dict`'i Unpack Etmek { #unpacking-a-dict } + +`user_dict` gibi bir `dict` alıp bunu bir fonksiyona (ya da sınıfa) `**user_dict` ile gönderirsek, Python bunu "unpack" eder. Yani `user_dict` içindeki key ve value'ları doğrudan key-value argümanları olarak geçirir. + +Dolayısıyla, yukarıdaki `user_dict` ile devam edersek, şunu yazmak: + +```Python +UserInDB(**user_dict) +``` + +şuna eşdeğer bir sonuç üretir: + +```Python +UserInDB( + username="john", + password="secret", + email="john.doe@example.com", + full_name=None, +) +``` + +Ya da daha net şekilde, `user_dict`'i doğrudan kullanarak, gelecekte içeriği ne olursa olsun: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], +) +``` + +#### Bir Pydantic Modelinden Diğerinin İçeriğiyle Pydantic Model Oluşturmak { #a-pydantic-model-from-the-contents-of-another } + +Yukarıdaki örnekte `user_dict`'i `user_in.model_dump()` ile elde ettiğimiz için, şu kod: + +```Python +user_dict = user_in.model_dump() +UserInDB(**user_dict) +``` + +şuna eşdeğerdir: + +```Python +UserInDB(**user_in.model_dump()) +``` + +...çünkü `user_in.model_dump()` bir `dict` döndürür ve biz de bunu `UserInDB`'ye `**` önekiyle vererek Python'ın "unpack" etmesini sağlarız. + +Böylece, bir Pydantic modelindeki verilerden başka bir Pydantic model üretmiş oluruz. + +#### Bir `dict`'i Unpack Etmek ve Ek Keyword'ler { #unpacking-a-dict-and-extra-keywords } + +Sonrasında, aşağıdaki gibi ek keyword argümanı `hashed_password=hashed_password` eklemek: + +```Python +UserInDB(**user_in.model_dump(), hashed_password=hashed_password) +``` + +...şuna benzer bir sonuca dönüşür: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], + hashed_password = hashed_password, +) +``` + +/// warning + +Ek destek fonksiyonları olan `fake_password_hasher` ve `fake_save_user` sadece verinin olası bir akışını göstermek içindir; elbette gerçek bir güvenlik sağlamazlar. + +/// + +## Tekrarı Azaltma { #reduce-duplication } + +Kod tekrarını azaltmak, **FastAPI**'nin temel fikirlerinden biridir. + +Kod tekrarı; bug, güvenlik problemi, kodun senkron dışına çıkması (bir yeri güncelleyip diğerlerini güncellememek) gibi sorunların olasılığını artırır. + +Bu modellerin hepsi verinin büyük bir kısmını paylaşıyor ve attribute adlarını ve type'larını tekrar ediyor. + +Daha iyisini yapabiliriz. + +Diğer modellerimiz için temel olacak bir `UserBase` modeli tanımlayabiliriz. Sonra da bu modelden türeyen (subclass) modeller oluşturup onun attribute'larını (type deklarasyonları, doğrulama vb.) miras aldırabiliriz. + +Tüm veri dönüştürme, doğrulama, dokümantasyon vb. her zamanki gibi çalışmaya devam eder. + +Bu sayede modeller arasındaki farkları (plaintext `password` olan, `hashed_password` olan ve `password` olmayan) sadece o farklılıklar olarak tanımlayabiliriz: + +{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *} + +## `Union` veya `anyOf` { #union-or-anyof } + +Bir response'u iki ya da daha fazla type'ın `Union`'ı olarak tanımlayabilirsiniz; bu, response'un bunlardan herhangi biri olabileceği anlamına gelir. + +OpenAPI'de bu `anyOf` ile tanımlanır. + +Bunu yapmak için standart Python type hint'i olan `typing.Union`'ı kullanın: + +/// note + +Bir `Union` tanımlarken en spesifik type'ı önce, daha az spesifik olanı sonra ekleyin. Aşağıdaki örnekte daha spesifik olan `PlaneItem`, `Union[PlaneItem, CarItem]` içinde `CarItem`'dan önce gelir. + +/// + +{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *} + +### Python 3.10'da `Union` { #union-in-python-3-10 } + +Bu örnekte `Union[PlaneItem, CarItem]` değerini `response_model` argümanına veriyoruz. + +Bunu bir **type annotation** içine koymak yerine bir **argümana değer** olarak geçtiğimiz için, Python 3.10'da bile `Union` kullanmamız gerekiyor. + +Eğer bu bir type annotation içinde olsaydı, dikey çizgiyi kullanabilirdik: + +```Python +some_variable: PlaneItem | CarItem +``` + +Ancak bunu `response_model=PlaneItem | CarItem` atamasına koyarsak hata alırız; çünkü Python bunu bir type annotation olarak yorumlamak yerine `PlaneItem` ile `CarItem` arasında **geçersiz bir işlem** yapmaya çalışır. + +## Model Listesi { #list-of-models } + +Aynı şekilde, nesne listesi döndüren response'ları da tanımlayabilirsiniz. + +Bunun için standart Python `typing.List`'i (ya da Python 3.9 ve üzeri için sadece `list`) kullanın: + +{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} + +## Rastgele `dict` ile Response { #response-with-arbitrary-dict } + +Bir Pydantic modeli kullanmadan, sadece key ve value type'larını belirterek düz, rastgele bir `dict` ile de response tanımlayabilirsiniz. + +Bu, geçerli field/attribute adlarını (Pydantic modeli için gerekli olurdu) önceden bilmiyorsanız kullanışlıdır. + +Bu durumda `typing.Dict` (ya da Python 3.9 ve üzeri için sadece `dict`) kullanabilirsiniz: + +{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} + +## Özet { #recap } + +Her duruma göre birden fazla Pydantic modeli kullanın ve gerekirse özgürce inheritance uygulayın. + +Bir entity'nin farklı "state"lere sahip olması gerekiyorsa, o entity için tek bir veri modeli kullanmak zorunda değilsiniz. Örneğin `password` içeren, `password_hash` içeren ve `password` içermeyen state'lere sahip kullanıcı "entity"si gibi. diff --git a/docs/tr/docs/tutorial/handling-errors.md b/docs/tr/docs/tutorial/handling-errors.md new file mode 100644 index 000000000..a4d693792 --- /dev/null +++ b/docs/tr/docs/tutorial/handling-errors.md @@ -0,0 +1,244 @@ +# Hataları Yönetme { #handling-errors } + +API’nizi kullanan bir client’a hata bildirmek zorunda olduğunuz pek çok durum vardır. + +Bu client; frontend’i olan bir tarayıcı, başka birinin yazdığı bir kod, bir IoT cihazı vb. olabilir. + +Client’a şunları söylemeniz gerekebilir: + +* Client’ın bu işlem için yeterli yetkisi yok. +* Client’ın bu kaynağa erişimi yok. +* Client’ın erişmeye çalıştığı öğe mevcut değil. +* vb. + +Bu durumlarda genellikle **400** aralığında (**400** ile **499** arası) bir **HTTP status code** döndürürsünüz. + +Bu, 200 HTTP status code’larına (200 ile 299 arası) benzer. Bu "200" status code’ları, request’in bir şekilde "başarılı" olduğunu ifade eder. + +400 aralığındaki status code’lar ise hatanın client tarafından kaynaklandığını gösterir. + +Şu meşhur **"404 Not Found"** hatalarını (ve şakalarını) hatırlıyor musunuz? + +## `HTTPException` Kullanma { #use-httpexception } + +Client’a hata içeren HTTP response’ları döndürmek için `HTTPException` kullanırsınız. + +### `HTTPException`’ı Import Etme { #import-httpexception } + +{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *} + +### Kodunuzda Bir `HTTPException` Raise Etme { #raise-an-httpexception-in-your-code } + +`HTTPException`, API’lerle ilgili ek veriler içeren normal bir Python exception’ıdır. + +Python exception’ı olduğu için `return` etmezsiniz, `raise` edersiniz. + +Bu aynı zamanda şunu da ifade eder: *path operation function*’ınızın içinde çağırdığınız bir yardımcı (utility) fonksiyonun içindeyken `HTTPException` raise ederseniz, *path operation function* içindeki kodun geri kalanı çalışmaz; request’i hemen sonlandırır ve `HTTPException` içindeki HTTP hatasını client’a gönderir. + +Bir değer döndürmek yerine exception raise etmenin faydası, Dependencies ve Security bölümünde daha da netleşecektir. + +Bu örnekte, client var olmayan bir ID ile bir item istediğinde, `404` status code’u ile bir exception raise edelim: + +{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *} + +### Ortaya Çıkan Response { #the-resulting-response } + +Client `http://example.com/items/foo` (bir `item_id` `"foo"`) isterse, HTTP status code olarak 200 ve şu JSON response’u alır: + +```JSON +{ + "item": "The Foo Wrestlers" +} +``` + +Ancak client `http://example.com/items/bar` (mevcut olmayan bir `item_id` `"bar"`) isterse, HTTP status code olarak 404 ("not found" hatası) ve şu JSON response’u alır: + +```JSON +{ + "detail": "Item not found" +} +``` + +/// tip | İpucu + +Bir `HTTPException` raise ederken, `detail` parametresine sadece `str` değil, JSON’a dönüştürülebilen herhangi bir değer geçebilirsiniz. + +Örneğin `dict`, `list` vb. geçebilirsiniz. + +Bunlar **FastAPI** tarafından otomatik olarak işlenir ve JSON’a dönüştürülür. + +/// + +## Özel Header’lar Eklemek { #add-custom-headers } + +Bazı durumlarda HTTP hata response’una özel header’lar eklemek faydalıdır. Örneğin bazı güvenlik türlerinde. + +Muhtemelen bunu doğrudan kendi kodunuzda kullanmanız gerekmeyecek. + +Ama ileri seviye bir senaryo için ihtiyaç duyarsanız, özel header’lar ekleyebilirsiniz: + +{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *} + +## Özel Exception Handler’ları Kurmak { #install-custom-exception-handlers } + +Starlette’in aynı exception yardımcı araçlarıyla özel exception handler’lar ekleyebilirsiniz. + +Diyelim ki sizin (ya da kullandığınız bir kütüphanenin) `raise` edebileceği `UnicornException` adında özel bir exception’ınız var. + +Ve bu exception’ı FastAPI ile global olarak handle etmek istiyorsunuz. + +`@app.exception_handler()` ile özel bir exception handler ekleyebilirsiniz: + +{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *} + +Burada `/unicorns/yolo` için request atarsanız, *path operation* bir `UnicornException` `raise` eder. + +Ancak bu, `unicorn_exception_handler` tarafından handle edilir. + +Böylece HTTP status code’u `418` olan, JSON içeriği şu şekilde temiz bir hata response’u alırsınız: + +```JSON +{"message": "Oops! yolo did something. There goes a rainbow..."} +``` + +/// note | Teknik Detaylar + +`from starlette.requests import Request` ve `from starlette.responses import JSONResponse` da kullanabilirsiniz. + +**FastAPI**, geliştirici olarak size kolaylık olsun diye `starlette.responses` içeriğini `fastapi.responses` olarak da sunar. Ancak mevcut response’ların çoğu doğrudan Starlette’ten gelir. `Request` için de aynısı geçerlidir. + +/// + +## Varsayılan Exception Handler’ları Override Etmek { #override-the-default-exception-handlers } + +**FastAPI** bazı varsayılan exception handler’lara sahiptir. + +Bu handler’lar, `HTTPException` `raise` ettiğinizde ve request geçersiz veri içerdiğinde varsayılan JSON response’ları döndürmekten sorumludur. + +Bu exception handler’ları kendi handler’larınızla override edebilirsiniz. + +### Request Validation Exception’larını Override Etmek { #override-request-validation-exceptions } + +Bir request geçersiz veri içerdiğinde, **FastAPI** içeride `RequestValidationError` raise eder. + +Ve bunun için varsayılan bir exception handler da içerir. + +Override etmek için `RequestValidationError`’ı import edin ve exception handler’ı `@app.exception_handler(RequestValidationError)` ile decorate edin. + +Exception handler, bir `Request` ve exception’ı alır. + +{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *} + +Artık `/items/foo`’ya giderseniz, şu varsayılan JSON hatası yerine: + +```JSON +{ + "detail": [ + { + "loc": [ + "path", + "item_id" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ] +} +``` + +şu şekilde bir metin (text) versiyonu alırsınız: + +``` +Validation errors: +Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer +``` + +### `HTTPException` Hata Handler’ını Override Etmek { #override-the-httpexception-error-handler } + +Benzer şekilde `HTTPException` handler’ını da override edebilirsiniz. + +Örneğin bu hatalar için JSON yerine plain text response döndürmek isteyebilirsiniz: + +{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *} + +/// note | Teknik Detaylar + +`from starlette.responses import PlainTextResponse` da kullanabilirsiniz. + +**FastAPI**, geliştirici olarak size kolaylık olsun diye `starlette.responses` içeriğini `fastapi.responses` olarak da sunar. Ancak mevcut response’ların çoğu doğrudan Starlette’ten gelir. + +/// + +/// warning | Uyarı + +`RequestValidationError`, validation hatasının gerçekleştiği dosya adı ve satır bilgilerini içerir; isterseniz bunu log’larınıza ilgili bilgilerle birlikte yazdırabilirsiniz. + +Ancak bu, eğer sadece string’e çevirip bu bilgiyi doğrudan response olarak döndürürseniz sisteminiz hakkında bir miktar bilgi sızdırabileceğiniz anlamına gelir. Bu yüzden burada kod, her bir hatayı ayrı ayrı çıkarıp gösterir. + +/// + +### `RequestValidationError` Body’sini Kullanmak { #use-the-requestvalidationerror-body } + +`RequestValidationError`, geçersiz veriyle aldığı `body`’yi içerir. + +Uygulamanızı geliştirirken body’yi log’lamak, debug etmek, kullanıcıya döndürmek vb. için bunu kullanabilirsiniz. + +{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *} + +Şimdi şu gibi geçersiz bir item göndermeyi deneyin: + +```JSON +{ + "title": "towel", + "size": "XL" +} +``` + +Aldığınız body’yi de içeren, verinin geçersiz olduğunu söyleyen bir response alırsınız: + +```JSON hl_lines="12-15" +{ + "detail": [ + { + "loc": [ + "body", + "size" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ], + "body": { + "title": "towel", + "size": "XL" + } +} +``` + +#### FastAPI’nin `HTTPException`’ı vs Starlette’in `HTTPException`’ı { #fastapis-httpexception-vs-starlettes-httpexception } + +**FastAPI**’nin kendi `HTTPException`’ı vardır. + +Ve **FastAPI**’nin `HTTPException` hata sınıfı, Starlette’in `HTTPException` hata sınıfından kalıtım alır (inherit). + +Tek fark şudur: **FastAPI**’nin `HTTPException`’ı `detail` alanı için JSON’a çevrilebilir herhangi bir veri kabul ederken, Starlette’in `HTTPException`’ı burada sadece string kabul eder. + +Bu yüzden kodunuzda her zamanki gibi **FastAPI**’nin `HTTPException`’ını raise etmeye devam edebilirsiniz. + +Ancak bir exception handler register ederken, bunu Starlette’in `HTTPException`’ı için register etmelisiniz. + +Böylece Starlette’in internal kodunun herhangi bir bölümü ya da bir Starlette extension/plug-in’i Starlette `HTTPException` raise ederse, handler’ınız bunu yakalayıp (catch) handle edebilir. + +Bu örnekte, iki `HTTPException`’ı da aynı kodda kullanabilmek için Starlette’in exception’ı `StarletteHTTPException` olarak yeniden adlandırılıyor: + +```Python +from starlette.exceptions import HTTPException as StarletteHTTPException +``` + +### **FastAPI**’nin Exception Handler’larını Yeniden Kullanmak { #reuse-fastapis-exception-handlers } + +Exception’ı, **FastAPI**’nin aynı varsayılan exception handler’larıyla birlikte kullanmak isterseniz, varsayılan exception handler’ları `fastapi.exception_handlers` içinden import edip yeniden kullanabilirsiniz: + +{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *} + +Bu örnekte sadece oldukça açıklayıcı bir mesajla hatayı yazdırıyorsunuz; ama fikir anlaşılıyor. Exception’ı kullanıp ardından varsayılan exception handler’ları olduğu gibi yeniden kullanabilirsiniz. diff --git a/docs/tr/docs/tutorial/header-param-models.md b/docs/tr/docs/tutorial/header-param-models.md new file mode 100644 index 000000000..9170515dc --- /dev/null +++ b/docs/tr/docs/tutorial/header-param-models.md @@ -0,0 +1,72 @@ +# Header Parametre Modelleri { #header-parameter-models } + +Birbiriyle ilişkili **header parametreleri**nden oluşan bir grubunuz varsa, bunları tanımlamak için bir **Pydantic model** oluşturabilirsiniz. + +Bu sayede modeli **birden fazla yerde yeniden kullanabilir**, ayrıca tüm parametreler için doğrulamaları ve metadata bilgilerini tek seferde tanımlayabilirsiniz. 😎 + +/// note | Not + +Bu özellik FastAPI `0.115.0` sürümünden beri desteklenmektedir. 🤓 + +/// + +## Pydantic Model ile Header Parametreleri { #header-parameters-with-a-pydantic-model } + +İhtiyacınız olan **header parametreleri**ni bir **Pydantic model** içinde tanımlayın, ardından parametreyi `Header` olarak belirtin: + +{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *} + +**FastAPI**, request içindeki **headers** bölümünden **her alan** için veriyi **çıkarır** ve size tanımladığınız Pydantic model örneğini verir. + +## Dokümanları Kontrol Edin { #check-the-docs } + +Gerekli header'ları `/docs` altındaki doküman arayüzünde görebilirsiniz: + +
+ +
+ +## Ek Header'ları Yasaklayın { #forbid-extra-headers } + +Bazı özel kullanım senaryolarında (muhtemelen çok yaygın değil), kabul etmek istediğiniz header'ları **kısıtlamak** isteyebilirsiniz. + +Pydantic'in model yapılandırmasını kullanarak `extra` alanları `forbid` edebilirsiniz: + +{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *} + +Bir client bazı **ek header'lar** göndermeye çalışırsa, **hata** response'u alır. + +Örneğin client, değeri `plumbus` olan bir `tool` header'ı göndermeye çalışırsa, `tool` header parametresine izin verilmediğini söyleyen bir **hata** response'u alır: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["header", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ] +} +``` + +## Alt Çizgileri Dönüştürmeyi Kapatın { #disable-convert-underscores } + +Normal header parametrelerinde olduğu gibi, parametre adlarında alt çizgi karakterleri olduğunda bunlar **otomatik olarak tireye dönüştürülür**. + +Örneğin kodda `save_data` adlı bir header parametreniz varsa, beklenen HTTP header `save-data` olur ve dokümanlarda da bu şekilde görünür. + +Herhangi bir sebeple bu otomatik dönüşümü kapatmanız gerekiyorsa, header parametreleri için kullandığınız Pydantic model'lerde de bunu devre dışı bırakabilirsiniz. + +{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *} + +/// warning | Uyarı + +`convert_underscores` değerini `False` yapmadan önce, bazı HTTP proxy'lerinin ve server'ların alt çizgi içeren header'ların kullanımına izin vermediğini unutmayın. + +/// + +## Özet { #summary } + +**FastAPI**'de **headers** tanımlamak için **Pydantic model** kullanabilirsiniz. 😎 diff --git a/docs/tr/docs/tutorial/header-params.md b/docs/tr/docs/tutorial/header-params.md new file mode 100644 index 000000000..ca787f534 --- /dev/null +++ b/docs/tr/docs/tutorial/header-params.md @@ -0,0 +1,91 @@ +# Header Parametreleri { #header-parameters } + +`Query`, `Path` ve `Cookie` parametrelerini nasıl tanımlıyorsanız, Header parametrelerini de aynı şekilde tanımlayabilirsiniz. + +## `Header`'ı Import Edin { #import-header } + +Önce `Header`'ı import edin: + +{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *} + +## `Header` Parametrelerini Tanımlayın { #declare-header-parameters } + +Ardından header parametrelerini, `Path`, `Query` ve `Cookie` ile kullandığınız yapının aynısıyla tanımlayın. + +Default değeri ve ek validation ya da annotation parametrelerinin tamamını belirleyebilirsiniz: + +{* ../../docs_src/header_params/tutorial001_an_py310.py hl[9] *} + +/// note | Teknik Detaylar + +`Header`, `Path`, `Query` ve `Cookie`'nin "kardeş" sınıfıdır. Ayrıca aynı ortak `Param` sınıfından kalıtım alır. + +Ancak şunu unutmayın: `fastapi`'den `Query`, `Path`, `Header` ve diğerlerini import ettiğinizde, bunlar aslında özel sınıfları döndüren fonksiyonlardır. + +/// + +/// info | Bilgi + +Header'ları tanımlamak için `Header` kullanmanız gerekir; aksi halde parametreler query parametreleri olarak yorumlanır. + +/// + +## Otomatik Dönüştürme { #automatic-conversion } + +`Header`, `Path`, `Query` ve `Cookie`'nin sağladıklarına ek olarak küçük bir ekstra işlevsellik sunar. + +Standart header'ların çoğu, "hyphen" karakteri (diğer adıyla "minus symbol" (`-`)) ile ayrılır. + +Ancak `user-agent` gibi bir değişken adı Python'da geçersizdir. + +Bu yüzden, default olarak `Header`, header'ları almak ve dokümante etmek için parametre adlarındaki underscore (`_`) karakterlerini hyphen (`-`) ile dönüştürür. + +Ayrıca HTTP header'ları büyük/küçük harfe duyarlı değildir; dolayısıyla onları standart Python stiliyle (diğer adıyla "snake_case") tanımlayabilirsiniz. + +Yani `User_Agent` gibi bir şey yazıp ilk harfleri büyütmeniz gerekmeden, Python kodunda normalde kullandığınız gibi `user_agent` kullanabilirsiniz. + +Herhangi bir nedenle underscore'ların hyphen'lara otomatik dönüştürülmesini kapatmanız gerekirse, `Header`'ın `convert_underscores` parametresini `False` yapın: + +{* ../../docs_src/header_params/tutorial002_an_py310.py hl[10] *} + +/// warning | Uyarı + +`convert_underscores`'u `False` yapmadan önce, bazı HTTP proxy'lerinin ve server'ların underscore içeren header'ların kullanımına izin vermediğini unutmayın. + +/// + +## Yinelenen Header'lar { #duplicate-headers } + +Yinelenen header'lar almak mümkündür. Yani aynı header'ın birden fazla değeri olabilir. + +Bu tür durumları, type tanımında bir list kullanarak belirtebilirsiniz. + +Yinelenen header'daki tüm değerleri Python `list` olarak alırsınız. + +Örneğin, birden fazla kez gelebilen `X-Token` header'ını tanımlamak için şöyle yazabilirsiniz: + +{* ../../docs_src/header_params/tutorial003_an_py310.py hl[9] *} + +Eğer bu *path operation* ile iki HTTP header göndererek iletişim kurarsanız: + +``` +X-Token: foo +X-Token: bar +``` + +response şöyle olur: + +```JSON +{ + "X-Token values": [ + "bar", + "foo" + ] +} +``` + +## Özet { #recap } + +Header'ları `Header` ile tanımlayın; `Query`, `Path` ve `Cookie` ile kullanılan ortak kalıbı burada da kullanın. + +Değişkenlerinizdeki underscore'lar konusunda endişelenmeyin, **FastAPI** bunları dönüştürmeyi halleder. diff --git a/docs/tr/docs/tutorial/index.md b/docs/tr/docs/tutorial/index.md new file mode 100644 index 000000000..f672c9e20 --- /dev/null +++ b/docs/tr/docs/tutorial/index.md @@ -0,0 +1,95 @@ +# Eğitim - Kullanıcı Rehberi { #tutorial-user-guide } + +Bu eğitim, **FastAPI**'yi özelliklerinin çoğuyla birlikte adım adım nasıl kullanacağınızı gösterir. + +Her bölüm bir öncekilerin üzerine kademeli olarak eklenir, ancak konular birbirinden ayrılacak şekilde yapılandırılmıştır; böylece API ihtiyaçlarınıza göre doğrudan belirli bir konuya gidip aradığınızı bulabilirsiniz. + +Ayrıca, ileride tekrar dönüp tam olarak ihtiyaç duyduğunuz şeyi görebileceğiniz bir referans olarak da tasarlanmıştır. + +## Kodu Çalıştırın { #run-the-code } + +Tüm code block'lar kopyalanıp doğrudan kullanılabilir (zaten test edilmiş Python dosyalarıdır). + +Örneklerden herhangi birini çalıştırmak için, kodu `main.py` adlı bir dosyaya kopyalayın ve şu komutla `fastapi dev`'i başlatın: + +
+ +```console +$ 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. +``` + +
+ +Kodu yazmanız ya da kopyalayıp düzenlemeniz ve yerelinizde çalıştırmanız **şiddetle önerilir**. + +Editörünüzde kullanmak FastAPI'nin avantajlarını gerçekten gösterir: ne kadar az kod yazmanız gerektiğini, type check'leri, autocompletion'ı vb. görürsünüz. + +--- + +## FastAPI'yi Kurun { #install-fastapi } + +İlk adım FastAPI'yi kurmaktır. + +Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan emin olun, etkinleştirin ve ardından **FastAPI'yi kurun**: + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +/// note | Not + +`pip install "fastapi[standard]"` ile kurduğunuzda, bazı varsayılan opsiyonel standard bağımlılıklarla birlikte gelir. Bunlara `fastapi-cloud-cli` da dahildir; bu sayede FastAPI Cloud'a deploy edebilirsiniz. + +Bu opsiyonel bağımlılıkları istemiyorsanız bunun yerine `pip install fastapi` kurabilirsiniz. + +Standard bağımlılıkları kurmak istiyor ama `fastapi-cloud-cli` olmasın diyorsanız, `pip install "fastapi[standard-no-fastapi-cloud-cli]"` ile kurabilirsiniz. + +/// + +## İleri Düzey Kullanıcı Rehberi { #advanced-user-guide } + +Bu **Eğitim - Kullanıcı Rehberi**'ni bitirdikten sonra daha sonra okuyabileceğiniz bir **İleri Düzey Kullanıcı Rehberi** de var. + +**İleri Düzey Kullanıcı Rehberi** bunun üzerine inşa eder, aynı kavramları kullanır ve size bazı ek özellikler öğretir. + +Ancak önce **Eğitim - Kullanıcı Rehberi**'ni (şu anda okuduğunuz bölümü) okumalısınız. + +Yalnızca **Eğitim - Kullanıcı Rehberi** ile eksiksiz bir uygulama oluşturabilmeniz hedeflenmiştir; ardından ihtiyaçlarınıza göre, **İleri Düzey Kullanıcı Rehberi**'ndeki ek fikirlerden bazılarını kullanarak farklı şekillerde genişletebilirsiniz. diff --git a/docs/tr/docs/tutorial/metadata.md b/docs/tr/docs/tutorial/metadata.md new file mode 100644 index 000000000..dacd68cf5 --- /dev/null +++ b/docs/tr/docs/tutorial/metadata.md @@ -0,0 +1,120 @@ +# Metadata ve Doküman URL'leri { #metadata-and-docs-urls } + +**FastAPI** uygulamanızda çeşitli metadata yapılandırmalarını özelleştirebilirsiniz. + +## API için Metadata { #metadata-for-api } + +OpenAPI spesifikasyonunda ve otomatik API doküman arayüzlerinde kullanılan şu alanları ayarlayabilirsiniz: + +| Parametre | Tip | Açıklama | +|------------|------|-------------| +| `title` | `str` | API'nin başlığı. | +| `summary` | `str` | API'nin kısa özeti. OpenAPI 3.1.0, FastAPI 0.99.0 sürümünden itibaren mevcut. | +| `description` | `str` | API'nin kısa açıklaması. Markdown kullanabilir. | +| `version` | `string` | API'nin sürümü. Bu, OpenAPI'nin değil, kendi uygulamanızın sürümüdür. Örneğin `2.5.0`. | +| `terms_of_service` | `str` | API'nin Kullanım Koşulları (Terms of Service) için bir URL. Verilirse, URL formatında olmalıdır. | +| `contact` | `dict` | Yayınlanan API için iletişim bilgileri. Birden fazla alan içerebilir.
contact alanları
ParametreTipAçıklama
namestrİletişim kişisi/kuruluşunu tanımlayan ad.
urlstrİletişim bilgilerine işaret eden URL. URL formatında OLMALIDIR.
emailstrİletişim kişisi/kuruluşunun e-posta adresi. E-posta adresi formatında OLMALIDIR.
| +| `license_info` | `dict` | Yayınlanan API için lisans bilgileri. Birden fazla alan içerebilir.
license_info alanları
ParametreTipAçıklama
namestrZORUNLU (license_info ayarlanmışsa). API için kullanılan lisans adı.
identifierstrAPI için bir SPDX lisans ifadesi. identifier alanı, url alanıyla karşılıklı olarak dışlayıcıdır (ikisi aynı anda kullanılamaz). OpenAPI 3.1.0, FastAPI 0.99.0 sürümünden itibaren mevcut.
urlstrAPI için kullanılan lisansa ait URL. URL formatında OLMALIDIR.
| + +Şu şekilde ayarlayabilirsiniz: + +{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} + +/// tip | İpucu + +`description` alanına Markdown yazabilirsiniz; çıktı tarafında render edilir. + +/// + +Bu yapılandırmayla otomatik API dokümanları şöyle görünür: + + + +## License identifier { #license-identifier } + +OpenAPI 3.1.0 ve FastAPI 0.99.0 sürümünden itibaren, `license_info` içinde `url` yerine bir `identifier` da ayarlayabilirsiniz. + +Örneğin: + +{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} + +## Tag'ler için Metadata { #metadata-for-tags } + +`openapi_tags` parametresiyle, path operation'larınızı gruplamak için kullandığınız farklı tag'ler adına ek metadata da ekleyebilirsiniz. + +Bu parametre, her tag için bir sözlük (dictionary) içeren bir liste alır. + +Her sözlük şunları içerebilir: + +* `name` (**zorunlu**): *path operation*'larda ve `APIRouter`'larda `tags` parametresinde kullandığınız tag adıyla aynı olan bir `str`. +* `description`: tag için kısa bir açıklama içeren `str`. Markdown içerebilir ve doküman arayüzünde gösterilir. +* `externalDocs`: harici dokümanları tanımlayan bir `dict`: + * `description`: harici dokümanlar için kısa açıklama içeren `str`. + * `url` (**zorunlu**): harici dokümantasyonun URL'sini içeren `str`. + +### Tag'ler için metadata oluşturun { #create-metadata-for-tags } + +`users` ve `items` tag'lerini içeren bir örnekle deneyelim. + +Tag'leriniz için metadata oluşturup `openapi_tags` parametresine geçin: + +{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} + +Açıklamaların içinde Markdown kullanabileceğinizi unutmayın; örneğin "login" kalın (**login**) ve "fancy" italik (_fancy_) olarak gösterilecektir. + +/// tip | İpucu + +Kullandığınız tüm tag'ler için metadata eklemek zorunda değilsiniz. + +/// + +### Tag'lerinizi kullanın { #use-your-tags } + +*path operation*'larınızı (ve `APIRouter`'ları) farklı tag'lere atamak için `tags` parametresini kullanın: + +{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} + +/// info | Bilgi + +Tag'ler hakkında daha fazlası için: [Path Operation Configuration](path-operation-configuration.md#tags){.internal-link target=_blank}. + +/// + +### Dokümanları kontrol edin { #check-the-docs } + +Artık dokümanlara baktığınızda, eklediğiniz tüm metadata gösterilir: + + + +### Tag sırası { #order-of-tags } + +Her tag metadata sözlüğünün listedeki sırası, doküman arayüzünde gösterilecek sırayı da belirler. + +Örneğin alfabetik sıralamada `users`, `items`'tan sonra gelirdi; ancak listedeki ilk sözlük olarak `users` metadata'sını eklediğimiz için, dokümanlarda önce o görünür. + +## OpenAPI URL'si { #openapi-url } + +Varsayılan olarak OpenAPI şeması `/openapi.json` adresinden sunulur. + +Ancak bunu `openapi_url` parametresiyle yapılandırabilirsiniz. + +Örneğin `/api/v1/openapi.json` adresinden sunulacak şekilde ayarlamak için: + +{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} + +OpenAPI şemasını tamamen kapatmak isterseniz `openapi_url=None` ayarlayabilirsiniz; bu, onu kullanan dokümantasyon arayüzlerini de devre dışı bırakır. + +## Doküman URL'leri { #docs-urls } + +Dahil gelen iki dokümantasyon arayüzünü yapılandırabilirsiniz: + +* **Swagger UI**: `/docs` adresinden sunulur. + * URL'sini `docs_url` parametresiyle ayarlayabilirsiniz. + * `docs_url=None` ayarlayarak devre dışı bırakabilirsiniz. +* **ReDoc**: `/redoc` adresinden sunulur. + * URL'sini `redoc_url` parametresiyle ayarlayabilirsiniz. + * `redoc_url=None` ayarlayarak devre dışı bırakabilirsiniz. + +Örneğin Swagger UI'yi `/documentation` adresinden sunup ReDoc'u kapatmak için: + +{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} diff --git a/docs/tr/docs/tutorial/middleware.md b/docs/tr/docs/tutorial/middleware.md new file mode 100644 index 000000000..68222265e --- /dev/null +++ b/docs/tr/docs/tutorial/middleware.md @@ -0,0 +1,95 @@ +# Middleware { #middleware } + +**FastAPI** uygulamalarına middleware ekleyebilirsiniz. + +"Middleware", herhangi bir özel *path operation* tarafından işlenmeden önce her **request** ile çalışan bir fonksiyondur. Ayrıca geri döndürmeden önce her **response** ile de çalışır. + +* Uygulamanıza gelen her **request**'i alır. +* Ardından o **request** üzerinde bir işlem yapabilir veya gerekli herhangi bir kodu çalıştırabilir. +* Sonra **request**'i uygulamanın geri kalanı tarafından işlenmesi için iletir (bir *path operation* tarafından). +* Ardından uygulama tarafından üretilen **response**'u alır (bir *path operation* tarafından). +* Sonra o **response** üzerinde bir işlem yapabilir veya gerekli herhangi bir kodu çalıştırabilir. +* Son olarak **response**'u döndürür. + +/// note | Teknik Detaylar + +`yield` ile dependency'leriniz varsa, çıkış (exit) kodu middleware'den *sonra* çalışır. + +Herhangi bir background task varsa ([Background Tasks](background-tasks.md){.internal-link target=_blank} bölümünde ele alınıyor, ileride göreceksiniz), bunlar tüm middleware'ler *tamamlandıktan sonra* çalışır. + +/// + +## Middleware Oluşturma { #create-a-middleware } + +Bir middleware oluşturmak için bir fonksiyonun üzerine `@app.middleware("http")` decorator'ünü kullanırsınız. + +Middleware fonksiyonu şunları alır: + +* `request`. +* Parametre olarak `request` alacak bir `call_next` fonksiyonu. + * Bu fonksiyon `request`'i ilgili *path operation*'a iletir. + * Ardından ilgili *path operation* tarafından üretilen `response`'u döndürür. +* Sonrasında `response`'u döndürmeden önce ayrıca değiştirebilirsiniz. + +{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *} + +/// tip | İpucu + +Özel (proprietary) header'lar `X-` prefix'i kullanılarak eklenebilir, bunu aklınızda tutun. + +Ancak tarayıcıdaki bir client'ın görebilmesini istediğiniz özel header'larınız varsa, bunları CORS konfigürasyonlarınıza ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) eklemeniz gerekir. Bunun için, Starlette'ın CORS dokümanlarında belgelenen `expose_headers` parametresini kullanın. + +/// + +/// note | Teknik Detaylar + +`from starlette.requests import Request` da kullanabilirdiniz. + +**FastAPI** bunu geliştirici olarak size kolaylık olsun diye sunar. Ancak doğrudan Starlette'tan gelir. + +/// + +### `response`'tan Önce ve Sonra { #before-and-after-the-response } + +Herhangi bir *path operation* `request`'i almadan önce, `request` ile birlikte çalışacak kod ekleyebilirsiniz. + +Ayrıca `response` üretildikten sonra, geri döndürmeden önce de kod çalıştırabilirsiniz. + +Örneğin, request'i işleyip response üretmenin kaç saniye sürdüğünü içeren `X-Process-Time` adlı özel bir header ekleyebilirsiniz: + +{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *} + +/// tip | İpucu + +Burada `time.time()` yerine `time.perf_counter()` kullanıyoruz, çünkü bu kullanım senaryolarında daha hassas olabilir. 🤓 + +/// + +## Birden Fazla Middleware Çalıştırma Sırası { #multiple-middleware-execution-order } + +`@app.middleware()` decorator'ü veya `app.add_middleware()` metodu ile birden fazla middleware eklediğinizde, eklenen her yeni middleware uygulamayı sarar ve bir stack oluşturur. En son eklenen middleware en *dıştaki* (outermost), ilk eklenen ise en *içteki* (innermost) olur. + +Request tarafında önce en *dıştaki* middleware çalışır. + +Response tarafında ise en son o çalışır. + +Örneğin: + +```Python +app.add_middleware(MiddlewareA) +app.add_middleware(MiddlewareB) +``` + +Bu, aşağıdaki çalıştırma sırasını oluşturur: + +* **Request**: MiddlewareB → MiddlewareA → route + +* **Response**: route → MiddlewareA → MiddlewareB + +Bu stack davranışı, middleware'lerin öngörülebilir ve kontrol edilebilir bir sırayla çalıştırılmasını sağlar. + +## Diğer Middleware'ler { #other-middlewares } + +Diğer middleware'ler hakkında daha fazlasını daha sonra [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank} bölümünde okuyabilirsiniz. + +Bir sonraki bölümde, middleware ile CORS'un nasıl ele alınacağını göreceksiniz. diff --git a/docs/tr/docs/tutorial/path-operation-configuration.md b/docs/tr/docs/tutorial/path-operation-configuration.md new file mode 100644 index 000000000..610b863a4 --- /dev/null +++ b/docs/tr/docs/tutorial/path-operation-configuration.md @@ -0,0 +1,107 @@ +# Path Operation Yapılandırması { #path-operation-configuration } + +Onu yapılandırmak için *path operation decorator*’ınıza geçebileceğiniz çeşitli parametreler vardır. + +/// warning | Uyarı + +Bu parametrelerin *path operation function*’ınıza değil, doğrudan *path operation decorator*’ına verildiğine dikkat edin. + +/// + +## Response Status Code { #response-status-code } + +*Path operation*’ınızın response’unda kullanılacak (HTTP) `status_code`’u tanımlayabilirsiniz. + +`404` gibi `int` kodu doğrudan verebilirsiniz. + +Ancak her sayısal kodun ne işe yaradığını hatırlamıyorsanız, `status` içindeki kısayol sabitlerini kullanabilirsiniz: + +{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *} + +Bu status code response’da kullanılacak ve OpenAPI şemasına eklenecektir. + +/// note | Teknik Detaylar + +`from starlette import status` da kullanabilirsiniz. + +**FastAPI**, geliştirici olarak işinizi kolaylaştırmak için `starlette.status`’u `fastapi.status` olarak da sunar. Ancak kaynağı doğrudan Starlette’tir. + +/// + +## Tags { #tags } + +*Path operation*’ınıza tag ekleyebilirsiniz; `tags` parametresine `str` öğelerinden oluşan bir `list` verin (genellikle tek bir `str`): + +{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *} + +Bunlar OpenAPI şemasına eklenecek ve otomatik dokümantasyon arayüzleri tarafından kullanılacaktır: + + + +### Enum ile Tags { #tags-with-enums } + +Büyük bir uygulamanız varsa, zamanla **birden fazla tag** birikebilir ve ilişkili *path operation*’lar için her zaman **aynı tag**’i kullandığınızdan emin olmak isteyebilirsiniz. + +Bu durumlarda tag’leri bir `Enum` içinde tutmak mantıklı olabilir. + +**FastAPI** bunu düz string’lerde olduğu gibi aynı şekilde destekler: + +{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *} + +## Özet ve açıklama { #summary-and-description } + +Bir `summary` ve `description` ekleyebilirsiniz: + +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *} + +## Docstring’den Description { #description-from-docstring } + +Açıklamalar genelde uzun olur ve birden fazla satıra yayılır; bu yüzden *path operation* açıklamasını, fonksiyonun içinde docstring olarak tanımlayabilirsiniz; **FastAPI** de onu buradan okur. + +Docstring içinde Markdown yazabilirsiniz; doğru şekilde yorumlanır ve gösterilir (docstring girintisi dikkate alınarak). + +{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *} + +Interactive docs’ta şöyle kullanılacaktır: + + + +## Response description { #response-description } + +`response_description` parametresi ile response açıklamasını belirtebilirsiniz: + +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *} + +/// info | Bilgi + +`response_description` özellikle response’u ifade eder; `description` ise genel olarak *path operation*’ı ifade eder. + +/// + +/// check | Ek bilgi + +OpenAPI, her *path operation* için bir response description zorunlu kılar. + +Bu yüzden siz sağlamazsanız, **FastAPI** otomatik olarak "Successful response" üretir. + +/// + + + +## Bir *path operation*’ı Deprecate Etmek { #deprecate-a-path-operation } + +Bir *path operation*’ı kaldırmadan, deprecated olarak işaretlemeniz gerekiyorsa `deprecated` parametresini verin: + +{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} + +Interactive docs’ta deprecated olduğu net şekilde işaretlenecektir: + + + +Deprecated olan ve olmayan *path operation*’ların nasıl göründüğüne bakın: + + + +## Özet { #recap } + +*Path operation*’larınızı, *path operation decorator*’larına parametre geçirerek kolayca yapılandırabilir ve metadata ekleyebilirsiniz. diff --git a/docs/tr/docs/tutorial/path-params-numeric-validations.md b/docs/tr/docs/tutorial/path-params-numeric-validations.md new file mode 100644 index 000000000..e0118d71d --- /dev/null +++ b/docs/tr/docs/tutorial/path-params-numeric-validations.md @@ -0,0 +1,154 @@ +# Path Parametreleri ve Sayısal Doğrulamalar { #path-parameters-and-numeric-validations } + +`Query` ile query parametreleri için daha fazla doğrulama ve metadata tanımlayabildiğiniz gibi, `Path` ile de path parametreleri için aynı tür doğrulama ve metadata tanımlayabilirsiniz. + +## `Path`'i İçe Aktarın { #import-path } + +Önce `fastapi` içinden `Path`'i ve `Annotated`'ı içe aktarın: + +{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *} + +/// info | Bilgi + +FastAPI, 0.95.0 sürümünde `Annotated` desteğini ekledi (ve bunu önermeye başladı). + +Daha eski bir sürüm kullanıyorsanız, `Annotated` kullanmaya çalıştığınızda hata alırsınız. + +`Annotated` kullanmadan önce mutlaka FastAPI sürümünü en az 0.95.1 olacak şekilde [FastAPI sürümünü yükseltin](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}. + +/// + +## Metadata Tanımlayın { #declare-metadata } + +`Query` için geçerli olan parametrelerin aynısını tanımlayabilirsiniz. + +Örneğin, `item_id` path parametresi için bir `title` metadata değeri tanımlamak isterseniz şunu yazabilirsiniz: + +{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *} + +/// note | Not + +Bir path parametresi her zaman zorunludur, çünkü path'in bir parçası olmak zorundadır. `None` ile tanımlasanız veya bir varsayılan değer verseniz bile bu hiçbir şeyi değiştirmez; yine her zaman zorunlu olur. + +/// + +## Parametreleri İhtiyacınıza Göre Sıralayın { #order-the-parameters-as-you-need } + +/// tip | İpucu + +`Annotated` kullanıyorsanız, bu muhtemelen o kadar önemli ya da gerekli değildir. + +/// + +Diyelim ki query parametresi `q`'yu zorunlu bir `str` olarak tanımlamak istiyorsunuz. + +Ayrıca bu parametre için başka bir şey tanımlamanız gerekmiyor; dolayısıyla `Query` kullanmanıza da aslında gerek yok. + +Ancak `item_id` path parametresi için yine de `Path` kullanmanız gerekiyor. Ve bir sebepten `Annotated` kullanmak istemiyorsunuz. + +Python, "default" değeri olan bir parametreyi, "default" değeri olmayan bir parametreden önce yazarsanız şikayet eder. + +Ama bunların sırasını değiştirebilir ve default değeri olmayan parametreyi (query parametresi `q`) en başa koyabilirsiniz. + +Bu **FastAPI** için önemli değildir. FastAPI parametreleri isimlerine, tiplerine ve default tanımlarına (`Query`, `Path`, vb.) göre tespit eder; sırayla ilgilenmez. + +Dolayısıyla fonksiyonunuzu şöyle tanımlayabilirsiniz: + +{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} + +Ancak şunu unutmayın: `Annotated` kullanırsanız bu problem olmaz; çünkü `Query()` veya `Path()` için fonksiyon parametresi default değerlerini kullanmıyorsunuz. + +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} + +## Parametreleri İhtiyacınıza Göre Sıralayın: Küçük Hileler { #order-the-parameters-as-you-need-tricks } + +/// tip | İpucu + +`Annotated` kullanıyorsanız, bu muhtemelen o kadar önemli ya da gerekli değildir. + +/// + +İşte bazen işe yarayan **küçük bir hile**; ama çok sık ihtiyacınız olmayacak. + +Şunları yapmak istiyorsanız: + +* `q` query parametresini `Query` kullanmadan ve herhangi bir default değer vermeden tanımlamak +* `item_id` path parametresini `Path` kullanarak tanımlamak +* bunları farklı bir sırada yazmak +* `Annotated` kullanmamak + +...Python bunun için küçük, özel bir sözdizimi sunar. + +Fonksiyonun ilk parametresi olarak `*` geçin. + +Python bu `*` ile bir şey yapmaz; ama bundan sonraki tüm parametrelerin keyword argument (anahtar-değer çiftleri) olarak çağrılması gerektiğini bilir; buna kwargs da denir. Default değerleri olmasa bile. + +{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} + +### `Annotated` ile Daha İyi { #better-with-annotated } + +Şunu da unutmayın: `Annotated` kullanırsanız, fonksiyon parametresi default değerlerini kullanmadığınız için bu sorun ortaya çıkmaz ve muhtemelen `*` kullanmanız da gerekmez. + +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} + +## Sayı Doğrulamaları: Büyük Eşit { #number-validations-greater-than-or-equal } + +`Query` ve `Path` (ve ileride göreceğiniz diğerleri) ile sayı kısıtları tanımlayabilirsiniz. + +Burada `ge=1` ile, `item_id` değerinin `1`'den "`g`reater than or `e`qual" olacak şekilde bir tam sayı olması gerekir. + +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} + +## Sayı Doğrulamaları: Büyük ve Küçük Eşit { #number-validations-greater-than-and-less-than-or-equal } + +Aynısı şunlar için de geçerlidir: + +* `gt`: `g`reater `t`han +* `le`: `l`ess than or `e`qual + +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} + +## Sayı Doğrulamaları: `float` Değerler, Büyük ve Küçük { #number-validations-floats-greater-than-and-less-than } + +Sayı doğrulamaları `float` değerler için de çalışır. + +Burada gt tanımlayabilmek (sadece ge değil) önemli hale gelir. Çünkü örneğin bir değerin `0`'dan büyük olmasını isteyebilirsiniz; `1`'den küçük olsa bile. + +Bu durumda `0.5` geçerli bir değer olur. Ancak `0.0` veya `0` geçerli olmaz. + +Aynısı lt için de geçerlidir. + +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} + +## Özet { #recap } + +`Query`, `Path` (ve henüz görmedikleriniz) ile metadata ve string doğrulamalarını, [Query Parametreleri ve String Doğrulamalar](query-params-str-validations.md){.internal-link target=_blank} bölümündekiyle aynı şekilde tanımlayabilirsiniz. + +Ayrıca sayısal doğrulamalar da tanımlayabilirsiniz: + +* `gt`: `g`reater `t`han +* `ge`: `g`reater than or `e`qual +* `lt`: `l`ess `t`han +* `le`: `l`ess than or `e`qual + +/// info | Bilgi + +`Query`, `Path` ve ileride göreceğiniz diğer class'lar ortak bir `Param` class'ının alt class'larıdır. + +Hepsi, gördüğünüz ek doğrulama ve metadata parametrelerini paylaşır. + +/// + +/// note | Teknik Detaylar + +`Query`, `Path` ve diğerlerini `fastapi` içinden import ettiğinizde, bunlar aslında birer fonksiyondur. + +Çağrıldıklarında, aynı isme sahip class'ların instance'larını döndürürler. + +Yani `Query`'yi import edersiniz; bu bir fonksiyondur. Onu çağırdığınızda, yine `Query` adlı bir class'ın instance'ını döndürür. + +Bu fonksiyonlar (class'ları doğrudan kullanmak yerine) editörünüzün type'larıyla ilgili hata işaretlememesi için vardır. + +Bu sayede, bu hataları yok saymak üzere özel ayarlar eklemeden normal editörünüzü ve coding araçlarınızı kullanabilirsiniz. + +/// diff --git a/docs/tr/docs/tutorial/query-param-models.md b/docs/tr/docs/tutorial/query-param-models.md new file mode 100644 index 000000000..75139a677 --- /dev/null +++ b/docs/tr/docs/tutorial/query-param-models.md @@ -0,0 +1,68 @@ +# Query Parameter Modelleri { #query-parameter-models } + +Birbirleriyle ilişkili bir **query parameter** grubunuz varsa, bunları tanımlamak için bir **Pydantic model** oluşturabilirsiniz. + +Böylece **modeli yeniden kullanabilir**, **birden fazla yerde** tekrar tekrar kullanabilir ve tüm parametreler için validation (doğrulama) ile metadata’yı tek seferde tanımlayabilirsiniz. 😎 + +/// note | Not + +Bu özellik FastAPI `0.115.0` sürümünden beri desteklenmektedir. 🤓 + +/// + +## Pydantic Model ile Query Parameters { #query-parameters-with-a-pydantic-model } + +İhtiyacınız olan **query parameter**’ları bir **Pydantic model** içinde tanımlayın, ardından parametreyi `Query` olarak belirtin: + +{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} + +**FastAPI**, request’teki **query parameter**’lardan **her field** için veriyi **extract** eder ve tanımladığınız Pydantic model’i size verir. + +## Dokümanları Kontrol Edin { #check-the-docs } + +Query parameter’ları `/docs` altındaki dokümantasyon arayüzünde görebilirsiniz: + +
+ +
+ +## Ek Query Parameter'ları Yasaklayın { #forbid-extra-query-parameters } + +Bazı özel kullanım senaryolarında (muhtemelen çok yaygın değil), almak istediğiniz query parameter’ları **kısıtlamak** isteyebilirsiniz. + +Pydantic’in model konfigürasyonunu kullanarak `extra` field’ları `forbid` edebilirsiniz: + +{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} + +Bir client, **query parameter**’larda **ek (extra)** veri göndermeye çalışırsa, **error** response alır. + +Örneğin client, değeri `plumbus` olan bir `tool` query parameter’ı göndermeye çalışırsa: + +```http +https://example.com/items/?limit=10&tool=plumbus +``` + +`tool` query parameter’ına izin verilmediğini söyleyen bir **error** response alır: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus" + } + ] +} +``` + +## Özet { #summary } + +**FastAPI** içinde **query parameter**’ları tanımlamak için **Pydantic model**’leri kullanabilirsiniz. 😎 + +/// tip | İpucu + +Spoiler: cookie ve header’ları tanımlamak için de Pydantic model’leri kullanabilirsiniz; ancak bunu tutorial’ın ilerleyen bölümlerinde göreceksiniz. 🤫 + +/// diff --git a/docs/tr/docs/tutorial/query-params-str-validations.md b/docs/tr/docs/tutorial/query-params-str-validations.md new file mode 100644 index 000000000..18f0249e5 --- /dev/null +++ b/docs/tr/docs/tutorial/query-params-str-validations.md @@ -0,0 +1,473 @@ +# Query Parametreleri ve String Doğrulamaları { #query-parameters-and-string-validations } + +**FastAPI**, parametreleriniz için ek bilgi ve doğrulamalar (validation) tanımlamanıza izin verir. + +Örnek olarak şu uygulamayı ele alalım: + +{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} + +Query parametresi `q`, `str | None` tipindedir. Yani tipi `str`’dir ama `None` da olabilir. Nitekim varsayılan değer `None` olduğu için FastAPI bunun zorunlu olmadığını anlar. + +/// note | Not + +FastAPI, `q`’nun zorunlu olmadığını `= None` varsayılan değerinden anlar. + +`str | None` kullanmak, editörünüzün daha iyi destek vermesini ve hataları yakalamasını sağlar. + +/// + +## Ek doğrulama { #additional-validation } + +`q` opsiyonel olsa bile, verildiği durumda **uzunluğunun 50 karakteri geçmemesini** zorlayacağız. + +### `Query` ve `Annotated` import edin { #import-query-and-annotated } + +Bunu yapmak için önce şunları import edin: + +* `fastapi` içinden `Query` +* `typing` içinden `Annotated` + +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *} + +/// info | Bilgi + +FastAPI, 0.95.0 sürümünde `Annotated` desteğini ekledi (ve önermeye başladı). + +Daha eski bir sürüm kullanıyorsanız `Annotated` kullanmaya çalışırken hata alırsınız. + +`Annotated` kullanmadan önce FastAPI sürümünü en az 0.95.1’e yükseltmek için [FastAPI sürümünü yükseltin](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}. + +/// + +## `q` parametresinin tipinde `Annotated` kullanın { #use-annotated-in-the-type-for-the-q-parameter } + +[Python Types Intro](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank} içinde `Annotated` ile parametrelerinize metadata ekleyebileceğinizi söylemiştim, hatırlıyor musunuz? + +Şimdi bunu FastAPI ile kullanmanın zamanı. 🚀 + +Şu tip anotasyonuna sahiptik: + +//// tab | Python 3.10+ + +```Python +q: str | None = None +``` + +//// + +//// tab | Python 3.9+ + +```Python +q: Union[str, None] = None +``` + +//// + +Şimdi bunu `Annotated` ile saracağız; şöyle olacak: + +//// tab | Python 3.10+ + +```Python +q: Annotated[str | None] = None +``` + +//// + +//// tab | Python 3.9+ + +```Python +q: Annotated[Union[str, None]] = None +``` + +//// + +Bu iki sürüm de aynı anlama gelir: `q`, `str` veya `None` olabilen bir parametredir ve varsayılan olarak `None`’dır. + +Şimdi işin eğlenceli kısmına geçelim. 🎉 + +## `q` parametresindeki `Annotated` içine `Query` ekleyin { #add-query-to-annotated-in-the-q-parameter } + +Artık ek bilgi (bu durumda ek doğrulama) koyabildiğimiz bir `Annotated`’ımız olduğuna göre, `Annotated` içine `Query` ekleyin ve `max_length` parametresini `50` olarak ayarlayın: + +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *} + +Varsayılan değerin hâlâ `None` olduğuna dikkat edin; yani parametre hâlâ opsiyonel. + +Ama şimdi `Annotated` içinde `Query(max_length=50)` kullanarak FastAPI’ye bu değer için **ek doğrulama** istediğimizi söylüyoruz: en fazla 50 karakter. 😎 + +/// tip | İpucu + +Burada `Query()` kullanıyoruz çünkü bu bir **query parametresi**. İleride `Path()`, `Body()`, `Header()` ve `Cookie()` gibi, `Query()` ile aynı argümanları kabul eden diğerlerini de göreceğiz. + +/// + +FastAPI artık şunları yapacak: + +* Verinin uzunluğunun en fazla 50 karakter olduğundan emin olacak şekilde **doğrulayacak** +* Veri geçerli değilse client için **net bir hata** gösterecek +* Parametreyi OpenAPI şemasındaki *path operation* içinde **dokümante edecek** (dolayısıyla **otomatik dokümantasyon arayüzünde** görünecek) + +## Alternatif (eski): Varsayılan değer olarak `Query` { #alternative-old-query-as-the-default-value } + +FastAPI’nin önceki sürümlerinde (0.95.0 öncesi) `Query`’yi `Annotated` içine koymak yerine, parametrenizin varsayılan değeri olarak kullanmanız gerekiyordu. Etrafta bu şekilde yazılmış kod görme ihtimaliniz yüksek; bu yüzden açıklayalım. + +/// tip | İpucu + +Yeni kodlarda ve mümkün olduğunda, yukarıda anlatıldığı gibi `Annotated` kullanın. Birden fazla avantajı vardır (aşağıda anlatılıyor) ve dezavantajı yoktur. 🍰 + +/// + +Fonksiyon parametresinin varsayılan değeri olarak `Query()` kullanıp `max_length` parametresini 50 yapmak şöyle olurdu: + +{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *} + +Bu senaryoda (`Annotated` kullanmadığımız için) fonksiyondaki `None` varsayılan değerini `Query()` ile değiştirmemiz gerekiyor. Bu durumda varsayılan değeri `Query(default=None)` ile vermeliyiz; bu, (en azından FastAPI açısından) aynı varsayılan değeri tanımlama amacına hizmet eder. + +Yani: + +```Python +q: str | None = Query(default=None) +``` + +...parametreyi `None` varsayılan değeriyle opsiyonel yapar; şununla aynı: + +```Python +q: str | None = None +``` + +Ancak `Query` sürümü bunun bir query parametresi olduğunu açıkça belirtir. + +Sonrasında `Query`’ye daha fazla parametre geçebiliriz. Bu örnekte string’ler için geçerli olan `max_length`: + +```Python +q: str | None = Query(default=None, max_length=50) +``` + +Bu, veriyi doğrular, veri geçerli değilse net bir hata gösterir ve parametreyi OpenAPI şemasındaki *path operation* içinde dokümante eder. + +### Varsayılan değer olarak `Query` veya `Annotated` içinde `Query` { #query-as-the-default-value-or-in-annotated } + +`Annotated` içinde `Query` kullanırken `Query` için `default` parametresini kullanamayacağınızı unutmayın. + +Bunun yerine fonksiyon parametresinin gerçek varsayılan değerini kullanın. Aksi halde tutarsız olur. + +Örneğin şu kullanım izinli değildir: + +```Python +q: Annotated[str, Query(default="rick")] = "morty" +``` + +...çünkü varsayılan değerin `"rick"` mi `"morty"` mi olması gerektiği belli değildir. + +Bu nedenle (tercihen) şöyle kullanırsınız: + +```Python +q: Annotated[str, Query()] = "rick" +``` + +...veya eski kod tabanlarında şuna rastlarsınız: + +```Python +q: str = Query(default="rick") +``` + +### `Annotated`’ın avantajları { #advantages-of-annotated } + +Fonksiyon parametrelerindeki varsayılan değer stiline göre **`Annotated` kullanmanız önerilir**; birden fazla nedenle **daha iyidir**. 🤓 + +**Fonksiyon parametresinin** **varsayılan** değeri, **gerçek varsayılan** değerdir; bu genel olarak Python açısından daha sezgiseldir. 😌 + +Aynı fonksiyonu FastAPI olmadan **başka yerlerde** de **çağırabilirsiniz** ve **beklendiği gibi çalışır**. Eğer **zorunlu** bir parametre varsa (varsayılan değer yoksa) editörünüz hata ile bunu belirtir; ayrıca gerekli parametreyi vermeden çalıştırırsanız **Python** da şikayet eder. + +`Annotated` kullanmayıp bunun yerine **(eski) varsayılan değer stilini** kullanırsanız, o fonksiyonu FastAPI olmadan **başka yerlerde** çağırdığınızda doğru çalışması için argümanları geçmeniz gerektiğini **hatırlamak** zorunda kalırsınız; yoksa değerler beklediğinizden farklı olur (ör. `str` yerine `QueryInfo` veya benzeri). Üstelik editörünüz de şikayet etmez ve Python da fonksiyonu çalıştırırken şikayet etmez; ancak içerideki operasyonlar hata verince ortaya çıkar. + +`Annotated` birden fazla metadata anotasyonu alabildiği için, artık aynı fonksiyonu Typer gibi başka araçlarla da kullanabilirsiniz. 🚀 + +## Daha fazla doğrulama ekleyin { #add-more-validations } + +`min_length` parametresini de ekleyebilirsiniz: + +{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *} + +## Regular expression ekleyin { #add-regular-expressions } + +Parametrenin eşleşmesi gereken bir `pattern` regular expression tanımlayabilirsiniz: + +{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} + +Bu özel regular expression pattern’i, gelen parametre değerinin şunları sağladığını kontrol eder: + +* `^`: Aşağıdaki karakterlerle başlar; öncesinde karakter yoktur. +* `fixedquery`: Tam olarak `fixedquery` değerine sahiptir. +* `$`: Orada biter; `fixedquery` sonrasında başka karakter yoktur. + +Bu **"regular expression"** konuları gözünüzü korkutuyorsa sorun değil. Birçok kişi için zor bir konudur. Regular expression’lara ihtiyaç duymadan da pek çok şey yapabilirsiniz. + +Artık ihtiyaç duyduğunuzda **FastAPI** içinde kullanabileceğinizi biliyorsunuz. + +## Varsayılan değerler { #default-values } + +Elbette `None` dışında varsayılan değerler de kullanabilirsiniz. + +Örneğin `q` query parametresi için `min_length` değerini `3` yapmak ve varsayılan değer olarak `"fixedquery"` vermek istediğinizi düşünelim: + +{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} + +/// note | Not + +`None` dahil herhangi bir tipte varsayılan değere sahip olmak, parametreyi opsiyonel (zorunlu değil) yapar. + +/// + +## Zorunlu parametreler { #required-parameters } + +Daha fazla doğrulama veya metadata tanımlamamız gerekmiyorsa, `q` query parametresini yalnızca varsayılan değer tanımlamayarak zorunlu yapabiliriz: + +```Python +q: str +``` + +şunun yerine: + +```Python +q: str | None = None +``` + +Ancak biz artık `Query` ile tanımlıyoruz; örneğin şöyle: + +```Python +q: Annotated[str | None, Query(min_length=3)] = None +``` + +Dolayısıyla `Query` kullanırken bir değeri zorunlu yapmak istediğinizde, varsayılan değer tanımlamamanız yeterlidir: + +{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} + +### Zorunlu ama `None` olabilir { #required-can-be-none } + +Bir parametrenin `None` kabul edebileceğini söyleyip yine de zorunlu olmasını sağlayabilirsiniz. Bu, client’ların değer göndermesini zorunlu kılar; değer `None` olsa bile. + +Bunu yapmak için `None`’ı geçerli bir tip olarak tanımlayın ama varsayılan değer vermeyin: + +{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} + +## Query parametresi listesi / birden fazla değer { #query-parameter-list-multiple-values } + +Bir query parametresini `Query` ile açıkça tanımladığınızda, bir değer listesi alacak şekilde (başka bir deyişle, birden fazla değer alacak şekilde) de tanımlayabilirsiniz. + +Örneğin URL’de `q` query parametresinin birden fazla kez görünebilmesini istiyorsanız şöyle yazabilirsiniz: + +{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *} + +Sonra şu URL ile: + +``` +http://localhost:8000/items/?q=foo&q=bar +``` + +*path operation function* içinde, *function parameter* olan `q` parametresinde, birden fazla `q` *query parameters* değerini (`foo` ve `bar`) bir Python `list`’i olarak alırsınız. + +Dolayısıyla bu URL’ye response şöyle olur: + +```JSON +{ + "q": [ + "foo", + "bar" + ] +} +``` + +/// tip | İpucu + +Yukarıdaki örnekte olduğu gibi tipi `list` olan bir query parametresi tanımlamak için `Query`’yi açıkça kullanmanız gerekir; aksi halde request body olarak yorumlanır. + +/// + +Etkileşimli API dokümanları da buna göre güncellenir ve birden fazla değere izin verir: + + + +### Varsayılanlarla query parametresi listesi / birden fazla değer { #query-parameter-list-multiple-values-with-defaults } + +Hiç değer verilmezse varsayılan bir `list` de tanımlayabilirsiniz: + +{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} + +Şu adrese giderseniz: + +``` +http://localhost:8000/items/ +``` + +`q`’nun varsayılanı `["foo", "bar"]` olur ve response şöyle olur: + +```JSON +{ + "q": [ + "foo", + "bar" + ] +} +``` + +#### Sadece `list` kullanmak { #using-just-list } + +`list[str]` yerine doğrudan `list` de kullanabilirsiniz: + +{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} + +/// note | Not + +Bu durumda FastAPI, listenin içeriğini kontrol etmez. + +Örneğin `list[int]`, listenin içeriğinin integer olduğunu kontrol eder (ve dokümante eder). Ancak tek başına `list` bunu yapmaz. + +/// + +## Daha fazla metadata tanımlayın { #declare-more-metadata } + +Parametre hakkında daha fazla bilgi ekleyebilirsiniz. + +Bu bilgiler oluşturulan OpenAPI’a dahil edilir ve dokümantasyon arayüzleri ile harici araçlar tarafından kullanılır. + +/// note | Not + +Farklı araçların OpenAPI desteği farklı seviyelerde olabilir. + +Bazıları tanımladığınız ek bilgilerin hepsini göstermeyebilir; ancak çoğu durumda eksik özellik geliştirme planındadır. + +/// + +Bir `title` ekleyebilirsiniz: + +{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *} + +Ve bir `description`: + +{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *} + +## Alias parametreleri { #alias-parameters } + +Parametrenin adının `item-query` olmasını istediğinizi düşünün. + +Örneğin: + +``` +http://127.0.0.1:8000/items/?item-query=foobaritems +``` + +Ancak `item-query` geçerli bir Python değişken adı değildir. + +En yakın seçenek `item_query` olur. + +Ama sizin hâlâ tam olarak `item-query` olmasına ihtiyacınız var... + +O zaman bir `alias` tanımlayabilirsiniz; bu alias, parametre değerini bulmak için kullanılacaktır: + +{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *} + +## Parametreleri deprecated yapmak { #deprecating-parameters } + +Diyelim ki artık bu parametreyi istemiyorsunuz. + +Bazı client’lar hâlâ kullandığı için bir süre tutmanız gerekiyor, ama dokümanların bunu açıkça deprecated olarak göstermesini istiyorsunuz. + +O zaman `Query`’ye `deprecated=True` parametresini geçin: + +{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *} + +Dokümanlarda şöyle görünür: + + + +## Parametreleri OpenAPI’dan hariç tutun { #exclude-parameters-from-openapi } + +Oluşturulan OpenAPI şemasından (dolayısıyla otomatik dokümantasyon sistemlerinden) bir query parametresini hariç tutmak için `Query`’nin `include_in_schema` parametresini `False` yapın: + +{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *} + +## Özel Doğrulama { #custom-validation } + +Yukarıdaki parametrelerle yapılamayan bazı **özel doğrulama** ihtiyaçlarınız olabilir. + +Bu durumlarda, normal doğrulamadan sonra (ör. değerin `str` olduğunun doğrulanmasından sonra) uygulanacak bir **custom validator function** kullanabilirsiniz. + +Bunu, `Annotated` içinde Pydantic’in `AfterValidator`’ını kullanarak yapabilirsiniz. + +/// tip | İpucu + +Pydantic’te `BeforeValidator` ve başka validator’lar da vardır. 🤓 + +/// + +Örneğin bu custom validator, bir item ID’sinin ISBN kitap numarası için `isbn-` ile veya IMDB film URL ID’si için `imdb-` ile başladığını kontrol eder: + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} + +/// info | Bilgi + +Bu özellik Pydantic 2 ve üzeri sürümlerde mevcuttur. 😎 + +/// + +/// tip | İpucu + +Veritabanı veya başka bir API gibi herhangi bir **harici bileşen** ile iletişim gerektiren bir doğrulama yapmanız gerekiyorsa, bunun yerine **FastAPI Dependencies** kullanmalısınız; onları ileride öğreneceksiniz. + +Bu custom validator’lar, request’te sağlanan **yalnızca** **aynı veri** ile kontrol edilebilen şeyler içindir. + +/// + +### O Kodu Anlamak { #understand-that-code } + +Önemli nokta, **`Annotated` içinde bir fonksiyonla birlikte `AfterValidator` kullanmak**. İsterseniz bu kısmı atlayabilirsiniz. 🤸 + +--- + +Ama bu örnek kodun detaylarını merak ediyorsanız, birkaç ek bilgi: + +#### `value.startswith()` ile String { #string-with-value-startswith } + +Fark ettiniz mi? `value.startswith()` ile bir string, tuple alabilir ve tuple içindeki her değeri kontrol eder: + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *} + +#### Rastgele Bir Item { #a-random-item } + +`data.items()` ile, her dictionary öğesi için key ve value içeren tuple’lardan oluşan bir iterable object elde ederiz. + +Bu iterable object’i `list(data.items())` ile düzgün bir `list`’e çeviririz. + +Ardından `random.choice()` ile list’ten **rastgele bir değer** alırız; yani `(id, name)` içeren bir tuple elde ederiz. Şuna benzer: `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. + +Sonra tuple içindeki bu **iki değeri** `id` ve `name` değişkenlerine **atarız**. + +Böylece kullanıcı bir item ID’si vermemiş olsa bile yine de rastgele bir öneri alır. + +...bütün bunları **tek bir basit satırda** yapıyoruz. 🤯 Python’u sevmemek elde mi? 🐍 + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} + +## Özet { #recap } + +Parametreleriniz için ek doğrulamalar ve metadata tanımlayabilirsiniz. + +Genel doğrulamalar ve metadata: + +* `alias` +* `title` +* `description` +* `deprecated` + +String’lere özel doğrulamalar: + +* `min_length` +* `max_length` +* `pattern` + +`AfterValidator` ile custom doğrulamalar. + +Bu örneklerde `str` değerleri için doğrulamanın nasıl tanımlanacağını gördünüz. + +Sayılar gibi diğer tipler için doğrulamaları nasıl tanımlayacağınızı öğrenmek için sonraki bölümlere geçin. diff --git a/docs/tr/docs/tutorial/request-files.md b/docs/tr/docs/tutorial/request-files.md new file mode 100644 index 000000000..0bbc557e0 --- /dev/null +++ b/docs/tr/docs/tutorial/request-files.md @@ -0,0 +1,176 @@ +# Request Dosyaları { #request-files } + +İstemcinin upload edeceği dosyaları `File` kullanarak tanımlayabilirsiniz. + +/// info | Bilgi + +Upload edilen dosyaları alabilmek için önce `python-multipart` yükleyin. + +Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, aktive ettiğinizden ve ardından paketi yüklediğinizden emin olun. Örneğin: + +```console +$ pip install python-multipart +``` + +Bunun nedeni, upload edilen dosyaların "form data" olarak gönderilmesidir. + +/// + +## `File` Import Edin { #import-file } + +`fastapi` içinden `File` ve `UploadFile` import edin: + +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} + +## `File` Parametrelerini Tanımlayın { #define-file-parameters } + +`Body` veya `Form` için yaptığınız gibi dosya parametreleri oluşturun: + +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} + +/// info | Bilgi + +`File`, doğrudan `Form`’dan türeyen bir sınıftır. + +Ancak unutmayın: `fastapi` içinden `Query`, `Path`, `File` ve diğerlerini import ettiğinizde, bunlar aslında özel sınıflar döndüren fonksiyonlardır. + +/// + +/// tip | İpucu + +File body’leri tanımlamak için `File` kullanmanız gerekir; aksi halde parametreler query parametreleri veya body (JSON) parametreleri olarak yorumlanır. + +/// + +Dosyalar "form data" olarak upload edilir. + +*path operation function* parametrenizin tipini `bytes` olarak tanımlarsanız, **FastAPI** dosyayı sizin için okur ve içeriği `bytes` olarak alırsınız. + +Bunun, dosyanın tüm içeriğinin bellekte tutulacağı anlamına geldiğini unutmayın. Küçük dosyalar için iyi çalışır. + +Ancak bazı durumlarda `UploadFile` kullanmak size fayda sağlayabilir. + +## `UploadFile` ile Dosya Parametreleri { #file-parameters-with-uploadfile } + +Tipi `UploadFile` olan bir dosya parametresi tanımlayın: + +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} + +`UploadFile` kullanmanın `bytes`’a göre birkaç avantajı vardır: + +* Parametrenin varsayılan değerinde `File()` kullanmak zorunda değilsiniz. +* "Spooled" bir dosya kullanır: + * Belirli bir maksimum boyuta kadar bellekte tutulan, bu limiti aşınca diske yazılan bir dosya. +* Bu sayede görüntüler, videolar, büyük binary’ler vb. gibi büyük dosyalarda tüm belleği tüketmeden iyi çalışır. +* Upload edilen dosyadan metadata alabilirsiniz. +* file-like bir `async` arayüze sahiptir. +* `SpooledTemporaryFile` nesnesini dışa açar; bunu, file-like nesne bekleyen diğer library’lere doğrudan geçebilirsiniz. + +### `UploadFile` { #uploadfile } + +`UploadFile` şu attribute’lara sahiptir: + +* `filename`: Upload edilen orijinal dosya adını içeren bir `str` (örn. `myimage.jpg`). +* `content_type`: Content type’ı (MIME type / media type) içeren bir `str` (örn. `image/jpeg`). +* `file`: Bir `SpooledTemporaryFile` (bir file-like nesne). Bu, "file-like" nesne bekleyen diğer fonksiyonlara veya library’lere doğrudan verebileceğiniz gerçek Python file nesnesidir. + +`UploadFile` şu `async` method’lara sahiptir. Bunların hepsi altta ilgili dosya method’larını çağırır (dahili `SpooledTemporaryFile` kullanarak). + +* `write(data)`: Dosyaya `data` (`str` veya `bytes`) yazar. +* `read(size)`: Dosyadan `size` (`int`) kadar byte/karakter okur. +* `seek(offset)`: Dosyada `offset` (`int`) byte pozisyonuna gider. + * Örn. `await myfile.seek(0)` dosyanın başına gider. + * Bu, özellikle bir kez `await myfile.read()` çalıştırdıysanız ve sonra içeriği yeniden okumaya ihtiyaç duyuyorsanız faydalıdır. +* `close()`: Dosyayı kapatır. + +Bu method’ların hepsi `async` olduğundan, bunları "await" etmeniz gerekir. + +Örneğin, bir `async` *path operation function* içinde içeriği şöyle alabilirsiniz: + +```Python +contents = await myfile.read() +``` + +Normal bir `def` *path operation function* içindeyseniz `UploadFile.file`’a doğrudan erişebilirsiniz, örneğin: + +```Python +contents = myfile.file.read() +``` + +/// note | `async` Teknik Detaylar + +`async` method’ları kullandığınızda, **FastAPI** dosya method’larını bir threadpool içinde çalıştırır ve bunları await eder. + +/// + +/// note | Starlette Teknik Detaylar + +**FastAPI**’nin `UploadFile`’ı doğrudan **Starlette**’in `UploadFile`’ından türetilmiştir; ancak **Pydantic** ve FastAPI’nin diğer parçalarıyla uyumlu olması için bazı gerekli eklemeler yapar. + +/// + +## "Form Data" Nedir { #what-is-form-data } + +HTML formları (`
`) veriyi server’a gönderirken normalde JSON’dan farklı, veri için "özel" bir encoding kullanır. + +**FastAPI**, JSON yerine bu veriyi doğru yerden okuyacağından emin olur. + +/// note | Teknik Detaylar + +Formlardan gelen veri, dosya içermiyorsa normalde "media type" olarak `application/x-www-form-urlencoded` ile encode edilir. + +Ancak form dosya içeriyorsa `multipart/form-data` olarak encode edilir. `File` kullanırsanız, **FastAPI** dosyaları body’nin doğru kısmından alması gerektiğini bilir. + +Bu encoding’ler ve form alanları hakkında daha fazla okumak isterseniz MDN web dokümanlarındaki POST sayfasına bakın. + +/// + +/// warning | Uyarı + +Bir *path operation* içinde birden fazla `File` ve `Form` parametresi tanımlayabilirsiniz, ancak JSON olarak almayı beklediğiniz `Body` alanlarını ayrıca tanımlayamazsınız; çünkü request body `application/json` yerine `multipart/form-data` ile encode edilmiş olur. + +Bu, **FastAPI**’nin bir kısıtı değildir; HTTP protocol’ünün bir parçasıdır. + +/// + +## Opsiyonel Dosya Upload { #optional-file-upload } + +Standart type annotation’ları kullanıp varsayılan değeri `None` yaparak bir dosyayı opsiyonel hale getirebilirsiniz: + +{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} + +## Ek Metadata ile `UploadFile` { #uploadfile-with-additional-metadata } + +Ek metadata ayarlamak için `UploadFile` ile birlikte `File()` da kullanabilirsiniz. Örneğin: + +{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} + +## Birden Fazla Dosya Upload { #multiple-file-uploads } + +Aynı anda birden fazla dosya upload etmek mümkündür. + +Bu dosyalar, "form data" ile gönderilen aynı "form field" ile ilişkilendirilir. + +Bunu kullanmak için `bytes` veya `UploadFile` listesini tanımlayın: + +{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} + +Tanımladığınız gibi, `bytes` veya `UploadFile`’lardan oluşan bir `list` alırsınız. + +/// note | Teknik Detaylar + +`from starlette.responses import HTMLResponse` da kullanabilirsiniz. + +**FastAPI**, geliştiriciye kolaylık olsun diye `starlette.responses` modülünü `fastapi.responses` olarak da sağlar. Ancak mevcut response’ların çoğu doğrudan Starlette’ten gelir. + +/// + +### Ek Metadata ile Birden Fazla Dosya Upload { #multiple-file-uploads-with-additional-metadata } + +Daha önce olduğu gibi, `UploadFile` için bile ek parametreler ayarlamak amacıyla `File()` kullanabilirsiniz: + +{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} + +## Özet { #recap } + +Request’te (form data olarak gönderilen) upload edilecek dosyaları tanımlamak için `File`, `bytes` ve `UploadFile` kullanın. diff --git a/docs/tr/docs/tutorial/request-form-models.md b/docs/tr/docs/tutorial/request-form-models.md new file mode 100644 index 000000000..c35f956fc --- /dev/null +++ b/docs/tr/docs/tutorial/request-form-models.md @@ -0,0 +1,78 @@ +# Form Model'leri { #form-models } + +FastAPI'de **form field**'larını tanımlamak için **Pydantic model**'lerini kullanabilirsiniz. + +/// info | Bilgi + +Form'ları kullanmak için önce `python-multipart`'ı yükleyin. + +Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu etkinleştirdiğinizden ve ardından paketi kurduğunuzdan emin olun. Örneğin: + +```console +$ pip install python-multipart +``` + +/// + +/// note | Not + +Bu özellik FastAPI `0.113.0` sürümünden itibaren desteklenmektedir. 🤓 + +/// + +## Form'lar için Pydantic Model'leri { #pydantic-models-for-forms } + +Sadece, **form field** olarak almak istediğiniz alanlarla bir **Pydantic model** tanımlayın ve ardından parametreyi `Form` olarak bildirin: + +{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} + +**FastAPI**, request içindeki **form data**'dan **her bir field** için veriyi **çıkarır** ve size tanımladığınız Pydantic model'ini verir. + +## Dokümanları Kontrol Edin { #check-the-docs } + +Bunu `/docs` altındaki doküman arayüzünde doğrulayabilirsiniz: + +
+ +
+ +## Fazladan Form Field'larını Yasaklayın { #forbid-extra-form-fields } + +Bazı özel kullanım senaryolarında (muhtemelen çok yaygın değildir), form field'larını yalnızca Pydantic model'inde tanımlananlarla **sınırlamak** isteyebilirsiniz. Ve **fazladan** gelen field'ları **yasaklayabilirsiniz**. + +/// note | Not + +Bu özellik FastAPI `0.114.0` sürümünden itibaren desteklenmektedir. 🤓 + +/// + +Herhangi bir `extra` field'ı `forbid` etmek için Pydantic'in model konfigürasyonunu kullanabilirsiniz: + +{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} + +Bir client fazladan veri göndermeye çalışırsa, bir **error** response alır. + +Örneğin, client şu form field'larını göndermeye çalışırsa: + +* `username`: `Rick` +* `password`: `Portal Gun` +* `extra`: `Mr. Poopybutthole` + +`extra` field'ının izinli olmadığını söyleyen bir error response alır: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["body", "extra"], + "msg": "Extra inputs are not permitted", + "input": "Mr. Poopybutthole" + } + ] +} +``` + +## Özet { #summary } + +FastAPI'de form field'larını tanımlamak için Pydantic model'lerini kullanabilirsiniz. 😎 diff --git a/docs/tr/docs/tutorial/request-forms-and-files.md b/docs/tr/docs/tutorial/request-forms-and-files.md new file mode 100644 index 000000000..86d26b498 --- /dev/null +++ b/docs/tr/docs/tutorial/request-forms-and-files.md @@ -0,0 +1,41 @@ +# Request Forms ve Files { #request-forms-and-files } + +`File` ve `Form` kullanarak aynı anda hem dosyaları hem de form alanlarını tanımlayabilirsiniz. + +/// info | Bilgi + +Yüklenen dosyaları ve/veya form verisini almak için önce `python-multipart` paketini kurun. + +Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu aktive ettiğinizden ve ardından paketi kurduğunuzdan emin olun, örneğin: + +```console +$ pip install python-multipart +``` + +/// + +## `File` ve `Form` Import Edin { #import-file-and-form } + +{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} + +## `File` ve `Form` Parametrelerini Tanımlayın { #define-file-and-form-parameters } + +Dosya ve form parametrelerini, `Body` veya `Query` için yaptığınız şekilde oluşturun: + +{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} + +Dosyalar ve form alanları form data olarak upload edilir ve siz de dosyaları ve form alanlarını alırsınız. + +Ayrıca bazı dosyaları `bytes` olarak, bazılarını da `UploadFile` olarak tanımlayabilirsiniz. + +/// warning | Uyarı + +Bir *path operation* içinde birden fazla `File` ve `Form` parametresi tanımlayabilirsiniz; ancak request'in body'si `application/json` yerine `multipart/form-data` ile encode edileceği için, JSON olarak almayı beklediğiniz `Body` alanlarını aynı anda tanımlayamazsınız. + +Bu **FastAPI** kısıtı değildir; HTTP protokolünün bir parçasıdır. + +/// + +## Özet { #recap } + +Aynı request içinde hem veri hem de dosya almanız gerektiğinde `File` ve `Form`'u birlikte kullanın. diff --git a/docs/tr/docs/tutorial/response-model.md b/docs/tr/docs/tutorial/response-model.md new file mode 100644 index 000000000..f1d1f7d15 --- /dev/null +++ b/docs/tr/docs/tutorial/response-model.md @@ -0,0 +1,343 @@ +# Response Model - Dönüş Tipi { #response-model-return-type } + +*Path operation function* **dönüş tipini** (return type) type annotation ile belirtip response için kullanılacak tipi tanımlayabilirsiniz. + +Fonksiyon **parametreleri** için input data’da kullandığınız **type annotations** yaklaşımının aynısını burada da kullanabilirsiniz; Pydantic model’leri, list’ler, dict’ler, integer, boolean gibi skaler değerler vb. + +{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} + +FastAPI bu dönüş tipini şunlar için kullanır: + +* Dönen veriyi **doğrulamak** (validate). + * Veri geçersizse (ör. bir field eksikse), bu *sizin* uygulama kodunuzun bozuk olduğu, olması gerekeni döndürmediği anlamına gelir; bu yüzden yanlış veri döndürmek yerine server error döner. Böylece siz ve client’larınız, beklenen veri ve veri şeklinin geleceğinden emin olabilirsiniz. +* OpenAPI’deki *path operation* içine response için bir **JSON Schema** eklemek. + * Bu, **otomatik dokümantasyon** tarafından kullanılır. + * Ayrıca otomatik client code generation araçları tarafından da kullanılır. + +Ama en önemlisi: + +* Çıktı verisini, dönüş tipinde tanımlı olana göre **sınırlar ve filtreler**. + * Bu, özellikle **güvenlik** açısından önemlidir; aşağıda daha fazlasını göreceğiz. + +## `response_model` Parametresi { #response-model-parameter } + +Bazı durumlarda, tam olarak dönüş tipinin söylediği gibi olmayan bir veriyi döndürmeniz gerekebilir ya da isteyebilirsiniz. + +Örneğin, **bir dict** veya bir veritabanı objesi döndürmek isteyip, ama **onu bir Pydantic model olarak declare etmek** isteyebilirsiniz. Böylece Pydantic model, döndürdüğünüz obje (ör. dict veya veritabanı objesi) için dokümantasyon, doğrulama vb. işlerin tamamını yapar. + +Eğer dönüş tipi annotation’ını eklerseniz, araçlar ve editörler (doğru şekilde) fonksiyonunuzun, declare ettiğiniz tipten (ör. Pydantic model) farklı bir tip (ör. dict) döndürdüğünü söyleyip hata verir. + +Bu gibi durumlarda, dönüş tipi yerine *path operation decorator* parametresi olan `response_model`’i kullanabilirsiniz. + +`response_model` parametresini herhangi bir *path operation* içinde kullanabilirsiniz: + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* vb. + +{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *} + +/// note | Not + +`response_model`’in "decorator" metodunun (`get`, `post` vb.) bir parametresi olduğuna dikkat edin. Body ve diğer parametreler gibi, sizin *path operation function*’ınızın parametresi değildir. + +/// + +`response_model`, Pydantic model field’ı için declare edeceğiniz aynı tipi alır; yani bir Pydantic model olabilir ama örneğin `List[Item]` gibi Pydantic model’lerden oluşan bir `list` de olabilir. + +FastAPI bu `response_model`’i; dokümantasyon, doğrulama vb. her şey için ve ayrıca çıktı verisini **tip tanımına göre dönüştürmek ve filtrelemek** için kullanır. + +/// tip | İpucu + +Editörünüzde, mypy vb. ile sıkı type kontrolü yapıyorsanız, fonksiyon dönüş tipini `Any` olarak declare edebilirsiniz. + +Böylece editöre bilerek her şeyi döndürebileceğinizi söylemiş olursunuz. Ancak FastAPI, `response_model` ile dokümantasyon, doğrulama, filtreleme vb. işlemleri yine de yapar. + +/// + +### `response_model` Önceliği { #response-model-priority } + +Hem dönüş tipi hem de `response_model` declare ederseniz, FastAPI’de `response_model` önceliklidir ve o kullanılır. + +Böylece, response model’den farklı bir tip döndürdüğünüz durumlarda bile editör ve mypy gibi araçlar için fonksiyonlarınıza doğru type annotation’lar ekleyebilir, aynı zamanda FastAPI’nin `response_model` üzerinden veri doğrulama, dokümantasyon vb. yapmasını sağlayabilirsiniz. + +Ayrıca `response_model=None` kullanarak, ilgili *path operation* için response model oluşturulmasını devre dışı bırakabilirsiniz. Bu, Pydantic field’ı olarak geçerli olmayan şeyler için type annotation eklediğinizde gerekebilir; aşağıdaki bölümlerden birinde bunun örneğini göreceksiniz. + +## Aynı input verisini geri döndürmek { #return-the-same-input-data } + +Burada `UserIn` adında bir model declare ediyoruz; bu model plaintext bir password içerecek: + +{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *} + +/// info | Bilgi + +`EmailStr` kullanmak için önce `email-validator` paketini kurun. + +Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu aktive ettiğinizden emin olun ve ardından örneğin şöyle kurun: + +```console +$ pip install email-validator +``` + +veya şöyle: + +```console +$ pip install "pydantic[email]" +``` + +/// + +Bu model ile hem input’u declare ediyoruz hem de output’u aynı model ile declare ediyoruz: + +{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *} + +Artık bir browser password ile user oluşturduğunda, API response içinde aynı password’ü geri döndürecek. + +Bu örnekte sorun olmayabilir; çünkü password’ü gönderen kullanıcı zaten aynı kişi. + +Ancak aynı modeli başka bir *path operation* için kullanırsak, kullanıcının password’lerini her client’a gönderiyor olabiliriz. + +/// danger + +Tüm riskleri bildiğinizden ve ne yaptığınızdan emin olmadığınız sürece, bir kullanıcının plain password’ünü asla saklamayın ve bu şekilde response içinde göndermeyin. + +/// + +## Bir output modeli ekleyin { #add-an-output-model } + +Bunun yerine, plaintext password içeren bir input modeli ve password’ü içermeyen bir output modeli oluşturabiliriz: + +{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *} + +Burada *path operation function* password içeren aynı input user’ı döndürüyor olsa bile: + +{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *} + +...`response_model` olarak, password’ü içermeyen `UserOut` modelimizi declare ettik: + +{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *} + +Dolayısıyla **FastAPI**, output model’de declare edilmemiş tüm verileri (Pydantic kullanarak) filtrelemekle ilgilenir. + +### `response_model` mi Return Type mı? { #response-model-or-return-type } + +Bu durumda iki model farklı olduğu için fonksiyon dönüş tipini `UserOut` olarak annotate etseydik, editör ve araçlar farklı class’lar olduğu için geçersiz bir tip döndürdüğümüzü söyleyip hata verecekti. + +Bu yüzden bu örnekte `response_model` parametresinde declare etmek zorundayız. + +...ama bunu nasıl aşabileceğinizi görmek için aşağıyı okumaya devam edin. + +## Return Type ve Veri Filtreleme { #return-type-and-data-filtering } + +Önceki örnekten devam edelim. Fonksiyonu **tek bir tip ile annotate etmek** istiyoruz; ama fonksiyondan gerçekte **daha fazla veri** içeren bir şey döndürebilmek istiyoruz. + +FastAPI’nin response model’i kullanarak veriyi **filtrelemeye** devam etmesini istiyoruz. Yani fonksiyon daha fazla veri döndürse bile response, sadece response model’de declare edilmiş field’ları içersin. + +Önceki örnekte class’lar farklı olduğu için `response_model` parametresini kullanmak zorundaydık. Ancak bu, editör ve araçların fonksiyon dönüş tipi kontrolünden gelen desteğini alamadığımız anlamına da geliyor. + +Ama bu tarz durumların çoğunda modelin amacı, bu örnekteki gibi bazı verileri **filtrelemek/kaldırmak** olur. + +Bu gibi durumlarda class’lar ve inheritance kullanarak, fonksiyon **type annotations** sayesinde editör ve araçlarda daha iyi destek alabilir, aynı zamanda FastAPI’nin **veri filtrelemesini** de koruyabiliriz. + +{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *} + +Bununla birlikte, code type’lar açısından doğru olduğu için editörler ve mypy araç desteği verir; ayrıca FastAPI’den veri filtrelemeyi de alırız. + +Bu nasıl çalışıyor? Bir bakalım. 🤓 + +### Type Annotations ve Araç Desteği { #type-annotations-and-tooling } + +Önce editörler, mypy ve diğer araçlar bunu nasıl görür, ona bakalım. + +`BaseUser` temel field’lara sahiptir. Ardından `UserIn`, `BaseUser`’dan miras alır ve `password` field’ını ekler; yani iki modelin field’larının tamamını içerir. + +Fonksiyonun dönüş tipini `BaseUser` olarak annotate ediyoruz ama gerçekte bir `UserIn` instance’ı döndürüyoruz. + +Editör, mypy ve diğer araçlar buna itiraz etmez; çünkü typing açısından `UserIn`, `BaseUser`’ın subclass’ıdır. Bu da, bir `BaseUser` bekleniyorken `UserIn`’in *geçerli* bir tip olduğu anlamına gelir. + +### FastAPI Veri Filtreleme { #fastapi-data-filtering } + +FastAPI açısından ise dönüş tipini görür ve döndürdüğünüz şeyin **yalnızca** tipte declare edilen field’ları içerdiğinden emin olur. + +FastAPI, Pydantic ile içeride birkaç işlem yapar; böylece class inheritance kurallarının dönen veri filtrelemede aynen kullanılmasına izin vermez. Aksi halde beklediğinizden çok daha fazla veriyi response’ta döndürebilirdiniz. + +Bu sayede iki dünyanın da en iyisini alırsınız: **araç desteği** veren type annotations ve **veri filtreleme**. + +## Dokümanlarda görün { #see-it-in-the-docs } + +Otomatik dokümanları gördüğünüzde, input model ve output model’in her birinin kendi JSON Schema’sına sahip olduğunu kontrol edebilirsiniz: + + + +Ve her iki model de etkileşimli API dokümantasyonunda kullanılır: + + + +## Diğer Return Type Annotation’ları { #other-return-type-annotations } + +Bazı durumlarda Pydantic field olarak geçerli olmayan bir şey döndürebilir ve bunu fonksiyonda annotate edebilirsiniz; amaç sadece araçların (editör, mypy vb.) sağladığı desteği almaktır. + +### Doğrudan Response Döndürmek { #return-a-response-directly } + +En yaygın durum, [ileri seviye dokümanlarda daha sonra anlatıldığı gibi doğrudan bir Response döndürmektir](../advanced/response-directly.md){.internal-link target=_blank}. + +{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} + +Bu basit durum FastAPI tarafından otomatik olarak ele alınır; çünkü dönüş tipi annotation’ı `Response` class’ıdır (veya onun bir subclass’ı). + +Araçlar da memnun olur; çünkü hem `RedirectResponse` hem `JSONResponse`, `Response`’un subclass’ıdır. Yani type annotation doğrudur. + +### Bir Response Subclass’ını Annotate Etmek { #annotate-a-response-subclass } + +Type annotation içinde `Response`’un bir subclass’ını da kullanabilirsiniz: + +{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} + +Bu da çalışır; çünkü `RedirectResponse`, `Response`’un subclass’ıdır ve FastAPI bu basit durumu otomatik olarak yönetir. + +### Geçersiz Return Type Annotation’ları { #invalid-return-type-annotations } + +Ancak geçerli bir Pydantic tipi olmayan başka rastgele bir obje (ör. bir veritabanı objesi) döndürür ve fonksiyonu da öyle annotate ederseniz, FastAPI bu type annotation’dan bir Pydantic response model oluşturmaya çalışır ve başarısız olur. + +Aynı şey, farklı tipler arasında bir union kullandığınızda ve bu tiplerden biri veya birkaçı geçerli bir Pydantic tipi değilse de olur; örneğin şu kullanım patlar 💥: + +{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} + +...bu, type annotation Pydantic tipi olmadığı ve tek bir `Response` class’ı (veya subclass’ı) olmadığı için başarısız olur; bu, bir `Response` ile bir `dict` arasında union’dır (ikiden herhangi biri). + +### Response Model’i Devre Dışı Bırakmak { #disable-response-model } + +Yukarıdaki örnekten devam edersek; FastAPI’nin varsayılan olarak yaptığı veri doğrulama, dokümantasyon, filtreleme vb. işlemleri istemiyor olabilirsiniz. + +Ancak yine de editörler ve type checker’lar (ör. mypy) gibi araçların desteğini almak için fonksiyonda dönüş tipi annotation’ını korumak isteyebilirsiniz. + +Bu durumda `response_model=None` ayarlayarak response model üretimini devre dışı bırakabilirsiniz: + +{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *} + +Bu, FastAPI’nin response model üretimini atlamasını sağlar; böylece FastAPI uygulamanızı etkilemeden ihtiyacınız olan herhangi bir return type annotation’ını kullanabilirsiniz. 🤓 + +## Response Model encoding parametreleri { #response-model-encoding-parameters } + +Response model’inizde şu şekilde default değerler olabilir: + +{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *} + +* `description: Union[str, None] = None` (veya Python 3.10’da `str | None = None`) için default `None`’dır. +* `tax: float = 10.5` için default `10.5`’tir. +* `tags: List[str] = []` için default, boş bir list’tir: `[]`. + +Ancak gerçekte kaydedilmedilerse, bunları sonuçtan çıkarmak isteyebilirsiniz. + +Örneğin NoSQL veritabanında çok sayıda optional attribute içeren modelleriniz varsa, default değerlerle dolu çok uzun JSON response’ları göndermek istemeyebilirsiniz. + +### `response_model_exclude_unset` parametresini kullanın { #use-the-response-model-exclude-unset-parameter } + +*Path operation decorator* parametresi olarak `response_model_exclude_unset=True` ayarlayabilirsiniz: + +{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *} + +böylece response’a default değerler dahil edilmez; yalnızca gerçekten set edilmiş değerler gelir. + +Dolayısıyla ID’si `foo` olan item için bu *path operation*’a request atarsanız, response (default değerler olmadan) şöyle olur: + +```JSON +{ + "name": "Foo", + "price": 50.2 +} +``` + +/// info | Bilgi + +Ayrıca şunları da kullanabilirsiniz: + +* `response_model_exclude_defaults=True` +* `response_model_exclude_none=True` + +Bunlar, `exclude_defaults` ve `exclude_none` için Pydantic dokümanlarında anlatıldığı gibidir. + +/// + +#### Default’u olan field’lar için değer içeren data { #data-with-values-for-fields-with-defaults } + +Ama data’nız modelde default değeri olan field’lar için değer içeriyorsa, örneğin ID’si `bar` olan item gibi: + +```Python hl_lines="3 5" +{ + "name": "Bar", + "description": "The bartenders", + "price": 62, + "tax": 20.2 +} +``` + +bunlar response’a dahil edilir. + +#### Default değerlerle aynı değerlere sahip data { #data-with-the-same-values-as-the-defaults } + +Eğer data, default değerlerle aynı değerlere sahipse, örneğin ID’si `baz` olan item gibi: + +```Python hl_lines="3 5-6" +{ + "name": "Baz", + "description": None, + "price": 50.2, + "tax": 10.5, + "tags": [] +} +``` + +FastAPI yeterince akıllıdır (aslında Pydantic yeterince akıllıdır) ve `description`, `tax`, `tags` default ile aynı olsa bile bunların explicit olarak set edildiğini (default’tan alınmadığını) anlar. + +Bu yüzden JSON response içinde yer alırlar. + +/// tip | İpucu + +Default değerlerin yalnızca `None` olmak zorunda olmadığını unutmayın. + +Bir list (`[]`), `10.5` gibi bir `float` vb. olabilirler. + +/// + +### `response_model_include` ve `response_model_exclude` { #response-model-include-and-response-model-exclude } + +Ayrıca *path operation decorator* parametreleri `response_model_include` ve `response_model_exclude`’u da kullanabilirsiniz. + +Bunlar; dahil edilecek attribute isimlerini (geri kalanını atlayarak) ya da hariç tutulacak attribute isimlerini (geri kalanını dahil ederek) belirten `str` değerlerinden oluşan bir `set` alır. + +Tek bir Pydantic model’iniz varsa ve output’tan bazı verileri hızlıca çıkarmak istiyorsanız, bu yöntem pratik bir kısayol olabilir. + +/// tip | İpucu + +Ancak yine de, bu parametreler yerine yukarıdaki yaklaşımı (birden fazla class kullanmayı) tercih etmeniz önerilir. + +Çünkü `response_model_include` veya `response_model_exclude` ile bazı attribute’ları atlıyor olsanız bile, uygulamanızın OpenAPI’sinde (ve dokümanlarda) üretilen JSON Schema hâlâ tam modelin JSON Schema’sı olacaktır. + +Bu durum, benzer şekilde çalışan `response_model_by_alias` için de geçerlidir. + +/// + +{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *} + +/// tip | İpucu + +`{"name", "description"}` sözdizimi, bu iki değere sahip bir `set` oluşturur. + +Bu, `set(["name", "description"])` ile eşdeğerdir. + +/// + +#### `set` yerine `list` kullanmak { #using-lists-instead-of-sets } + +Yanlışlıkla `set` yerine `list` veya `tuple` kullanırsanız, FastAPI bunu yine `set`’e çevirir ve doğru şekilde çalışır: + +{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *} + +## Özet { #recap } + +Response model’leri tanımlamak ve özellikle private data’nın filtrelendiğinden emin olmak için *path operation decorator* parametresi `response_model`’i kullanın. + +Yalnızca explicit olarak set edilmiş değerleri döndürmek için `response_model_exclude_unset` kullanın. diff --git a/docs/tr/docs/tutorial/response-status-code.md b/docs/tr/docs/tutorial/response-status-code.md new file mode 100644 index 000000000..57ae7bde3 --- /dev/null +++ b/docs/tr/docs/tutorial/response-status-code.md @@ -0,0 +1,101 @@ +# Response Status Code { #response-status-code } + +Bir response model tanımlayabildiğiniz gibi, herhangi bir *path operation* içinde `status_code` parametresiyle response için kullanılacak HTTP status code'u da belirtebilirsiniz: + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* vb. + +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} + +/// note | Not + +`status_code`'un, "decorator" metodunun (`get`, `post`, vb.) bir parametresi olduğuna dikkat edin. Tüm parametreler ve body gibi, sizin *path operation function*'ınızın bir parametresi değildir. + +/// + +`status_code` parametresi, HTTP status code'u içeren bir sayı alır. + +/// info | Bilgi + +Alternatif olarak `status_code`, Python'un `http.HTTPStatus`'ı gibi bir `IntEnum` da alabilir. + +/// + +Bu sayede: + +* Response'da o status code döner. +* OpenAPI şemasında (dolayısıyla kullanıcı arayüzlerinde de) bu şekilde dokümante edilir: + + + +/// note | Not + +Bazı response code'lar (bir sonraki bölümde göreceğiz) response'un bir body'ye sahip olmadığını belirtir. + +FastAPI bunu bilir ve response body olmadığını söyleyen OpenAPI dokümantasyonunu üretir. + +/// + +## HTTP status code'lar hakkında { #about-http-status-codes } + +/// note | Not + +HTTP status code'ların ne olduğunu zaten biliyorsanız, bir sonraki bölüme geçin. + +/// + +HTTP'de, response'un bir parçası olarak 3 basamaklı sayısal bir status code gönderirsiniz. + +Bu status code'ların tanınmalarını sağlayan bir isimleri de vardır; ancak önemli olan kısım sayıdır. + +Kısaca: + +* `100 - 199` "Information" içindir. Doğrudan nadiren kullanırsınız. Bu status code'lara sahip response'lar body içeremez. +* **`200 - 299`** "Successful" response'lar içindir. En sık kullanacağınız aralık budur. + * `200`, varsayılan status code'dur ve her şeyin "OK" olduğunu ifade eder. + * Başka bir örnek `201` ("Created") olabilir. Genellikle veritabanında yeni bir kayıt oluşturduktan sonra kullanılır. + * Özel bir durum ise `204` ("No Content")'tür. Client'a döndürülecek içerik olmadığında kullanılır; bu nedenle response body olmamalıdır. +* **`300 - 399`** "Redirection" içindir. Bu status code'lara sahip response'lar, `304` ("Not Modified") hariç, body içerebilir de içermeyebilir de; `304` kesinlikle body içermemelidir. +* **`400 - 499`** "Client error" response'ları içindir. Muhtemelen en sık kullanacağınız ikinci aralık budur. + * Örneğin `404`, "Not Found" response'u içindir. + * Client kaynaklı genel hatalar için doğrudan `400` kullanabilirsiniz. +* `500 - 599` server hataları içindir. Neredeyse hiç doğrudan kullanmazsınız. Uygulama kodunuzun bir bölümünde ya da server'da bir şeyler ters giderse, otomatik olarak bu status code'lardan biri döner. + +/// tip | İpucu + +Her bir status code hakkında daha fazla bilgi almak ve hangi kodun ne için kullanıldığını görmek için HTTP status code'lar hakkında MDN dokümantasyonuna göz atın. + +/// + +## İsimleri hatırlamak için kısayol { #shortcut-to-remember-the-names } + +Önceki örneğe tekrar bakalım: + +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} + +`201`, "Created" için kullanılan status code'dur. + +Ancak bu kodların her birinin ne anlama geldiğini ezberlemek zorunda değilsiniz. + +`fastapi.status` içindeki kolaylık değişkenlerini (convenience variables) kullanabilirsiniz. + +{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} + +Bunlar sadece kolaylık sağlar; aynı sayıyı taşırlar. Ancak bu şekilde editörün autocomplete özelliğiyle kolayca bulabilirsiniz: + + + +/// note | Teknik Detaylar + +`from starlette import status` da kullanabilirsiniz. + +**FastAPI**, geliştirici olarak size kolaylık olsun diye `starlette.status`'u `fastapi.status` olarak da sunar. Ancak bu aslında doğrudan Starlette'den gelir. + +/// + +## Varsayılanı değiştirmek { #changing-the-default } + +Daha sonra, [İleri Düzey Kullanıcı Kılavuzu](../advanced/response-change-status-code.md){.internal-link target=_blank} içinde, burada tanımladığınız varsayılanın dışında farklı bir status code nasıl döndüreceğinizi göreceksiniz. diff --git a/docs/tr/docs/tutorial/schema-extra-example.md b/docs/tr/docs/tutorial/schema-extra-example.md new file mode 100644 index 000000000..a91dda892 --- /dev/null +++ b/docs/tr/docs/tutorial/schema-extra-example.md @@ -0,0 +1,202 @@ +# Request Örnek Verilerini Tanımlama { #declare-request-example-data } + +Uygulamanızın alabileceği veriler için örnekler (examples) tanımlayabilirsiniz. + +Bunu yapmanın birkaç yolu var. + +## Pydantic modellerinde ek JSON Schema verisi { #extra-json-schema-data-in-pydantic-models } + +Oluşturulan JSON Schema’ya eklenecek şekilde bir Pydantic model için `examples` tanımlayabilirsiniz. + +{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} + +Bu ek bilgi, o modelin çıktı **JSON Schema**’sına olduğu gibi eklenir ve API dokümanlarında kullanılır. + +Pydantic dokümanları: Configuration bölümünde anlatıldığı gibi, bir `dict` alan `model_config` niteliğini kullanabilirsiniz. + +Üretilen JSON Schema’da görünmesini istediğiniz (ör. `examples` dahil) her türlü ek veriyi içeren bir `dict` ile `"json_schema_extra"` ayarlayabilirsiniz. + +/// tip | İpucu + +Aynı tekniği JSON Schema’yı genişletmek ve kendi özel ek bilgilerinizi eklemek için de kullanabilirsiniz. + +Örneğin, bir frontend kullanıcı arayüzü için metadata eklemek vb. amaçlarla kullanılabilir. + +/// + +/// info | Bilgi + +OpenAPI 3.1.0 (FastAPI 0.99.0’dan beri kullanılıyor), **JSON Schema** standardının bir parçası olan `examples` için destek ekledi. + +Bundan önce yalnızca tek bir örnek için `example` anahtar kelimesini destekliyordu. Bu hâlâ OpenAPI 3.1.0 tarafından desteklenir; ancak artık deprecated durumdadır ve JSON Schema standardının parçası değildir. Bu nedenle `example` kullanımını `examples`’a taşımanız önerilir. 🤓 + +Daha fazlasını sayfanın sonunda okuyabilirsiniz. + +/// + +## `Field` ek argümanları { #field-additional-arguments } + +Pydantic modelleriyle `Field()` kullanırken ek `examples` de tanımlayabilirsiniz: + +{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} + +## JSON Schema - OpenAPI içinde `examples` { #examples-in-json-schema-openapi } + +Aşağıdakilerden herhangi birini kullanırken: + +* `Path()` +* `Query()` +* `Header()` +* `Cookie()` +* `Body()` +* `Form()` +* `File()` + +OpenAPI içindeki **JSON Schema**’larına eklenecek ek bilgilerle birlikte bir `examples` grubu da tanımlayabilirsiniz. + +### `examples` ile `Body` { #body-with-examples } + +Burada `Body()` içinde beklenen veri için tek bir örnek içeren `examples` geçiriyoruz: + +{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *} + +### Doküman arayüzünde örnek { #example-in-the-docs-ui } + +Yukarıdaki yöntemlerden herhangi biriyle `/docs` içinde şöyle görünür: + + + +### Birden fazla `examples` ile `Body` { #body-with-multiple-examples } + +Elbette birden fazla `examples` da geçebilirsiniz: + +{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} + +Bunu yaptığınızda, örnekler bu body verisi için dahili **JSON Schema**’nın bir parçası olur. + +Buna rağmen, time of writing this, doküman arayüzünü gösteren araç olan Swagger UI, **JSON Schema** içindeki veriler için birden fazla örneği göstermeyi desteklemiyor. Ancak aşağıda bir çözüm yolu var. + +### OpenAPI’ye özel `examples` { #openapi-specific-examples } + +**JSON Schema** `examples`’ı desteklemeden önce OpenAPI, yine `examples` adlı farklı bir alanı destekliyordu. + +Bu **OpenAPI’ye özel** `examples`, OpenAPI spesifikasyonunda başka bir bölümde yer alır. Her bir JSON Schema’nın içinde değil, **her bir *path operation* detayları** içinde bulunur. + +Swagger UI da bu özel `examples` alanını bir süredir destekliyor. Dolayısıyla bunu, **doküman arayüzünde** farklı **örnekleri göstermek** için kullanabilirsiniz. + +OpenAPI’ye özel bu `examples` alanının şekli, (bir `list` yerine) **birden fazla örnek** içeren bir `dict`’tir; her örnek ayrıca **OpenAPI**’ye eklenecek ekstra bilgiler içerir. + +Bu, OpenAPI’nin içerdiği JSON Schema’ların içine girmez; bunun yerine doğrudan *path operation* üzerinde, dışarıda yer alır. + +### `openapi_examples` Parametresini Kullanma { #using-the-openapi-examples-parameter } + +FastAPI’de OpenAPI’ye özel `examples`’ı, şu araçlar için `openapi_examples` parametresiyle tanımlayabilirsiniz: + +* `Path()` +* `Query()` +* `Header()` +* `Cookie()` +* `Body()` +* `Form()` +* `File()` + +`dict`’in anahtarları her bir örneği tanımlar; her bir değer ise başka bir `dict`’tir. + +`examples` içindeki her bir örnek `dict`’i şunları içerebilir: + +* `summary`: Örnek için kısa açıklama. +* `description`: Markdown metni içerebilen uzun açıklama. +* `value`: Gösterilecek gerçek örnek (ör. bir `dict`). +* `externalValue`: `value`’a alternatif; örneğe işaret eden bir URL. Ancak bu, `value` kadar çok araç tarafından desteklenmiyor olabilir. + +Şöyle kullanabilirsiniz: + +{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *} + +### Doküman Arayüzünde OpenAPI Örnekleri { #openapi-examples-in-the-docs-ui } + +`Body()`’ye `openapi_examples` eklendiğinde `/docs` şöyle görünür: + + + +## Teknik Detaylar { #technical-details } + +/// tip | İpucu + +Zaten **FastAPI** sürümü **0.99.0 veya üzerini** kullanıyorsanız, büyük olasılıkla bu detayları **atlanabilirsiniz**. + +Bunlar daha çok OpenAPI 3.1.0’ın henüz mevcut olmadığı eski sürümler için geçerlidir. + +Bunu kısa bir OpenAPI ve JSON Schema **tarih dersi** gibi düşünebilirsiniz. 🤓 + +/// + +/// warning | Uyarı + +Bunlar **JSON Schema** ve **OpenAPI** standartları hakkında oldukça teknik detaylardır. + +Yukarıdaki fikirler sizin için zaten çalışıyorsa bu kadarı yeterli olabilir; muhtemelen bu detaylara ihtiyacınız yoktur, gönül rahatlığıyla atlayabilirsiniz. + +/// + +OpenAPI 3.1.0’dan önce OpenAPI, **JSON Schema**’nın daha eski ve değiştirilmiş bir sürümünü kullanıyordu. + +JSON Schema’da `examples` yoktu; bu yüzden OpenAPI, değiştirilmiş sürümüne kendi `example` alanını ekledi. + +OpenAPI ayrıca spesifikasyonun diğer bölümlerine de `example` ve `examples` alanlarını ekledi: + +* `Parameter Object` (spesifikasyonda) — FastAPI’de şunlar tarafından kullanılıyordu: + * `Path()` + * `Query()` + * `Header()` + * `Cookie()` +* `Request Body Object`; `content` alanında, `Media Type Object` üzerinde (spesifikasyonda) — FastAPI’de şunlar tarafından kullanılıyordu: + * `Body()` + * `File()` + * `Form()` + +/// info | Bilgi + +Bu eski OpenAPI’ye özel `examples` parametresi, FastAPI `0.103.0` sürümünden beri `openapi_examples` olarak kullanılıyor. + +/// + +### JSON Schema’nın `examples` alanı { #json-schemas-examples-field } + +Sonrasında JSON Schema, spesifikasyonun yeni bir sürümüne `examples` alanını ekledi. + +Ardından yeni OpenAPI 3.1.0, bu yeni `examples` alanını içeren en güncel sürümü (JSON Schema 2020-12) temel aldı. + +Ve artık, deprecated olan eski tekil (ve özel) `example` alanına kıyasla bu yeni `examples` alanı önceliklidir. + +JSON Schema’daki bu yeni `examples` alanı, OpenAPI’de başka yerlerde kullanılan (yukarıda anlatılan) metadata’lı `dict` yapısından farklı olarak **sadece örneklerden oluşan bir `list`**’tir. + +/// info | Bilgi + +OpenAPI 3.1.0, JSON Schema ile bu yeni ve daha basit entegrasyonla yayımlandıktan sonra bile bir süre, otomatik dokümantasyonu sağlayan araç Swagger UI OpenAPI 3.1.0’ı desteklemiyordu (5.0.0 sürümünden beri destekliyor 🎉). + +Bu nedenle, FastAPI’nin 0.99.0 öncesi sürümleri OpenAPI 3.1.0’dan daha düşük sürümleri kullanmaya devam etti. + +/// + +### Pydantic ve FastAPI `examples` { #pydantic-and-fastapi-examples } + +Bir Pydantic modelinin içine `schema_extra` ya da `Field(examples=["something"])` kullanarak `examples` eklediğinizde, bu örnek o Pydantic modelinin **JSON Schema**’sına eklenir. + +Ve Pydantic modelinin bu **JSON Schema**’sı, API’nizin **OpenAPI**’sine dahil edilir; ardından doküman arayüzünde kullanılır. + +FastAPI 0.99.0’dan önceki sürümlerde (0.99.0 ve üzeri daha yeni OpenAPI 3.1.0’ı kullanır) `Query()`, `Body()` vb. diğer araçlarla `example` veya `examples` kullandığınızda, bu örnekler o veriyi tanımlayan JSON Schema’ya (OpenAPI’nin kendi JSON Schema sürümüne bile) eklenmiyordu; bunun yerine doğrudan OpenAPI’deki *path operation* tanımına ekleniyordu (JSON Schema kullanan OpenAPI bölümlerinin dışında). + +Ancak artık FastAPI 0.99.0 ve üzeri OpenAPI 3.1.0 kullandığı (JSON Schema 2020-12) ve Swagger UI 5.0.0 ve üzeriyle birlikte, her şey daha tutarlı ve örnekler JSON Schema’ya dahil ediliyor. + +### Swagger UI ve OpenAPI’ye özel `examples` { #swagger-ui-and-openapi-specific-examples } + +Swagger UI (2023-08-26 itibarıyla) birden fazla JSON Schema örneğini desteklemediği için, kullanıcıların dokümanlarda birden fazla örnek göstermesi mümkün değildi. + +Bunu çözmek için FastAPI `0.103.0`, yeni `openapi_examples` parametresiyle aynı eski **OpenAPI’ye özel** `examples` alanını tanımlamayı **desteklemeye başladı**. 🤓 + +### Özet { #summary } + +Eskiden tarihten pek hoşlanmadığımı söylerdim... şimdi bakın, "teknoloji tarihi" dersi anlatıyorum. 😅 + +Kısacası, **FastAPI 0.99.0 veya üzerine yükseltin**; her şey çok daha **basit, tutarlı ve sezgisel** olur ve bu tarihsel detayların hiçbirini bilmeniz gerekmez. 😎 diff --git a/docs/tr/docs/tutorial/security/first-steps.md b/docs/tr/docs/tutorial/security/first-steps.md new file mode 100644 index 000000000..7e0a70a02 --- /dev/null +++ b/docs/tr/docs/tutorial/security/first-steps.md @@ -0,0 +1,203 @@ +# Güvenlik - İlk Adımlar { #security-first-steps } + +**backend** API’nizin bir domain’de olduğunu düşünelim. + +Ve başka bir domain’de ya da aynı domain’in farklı bir path’inde (veya bir mobil uygulamada) bir **frontend**’iniz var. + +Ve frontend’in, **username** ve **password** kullanarak backend ile kimlik doğrulaması yapabilmesini istiyorsunuz. + +Bunu **FastAPI** ile **OAuth2** kullanarak oluşturabiliriz. + +Ama ihtiyacınız olan küçük bilgi parçalarını bulmak için uzun spesifikasyonun tamamını okuma zahmetine girmeyelim. + +Güvenliği yönetmek için **FastAPI**’nin sunduğu araçları kullanalım. + +## Nasıl Görünüyor { #how-it-looks } + +Önce kodu kullanıp nasıl çalıştığına bakalım, sonra neler olup bittiğini anlamak için geri döneriz. + +## `main.py` Oluşturun { #create-main-py } + +Örneği `main.py` adlı bir dosyaya kopyalayın: + +{* ../../docs_src/security/tutorial001_an_py39.py *} + +## Çalıştırın { #run-it } + +/// info | Bilgi + +`python-multipart` paketi, `pip install "fastapi[standard]"` komutunu çalıştırdığınızda **FastAPI** ile birlikte otomatik olarak kurulur. + +Ancak `pip install fastapi` komutunu kullanırsanız, `python-multipart` paketi varsayılan olarak dahil edilmez. + +Elle kurmak için bir [virtual environment](../../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu aktive ettiğinizden emin olun ve ardından şununla kurun: + +```console +$ pip install python-multipart +``` + +Bunun nedeni, **OAuth2**’nin `username` ve `password` göndermek için "form data" kullanmasıdır. + +/// + +Örneği şu şekilde çalıştırın: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +## Kontrol Edin { #check-it } + +Etkileşimli dokümantasyona gidin: http://127.0.0.1:8000/docs. + +Şuna benzer bir şey göreceksiniz: + + + +/// check | Authorize butonu! + +Artık parıl parıl yeni bir "Authorize" butonunuz var. + +Ayrıca *path operation*’ınızın sağ üst köşesinde tıklayabileceğiniz küçük bir kilit simgesi de bulunuyor. + +/// + +Ve ona tıklarsanız, `username` ve `password` (ve diğer opsiyonel alanları) girebileceğiniz küçük bir yetkilendirme formu görürsünüz: + + + +/// note | Not + +Formda ne yazdığınızın önemi yok; şimdilik çalışmayacak. Ama birazdan oraya da geleceğiz. + +/// + +Bu, elbette son kullanıcılar için bir frontend değil; ancak tüm API’nizi etkileşimli şekilde belgelemek için harika bir otomatik araçtır. + +Frontend ekibi tarafından kullanılabilir (bu ekip siz de olabilirsiniz). + +Üçüncü taraf uygulamalar ve sistemler tarafından kullanılabilir. + +Ve aynı uygulamayı debug etmek, kontrol etmek ve test etmek için sizin tarafınızdan da kullanılabilir. + +## `password` Flow { #the-password-flow } + +Şimdi biraz geri dönüp bunların ne olduğuna bakalım. + +`password` "flow"u, OAuth2’de güvenlik ve authentication’ı yönetmek için tanımlanmış yöntemlerden ("flow"lardan) biridir. + +OAuth2, backend’in veya API’nin, kullanıcıyı authenticate eden server’dan bağımsız olabilmesi için tasarlanmıştır. + +Ancak bu örnekte, aynı **FastAPI** uygulaması hem API’yi hem de authentication’ı yönetecek. + +O yüzden basitleştirilmiş bu bakış açısından üzerinden geçelim: + +* Kullanıcı frontend’de `username` ve `password` yazar ve `Enter`’a basar. +* Frontend (kullanıcının browser’ında çalışır), bu `username` ve `password` değerlerini API’mizdeki belirli bir URL’ye gönderir (`tokenUrl="token"` ile tanımlanan). +* API, `username` ve `password` değerlerini kontrol eder ve bir "token" ile response döner (henüz bunların hiçbirini implement etmedik). + * "Token", daha sonra bu kullanıcıyı doğrulamak için kullanabileceğimiz içerik taşıyan bir string’dir. + * Normalde token’ın bir süre sonra süresi dolacak şekilde ayarlanması beklenir. + * Böylece kullanıcının bir noktada tekrar giriş yapması gerekir. + * Ayrıca token çalınırsa risk daha düşük olur. Çoğu durumda, sonsuza kadar çalışacak kalıcı bir anahtar gibi değildir. +* Frontend bu token’ı geçici olarak bir yerde saklar. +* Kullanıcı frontend’de tıklayarak web uygulamasının başka bir bölümüne gider. +* Frontend’in API’den daha fazla veri alması gerekir. + * Ancak o endpoint için authentication gereklidir. + * Bu yüzden API’mizle authenticate olmak için `Authorization` header’ını, `Bearer ` + token değeriyle gönderir. + * Token `foobar` içeriyorsa `Authorization` header’ının içeriği `Bearer foobar` olur. + +## **FastAPI**’nin `OAuth2PasswordBearer`’ı { #fastapis-oauth2passwordbearer } + +**FastAPI**, bu güvenlik özelliklerini implement etmek için farklı soyutlama seviyelerinde çeşitli araçlar sağlar. + +Bu örnekte **OAuth2**’yi, **Password** flow ile, **Bearer** token kullanarak uygulayacağız. Bunu `OAuth2PasswordBearer` sınıfı ile yaparız. + +/// info | Bilgi + +"Bearer" token tek seçenek değildir. + +Ama bizim kullanım senaryomuz için en iyi seçenek odur. + +Ayrıca bir OAuth2 uzmanı değilseniz ve ihtiyaçlarınıza daha uygun başka bir seçeneğin neden gerekli olduğunu net olarak bilmiyorsanız, çoğu kullanım senaryosu için de en uygun seçenek olacaktır. + +Bu durumda bile **FastAPI**, onu oluşturabilmeniz için gereken araçları sunar. + +/// + +`OAuth2PasswordBearer` sınıfının bir instance’ını oluştururken `tokenUrl` parametresini veririz. Bu parametre, client’ın (kullanıcının browser’ında çalışan frontend’in) token almak için `username` ve `password` göndereceği URL’yi içerir. + +{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} + +/// tip | İpucu + +Burada `tokenUrl="token"`, henüz oluşturmadığımız göreli bir URL olan `token`’ı ifade eder. Göreli URL olduğu için `./token` ile eşdeğerdir. + +Göreli URL kullandığımız için, API’niz `https://example.com/` adresinde olsaydı `https://example.com/token` anlamına gelirdi. Ama API’niz `https://example.com/api/v1/` adresinde olsaydı, bu kez `https://example.com/api/v1/token` anlamına gelirdi. + +Göreli URL kullanmak, [Behind a Proxy](../../advanced/behind-a-proxy.md){.internal-link target=_blank} gibi daha ileri kullanım senaryolarında bile uygulamanızın çalışmaya devam etmesini garanti etmek açısından önemlidir. + +/// + +Bu parametre o endpoint’i / *path operation*’ı oluşturmaz; fakat `/token` URL’sinin client’ın token almak için kullanması gereken URL olduğunu bildirir. Bu bilgi OpenAPI’de, dolayısıyla etkileşimli API dokümantasyon sistemlerinde kullanılır. + +Birazdan gerçek path operation’ı da oluşturacağız. + +/// info | Teknik Detaylar + +Eğer çok katı bir "Pythonista" iseniz, `token_url` yerine `tokenUrl` şeklindeki parametre adlandırma stilini sevmeyebilirsiniz. + +Bunun nedeni, OpenAPI spesifikasyonundaki isimle aynı adın kullanılmasıdır. Böylece bu güvenlik şemalarından herhangi biri hakkında daha fazla araştırma yapmanız gerekirse, adı kopyalayıp yapıştırarak kolayca daha fazla bilgi bulabilirsiniz. + +/// + +`oauth2_scheme` değişkeni, `OAuth2PasswordBearer`’ın bir instance’ıdır; ama aynı zamanda "callable"dır. + +Şu şekilde çağrılabilir: + +```Python +oauth2_scheme(some, parameters) +``` + +Dolayısıyla `Depends` ile kullanılabilir. + +### Kullanın { #use-it } + +Artık `Depends` ile bir dependency olarak `oauth2_scheme`’i geçebilirsiniz. + +{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} + +Bu dependency, *path operation function* içindeki `token` parametresine atanacak bir `str` sağlar. + +**FastAPI**, bu dependency’yi OpenAPI şemasında (ve otomatik API dokümanlarında) bir "security scheme" tanımlamak için kullanabileceğini bilir. + +/// info | Teknik Detaylar + +**FastAPI**, bir dependency içinde tanımlanan `OAuth2PasswordBearer` sınıfını OpenAPI’de security scheme tanımlamak için kullanabileceğini bilir; çünkü bu sınıf `fastapi.security.oauth2.OAuth2`’den kalıtım alır, o da `fastapi.security.base.SecurityBase`’den kalıtım alır. + +OpenAPI (ve otomatik API dokümanları) ile entegre olan tüm security araçları `SecurityBase`’den kalıtım alır; **FastAPI** bu sayede onları OpenAPI’ye nasıl entegre edeceğini anlayabilir. + +/// + +## Ne Yapar { #what-it-does } + +Request içinde `Authorization` header’ını arar, değerin `Bearer ` + bir token olup olmadığını kontrol eder ve token’ı `str` olarak döndürür. + +Eğer `Authorization` header’ını görmezse ya da değer `Bearer ` token’ı içermiyorsa, doğrudan 401 status code hatasıyla (`UNAUTHORIZED`) response döner. + +Token’ın var olup olmadığını kontrol edip ayrıca hata döndürmenize bile gerek yoktur. Fonksiyonunuz çalışıyorsa, token içinde bir `str` olacağından emin olabilirsiniz. + +Bunu şimdiden etkileşimli dokümanlarda deneyebilirsiniz: + + + +Henüz token’ın geçerliliğini doğrulamıyoruz, ama başlangıç için bu bile yeterli. + +## Özet { #recap } + +Yani sadece 3 veya 4 ekstra satırla, şimdiden ilkel de olsa bir güvenlik katmanı elde etmiş oldunuz. diff --git a/docs/tr/docs/tutorial/security/get-current-user.md b/docs/tr/docs/tutorial/security/get-current-user.md new file mode 100644 index 000000000..9f56c7628 --- /dev/null +++ b/docs/tr/docs/tutorial/security/get-current-user.md @@ -0,0 +1,105 @@ +# Mevcut Kullanıcıyı Alma { #get-current-user } + +Önceki bölümde güvenlik sistemi (dependency injection sistemine dayanır) *path operation function*'a `str` olarak bir `token` veriyordu: + +{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} + +Ancak bu hâlâ pek kullanışlı değil. + +Bize mevcut kullanıcıyı verecek şekilde düzenleyelim. + +## Bir kullanıcı modeli oluşturun { #create-a-user-model } + +Önce bir Pydantic kullanıcı modeli oluşturalım. + +Body'leri bildirmek için Pydantic'i nasıl kullanıyorsak, aynı şekilde onu başka her yerde de kullanabiliriz: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *} + +## `get_current_user` dependency'si oluşturun { #create-a-get-current-user-dependency } + +Bir `get_current_user` dependency'si oluşturalım. + +Dependency'lerin alt dependency'leri olabileceğini hatırlıyor musunuz? + +`get_current_user`, daha önce oluşturduğumuz `oauth2_scheme` ile aynı dependency'yi kullanacak. + +Daha önce *path operation* içinde doğrudan yaptığımız gibi, yeni dependency'miz `get_current_user`, alt dependency olan `oauth2_scheme` üzerinden `str` olarak bir `token` alacak: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *} + +## Kullanıcıyı alın { #get-the-user } + +`get_current_user`, oluşturduğumuz (sahte) bir yardımcı (utility) fonksiyonu kullanacak; bu fonksiyon `str` olarak bir token alır ve Pydantic `User` modelimizi döndürür: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *} + +## Mevcut kullanıcıyı enjekte edin { #inject-the-current-user } + +Artık *path operation* içinde `get_current_user` ile aynı `Depends` yaklaşımını kullanabiliriz: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *} + +`current_user` tipini Pydantic `User` modeli olarak belirttiğimize dikkat edin. + +Bu sayede fonksiyonun içinde otomatik tamamlama ve tip kontrollerinin tamamından faydalanırız. + +/// tip | İpucu + +Request body'lerinin de Pydantic modelleri ile bildirildiğini hatırlıyor olabilirsiniz. + +Burada `Depends` kullandığınız için **FastAPI** karışıklık yaşamaz. + +/// + +/// check | Ek bilgi + +Bu dependency sisteminin tasarımı, hepsi `User` modeli döndüren farklı dependency'lere (farklı "dependable"lara) sahip olmamıza izin verir. + +Bu tipte veri döndürebilen yalnızca tek bir dependency ile sınırlı değiliz. + +/// + +## Diğer modeller { #other-models } + +Artık *path operation function* içinde mevcut kullanıcıyı doğrudan alabilir ve güvenlik mekanizmalarını `Depends` kullanarak **Dependency Injection** seviyesinde yönetebilirsiniz. + +Ayrıca güvenlik gereksinimleri için herhangi bir model veya veri kullanabilirsiniz (bu örnekte bir Pydantic `User` modeli). + +Ancak belirli bir data model, class ya da type kullanmak zorunda değilsiniz. + +Modelinizde bir `id` ve `email` olsun, ama `username` olmasın mı istiyorsunuz? Elbette. Aynı araçları kullanabilirsiniz. + +Sadece bir `str` mı istiyorsunuz? Ya da sadece bir `dict`? Veya doğrudan bir veritabanı class model instance'ı? Hepsi aynı şekilde çalışır. + +Uygulamanıza giriş yapan kullanıcılar yok da robotlar, botlar veya yalnızca bir access token'a sahip başka sistemler mi var? Yine, her şey aynı şekilde çalışır. + +Uygulamanız için neye ihtiyacınız varsa o türden bir model, class ve veritabanı kullanın. **FastAPI**, dependency injection sistemiyle bunları destekler. + +## Kod boyutu { #code-size } + +Bu örnek biraz uzun görünebilir. Güvenlik, data model'ler, utility fonksiyonlar ve *path operation*'ları aynı dosyada bir araya getirdiğimizi unutmayın. + +Ama kritik nokta şu: + +Güvenlik ve dependency injection tarafını bir kez yazarsınız. + +İstediğiniz kadar karmaşık hâle getirebilirsiniz. Yine de hepsi tek bir yerde ve sadece bir kez yazılmış olur. Üstelik tüm esneklikle. + +Sonrasında aynı güvenlik sistemini kullanan binlerce endpoint (*path operation*) olabilir. + +Ve bunların hepsi (ya da istediğiniz bir kısmı) bu dependency'leri veya oluşturacağınız başka dependency'leri yeniden kullanmaktan faydalanabilir. + +Hatta bu binlerce *path operation* 3 satır kadar kısa olabilir: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *} + +## Özet { #recap } + +Artık *path operation function* içinde mevcut kullanıcıyı doğrudan alabilirsiniz. + +Yolun yarısına geldik. + +Kullanıcının/istemcinin gerçekten `username` ve `password` göndermesini sağlayacak bir *path operation* eklememiz gerekiyor. + +Sırada bu var. diff --git a/docs/tr/docs/tutorial/security/index.md b/docs/tr/docs/tutorial/security/index.md new file mode 100644 index 000000000..a592db6df --- /dev/null +++ b/docs/tr/docs/tutorial/security/index.md @@ -0,0 +1,106 @@ +# Güvenlik { #security } + +Güvenlik, authentication ve authorization’ı ele almanın birçok yolu vardır. + +Ve bu konu genellikle karmaşık ve "zor"dur. + +Birçok framework ve sistemde yalnızca security ve authentication’ı yönetmek bile ciddi miktarda emek ve kod gerektirir (çoğu durumda yazılan toplam kodun %50’si veya daha fazlası olabilir). + +**FastAPI**, tüm security spesifikasyonlarını baştan sona inceleyip öğrenmek zorunda kalmadan **Security** konusunu kolay, hızlı ve standart bir şekilde ele almanıza yardımcı olacak çeşitli araçlar sunar. + +Ama önce, küçük birkaç kavrama bakalım. + +## Acelem var? { #in-a-hurry } + +Bu terimlerin hiçbirini umursamıyorsanız ve sadece kullanıcı adı ve parola tabanlı authentication ile security’yi *hemen şimdi* eklemeniz gerekiyorsa, bir sonraki bölümlere geçin. + +## OAuth2 { #oauth2 } + +OAuth2, authentication ve authorization’ı yönetmek için çeşitli yöntemleri tanımlayan bir spesifikasyondur. + +Oldukça kapsamlı bir spesifikasyondur ve birkaç karmaşık use case’i kapsar. + +"Üçüncü taraf" kullanarak authentication yapmanın yollarını da içerir. + +"Facebook, Google, X (Twitter), GitHub ile giriş yap" bulunan sistemlerin arka planda kullandığı şey de budur. + +### OAuth 1 { #oauth-1 } + +OAuth 1 de vardı; OAuth2’den çok farklıdır ve daha karmaşıktır, çünkü iletişimi nasıl şifreleyeceğinize dair doğrudan spesifikasyonlar içeriyordu. + +Günümüzde pek popüler değildir veya pek kullanılmaz. + +OAuth2 ise iletişimin nasıl şifreleneceğini belirtmez; uygulamanızın HTTPS ile sunulmasını bekler. + +/// tip | İpucu + +**deployment** bölümünde Traefik ve Let's Encrypt kullanarak ücretsiz şekilde HTTPS’i nasıl kuracağınızı göreceksiniz. + +/// + +## OpenID Connect { #openid-connect } + +OpenID Connect, **OAuth2** tabanlı başka bir spesifikasyondur. + +OAuth2’de nispeten belirsiz kalan bazı noktaları tanımlayarak onu daha birlikte çalışabilir (interoperable) hâle getirmeye çalışır. + +Örneğin, Google ile giriş OpenID Connect kullanır (arka planda OAuth2 kullanır). + +Ancak Facebook ile giriş OpenID Connect’i desteklemez. Kendine özgü bir OAuth2 çeşidi vardır. + +### OpenID ("OpenID Connect" değil) { #openid-not-openid-connect } + +Bir de "OpenID" spesifikasyonu vardı. Bu da **OpenID Connect** ile aynı problemi çözmeye çalışıyordu ama OAuth2 tabanlı değildi. + +Dolayısıyla tamamen ayrı, ek bir sistemdi. + +Günümüzde pek popüler değildir veya pek kullanılmaz. + +## OpenAPI { #openapi } + +OpenAPI (önceden Swagger olarak biliniyordu), API’ler inşa etmek için açık spesifikasyondur (artık Linux Foundation’ın bir parçası). + +**FastAPI**, **OpenAPI** tabanlıdır. + +Bu sayede birden fazla otomatik etkileşimli dokümantasyon arayüzü, code generation vb. mümkün olur. + +OpenAPI, birden fazla security "scheme" tanımlamanın bir yolunu sunar. + +Bunları kullanarak, etkileşimli dokümantasyon sistemleri de dahil olmak üzere tüm bu standart tabanlı araçlardan faydalanabilirsiniz. + +OpenAPI şu security scheme’lerini tanımlar: + +* `apiKey`: uygulamaya özel bir anahtar; şuradan gelebilir: + * Bir query parameter. + * Bir header. + * Bir cookie. +* `http`: standart HTTP authentication sistemleri, örneğin: + * `bearer`: `Authorization` header’ı; değeri `Bearer ` + bir token olacak şekilde. Bu, OAuth2’den gelir. + * HTTP Basic authentication. + * HTTP Digest, vb. +* `oauth2`: OAuth2 ile security’yi yönetmenin tüm yolları ("flow" olarak adlandırılır). + * Bu flow’ların birçoğu, bir OAuth 2.0 authentication provider (Google, Facebook, X (Twitter), GitHub vb.) oluşturmak için uygundur: + * `implicit` + * `clientCredentials` + * `authorizationCode` + * Ancak, aynı uygulamanın içinde doğrudan authentication yönetmek için mükemmel şekilde kullanılabilecek özel bir "flow" vardır: + * `password`: sonraki bazı bölümlerde bunun örnekleri ele alınacak. +* `openIdConnect`: OAuth2 authentication verisinin otomatik olarak nasıl keşfedileceğini tanımlamanın bir yolunu sunar. + * Bu otomatik keşif, OpenID Connect spesifikasyonunda tanımlanan şeydir. + + +/// tip | İpucu + +Google, Facebook, X (Twitter), GitHub vb. gibi diğer authentication/authorization provider’larını entegre etmek de mümkündür ve nispeten kolaydır. + +En karmaşık kısım, bu tür bir authentication/authorization provider’ı inşa etmektir; ancak **FastAPI** ağır işleri sizin yerinize yaparken bunu kolayca yapabilmeniz için araçlar sunar. + +/// + +## **FastAPI** yardımcı araçları { #fastapi-utilities } + +FastAPI, `fastapi.security` modülünde bu security scheme’lerinin her biri için, bu mekanizmaları kullanmayı kolaylaştıran çeşitli araçlar sağlar. + +Sonraki bölümlerde, **FastAPI**’nin sunduğu bu araçları kullanarak API’nize nasıl security ekleyeceğinizi göreceksiniz. + +Ayrıca bunun etkileşimli dokümantasyon sistemine nasıl otomatik olarak entegre edildiğini de göreceksiniz. diff --git a/docs/tr/docs/tutorial/security/oauth2-jwt.md b/docs/tr/docs/tutorial/security/oauth2-jwt.md new file mode 100644 index 000000000..716761157 --- /dev/null +++ b/docs/tr/docs/tutorial/security/oauth2-jwt.md @@ -0,0 +1,273 @@ +# Password ile OAuth2 (ve hashing), JWT token'ları ile Bearer { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens } + +Artık tüm security flow elimizde olduğuna göre, uygulamayı gerçekten güvenli hâle getirelim: JWT token'ları ve güvenli password hashing kullanacağız. + +Bu kodu uygulamanızda gerçekten kullanabilirsiniz; password hash'lerini veritabanınıza kaydedebilirsiniz, vb. + +Bir önceki bölümde bıraktığımız yerden başlayıp üzerine ekleyerek ilerleyeceğiz. + +## JWT Hakkında { #about-jwt } + +JWT, "JSON Web Tokens" anlamına gelir. + +Bir JSON nesnesini, boşluk içermeyen uzun ve yoğun bir string'e kodlamak için kullanılan bir standarttır. Şuna benzer: + +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +``` + +Şifrelenmiş değildir; yani herkes içeriğindeki bilgiyi geri çıkarabilir. + +Ancak imzalanmıştır. Bu yüzden, sizin ürettiğiniz bir token'ı aldığınızda, gerçekten onu sizin ürettiğinizi doğrulayabilirsiniz. + +Bu şekilde, örneğin 1 haftalık süre sonu (expiration) olan bir token oluşturabilirsiniz. Sonra kullanıcı ertesi gün token ile geri geldiğinde, kullanıcının hâlâ sisteminizde oturum açmış olduğunu bilirsiniz. + +Bir hafta sonra token'ın süresi dolar; kullanıcı yetkilendirilmez ve yeni bir token almak için tekrar giriş yapmak zorunda kalır. Ayrıca kullanıcı (veya üçüncü bir taraf) token'ı değiştirip süre sonunu farklı göstermek isterse bunu tespit edebilirsiniz; çünkü imzalar eşleşmez. + +JWT token'larıyla oynayıp nasıl çalıştıklarını görmek isterseniz https://jwt.io adresine bakın. + +## `PyJWT` Kurulumu { #install-pyjwt } + +Python'da JWT token'larını üretmek ve doğrulamak için `PyJWT` kurmamız gerekiyor. + +Bir [virtual environment](../../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan emin olun, aktif edin ve ardından `pyjwt` kurun: + +
+ +```console +$ pip install pyjwt + +---> 100% +``` + +
+ +/// info | Bilgi + +RSA veya ECDSA gibi dijital imza algoritmaları kullanmayı planlıyorsanız, `pyjwt[crypto]` bağımlılığı olan `cryptography` kütüphanesini kurmalısınız. + +Daha fazla bilgi için PyJWT Installation docs sayfasını okuyabilirsiniz. + +/// + +## Password hashing { #password-hashing } + +"Hashing", bazı içerikleri (bu örnekte bir password) anlamsız görünen bir bayt dizisine (pratikte bir string) dönüştürmek demektir. + +Aynı içeriği (aynı password'ü) her seferinde verirseniz, her seferinde aynı anlamsız çıktıyı elde edersiniz. + +Ancak bu anlamsız çıktıdan geri password'e dönüştürme yapılamaz. + +### Neden password hashing kullanılır { #why-use-password-hashing } + +Veritabanınız çalınırsa, hırsız kullanıcılarınızın düz metin (plaintext) password'lerini değil, sadece hash'leri elde eder. + +Dolayısıyla, o password'ü başka bir sistemde denemek kolay olmaz (pek çok kullanıcı her yerde aynı password'ü kullandığı için bu tehlikeli olurdu). + +## `pwdlib` Kurulumu { #install-pwdlib } + +pwdlib, password hash'leriyle çalışmak için çok iyi bir Python paketidir. + +Birçok güvenli hashing algoritmasını ve bunlarla çalışmak için yardımcı araçları destekler. + +Önerilen algoritma "Argon2"dir. + +Bir [virtual environment](../../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan emin olun, aktif edin ve sonra Argon2 ile birlikte pwdlib'i kurun: + +
+ +```console +$ pip install "pwdlib[argon2]" + +---> 100% +``` + +
+ +/// tip | İpucu + +`pwdlib` ile, **Django** tarafından oluşturulmuş password'leri, bir **Flask** security eklentisinin ürettiklerini veya başka birçok kaynaktan gelen password'leri okuyabilecek şekilde bile yapılandırabilirsiniz. + +Böylece örneğin bir Django uygulamasındaki verileri aynı veritabanında bir FastAPI uygulamasıyla paylaşabilirsiniz. Ya da aynı veritabanını kullanarak bir Django uygulamasını kademeli şekilde taşıyabilirsiniz. + +Ayrıca kullanıcılarınız, aynı anda hem Django uygulamanızdan hem de **FastAPI** uygulamanızdan login olabilir. + +/// + +## Password'leri hash'leme ve doğrulama { #hash-and-verify-the-passwords } + +Gerekli araçları `pwdlib` içinden import edelim. + +Önerilen ayarlarla bir PasswordHash instance'ı oluşturalım; bunu password'leri hash'lemek ve doğrulamak için kullanacağız. + +/// tip | İpucu + +pwdlib, bcrypt hashing algoritmasını da destekler; ancak legacy algoritmaları içermez. Eski hash'lerle çalışmak için passlib kütüphanesini kullanmanız önerilir. + +Örneğin, başka bir sistemin (Django gibi) ürettiği password'leri okuyup doğrulayabilir, ancak yeni password'leri Argon2 veya Bcrypt gibi farklı bir algoritmayla hash'leyebilirsiniz. + +Ve aynı anda hepsiyle uyumlu kalabilirsiniz. + +/// + +Kullanıcıdan gelen password'ü hash'lemek için bir yardımcı (utility) fonksiyon oluşturalım. + +Sonra, alınan password'ün kayıttaki hash ile eşleşip eşleşmediğini doğrulayan başka bir yardımcı fonksiyon yazalım. + +Bir tane de kullanıcıyı authenticate edip geri döndüren bir yardımcı fonksiyon ekleyelim. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} + +/// note | Not + +Yeni (sahte) veritabanı `fake_users_db`'ye bakarsanız, hash'lenmiş password'ün artık nasıl göründüğünü görebilirsiniz: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`. + +/// + +## JWT token'larını yönetme { #handle-jwt-tokens } + +Kurulu modülleri import edelim. + +JWT token'larını imzalamak için kullanılacak rastgele bir secret key oluşturalım. + +Güvenli, rastgele bir secret key üretmek için şu komutu kullanın: + +
+ +```console +$ openssl rand -hex 32 + +09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 +``` + +
+ +Çıktıyı `SECRET_KEY` değişkenine kopyalayın (örnektekini kullanmayın). + +JWT token'ını imzalamak için kullanılan algoritmayı tutacak `ALGORITHM` adlı bir değişken oluşturup değerini `"HS256"` yapın. + +Token'ın süre sonu (expiration) için bir değişken oluşturun. + +Response için token endpoint'inde kullanılacak bir Pydantic Model tanımlayın. + +Yeni bir access token üretmek için bir yardımcı fonksiyon oluşturun. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} + +## Dependency'leri güncelleme { #update-the-dependencies } + +`get_current_user` fonksiyonunu, öncekiyle aynı token'ı alacak şekilde güncelleyelim; ancak bu sefer JWT token'larını kullanacağız. + +Gelen token'ı decode edin, doğrulayın ve mevcut kullanıcıyı döndürün. + +Token geçersizse, hemen bir HTTP hatası döndürün. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} + +## `/token` *path operation*'ını güncelleme { #update-the-token-path-operation } + +Token'ın süre sonu için bir `timedelta` oluşturun. + +Gerçek bir JWT access token üretip döndürün. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} + +### JWT "subject" `sub` Hakkında Teknik Detaylar { #technical-details-about-the-jwt-subject-sub } + +JWT spesifikasyonu, token'ın konusu (subject) için `sub` adlı bir anahtar olduğunu söyler. + +Bunu kullanmak zorunlu değildir; ancak kullanıcı kimliğini koymak için uygun yer burasıdır, bu yüzden burada onu kullanıyoruz. + +JWT, sadece bir kullanıcıyı tanımlamak ve API'nizde doğrudan işlem yapmasına izin vermek dışında başka amaçlarla da kullanılabilir. + +Örneğin bir "araba"yı veya bir "blog post"u tanımlayabilirsiniz. + +Sonra o varlık için izinler ekleyebilirsiniz; örneğin (araba için) "drive" ya da (blog için) "edit". + +Ardından bu JWT token'ını bir kullanıcıya (veya bot'a) verebilirsiniz; onlar da, hesapları olmasına bile gerek kalmadan, sadece API'nizin bunun için ürettiği JWT token'ıyla bu aksiyonları gerçekleştirebilir (arabayı sürmek veya blog post'u düzenlemek gibi). + +Bu fikirlerle JWT, çok daha gelişmiş senaryolarda kullanılabilir. + +Bu durumlarda, birden fazla varlığın aynı ID'ye sahip olması mümkündür; örneğin `foo` (kullanıcı `foo`, araba `foo`, blog post `foo`). + +Dolayısıyla ID çakışmalarını önlemek için, kullanıcı için JWT token oluştururken `sub` anahtarının değerine bir önek ekleyebilirsiniz; örneğin `username:`. Bu örnekte `sub` değeri şöyle olabilirdi: `username:johndoe`. + +Unutmamanız gereken önemli nokta şudur: `sub` anahtarı, tüm uygulama genelinde benzersiz bir tanımlayıcı olmalı ve string olmalıdır. + +## Kontrol Edelim { #check-it } + +Server'ı çalıştırın ve docs'a gidin: http://127.0.0.1:8000/docs. + +Şuna benzer bir arayüz göreceksiniz: + + + +Uygulamayı, öncekiyle aynı şekilde authorize edin. + +Şu kimlik bilgilerini kullanarak: + +Username: `johndoe` +Password: `secret` + +/// check | Ek bilgi + +Kodun hiçbir yerinde düz metin password "`secret`" yok; sadece hash'lenmiş hâli var. + +/// + + + +`/users/me/` endpoint'ini çağırın; response şöyle olacaktır: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + + + +Developer tools'u açarsanız, gönderilen verinin sadece token'ı içerdiğini görebilirsiniz. Password sadece kullanıcıyı authenticate edip access token almak için yapılan ilk request'te gönderilir, sonrasında gönderilmez: + + + +/// note | Not + +`Authorization` header'ına dikkat edin; değeri `Bearer ` ile başlıyor. + +/// + +## `scopes` ile İleri Seviye Kullanım { #advanced-usage-with-scopes } + +OAuth2'nin "scopes" kavramı vardır. + +Bunları kullanarak bir JWT token'a belirli bir izin seti ekleyebilirsiniz. + +Sonra bu token'ı bir kullanıcıya doğrudan veya bir üçüncü tarafa verip, API'nizle belirli kısıtlarla etkileşime girmesini sağlayabilirsiniz. + +Nasıl kullanıldıklarını ve **FastAPI** ile nasıl entegre olduklarını, ileride **Advanced User Guide** içinde öğreneceksiniz. + +## Özet { #recap } + +Şimdiye kadar gördüklerinizle, OAuth2 ve JWT gibi standartları kullanarak güvenli bir **FastAPI** uygulaması kurabilirsiniz. + +Neredeyse her framework'te security'yi ele almak oldukça hızlı bir şekilde karmaşık bir konu hâline gelir. + +Bunu çok basitleştiren birçok paket, veri modeli, veritabanı ve mevcut özelliklerle ilgili pek çok ödün vermek zorunda kalır. Hatta bazıları işi aşırı basitleştirirken arka planda güvenlik açıkları da barındırır. + +--- + +**FastAPI**, hiçbir veritabanı, veri modeli veya araç konusunda ödün vermez. + +Projenize en uygun olanları seçebilmeniz için size tam esneklik sağlar. + +Ayrıca `pwdlib` ve `PyJWT` gibi iyi bakımı yapılan ve yaygın kullanılan paketleri doğrudan kullanabilirsiniz; çünkü **FastAPI**, haricî paketleri entegre etmek için karmaşık mekanizmalara ihtiyaç duymaz. + +Buna rağmen, esneklikten, sağlamlıktan veya güvenlikten ödün vermeden süreci mümkün olduğunca basitleştiren araçları sağlar. + +Ve OAuth2 gibi güvenli, standart protokolleri nispeten basit bir şekilde kullanabilir ve uygulayabilirsiniz. + +Aynı standartları izleyerek, daha ince taneli (fine-grained) bir izin sistemi için OAuth2 "scopes" kullanımını **Advanced User Guide** içinde daha detaylı öğrenebilirsiniz. Scopes'lu OAuth2; Facebook, Google, GitHub, Microsoft, X (Twitter) vb. pek çok büyük kimlik doğrulama sağlayıcısının, üçüncü taraf uygulamaların kullanıcıları adına API'leriyle etkileşebilmesine izin vermek için kullandığı mekanizmadır. diff --git a/docs/tr/docs/tutorial/security/simple-oauth2.md b/docs/tr/docs/tutorial/security/simple-oauth2.md new file mode 100644 index 000000000..88efd98e5 --- /dev/null +++ b/docs/tr/docs/tutorial/security/simple-oauth2.md @@ -0,0 +1,289 @@ +# Password ve Bearer ile Basit OAuth2 { #simple-oauth2-with-password-and-bearer } + +Şimdi önceki bölümün üzerine inşa edip, eksik parçaları ekleyerek tam bir güvenlik akışı oluşturalım. + +## `username` ve `password`’ü Alma { #get-the-username-and-password } + +`username` ve `password`’ü almak için **FastAPI** security yardımcı araçlarını kullanacağız. + +OAuth2, (bizim kullandığımız) "password flow" kullanılırken client/kullanıcının form verisi olarak `username` ve `password` alanlarını göndermesi gerektiğini belirtir. + +Ayrıca spesifikasyon, bu alanların adlarının tam olarak böyle olması gerektiğini söyler. Yani `user-name` veya `email` işe yaramaz. + +Ancak merak etmeyin, frontend’de son kullanıcılarınıza dilediğiniz gibi gösterebilirsiniz. + +Veritabanı model(ler)inizde de istediğiniz başka isimleri kullanabilirsiniz. + +Fakat login *path operation*’ı için, spesifikasyonla uyumlu olmak (ve örneğin entegre API dokümantasyon sistemini kullanabilmek) adına bu isimleri kullanmamız gerekiyor. + +Spesifikasyon ayrıca `username` ve `password`’ün form verisi olarak gönderilmesi gerektiğini de söyler (yani burada JSON yok). + +### `scope` { #scope } + +Spesifikasyon, client’ın "`scope`" adlı başka bir form alanı da gönderebileceğini söyler. + +Form alanının adı `scope`’tur (tekil), ama aslında boşluklarla ayrılmış "scope"’lardan oluşan uzun bir string’dir. + +Her bir "scope" sadece bir string’dir (boşluk içermez). + +Genelde belirli güvenlik izinlerini (permission) belirtmek için kullanılırlar, örneğin: + +* `users:read` veya `users:write` yaygın örneklerdir. +* `instagram_basic` Facebook / Instagram tarafından kullanılır. +* `https://www.googleapis.com/auth/drive` Google tarafından kullanılır. + +/// info | Bilgi + +OAuth2’de bir "scope", gerekli olan belirli bir izni ifade eden basit bir string’dir. + +`:` gibi başka karakterler içermesi veya URL olması önemli değildir. + +Bu detaylar implementasyon’a özeldir. + +OAuth2 açısından bunlar sadece string’lerdir. + +/// + +## `username` ve `password`’ü Almak İçin Kod { #code-to-get-the-username-and-password } + +Şimdi bunu yönetmek için **FastAPI**’nin sağladığı araçları kullanalım. + +### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform } + +Önce `OAuth2PasswordRequestForm`’u import edin ve `/token` için *path operation* içinde `Depends` ile dependency olarak kullanın: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *} + +`OAuth2PasswordRequestForm`, şu alanları içeren bir form body tanımlayan bir class dependency’sidir: + +* `username`. +* `password`. +* Boşlukla ayrılmış string’lerden oluşan büyük bir string olarak opsiyonel `scope` alanı. +* Opsiyonel `grant_type`. + +/// tip | İpucu + +OAuth2 spesifikasyonu aslında `grant_type` alanını sabit bir `password` değeriyle *zorunlu kılar*, ancak `OAuth2PasswordRequestForm` bunu zorlamaz. + +Bunu zorlamak istiyorsanız, `OAuth2PasswordRequestForm` yerine `OAuth2PasswordRequestFormStrict` kullanın. + +/// + +* Opsiyonel `client_id` (bu örnekte ihtiyacımız yok). +* Opsiyonel `client_secret` (bu örnekte ihtiyacımız yok). + +/// info | Bilgi + +`OAuth2PasswordRequestForm`, `OAuth2PasswordBearer` gibi **FastAPI**’ye özel “özel bir sınıf” değildir. + +`OAuth2PasswordBearer`, bunun bir security scheme olduğunu **FastAPI**’ye bildirir. Bu yüzden OpenAPI’ye o şekilde eklenir. + +Ama `OAuth2PasswordRequestForm` sadece bir class dependency’dir; bunu kendiniz de yazabilirdiniz ya da doğrudan `Form` parametreleri tanımlayabilirdiniz. + +Fakat çok yaygın bir kullanım olduğu için **FastAPI** bunu işleri kolaylaştırmak adına doğrudan sağlar. + +/// + +### Form Verisini Kullanma { #use-the-form-data } + +/// tip | İpucu + +`OAuth2PasswordRequestForm` dependency class’ının instance’ında boşluklarla ayrılmış uzun string olarak bir `scope` attribute’u olmaz; bunun yerine gönderilen her scope için gerçek string listesini içeren `scopes` attribute’u olur. + +Bu örnekte `scopes` kullanmıyoruz, ama ihtiyacınız olursa bu özellik hazır. + +/// + +Şimdi form alanındaki `username`’i kullanarak (sahte) veritabanından kullanıcı verisini alın. + +Böyle bir kullanıcı yoksa, "Incorrect username or password" diyerek bir hata döndürelim. + +Hata için `HTTPException` exception’ını kullanıyoruz: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *} + +### Password’ü Kontrol Etme { #check-the-password } + +Bu noktada veritabanından kullanıcı verisine sahibiz, ancak password’ü henüz kontrol etmedik. + +Önce bu veriyi Pydantic `UserInDB` modeline koyalım. + +Asla düz metin (plaintext) password kaydetmemelisiniz; bu yüzden (sahte) password hashing sistemini kullanacağız. + +Password’ler eşleşmezse, aynı hatayı döndürürüz. + +#### Password hashing { #password-hashing } + +"Hashing" şudur: bir içeriği (bu örnekte password) anlaşılmaz görünen bayt dizisine (yani bir string’e) dönüştürmek. + +Aynı içeriği (aynı password’ü) her verdiğinizde, birebir aynı anlamsız görünen çıktıyı elde edersiniz. + +Ama bu anlamsız çıktıyı tekrar password’e geri çeviremezsiniz. + +##### Neden password hashing kullanılır { #why-use-password-hashing } + +Veritabanınız çalınırsa, hırsız kullanıcılarınızın düz metin password’lerine değil, sadece hash’lere sahip olur. + +Dolayısıyla hırsız, aynı password’leri başka bir sistemde denemeye çalışamaz (birçok kullanıcı her yerde aynı password’ü kullandığı için bu tehlikeli olurdu). + +{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *} + +#### `**user_dict` Hakkında { #about-user-dict } + +`UserInDB(**user_dict)` şu anlama gelir: + +*`user_dict` içindeki key ve value’ları doğrudan key-value argümanları olarak geçir; şu ifadeyle eşdeğerdir:* + +```Python +UserInDB( + username = user_dict["username"], + email = user_dict["email"], + full_name = user_dict["full_name"], + disabled = user_dict["disabled"], + hashed_password = user_dict["hashed_password"], +) +``` + +/// info | Bilgi + +`**user_dict` için daha kapsamlı bir açıklama için [**Extra Models** dokümantasyonundaki ilgili bölüme](../extra-models.md#about-user-in-dict){.internal-link target=_blank} geri dönüp bakın. + +/// + +## Token’ı Döndürme { #return-the-token } + +`token` endpoint’inin response’u bir JSON object olmalıdır. + +Bir `token_type` içermelidir. Biz "Bearer" token’ları kullandığımız için token type "`bearer`" olmalıdır. + +Ayrıca `access_token` içermelidir; bunun değeri access token’ımızı içeren bir string olmalıdır. + +Bu basit örnekte tamamen güvensiz davranıp token olarak aynı `username`’i döndüreceğiz. + +/// tip | İpucu + +Bir sonraki bölümde, password hashing ve JWT token’ları ile gerçekten güvenli bir implementasyon göreceksiniz. + +Ama şimdilik ihtiyacımız olan spesifik detaylara odaklanalım. + +/// + +{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *} + +/// tip | İpucu + +Spesifikasyona göre, bu örnekteki gibi `access_token` ve `token_type` içeren bir JSON döndürmelisiniz. + +Bunu kendi kodunuzda kendiniz yapmalı ve bu JSON key’lerini kullandığınızdan emin olmalısınız. + +Spesifikasyonlara uyum için, doğru yapmanız gereken neredeyse tek şey budur. + +Geri kalanını **FastAPI** sizin yerinize yönetir. + +/// + +## Dependency’leri Güncelleme { #update-the-dependencies } + +Şimdi dependency’lerimizi güncelleyeceğiz. + +`current_user`’ı *sadece* kullanıcı aktifse almak istiyoruz. + +Bu yüzden, `get_current_user`’ı dependency olarak kullanan ek bir dependency olan `get_current_active_user`’ı oluşturuyoruz. + +Bu iki dependency de kullanıcı yoksa veya pasifse sadece HTTP hatası döndürecek. + +Dolayısıyla endpoint’imizde kullanıcıyı ancak kullanıcı varsa, doğru şekilde authenticate edildiyse ve aktifse alacağız: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *} + +/// info | Bilgi + +Burada `Bearer` değerine sahip ek `WWW-Authenticate` header’ını döndürmemiz de spesifikasyonun bir parçasıdır. + +Herhangi bir HTTP (hata) durum kodu 401 "UNAUTHORIZED", ayrıca `WWW-Authenticate` header’ı da döndürmelidir. + +Bearer token’lar (bizim durumumuz) için bu header’ın değeri `Bearer` olmalıdır. + +Aslında bu ekstra header’ı atlayabilirsiniz, yine de çalışır. + +Ama spesifikasyonlara uyumlu olması için burada eklenmiştir. + +Ayrıca, bunu bekleyen ve kullanan araçlar olabilir (şimdi veya ileride) ve bu da sizin ya da kullanıcılarınız için faydalı olabilir. + +Standartların faydası da bu... + +/// + +## Çalışır Halini Görün { #see-it-in-action } + +Etkileşimli dokümanları açın: http://127.0.0.1:8000/docs. + +### Authenticate Olma { #authenticate } + +"Authorize" butonuna tıklayın. + +Şu bilgileri kullanın: + +User: `johndoe` + +Password: `secret` + + + +Sistemde authenticate olduktan sonra şöyle görürsünüz: + + + +### Kendi Kullanıcı Verinizi Alma { #get-your-own-user-data } + +Şimdi `/users/me` path’inde `GET` operasyonunu kullanın. + +Kullanıcınızın verisini şöyle alırsınız: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" +} +``` + + + +Kilit ikonuna tıklayıp logout olursanız ve sonra aynı operasyonu tekrar denerseniz, şu şekilde bir HTTP 401 hatası alırsınız: + +```JSON +{ + "detail": "Not authenticated" +} +``` + +### Pasif Kullanıcı { #inactive-user } + +Şimdi pasif bir kullanıcıyla deneyin; şu bilgilerle authenticate olun: + +User: `alice` + +Password: `secret2` + +Ve `/users/me` path’inde `GET` operasyonunu kullanmayı deneyin. + +Şöyle bir "Inactive user" hatası alırsınız: + +```JSON +{ + "detail": "Inactive user" +} +``` + +## Özet { #recap } + +Artık API’niz için `username` ve `password` tabanlı, eksiksiz bir güvenlik sistemi implement etmek için gerekli araçlara sahipsiniz. + +Bu araçlarla güvenlik sistemini herhangi bir veritabanıyla ve herhangi bir user veya veri modeliyle uyumlu hale getirebilirsiniz. + +Eksik kalan tek detay, bunun henüz gerçekten "güvenli" olmamasıdır. + +Bir sonraki bölümde güvenli bir password hashing kütüphanesini ve JWT token’larını nasıl kullanacağınızı göreceksiniz. diff --git a/docs/tr/docs/tutorial/sql-databases.md b/docs/tr/docs/tutorial/sql-databases.md new file mode 100644 index 000000000..e1638cb04 --- /dev/null +++ b/docs/tr/docs/tutorial/sql-databases.md @@ -0,0 +1,357 @@ +# SQL (İlişkisel) Veritabanları { #sql-relational-databases } + +**FastAPI**, SQL (ilişkisel) bir veritabanı kullanmanızı zorunlu kılmaz. Ancak isterseniz **istediğiniz herhangi bir veritabanını** kullanabilirsiniz. + +Burada SQLModel kullanarak bir örnek göreceğiz. + +**SQLModel**, SQLAlchemy ve Pydantic’in üzerine inşa edilmiştir. **FastAPI**’nin yazarı tarafından, **SQL veritabanları** kullanması gereken FastAPI uygulamalarıyla mükemmel uyum sağlaması için geliştirilmiştir. + +/// tip | İpucu + +İstediğiniz başka bir SQL veya NoSQL veritabanı kütüphanesini kullanabilirsiniz (bazı durumlarda "ORMs" olarak adlandırılır). FastAPI sizi hiçbir şeye zorlamaz. + +/// + +SQLModel, SQLAlchemy tabanlı olduğu için SQLAlchemy’nin **desteklediği herhangi bir veritabanını** kolayca kullanabilirsiniz (bu da SQLModel tarafından da desteklendikleri anlamına gelir), örneğin: + +* PostgreSQL +* MySQL +* SQLite +* Oracle +* Microsoft SQL Server, vb. + +Bu örnekte **SQLite** kullanacağız; çünkü tek bir dosya kullanır ve Python’da yerleşik desteği vardır. Yani bu örneği kopyalayıp olduğu gibi çalıştırabilirsiniz. + +Daha sonra, production uygulamanız için **PostgreSQL** gibi bir veritabanı sunucusu kullanmak isteyebilirsiniz. + +/// tip | İpucu + +Frontend ve daha fazla araçla birlikte **FastAPI** + **PostgreSQL** içeren resmi bir proje oluşturucu (project generator) var: https://github.com/fastapi/full-stack-fastapi-template + +/// + +Bu çok basit ve kısa bir eğitimdir. Veritabanları genelinde, SQL hakkında veya daha ileri özellikler hakkında öğrenmek isterseniz SQLModel dokümantasyonuna gidin. + +## `SQLModel` Kurulumu { #install-sqlmodel } + +Önce [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan emin olun, aktive edin ve ardından `sqlmodel`’i yükleyin: + +
+ +```console +$ pip install sqlmodel +---> 100% +``` + +
+ +## Tek Model ile Uygulamayı Oluşturma { #create-the-app-with-a-single-model } + +Önce, tek bir **SQLModel** modeliyle uygulamanın en basit ilk sürümünü oluşturacağız. + +Aşağıda, **birden fazla model** kullanarak güvenliği ve esnekliği artırıp geliştireceğiz. + +### Modelleri Oluşturma { #create-models } + +`SQLModel`’i import edin ve bir veritabanı modeli oluşturun: + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} + +`Hero` sınıfı, bir Pydantic modeline çok benzer (hatta altta aslında *bir Pydantic modelidir*). + +Birkaç fark var: + +* `table=True`, SQLModel’e bunun bir *table model* olduğunu söyler; SQL veritabanında bir **table**’ı temsil etmelidir, sadece bir *data model* değildir (diğer normal Pydantic sınıflarında olduğu gibi). + +* `Field(primary_key=True)`, SQLModel’e `id`’nin SQL veritabanındaki **primary key** olduğunu söyler (SQL primary key’leri hakkında daha fazlasını SQLModel dokümantasyonunda öğrenebilirsiniz). + + **Not:** primary key alanı için `int | None` kullanıyoruz; böylece Python kodunda *`id` olmadan bir nesne oluşturabiliriz* (`id=None`) ve veritabanının *kaydederken bunu üreteceğini* varsayarız. SQLModel, veritabanının `id` sağlayacağını anlar ve *veritabanı şemasında sütunu null olamayan bir `INTEGER`* olarak tanımlar. Detaylar için primary key’ler hakkında SQLModel dokümantasyonuna bakın. + +* `Field(index=True)`, SQLModel’e bu sütun için bir **SQL index** oluşturmasını söyler; bu da bu sütuna göre filtrelenmiş verileri okurken veritabanında daha hızlı arama yapılmasını sağlar. + + SQLModel, `str` olarak tanımlanan bir şeyin SQL tarafında `TEXT` (veya veritabanına bağlı olarak `VARCHAR`) tipinde bir sütun olacağını bilir. + +### Engine Oluşturma { #create-an-engine } + +Bir SQLModel `engine`’i (altta aslında bir SQLAlchemy `engine`’idir) veritabanına olan **bağlantıları tutan** yapıdır. + +Tüm kodunuzun aynı veritabanına bağlanması için **tek bir `engine` nesnesi** kullanırsınız. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} + +`check_same_thread=False` kullanmak, FastAPI’nin aynı SQLite veritabanını farklı thread’lerde kullanmasına izin verir. Bu gereklidir; çünkü **tek bir request** **birden fazla thread** kullanabilir (örneğin dependency’lerde). + +Merak etmeyin; kodun yapısı gereği, ileride **her request için tek bir SQLModel *session*** kullandığımızdan emin olacağız. Zaten `check_same_thread` de temelde bunu mümkün kılmaya çalışır. + +### Table’ları Oluşturma { #create-the-tables } + +Sonra `SQLModel.metadata.create_all(engine)` kullanan bir fonksiyon ekleyerek tüm *table model*’ler için **table’ları oluştururuz**. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} + +### Session Dependency’si Oluşturma { #create-a-session-dependency } + +Bir **`Session`**, **nesneleri memory’de** tutar ve verideki gerekli değişiklikleri takip eder; ardından veritabanıyla iletişim kurmak için **`engine` kullanır**. + +`yield` ile, her request için yeni bir `Session` sağlayacak bir FastAPI **dependency** oluşturacağız. Bu da her request’te tek session kullanmamızı garanti eder. + +Ardından bu dependency’yi kullanacak kodun geri kalanını sadeleştirmek için `Annotated` ile `SessionDep` dependency’sini oluştururuz. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} + +### Startup’ta Veritabanı Table’larını Oluşturma { #create-database-tables-on-startup } + +Uygulama başlarken veritabanı table’larını oluşturacağız. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} + +Burada bir uygulama startup event’inde table’ları oluşturuyoruz. + +Production’da büyük ihtimalle uygulamayı başlatmadan önce çalışan bir migration script’i kullanırsınız. + +/// tip | İpucu + +SQLModel, Alembic’i saran migration araçlarına sahip olacak; ancak şimdilik Alembic’i doğrudan kullanabilirsiniz. + +/// + +### Hero Oluşturma { #create-a-hero } + +Her SQLModel modeli aynı zamanda bir Pydantic modeli olduğu için, Pydantic modelleriyle kullanabildiğiniz **type annotation**’larda aynı şekilde kullanabilirsiniz. + +Örneğin `Hero` tipinde bir parametre tanımlarsanız, bu parametre **JSON body**’den okunur. + +Aynı şekilde, bunu fonksiyonun **return type**’ı olarak da tanımlayabilirsiniz; böylece verinin şekli otomatik API docs arayüzünde görünür. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} + +Burada `SessionDep` dependency’sini (bir `Session`) kullanarak yeni `Hero`’yu `Session` instance’ına ekliyoruz, değişiklikleri veritabanına commit ediyoruz, `hero` içindeki veriyi refresh ediyoruz ve sonra geri döndürüyoruz. + +### Hero’ları Okuma { #read-heroes } + +`select()` kullanarak veritabanından `Hero`’ları **okuyabiliriz**. Sonuçları sayfalama (pagination) yapmak için `limit` ve `offset` ekleyebiliriz. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} + +### Tek Bir Hero Okuma { #read-one-hero } + +Tek bir `Hero` **okuyabiliriz**. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} + +### Hero Silme { #delete-a-hero } + +Bir `Hero`’yu **silebiliriz**. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} + +### Uygulamayı Çalıştırma { #run-the-app } + +Uygulamayı çalıştırabilirsiniz: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Sonra `/docs` arayüzüne gidin; **FastAPI**’nin API’yi **dokümante etmek** için bu **modelleri** kullandığını göreceksiniz. Ayrıca veriyi **serialize** ve **validate** etmek için de onları kullanacaktır. + +
+ +
+ +## Birden Fazla Model ile Uygulamayı Güncelleme { #update-the-app-with-multiple-models } + +Şimdi bu uygulamayı biraz **refactor** edelim ve **güvenliği** ile **esnekliği** artıralım. + +Önceki uygulamaya bakarsanız, UI’da şu ana kadar client’ın oluşturulacak `Hero`’nun `id` değerini belirlemesine izin verdiğini görebilirsiniz. + +Buna izin vermemeliyiz; DB’de zaten atanmış bir `id`’yi ezebilirler. `id` belirlemek **client** tarafından değil, **backend** veya **veritabanı** tarafından yapılmalıdır. + +Ayrıca hero için bir `secret_name` oluşturuyoruz ama şimdiye kadar her yerde geri döndürüyoruz; bu pek de **secret** sayılmaz... + +Bunları birkaç **ek model** ekleyerek düzelteceğiz. SQLModel’in parlayacağı yer de burası. + +### Birden Fazla Model Oluşturma { #create-multiple-models } + +**SQLModel**’de, `table=True` olan herhangi bir model sınıfı bir **table model**’dir. + +`table=True` olmayan her model sınıfı ise bir **data model**’dir; bunlar aslında sadece Pydantic modelleridir (bazı küçük ek özelliklerle). + +SQLModel ile **inheritance** kullanarak her durumda tüm alanları tekrar tekrar yazmaktan **kaçınabiliriz**. + +#### `HeroBase` - temel sınıf { #herobase-the-base-class } + +Önce tüm modeller tarafından **paylaşılan alanları** içeren bir `HeroBase` modeliyle başlayalım: + +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} + +#### `Hero` - *table model* { #hero-the-table-model } + +Sonra gerçek *table model* olan `Hero`’yu, diğer modellerde her zaman bulunmayan **ek alanlarla** oluşturalım: + +* `id` +* `secret_name` + +`Hero`, `HeroBase`’ten miras aldığı için `HeroBase`’te tanımlanan alanlara da sahiptir. Dolayısıyla `Hero` için tüm alanlar: + +* `id` +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} + +#### `HeroPublic` - public *data model* { #heropublic-the-public-data-model } + +Sonraki adımda `HeroPublic` modelini oluştururuz; bu model API client’larına **geri döndürülecek** modeldir. + +`HeroBase` ile aynı alanlara sahiptir; dolayısıyla `secret_name` içermez. + +Sonunda kahramanlarımızın kimliği korunmuş oldu! + +Ayrıca `id: int` alanını yeniden tanımlar. Bunu yaparak API client’larıyla bir **contract** (sözleşme) oluşturmuş oluruz; böylece `id` alanının her zaman var olacağını ve `int` olacağını (asla `None` olmayacağını) bilirler. + +/// tip | İpucu + +Return model’in bir değerin her zaman mevcut olduğunu ve her zaman `int` olduğunu (`None` değil) garanti etmesi API client’ları için çok faydalıdır; bu kesinlik sayesinde daha basit kod yazabilirler. + +Ayrıca **otomatik üretilen client**’ların arayüzleri de daha basit olur; böylece API’nizle çalışan geliştiriciler için süreç çok daha rahat olur. + +/// + +`HeroPublic` içindeki tüm alanlar `HeroBase` ile aynıdır; tek fark `id`’nin `int` olarak tanımlanmasıdır (`None` değil): + +* `id` +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} + +#### `HeroCreate` - hero oluşturmak için *data model* { #herocreate-the-data-model-to-create-a-hero } + +Şimdi `HeroCreate` modelini oluştururuz; bu model client’tan gelen veriyi **validate** etmek için kullanılır. + +`HeroBase` ile aynı alanlara sahiptir ve ek olarak `secret_name` içerir. + +Artık client’lar **yeni bir hero oluştururken** `secret_name` gönderecek; bu değer veritabanında saklanacak, ancak API response’larında client’a geri döndürülmeyecek. + +/// tip | İpucu + +**Password**’ları bu şekilde ele alırsınız: alırsınız ama API’de geri döndürmezsiniz. + +Ayrıca password değerlerini saklamadan önce **hash** etmelisiniz; **asla plain text olarak saklamayın**. + +/// + +`HeroCreate` alanları: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} + +#### `HeroUpdate` - hero güncellemek için *data model* { #heroupdate-the-data-model-to-update-a-hero } + +Uygulamanın önceki sürümünde bir hero’yu **güncellemenin** bir yolu yoktu; ancak artık **birden fazla model** ile bunu yapabiliriz. + +`HeroUpdate` *data model* biraz özeldir: yeni bir hero oluşturmak için gereken alanların **tamamına** sahiptir, ancak tüm alanlar **opsiyoneldir** (hepsinin bir default değeri vardır). Bu sayede hero güncellerken sadece güncellemek istediğiniz alanları gönderebilirsiniz. + +Tüm **alanlar aslında değiştiği** için (tip artık `None` içeriyor ve default değerleri `None` oluyor), onları **yeniden tanımlamamız** gerekir. + +Aslında `HeroBase`’ten miras almamız gerekmiyor; çünkü tüm alanları yeniden tanımlıyoruz. Tutarlılık için miras almayı bırakıyorum ama bu gerekli değil. Daha çok kişisel tercih meselesi. + +`HeroUpdate` alanları: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} + +### `HeroCreate` ile Oluşturma ve `HeroPublic` Döndürme { #create-with-herocreate-and-return-a-heropublic } + +Artık **birden fazla model** olduğuna göre, onları kullanan uygulama kısımlarını güncelleyebiliriz. + +Request’te bir `HeroCreate` *data model* alırız ve bundan bir `Hero` *table model* oluştururuz. + +Bu yeni *table model* `Hero`, client’ın gönderdiği alanlara sahip olur ve ayrıca veritabanının ürettiği bir `id` alır. + +Sonra fonksiyondan bu *table model* `Hero`’yu olduğu gibi döndürürüz. Ancak `response_model`’i `HeroPublic` *data model* olarak belirlediğimiz için **FastAPI**, veriyi validate ve serialize etmek için `HeroPublic` kullanır. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} + +/// tip | İpucu + +Burada **return type annotation** `-> HeroPublic` yerine `response_model=HeroPublic` kullanıyoruz; çünkü gerçekte döndürdüğümüz değer *bir* `HeroPublic` değil. + +Eğer `-> HeroPublic` yazsaydık, editörünüz ve linter’ınız (haklı olarak) `HeroPublic` yerine `Hero` döndürdüğünüz için şikayet edecekti. + +Bunu `response_model` içinde belirterek **FastAPI**’ye işini yapmasını söylüyoruz; type annotation’lara ve editörünüzün/diğer araçların sağladığı desteğe karışmamış oluyoruz. + +/// + +### `HeroPublic` ile Hero’ları Okuma { #read-heroes-with-heropublic } + +Daha öncekiyle aynı şekilde `Hero`’ları **okuyabiliriz**; yine `response_model=list[HeroPublic]` kullanarak verinin doğru biçimde validate ve serialize edilmesini garanti ederiz. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} + +### `HeroPublic` ile Tek Bir Hero Okuma { #read-one-hero-with-heropublic } + +Tek bir hero’yu **okuyabiliriz**: + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} + +### `HeroUpdate` ile Hero Güncelleme { #update-a-hero-with-heroupdate } + +Bir hero’yu **güncelleyebiliriz**. Bunun için HTTP `PATCH` operasyonu kullanırız. + +Kodda, client’ın gönderdiği tüm verilerle bir `dict` alırız; **yalnızca client’ın gönderdiği veriler**, yani sadece default değer oldukları için orada bulunan değerler hariç. Bunu yapmak için `exclude_unset=True` kullanırız. Asıl numara bu. + +Sonra `hero_db.sqlmodel_update(hero_data)` ile `hero_db`’yi `hero_data` içindeki verilerle güncelleriz. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} + +### Hero’yu Tekrar Silme { #delete-a-hero-again } + +Bir hero’yu **silmek** büyük ölçüde aynı kalıyor. + +Bu örnekte her şeyi refactor etme isteğimizi bastıracağız. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} + +### Uygulamayı Tekrar Çalıştırma { #run-the-app-again } + +Uygulamayı tekrar çalıştırabilirsiniz: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +`/docs` API UI’a giderseniz artık güncellendiğini göreceksiniz; hero oluştururken client’tan `id` beklemeyecek, vb. + +
+ +
+ +## Özet { #recap } + +Bir SQL veritabanıyla etkileşim kurmak için **SQLModel** kullanabilir ve *data model* ile *table model* yaklaşımıyla kodu sadeleştirebilirsiniz. + +**SQLModel** dokümantasyonunda çok daha fazlasını öğrenebilirsiniz; **FastAPI** ile SQLModel kullanımı için daha uzun bir mini tutorial da bulunuyor. diff --git a/docs/tr/docs/tutorial/testing.md b/docs/tr/docs/tutorial/testing.md new file mode 100644 index 000000000..887156606 --- /dev/null +++ b/docs/tr/docs/tutorial/testing.md @@ -0,0 +1,190 @@ +# Test Etme { #testing } + +Starlette sayesinde **FastAPI** uygulamalarını test etmek kolay ve keyiflidir. + +Temelde HTTPX üzerine kuruludur; HTTPX de Requests’i temel alarak tasarlandığı için oldukça tanıdık ve sezgiseldir. + +Bununla birlikte **FastAPI** ile pytest'i doğrudan kullanabilirsiniz. + +## `TestClient` Kullanımı { #using-testclient } + +/// info | Bilgi + +`TestClient` kullanmak için önce `httpx`'i kurun. + +Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu aktifleştirdiğinizden ve sonra kurulumu yaptığınızdan emin olun; örneğin: + +```console +$ pip install httpx +``` + +/// + +`TestClient`'ı import edin. + +**FastAPI** uygulamanızı ona vererek bir `TestClient` oluşturun. + +Adı `test_` ile başlayan fonksiyonlar oluşturun (bu, `pytest`'in standart konvansiyonudur). + +`TestClient` nesnesini `httpx` ile kullandığınız şekilde kullanın. + +Kontrol etmeniz gereken şeyler için standart Python ifadeleriyle basit `assert` satırları yazın (bu da `pytest` standardıdır). + +{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} + +/// tip | İpucu + +Test fonksiyonlarının `async def` değil, normal `def` olduğuna dikkat edin. + +Client'a yapılan çağrılar da `await` kullanılmadan, normal çağrılardır. + +Bu sayede `pytest`'i ek bir karmaşıklık olmadan doğrudan kullanabilirsiniz. + +/// + +/// note | Teknik Detaylar + +İsterseniz `from starlette.testclient import TestClient` da kullanabilirsiniz. + +**FastAPI**, geliştirici olarak size kolaylık olsun diye `starlette.testclient`'ı `fastapi.testclient` üzerinden de sunar. Ancak asıl kaynak doğrudan Starlette'tır. + +/// + +/// tip | İpucu + +FastAPI uygulamanıza request göndermenin dışında testlerinizde `async` fonksiyonlar çağırmak istiyorsanız (örn. asenkron veritabanı fonksiyonları), ileri seviye bölümdeki [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} dokümanına göz atın. + +/// + +## Testleri Ayırma { #separating-tests } + +Gerçek bir uygulamada testlerinizi büyük ihtimalle farklı bir dosyada tutarsınız. + +Ayrıca **FastAPI** uygulamanız birden fazla dosya/modül vb. ile de oluşturulmuş olabilir. + +### **FastAPI** Uygulama Dosyası { #fastapi-app-file } + +[Bigger Applications](bigger-applications.md){.internal-link target=_blank}'te anlatılan şekilde bir dosya yapınız olduğunu varsayalım: + +``` +. +├── app +│   ├── __init__.py +│   └── main.py +``` + +`main.py` dosyasında **FastAPI** uygulamanız bulunuyor olsun: + +{* ../../docs_src/app_testing/app_a_py39/main.py *} + +### Test Dosyası { #testing-file } + +Sonra testlerinizin olduğu bir `test_main.py` dosyanız olabilir. Bu dosya aynı Python package içinde (yani `__init__.py` dosyası olan aynı dizinde) durabilir: + +``` hl_lines="5" +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Bu dosya aynı package içinde olduğu için, `main` modülünden (`main.py`) `app` nesnesini import etmek üzere relative import kullanabilirsiniz: + +{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} + +...ve test kodunu da öncekiyle aynı şekilde yazabilirsiniz. + +## Test Etme: Genişletilmiş Örnek { #testing-extended-example } + +Şimdi bu örneği genişletelim ve farklı parçaların nasıl test edildiğini görmek için daha fazla detay ekleyelim. + +### Genişletilmiş **FastAPI** Uygulama Dosyası { #extended-fastapi-app-file } + +Aynı dosya yapısıyla devam edelim: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Diyelim ki **FastAPI** uygulamanızın bulunduğu `main.py` dosyasında artık başka **path operations** da var. + +Hata döndürebilecek bir `GET` operation'ı var. + +Birden fazla farklı hata döndürebilecek bir `POST` operation'ı var. + +Her iki *path operation* da `X-Token` header'ını gerektiriyor. + +{* ../../docs_src/app_testing/app_b_an_py310/main.py *} + +### Genişletilmiş Test Dosyası { #extended-testing-file } + +Sonrasında `test_main.py` dosyanızı genişletilmiş testlerle güncelleyebilirsiniz: + +{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *} + +Client'ın request içinde bir bilgi göndermesi gerektiğinde ve bunu nasıl yapacağınızı bilemediğinizde, `httpx` ile nasıl yapılacağını aratabilirsiniz (Google) ya da HTTPX’in tasarımı Requests’e dayandığı için `requests` ile nasıl yapıldığını da arayabilirsiniz. + +Sonra testlerinizde aynısını uygularsınız. + +Örn.: + +* Bir *path* veya *query* parametresi geçirmek için, URL’nin kendisine ekleyin. +* JSON body göndermek için, `json` parametresine bir Python nesnesi (örn. bir `dict`) verin. +* JSON yerine *Form Data* göndermeniz gerekiyorsa, bunun yerine `data` parametresini kullanın. +* *headers* göndermek için, `headers` parametresine bir `dict` verin. +* *cookies* için, `cookies` parametresine bir `dict` verin. + +Backend'e veri geçme hakkında daha fazla bilgi için (`httpx` veya `TestClient` kullanarak) HTTPX dokümantasyonu'na bakın. + +/// info | Bilgi + +`TestClient`'ın Pydantic model'lerini değil, JSON'a dönüştürülebilen verileri aldığını unutmayın. + +Testinizde bir Pydantic model'iniz varsa ve test sırasında verisini uygulamaya göndermek istiyorsanız, [JSON Compatible Encoder](encoder.md){.internal-link target=_blank} içinde açıklanan `jsonable_encoder`'ı kullanabilirsiniz. + +/// + +## Çalıştırma { #run-it } + +Bundan sonra yapmanız gereken tek şey `pytest`'i kurmaktır. + +Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu aktifleştirdiğinizden ve sonra kurulumu yaptığınızdan emin olun; örneğin: + +
+ +```console +$ pip install pytest + +---> 100% +``` + +
+ +Dosyaları ve testleri otomatik olarak bulur, çalıştırır ve sonuçları size raporlar. + +Testleri şu şekilde çalıştırın: + +
+ +```console +$ pytest + +================ test session starts ================ +platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 +rootdir: /home/user/code/superawesome-cli/app +plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 +collected 6 items + +---> 100% + +test_main.py ...... [100%] + +================= 1 passed in 0.03s ================= +``` + +
diff --git a/docs/tr/docs/virtual-environments.md b/docs/tr/docs/virtual-environments.md new file mode 100644 index 000000000..cf7fab778 --- /dev/null +++ b/docs/tr/docs/virtual-environments.md @@ -0,0 +1,862 @@ +# Virtual Environments { #virtual-environments } + +Python projeleriyle çalışırken, her proje için kurduğunuz package'leri birbirinden izole etmek adına büyük ihtimalle bir **virtual environment** (veya benzer bir mekanizma) kullanmalısınız. + +/// info | Bilgi + +Virtual environment'leri, nasıl oluşturulduklarını ve nasıl kullanıldıklarını zaten biliyorsanız bu bölümü atlamak isteyebilirsiniz. 🤓 + +/// + +/// tip | İpucu + +**Virtual environment**, **environment variable** ile aynı şey değildir. + +**Environment variable**, sistemde bulunan ve programların kullanabildiği bir değişkendir. + +**Virtual environment** ise içinde bazı dosyalar bulunan bir klasördür. + +/// + +/// info | Bilgi + +Bu sayfada **virtual environment**'leri nasıl kullanacağınızı ve nasıl çalıştıklarını öğreneceksiniz. + +Eğer Python'ı kurmak dahil her şeyi sizin yerinize yöneten bir **tool** kullanmaya hazırsanız, uv'yi deneyin. + +/// + +## Proje Oluşturun { #create-a-project } + +Önce projeniz için bir klasör oluşturun. + +Ben genelde home/user klasörümün içinde `code` adlı bir klasör oluştururum. + +Sonra bunun içinde her proje için ayrı bir klasör oluştururum. + +
+ +```console +// Go to the home directory +$ cd +// Create a directory for all your code projects +$ mkdir code +// Enter into that code directory +$ cd code +// Create a directory for this project +$ mkdir awesome-project +// Enter into that project directory +$ cd awesome-project +``` + +
+ +## Virtual Environment Oluşturun { #create-a-virtual-environment } + +Bir Python projesi üzerinde **ilk kez** çalışmaya başladığınızda, projenizin içinde **virtual environment** oluşturun. + +/// tip | İpucu + +Bunu her çalıştığınızda değil, **proje başına sadece bir kez** yapmanız yeterlidir. + +/// + +//// tab | `venv` + +Bir virtual environment oluşturmak için, Python ile birlikte gelen `venv` modülünü kullanabilirsiniz. + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | Bu komut ne anlama geliyor + +* `python`: `python` adlı programı kullan +* `-m`: bir modülü script gibi çalıştır; bir sonraki kısımda hangi modül olduğunu söyleyeceğiz +* `venv`: normalde Python ile birlikte kurulu gelen `venv` modülünü kullan +* `.venv`: virtual environment'i yeni `.venv` klasörünün içine oluştur + +/// + +//// + +//// tab | `uv` + +Eğer `uv` kuruluysa, onunla da virtual environment oluşturabilirsiniz. + +
+ +```console +$ uv venv +``` + +
+ +/// tip | İpucu + +Varsayılan olarak `uv`, `.venv` adlı bir klasörde virtual environment oluşturur. + +Ancak ek bir argümanla klasör adını vererek bunu özelleştirebilirsiniz. + +/// + +//// + +Bu komut `.venv` adlı bir klasörün içinde yeni bir virtual environment oluşturur. + +/// details | `.venv` veya başka bir ad + +Virtual environment'i başka bir klasörde de oluşturabilirsiniz; ancak buna `.venv` demek yaygın bir konvansiyondur. + +/// + +## Virtual Environment'i Aktif Edin { #activate-the-virtual-environment } + +Oluşturduğunuz virtual environment'i aktif edin; böylece çalıştırdığınız her Python komutu veya kurduğunuz her package onu kullanır. + +/// tip | İpucu + +Projede çalışmak için **yeni bir terminal oturumu** başlattığınız **her seferinde** bunu yapın. + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Ya da Windows'ta Bash kullanıyorsanız (örn. Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip | İpucu + +Bu environment'e **yeni bir package** kurduğunuz her seferinde environment'i yeniden **aktif edin**. + +Böylece, o package'in kurduğu bir **terminal (CLI) programı** kullanıyorsanız, global olarak kurulu (ve muhtemelen ihtiyacınız olandan farklı bir versiyona sahip) başka bir program yerine, virtual environment'inizdeki programı kullanmış olursunuz. + +/// + +## Virtual Environment'in Aktif Olduğunu Kontrol Edin { #check-the-virtual-environment-is-active } + +Virtual environment'in aktif olduğunu (bir önceki komutun çalıştığını) kontrol edin. + +/// tip | İpucu + +Bu **opsiyoneldir**; ancak her şeyin beklendiği gibi çalıştığını ve hedeflediğiniz virtual environment'i kullandığınızı **kontrol etmek** için iyi bir yöntemdir. + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +Eğer `python` binary'sini projenizin içinde (bu örnekte `awesome-project`) `.venv/bin/python` yolunda gösteriyorsa, tamamdır. 🎉 + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +Eğer `python` binary'sini projenizin içinde (bu örnekte `awesome-project`) `.venv\Scripts\python` yolunda gösteriyorsa, tamamdır. 🎉 + +//// + +## `pip`'i Yükseltin { #upgrade-pip } + +/// tip | İpucu + +`uv` kullanıyorsanız, `pip` yerine onunla kurulum yaparsınız; dolayısıyla `pip`'i yükseltmeniz gerekmez. 😎 + +/// + +Package'leri kurmak için `pip` kullanıyorsanız (Python ile varsayılan olarak gelir), en güncel sürüme **yükseltmeniz** gerekir. + +Bir package kurarken görülen birçok garip hata, önce `pip`'i yükseltince çözülür. + +/// tip | İpucu + +Bunu genelde virtual environment'i oluşturduktan hemen sonra **bir kez** yaparsınız. + +/// + +Virtual environment'in aktif olduğundan emin olun (yukarıdaki komutla) ve sonra şunu çalıştırın: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +/// tip | İpucu + +Bazen pip'i yükseltmeye çalışırken **`No module named pip`** hatası alabilirsiniz. + +Böyle olursa, aşağıdaki komutla pip'i kurup yükseltin: + +
+ +```console +$ python -m ensurepip --upgrade + +---> 100% +``` + +
+ +Bu komut pip kurulu değilse kurar ve ayrıca kurulu pip sürümünün `ensurepip` içinde bulunan sürüm kadar güncel olmasını garanti eder. + +/// + +## `.gitignore` Ekleyin { #add-gitignore } + +**Git** kullanıyorsanız (kullanmalısınız), `.venv` içindeki her şeyi Git'ten hariç tutmak için bir `.gitignore` dosyası ekleyin. + +/// tip | İpucu + +Virtual environment'i `uv` ile oluşturduysanız, bunu zaten sizin için yaptı; bu adımı atlayabilirsiniz. 😎 + +/// + +/// tip | İpucu + +Bunu virtual environment'i oluşturduktan hemen sonra **bir kez** yapın. + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | Bu komut ne anlama geliyor + +* `echo "*"`: terminale `*` metnini "yazar" (sonraki kısım bunu biraz değiştiriyor) +* `>`: `>` işaretinin solundaki komutun terminale yazdıracağı çıktı, ekrana basılmak yerine sağ taraftaki dosyaya yazılsın +* `.gitignore`: metnin yazılacağı dosyanın adı + +Git'te `*` "her şey" demektir. Yani `.venv` klasörü içindeki her şeyi ignore eder. + +Bu komut, içeriği şu olan bir `.gitignore` dosyası oluşturur: + +```gitignore +* +``` + +/// + +## Package'leri Kurun { #install-packages } + +Environment'i aktif ettikten sonra, içine package kurabilirsiniz. + +/// tip | İpucu + +Projede ihtiyaç duyduğunuz package'leri ilk kez kurarken veya yükseltirken bunu **bir kez** yapın. + +Bir sürümü yükseltmeniz veya yeni bir package eklemeniz gerekirse **tekrar** yaparsınız. + +/// + +### Package'leri Doğrudan Kurun { #install-packages-directly } + +Acele ediyorsanız ve projenizin package gereksinimlerini bir dosyada belirtmek istemiyorsanız, doğrudan kurabilirsiniz. + +/// tip | İpucu + +Programınızın ihtiyaç duyduğu package'leri ve versiyonlarını bir dosyada tutmak (ör. `requirements.txt` veya `pyproject.toml`) (çok) iyi bir fikirdir. + +/// + +//// tab | `pip` + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Eğer `uv` varsa: + +
+ +```console +$ uv pip install "fastapi[standard]" +---> 100% +``` + +
+ +//// + +### `requirements.txt`'ten Kurun { #install-from-requirements-txt } + +Bir `requirements.txt` dosyanız varsa, içindeki package'leri kurmak için artık onu kullanabilirsiniz. + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Eğer `uv` varsa: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | `requirements.txt` + +Bazı package'ler içeren bir `requirements.txt` şöyle görünebilir: + +```requirements.txt +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +/// + +## Programınızı Çalıştırın { #run-your-program } + +Virtual environment'i aktif ettikten sonra programınızı çalıştırabilirsiniz; program, virtual environment'in içindeki Python'ı ve oraya kurduğunuz package'leri kullanır. + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## Editörünüzü Yapılandırın { #configure-your-editor } + +Muhtemelen bir editör kullanırsınız; otomatik tamamlamayı ve satır içi hataları alabilmek için, editörünüzü oluşturduğunuz aynı virtual environment'i kullanacak şekilde yapılandırdığınızdan emin olun (muhtemelen otomatik algılar). + +Örneğin: + +* VS Code +* PyCharm + +/// tip | İpucu + +Bunu genelde yalnızca **bir kez**, virtual environment'i oluşturduğunuzda yapmanız gerekir. + +/// + +## Virtual Environment'i Devre Dışı Bırakın { #deactivate-the-virtual-environment } + +Projeniz üzerinde işiniz bittiğinde virtual environment'i **deactivate** edebilirsiniz. + +
+ +```console +$ deactivate +``` + +
+ +Böylece `python` çalıştırdığınızda, o virtual environment içinden (ve oraya kurulu package'lerle) çalıştırmaya çalışmaz. + +## Çalışmaya Hazırsınız { #ready-to-work } + +Artık projeniz üzerinde çalışmaya başlayabilirsiniz. + +/// tip | İpucu + +Yukarıdaki her şeyin aslında ne olduğunu anlamak ister misiniz? + +Okumaya devam edin. 👇🤓 + +/// + +## Neden Virtual Environment { #why-virtual-environments } + +FastAPI ile çalışmak için Python kurmanız gerekir. + +Sonrasında FastAPI'yi ve kullanmak istediğiniz diğer tüm **package**'leri **kurmanız** gerekir. + +Package kurmak için genelde Python ile gelen `pip` komutunu (veya benzeri alternatifleri) kullanırsınız. + +Ancak `pip`'i doğrudan kullanırsanız, package'ler **global Python environment**'ınıza (Python'ın global kurulumuna) yüklenir. + +### Problem { #the-problem } + +Peki package'leri global Python environment'a kurmanın sorunu ne? + +Bir noktada, muhtemelen **farklı package**'lere bağımlı birçok farklı program yazacaksınız. Ayrıca üzerinde çalıştığınız bazı projeler, aynı package'in **farklı versiyonlarına** ihtiyaç duyacak. 😱 + +Örneğin `philosophers-stone` adında bir proje oluşturduğunuzu düşünün; bu program, `harry` adlı başka bir package'e **`1` versiyonu ile** bağlı. Yani `harry`'yi kurmanız gerekir. + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` + +Sonra daha ileri bir zamanda `prisoner-of-azkaban` adlı başka bir proje oluşturuyorsunuz; bu proje de `harry`'ye bağlı, fakat bu proje **`harry` versiyon `3`** istiyor. + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] +``` + +Şimdi sorun şu: package'leri local bir **virtual environment** yerine global (global environment) olarak kurarsanız, `harry`'nin hangi versiyonunu kuracağınıza karar vermek zorunda kalırsınız. + +`philosophers-stone`'u çalıştırmak istiyorsanız önce `harry` versiyon `1`'i kurmanız gerekir; örneğin: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +Sonuç olarak global Python environment'ınızda `harry` versiyon `1` kurulu olur. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -->|requires| harry-1 + end +``` + +Fakat `prisoner-of-azkaban`'ı çalıştırmak istiyorsanız, `harry` versiyon `1`'i kaldırıp `harry` versiyon `3`'ü kurmanız gerekir (ya da sadece `3`'ü kurmak, otomatik olarak `1`'i kaldırabilir). + +
+ +```console +$ pip install "harry==3" +``` + +
+ +Sonuç olarak global Python environment'ınızda `harry` versiyon `3` kurulu olur. + +Ve `philosophers-stone`'u tekrar çalıştırmaya kalkarsanız, `harry` versiyon `1`'e ihtiyaç duyduğu için **çalışmama** ihtimali vardır. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -.-x|⛔️| harry-1 + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --> |requires| harry-3 + end +``` + +/// tip | İpucu + +Python package'lerinde **yeni versiyonlarda** **breaking change**'lerden kaçınmak oldukça yaygındır; ancak yine de daha güvenlisi, yeni versiyonları bilinçli şekilde kurmak ve mümkünse test'leri çalıştırıp her şeyin doğru çalıştığını doğrulamaktır. + +/// + +Şimdi bunu, **projelerinizin bağımlı olduğu** daha **birçok** başka **package** ile birlikte düşünün. Yönetmesi epey zorlaşır. Sonunda bazı projeleri package'lerin **uyumsuz versiyonlarıyla** çalıştırıp, bir şeylerin neden çalışmadığını anlamamak gibi durumlara düşebilirsiniz. + +Ayrıca işletim sisteminize (örn. Linux, Windows, macOS) bağlı olarak Python zaten kurulu gelmiş olabilir. Bu durumda, sisteminizin **ihtiyaç duyduğu** bazı package'ler belirli versiyonlarla önceden kurulu olabilir. Global Python environment'a package kurarsanız, işletim sistemiyle gelen bazı programları **bozma** ihtimaliniz olabilir. + +## Package'ler Nereye Kuruluyor { #where-are-packages-installed } + +Python'ı kurduğunuzda, bilgisayarınızda bazı dosyalar içeren klasörler oluşturulur. + +Bu klasörlerin bir kısmı, kurduğunuz tüm package'leri barındırmaktan sorumludur. + +Şunu çalıştırdığınızda: + +
+ +```console +// Don't run this now, it's just an example 🤓 +$ pip install "fastapi[standard]" +---> 100% +``` + +
+ +Bu, FastAPI kodunu içeren sıkıştırılmış bir dosyayı genellikle PyPI'dan indirir. + +Ayrıca FastAPI'nin bağımlı olduğu diğer package'ler için de dosyaları **indirir**. + +Sonra tüm bu dosyaları **açar (extract)** ve bilgisayarınızdaki bir klasöre koyar. + +Varsayılan olarak bu indirilip çıkarılan dosyaları, Python kurulumunuzla birlikte gelen klasöre yerleştirir; yani **global environment**'a. + +## Virtual Environment Nedir { #what-are-virtual-environments } + +Global environment'da tüm package'leri bir arada tutmanın sorunlarına çözüm, çalıştığınız her proje için ayrı bir **virtual environment** kullanmaktır. + +Virtual environment, global olana çok benzeyen bir **klasördür**; bir projenin ihtiyaç duyduğu package'leri buraya kurarsınız. + +Böylece her projenin kendi virtual environment'i (`.venv` klasörü) ve kendi package'leri olur. + +```mermaid +flowchart TB + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) --->|requires| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --->|requires| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## Virtual Environment'i Aktif Etmek Ne Demek { #what-does-activating-a-virtual-environment-mean } + +Bir virtual environment'i örneğin şununla aktif ettiğinizde: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Ya da Windows'ta Bash kullanıyorsanız (örn. Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +Bu komut, sonraki komutlarda kullanılabilecek bazı [environment variable](environment-variables.md){.internal-link target=_blank}'ları oluşturur veya değiştirir. + +Bunlardan biri `PATH` değişkenidir. + +/// tip | İpucu + +`PATH` environment variable hakkında daha fazla bilgiyi [Environment Variables](environment-variables.md#path-environment-variable){.internal-link target=_blank} bölümünde bulabilirsiniz. + +/// + +Bir virtual environment'i aktive etmek, onun `.venv/bin` (Linux ve macOS'ta) veya `.venv\Scripts` (Windows'ta) yolunu `PATH` environment variable'ına ekler. + +Diyelim ki environment'i aktive etmeden önce `PATH` değişkeni şöyleydi: + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +Bu, sistemin programları şu klasörlerde arayacağı anlamına gelir: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +Bu, sistemin programları şurada arayacağı anlamına gelir: + +* `C:\Windows\System32` + +//// + +Virtual environment'i aktive ettikten sonra `PATH` değişkeni şuna benzer hale gelir: + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Bu, sistemin artık programları önce şurada aramaya başlayacağı anlamına gelir: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +diğer klasörlere bakmadan önce. + +Dolayısıyla terminale `python` yazdığınızda, sistem Python programını şurada bulur: + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +ve onu kullanır. + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +Bu, sistemin artık programları önce şurada aramaya başlayacağı anlamına gelir: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +diğer klasörlere bakmadan önce. + +Dolayısıyla terminale `python` yazdığınızda, sistem Python programını şurada bulur: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +ve onu kullanır. + +//// + +Önemli bir detay: virtual environment yolu `PATH` değişkeninin **en başına** eklenir. Sistem, mevcut başka herhangi bir Python'ı bulmadan **önce** bunu bulur. Böylece `python` çalıştırdığınızda, başka bir `python` (örneğin global environment'tan gelen `python`) yerine **virtual environment'taki** Python kullanılır. + +Virtual environment'i aktive etmek birkaç şeyi daha değiştirir; ancak yaptığı en önemli işlerden biri budur. + +## Virtual Environment'i Kontrol Etmek { #checking-a-virtual-environment } + +Bir virtual environment'in aktif olup olmadığını örneğin şununla kontrol ettiğinizde: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +Bu, kullanılacak `python` programının **virtual environment'in içindeki** Python olduğu anlamına gelir. + +Linux ve macOS'ta `which`, Windows PowerShell'de ise `Get-Command` kullanırsınız. + +Bu komutun çalışma mantığı şudur: `PATH` environment variable içindeki **her yolu sırayla** dolaşır, `python` adlı programı arar. Bulduğunda, size o programın **dosya yolunu** gösterir. + +En önemli kısım şu: `python` dediğinizde çalışacak olan "`python`" tam olarak budur. + +Yani doğru virtual environment'da olup olmadığınızı doğrulayabilirsiniz. + +/// tip | İpucu + +Bir virtual environment'i aktive etmek kolaydır; sonra o Python ile kalıp **başka bir projeye geçmek** de kolaydır. + +Bu durumda ikinci proje, başka bir projenin virtual environment'ından gelen **yanlış Python**'ı kullandığınız için **çalışmayabilir**. + +Hangi `python`'ın kullanıldığını kontrol edebilmek bu yüzden faydalıdır. 🤓 + +/// + +## Neden Virtual Environment'i Deactivate Edelim { #why-deactivate-a-virtual-environment } + +Örneğin `philosophers-stone` projesi üzerinde çalışıyor olabilirsiniz; **o virtual environment'i aktive eder**, package kurar ve o environment ile çalışırsınız. + +Sonra **başka bir proje** olan `prisoner-of-azkaban` üzerinde çalışmak istersiniz. + +O projeye gidersiniz: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +Eğer `philosophers-stone` için olan virtual environment'i deactivate etmezseniz, terminalde `python` çalıştırdığınızda `philosophers-stone`'dan gelen Python'ı kullanmaya çalışır. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// Error importing sirius, it's not installed 😱 +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +Ama virtual environment'i deactivate edip `prisoner-of-askaban` için yeni olanı aktive ederseniz, `python` çalıştırdığınızda `prisoner-of-azkaban` içindeki virtual environment'dan gelen Python kullanılır. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project 😎 +$ deactivate + +// Activate the virtual environment in prisoner-of-azkaban/.venv 🚀 +$ source .venv/bin/activate + +// Now when you run python, it will find the package sirius installed in this virtual environment ✨ +$ python main.py + +I solemnly swear 🐺 +``` + +
+ +## Alternatifler { #alternatives } + +Bu, başlamanız için basit bir rehber ve alttaki mekanizmaların nasıl çalıştığını öğretmeyi amaçlıyor. + +Virtual environment'leri, package bağımlılıklarını (requirements) ve projeleri yönetmek için birçok **alternatif** vardır. + +Hazır olduğunuzda ve package bağımlılıkları, virtual environment'ler vb. dahil **tüm projeyi yönetmek** için bir tool kullanmak istediğinizde, uv'yi denemenizi öneririm. + +`uv` birçok şey yapabilir, örneğin: + +* Sizin için **Python kurabilir**, farklı sürümler dahil +* Projelerinizin **virtual environment**'ini yönetebilir +* **Package** kurabilir +* Projeniz için package **bağımlılıklarını ve versiyonlarını** yönetebilir +* Bağımlılıkları dahil, kurulacak package ve versiyonların **tam (exact)** bir setini garanti edebilir; böylece geliştirirken bilgisayarınızda çalıştırdığınız projeyi production'da da birebir aynı şekilde çalıştırabileceğinizden emin olursunuz; buna **locking** denir +* Ve daha birçok şey + +## Sonuç { #conclusion } + +Buradaki her şeyi okuduysanız ve anladıysanız, artık birçok geliştiriciden **çok daha fazla** virtual environment bilgisine sahipsiniz. 🤓 + +Bu detayları bilmek, ileride karmaşık görünen bir sorunu debug ederken büyük olasılıkla işinize yarayacak; çünkü **altta nasıl çalıştığını** biliyor olacaksınız. 😎 From c7682a198a68455d0004dd0e8e829eef807065e9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 15:44:05 +0000 Subject: [PATCH 010/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c54875f06..930403210 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for tr (add-missing). PR [#14790](https://github.com/fastapi/fastapi/pull/14790) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for fr (update-outdated). PR [#14826](https://github.com/fastapi/fastapi/pull/14826) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for zh-hant (update-outdated). PR [#14825](https://github.com/fastapi/fastapi/pull/14825) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for uk (update-outdated). PR [#14822](https://github.com/fastapi/fastapi/pull/14822) by [@tiangolo](https://github.com/tiangolo). From dcfd432c3645055ab6f23227c26f56aa182358ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 07:47:42 -0800 Subject: [PATCH 011/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20for=20tr?= =?UTF-8?q?anslations=20(#14830)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/contributing.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/en/docs/contributing.md b/docs/en/docs/contributing.md index 5e6d821b3..af7944e75 100644 --- a/docs/en/docs/contributing.md +++ b/docs/en/docs/contributing.md @@ -179,19 +179,23 @@ as Uvicorn by default will use the port `8000`, the documentation on port `8008` Help with translations is VERY MUCH appreciated! And it can't be done without the help from the community. 🌎 🚀 -Here are the steps to help with translations. - -#### Translation PRs - Translation pull requests are made by LLMs guided with prompts designed by the FastAPI team together with the community of native speakers for each supported language. -These translations are normally still reviewed by native speakers, and here's where you can help! +#### LLM Prompt per Language -* Check the currently existing pull requests for your language. You can filter the pull requests by the ones with the label for your language. For example, for Spanish, the label is `lang-es`. +Each language has a directory: https://github.com/fastapi/fastapi/tree/master/docs, in it you can see a file `llm-prompt.md` with the prompt specific for that language. -* When reviewing a pull request, it's better not to suggest changes in the same pull request, because it is LLM generated, and it won't be possible to make sure that small individual changes are replicated in other similar sections, or that they are preserved when translating the same content again. +For example, for Spanish, the prompt is at: `docs/es/llm-prompt.md`. -* Instead of adding suggestions to the translation PR, make the suggestions to the LLM prompt file for that language, in a new PR. For example, for Spanish, the LLM prompt file is at: `docs/es/llm-prompt.md`. +If you see mistakes in your language, you can make suggestions to the prompt in that file for your language, and request the specific pages you would like to re-generate after the changes. + +#### Reviewing Translation PRs + +You can also check the currently existing pull requests for your language. You can filter the pull requests by the ones with the label for your language. For example, for Spanish, the label is `lang-es`. + +When reviewing a pull request, it's better not to suggest changes in the same pull request, because it is LLM generated, and it won't be possible to make sure that small individual changes are replicated in other similar sections, or that they are preserved when translating the same content again. + +Instead of adding suggestions to the translation PR, make the suggestions to the LLM prompt file for that language, in a new PR. For example, for Spanish, the LLM prompt file is at: `docs/es/llm-prompt.md`. /// tip @@ -201,9 +205,9 @@ Check the docs about GitHub Discussion to coordinate translations for your language. You can subscribe to it, and when there's a new pull request to review, an automatic comment will be added to the discussion. +Check if there's a GitHub Discussion to coordinate translations for your language. You can subscribe to it, and when there's a new pull request to review, an automatic comment will be added to the discussion. -* To check the 2-letter code for the language you want to translate, you can use the table List of ISO 639-1 codes. +To check the 2-letter code for the language you want to translate, you can use the table List of ISO 639-1 codes. #### Request a New Language From 8488d31aff7da56915f424d0b16e2b1cad70ec2d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 15:48:06 +0000 Subject: [PATCH 012/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 930403210..a91ea5a2b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* 📝 Update docs for translations. PR [#14830](https://github.com/fastapi/fastapi/pull/14830) by [@tiangolo](https://github.com/tiangolo). * 📝 Fix duplicate word in `advanced-dependencies.md`. PR [#14815](https://github.com/fastapi/fastapi/pull/14815) by [@Rayyan-Oumlil](https://github.com/Rayyan-Oumlil). ### Translations From da9e101d5007dbb35bfe4fa695bbc2e22b42d568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 07:56:49 -0800 Subject: [PATCH 013/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20tr=20(update-outdated)=20(#14831)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/tr/docs/advanced/wsgi.md | 22 +++++++++++++++++++--- docs/tr/docs/index.md | 18 ++++++------------ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/docs/tr/docs/advanced/wsgi.md b/docs/tr/docs/advanced/wsgi.md index 442f83a59..6f6b10b68 100644 --- a/docs/tr/docs/advanced/wsgi.md +++ b/docs/tr/docs/advanced/wsgi.md @@ -1,18 +1,34 @@ # WSGI'yi Dahil Etme - Flask, Django ve Diğerleri { #including-wsgi-flask-django-others } -WSGI uygulamalarını [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}, [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank} bölümlerinde gördüğünüz gibi mount edebilirsiniz. +WSGI uygulamalarını [Alt Uygulamalar - Mount Etme](sub-applications.md){.internal-link target=_blank}, [Bir Proxy Arkasında](behind-a-proxy.md){.internal-link target=_blank} bölümlerinde gördüğünüz gibi mount edebilirsiniz. Bunun için `WSGIMiddleware`'ı kullanabilir ve bunu WSGI uygulamanızı (örneğin Flask, Django vb.) sarmalamak için kullanabilirsiniz. ## `WSGIMiddleware` Kullanımı { #using-wsgimiddleware } -`WSGIMiddleware`'ı import etmeniz gerekir. +/// info | Bilgi + +Bunun için `a2wsgi` kurulmalıdır; örneğin `pip install a2wsgi` ile. + +/// + +`WSGIMiddleware`'ı `a2wsgi` paketinden import etmeniz gerekir. Ardından WSGI (örn. Flask) uygulamasını middleware ile sarmalayın. Ve sonra bunu bir path'in altına mount edin. -{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *} +{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} + +/// note | Not + +Önceden, `fastapi.middleware.wsgi` içindeki `WSGIMiddleware`'ın kullanılması öneriliyordu, ancak artık kullanımdan kaldırıldı. + +Bunun yerine `a2wsgi` paketini kullanmanız önerilir. Kullanım aynıdır. + +Sadece `a2wsgi` paketinin kurulu olduğundan emin olun ve `WSGIMiddleware`'ı `a2wsgi` içinden doğru şekilde import edin. + +/// ## Kontrol Edelim { #check-it } diff --git a/docs/tr/docs/index.md b/docs/tr/docs/index.md index 9cffd4274..16c425f5d 100644 --- a/docs/tr/docs/index.md +++ b/docs/tr/docs/index.md @@ -161,8 +161,6 @@ $ pip install "fastapi[standard]" Şu içerikle `main.py` adında bir dosya oluşturalım: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -174,7 +172,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -183,9 +181,7 @@ def read_item(item_id: int, q: Union[str, None] = None): Eğer kodunuz `async` / `await` kullanıyorsa, `async def` kullanın: -```Python hl_lines="9 14" -from typing import Union - +```Python hl_lines="7 12" from fastapi import FastAPI app = FastAPI() @@ -197,7 +193,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): +async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -288,9 +284,7 @@ Alternatif otomatik dokümantasyonu göreceksiniz ( Date: Thu, 5 Feb 2026 15:57:15 +0000 Subject: [PATCH 014/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a91ea5a2b..e85e3aa87 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -14,6 +14,7 @@ hide: ### Translations +* 🌐 Update translations for tr (update-outdated). PR [#14831](https://github.com/fastapi/fastapi/pull/14831) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for tr (add-missing). PR [#14790](https://github.com/fastapi/fastapi/pull/14790) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for fr (update-outdated). PR [#14826](https://github.com/fastapi/fastapi/pull/14826) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for zh-hant (update-outdated). PR [#14825](https://github.com/fastapi/fastapi/pull/14825) by [@tiangolo](https://github.com/tiangolo). From 4fe06cc24a9e502be2e2aeef9eeb236cc959dea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 08:02:22 -0800 Subject: [PATCH 015/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20es=20(update-outdated)=20(#14832)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/es/docs/advanced/wsgi.md | 22 ++++++++++++++++--- docs/es/docs/deployment/docker.md | 6 ++--- docs/es/docs/index.md | 18 +++++---------- docs/es/docs/tutorial/body-multiple-params.md | 6 ++--- .../tutorial/path-operation-configuration.md | 4 ++-- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/docs/es/docs/advanced/wsgi.md b/docs/es/docs/advanced/wsgi.md index d5113250a..ae31185ee 100644 --- a/docs/es/docs/advanced/wsgi.md +++ b/docs/es/docs/advanced/wsgi.md @@ -2,17 +2,33 @@ Puedes montar aplicaciones WSGI como viste con [Sub Aplicaciones - Mounts](sub-applications.md){.internal-link target=_blank}, [Detrás de un Proxy](behind-a-proxy.md){.internal-link target=_blank}. -Para eso, puedes usar `WSGIMiddleware` y usarlo para envolver tu aplicación WSGI, por ejemplo, Flask, Django, etc. +Para eso, puedes usar el `WSGIMiddleware` y usarlo para envolver tu aplicación WSGI, por ejemplo, Flask, Django, etc. ## Usando `WSGIMiddleware` { #using-wsgimiddleware } -Necesitas importar `WSGIMiddleware`. +/// info | Información + +Esto requiere instalar `a2wsgi`, por ejemplo con `pip install a2wsgi`. + +/// + +Necesitas importar `WSGIMiddleware` de `a2wsgi`. Luego envuelve la aplicación WSGI (p. ej., Flask) con el middleware. Y luego móntala bajo un path. -{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *} +{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} + +/// note | Nota + +Anteriormente, se recomendaba usar `WSGIMiddleware` de `fastapi.middleware.wsgi`, pero ahora está deprecado. + +Se aconseja usar el paquete `a2wsgi` en su lugar. El uso sigue siendo el mismo. + +Solo asegúrate de tener instalado el paquete `a2wsgi` e importar `WSGIMiddleware` correctamente desde `a2wsgi`. + +/// ## Revisa { #check-it } diff --git a/docs/es/docs/deployment/docker.md b/docs/es/docs/deployment/docker.md index 114a62ec3..9a0b88955 100644 --- a/docs/es/docs/deployment/docker.md +++ b/docs/es/docs/deployment/docker.md @@ -145,8 +145,6 @@ Existen otros formatos y herramientas para definir e instalar dependencias de pa * Crea un archivo `main.py` con: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -158,7 +156,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -572,7 +570,7 @@ Si tienes una configuración simple, con un **contenedor único** que luego inic ### Imagen Base de Docker { #base-docker-image } -Solía haber una imagen official de Docker de FastAPI: tiangolo/uvicorn-gunicorn-fastapi-docker. Pero ahora está obsoleta. ⛔️ +Solía haber una imagen official de Docker de FastAPI: tiangolo/uvicorn-gunicorn-fastapi. Pero ahora está obsoleta. ⛔️ Probablemente **no** deberías usar esta imagen base de Docker (o cualquier otra similar). diff --git a/docs/es/docs/index.md b/docs/es/docs/index.md index 14fa07e41..ffea0ed54 100644 --- a/docs/es/docs/index.md +++ b/docs/es/docs/index.md @@ -161,8 +161,6 @@ $ pip install "fastapi[standard]" Crea un archivo `main.py` con: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -174,7 +172,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -183,9 +181,7 @@ def read_item(item_id: int, q: Union[str, None] = None): Si tu código usa `async` / `await`, usa `async def`: -```Python hl_lines="9 14" -from typing import Union - +```Python hl_lines="7 12" from fastapi import FastAPI app = FastAPI() @@ -197,7 +193,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): +async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -288,9 +284,7 @@ Ahora modifica el archivo `main.py` para recibir un body desde un request `PUT`. Declara el body usando tipos estándar de Python, gracias a Pydantic. -```Python hl_lines="4 9-12 25-27" -from typing import Union - +```Python hl_lines="2 7-10 23-25" from fastapi import FastAPI from pydantic import BaseModel @@ -300,7 +294,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Union[bool, None] = None + is_offer: bool | None = None @app.get("/") @@ -309,7 +303,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} diff --git a/docs/es/docs/tutorial/body-multiple-params.md b/docs/es/docs/tutorial/body-multiple-params.md index 57cec1674..c52486f9b 100644 --- a/docs/es/docs/tutorial/body-multiple-params.md +++ b/docs/es/docs/tutorial/body-multiple-params.md @@ -101,13 +101,13 @@ Por supuesto, también puedes declarar parámetros adicionales de query siempre Como, por defecto, los valores singulares se interpretan como parámetros de query, no tienes que añadir explícitamente un `Query`, solo puedes hacer: ```Python -q: Union[str, None] = None +q: str | None = None ``` -O en Python 3.10 y superior: +O en Python 3.9: ```Python -q: str | None = None +q: Union[str, None] = None ``` Por ejemplo: diff --git a/docs/es/docs/tutorial/path-operation-configuration.md b/docs/es/docs/tutorial/path-operation-configuration.md index 945574b9e..f57b322fb 100644 --- a/docs/es/docs/tutorial/path-operation-configuration.md +++ b/docs/es/docs/tutorial/path-operation-configuration.md @@ -52,7 +52,7 @@ En estos casos, podría tener sentido almacenar las tags en un `Enum`. Puedes añadir un `summary` y `description`: -{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *} +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} ## Descripción desde docstring { #description-from-docstring } @@ -70,7 +70,7 @@ Será usado en la documentación interactiva: Puedes especificar la descripción del response con el parámetro `response_description`: -{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *} +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} /// info | Información From afb44f0ff83fef758144639a5e284e2f43d25eb9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 16:03:19 +0000 Subject: [PATCH 016/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e85e3aa87..c9470e138 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -14,6 +14,7 @@ hide: ### Translations +* 🌐 Update translations for es (update-outdated). PR [#14832](https://github.com/fastapi/fastapi/pull/14832) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for tr (update-outdated). PR [#14831](https://github.com/fastapi/fastapi/pull/14831) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for tr (add-missing). PR [#14790](https://github.com/fastapi/fastapi/pull/14790) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for fr (update-outdated). PR [#14826](https://github.com/fastapi/fastapi/pull/14826) by [@tiangolo](https://github.com/tiangolo). From 136d5f0a56493e289b58838abc82479d50b61df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 08:04:47 -0800 Subject: [PATCH 017/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20ko=20(update-outdated)=20(#14835)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- .../ko/docs/advanced/advanced-dependencies.md | 8 ++--- docs/ko/docs/advanced/wsgi.md | 36 +++++++++++++------ docs/ko/docs/deployment/docker.md | 12 +++---- docs/ko/docs/index.md | 28 ++++++--------- docs/ko/docs/tutorial/body-multiple-params.md | 11 +++--- .../tutorial/path-operation-configuration.md | 8 ++--- 6 files changed, 56 insertions(+), 47 deletions(-) diff --git a/docs/ko/docs/advanced/advanced-dependencies.md b/docs/ko/docs/advanced/advanced-dependencies.md index 04e557d15..fe1606258 100644 --- a/docs/ko/docs/advanced/advanced-dependencies.md +++ b/docs/ko/docs/advanced/advanced-dependencies.md @@ -79,13 +79,13 @@ checker(q="somequery") ### `yield`와 `scope`가 있는 의존성 { #dependencies-with-yield-and-scope } -0.121.0 버전에서 FastAPI는 `yield`가 있는 의존성에 대해 `Depends(scope="function")` 지원을 추가했습니다. +0.121.0 버전에서 FastAPI는 `Depends(scope="function")` 지원을 추가했습니다. `Depends(scope="function")`를 사용하면, `yield` 이후의 종료 코드는 *경로 처리 함수*가 끝난 직후(클라이언트에 응답이 반환되기 전)에 실행됩니다. 그리고 `Depends(scope="request")`(기본값)를 사용하면, `yield` 이후의 종료 코드는 응답이 전송된 후에 실행됩니다. -자세한 내용은 [Dependencies with `yield` - Early exit and `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope) 문서를 참고하세요. +자세한 내용은 [`yield`가 있는 의존성 - 조기 종료와 `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope) 문서를 참고하세요. ### `yield`가 있는 의존성과 `StreamingResponse`, 기술 세부사항 { #dependencies-with-yield-and-streamingresponse-technical-details } @@ -133,7 +133,7 @@ SQLModel(또는 SQLAlchemy)을 사용하면서 이런 특정 사용 사례가 그러면 세션이 데이터베이스 연결을 해제하여, 다른 요청들이 이를 사용할 수 있게 됩니다. -`yield`가 있는 의존성에서 조기 종료가 필요한 다른 사용 사례가 있다면, 여러분의 구체적인 사용 사례와 `yield`가 있는 의존성에 대한 조기 종료가 어떤 점에서 이득이 되는지를 포함해 GitHub Discussion Question을 생성해 주세요. +`yield`가 있는 의존성에서 조기 종료가 필요한 다른 사용 사례가 있다면, 여러분의 구체적인 사용 사례와 `yield`가 있는 의존성에 대한 조기 종료가 어떤 점에서 이득이 되는지를 포함해 GitHub Discussions 질문을 생성해 주세요. `yield`가 있는 의존성에서 조기 종료에 대한 설득력 있는 사용 사례가 있다면, 조기 종료를 선택적으로 활성화할 수 있는 새로운 방법을 추가하는 것을 고려하겠습니다. @@ -145,7 +145,7 @@ FastAPI 0.110.0 이전에는 `yield`가 있는 의존성을 사용한 다음 그 ### 백그라운드 태스크와 `yield`가 있는 의존성, 기술 세부사항 { #background-tasks-and-dependencies-with-yield-technical-details } -FastAPI 0.106.0 이전에는 `yield` 이후에 예외를 발생시키는 것이 불가능했습니다. `yield`가 있는 의존성의 종료 코드는 응답이 전송된 *후에* 실행되었기 때문에, [Exception Handlers](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}가 이미 실행된 뒤였습니다. +FastAPI 0.106.0 이전에는 `yield` 이후에 예외를 발생시키는 것이 불가능했습니다. `yield`가 있는 의존성의 종료 코드는 응답이 전송된 *후에* 실행되었기 때문에, [예외 핸들러](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}가 이미 실행된 뒤였습니다. 이는 주로 백그라운드 태스크 안에서 의존성이 "yield"한 동일한 객체들을 사용할 수 있게 하기 위한 설계였습니다. 백그라운드 태스크가 끝난 뒤에 종료 코드가 실행되었기 때문입니다. diff --git a/docs/ko/docs/advanced/wsgi.md b/docs/ko/docs/advanced/wsgi.md index 89cf57cfe..5e0e87c5e 100644 --- a/docs/ko/docs/advanced/wsgi.md +++ b/docs/ko/docs/advanced/wsgi.md @@ -1,32 +1,48 @@ -# WSGI 포함하기 - Flask, Django 그 외 { #including-wsgi-flask-django-others } +# WSGI 포함하기 - Flask, Django 등 { #including-wsgi-flask-django-others } -[서브 응용 프로그램 - 마운트](sub-applications.md){.internal-link target=_blank}, [프록시 뒤편에서](behind-a-proxy.md){.internal-link target=_blank}에서 보았듯이 WSGI 응용 프로그램들을 마운트 할 수 있습니다. +[서브 애플리케이션 - 마운트](sub-applications.md){.internal-link target=_blank}, [프록시 뒤에서](behind-a-proxy.md){.internal-link target=_blank}에서 본 것처럼 WSGI 애플리케이션을 마운트할 수 있습니다. -이를 위해 `WSGIMiddleware`를 사용해 WSGI 응용 프로그램(예: Flask, Django 등)을 감쌀 수 있습니다. +이를 위해 `WSGIMiddleware`를 사용해 WSGI 애플리케이션(예: Flask, Django 등)을 감쌀 수 있습니다. ## `WSGIMiddleware` 사용하기 { #using-wsgimiddleware } -`WSGIMiddleware`를 불러와야 합니다. +/// info | 정보 -그런 다음, WSGI(예: Flask) 응용 프로그램을 미들웨어로 포장합니다. +이를 사용하려면 `a2wsgi`를 설치해야 합니다. 예: `pip install a2wsgi` -그 후, 해당 경로에 마운트합니다. +/// -{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *} +`a2wsgi`에서 `WSGIMiddleware`를 import 해야 합니다. + +그런 다음, WSGI(예: Flask) 애플리케이션을 미들웨어로 감쌉니다. + +그리고 해당 경로에 마운트합니다. + +{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} + +/// note | 참고 + +이전에 `fastapi.middleware.wsgi`의 `WSGIMiddleware` 사용을 권장했지만 이제는 더 이상 권장되지 않습니다. + +대신 `a2wsgi` 패키지 사용을 권장합니다. 사용 방법은 동일합니다. + +단, `a2wsgi` 패키지가 설치되어 있고 `a2wsgi`에서 `WSGIMiddleware`를 올바르게 import 하는지만 확인하세요. + +/// ## 확인하기 { #check-it } -이제 `/v1/` 경로에 있는 모든 요청은 Flask 응용 프로그램에서 처리됩니다. +이제 `/v1/` 경로에 있는 모든 요청은 Flask 애플리케이션에서 처리됩니다. 그리고 나머지는 **FastAPI**에 의해 처리됩니다. -실행하면 http://localhost:8000/v1/으로 이동해서 Flask의 응답을 볼 수 있습니다: +실행하고 http://localhost:8000/v1/로 이동하면 Flask의 응답을 볼 수 있습니다: ```txt Hello, World from Flask! ``` -그리고 다음으로 이동하면 http://localhost:8000/v2 **FastAPI**의 응답을 볼 수 있습니다: +그리고 http://localhost:8000/v2로 이동하면 **FastAPI**의 응답을 볼 수 있습니다: ```JSON { diff --git a/docs/ko/docs/deployment/docker.md b/docs/ko/docs/deployment/docker.md index be04c923a..20e341c26 100644 --- a/docs/ko/docs/deployment/docker.md +++ b/docs/ko/docs/deployment/docker.md @@ -145,8 +145,6 @@ Successfully installed fastapi pydantic * 다음 내용으로 `main.py` 파일을 만듭니다: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -158,7 +156,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -245,14 +243,14 @@ Docker 지시어 cluster를 사용한다면, 각 컨테이너에서(**워커를 사용하는 Uvicorn** 같은) **프로세스 매니저**를 쓰는 대신, **클러스터 레벨**에서 **복제를 처리**하고 싶을 가능성이 큽니다. +**Kubernetes**, Docker Swarm Mode, Nomad 등의 복잡한 시스템으로 여러 머신에 분산된 컨테이너를 관리하는 클러스터를 사용한다면, 각 컨테이너에서(**워커를 사용하는 Uvicorn** 같은) **프로세스 매니저**를 쓰는 대신, **클러스터 레벨**에서 **복제를 처리**하고 싶을 가능성이 큽니다. Kubernetes 같은 분산 컨테이너 관리 시스템은 보통 들어오는 요청에 대한 **로드 밸런싱**을 지원하면서도, **컨테이너 복제**를 처리하는 통합된 방법을 가지고 있습니다. 모두 **클러스터 레벨**에서요. @@ -580,7 +578,7 @@ Kubernetes를 사용한다면, 이는 아마도 자동완성. 적은 디버깅 시간. +* **직관적**: 훌륭한 편집기 지원. 모든 곳에서 자동완성. 적은 디버깅 시간. * **쉬움**: 쉽게 사용하고 배우도록 설계. 적은 문서 읽기 시간. * **짧음**: 코드 중복 최소화. 각 매개변수 선언의 여러 기능. 적은 버그. * **견고함**: 준비된 프로덕션 용 코드를 얻으십시오. 자동 대화형 문서와 함께. @@ -127,7 +127,7 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트 -웹 API 대신 터미널에서 사용할 CLI 앱을 만들고 있다면, **Typer**를 확인해 보십시오. +웹 API 대신 터미널에서 사용할 CLI 앱을 만들고 있다면, **Typer**를 확인해 보십시오. **Typer**는 FastAPI의 동생입니다. 그리고 **CLI를 위한 FastAPI**가 되기 위해 생겼습니다. ⌨️ 🚀 @@ -161,8 +161,6 @@ $ pip install "fastapi[standard]" 다음 내용으로 `main.py` 파일을 만드십시오: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -174,7 +172,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -183,9 +181,7 @@ def read_item(item_id: int, q: Union[str, None] = None): 여러분의 코드가 `async` / `await`을 사용한다면, `async def`를 사용하십시오: -```Python hl_lines="9 14" -from typing import Union - +```Python hl_lines="7 12" from fastapi import FastAPI app = FastAPI() @@ -197,7 +193,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): +async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -288,9 +284,7 @@ INFO: Application startup complete. Pydantic 덕분에 표준 Python 타입을 사용해 본문을 선언합니다. -```Python hl_lines="4 9-12 25-27" -from typing import Union - +```Python hl_lines="2 7-10 23-25" from fastapi import FastAPI from pydantic import BaseModel @@ -300,7 +294,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Union[bool, None] = None + is_offer: bool | None = None @app.get("/") @@ -309,7 +303,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} @@ -374,7 +368,7 @@ item: Item * 데이터 검증: * 데이터가 유효하지 않을 때 자동으로 생성하는 명확한 에러. * 깊이 중첩된 JSON 객체에 대한 유효성 검사. -* 입력 데이터 변환: 네트워크에서 파이썬 데이터 및 타입으로 전송. 읽을 수 있는 것들: +* 입력 데이터 변환: 네트워크에서 파이썬 데이터 및 타입으로 전송. 읽을 수 있는 것들: * JSON. * 경로 매개변수. * 쿼리 매개변수. @@ -382,7 +376,7 @@ item: Item * 헤더. * 폼(Forms). * 파일. -* 출력 데이터 변환: 파이썬 데이터 및 타입을 네트워크 데이터로 전환(JSON 형식으로): +* 출력 데이터 변환: 파이썬 데이터 및 타입을 네트워크 데이터로 전환(JSON 형식으로): * 파이썬 타입 변환 (`str`, `int`, `float`, `bool`, `list`, 등). * `datetime` 객체. * `UUID` 객체. @@ -445,7 +439,7 @@ item: Item * 서로 다른 장소에서 **매개변수** 선언: **헤더**, **쿠키**, **폼 필드** 그리고 **파일**. * `maximum_length` 또는 `regex`처럼 **유효성 제약**하는 방법. -* 강력하고 사용하기 쉬운 **의존성 주입** 시스템. +* 강력하고 사용하기 쉬운 **의존성 주입** 시스템. * **OAuth2** 지원을 포함한 **JWT tokens** 및 **HTTP Basic**을 갖는 보안과 인증. * (Pydantic 덕분에) **깊은 중첩 JSON 모델**을 선언하는데 더 진보한 (하지만 마찬가지로 쉬운) 기술. * Strawberry 및 기타 라이브러리와의 **GraphQL** 통합. diff --git a/docs/ko/docs/tutorial/body-multiple-params.md b/docs/ko/docs/tutorial/body-multiple-params.md index 701351e63..bebdffed8 100644 --- a/docs/ko/docs/tutorial/body-multiple-params.md +++ b/docs/ko/docs/tutorial/body-multiple-params.md @@ -102,15 +102,16 @@ 기본적으로 단일 값은 쿼리 매개변수로 해석되므로, 명시적으로 `Query`를 추가할 필요 없이 이렇게 하면 됩니다: +```Python +q: str | None = None +``` + +또는 Python 3.9에서는: + ```Python q: Union[str, None] = None ``` -또는 Python 3.10 이상에서는: - -```Python -q: str | None = None -``` 예를 들어: diff --git a/docs/ko/docs/tutorial/path-operation-configuration.md b/docs/ko/docs/tutorial/path-operation-configuration.md index b8a87667a..baef3fb40 100644 --- a/docs/ko/docs/tutorial/path-operation-configuration.md +++ b/docs/ko/docs/tutorial/path-operation-configuration.md @@ -52,11 +52,11 @@ `summary`와 `description`을 추가할 수 있습니다: -{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *} +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} ## 독스트링으로 만든 설명 { #description-from-docstring } -설명은 보통 길어지고 여러 줄에 걸쳐있기 때문에, *경로 처리* 설명을 함수 docstring에 선언할 수 있으며, **FastAPI**는 그곳에서 이를 읽습니다. +설명은 보통 길어지고 여러 줄에 걸쳐있기 때문에, *경로 처리* 설명을 함수 docstring에 선언할 수 있으며, **FastAPI**는 그곳에서 이를 읽습니다. 독스트링에는 Markdown을 작성할 수 있으며, (독스트링의 들여쓰기를 고려하여) 올바르게 해석되고 표시됩니다. @@ -70,7 +70,7 @@ `response_description` 매개변수로 응답에 관한 설명을 명시할 수 있습니다: -{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *} +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} /// info | 정보 @@ -90,7 +90,7 @@ OpenAPI는 각 *경로 처리*가 응답에 관한 설명을 요구할 것을 ## *경로 처리* 지원중단하기 { #deprecate-a-path-operation } -*경로 처리*를 제거하지 않고 deprecated로 표시해야 한다면, `deprecated` 매개변수를 전달하면 됩니다: +*경로 처리*를 제거하지 않고 deprecated로 표시해야 한다면, `deprecated` 매개변수를 전달하면 됩니다: {* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} From 2224b4e63b13a97a3132c7c1ff0cc2476d02527b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 08:05:03 -0800 Subject: [PATCH 018/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20pt=20(update-outdated)=20(#14833)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- .../pt/docs/advanced/advanced-dependencies.md | 2 +- docs/pt/docs/advanced/wsgi.md | 20 ++++++++++++-- docs/pt/docs/deployment/docker.md | 6 ++--- docs/pt/docs/index.md | 26 +++++++------------ docs/pt/docs/tutorial/body-multiple-params.md | 6 ++--- .../tutorial/path-operation-configuration.md | 4 +-- 6 files changed, 36 insertions(+), 28 deletions(-) diff --git a/docs/pt/docs/advanced/advanced-dependencies.md b/docs/pt/docs/advanced/advanced-dependencies.md index 1ad9ea617..959a7f3c2 100644 --- a/docs/pt/docs/advanced/advanced-dependencies.md +++ b/docs/pt/docs/advanced/advanced-dependencies.md @@ -148,7 +148,7 @@ Antes do FastAPI 0.106.0, lançar exceções após o `yield` não era possível, Isso foi projetado assim principalmente para permitir o uso dos mesmos objetos "yielded" por dependências dentro de tarefas em segundo plano, porque o código de saída seria executado depois que as tarefas em segundo plano fossem concluídas. -Isso foi alterado no FastAPI 0.106.0 com a intenção de não manter recursos enquanto se espera a resposta percorrer a rede. +Isso foi alterado no FastAPI 0.106.0 com a intenção de não manter recursos enquanto se espera a response percorrer a rede. /// tip | Dica diff --git a/docs/pt/docs/advanced/wsgi.md b/docs/pt/docs/advanced/wsgi.md index c3c21d430..99b783cdb 100644 --- a/docs/pt/docs/advanced/wsgi.md +++ b/docs/pt/docs/advanced/wsgi.md @@ -6,13 +6,29 @@ Para isso, você pode utilizar o `WSGIMiddleware` para encapsular a sua aplicaç ## Usando `WSGIMiddleware` { #using-wsgimiddleware } -Você precisa importar o `WSGIMiddleware`. +/// info | Informação + +Isso requer instalar `a2wsgi`, por exemplo com `pip install a2wsgi`. + +/// + +Você precisa importar o `WSGIMiddleware` de `a2wsgi`. Em seguida, encapsule a aplicação WSGI (e.g. Flask) com o middleware. E então monte isso sob um path. -{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *} +{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} + +/// note | Nota + +Anteriormente, recomendava-se usar `WSGIMiddleware` de `fastapi.middleware.wsgi`, mas agora está descontinuado. + +É aconselhável usar o pacote `a2wsgi` em seu lugar. O uso permanece o mesmo. + +Apenas certifique-se de que o pacote `a2wsgi` está instalado e importe `WSGIMiddleware` corretamente de `a2wsgi`. + +/// ## Confira { #check-it } diff --git a/docs/pt/docs/deployment/docker.md b/docs/pt/docs/deployment/docker.md index b26a69b54..d47a15394 100644 --- a/docs/pt/docs/deployment/docker.md +++ b/docs/pt/docs/deployment/docker.md @@ -145,8 +145,6 @@ Há outros formatos e ferramentas para definir e instalar dependências de pacot * Crie um arquivo `main.py` com: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -158,7 +156,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -572,7 +570,7 @@ Se você tiver uma configuração simples, com um **único contêiner** que ent ### Imagem Docker base { #base-docker-image } -Antes havia uma imagem oficial do FastAPI para Docker: tiangolo/uvicorn-gunicorn-fastapi. Mas agora ela está descontinuada. ⛔️ +Antes havia uma imagem oficial do FastAPI para Docker: tiangolo/uvicorn-gunicorn-fastapi-docker. Mas agora ela está descontinuada. ⛔️ Você provavelmente **não** deve usar essa imagem base do Docker (ou qualquer outra semelhante). diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md index 4e3be586d..e61cc2e2f 100644 --- a/docs/pt/docs/index.md +++ b/docs/pt/docs/index.md @@ -33,7 +33,7 @@ --- -FastAPI é um moderno e rápido (alta performance) _framework web_ para construção de APIs com Python, baseado nos _type hints_ padrões do Python. +FastAPI é um moderno e rápido (alta performance) framework web para construção de APIs com Python, baseado nos type hints padrões do Python. Os recursos chave são: @@ -161,8 +161,6 @@ $ pip install "fastapi[standard]" Crie um arquivo `main.py` com: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -174,7 +172,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -183,9 +181,7 @@ def read_item(item_id: int, q: Union[str, None] = None): Se seu código utiliza `async` / `await`, use `async def`: -```Python hl_lines="9 14" -from typing import Union - +```Python hl_lines="7 12" from fastapi import FastAPI app = FastAPI() @@ -197,7 +193,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): +async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -288,9 +284,7 @@ Agora modifique o arquivo `main.py` para receber um corpo de uma requisição `P Declare o corpo utilizando tipos padrão Python, graças ao Pydantic. -```Python hl_lines="4 9-12 25-27" -from typing import Union - +```Python hl_lines="2 7-10 23-25" from fastapi import FastAPI from pydantic import BaseModel @@ -300,7 +294,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Union[bool, None] = None + is_offer: bool | None = None @app.get("/") @@ -309,7 +303,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} @@ -411,7 +405,7 @@ Voltando ao código do exemplo anterior, **FastAPI** irá: * Documentar tudo com OpenAPI, que poderá ser usado por: * Sistemas de documentação interativos. * Sistemas de clientes de geração de código automáticos, para muitas linguagens. -* Fornecer diretamente 2 interfaces _web_ de documentação interativa. +* Fornecer diretamente 2 interfaces web de documentação interativa. --- @@ -510,7 +504,7 @@ Siga os tutoriais do seu provedor de nuvem para implantar aplicações FastAPI c ## Performance { #performance } -Testes de performance da _Independent TechEmpower_ mostram aplicações **FastAPI** rodando sob Uvicorn como um dos _frameworks_ Python mais rápidos disponíveis, somente atrás de Starlette e Uvicorn (utilizados internamente pelo FastAPI). (*) +Testes de performance da Independent TechEmpower mostram aplicações **FastAPI** rodando sob Uvicorn como um dos frameworks Python mais rápidos disponíveis, somente atrás de Starlette e Uvicorn (utilizados internamente pelo FastAPI). (*) Para entender mais sobre isso, veja a seção Comparações. @@ -530,7 +524,7 @@ Utilizado pelo Starlette: * httpx - Obrigatório caso você queira utilizar o `TestClient`. * jinja2 - Obrigatório se você quer utilizar a configuração padrão de templates. -* python-multipart - Obrigatório se você deseja suporte a "parsing" de formulário, com `request.form()`. +* python-multipart - Obrigatório se você deseja suporte a "parsing" de formulário, com `request.form()`. Utilizado pelo FastAPI: diff --git a/docs/pt/docs/tutorial/body-multiple-params.md b/docs/pt/docs/tutorial/body-multiple-params.md index 3cba04912..24f35b210 100644 --- a/docs/pt/docs/tutorial/body-multiple-params.md +++ b/docs/pt/docs/tutorial/body-multiple-params.md @@ -101,13 +101,13 @@ Obviamente, você também pode declarar parâmetros de consulta assim que você Dado que, por padrão, valores singulares são interpretados como parâmetros de consulta, você não precisa explicitamente adicionar uma `Query`, você pode somente: ```Python -q: Union[str, None] = None +q: str | None = None ``` -Ou como em Python 3.10 e versões superiores: +Ou em Python 3.9: ```Python -q: str | None = None +q: Union[str, None] = None ``` Por exemplo: diff --git a/docs/pt/docs/tutorial/path-operation-configuration.md b/docs/pt/docs/tutorial/path-operation-configuration.md index 84eebc128..2527c2892 100644 --- a/docs/pt/docs/tutorial/path-operation-configuration.md +++ b/docs/pt/docs/tutorial/path-operation-configuration.md @@ -52,7 +52,7 @@ Nestes casos, pode fazer sentido armazenar as tags em um `Enum`. Você pode adicionar um `summary` e uma `description`: -{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *} +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} ## Descrição do docstring { #description-from-docstring } @@ -70,7 +70,7 @@ Ela será usada nas documentações interativas: Você pode especificar a descrição da resposta com o parâmetro `response_description`: -{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *} +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} /// info | Informação From 3578270af481d006bac53e1659640ce0bf257dba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 16:05:18 +0000 Subject: [PATCH 019/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c9470e138..7da59e9f2 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -14,6 +14,7 @@ hide: ### Translations +* 🌐 Update translations for ko (update-outdated). PR [#14835](https://github.com/fastapi/fastapi/pull/14835) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for es (update-outdated). PR [#14832](https://github.com/fastapi/fastapi/pull/14832) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for tr (update-outdated). PR [#14831](https://github.com/fastapi/fastapi/pull/14831) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for tr (add-missing). PR [#14790](https://github.com/fastapi/fastapi/pull/14790) by [@tiangolo](https://github.com/tiangolo). From b892c3126c270d5f2b695034703bdae2a2f148ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 08:06:07 -0800 Subject: [PATCH 020/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20de=20(update-outdated)=20(#14836)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/de/docs/advanced/wsgi.md | 20 +++++++++++++++++-- docs/de/docs/deployment/docker.md | 4 +--- docs/de/docs/index.md | 18 ++++++----------- docs/de/docs/tutorial/body-multiple-params.md | 8 ++++---- .../tutorial/path-operation-configuration.md | 4 ++-- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/docs/de/docs/advanced/wsgi.md b/docs/de/docs/advanced/wsgi.md index 3cd776a6a..0090883ce 100644 --- a/docs/de/docs/advanced/wsgi.md +++ b/docs/de/docs/advanced/wsgi.md @@ -6,13 +6,29 @@ Dazu können Sie die `WSGIMiddleware` verwenden und damit Ihre WSGI-Anwendung wr ## `WSGIMiddleware` verwenden { #using-wsgimiddleware } -Sie müssen `WSGIMiddleware` importieren. +/// info | Info + +Dafür muss `a2wsgi` installiert sein, z. B. mit `pip install a2wsgi`. + +/// + +Sie müssen `WSGIMiddleware` aus `a2wsgi` importieren. Wrappen Sie dann die WSGI-Anwendung (z. B. Flask) mit der Middleware. Und dann mounten Sie das auf einem Pfad. -{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *} +{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} + +/// note | Hinweis + +Früher wurde empfohlen, `WSGIMiddleware` aus `fastapi.middleware.wsgi` zu verwenden, dies ist jetzt deprecatet. + +Stattdessen wird empfohlen, das Paket `a2wsgi` zu verwenden. Die Nutzung bleibt gleich. + +Stellen Sie lediglich sicher, dass das Paket `a2wsgi` installiert ist und importieren Sie `WSGIMiddleware` korrekt aus `a2wsgi`. + +/// ## Es testen { #check-it } diff --git a/docs/de/docs/deployment/docker.md b/docs/de/docs/deployment/docker.md index d4b74635d..1e28efe52 100644 --- a/docs/de/docs/deployment/docker.md +++ b/docs/de/docs/deployment/docker.md @@ -145,8 +145,6 @@ Es gibt andere Formate und Tools zum Definieren und Installieren von Paketabhän * Erstellen Sie eine `main.py`-Datei mit: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -158,7 +156,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md index 11fb6c983..3ce5cb27f 100644 --- a/docs/de/docs/index.md +++ b/docs/de/docs/index.md @@ -161,8 +161,6 @@ $ pip install "fastapi[standard]" Erstellen Sie eine Datei `main.py` mit: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -174,7 +172,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -183,9 +181,7 @@ def read_item(item_id: int, q: Union[str, None] = None): Wenn Ihr Code `async` / `await` verwendet, benutzen Sie `async def`: -```Python hl_lines="9 14" -from typing import Union - +```Python hl_lines="7 12" from fastapi import FastAPI app = FastAPI() @@ -197,7 +193,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): +async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -288,9 +284,7 @@ Sie sehen die alternative automatische Dokumentation (bereitgestellt von Requestbody-Deklarationen an. +Nun, da wir gesehen haben, wie `Path` und `Query` verwendet werden, schauen wir uns fortgeschrittenere Verwendungsmöglichkeiten von Requestbody-Deklarationen an. ## `Path`-, `Query`- und Body-Parameter vermischen { #mix-path-query-and-body-parameters } @@ -101,13 +101,13 @@ Natürlich können Sie auch, wann immer Sie das brauchen, weitere Query-Paramete Da einfache Werte standardmäßig als Query-Parameter interpretiert werden, müssen Sie `Query` nicht explizit hinzufügen, Sie können einfach schreiben: ```Python -q: Union[str, None] = None +q: str | None = None ``` -Oder in Python 3.10 und darüber: +Oder in Python 3.9: ```Python -q: str | None = None +q: Union[str, None] = None ``` Zum Beispiel: diff --git a/docs/de/docs/tutorial/path-operation-configuration.md b/docs/de/docs/tutorial/path-operation-configuration.md index 3427b3052..a06c85e57 100644 --- a/docs/de/docs/tutorial/path-operation-configuration.md +++ b/docs/de/docs/tutorial/path-operation-configuration.md @@ -52,7 +52,7 @@ In diesem Fall macht es Sinn, die Tags in einem `Enum` zu speichern. Sie können eine `summary` und eine `description` hinzufügen: -{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *} +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} ## Beschreibung mittels Docstring { #description-from-docstring } @@ -70,7 +70,7 @@ Es wird in der interaktiven Dokumentation verwendet: Sie können die Response mit dem Parameter `response_description` beschreiben: -{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *} +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} /// info | Info From b82993643ecb709f3d732794c93f2562ae64731e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 16:06:09 +0000 Subject: [PATCH 021/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7da59e9f2..52aa38b8b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -14,6 +14,7 @@ hide: ### Translations +* 🌐 Update translations for pt (update-outdated). PR [#14833](https://github.com/fastapi/fastapi/pull/14833) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for ko (update-outdated). PR [#14835](https://github.com/fastapi/fastapi/pull/14835) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for es (update-outdated). PR [#14832](https://github.com/fastapi/fastapi/pull/14832) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for tr (update-outdated). PR [#14831](https://github.com/fastapi/fastapi/pull/14831) by [@tiangolo](https://github.com/tiangolo). From 2838dcb4a832ef65c3a98463593dda0aea0b982c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 16:06:44 +0000 Subject: [PATCH 022/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 52aa38b8b..e4864ce0f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -14,6 +14,7 @@ hide: ### Translations +* 🌐 Update translations for de (update-outdated). PR [#14836](https://github.com/fastapi/fastapi/pull/14836) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for pt (update-outdated). PR [#14833](https://github.com/fastapi/fastapi/pull/14833) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for ko (update-outdated). PR [#14835](https://github.com/fastapi/fastapi/pull/14835) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for es (update-outdated). PR [#14832](https://github.com/fastapi/fastapi/pull/14832) by [@tiangolo](https://github.com/tiangolo). From 457cd75c234bce2c1ec53abe9a6a1f6def0fcd1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 08:11:07 -0800 Subject: [PATCH 023/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20fr=20(translate-page)=20(#14837)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/fr/docs/deployment/manually.md | 198 +++++++++++++--------------- 1 file changed, 94 insertions(+), 104 deletions(-) diff --git a/docs/fr/docs/deployment/manually.md b/docs/fr/docs/deployment/manually.md index 3f99ccd9f..c0c388b02 100644 --- a/docs/fr/docs/deployment/manually.md +++ b/docs/fr/docs/deployment/manually.md @@ -1,33 +1,82 @@ -# Exécuter un serveur manuellement - Uvicorn +# Exécuter un serveur manuellement { #run-a-server-manually } -La principale chose dont vous avez besoin pour exécuter une application **FastAPI** sur une machine serveur distante est un programme serveur ASGI tel que **Uvicorn**. +## Utiliser la commande `fastapi run` { #use-the-fastapi-run-command } -Il existe 3 principales alternatives : +En bref, utilisez `fastapi run` pour servir votre application FastAPI : + +
+ +```console +$ fastapi run main.py + + FastAPI Starting production 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://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) +``` + +
+ +Cela fonctionnerait pour la plupart des cas. 😎 + +Vous pourriez utiliser cette commande par exemple pour démarrer votre application **FastAPI** dans un conteneur, sur un serveur, etc. + +## Serveurs ASGI { #asgi-servers } + +Allons un peu plus en détail. + +FastAPI utilise un standard pour construire des frameworks web Python et des serveurs appelé ASGI. FastAPI est un framework web ASGI. + +La principale chose dont vous avez besoin pour exécuter une application **FastAPI** (ou toute autre application ASGI) sur une machine serveur distante est un programme serveur ASGI comme **Uvicorn**, c'est celui utilisé par défaut par la commande `fastapi`. + +Il existe plusieurs alternatives, notamment : *
Uvicorn : un serveur ASGI haute performance. -* Hypercorn : un serveur - ASGI compatible avec HTTP/2 et Trio entre autres fonctionnalités. -* Daphne : le serveur ASGI - conçu pour Django Channels. +* Hypercorn : un serveur ASGI compatible avec HTTP/2 et Trio entre autres fonctionnalités. +* Daphne : le serveur ASGI conçu pour Django Channels. +* Granian : un serveur HTTP Rust pour les applications Python. +* NGINX Unit : NGINX Unit est un environnement d'exécution d'applications web léger et polyvalent. -## Machine serveur et programme serveur +## Machine serveur et programme serveur { #server-machine-and-server-program } Il y a un petit détail sur les noms à garder à l'esprit. 💡 -Le mot "**serveur**" est couramment utilisé pour désigner à la fois l'ordinateur distant/cloud (la machine physique ou virtuelle) et également le programme qui s'exécute sur cette machine (par exemple, Uvicorn). +Le mot « serveur » est couramment utilisé pour désigner à la fois l'ordinateur distant/cloud (la machine physique ou virtuelle) et également le programme qui s'exécute sur cette machine (par exemple, Uvicorn). -Gardez cela à l'esprit lorsque vous lisez "serveur" en général, cela pourrait faire référence à l'une de ces deux choses. +Gardez cela à l'esprit lorsque vous lisez « serveur » en général, cela pourrait faire référence à l'une de ces deux choses. -Lorsqu'on se réfère à la machine distante, il est courant de l'appeler **serveur**, mais aussi **machine**, **VM** (machine virtuelle), **nœud**. Tout cela fait référence à un type de machine distante, exécutant Linux, en règle générale, sur laquelle vous exécutez des programmes. +Lorsqu'on se réfère à la machine distante, il est courant de l'appeler **serveur**, mais aussi **machine**, **VM** (machine virtuelle), **nœud**. Tout cela fait référence à un type de machine distante, exécutant normalement Linux, sur laquelle vous exécutez des programmes. +## Installer le programme serveur { #install-the-server-program } -## Installer le programme serveur +Lorsque vous installez FastAPI, il est fourni avec un serveur de production, Uvicorn, et vous pouvez le démarrer avec la commande `fastapi run`. -Vous pouvez installer un serveur compatible ASGI avec : +Mais vous pouvez également installer un serveur ASGI manuellement. -//// tab | Uvicorn +Vous devez créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, l'activer, puis vous pouvez installer l'application serveur. -* Uvicorn, un serveur ASGI rapide comme l'éclair, basé sur uvloop et httptools. +Par exemple, pour installer Uvicorn :
@@ -39,39 +88,21 @@ $ pip install "uvicorn[standard]"
+Un processus similaire s'appliquerait à tout autre programme de serveur ASGI. + /// tip | Astuce En ajoutant `standard`, Uvicorn va installer et utiliser quelques dépendances supplémentaires recommandées. -Cela inclut `uvloop`, le remplaçant performant de `asyncio`, qui fournit le gros gain de performance en matière de concurrence. +Cela inclut `uvloop`, le remplaçant hautes performances de `asyncio`, qui fournit le gros gain de performance en matière de concurrence. + +Lorsque vous installez FastAPI avec quelque chose comme `pip install "fastapi[standard]"`, vous obtenez déjà `uvicorn[standard]` aussi. /// -//// +## Exécuter le programme serveur { #run-the-server-program } -//// tab | Hypercorn - -* Hypercorn, un serveur ASGI également compatible avec HTTP/2. - -
- -```console -$ pip install hypercorn - ----> 100% -``` - -
- -...ou tout autre serveur ASGI. - -//// - -## Exécutez le programme serveur - -Vous pouvez ensuite exécuter votre application de la même manière que vous l'avez fait dans les tutoriels, mais sans l'option `--reload`, par exemple : - -//// tab | Uvicorn +Si vous avez installé un serveur ASGI manuellement, vous devrez normalement passer une chaîne d'import dans un format spécial pour qu'il importe votre application FastAPI :
@@ -83,85 +114,44 @@ $ uvicorn main:app --host 0.0.0.0 --port 80
-//// +/// note | Remarque -//// tab | Hypercorn +La commande `uvicorn main:app` fait référence à : -
+* `main` : le fichier `main.py` (le « module » Python). +* `app` : l'objet créé dans `main.py` avec la ligne `app = FastAPI()`. -```console -$ hypercorn main:app --bind 0.0.0.0:80 +C'est équivalent à : -Running on 0.0.0.0:8080 over http (CTRL + C to quit) +```Python +from main import app ``` -
- -//// - -/// warning - -N'oubliez pas de supprimer l'option `--reload` si vous l'utilisiez. - - L'option `--reload` consomme beaucoup plus de ressources, est plus instable, etc. - - Cela aide beaucoup pendant le **développement**, mais vous **ne devriez pas** l'utiliser en **production**. - /// -## Hypercorn avec Trio +Chaque programme de serveur ASGI alternatif aurait une commande similaire, vous pouvez en lire plus dans leur documentation respective. -Starlette et **FastAPI** sont basés sur -AnyIO, qui les rend -compatibles avec asyncio, de la bibliothèque standard Python et -Trio. +/// warning | Alertes -Néanmoins, Uvicorn n'est actuellement compatible qu'avec asyncio, et il utilise normalement `uvloop`, le remplaçant hautes performances de `asyncio`. +Uvicorn et d'autres serveurs prennent en charge une option `--reload` utile pendant le développement. -Mais si vous souhaitez utiliser directement **Trio**, vous pouvez utiliser **Hypercorn** car il le prend en charge. ✨ +L'option `--reload` consomme beaucoup plus de ressources, est plus instable, etc. -### Installer Hypercorn avec Trio +Cela aide beaucoup pendant le **développement**, mais vous **ne devriez pas** l'utiliser en **production**. -Vous devez d'abord installer Hypercorn avec le support Trio : +/// -
+## Concepts de déploiement { #deployment-concepts } -```console -$ pip install "hypercorn[trio]" ----> 100% -``` +Ces exemples exécutent le programme serveur (par exemple Uvicorn), en démarrant **un seul processus**, à l'écoute sur toutes les IP (`0.0.0.0`) sur un port prédéfini (par exemple `80`). -
+C'est l'idée de base. Mais vous voudrez probablement vous occuper de certaines choses supplémentaires, comme : -### Exécuter avec Trio +* Sécurité - HTTPS +* Exécution au démarrage +* Redémarrages +* Réplication (le nombre de processus en cours d'exécution) +* Mémoire +* Étapes précédant le démarrage -Ensuite, vous pouvez passer l'option de ligne de commande `--worker-class` avec la valeur `trio` : - -
- -```console -$ hypercorn main:app --worker-class trio -``` - -
- -Et cela démarrera Hypercorn avec votre application en utilisant Trio comme backend. - -Vous pouvez désormais utiliser Trio en interne dans votre application. Ou mieux encore, vous pouvez utiliser AnyIO pour que votre code reste compatible avec Trio et asyncio. 🎉 - -## Concepts de déploiement - -Ces exemples lancent le programme serveur (e.g. Uvicorn), démarrant **un seul processus**, sur toutes les IPs (`0.0. -0.0`) sur un port prédéfini (par example, `80`). - -C'est l'idée de base. Mais vous vous préoccuperez probablement de certains concepts supplémentaires, tels que ... : - -* la sécurité - HTTPS -* l'exécution au démarrage -* les redémarrages -* la réplication (le nombre de processus en cours d'exécution) -* la mémoire -* les étapes précédant le démarrage - -Je vous en dirai plus sur chacun de ces concepts, sur la façon de les aborder, et donnerai quelques exemples concrets avec des stratégies pour les traiter dans les prochains chapitres. 🚀 +Je vous en dirai plus sur chacun de ces concepts, sur la manière d'y réfléchir, et donnerai quelques exemples concrets avec des stratégies pour les gérer dans les prochains chapitres. 🚀 From 464c359bb03f7ba72330198cc9f44df36194837d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 16:11:33 +0000 Subject: [PATCH 024/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e4864ce0f..2fea9e79c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -14,6 +14,7 @@ hide: ### Translations +* 🌐 Update translations for fr (translate-page). PR [#14837](https://github.com/fastapi/fastapi/pull/14837) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for de (update-outdated). PR [#14836](https://github.com/fastapi/fastapi/pull/14836) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for pt (update-outdated). PR [#14833](https://github.com/fastapi/fastapi/pull/14833) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for ko (update-outdated). PR [#14835](https://github.com/fastapi/fastapi/pull/14835) by [@tiangolo](https://github.com/tiangolo). From b49435becdf53942be248fc2ffc7f53df5ac313c Mon Sep 17 00:00:00 2001 From: Jonathan Fulton Date: Thu, 5 Feb 2026 13:23:16 -0500 Subject: [PATCH 025/195] =?UTF-8?q?=E2=9C=A8=20Allow=20`Response`=20type?= =?UTF-8?q?=20hint=20as=20dependency=20annotation=20(#14794)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Co-authored-by: Sebastián Ramírez --- fastapi/dependencies/utils.py | 5 +- tests/test_response_dependency.py | 173 ++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 tests/test_response_dependency.py diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index fc5dfed85..3037b233b 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -449,7 +449,9 @@ def analyze_param( depends = dataclasses.replace(depends, dependency=type_annotation) # Handle non-param type annotations like Request - if lenient_issubclass( + # Only apply special handling when there's no explicit Depends - if there's a Depends, + # the dependency will be called and its return value used instead of the special injection + if depends is None and lenient_issubclass( type_annotation, ( Request, @@ -460,7 +462,6 @@ def analyze_param( SecurityScopes, ), ): - assert depends is None, f"Cannot specify `Depends` for type {type_annotation!r}" assert field_info is None, ( f"Cannot specify FastAPI annotation for type {type_annotation!r}" ) diff --git a/tests/test_response_dependency.py b/tests/test_response_dependency.py new file mode 100644 index 000000000..38c359594 --- /dev/null +++ b/tests/test_response_dependency.py @@ -0,0 +1,173 @@ +"""Test using special types (Response, Request, BackgroundTasks) as dependency annotations. + +These tests verify that special FastAPI types can be used with Depends() annotations +and that the dependency injection system properly handles them. +""" + +from typing import Annotated + +from fastapi import BackgroundTasks, Depends, FastAPI, Request, Response +from fastapi.responses import JSONResponse +from fastapi.testclient import TestClient + + +def test_response_with_depends_annotated(): + """Response type hint should work with Annotated[Response, Depends(...)].""" + app = FastAPI() + + def modify_response(response: Response) -> Response: + response.headers["X-Custom"] = "modified" + return response + + @app.get("/") + def endpoint(response: Annotated[Response, Depends(modify_response)]): + return {"status": "ok"} + + client = TestClient(app) + resp = client.get("/") + + assert resp.status_code == 200 + assert resp.json() == {"status": "ok"} + assert resp.headers.get("X-Custom") == "modified" + + +def test_response_with_depends_default(): + """Response type hint should work with Response = Depends(...).""" + app = FastAPI() + + def modify_response(response: Response) -> Response: + response.headers["X-Custom"] = "modified" + return response + + @app.get("/") + def endpoint(response: Response = Depends(modify_response)): + return {"status": "ok"} + + client = TestClient(app) + resp = client.get("/") + + assert resp.status_code == 200 + assert resp.json() == {"status": "ok"} + assert resp.headers.get("X-Custom") == "modified" + + +def test_response_without_depends(): + """Regular Response injection should still work.""" + app = FastAPI() + + @app.get("/") + def endpoint(response: Response): + response.headers["X-Direct"] = "set" + return {"status": "ok"} + + client = TestClient(app) + resp = client.get("/") + + assert resp.status_code == 200 + assert resp.json() == {"status": "ok"} + assert resp.headers.get("X-Direct") == "set" + + +def test_response_dependency_chain(): + """Response dependency should work in a chain of dependencies.""" + app = FastAPI() + + def first_modifier(response: Response) -> Response: + response.headers["X-First"] = "1" + return response + + def second_modifier( + response: Annotated[Response, Depends(first_modifier)], + ) -> Response: + response.headers["X-Second"] = "2" + return response + + @app.get("/") + def endpoint(response: Annotated[Response, Depends(second_modifier)]): + return {"status": "ok"} + + client = TestClient(app) + resp = client.get("/") + + assert resp.status_code == 200 + assert resp.headers.get("X-First") == "1" + assert resp.headers.get("X-Second") == "2" + + +def test_response_dependency_returns_different_response_instance(): + """Dependency that returns a different Response instance should work. + + When a dependency returns a new Response object (e.g., JSONResponse) instead + of modifying the injected one, the returned response should be used and any + modifications to it in the endpoint should be preserved. + """ + app = FastAPI() + + def default_response() -> Response: + response = JSONResponse(content={"status": "ok"}) + response.headers["X-Custom"] = "initial" + return response + + @app.get("/") + def endpoint(response: Annotated[Response, Depends(default_response)]): + response.headers["X-Custom"] = "modified" + return response + + client = TestClient(app) + resp = client.get("/") + + assert resp.status_code == 200 + assert resp.json() == {"status": "ok"} + assert resp.headers.get("X-Custom") == "modified" + + +# Tests for Request type hint with Depends +def test_request_with_depends_annotated(): + """Request type hint should work in dependency chain.""" + app = FastAPI() + + def extract_request_info(request: Request) -> dict: + return { + "path": request.url.path, + "user_agent": request.headers.get("user-agent", "unknown"), + } + + @app.get("/") + def endpoint( + info: Annotated[dict, Depends(extract_request_info)], + ): + return info + + client = TestClient(app) + resp = client.get("/", headers={"user-agent": "test-agent"}) + + assert resp.status_code == 200 + assert resp.json() == {"path": "/", "user_agent": "test-agent"} + + +# Tests for BackgroundTasks type hint with Depends +def test_background_tasks_with_depends_annotated(): + """BackgroundTasks type hint should work with Annotated[BackgroundTasks, Depends(...)].""" + app = FastAPI() + task_results = [] + + def background_task(message: str): + task_results.append(message) + + def add_background_task(background_tasks: BackgroundTasks) -> BackgroundTasks: + background_tasks.add_task(background_task, "from dependency") + return background_tasks + + @app.get("/") + def endpoint( + background_tasks: Annotated[BackgroundTasks, Depends(add_background_task)], + ): + background_tasks.add_task(background_task, "from endpoint") + return {"status": "ok"} + + client = TestClient(app) + resp = client.get("/") + + assert resp.status_code == 200 + assert "from dependency" in task_results + assert "from endpoint" in task_results From 0dd42b746e17d33a74ea267a62eace9ccdccf345 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 18:23:44 +0000 Subject: [PATCH 026/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2fea9e79c..43600561b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Features + +* ✨ Allow `Response` type hint as dependency annotation. PR [#14794](https://github.com/fastapi/fastapi/pull/14794) by [@jonathan-fulton](https://github.com/jonathan-fulton). + ### Docs * 📝 Update docs for translations. PR [#14830](https://github.com/fastapi/fastapi/pull/14830) by [@tiangolo](https://github.com/tiangolo). From 97145588f5442aa7c3830173813d5d1a2c893521 Mon Sep 17 00:00:00 2001 From: Albin Skott Date: Thu, 5 Feb 2026 19:34:34 +0100 Subject: [PATCH 027/195] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20PEP69?= =?UTF-8?q?5=20`TypeAliasType`=20(#13920)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: lokidev Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Yurii Motov Co-authored-by: Sebastián Ramírez --- fastapi/dependencies/utils.py | 4 ++++ pyproject.toml | 1 + tests/test_dependency_pep695.py | 27 +++++++++++++++++++++++++++ uv.lock | 2 ++ 4 files changed, 34 insertions(+) create mode 100644 tests/test_dependency_pep695.py diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 3037b233b..e5ac51d53 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -66,6 +66,7 @@ from starlette.requests import HTTPConnection, Request from starlette.responses import Response from starlette.websockets import WebSocket from typing_extensions import Literal, get_args, get_origin +from typing_inspection.typing_objects import is_typealiastype multipart_not_installed_error = ( 'Form data requires "python-multipart" to be installed. \n' @@ -370,6 +371,9 @@ def analyze_param( depends = None type_annotation: Any = Any use_annotation: Any = Any + if is_typealiastype(annotation): + # unpack in case PEP 695 type syntax is used + annotation = annotation.__value__ if annotation is not inspect.Signature.empty: use_annotation = annotation type_annotation = annotation diff --git a/pyproject.toml b/pyproject.toml index 0f6bf1e4a..e62baa5f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ dependencies = [ "starlette>=0.40.0,<0.51.0", "pydantic>=2.7.0", "typing-extensions>=4.8.0", + "typing-inspection>=0.4.2", "annotated-doc>=0.0.2", ] diff --git a/tests/test_dependency_pep695.py b/tests/test_dependency_pep695.py new file mode 100644 index 000000000..ef5636638 --- /dev/null +++ b/tests/test_dependency_pep695.py @@ -0,0 +1,27 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI +from fastapi.testclient import TestClient +from typing_extensions import TypeAliasType + + +async def some_value() -> int: + return 123 + + +DependedValue = TypeAliasType( + "DependedValue", Annotated[int, Depends(some_value)], type_params=() +) + + +def test_pep695_type_dependencies(): + app = FastAPI() + + @app.get("/") + async def get_with_dep(value: DependedValue) -> str: # noqa + return f"value: {value}" + + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200 + assert response.text == '"value: 123"' diff --git a/uv.lock b/uv.lock index 931a27021..db5d2e9b1 100644 --- a/uv.lock +++ b/uv.lock @@ -1021,6 +1021,7 @@ dependencies = [ { name = "starlette", version = "0.49.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] [package.optional-dependencies] @@ -1202,6 +1203,7 @@ requires-dist = [ { name = "pyyaml", marker = "extra == 'all'", specifier = ">=5.3.1" }, { name = "starlette", specifier = ">=0.40.0,<0.51.0" }, { name = "typing-extensions", specifier = ">=4.8.0" }, + { name = "typing-inspection", specifier = ">=0.4.2" }, { name = "ujson", marker = "extra == 'all'", specifier = ">=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.12.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.12.0" }, From 54f8aeeb9a15e4d5a12401ec5549840966df0087 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 18:34:59 +0000 Subject: [PATCH 028/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 43600561b..3280ad699 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Features +* ✨ Add support for PEP695 `TypeAliasType`. PR [#13920](https://github.com/fastapi/fastapi/pull/13920) by [@cstruct](https://github.com/cstruct). * ✨ Allow `Response` type hint as dependency annotation. PR [#14794](https://github.com/fastapi/fastapi/pull/14794) by [@jonathan-fulton](https://github.com/jonathan-fulton). ### Docs From c5fd75a321496b1f8212744bde217ec8ea956154 Mon Sep 17 00:00:00 2001 From: Kanetsuna Masaya <156692516+mkanetsuna@users.noreply.github.com> Date: Fri, 6 Feb 2026 03:41:43 +0900 Subject: [PATCH 029/195] =?UTF-8?q?=F0=9F=90=9B=20Fix=20using=20`Json[list?= =?UTF-8?q?[str]]`=20type=20(issue=20#10997)=20(#14616)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Co-authored-by: Sebastián Ramírez --- fastapi/dependencies/utils.py | 12 +++++-- tests/test_json_type.py | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 tests/test_json_type.py diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index e5ac51d53..b8f7f948c 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -51,7 +51,7 @@ from fastapi.logger import logger from fastapi.security.oauth2 import SecurityScopes from fastapi.types import DependencyCacheKey from fastapi.utils import create_model_field, get_path_param_names -from pydantic import BaseModel +from pydantic import BaseModel, Json from pydantic.fields import FieldInfo from starlette.background import BackgroundTasks as StarletteBackgroundTasks from starlette.concurrency import run_in_threadpool @@ -726,11 +726,19 @@ def _validate_value_with_model_field( return v_, [] +def _is_json_field(field: ModelField) -> bool: + return any(type(item) is Json for item in field.field_info.metadata) + + def _get_multidict_value( field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None ) -> Any: alias = alias or get_validation_alias(field) - if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)): + if ( + (not _is_json_field(field)) + and is_sequence_field(field) + and isinstance(values, (ImmutableMultiDict, Headers)) + ): value = values.getlist(alias) else: value = values.get(alias, None) diff --git a/tests/test_json_type.py b/tests/test_json_type.py new file mode 100644 index 000000000..3e213eaca --- /dev/null +++ b/tests/test_json_type.py @@ -0,0 +1,63 @@ +import json +from typing import Annotated + +from fastapi import Cookie, FastAPI, Form, Header, Query +from fastapi.testclient import TestClient +from pydantic import Json + +app = FastAPI() + + +@app.post("/form-json-list") +def form_json_list(items: Annotated[Json[list[str]], Form()]) -> list[str]: + return items + + +@app.get("/query-json-list") +def query_json_list(items: Annotated[Json[list[str]], Query()]) -> list[str]: + return items + + +@app.get("/header-json-list") +def header_json_list(x_items: Annotated[Json[list[str]], Header()]) -> list[str]: + return x_items + + +@app.get("/cookie-json-list") +def cookie_json_list(items: Annotated[Json[list[str]], Cookie()]) -> list[str]: + return items + + +client = TestClient(app) + + +def test_form_json_list(): + response = client.post( + "/form-json-list", data={"items": json.dumps(["abc", "def"])} + ) + assert response.status_code == 200, response.text + assert response.json() == ["abc", "def"] + + +def test_query_json_list(): + response = client.get( + "/query-json-list", params={"items": json.dumps(["abc", "def"])} + ) + assert response.status_code == 200, response.text + assert response.json() == ["abc", "def"] + + +def test_header_json_list(): + response = client.get( + "/header-json-list", headers={"x-items": json.dumps(["abc", "def"])} + ) + assert response.status_code == 200, response.text + assert response.json() == ["abc", "def"] + + +def test_cookie_json_list(): + client.cookies.set("items", json.dumps(["abc", "def"])) + response = client.get("/cookie-json-list") + assert response.status_code == 200, response.text + assert response.json() == ["abc", "def"] + client.cookies.clear() From 6646e2b94f13fe4145560d02cef05cfd3a6bc925 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 18:42:10 +0000 Subject: [PATCH 030/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 3280ad699..6ec199a16 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -12,6 +12,10 @@ hide: * ✨ Add support for PEP695 `TypeAliasType`. PR [#13920](https://github.com/fastapi/fastapi/pull/13920) by [@cstruct](https://github.com/cstruct). * ✨ Allow `Response` type hint as dependency annotation. PR [#14794](https://github.com/fastapi/fastapi/pull/14794) by [@jonathan-fulton](https://github.com/jonathan-fulton). +### Fixes + +* 🐛 Fix using `Json[list[str]]` type (issue #10997). PR [#14616](https://github.com/fastapi/fastapi/pull/14616) by [@mkanetsuna](https://github.com/mkanetsuna). + ### Docs * 📝 Update docs for translations. PR [#14830](https://github.com/fastapi/fastapi/pull/14830) by [@tiangolo](https://github.com/tiangolo). From 23ddf09dd197ba099edc1b06eba61f7fc20a5ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 11:25:49 -0800 Subject: [PATCH 031/195] =?UTF-8?q?=F0=9F=94=A8=20Add=20max=20pages=20to?= =?UTF-8?q?=20translate=20to=20configs=20(#14840)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/translate.yml | 6 ++++++ scripts/translate.py | 23 ++++++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index 83518614b..bb23fa32d 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -35,6 +35,11 @@ on: type: boolean required: false default: false + max: + description: Maximum number of items to translate (e.g. 10) + type: number + required: false + default: 10 jobs: langs: @@ -115,3 +120,4 @@ jobs: EN_PATH: ${{ github.event.inputs.en_path }} COMMAND: ${{ matrix.command }} COMMIT_IN_PLACE: ${{ github.event.inputs.commit_in_place }} + MAX: ${{ github.event.inputs.max }} diff --git a/scripts/translate.py b/scripts/translate.py index 31c44583d..ddcfa311d 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -347,9 +347,12 @@ def list_outdated(language: str) -> list[Path]: @app.command() -def update_outdated(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None: +def update_outdated( + language: Annotated[str, typer.Option(envvar="LANGUAGE")], + max: Annotated[int, typer.Option(envvar="MAX")] = 10, +) -> None: outdated_paths = list_outdated(language) - for path in outdated_paths: + for path in outdated_paths[:max]: print(f"Updating lang: {language} path: {path}") translate_page(language=language, en_path=path) print(f"Done updating: {path}") @@ -357,9 +360,12 @@ def update_outdated(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) - @app.command() -def add_missing(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None: +def add_missing( + language: Annotated[str, typer.Option(envvar="LANGUAGE")], + max: Annotated[int, typer.Option(envvar="MAX")] = 10, +) -> None: missing_paths = list_missing(language) - for path in missing_paths: + for path in missing_paths[:max]: print(f"Adding lang: {language} path: {path}") translate_page(language=language, en_path=path) print(f"Done adding: {path}") @@ -367,11 +373,14 @@ def add_missing(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> No @app.command() -def update_and_add(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None: +def update_and_add( + language: Annotated[str, typer.Option(envvar="LANGUAGE")], + max: Annotated[int, typer.Option(envvar="MAX")] = 10, +) -> None: print(f"Updating outdated translations for {language}") - update_outdated(language=language) + update_outdated(language=language, max=max) print(f"Adding missing translations for {language}") - add_missing(language=language) + add_missing(language=language, max=max) print(f"Done updating and adding for {language}") From 8bdbd3725f7c88f3071ed9623da0945a269d3706 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 19:26:13 +0000 Subject: [PATCH 032/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 6ec199a16..6a7fb926c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -35,6 +35,10 @@ hide: * 🌐 Update translations for uk (update-outdated). PR [#14822](https://github.com/fastapi/fastapi/pull/14822) by [@tiangolo](https://github.com/tiangolo). * 🔨 Update docs and translations scripts, enable Turkish. PR [#14824](https://github.com/fastapi/fastapi/pull/14824) by [@tiangolo](https://github.com/tiangolo). +### Internal + +* 🔨 Add max pages to translate to configs. PR [#14840](https://github.com/fastapi/fastapi/pull/14840) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.1 ### Features From 72325f698f7ab55e7df2f7fc40cc52693eadd687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 11:27:51 -0800 Subject: [PATCH 033/195] =?UTF-8?q?=F0=9F=8C=90=20Enable=20French=20docs?= =?UTF-8?q?=20translations=20(#14841)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/mkdocs.yml | 2 ++ scripts/docs.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index f713f2b12..38fe5f9ce 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -317,6 +317,8 @@ extra: name: de - Deutsch - link: /es/ name: es - español + - link: /fr/ + name: fr - français - link: /ja/ name: ja - 日本語 - link: /ko/ diff --git a/scripts/docs.py b/scripts/docs.py index c1fb84bba..4c065f945 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -23,7 +23,7 @@ SUPPORTED_LANGS = { "de", "en", "es", - # "fr", + "fr", "ja", "ko", "pt", From 110b45d9b245d42a114a965bbd3039458afbf95e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 19:28:17 +0000 Subject: [PATCH 034/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 6a7fb926c..9e8760e99 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -23,6 +23,7 @@ hide: ### Translations +* 🌐 Enable French docs translations. PR [#14841](https://github.com/fastapi/fastapi/pull/14841) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for fr (translate-page). PR [#14837](https://github.com/fastapi/fastapi/pull/14837) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for de (update-outdated). PR [#14836](https://github.com/fastapi/fastapi/pull/14836) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for pt (update-outdated). PR [#14833](https://github.com/fastapi/fastapi/pull/14833) by [@tiangolo](https://github.com/tiangolo). From 570e592a03c37c0436fea3727bd1be873417d9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 11:42:18 -0800 Subject: [PATCH 035/195] =?UTF-8?q?=F0=9F=8C=90=20Enable=20Traditional=20C?= =?UTF-8?q?hinese=20translations=20(#14842)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/mkdocs.yml | 2 ++ scripts/docs.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 38fe5f9ce..ccf5ffc7a 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -331,6 +331,8 @@ extra: name: tr - Türkçe - link: /uk/ name: uk - українська мова + - link: /zh-hant/ + name: zh-hant - 繁體中文 extra_css: - css/termynal.css - css/custom.css diff --git a/scripts/docs.py b/scripts/docs.py index 4c065f945..2c7d6d5e4 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -31,7 +31,7 @@ SUPPORTED_LANGS = { "tr", "uk", # "zh", - # "zh-hant", + "zh-hant", } From de56c96c640dddb224c0088ed6d949efbf86d853 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 19:42:42 +0000 Subject: [PATCH 036/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 9e8760e99..85a2157e0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -38,6 +38,7 @@ hide: ### Internal +* 🌐 Enable Traditional Chinese translations. PR [#14842](https://github.com/fastapi/fastapi/pull/14842) by [@tiangolo](https://github.com/tiangolo). * 🔨 Add max pages to translate to configs. PR [#14840](https://github.com/fastapi/fastapi/pull/14840) by [@tiangolo](https://github.com/tiangolo). ## 0.128.1 From 79406a4b04078155ccc03e2d48ca0799836cfb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 5 Feb 2026 20:46:37 +0100 Subject: [PATCH 037/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?8.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 4 +++- fastapi/__init__.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 85a2157e0..c1a563f18 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.128.2 + ### Features * ✨ Add support for PEP695 `TypeAliasType`. PR [#13920](https://github.com/fastapi/fastapi/pull/13920) by [@cstruct](https://github.com/cstruct). @@ -23,6 +25,7 @@ hide: ### Translations +* 🌐 Enable Traditional Chinese translations. PR [#14842](https://github.com/fastapi/fastapi/pull/14842) by [@tiangolo](https://github.com/tiangolo). * 🌐 Enable French docs translations. PR [#14841](https://github.com/fastapi/fastapi/pull/14841) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for fr (translate-page). PR [#14837](https://github.com/fastapi/fastapi/pull/14837) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for de (update-outdated). PR [#14836](https://github.com/fastapi/fastapi/pull/14836) by [@tiangolo](https://github.com/tiangolo). @@ -38,7 +41,6 @@ hide: ### Internal -* 🌐 Enable Traditional Chinese translations. PR [#14842](https://github.com/fastapi/fastapi/pull/14842) by [@tiangolo](https://github.com/tiangolo). * 🔨 Add max pages to translate to configs. PR [#14840](https://github.com/fastapi/fastapi/pull/14840) by [@tiangolo](https://github.com/tiangolo). ## 0.128.1 diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 160e7235c..99b9630d2 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.128.1" +__version__ = "0.128.2" from starlette import status as status From 3b8b310eda78d075ab8dd919bc17a9dce1c21f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 6 Feb 2026 05:45:18 -0800 Subject: [PATCH 038/195] =?UTF-8?q?=F0=9F=91=B7=20Run=20tests=20with=20low?= =?UTF-8?q?er=20bound=20uv=20sync,=20upgrade=20`fastapi[all]`=20minimum=20?= =?UTF-8?q?dependencies:=20`ujson=20>=3D5.8.0`,=20`orjson=20>=3D3.9.3`=20(?= =?UTF-8?q?#14846)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 11 +- pyproject.toml | 6 +- uv.lock | 1152 ++++++++++++++++++++---------------- 3 files changed, 657 insertions(+), 512 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5cbbde61f..70710e340 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,31 +49,40 @@ jobs: matrix: os: [ windows-latest, macos-latest ] python-version: [ "3.14" ] + uv-resolution: + - highest include: - os: ubuntu-latest python-version: "3.9" coverage: coverage + uv-resolution: lowest-direct - os: macos-latest python-version: "3.10" coverage: coverage + uv-resolution: highest - os: windows-latest python-version: "3.12" coverage: coverage + uv-resolution: lowest-direct - os: ubuntu-latest python-version: "3.13" coverage: coverage + uv-resolution: highest # Ubuntu with 3.13 needs coverage for CodSpeed benchmarks - os: ubuntu-latest python-version: "3.13" coverage: coverage + uv-resolution: highest codspeed: codspeed - os: ubuntu-latest python-version: "3.14" coverage: coverage + uv-resolution: highest fail-fast: false runs-on: ${{ matrix.os }} env: UV_PYTHON: ${{ matrix.python-version }} + UV_RESOLUTION: ${{ matrix.uv-resolution }} steps: - name: Dump GitHub context env: @@ -92,7 +101,7 @@ jobs: pyproject.toml uv.lock - name: Install Dependencies - run: uv sync --locked --no-dev --group tests --extra all + run: uv sync --no-dev --group tests --extra all - run: mkdir coverage - name: Test if: matrix.codspeed != 'codspeed' diff --git a/pyproject.toml b/pyproject.toml index e62baa5f8..ae9ae891e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,9 +108,9 @@ all = [ # For Starlette's schema generation, would not be used with FastAPI "pyyaml >=5.3.1", # For UJSONResponse - "ujson >=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0", + "ujson >=5.8.0", # For ORJSONResponse - "orjson >=3.2.1", + "orjson >=3.9.3", # To validate email fields "email-validator >=2.0.0", # Uvicorn with uvloop @@ -167,7 +167,7 @@ tests = [ "anyio[trio]>=3.2.1,<5.0.0", "coverage[toml]>=6.5.0,<8.0", "dirty-equals==0.9.0", - "flask>=1.1.2,<4.0.0", + "flask>=3.0.0,<4.0.0", "inline-snapshot>=0.21.1", "mypy==1.14.1", "pwdlib[argon2]>=0.2.1", diff --git a/uv.lock b/uv.lock index db5d2e9b1..435dddec5 100644 --- a/uv.lock +++ b/uv.lock @@ -51,7 +51,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.75.0" +version = "0.78.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -63,9 +63,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/1f/08e95f4b7e2d35205ae5dcbb4ae97e7d477fc521c275c02609e2931ece2d/anthropic-0.75.0.tar.gz", hash = "sha256:e8607422f4ab616db2ea5baacc215dd5f028da99ce2f022e33c7c535b29f3dfb", size = 439565, upload-time = "2025-11-24T20:41:45.28Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/51/32849a48f9b1cfe80a508fd269b20bd8f0b1357c70ba092890fde5a6a10b/anthropic-0.78.0.tar.gz", hash = "sha256:55fd978ab9b049c61857463f4c4e9e092b24f892519c6d8078cee1713d8af06e", size = 509136, upload-time = "2026-02-05T17:52:04.986Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/1c/1cd02b7ae64302a6e06724bf80a96401d5313708651d277b1458504a1730/anthropic-0.75.0-py3-none-any.whl", hash = "sha256:ea8317271b6c15d80225a9f3c670152746e88805a7a61e14d4a374577164965b", size = 388164, upload-time = "2025-11-24T20:41:43.587Z" }, + { url = "https://files.pythonhosted.org/packages/3b/03/2f50931a942e5e13f80e24d83406714672c57964be593fc046d81369335b/anthropic-0.78.0-py3-none-any.whl", hash = "sha256:2a9887d2e99d1b0f9fe08857a1e9fe5d2d4030455dbf9ac65aab052e2efaeac4", size = 405485, upload-time = "2026-02-05T17:52:03.674Z" }, ] [[package]] @@ -184,11 +184,11 @@ wheels = [ [[package]] name = "babel" -version = "2.17.0" +version = "2.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] @@ -256,21 +256,21 @@ wheels = [ [[package]] name = "boto3" -version = "1.42.24" +version = "1.42.43" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/21/8be0e3685c3a4868be48d8d2f6e5b4641727e1d8a5d396b8b401d2b5f06e/boto3-1.42.24.tar.gz", hash = "sha256:c47a2f40df933e3861fc66fd8d6b87ee36d4361663a7e7ba39a87f5a78b2eae1", size = 112788, upload-time = "2026-01-07T20:30:51.019Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/47/29afb754de7df0a0ebceaa9d83e209136ef7b62744259a6c09862fef4765/boto3-1.42.43.tar.gz", hash = "sha256:01fc5501209b23849fb30b01c6c086583ac91c40842a76083662fbfb84a82491", size = 112844, upload-time = "2026-02-05T20:31:44.974Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/75/bbfccb268f9faa4f59030888e859dca9797a980b77d6a074113af73bd4bf/boto3-1.42.24-py3-none-any.whl", hash = "sha256:8ed6ad670a5a2d7f66c1b0d3362791b48392c7a08f78479f5d8ab319a4d9118f", size = 140572, upload-time = "2026-01-07T20:30:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/80/92/584447b14ae70f57f133a4bc64393902a72a3087486a7c09ce1bab25263c/boto3-1.42.43-py3-none-any.whl", hash = "sha256:44ddcaa37c350333c5a4799f533e786a595a97f1ee2fd7fc3e394cdebeb15e44", size = 140603, upload-time = "2026-02-05T20:31:43.698Z" }, ] [[package]] name = "botocore" -version = "1.42.24" +version = "1.42.43" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, @@ -278,9 +278,9 @@ dependencies = [ { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/d7/bb4a4e839b238ffb67b002d7326b328ebe5eb23ed5180f2ca10399a802de/botocore-1.42.24.tar.gz", hash = "sha256:be8d1bea64fb91eea08254a1e5fea057e4428d08e61f4e11083a02cafc1f8cc6", size = 14878455, upload-time = "2026-01-07T20:30:40.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/d6/def916ad1d13de5d511074afcde538a958e2e8a7c7020fb698d1f392f63b/botocore-1.42.43.tar.gz", hash = "sha256:41d04ead0b0862eec21f841811fb5764fe370a2df9b319e0d5297325c50fba1b", size = 14934077, upload-time = "2026-02-05T20:31:35.15Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/d4/f2655d777eed8b069ecab3761454cb83f830f8be8b5b0d292e4b3a980d00/botocore-1.42.24-py3-none-any.whl", hash = "sha256:8fca9781d7c84f7ad070fceffaff7179c4aa7a5ffb27b43df9d1d957801e0a8d", size = 14551806, upload-time = "2026-01-07T20:30:38.103Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a8/95656f91b795eb47b73a00d36c51c7a5729eafa632c7348caa068ff63e50/botocore-1.42.43-py3-none-any.whl", hash = "sha256:1c0e30f62e274978ac3bcab253e3a859febea634b72b5e343589db7d17f83cd6", size = 14610179, upload-time = "2026-02-05T20:31:32.727Z" }, ] [[package]] @@ -326,7 +326,8 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, + { name = "pycparser", version = "2.23", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and implementation_name != 'PyPy'" }, + { name = "pycparser", version = "3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and implementation_name != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ @@ -553,7 +554,7 @@ wheels = [ [[package]] name = "cohere" -version = "5.20.1" +version = "5.20.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastavro" }, @@ -566,9 +567,9 @@ dependencies = [ { name = "types-requests", version = "2.32.4.20260107", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/ed/bb02083654bdc089ae4ef1cd7691fd2233f1fd9f32bcbfacc80ff57d9775/cohere-5.20.1.tar.gz", hash = "sha256:50973f63d2c6138ff52ce37d8d6f78ccc539af4e8c43865e960d68e0bf835b6f", size = 180820, upload-time = "2025-12-18T16:39:50.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/37/65af9c50b5d5772a5528c6a949799f98ae286b8ebb924e0fac0619b3ae88/cohere-5.20.4.tar.gz", hash = "sha256:3b3017048ff5d5b4f113180947d538ca3d0f274de5875f0345be4c8cb3d5119a", size = 180737, upload-time = "2026-02-05T14:47:54.639Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/e3/94eb11ac3ebaaa3a6afb5d2ff23db95d58bc468ae538c388edf49f2f20b5/cohere-5.20.1-py3-none-any.whl", hash = "sha256:d230fd13d95ba92ae927fce3dd497599b169883afc7954fe29b39fb8d5df5fc7", size = 318973, upload-time = "2025-12-18T16:39:49.504Z" }, + { url = "https://files.pythonhosted.org/packages/6d/14/5c5077c6b01aed7a18dfc5ab775a35c7a6cb118e5bc1dafcfc06abdb9d9e/cohere-5.20.4-py3-none-any.whl", hash = "sha256:9cc6ebb0adac3d9f96ac0efffde6a2484534fb0aec3684a62c250d49da958f29", size = 318987, upload-time = "2026-02-05T14:47:53.505Z" }, ] [[package]] @@ -701,105 +702,105 @@ toml = [ [[package]] name = "coverage" -version = "7.13.1" +version = "7.13.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version >= '3.10' and python_full_version < '3.14'", ] -sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", hash = "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", size = 826832, upload-time = "2026-02-03T14:02:30.944Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/9a/3742e58fd04b233df95c012ee9f3dfe04708a5e1d32613bd2d47d4e1be0d/coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147", size = 218633, upload-time = "2025-12-28T15:40:10.165Z" }, - { url = "https://files.pythonhosted.org/packages/7e/45/7e6bdc94d89cd7c8017ce735cf50478ddfe765d4fbf0c24d71d30ea33d7a/coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d", size = 219147, upload-time = "2025-12-28T15:40:12.069Z" }, - { url = "https://files.pythonhosted.org/packages/f7/38/0d6a258625fd7f10773fe94097dc16937a5f0e3e0cdf3adef67d3ac6baef/coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0", size = 245894, upload-time = "2025-12-28T15:40:13.556Z" }, - { url = "https://files.pythonhosted.org/packages/27/58/409d15ea487986994cbd4d06376e9860e9b157cfbfd402b1236770ab8dd2/coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90", size = 247721, upload-time = "2025-12-28T15:40:15.37Z" }, - { url = "https://files.pythonhosted.org/packages/da/bf/6e8056a83fd7a96c93341f1ffe10df636dd89f26d5e7b9ca511ce3bcf0df/coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d", size = 249585, upload-time = "2025-12-28T15:40:17.226Z" }, - { url = "https://files.pythonhosted.org/packages/f4/15/e1daff723f9f5959acb63cbe35b11203a9df77ee4b95b45fffd38b318390/coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b", size = 246597, upload-time = "2025-12-28T15:40:19.028Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/1efd31c5433743a6ddbc9d37ac30c196bb07c7eab3d74fbb99b924c93174/coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6", size = 247626, upload-time = "2025-12-28T15:40:20.846Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9f/1609267dd3e749f57fdd66ca6752567d1c13b58a20a809dc409b263d0b5f/coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e", size = 245629, upload-time = "2025-12-28T15:40:22.397Z" }, - { url = "https://files.pythonhosted.org/packages/e2/f6/6815a220d5ec2466383d7cc36131b9fa6ecbe95c50ec52a631ba733f306a/coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae", size = 245901, upload-time = "2025-12-28T15:40:23.836Z" }, - { url = "https://files.pythonhosted.org/packages/ac/58/40576554cd12e0872faf6d2c0eb3bc85f71d78427946ddd19ad65201e2c0/coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29", size = 246505, upload-time = "2025-12-28T15:40:25.421Z" }, - { url = "https://files.pythonhosted.org/packages/3b/77/9233a90253fba576b0eee81707b5781d0e21d97478e5377b226c5b096c0f/coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f", size = 221257, upload-time = "2025-12-28T15:40:27.217Z" }, - { url = "https://files.pythonhosted.org/packages/e0/43/e842ff30c1a0a623ec80db89befb84a3a7aad7bfe44a6ea77d5a3e61fedd/coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1", size = 222191, upload-time = "2025-12-28T15:40:28.916Z" }, - { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, - { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, - { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, - { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, - { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, - { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, - { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, - { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, - { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, - { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, - { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, - { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, - { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, - { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, - { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" }, - { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" }, - { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" }, - { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" }, - { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" }, - { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" }, - { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" }, - { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" }, - { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" }, - { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" }, - { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" }, - { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" }, - { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" }, - { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, - { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, - { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, - { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, - { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, - { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, - { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, - { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, - { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, - { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, + { url = "https://files.pythonhosted.org/packages/ab/07/1c8099563a8a6c389a31c2d0aa1497cee86d6248bb4b9ba5e779215db9f9/coverage-7.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b4f345f7265cdbdb5ec2521ffff15fa49de6d6c39abf89fc7ad68aa9e3a55f0", size = 219143, upload-time = "2026-02-03T13:59:40.459Z" }, + { url = "https://files.pythonhosted.org/packages/69/39/a892d44af7aa092cab70e0cc5cdbba18eeccfe1d6930695dab1742eef9e9/coverage-7.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96c3be8bae9d0333e403cc1a8eb078a7f928b5650bae94a18fb4820cc993fb9b", size = 219663, upload-time = "2026-02-03T13:59:41.951Z" }, + { url = "https://files.pythonhosted.org/packages/9a/25/9669dcf4c2bb4c3861469e6db20e52e8c11908cf53c14ec9b12e9fd4d602/coverage-7.13.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d6f4a21328ea49d38565b55599e1c02834e76583a6953e5586d65cb1efebd8f8", size = 246424, upload-time = "2026-02-03T13:59:43.418Z" }, + { url = "https://files.pythonhosted.org/packages/f3/68/d9766c4e298aca62ea5d9543e1dd1e4e1439d7284815244d8b7db1840bfb/coverage-7.13.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc970575799a9d17d5c3fafd83a0f6ccf5d5117cdc9ad6fbd791e9ead82418b0", size = 248228, upload-time = "2026-02-03T13:59:44.816Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e2/eea6cb4a4bd443741adf008d4cccec83a1f75401df59b6559aca2bdd9710/coverage-7.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ff33b652b3556b05e204ae20793d1f872161b0fa5ec8a9ac76f8430e152ed6", size = 250103, upload-time = "2026-02-03T13:59:46.271Z" }, + { url = "https://files.pythonhosted.org/packages/db/77/664280ecd666c2191610842177e2fab9e5dbdeef97178e2078fed46a3d2c/coverage-7.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7df8759ee57b9f3f7b66799b7660c282f4375bef620ade1686d6a7b03699e75f", size = 247107, upload-time = "2026-02-03T13:59:48.53Z" }, + { url = "https://files.pythonhosted.org/packages/2b/df/2a672eab99e0d0eba52d8a63e47dc92245eee26954d1b2d3c8f7d372151f/coverage-7.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f45c9bcb16bee25a798ccba8a2f6a1251b19de6a0d617bb365d7d2f386c4e20e", size = 248143, upload-time = "2026-02-03T13:59:50.027Z" }, + { url = "https://files.pythonhosted.org/packages/a5/dc/a104e7a87c13e57a358b8b9199a8955676e1703bb372d79722b54978ae45/coverage-7.13.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:318b2e4753cbf611061e01b6cc81477e1cdfeb69c36c4a14e6595e674caadb56", size = 246148, upload-time = "2026-02-03T13:59:52.025Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/e113d3a58dc20b03b7e59aed1e53ebc9ca6167f961876443e002b10e3ae9/coverage-7.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:24db3959de8ee394eeeca89ccb8ba25305c2da9a668dd44173394cbd5aa0777f", size = 246414, upload-time = "2026-02-03T13:59:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/3f/60/a3fd0a6e8d89b488396019a2268b6a1f25ab56d6d18f3be50f35d77b47dc/coverage-7.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be14d0622125edef21b3a4d8cd2d138c4872bf6e38adc90fd92385e3312f406a", size = 247023, upload-time = "2026-02-03T13:59:55.454Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/de4840bb939dbb22ba0648a6d8069fa91c9cf3b3fca8b0d1df461e885b3d/coverage-7.13.3-cp310-cp310-win32.whl", hash = "sha256:53be4aab8ddef18beb6188f3a3fdbf4d1af2277d098d4e618be3a8e6c88e74be", size = 221751, upload-time = "2026-02-03T13:59:57.383Z" }, + { url = "https://files.pythonhosted.org/packages/de/87/233ff8b7ef62fb63f58c78623b50bef69681111e0c4d43504f422d88cda4/coverage-7.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:bfeee64ad8b4aae3233abb77eb6b52b51b05fa89da9645518671b9939a78732b", size = 222686, upload-time = "2026-02-03T13:59:58.825Z" }, + { url = "https://files.pythonhosted.org/packages/ec/09/1ac74e37cf45f17eb41e11a21854f7f92a4c2d6c6098ef4a1becb0c6d8d3/coverage-7.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73", size = 219276, upload-time = "2026-02-03T14:00:00.296Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cb/71908b08b21beb2c437d0d5870c4ec129c570ca1b386a8427fcdb11cf89c/coverage-7.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00", size = 219776, upload-time = "2026-02-03T14:00:02.414Z" }, + { url = "https://files.pythonhosted.org/packages/09/85/c4f3dd69232887666a2c0394d4be21c60ea934d404db068e6c96aa59cd87/coverage-7.13.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2", size = 250196, upload-time = "2026-02-03T14:00:04.197Z" }, + { url = "https://files.pythonhosted.org/packages/9c/cc/560ad6f12010344d0778e268df5ba9aa990aacccc310d478bf82bf3d302c/coverage-7.13.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c", size = 252111, upload-time = "2026-02-03T14:00:05.639Z" }, + { url = "https://files.pythonhosted.org/packages/f0/66/3193985fb2c58e91f94cfbe9e21a6fdf941e9301fe2be9e92c072e9c8f8c/coverage-7.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b", size = 254217, upload-time = "2026-02-03T14:00:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c5/78/f0f91556bf1faa416792e537c523c5ef9db9b1d32a50572c102b3d7c45b3/coverage-7.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0", size = 250318, upload-time = "2026-02-03T14:00:09.224Z" }, + { url = "https://files.pythonhosted.org/packages/6f/aa/fc654e45e837d137b2c1f3a2cc09b4aea1e8b015acd2f774fa0f3d2ddeba/coverage-7.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14", size = 251909, upload-time = "2026-02-03T14:00:10.712Z" }, + { url = "https://files.pythonhosted.org/packages/73/4d/ab53063992add8a9ca0463c9d92cce5994a29e17affd1c2daa091b922a93/coverage-7.13.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4", size = 249971, upload-time = "2026-02-03T14:00:12.402Z" }, + { url = "https://files.pythonhosted.org/packages/29/25/83694b81e46fcff9899694a1b6f57573429cdd82b57932f09a698f03eea5/coverage-7.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad", size = 249692, upload-time = "2026-02-03T14:00:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ef/d68fc304301f4cb4bf6aefa0045310520789ca38dabdfba9dbecd3f37919/coverage-7.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222", size = 250597, upload-time = "2026-02-03T14:00:15.461Z" }, + { url = "https://files.pythonhosted.org/packages/8d/85/240ad396f914df361d0f71e912ddcedb48130c71b88dc4193fe3c0306f00/coverage-7.13.3-cp311-cp311-win32.whl", hash = "sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb", size = 221773, upload-time = "2026-02-03T14:00:17.462Z" }, + { url = "https://files.pythonhosted.org/packages/2f/71/165b3a6d3d052704a9ab52d11ea64ef3426745de517dda44d872716213a7/coverage-7.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301", size = 222711, upload-time = "2026-02-03T14:00:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/51/d0/0ddc9c5934cdd52639c5df1f1eb0fdab51bb52348f3a8d1c7db9c600d93a/coverage-7.13.3-cp311-cp311-win_arm64.whl", hash = "sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba", size = 221377, upload-time = "2026-02-03T14:00:20.968Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/330f8e83b143f6668778ed61d17ece9dc48459e9e74669177de02f45fec5/coverage-7.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595", size = 219441, upload-time = "2026-02-03T14:00:22.585Z" }, + { url = "https://files.pythonhosted.org/packages/08/e7/29db05693562c2e65bdf6910c0af2fd6f9325b8f43caf7a258413f369e30/coverage-7.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6", size = 219801, upload-time = "2026-02-03T14:00:24.186Z" }, + { url = "https://files.pythonhosted.org/packages/90/ae/7f8a78249b02b0818db46220795f8ac8312ea4abd1d37d79ea81db5cae81/coverage-7.13.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395", size = 251306, upload-time = "2026-02-03T14:00:25.798Z" }, + { url = "https://files.pythonhosted.org/packages/62/71/a18a53d1808e09b2e9ebd6b47dad5e92daf4c38b0686b4c4d1b2f3e42b7f/coverage-7.13.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23", size = 254051, upload-time = "2026-02-03T14:00:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/4a/0a/eb30f6455d04c5a3396d0696cad2df0269ae7444bb322f86ffe3376f7bf9/coverage-7.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34", size = 255160, upload-time = "2026-02-03T14:00:29.024Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/a45baac86274ce3ed842dbb84f14560c673ad30535f397d89164ec56c5df/coverage-7.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8", size = 251709, upload-time = "2026-02-03T14:00:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/df/dd0dc12f30da11349993f3e218901fdf82f45ee44773596050c8f5a1fb25/coverage-7.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a", size = 253083, upload-time = "2026-02-03T14:00:32.14Z" }, + { url = "https://files.pythonhosted.org/packages/ab/32/fc764c8389a8ce95cb90eb97af4c32f392ab0ac23ec57cadeefb887188d3/coverage-7.13.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4", size = 251227, upload-time = "2026-02-03T14:00:34.721Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ca/d025e9da8f06f24c34d2da9873957cfc5f7e0d67802c3e34d0caa8452130/coverage-7.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7", size = 250794, upload-time = "2026-02-03T14:00:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/45/c7/76bf35d5d488ec8f68682eb8e7671acc50a6d2d1c1182de1d2b6d4ffad3b/coverage-7.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0", size = 252671, upload-time = "2026-02-03T14:00:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/bf/10/1921f1a03a7c209e1cb374f81a6b9b68b03cdb3ecc3433c189bc90e2a3d5/coverage-7.13.3-cp312-cp312-win32.whl", hash = "sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1", size = 221986, upload-time = "2026-02-03T14:00:40.442Z" }, + { url = "https://files.pythonhosted.org/packages/3c/7c/f5d93297f8e125a80c15545edc754d93e0ed8ba255b65e609b185296af01/coverage-7.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d", size = 222793, upload-time = "2026-02-03T14:00:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/43/59/c86b84170015b4555ebabca8649bdf9f4a1f737a73168088385ed0f947c4/coverage-7.13.3-cp312-cp312-win_arm64.whl", hash = "sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f", size = 221410, upload-time = "2026-02-03T14:00:43.726Z" }, + { url = "https://files.pythonhosted.org/packages/81/f3/4c333da7b373e8c8bfb62517e8174a01dcc373d7a9083698e3b39d50d59c/coverage-7.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25", size = 219468, upload-time = "2026-02-03T14:00:45.829Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/0714337b7d23630c8de2f4d56acf43c65f8728a45ed529b34410683f7217/coverage-7.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a", size = 219839, upload-time = "2026-02-03T14:00:47.407Z" }, + { url = "https://files.pythonhosted.org/packages/12/99/bd6f2a2738144c98945666f90cae446ed870cecf0421c767475fcf42cdbe/coverage-7.13.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627", size = 250828, upload-time = "2026-02-03T14:00:49.029Z" }, + { url = "https://files.pythonhosted.org/packages/6f/99/97b600225fbf631e6f5bfd3ad5bcaf87fbb9e34ff87492e5a572ff01bbe2/coverage-7.13.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8", size = 253432, upload-time = "2026-02-03T14:00:50.655Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5c/abe2b3490bda26bd4f5e3e799be0bdf00bd81edebedc2c9da8d3ef288fa8/coverage-7.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1", size = 254672, upload-time = "2026-02-03T14:00:52.757Z" }, + { url = "https://files.pythonhosted.org/packages/31/ba/5d1957c76b40daff53971fe0adb84d9c2162b614280031d1d0653dd010c1/coverage-7.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b", size = 251050, upload-time = "2026-02-03T14:00:54.332Z" }, + { url = "https://files.pythonhosted.org/packages/69/dc/dffdf3bfe9d32090f047d3c3085378558cb4eb6778cda7de414ad74581ed/coverage-7.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc", size = 252801, upload-time = "2026-02-03T14:00:56.121Z" }, + { url = "https://files.pythonhosted.org/packages/87/51/cdf6198b0f2746e04511a30dc9185d7b8cdd895276c07bdb538e37f1cd50/coverage-7.13.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea", size = 250763, upload-time = "2026-02-03T14:00:58.719Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1a/596b7d62218c1d69f2475b69cc6b211e33c83c902f38ee6ae9766dd422da/coverage-7.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67", size = 250587, upload-time = "2026-02-03T14:01:01.197Z" }, + { url = "https://files.pythonhosted.org/packages/f7/46/52330d5841ff660f22c130b75f5e1dd3e352c8e7baef5e5fef6b14e3e991/coverage-7.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86", size = 252358, upload-time = "2026-02-03T14:01:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/36/8a/e69a5be51923097ba7d5cff9724466e74fe486e9232020ba97c809a8b42b/coverage-7.13.3-cp313-cp313-win32.whl", hash = "sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43", size = 222007, upload-time = "2026-02-03T14:01:04.876Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/a5a069bcee0d613bdd48ee7637fa73bc09e7ed4342b26890f2df97cc9682/coverage-7.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587", size = 222812, upload-time = "2026-02-03T14:01:07.296Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4f/d62ad7dfe32f9e3d4a10c178bb6f98b10b083d6e0530ca202b399371f6c1/coverage-7.13.3-cp313-cp313-win_arm64.whl", hash = "sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051", size = 221433, upload-time = "2026-02-03T14:01:09.156Z" }, + { url = "https://files.pythonhosted.org/packages/04/b2/4876c46d723d80b9c5b695f1a11bf5f7c3dabf540ec00d6edc076ff025e6/coverage-7.13.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9", size = 220162, upload-time = "2026-02-03T14:01:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/fc/04/9942b64a0e0bdda2c109f56bda42b2a59d9d3df4c94b85a323c1cae9fc77/coverage-7.13.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e", size = 220510, upload-time = "2026-02-03T14:01:13.038Z" }, + { url = "https://files.pythonhosted.org/packages/5a/82/5cfe1e81eae525b74669f9795f37eb3edd4679b873d79d1e6c1c14ee6c1c/coverage-7.13.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107", size = 261801, upload-time = "2026-02-03T14:01:14.674Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ec/a553d7f742fd2cd12e36a16a7b4b3582d5934b496ef2b5ea8abeb10903d4/coverage-7.13.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43", size = 263882, upload-time = "2026-02-03T14:01:16.343Z" }, + { url = "https://files.pythonhosted.org/packages/e1/58/8f54a2a93e3d675635bc406de1c9ac8d551312142ff52c9d71b5e533ad45/coverage-7.13.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3", size = 266306, upload-time = "2026-02-03T14:01:18.02Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/e593399fd6ea1f00aee79ebd7cc401021f218d34e96682a92e1bae092ff6/coverage-7.13.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a", size = 261051, upload-time = "2026-02-03T14:01:19.757Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e5/e9e0f6138b21bcdebccac36fbfde9cf15eb1bbcea9f5b1f35cd1f465fb91/coverage-7.13.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e", size = 263868, upload-time = "2026-02-03T14:01:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bf/de72cfebb69756f2d4a2dde35efcc33c47d85cd3ebdf844b3914aac2ef28/coverage-7.13.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155", size = 261498, upload-time = "2026-02-03T14:01:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/f2/91/4a2d313a70fc2e98ca53afd1c8ce67a89b1944cd996589a5b1fe7fbb3e5c/coverage-7.13.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e", size = 260394, upload-time = "2026-02-03T14:01:24.949Z" }, + { url = "https://files.pythonhosted.org/packages/40/83/25113af7cf6941e779eb7ed8de2a677865b859a07ccee9146d4cc06a03e3/coverage-7.13.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96", size = 262579, upload-time = "2026-02-03T14:01:26.703Z" }, + { url = "https://files.pythonhosted.org/packages/1e/19/a5f2b96262977e82fb9aabbe19b4d83561f5d063f18dde3e72f34ffc3b2f/coverage-7.13.3-cp313-cp313t-win32.whl", hash = "sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f", size = 222679, upload-time = "2026-02-03T14:01:28.553Z" }, + { url = "https://files.pythonhosted.org/packages/81/82/ef1747b88c87a5c7d7edc3704799ebd650189a9158e680a063308b6125ef/coverage-7.13.3-cp313-cp313t-win_amd64.whl", hash = "sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c", size = 223740, upload-time = "2026-02-03T14:01:30.776Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4c/a67c7bb5b560241c22736a9cb2f14c5034149ffae18630323fde787339e4/coverage-7.13.3-cp313-cp313t-win_arm64.whl", hash = "sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9", size = 221996, upload-time = "2026-02-03T14:01:32.495Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/677bb43427fed9298905106f39c6520ac75f746f81b8f01104526a8026e4/coverage-7.13.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b", size = 219513, upload-time = "2026-02-03T14:01:34.29Z" }, + { url = "https://files.pythonhosted.org/packages/42/53/290046e3bbf8986cdb7366a42dab3440b9983711eaff044a51b11006c67b/coverage-7.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10", size = 219850, upload-time = "2026-02-03T14:01:35.984Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/ab41f10345ba2e49d5e299be8663be2b7db33e77ac1b85cd0af985ea6406/coverage-7.13.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39", size = 250886, upload-time = "2026-02-03T14:01:38.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/2d/b3f6913ee5a1d5cdd04106f257e5fac5d048992ffc2d9995d07b0f17739f/coverage-7.13.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f", size = 253393, upload-time = "2026-02-03T14:01:40.118Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f6/b1f48810ffc6accf49a35b9943636560768f0812330f7456aa87dc39aff5/coverage-7.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4", size = 254740, upload-time = "2026-02-03T14:01:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/57/d0/e59c54f9be0b61808f6bc4c8c4346bd79f02dd6bbc3f476ef26124661f20/coverage-7.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef", size = 250905, upload-time = "2026-02-03T14:01:44.163Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f7/5291bcdf498bafbee3796bb32ef6966e9915aebd4d0954123c8eae921c32/coverage-7.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75", size = 252753, upload-time = "2026-02-03T14:01:45.974Z" }, + { url = "https://files.pythonhosted.org/packages/a0/a9/1dcafa918c281554dae6e10ece88c1add82db685be123e1b05c2056ff3fb/coverage-7.13.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895", size = 250716, upload-time = "2026-02-03T14:01:48.844Z" }, + { url = "https://files.pythonhosted.org/packages/44/bb/4ea4eabcce8c4f6235df6e059fbc5db49107b24c4bdffc44aee81aeca5a8/coverage-7.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c", size = 250530, upload-time = "2026-02-03T14:01:50.793Z" }, + { url = "https://files.pythonhosted.org/packages/6d/31/4a6c9e6a71367e6f923b27b528448c37f4e959b7e4029330523014691007/coverage-7.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a", size = 252186, upload-time = "2026-02-03T14:01:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/27/92/e1451ef6390a4f655dc42da35d9971212f7abbbcad0bdb7af4407897eb76/coverage-7.13.3-cp314-cp314-win32.whl", hash = "sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4", size = 222253, upload-time = "2026-02-03T14:01:55.071Z" }, + { url = "https://files.pythonhosted.org/packages/8a/98/78885a861a88de020c32a2693487c37d15a9873372953f0c3c159d575a43/coverage-7.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0", size = 223069, upload-time = "2026-02-03T14:01:56.95Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fb/3784753a48da58a5337972abf7ca58b1fb0f1bda21bc7b4fae992fd28e47/coverage-7.13.3-cp314-cp314-win_arm64.whl", hash = "sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3", size = 221633, upload-time = "2026-02-03T14:01:58.758Z" }, + { url = "https://files.pythonhosted.org/packages/40/f9/75b732d9674d32cdbffe801ed5f770786dd1c97eecedef2125b0d25102dc/coverage-7.13.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8", size = 220243, upload-time = "2026-02-03T14:02:01.109Z" }, + { url = "https://files.pythonhosted.org/packages/cf/7e/2868ec95de5a65703e6f0c87407ea822d1feb3619600fbc3c1c4fa986090/coverage-7.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca", size = 220515, upload-time = "2026-02-03T14:02:02.862Z" }, + { url = "https://files.pythonhosted.org/packages/7d/eb/9f0d349652fced20bcaea0f67fc5777bd097c92369f267975732f3dc5f45/coverage-7.13.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba", size = 261874, upload-time = "2026-02-03T14:02:04.727Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a5/6619bc4a6c7b139b16818149a3e74ab2e21599ff9a7b6811b6afde99f8ec/coverage-7.13.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f", size = 264004, upload-time = "2026-02-03T14:02:06.634Z" }, + { url = "https://files.pythonhosted.org/packages/29/b7/90aa3fc645a50c6f07881fca4fd0ba21e3bfb6ce3a7078424ea3a35c74c9/coverage-7.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508", size = 266408, upload-time = "2026-02-03T14:02:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/55/08bb2a1e4dcbae384e638f0effef486ba5987b06700e481691891427d879/coverage-7.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba", size = 260977, upload-time = "2026-02-03T14:02:11.755Z" }, + { url = "https://files.pythonhosted.org/packages/9b/76/8bd4ae055a42d8fb5dd2230e5cf36ff2e05f85f2427e91b11a27fea52ed7/coverage-7.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd", size = 263868, upload-time = "2026-02-03T14:02:13.565Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/ba000560f11e9e32ec03df5aa8477242c2d95b379c99ac9a7b2e7fbacb1a/coverage-7.13.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab", size = 261474, upload-time = "2026-02-03T14:02:16.069Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/4de4de8f9ca7af4733bfcf4baa440121b7dbb3856daf8428ce91481ff63b/coverage-7.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e", size = 260317, upload-time = "2026-02-03T14:02:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/05/71/5cd8436e2c21410ff70be81f738c0dddea91bcc3189b1517d26e0102ccb3/coverage-7.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024", size = 262635, upload-time = "2026-02-03T14:02:20.405Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f8/2834bb45bdd70b55a33ec354b8b5f6062fc90e5bb787e14385903a979503/coverage-7.13.3-cp314-cp314t-win32.whl", hash = "sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3", size = 223035, upload-time = "2026-02-03T14:02:22.323Z" }, + { url = "https://files.pythonhosted.org/packages/26/75/f8290f0073c00d9ae14056d2b84ab92dff21d5370e464cb6cb06f52bf580/coverage-7.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8", size = 224142, upload-time = "2026-02-03T14:02:24.143Z" }, + { url = "https://files.pythonhosted.org/packages/03/01/43ac78dfea8946c4a9161bbc034b5549115cb2b56781a4b574927f0d141a/coverage-7.13.3-cp314-cp314t-win_arm64.whl", hash = "sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3", size = 222166, upload-time = "2026-02-03T14:02:26.005Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/70af542d2d938c778c9373ce253aa4116dbe7c0a5672f78b2b2ae0e1b94b/coverage-7.13.3-py3-none-any.whl", hash = "sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910", size = 211237, upload-time = "2026-02-03T14:02:27.986Z" }, ] [package.optional-dependencies] @@ -809,79 +810,74 @@ toml = [ [[package]] name = "cross-web" -version = "0.4.0" +version = "0.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/93/4f/bdb62e969649ee76d4741ef8eee34384ec2bc21cc66eb7fd244e6ad62be8/cross_web-0.4.0.tar.gz", hash = "sha256:4ae65619ddfcd06d6803432c0366342d7e8aeba10194b4e144d73a662e75370c", size = 157111, upload-time = "2025-12-25T20:45:21.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/58/e688e99d1493c565d1587e64b499268d0a3129ae59f4efe440aac395f803/cross_web-0.4.1.tar.gz", hash = "sha256:0466295028dcae98c9ab3d18757f90b0e74fac2ff90efbe87e74657546d9993d", size = 157385, upload-time = "2026-01-09T18:17:41.534Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/d6/6c6a036655e5091b26b9f350dcf43821895325aa4727396b14c67679a957/cross_web-0.4.0-py3-none-any.whl", hash = "sha256:0c675bd26e91428cab31e3e927929b42da94aa96da92974e57c78f9a732d0e9b", size = 14200, upload-time = "2025-12-25T20:45:23.075Z" }, + { url = "https://files.pythonhosted.org/packages/67/49/92b46b6e65f09b717a66c4e5a9bc47a45ebc83dd0e0ed126f8258363479d/cross_web-0.4.1-py3-none-any.whl", hash = "sha256:41b07c3a38253c517ec0603c1a366353aff77538946092b0f9a2235033f192c2", size = 14320, upload-time = "2026-01-09T18:17:40.325Z" }, ] [[package]] name = "cryptography" -version = "46.0.3" +version = "46.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/19/f748958276519adf6a0c1e79e7b8860b4830dda55ccdf29f2719b5fc499c/cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59", size = 749301, upload-time = "2026-01-28T00:24:37.379Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, - { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, - { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, - { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, - { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, - { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, - { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" }, - { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, - { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, - { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, - { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, - { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, + { url = "https://files.pythonhosted.org/packages/8d/99/157aae7949a5f30d51fcb1a9851e8ebd5c74bf99b5285d8bb4b8b9ee641e/cryptography-46.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:281526e865ed4166009e235afadf3a4c4cba6056f99336a99efba65336fd5485", size = 7173686, upload-time = "2026-01-28T00:23:07.515Z" }, + { url = "https://files.pythonhosted.org/packages/87/91/874b8910903159043b5c6a123b7e79c4559ddd1896e38967567942635778/cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc", size = 4275871, upload-time = "2026-01-28T00:23:09.439Z" }, + { url = "https://files.pythonhosted.org/packages/c0/35/690e809be77896111f5b195ede56e4b4ed0435b428c2f2b6d35046fbb5e8/cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0", size = 4423124, upload-time = "2026-01-28T00:23:11.529Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa", size = 4277090, upload-time = "2026-01-28T00:23:13.123Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d8/4bb7aec442a9049827aa34cee1aa83803e528fa55da9a9d45d01d1bb933e/cryptography-46.0.4-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5aa3e463596b0087b3da0dbe2b2487e9fc261d25da85754e30e3b40637d61f81", size = 4947652, upload-time = "2026-01-28T00:23:14.554Z" }, + { url = "https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255", size = 4455157, upload-time = "2026-01-28T00:23:16.443Z" }, + { url = "https://files.pythonhosted.org/packages/0a/05/19d849cf4096448779d2dcc9bb27d097457dac36f7273ffa875a93b5884c/cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e", size = 3981078, upload-time = "2026-01-28T00:23:17.838Z" }, + { url = "https://files.pythonhosted.org/packages/e6/89/f7bac81d66ba7cde867a743ea5b37537b32b5c633c473002b26a226f703f/cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c", size = 4276213, upload-time = "2026-01-28T00:23:19.257Z" }, + { url = "https://files.pythonhosted.org/packages/da/9f/7133e41f24edd827020ad21b068736e792bc68eecf66d93c924ad4719fb3/cryptography-46.0.4-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:eeeb2e33d8dbcccc34d64651f00a98cb41b2dc69cef866771a5717e6734dfa32", size = 4912190, upload-time = "2026-01-28T00:23:21.244Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f7/6d43cbaddf6f65b24816e4af187d211f0bc536a29961f69faedc48501d8e/cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616", size = 4454641, upload-time = "2026-01-28T00:23:22.866Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4f/ebd0473ad656a0ac912a16bd07db0f5d85184924e14fc88feecae2492834/cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0", size = 4405159, upload-time = "2026-01-28T00:23:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f7/7923886f32dc47e27adeff8246e976d77258fd2aa3efdd1754e4e323bf49/cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0", size = 4666059, upload-time = "2026-01-28T00:23:26.766Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a7/0fca0fd3591dffc297278a61813d7f661a14243dd60f499a7a5b48acb52a/cryptography-46.0.4-cp311-abi3-win32.whl", hash = "sha256:82a62483daf20b8134f6e92898da70d04d0ef9a75829d732ea1018678185f4f5", size = 3026378, upload-time = "2026-01-28T00:23:28.317Z" }, + { url = "https://files.pythonhosted.org/packages/2d/12/652c84b6f9873f0909374864a57b003686c642ea48c84d6c7e2c515e6da5/cryptography-46.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:6225d3ebe26a55dbc8ead5ad1265c0403552a63336499564675b29eb3184c09b", size = 3478614, upload-time = "2026-01-28T00:23:30.275Z" }, + { url = "https://files.pythonhosted.org/packages/b9/27/542b029f293a5cce59349d799d4d8484b3b1654a7b9a0585c266e974a488/cryptography-46.0.4-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:485e2b65d25ec0d901bca7bcae0f53b00133bf3173916d8e421f6fddde103908", size = 7116417, upload-time = "2026-01-28T00:23:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f5/559c25b77f40b6bf828eabaf988efb8b0e17b573545edb503368ca0a2a03/cryptography-46.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:078e5f06bd2fa5aea5a324f2a09f914b1484f1d0c2a4d6a8a28c74e72f65f2da", size = 4264508, upload-time = "2026-01-28T00:23:34.264Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/551fa162d33074b660dc35c9bc3616fefa21a0e8c1edd27b92559902e408/cryptography-46.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dce1e4f068f03008da7fa51cc7abc6ddc5e5de3e3d1550334eaf8393982a5829", size = 4409080, upload-time = "2026-01-28T00:23:35.793Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/4d8d129a755f5d6df1bbee69ea2f35ebfa954fa1847690d1db2e8bca46a5/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2067461c80271f422ee7bdbe79b9b4be54a5162e90345f86a23445a0cf3fd8a2", size = 4270039, upload-time = "2026-01-28T00:23:37.263Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f5/ed3fcddd0a5e39321e595e144615399e47e7c153a1fb8c4862aec3151ff9/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:c92010b58a51196a5f41c3795190203ac52edfd5dc3ff99149b4659eba9d2085", size = 4926748, upload-time = "2026-01-28T00:23:38.884Z" }, + { url = "https://files.pythonhosted.org/packages/43/ae/9f03d5f0c0c00e85ecb34f06d3b79599f20630e4db91b8a6e56e8f83d410/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:829c2b12bbc5428ab02d6b7f7e9bbfd53e33efd6672d21341f2177470171ad8b", size = 4442307, upload-time = "2026-01-28T00:23:40.56Z" }, + { url = "https://files.pythonhosted.org/packages/8b/22/e0f9f2dae8040695103369cf2283ef9ac8abe4d51f68710bec2afd232609/cryptography-46.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:62217ba44bf81b30abaeda1488686a04a702a261e26f87db51ff61d9d3510abd", size = 3959253, upload-time = "2026-01-28T00:23:42.827Z" }, + { url = "https://files.pythonhosted.org/packages/01/5b/6a43fcccc51dae4d101ac7d378a8724d1ba3de628a24e11bf2f4f43cba4d/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:9c2da296c8d3415b93e6053f5a728649a87a48ce084a9aaf51d6e46c87c7f2d2", size = 4269372, upload-time = "2026-01-28T00:23:44.655Z" }, + { url = "https://files.pythonhosted.org/packages/17/b7/0f6b8c1dd0779df2b526e78978ff00462355e31c0a6f6cff8a3e99889c90/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9b34d8ba84454641a6bf4d6762d15847ecbd85c1316c0a7984e6e4e9f748ec2e", size = 4891908, upload-time = "2026-01-28T00:23:46.48Z" }, + { url = "https://files.pythonhosted.org/packages/83/17/259409b8349aa10535358807a472c6a695cf84f106022268d31cea2b6c97/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:df4a817fa7138dd0c96c8c8c20f04b8aaa1fac3bbf610913dcad8ea82e1bfd3f", size = 4441254, upload-time = "2026-01-28T00:23:48.403Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fe/e4a1b0c989b00cee5ffa0764401767e2d1cf59f45530963b894129fd5dce/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b1de0ebf7587f28f9190b9cb526e901bf448c9e6a99655d2b07fff60e8212a82", size = 4396520, upload-time = "2026-01-28T00:23:50.26Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/ba8fd9657d27076eb40d6a2f941b23429a3c3d2f56f5a921d6b936a27bc9/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9b4d17bc7bd7cdd98e3af40b441feaea4c68225e2eb2341026c84511ad246c0c", size = 4651479, upload-time = "2026-01-28T00:23:51.674Z" }, + { url = "https://files.pythonhosted.org/packages/00/03/0de4ed43c71c31e4fe954edd50b9d28d658fef56555eba7641696370a8e2/cryptography-46.0.4-cp314-cp314t-win32.whl", hash = "sha256:c411f16275b0dea722d76544a61d6421e2cc829ad76eec79280dbdc9ddf50061", size = 3001986, upload-time = "2026-01-28T00:23:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/5c/70/81830b59df7682917d7a10f833c4dab2a5574cd664e86d18139f2b421329/cryptography-46.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:728fedc529efc1439eb6107b677f7f7558adab4553ef8669f0d02d42d7b959a7", size = 3468288, upload-time = "2026-01-28T00:23:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/56/f7/f648fdbb61d0d45902d3f374217451385edc7e7768d1b03ff1d0e5ffc17b/cryptography-46.0.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9556ba711f7c23f77b151d5798f3ac44a13455cc68db7697a1096e6d0563cab", size = 7169583, upload-time = "2026-01-28T00:23:56.558Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cc/8f3224cbb2a928de7298d6ed4790f5ebc48114e02bdc9559196bfb12435d/cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef", size = 4275419, upload-time = "2026-01-28T00:23:58.364Z" }, + { url = "https://files.pythonhosted.org/packages/17/43/4a18faa7a872d00e4264855134ba82d23546c850a70ff209e04ee200e76f/cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d", size = 4419058, upload-time = "2026-01-28T00:23:59.867Z" }, + { url = "https://files.pythonhosted.org/packages/ee/64/6651969409821d791ba12346a124f55e1b76f66a819254ae840a965d4b9c/cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973", size = 4278151, upload-time = "2026-01-28T00:24:01.731Z" }, + { url = "https://files.pythonhosted.org/packages/20/0b/a7fce65ee08c3c02f7a8310cc090a732344066b990ac63a9dfd0a655d321/cryptography-46.0.4-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a90e43e3ef65e6dcf969dfe3bb40cbf5aef0d523dff95bfa24256be172a845f4", size = 4939441, upload-time = "2026-01-28T00:24:03.175Z" }, + { url = "https://files.pythonhosted.org/packages/db/a7/20c5701e2cd3e1dfd7a19d2290c522a5f435dd30957d431dcb531d0f1413/cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af", size = 4451617, upload-time = "2026-01-28T00:24:05.403Z" }, + { url = "https://files.pythonhosted.org/packages/00/dc/3e16030ea9aa47b63af6524c354933b4fb0e352257c792c4deeb0edae367/cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263", size = 3977774, upload-time = "2026-01-28T00:24:06.851Z" }, + { url = "https://files.pythonhosted.org/packages/42/c8/ad93f14118252717b465880368721c963975ac4b941b7ef88f3c56bf2897/cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095", size = 4277008, upload-time = "2026-01-28T00:24:08.926Z" }, + { url = "https://files.pythonhosted.org/packages/00/cf/89c99698151c00a4631fbfcfcf459d308213ac29e321b0ff44ceeeac82f1/cryptography-46.0.4-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d5a45ddc256f492ce42a4e35879c5e5528c09cd9ad12420828c972951d8e016b", size = 4903339, upload-time = "2026-01-28T00:24:12.009Z" }, + { url = "https://files.pythonhosted.org/packages/03/c3/c90a2cb358de4ac9309b26acf49b2a100957e1ff5cc1e98e6c4996576710/cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019", size = 4451216, upload-time = "2026-01-28T00:24:13.975Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/8d7f4171388a10208671e181ca43cdc0e596d8259ebacbbcfbd16de593da/cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4", size = 4404299, upload-time = "2026-01-28T00:24:16.169Z" }, + { url = "https://files.pythonhosted.org/packages/e9/23/cbb2036e450980f65c6e0a173b73a56ff3bccd8998965dea5cc9ddd424a5/cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b", size = 4664837, upload-time = "2026-01-28T00:24:17.629Z" }, + { url = "https://files.pythonhosted.org/packages/0a/21/f7433d18fe6d5845329cbdc597e30caf983229c7a245bcf54afecc555938/cryptography-46.0.4-cp38-abi3-win32.whl", hash = "sha256:0563655cb3c6d05fb2afe693340bc050c30f9f34e15763361cf08e94749401fc", size = 3009779, upload-time = "2026-01-28T00:24:20.198Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6a/bd2e7caa2facffedf172a45c1a02e551e6d7d4828658c9a245516a598d94/cryptography-46.0.4-cp38-abi3-win_amd64.whl", hash = "sha256:fa0900b9ef9c49728887d1576fd8d9e7e3ea872fa9b25ef9b64888adc434e976", size = 3466633, upload-time = "2026-01-28T00:24:21.851Z" }, + { url = "https://files.pythonhosted.org/packages/59/e0/f9c6c53e1f2a1c2507f00f2faba00f01d2f334b35b0fbfe5286715da2184/cryptography-46.0.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:766330cce7416c92b5e90c3bb71b1b79521760cdcfc3a6a1a182d4c9fab23d2b", size = 3476316, upload-time = "2026-01-28T00:24:24.144Z" }, + { url = "https://files.pythonhosted.org/packages/27/7a/f8d2d13227a9a1a9fe9c7442b057efecffa41f1e3c51d8622f26b9edbe8f/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c236a44acfb610e70f6b3e1c3ca20ff24459659231ef2f8c48e879e2d32b73da", size = 4216693, upload-time = "2026-01-28T00:24:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/c5/de/3787054e8f7972658370198753835d9d680f6cd4a39df9f877b57f0dd69c/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8a15fb869670efa8f83cbffbc8753c1abf236883225aed74cd179b720ac9ec80", size = 4382765, upload-time = "2026-01-28T00:24:27.577Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5f/60e0afb019973ba6a0b322e86b3d61edf487a4f5597618a430a2a15f2d22/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:fdc3daab53b212472f1524d070735b2f0c214239df131903bae1d598016fa822", size = 4216066, upload-time = "2026-01-28T00:24:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/81/8e/bf4a0de294f147fee66f879d9bae6f8e8d61515558e3d12785dd90eca0be/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:44cc0675b27cadb71bdbb96099cca1fa051cd11d2ade09e5cd3a2edb929ed947", size = 4382025, upload-time = "2026-01-28T00:24:30.681Z" }, + { url = "https://files.pythonhosted.org/packages/79/f4/9ceb90cfd6a3847069b0b0b353fd3075dc69b49defc70182d8af0c4ca390/cryptography-46.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be8c01a7d5a55f9a47d1888162b76c8f49d62b234d88f0ff91a9fbebe32ffbc3", size = 3406043, upload-time = "2026-01-28T00:24:32.236Z" }, ] [[package]] @@ -1031,12 +1027,13 @@ all = [ { name = "httpx" }, { name = "itsdangerous" }, { name = "jinja2" }, - { name = "orjson" }, + { name = "orjson", version = "3.11.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "orjson", version = "3.11.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pydantic-extra-types" }, { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "python-multipart", version = "0.0.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "python-multipart", version = "0.0.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-multipart", version = "0.0.22", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pyyaml" }, { name = "ujson" }, { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, @@ -1051,7 +1048,7 @@ standard = [ { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "python-multipart", version = "0.0.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "python-multipart", version = "0.0.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-multipart", version = "0.0.22", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, ] @@ -1064,7 +1061,7 @@ standard-no-fastapi-cloud-cli = [ { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "python-multipart", version = "0.0.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "python-multipart", version = "0.0.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-multipart", version = "0.0.22", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, ] @@ -1076,7 +1073,7 @@ dev = [ { name = "black" }, { name = "cairosvg" }, { 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'" }, + { name = "coverage", version = "7.13.3", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, { name = "dirty-equals" }, { name = "flask" }, { name = "gitpython" }, @@ -1107,7 +1104,7 @@ dev = [ { name = "ruff" }, { name = "sqlmodel" }, { name = "strawberry-graphql", version = "0.283.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "strawberry-graphql", version = "0.288.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "strawberry-graphql", version = "0.291.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "typer" }, { name = "types-orjson" }, { name = "types-ujson" }, @@ -1148,7 +1145,7 @@ 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'" }, + { name = "coverage", version = "7.13.3", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, { name = "dirty-equals" }, { name = "flask" }, { name = "httpx" }, @@ -1163,7 +1160,7 @@ tests = [ { name = "ruff" }, { name = "sqlmodel" }, { name = "strawberry-graphql", version = "0.283.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "strawberry-graphql", version = "0.288.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "strawberry-graphql", version = "0.291.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "types-orjson" }, { name = "types-ujson" }, ] @@ -1189,7 +1186,7 @@ requires-dist = [ { name = "jinja2", marker = "extra == 'all'", specifier = ">=3.1.5" }, { name = "jinja2", marker = "extra == 'standard'", specifier = ">=3.1.5" }, { name = "jinja2", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=3.1.5" }, - { name = "orjson", marker = "extra == 'all'", specifier = ">=3.2.1" }, + { name = "orjson", marker = "extra == 'all'", specifier = ">=3.9.3" }, { name = "pydantic", specifier = ">=2.7.0" }, { name = "pydantic-extra-types", marker = "extra == 'all'", specifier = ">=2.0.0" }, { name = "pydantic-extra-types", marker = "extra == 'standard'", specifier = ">=2.0.0" }, @@ -1204,7 +1201,7 @@ requires-dist = [ { name = "starlette", specifier = ">=0.40.0,<0.51.0" }, { name = "typing-extensions", specifier = ">=4.8.0" }, { name = "typing-inspection", specifier = ">=0.4.2" }, - { name = "ujson", marker = "extra == 'all'", specifier = ">=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0" }, + { name = "ujson", marker = "extra == 'all'", specifier = ">=5.8.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.12.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.12.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.12.0" }, @@ -1219,7 +1216,7 @@ dev = [ { 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 = "flask", specifier = ">=3.0.0,<4.0.0" }, { name = "gitpython", specifier = "==3.1.46" }, { name = "griffe-typingdoc", specifier = "==0.3.0" }, { name = "griffe-warnings-deprecated", specifier = "==1.1.0" }, @@ -1287,7 +1284,7 @@ tests = [ { 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" }, - { name = "flask", specifier = ">=1.1.2,<4.0.0" }, + { name = "flask", specifier = ">=3.0.0,<4.0.0" }, { name = "httpx", specifier = ">=0.23.0,<1.0.0" }, { name = "inline-snapshot", specifier = ">=0.21.1" }, { name = "mypy", specifier = "==1.14.1" }, @@ -1337,7 +1334,7 @@ standard-no-fastapi-cloud-cli = [ [[package]] name = "fastapi-cloud-cli" -version = "0.8.0" +version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastar" }, @@ -1350,9 +1347,9 @@ dependencies = [ { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/5d/3b33438de35521fab4968b232caa9a4bd568a5078f2b2dfb7bb8a4528603/fastapi_cloud_cli-0.8.0.tar.gz", hash = "sha256:cf07c502528bfd9e6b184776659f05d9212811d76bbec9fbb6bf34bed4c7456f", size = 30257, upload-time = "2025-12-23T12:08:33.904Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/15/6c3d85d63964340fde6f36cc80f3f365d35f371e6a918d68ff3a3d588ef2/fastapi_cloud_cli-0.11.0.tar.gz", hash = "sha256:ecc83a5db106be35af528eccb01aa9bced1d29783efd48c8c1c831cf111eea99", size = 36170, upload-time = "2026-01-15T09:51:33.681Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/8e/abb95ef59e91bb5adaa2d18fbf9ea70fd524010bb03f406a2dd2a4775ef9/fastapi_cloud_cli-0.8.0-py3-none-any.whl", hash = "sha256:e9f40bee671d985fd25d7a5409b56d4f103777bf8a0c6d746ea5fbf97a8186d9", size = 22306, upload-time = "2025-12-23T12:08:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/1a/07/60f79270a3320780be7e2ae8a1740cb98a692920b569ba420b97bcc6e175/fastapi_cloud_cli-0.11.0-py3-none-any.whl", hash = "sha256:76857b0f09d918acfcb50ade34682ba3b2079ca0c43fda10215de301f185a7f8", size = 26884, upload-time = "2026-01-15T09:51:34.471Z" }, ] [[package]] @@ -1569,15 +1566,15 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.2" +version = "3.20.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version >= '3.10' and python_full_version < '3.14'", ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/e0/a75dbe4bca1e7d41307323dad5ea2efdd95408f74ab2de8bd7dba9b51a1a/filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64", size = 19510, upload-time = "2026-01-02T15:33:32.582Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8", size = 16697, upload-time = "2026-01-02T15:33:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] [[package]] @@ -1613,15 +1610,15 @@ wheels = [ [[package]] name = "fsspec" -version = "2025.12.0" +version = "2026.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version >= '3.10' and python_full_version < '3.14'", ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/27/954057b0d1f53f086f681755207dda6de6c660ce133c829158e8e8fe7895/fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973", size = 309748, upload-time = "2025-12-03T15:23:42.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422, upload-time = "2025-12-03T15:23:41.434Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, ] [[package]] @@ -1663,15 +1660,16 @@ wheels = [ [[package]] name = "google-auth" -version = "2.47.0" +version = "2.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "cryptography" }, { name = "pyasn1-modules" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/3c/ec64b9a275ca22fa1cd3b6e77fefcf837b0732c890aa32d2bd21313d9b33/google_auth-2.47.0.tar.gz", hash = "sha256:833229070a9dfee1a353ae9877dcd2dec069a8281a4e72e72f77d4a70ff945da", size = 323719, upload-time = "2026-01-06T21:55:31.045Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/41/242044323fbd746615884b1c16639749e73665b718209946ebad7ba8a813/google_auth-2.48.0.tar.gz", hash = "sha256:4f7e706b0cd3208a3d940a19a822c37a476ddba5450156c3e6624a71f7c841ce", size = 326522, upload-time = "2026-01-26T19:22:47.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/18/79e9008530b79527e0d5f79e7eef08d3b179b7f851cfd3a2f27822fbdfa9/google_auth-2.47.0-py3-none-any.whl", hash = "sha256:c516d68336bfde7cf0da26aab674a36fedcf04b37ac4edd59c597178760c3498", size = 234867, upload-time = "2026-01-06T21:55:28.6Z" }, + { url = "https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl", hash = "sha256:2e2a537873d449434252a9632c28bfc268b0adb1e53f9fb62afc5333a975903f", size = 236499, upload-time = "2026-01-26T19:22:45.099Z" }, ] [package.optional-dependencies] @@ -1692,7 +1690,7 @@ dependencies = [ { name = "httpx", marker = "python_full_version < '3.10'" }, { name = "pydantic", marker = "python_full_version < '3.10'" }, { name = "requests", marker = "python_full_version < '3.10'" }, - { name = "tenacity", marker = "python_full_version < '3.10'" }, + { name = "tenacity", version = "9.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, { name = "websockets", marker = "python_full_version < '3.10'" }, ] @@ -1703,7 +1701,7 @@ wheels = [ [[package]] name = "google-genai" -version = "1.57.0" +version = "1.62.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -1717,13 +1715,13 @@ dependencies = [ { name = "pydantic", marker = "python_full_version >= '3.10'" }, { name = "requests", marker = "python_full_version >= '3.10'" }, { name = "sniffio", marker = "python_full_version >= '3.10'" }, - { name = "tenacity", marker = "python_full_version >= '3.10'" }, + { name = "tenacity", version = "9.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, { name = "websockets", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/b4/8251c2d2576224a4b51a8ab6159820f9200b8da28ff555c78ee15607096e/google_genai-1.57.0.tar.gz", hash = "sha256:0ff9c36b8d68abfbdbd13b703ece926de5f3e67955666b36315ecf669b94a826", size = 485648, upload-time = "2026-01-07T20:38:20.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/4c/71b32b5c8db420cf2fd0d5ef8a672adbde97d85e5d44a0b4fca712264ef1/google_genai-1.62.0.tar.gz", hash = "sha256:709468a14c739a080bc240a4f3191df597bf64485b1ca3728e0fb67517774c18", size = 490888, upload-time = "2026-02-04T22:48:41.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/02/858bdae08e2184b6afe0b18bc3113318522c9cf326a5a1698055edd31f88/google_genai-1.57.0-py3-none-any.whl", hash = "sha256:d63c7a89a1f549c4d14032f41a0cdb4b6fe3f565e2eee6b5e0907a0aeceabefd", size = 713323, upload-time = "2026-01-07T20:38:18.051Z" }, + { url = "https://files.pythonhosted.org/packages/09/5f/4645d8a28c6e431d0dd6011003a852563f3da7037d36af53154925b099fd/google_genai-1.62.0-py3-none-any.whl", hash = "sha256:4c3daeff3d05fafee4b9a1a31f9c07f01bc22051081aa58b4d61f58d16d1bcc0", size = 724166, upload-time = "2026-02-04T22:48:39.956Z" }, ] [[package]] @@ -1816,61 +1814,66 @@ wheels = [ [[package]] name = "greenlet" -version = "3.3.0" +version = "3.3.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version >= '3.10' and python_full_version < '3.14'", ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/e5/40dbda2736893e3e53d25838e0f19a2b417dfc122b9989c91918db30b5d3/greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb", size = 190651, upload-time = "2025-12-04T14:49:44.05Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/99/1cd3411c56a410994669062bd73dd58270c00cc074cac15f385a1fd91f8a/greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", size = 184690, upload-time = "2026-01-23T15:31:02.076Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/6a/33d1702184d94106d3cdd7bfb788e19723206fce152e303473ca3b946c7b/greenlet-3.3.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f8496d434d5cb2dce025773ba5597f71f5410ae499d5dd9533e0653258cdb3d", size = 273658, upload-time = "2025-12-04T14:23:37.494Z" }, - { url = "https://files.pythonhosted.org/packages/d6/b7/2b5805bbf1907c26e434f4e448cd8b696a0b71725204fa21a211ff0c04a7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b96dc7eef78fd404e022e165ec55327f935b9b52ff355b067eb4a0267fc1cffb", size = 574810, upload-time = "2025-12-04T14:50:04.154Z" }, - { url = "https://files.pythonhosted.org/packages/94/38/343242ec12eddf3d8458c73f555c084359883d4ddc674240d9e61ec51fd6/greenlet-3.3.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73631cd5cccbcfe63e3f9492aaa664d278fda0ce5c3d43aeda8e77317e38efbd", size = 586248, upload-time = "2025-12-04T14:57:39.35Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d0/0ae86792fb212e4384041e0ef8e7bc66f59a54912ce407d26a966ed2914d/greenlet-3.3.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b299a0cb979f5d7197442dccc3aee67fce53500cd88951b7e6c35575701c980b", size = 597403, upload-time = "2025-12-04T15:07:10.831Z" }, - { url = "https://files.pythonhosted.org/packages/b6/a8/15d0aa26c0036a15d2659175af00954aaaa5d0d66ba538345bd88013b4d7/greenlet-3.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dee147740789a4632cace364816046e43310b59ff8fb79833ab043aefa72fd5", size = 586910, upload-time = "2025-12-04T14:25:59.705Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9b/68d5e3b7ccaba3907e5532cf8b9bf16f9ef5056a008f195a367db0ff32db/greenlet-3.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:39b28e339fc3c348427560494e28d8a6f3561c8d2bcf7d706e1c624ed8d822b9", size = 1547206, upload-time = "2025-12-04T15:04:21.027Z" }, - { url = "https://files.pythonhosted.org/packages/66/bd/e3086ccedc61e49f91e2cfb5ffad9d8d62e5dc85e512a6200f096875b60c/greenlet-3.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3c374782c2935cc63b2a27ba8708471de4ad1abaa862ffdb1ef45a643ddbb7d", size = 1613359, upload-time = "2025-12-04T14:27:26.548Z" }, - { url = "https://files.pythonhosted.org/packages/f4/6b/d4e73f5dfa888364bbf02efa85616c6714ae7c631c201349782e5b428925/greenlet-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b49e7ed51876b459bd645d83db257f0180e345d3f768a35a85437a24d5a49082", size = 300740, upload-time = "2025-12-04T14:47:52.773Z" }, - { url = "https://files.pythonhosted.org/packages/1f/cb/48e964c452ca2b92175a9b2dca037a553036cb053ba69e284650ce755f13/greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e", size = 274908, upload-time = "2025-12-04T14:23:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/28/da/38d7bff4d0277b594ec557f479d65272a893f1f2a716cad91efeb8680953/greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62", size = 577113, upload-time = "2025-12-04T14:50:05.493Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f2/89c5eb0faddc3ff014f1c04467d67dee0d1d334ab81fadbf3744847f8a8a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32", size = 590338, upload-time = "2025-12-04T14:57:41.136Z" }, - { url = "https://files.pythonhosted.org/packages/80/d7/db0a5085035d05134f8c089643da2b44cc9b80647c39e93129c5ef170d8f/greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45", size = 601098, upload-time = "2025-12-04T15:07:11.898Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/e959a127b630a58e23529972dbc868c107f9d583b5a9f878fb858c46bc1a/greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948", size = 590206, upload-time = "2025-12-04T14:26:01.254Z" }, - { url = "https://files.pythonhosted.org/packages/48/60/29035719feb91798693023608447283b266b12efc576ed013dd9442364bb/greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794", size = 1550668, upload-time = "2025-12-04T15:04:22.439Z" }, - { url = "https://files.pythonhosted.org/packages/0a/5f/783a23754b691bfa86bd72c3033aa107490deac9b2ef190837b860996c9f/greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5", size = 1615483, upload-time = "2025-12-04T14:27:28.083Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d5/c339b3b4bc8198b7caa4f2bd9fd685ac9f29795816d8db112da3d04175bb/greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71", size = 301164, upload-time = "2025-12-04T14:42:51.577Z" }, - { url = "https://files.pythonhosted.org/packages/f8/0a/a3871375c7b9727edaeeea994bfff7c63ff7804c9829c19309ba2e058807/greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb", size = 276379, upload-time = "2025-12-04T14:23:30.498Z" }, - { url = "https://files.pythonhosted.org/packages/43/ab/7ebfe34dce8b87be0d11dae91acbf76f7b8246bf9d6b319c741f99fa59c6/greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3", size = 597294, upload-time = "2025-12-04T14:50:06.847Z" }, - { url = "https://files.pythonhosted.org/packages/a4/39/f1c8da50024feecd0793dbd5e08f526809b8ab5609224a2da40aad3a7641/greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655", size = 607742, upload-time = "2025-12-04T14:57:42.349Z" }, - { url = "https://files.pythonhosted.org/packages/77/cb/43692bcd5f7a0da6ec0ec6d58ee7cddb606d055ce94a62ac9b1aa481e969/greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7", size = 622297, upload-time = "2025-12-04T15:07:13.552Z" }, - { url = "https://files.pythonhosted.org/packages/75/b0/6bde0b1011a60782108c01de5913c588cf51a839174538d266de15e4bf4d/greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b", size = 609885, upload-time = "2025-12-04T14:26:02.368Z" }, - { url = "https://files.pythonhosted.org/packages/49/0e/49b46ac39f931f59f987b7cd9f34bfec8ef81d2a1e6e00682f55be5de9f4/greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53", size = 1567424, upload-time = "2025-12-04T15:04:23.757Z" }, - { url = "https://files.pythonhosted.org/packages/05/f5/49a9ac2dff7f10091935def9165c90236d8f175afb27cbed38fb1d61ab6b/greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614", size = 1636017, upload-time = "2025-12-04T14:27:29.688Z" }, - { url = "https://files.pythonhosted.org/packages/6c/79/3912a94cf27ec503e51ba493692d6db1e3cd8ac7ac52b0b47c8e33d7f4f9/greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39", size = 301964, upload-time = "2025-12-04T14:36:58.316Z" }, - { url = "https://files.pythonhosted.org/packages/02/2f/28592176381b9ab2cafa12829ba7b472d177f3acc35d8fbcf3673d966fff/greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739", size = 275140, upload-time = "2025-12-04T14:23:01.282Z" }, - { url = "https://files.pythonhosted.org/packages/2c/80/fbe937bf81e9fca98c981fe499e59a3f45df2a04da0baa5c2be0dca0d329/greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808", size = 599219, upload-time = "2025-12-04T14:50:08.309Z" }, - { url = "https://files.pythonhosted.org/packages/c2/ff/7c985128f0514271b8268476af89aee6866df5eec04ac17dcfbc676213df/greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54", size = 610211, upload-time = "2025-12-04T14:57:43.968Z" }, - { url = "https://files.pythonhosted.org/packages/79/07/c47a82d881319ec18a4510bb30463ed6891f2ad2c1901ed5ec23d3de351f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492", size = 624311, upload-time = "2025-12-04T15:07:14.697Z" }, - { url = "https://files.pythonhosted.org/packages/fd/8e/424b8c6e78bd9837d14ff7df01a9829fc883ba2ab4ea787d4f848435f23f/greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527", size = 612833, upload-time = "2025-12-04T14:26:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ba/56699ff9b7c76ca12f1cdc27a886d0f81f2189c3455ff9f65246780f713d/greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39", size = 1567256, upload-time = "2025-12-04T15:04:25.276Z" }, - { url = "https://files.pythonhosted.org/packages/1e/37/f31136132967982d698c71a281a8901daf1a8fbab935dce7c0cf15f942cc/greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8", size = 1636483, upload-time = "2025-12-04T14:27:30.804Z" }, - { url = "https://files.pythonhosted.org/packages/7e/71/ba21c3fb8c5dce83b8c01f458a42e99ffdb1963aeec08fff5a18588d8fd7/greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38", size = 301833, upload-time = "2025-12-04T14:32:23.929Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f0a6d0ede2c7bf092d00bc83ad5bafb7e6ec9b4aab2fbdfa6f134dc73327/greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f", size = 275671, upload-time = "2025-12-04T14:23:05.267Z" }, - { url = "https://files.pythonhosted.org/packages/44/06/dac639ae1a50f5969d82d2e3dd9767d30d6dbdbab0e1a54010c8fe90263c/greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365", size = 646360, upload-time = "2025-12-04T14:50:10.026Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/0fb76fe6c5369fba9bf98529ada6f4c3a1adf19e406a47332245ef0eb357/greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3", size = 658160, upload-time = "2025-12-04T14:57:45.41Z" }, - { url = "https://files.pythonhosted.org/packages/93/79/d2c70cae6e823fac36c3bbc9077962105052b7ef81db2f01ec3b9bf17e2b/greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45", size = 671388, upload-time = "2025-12-04T15:07:15.789Z" }, - { url = "https://files.pythonhosted.org/packages/b8/14/bab308fc2c1b5228c3224ec2bf928ce2e4d21d8046c161e44a2012b5203e/greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955", size = 660166, upload-time = "2025-12-04T14:26:05.099Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d2/91465d39164eaa0085177f61983d80ffe746c5a1860f009811d498e7259c/greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55", size = 1615193, upload-time = "2025-12-04T15:04:27.041Z" }, - { url = "https://files.pythonhosted.org/packages/42/1b/83d110a37044b92423084d52d5d5a3b3a73cafb51b547e6d7366ff62eff1/greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc", size = 1683653, upload-time = "2025-12-04T14:27:32.366Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/9030e6f9aa8fd7808e9c31ba4c38f87c4f8ec324ee67431d181fe396d705/greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170", size = 305387, upload-time = "2025-12-04T14:26:51.063Z" }, - { url = "https://files.pythonhosted.org/packages/a0/66/bd6317bc5932accf351fc19f177ffba53712a202f9df10587da8df257c7e/greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931", size = 282638, upload-time = "2025-12-04T14:25:20.941Z" }, - { url = "https://files.pythonhosted.org/packages/30/cf/cc81cb030b40e738d6e69502ccbd0dd1bced0588e958f9e757945de24404/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388", size = 651145, upload-time = "2025-12-04T14:50:11.039Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ea/1020037b5ecfe95ca7df8d8549959baceb8186031da83d5ecceff8b08cd2/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3", size = 654236, upload-time = "2025-12-04T14:57:47.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/cc/1e4bae2e45ca2fa55299f4e85854606a78ecc37fead20d69322f96000504/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221", size = 662506, upload-time = "2025-12-04T15:07:16.906Z" }, - { url = "https://files.pythonhosted.org/packages/57/b9/f8025d71a6085c441a7eaff0fd928bbb275a6633773667023d19179fe815/greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b", size = 653783, upload-time = "2025-12-04T14:26:06.225Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c7/876a8c7a7485d5d6b5c6821201d542ef28be645aa024cfe1145b35c120c1/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd", size = 1614857, upload-time = "2025-12-04T15:04:28.484Z" }, - { url = "https://files.pythonhosted.org/packages/4f/dc/041be1dff9f23dac5f48a43323cd0789cb798342011c19a248d9c9335536/greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9", size = 1676034, upload-time = "2025-12-04T14:27:33.531Z" }, + { url = "https://files.pythonhosted.org/packages/fe/65/5b235b40581ad75ab97dcd8b4218022ae8e3ab77c13c919f1a1dfe9171fd/greenlet-3.3.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:04bee4775f40ecefcdaa9d115ab44736cd4b9c5fba733575bfe9379419582e13", size = 273723, upload-time = "2026-01-23T15:30:37.521Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ad/eb4729b85cba2d29499e0a04ca6fbdd8f540afd7be142fd571eea43d712f/greenlet-3.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e1457f4fed12a50e427988a07f0f9df53cf0ee8da23fab16e6732c2ec909d4", size = 574874, upload-time = "2026-01-23T16:00:54.551Z" }, + { url = "https://files.pythonhosted.org/packages/87/32/57cad7fe4c8b82fdaa098c89498ef85ad92dfbb09d5eb713adedfc2ae1f5/greenlet-3.3.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:070472cd156f0656f86f92e954591644e158fd65aa415ffbe2d44ca77656a8f5", size = 586309, upload-time = "2026-01-23T16:05:25.18Z" }, + { url = "https://files.pythonhosted.org/packages/66/66/f041005cb87055e62b0d68680e88ec1a57f4688523d5e2fb305841bc8307/greenlet-3.3.1-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1108b61b06b5224656121c3c8ee8876161c491cbe74e5c519e0634c837cf93d5", size = 597461, upload-time = "2026-01-23T16:15:51.943Z" }, + { url = "https://files.pythonhosted.org/packages/87/eb/8a1ec2da4d55824f160594a75a9d8354a5fe0a300fb1c48e7944265217e1/greenlet-3.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a300354f27dd86bae5fbf7002e6dd2b3255cd372e9242c933faf5e859b703fe", size = 586985, upload-time = "2026-01-23T15:32:47.968Z" }, + { url = "https://files.pythonhosted.org/packages/15/1c/0621dd4321dd8c351372ee8f9308136acb628600658a49be1b7504208738/greenlet-3.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e84b51cbebf9ae573b5fbd15df88887815e3253fc000a7d0ff95170e8f7e9729", size = 1547271, upload-time = "2026-01-23T16:04:18.977Z" }, + { url = "https://files.pythonhosted.org/packages/9d/53/24047f8924c83bea7a59c8678d9571209c6bfe5f4c17c94a78c06024e9f2/greenlet-3.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0093bd1a06d899892427217f0ff2a3c8f306182b8c754336d32e2d587c131b4", size = 1613427, upload-time = "2026-01-23T15:33:44.428Z" }, + { url = "https://files.pythonhosted.org/packages/ff/07/ac9bf1ec008916d1a3373cae212884c1dcff4a4ba0d41127ce81a8deb4e9/greenlet-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:7932f5f57609b6a3b82cc11877709aa7a98e3308983ed93552a1c377069b20c8", size = 226100, upload-time = "2026-01-23T15:30:56.957Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e8/2e1462c8fdbe0f210feb5ac7ad2d9029af8be3bf45bd9fa39765f821642f/greenlet-3.3.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5fd23b9bc6d37b563211c6abbb1b3cab27db385a4449af5c32e932f93017080c", size = 274974, upload-time = "2026-01-23T15:31:02.891Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a8/530a401419a6b302af59f67aaf0b9ba1015855ea7e56c036b5928793c5bd/greenlet-3.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f51496a0bfbaa9d74d36a52d2580d1ef5ed4fdfcff0a73730abfbbbe1403dd", size = 577175, upload-time = "2026-01-23T16:00:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/8e/89/7e812bb9c05e1aaef9b597ac1d0962b9021d2c6269354966451e885c4e6b/greenlet-3.3.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb0feb07fe6e6a74615ee62a880007d976cf739b6669cce95daa7373d4fc69c5", size = 590401, upload-time = "2026-01-23T16:05:26.365Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/e2d5f0e59b94a2269b68a629173263fa40b63da32f5c231307c349315871/greenlet-3.3.1-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67ea3fc73c8cd92f42467a72b75e8f05ed51a0e9b1d15398c913416f2dafd49f", size = 601161, upload-time = "2026-01-23T16:15:53.456Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ae/8d472e1f5ac5efe55c563f3eabb38c98a44b832602e12910750a7c025802/greenlet-3.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39eda9ba259cc9801da05351eaa8576e9aa83eb9411e8f0c299e05d712a210f2", size = 590272, upload-time = "2026-01-23T15:32:49.411Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/0fde34bebfcadc833550717eade64e35ec8738e6b097d5d248274a01258b/greenlet-3.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e2e7e882f83149f0a71ac822ebf156d902e7a5d22c9045e3e0d1daf59cee2cc9", size = 1550729, upload-time = "2026-01-23T16:04:20.867Z" }, + { url = "https://files.pythonhosted.org/packages/16/c9/2fb47bee83b25b119d5a35d580807bb8b92480a54b68fef009a02945629f/greenlet-3.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80aa4d79eb5564f2e0a6144fcc744b5a37c56c4a92d60920720e99210d88db0f", size = 1615552, upload-time = "2026-01-23T15:33:45.743Z" }, + { url = "https://files.pythonhosted.org/packages/1f/54/dcf9f737b96606f82f8dd05becfb8d238db0633dd7397d542a296fe9cad3/greenlet-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:32e4ca9777c5addcbf42ff3915d99030d8e00173a56f80001fb3875998fe410b", size = 226462, upload-time = "2026-01-23T15:36:50.422Z" }, + { url = "https://files.pythonhosted.org/packages/91/37/61e1015cf944ddd2337447d8e97fb423ac9bc21f9963fb5f206b53d65649/greenlet-3.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:da19609432f353fed186cc1b85e9440db93d489f198b4bdf42ae19cc9d9ac9b4", size = 225715, upload-time = "2026-01-23T15:33:17.298Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cd/7a7ca57588dac3389e97f7c9521cb6641fd8b6602faf1eaa4188384757df/greenlet-3.3.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c620051669fd04ac6b60ebc70478210119c56e2d5d5df848baec4312e260e4ca", size = 622363, upload-time = "2026-01-23T16:15:54.754Z" }, + { url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" }, + { url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/2f/5e0e41f33c69655300a5e54aeb637cf8ff57f1786a3aba374eacc0228c1d/greenlet-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc98b9c4e4870fa983436afa999d4eb16b12872fab7071423d5262fa7120d57a", size = 227156, upload-time = "2026-01-23T15:34:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ab/717c58343cf02c5265b531384b248787e04d8160b8afe53d9eec053d7b44/greenlet-3.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bfb2d1763d777de5ee495c85309460f6fd8146e50ec9d0ae0183dbf6f0a829d1", size = 226403, upload-time = "2026-01-23T15:31:39.372Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, + { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, + { url = "https://files.pythonhosted.org/packages/06/00/95df0b6a935103c0452dad2203f5be8377e551b8466a29650c4c5a5af6cc/greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e", size = 624375, upload-time = "2026-01-23T16:15:55.915Z" }, + { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/c9c23a6478b3bcc91f979ce4ca50879e4d0b2bd7b9a53d8ecded719b92e2/greenlet-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946", size = 227042, upload-time = "2026-01-23T15:33:58.216Z" }, + { url = "https://files.pythonhosted.org/packages/90/e7/824beda656097edee36ab15809fd063447b200cc03a7f6a24c34d520bc88/greenlet-3.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d", size = 226294, upload-time = "2026-01-23T15:30:52.73Z" }, + { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, + { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/e2/89/b95f2ddcc5f3c2bc09c8ee8d77be312df7f9e7175703ab780f2014a0e781/greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", size = 671455, upload-time = "2026-01-23T16:15:57.232Z" }, + { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, + { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/52/cb/c21a3fd5d2c9c8b622e7bede6d6d00e00551a5ee474ea6d831b5f567a8b4/greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a", size = 228125, upload-time = "2026-01-23T15:32:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8e/8a2db6d11491837af1de64b8aff23707c6e85241be13c60ed399a72e2ef8/greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79", size = 227519, upload-time = "2026-01-23T15:31:47.284Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, + { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, + { url = "https://files.pythonhosted.org/packages/7c/25/c51a63f3f463171e09cb586eb64db0861eb06667ab01a7968371a24c4f3b/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", size = 662574, upload-time = "2026-01-23T16:15:58.364Z" }, + { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, + { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" }, ] [[package]] @@ -2084,13 +2087,13 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "1.3.0" +version = "1.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "filelock", version = "3.20.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "filelock", version = "3.20.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "fsspec", version = "2025.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "fsspec", version = "2025.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "fsspec", version = "2026.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, { name = "httpx" }, { name = "packaging" }, @@ -2100,9 +2103,9 @@ dependencies = [ { name = "typer-slim" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/c9/d42b5cfa0a50b77cf9165e13edfaf2e3bd4e0def9cb67b6b8a07224a52ab/huggingface_hub-1.3.0.tar.gz", hash = "sha256:289e2a3586fdf01e35882944eaa06fbd57436de24b6e653d1fab248584acd66b", size = 622092, upload-time = "2026-01-09T09:54:44.663Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/fc/eb9bc06130e8bbda6a616e1b80a7aa127681c448d6b49806f61db2670b61/huggingface_hub-1.4.1.tar.gz", hash = "sha256:b41131ec35e631e7383ab26d6146b8d8972abc8b6309b963b306fbcca87f5ed5", size = 642156, upload-time = "2026-02-06T09:20:03.013Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/5b/c5fde1f56b1f072b3028ec5413f3f5bf472c5891ebb34589cddb1689609f/huggingface_hub-1.3.0-py3-none-any.whl", hash = "sha256:763f450169bb05ea3867990e9d3ba9464eb617b874791301dc81be2c6ffb0bf5", size = 533092, upload-time = "2026-01-09T09:54:43.228Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ae/2f6d96b4e6c5478d87d606a1934b5d436c4a2bce6bb7c6fdece891c128e3/huggingface_hub-1.4.1-py3-none-any.whl", hash = "sha256:9931d075fb7a79af5abc487106414ec5fba2c0ae86104c0c62fd6cae38873d18", size = 553326, upload-time = "2026-02-06T09:20:00.728Z" }, ] [[package]] @@ -2205,120 +2208,120 @@ wheels = [ [[package]] name = "jiter" -version = "0.12.0" +version = "0.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/9d/e0660989c1370e25848bb4c52d061c71837239738ad937e83edca174c273/jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b", size = 168294, upload-time = "2025-11-09T20:49:23.302Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/91/13cb9505f7be74a933f37da3af22e029f6ba64f5669416cb8b2774bc9682/jiter-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e7acbaba9703d5de82a2c98ae6a0f59ab9770ab5af5fa35e43a303aee962cf65", size = 316652, upload-time = "2025-11-09T20:46:41.021Z" }, - { url = "https://files.pythonhosted.org/packages/4e/76/4e9185e5d9bb4e482cf6dec6410d5f78dfeb374cfcecbbe9888d07c52daa/jiter-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:364f1a7294c91281260364222f535bc427f56d4de1d8ffd718162d21fbbd602e", size = 319829, upload-time = "2025-11-09T20:46:43.281Z" }, - { url = "https://files.pythonhosted.org/packages/86/af/727de50995d3a153138139f259baae2379d8cb0522c0c00419957bc478a6/jiter-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ee4d25805d4fb23f0a5167a962ef8e002dbfb29c0989378488e32cf2744b62", size = 350568, upload-time = "2025-11-09T20:46:45.075Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c1/d6e9f4b7a3d5ac63bcbdfddeb50b2dcfbdc512c86cffc008584fdc350233/jiter-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:796f466b7942107eb889c08433b6e31b9a7ed31daceaecf8af1be26fb26c0ca8", size = 369052, upload-time = "2025-11-09T20:46:46.818Z" }, - { url = "https://files.pythonhosted.org/packages/eb/be/00824cd530f30ed73fa8a4f9f3890a705519e31ccb9e929f1e22062e7c76/jiter-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35506cb71f47dba416694e67af996bbdefb8e3608f1f78799c2e1f9058b01ceb", size = 481585, upload-time = "2025-11-09T20:46:48.319Z" }, - { url = "https://files.pythonhosted.org/packages/74/b6/2ad7990dff9504d4b5052eef64aa9574bd03d722dc7edced97aad0d47be7/jiter-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:726c764a90c9218ec9e4f99a33d6bf5ec169163f2ca0fc21b654e88c2abc0abc", size = 380541, upload-time = "2025-11-09T20:46:49.643Z" }, - { url = "https://files.pythonhosted.org/packages/b5/c7/f3c26ecbc1adbf1db0d6bba99192143d8fe8504729d9594542ecc4445784/jiter-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa47810c5565274810b726b0dc86d18dce5fd17b190ebdc3890851d7b2a0e74", size = 364423, upload-time = "2025-11-09T20:46:51.731Z" }, - { url = "https://files.pythonhosted.org/packages/18/51/eac547bf3a2d7f7e556927278e14c56a0604b8cddae75815d5739f65f81d/jiter-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ec0259d3f26c62aed4d73b198c53e316ae11f0f69c8fbe6682c6dcfa0fcce2", size = 389958, upload-time = "2025-11-09T20:46:53.432Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1f/9ca592e67175f2db156cff035e0d817d6004e293ee0c1d73692d38fcb596/jiter-0.12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:79307d74ea83465b0152fa23e5e297149506435535282f979f18b9033c0bb025", size = 522084, upload-time = "2025-11-09T20:46:54.848Z" }, - { url = "https://files.pythonhosted.org/packages/83/ff/597d9cdc3028f28224f53e1a9d063628e28b7a5601433e3196edda578cdd/jiter-0.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cf6e6dd18927121fec86739f1a8906944703941d000f0639f3eb6281cc601dca", size = 513054, upload-time = "2025-11-09T20:46:56.487Z" }, - { url = "https://files.pythonhosted.org/packages/24/6d/1970bce1351bd02e3afcc5f49e4f7ef3dabd7fb688f42be7e8091a5b809a/jiter-0.12.0-cp310-cp310-win32.whl", hash = "sha256:b6ae2aec8217327d872cbfb2c1694489057b9433afce447955763e6ab015b4c4", size = 206368, upload-time = "2025-11-09T20:46:58.638Z" }, - { url = "https://files.pythonhosted.org/packages/e3/6b/eb1eb505b2d86709b59ec06681a2b14a94d0941db091f044b9f0e16badc0/jiter-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:c7f49ce90a71e44f7e1aa9e7ec415b9686bbc6a5961e57eab511015e6759bc11", size = 204847, upload-time = "2025-11-09T20:47:00.295Z" }, - { url = "https://files.pythonhosted.org/packages/32/f9/eaca4633486b527ebe7e681c431f529b63fe2709e7c5242fc0f43f77ce63/jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9", size = 316435, upload-time = "2025-11-09T20:47:02.087Z" }, - { url = "https://files.pythonhosted.org/packages/10/c1/40c9f7c22f5e6ff715f28113ebaba27ab85f9af2660ad6e1dd6425d14c19/jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd", size = 320548, upload-time = "2025-11-09T20:47:03.409Z" }, - { url = "https://files.pythonhosted.org/packages/6b/1b/efbb68fe87e7711b00d2cfd1f26bb4bfc25a10539aefeaa7727329ffb9cb/jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423", size = 351915, upload-time = "2025-11-09T20:47:05.171Z" }, - { url = "https://files.pythonhosted.org/packages/15/2d/c06e659888c128ad1e838123d0638f0efad90cc30860cb5f74dd3f2fc0b3/jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7", size = 368966, upload-time = "2025-11-09T20:47:06.508Z" }, - { url = "https://files.pythonhosted.org/packages/6b/20/058db4ae5fb07cf6a4ab2e9b9294416f606d8e467fb74c2184b2a1eeacba/jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2", size = 482047, upload-time = "2025-11-09T20:47:08.382Z" }, - { url = "https://files.pythonhosted.org/packages/49/bb/dc2b1c122275e1de2eb12905015d61e8316b2f888bdaac34221c301495d6/jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9", size = 380835, upload-time = "2025-11-09T20:47:09.81Z" }, - { url = "https://files.pythonhosted.org/packages/23/7d/38f9cd337575349de16da575ee57ddb2d5a64d425c9367f5ef9e4612e32e/jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6", size = 364587, upload-time = "2025-11-09T20:47:11.529Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a3/b13e8e61e70f0bb06085099c4e2462647f53cc2ca97614f7fedcaa2bb9f3/jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725", size = 390492, upload-time = "2025-11-09T20:47:12.993Z" }, - { url = "https://files.pythonhosted.org/packages/07/71/e0d11422ed027e21422f7bc1883c61deba2d9752b720538430c1deadfbca/jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6", size = 522046, upload-time = "2025-11-09T20:47:14.6Z" }, - { url = "https://files.pythonhosted.org/packages/9f/59/b968a9aa7102a8375dbbdfbd2aeebe563c7e5dddf0f47c9ef1588a97e224/jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e", size = 513392, upload-time = "2025-11-09T20:47:16.011Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e4/7df62002499080dbd61b505c5cb351aa09e9959d176cac2aa8da6f93b13b/jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c", size = 206096, upload-time = "2025-11-09T20:47:17.344Z" }, - { url = "https://files.pythonhosted.org/packages/bb/60/1032b30ae0572196b0de0e87dce3b6c26a1eff71aad5fe43dee3082d32e0/jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f", size = 204899, upload-time = "2025-11-09T20:47:19.365Z" }, - { url = "https://files.pythonhosted.org/packages/49/d5/c145e526fccdb834063fb45c071df78b0cc426bbaf6de38b0781f45d956f/jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5", size = 188070, upload-time = "2025-11-09T20:47:20.75Z" }, - { url = "https://files.pythonhosted.org/packages/92/c9/5b9f7b4983f1b542c64e84165075335e8a236fa9e2ea03a0c79780062be8/jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37", size = 314449, upload-time = "2025-11-09T20:47:22.999Z" }, - { url = "https://files.pythonhosted.org/packages/98/6e/e8efa0e78de00db0aee82c0cf9e8b3f2027efd7f8a71f859d8f4be8e98ef/jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274", size = 319855, upload-time = "2025-11-09T20:47:24.779Z" }, - { url = "https://files.pythonhosted.org/packages/20/26/894cd88e60b5d58af53bec5c6759d1292bd0b37a8b5f60f07abf7a63ae5f/jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3", size = 350171, upload-time = "2025-11-09T20:47:26.469Z" }, - { url = "https://files.pythonhosted.org/packages/f5/27/a7b818b9979ac31b3763d25f3653ec3a954044d5e9f5d87f2f247d679fd1/jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf", size = 365590, upload-time = "2025-11-09T20:47:27.918Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7e/e46195801a97673a83746170b17984aa8ac4a455746354516d02ca5541b4/jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1", size = 479462, upload-time = "2025-11-09T20:47:29.654Z" }, - { url = "https://files.pythonhosted.org/packages/ca/75/f833bfb009ab4bd11b1c9406d333e3b4357709ed0570bb48c7c06d78c7dd/jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df", size = 378983, upload-time = "2025-11-09T20:47:31.026Z" }, - { url = "https://files.pythonhosted.org/packages/71/b3/7a69d77943cc837d30165643db753471aff5df39692d598da880a6e51c24/jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403", size = 361328, upload-time = "2025-11-09T20:47:33.286Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ac/a78f90caf48d65ba70d8c6efc6f23150bc39dc3389d65bbec2a95c7bc628/jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126", size = 386740, upload-time = "2025-11-09T20:47:34.703Z" }, - { url = "https://files.pythonhosted.org/packages/39/b6/5d31c2cc8e1b6a6bcf3c5721e4ca0a3633d1ab4754b09bc7084f6c4f5327/jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9", size = 520875, upload-time = "2025-11-09T20:47:36.058Z" }, - { url = "https://files.pythonhosted.org/packages/30/b5/4df540fae4e9f68c54b8dab004bd8c943a752f0b00efd6e7d64aa3850339/jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86", size = 511457, upload-time = "2025-11-09T20:47:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/07/65/86b74010e450a1a77b2c1aabb91d4a91dd3cd5afce99f34d75fd1ac64b19/jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44", size = 204546, upload-time = "2025-11-09T20:47:40.47Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c7/6659f537f9562d963488e3e55573498a442503ced01f7e169e96a6110383/jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb", size = 205196, upload-time = "2025-11-09T20:47:41.794Z" }, - { url = "https://files.pythonhosted.org/packages/21/f4/935304f5169edadfec7f9c01eacbce4c90bb9a82035ac1de1f3bd2d40be6/jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789", size = 186100, upload-time = "2025-11-09T20:47:43.007Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a6/97209693b177716e22576ee1161674d1d58029eb178e01866a0422b69224/jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e", size = 313658, upload-time = "2025-11-09T20:47:44.424Z" }, - { url = "https://files.pythonhosted.org/packages/06/4d/125c5c1537c7d8ee73ad3d530a442d6c619714b95027143f1b61c0b4dfe0/jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1", size = 318605, upload-time = "2025-11-09T20:47:45.973Z" }, - { url = "https://files.pythonhosted.org/packages/99/bf/a840b89847885064c41a5f52de6e312e91fa84a520848ee56c97e4fa0205/jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf", size = 349803, upload-time = "2025-11-09T20:47:47.535Z" }, - { url = "https://files.pythonhosted.org/packages/8a/88/e63441c28e0db50e305ae23e19c1d8fae012d78ed55365da392c1f34b09c/jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44", size = 365120, upload-time = "2025-11-09T20:47:49.284Z" }, - { url = "https://files.pythonhosted.org/packages/0a/7c/49b02714af4343970eb8aca63396bc1c82fa01197dbb1e9b0d274b550d4e/jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45", size = 479918, upload-time = "2025-11-09T20:47:50.807Z" }, - { url = "https://files.pythonhosted.org/packages/69/ba/0a809817fdd5a1db80490b9150645f3aae16afad166960bcd562be194f3b/jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87", size = 379008, upload-time = "2025-11-09T20:47:52.211Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c3/c9fc0232e736c8877d9e6d83d6eeb0ba4e90c6c073835cc2e8f73fdeef51/jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed", size = 361785, upload-time = "2025-11-09T20:47:53.512Z" }, - { url = "https://files.pythonhosted.org/packages/96/61/61f69b7e442e97ca6cd53086ddc1cf59fb830549bc72c0a293713a60c525/jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9", size = 386108, upload-time = "2025-11-09T20:47:54.893Z" }, - { url = "https://files.pythonhosted.org/packages/e9/2e/76bb3332f28550c8f1eba3bf6e5efe211efda0ddbbaf24976bc7078d42a5/jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626", size = 519937, upload-time = "2025-11-09T20:47:56.253Z" }, - { url = "https://files.pythonhosted.org/packages/84/d6/fa96efa87dc8bff2094fb947f51f66368fa56d8d4fc9e77b25d7fbb23375/jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c", size = 510853, upload-time = "2025-11-09T20:47:58.32Z" }, - { url = "https://files.pythonhosted.org/packages/8a/28/93f67fdb4d5904a708119a6ab58a8f1ec226ff10a94a282e0215402a8462/jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de", size = 204699, upload-time = "2025-11-09T20:47:59.686Z" }, - { url = "https://files.pythonhosted.org/packages/c4/1f/30b0eb087045a0abe2a5c9c0c0c8da110875a1d3be83afd4a9a4e548be3c/jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a", size = 204258, upload-time = "2025-11-09T20:48:01.01Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f4/2b4daf99b96bce6fc47971890b14b2a36aef88d7beb9f057fafa032c6141/jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60", size = 185503, upload-time = "2025-11-09T20:48:02.35Z" }, - { url = "https://files.pythonhosted.org/packages/39/ca/67bb15a7061d6fe20b9b2a2fd783e296a1e0f93468252c093481a2f00efa/jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6", size = 317965, upload-time = "2025-11-09T20:48:03.783Z" }, - { url = "https://files.pythonhosted.org/packages/18/af/1788031cd22e29c3b14bc6ca80b16a39a0b10e611367ffd480c06a259831/jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4", size = 345831, upload-time = "2025-11-09T20:48:05.55Z" }, - { url = "https://files.pythonhosted.org/packages/05/17/710bf8472d1dff0d3caf4ced6031060091c1320f84ee7d5dcbed1f352417/jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb", size = 361272, upload-time = "2025-11-09T20:48:06.951Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f1/1dcc4618b59761fef92d10bcbb0b038b5160be653b003651566a185f1a5c/jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7", size = 204604, upload-time = "2025-11-09T20:48:08.328Z" }, - { url = "https://files.pythonhosted.org/packages/d9/32/63cb1d9f1c5c6632a783c0052cde9ef7ba82688f7065e2f0d5f10a7e3edb/jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3", size = 185628, upload-time = "2025-11-09T20:48:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/a8/99/45c9f0dbe4a1416b2b9a8a6d1236459540f43d7fb8883cff769a8db0612d/jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525", size = 312478, upload-time = "2025-11-09T20:48:10.898Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a7/54ae75613ba9e0f55fcb0bc5d1f807823b5167cc944e9333ff322e9f07dd/jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49", size = 318706, upload-time = "2025-11-09T20:48:12.266Z" }, - { url = "https://files.pythonhosted.org/packages/59/31/2aa241ad2c10774baf6c37f8b8e1f39c07db358f1329f4eb40eba179c2a2/jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1", size = 351894, upload-time = "2025-11-09T20:48:13.673Z" }, - { url = "https://files.pythonhosted.org/packages/54/4f/0f2759522719133a9042781b18cc94e335b6d290f5e2d3e6899d6af933e3/jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e", size = 365714, upload-time = "2025-11-09T20:48:15.083Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6f/806b895f476582c62a2f52c453151edd8a0fde5411b0497baaa41018e878/jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e", size = 478989, upload-time = "2025-11-09T20:48:16.706Z" }, - { url = "https://files.pythonhosted.org/packages/86/6c/012d894dc6e1033acd8db2b8346add33e413ec1c7c002598915278a37f79/jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff", size = 378615, upload-time = "2025-11-09T20:48:18.614Z" }, - { url = "https://files.pythonhosted.org/packages/87/30/d718d599f6700163e28e2c71c0bbaf6dace692e7df2592fd793ac9276717/jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a", size = 364745, upload-time = "2025-11-09T20:48:20.117Z" }, - { url = "https://files.pythonhosted.org/packages/8f/85/315b45ce4b6ddc7d7fceca24068543b02bdc8782942f4ee49d652e2cc89f/jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a", size = 386502, upload-time = "2025-11-09T20:48:21.543Z" }, - { url = "https://files.pythonhosted.org/packages/74/0b/ce0434fb40c5b24b368fe81b17074d2840748b4952256bab451b72290a49/jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67", size = 519845, upload-time = "2025-11-09T20:48:22.964Z" }, - { url = "https://files.pythonhosted.org/packages/e8/a3/7a7a4488ba052767846b9c916d208b3ed114e3eb670ee984e4c565b9cf0d/jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b", size = 510701, upload-time = "2025-11-09T20:48:24.483Z" }, - { url = "https://files.pythonhosted.org/packages/c3/16/052ffbf9d0467b70af24e30f91e0579e13ded0c17bb4a8eb2aed3cb60131/jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42", size = 205029, upload-time = "2025-11-09T20:48:25.749Z" }, - { url = "https://files.pythonhosted.org/packages/e4/18/3cf1f3f0ccc789f76b9a754bdb7a6977e5d1d671ee97a9e14f7eb728d80e/jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf", size = 204960, upload-time = "2025-11-09T20:48:27.415Z" }, - { url = "https://files.pythonhosted.org/packages/02/68/736821e52ecfdeeb0f024b8ab01b5a229f6b9293bbdb444c27efade50b0f/jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451", size = 185529, upload-time = "2025-11-09T20:48:29.125Z" }, - { url = "https://files.pythonhosted.org/packages/30/61/12ed8ee7a643cce29ac97c2281f9ce3956eb76b037e88d290f4ed0d41480/jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7", size = 318974, upload-time = "2025-11-09T20:48:30.87Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c6/f3041ede6d0ed5e0e79ff0de4c8f14f401bbf196f2ef3971cdbe5fd08d1d/jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684", size = 345932, upload-time = "2025-11-09T20:48:32.658Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5d/4d94835889edd01ad0e2dbfc05f7bdfaed46292e7b504a6ac7839aa00edb/jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c", size = 367243, upload-time = "2025-11-09T20:48:34.093Z" }, - { url = "https://files.pythonhosted.org/packages/fd/76/0051b0ac2816253a99d27baf3dda198663aff882fa6ea7deeb94046da24e/jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d", size = 479315, upload-time = "2025-11-09T20:48:35.507Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/83f793acd68e5cb24e483f44f482a1a15601848b9b6f199dacb970098f77/jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993", size = 380714, upload-time = "2025-11-09T20:48:40.014Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/4808a88338ad2c228b1126b93fcd8ba145e919e886fe910d578230dabe3b/jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f", size = 365168, upload-time = "2025-11-09T20:48:41.462Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d4/04619a9e8095b42aef436b5aeb4c0282b4ff1b27d1db1508df9f5dc82750/jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783", size = 387893, upload-time = "2025-11-09T20:48:42.921Z" }, - { url = "https://files.pythonhosted.org/packages/17/ea/d3c7e62e4546fdc39197fa4a4315a563a89b95b6d54c0d25373842a59cbe/jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b", size = 520828, upload-time = "2025-11-09T20:48:44.278Z" }, - { url = "https://files.pythonhosted.org/packages/cc/0b/c6d3562a03fd767e31cb119d9041ea7958c3c80cb3d753eafb19b3b18349/jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6", size = 511009, upload-time = "2025-11-09T20:48:45.726Z" }, - { url = "https://files.pythonhosted.org/packages/aa/51/2cb4468b3448a8385ebcd15059d325c9ce67df4e2758d133ab9442b19834/jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183", size = 205110, upload-time = "2025-11-09T20:48:47.033Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c5/ae5ec83dec9c2d1af805fd5fe8f74ebded9c8670c5210ec7820ce0dbeb1e/jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873", size = 205223, upload-time = "2025-11-09T20:48:49.076Z" }, - { url = "https://files.pythonhosted.org/packages/97/9a/3c5391907277f0e55195550cf3fa8e293ae9ee0c00fb402fec1e38c0c82f/jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473", size = 185564, upload-time = "2025-11-09T20:48:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/7d/da/3e1fbd1f03f89ff0b4469d481be0b5cf2880c8e7b56fd80303b3ab5ae52d/jiter-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c9d28b218d5f9e5f69a0787a196322a5056540cb378cac8ff542b4fa7219966c", size = 319378, upload-time = "2025-11-09T20:48:51.761Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/e07d69285e9e19a153050a6d281d2f0968600753a8fed8a3a141d6ffc140/jiter-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0ee12028daf8cfcf880dd492349a122a64f42c059b6c62a2b0c96a83a8da820", size = 312195, upload-time = "2025-11-09T20:48:53.547Z" }, - { url = "https://files.pythonhosted.org/packages/2d/82/1f1cb5231b36af9f3d6d5b6030e70110faf14fd143419fc5fe7d852e691a/jiter-0.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b135ebe757a82d67ed2821526e72d0acf87dd61f6013e20d3c45b8048af927b", size = 352777, upload-time = "2025-11-09T20:48:55.058Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5e/728393bbbc99b31e8f7a4fdd8fa55e455a0a9648f79097d9088baf1f676f/jiter-0.12.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15d7fafb81af8a9e3039fc305529a61cd933eecee33b4251878a1c89859552a3", size = 370738, upload-time = "2025-11-09T20:48:56.632Z" }, - { url = "https://files.pythonhosted.org/packages/30/08/ac92f0df7b14ac82f2fe0a382a8000e600ab90af95798d4a7db0c1bd0736/jiter-0.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92d1f41211d8a8fe412faad962d424d334764c01dac6691c44691c2e4d3eedaf", size = 483744, upload-time = "2025-11-09T20:48:58.006Z" }, - { url = "https://files.pythonhosted.org/packages/7e/f4/dbfa4e759a2b82e969a14c3d0a91b176f1ed94717183a2f495cf94a651b9/jiter-0.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a64a48d7c917b8f32f25c176df8749ecf08cec17c466114727efe7441e17f6d", size = 382888, upload-time = "2025-11-09T20:48:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d9/b86fff7f748b0bb54222a8f132ffaf4d1be56b4591fa76d3cfdd701a33e5/jiter-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122046f3b3710b85de99d9aa2f3f0492a8233a2f54a64902b096efc27ea747b5", size = 366465, upload-time = "2025-11-09T20:49:01.408Z" }, - { url = "https://files.pythonhosted.org/packages/93/3c/1152d8b433317a568927e13c1b125c680e6c058ff5d304833be8469bd4f2/jiter-0.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:27ec39225e03c32c6b863ba879deb427882f243ae46f0d82d68b695fa5b48b40", size = 392603, upload-time = "2025-11-09T20:49:02.784Z" }, - { url = "https://files.pythonhosted.org/packages/6e/92/ff19d8fb87f3f9438eb7464862c8d0126455bc046b345d59b21443640c62/jiter-0.12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26b9e155ddc132225a39b1995b3b9f0fe0f79a6d5cbbeacf103271e7d309b404", size = 523780, upload-time = "2025-11-09T20:49:04.42Z" }, - { url = "https://files.pythonhosted.org/packages/87/3a/4260e2d84e4a293c36d2a8e8b8dcd69609c671f3bd310e4625359217c517/jiter-0.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab05b7c58e29bb9e60b70c2e0094c98df79a1e42e397b9bb6eaa989b7a66dd0", size = 514874, upload-time = "2025-11-09T20:49:05.844Z" }, - { url = "https://files.pythonhosted.org/packages/2e/f7/574d2cb79e86feb035ade18c2254da71d04417555907c9df51dd6b183426/jiter-0.12.0-cp39-cp39-win32.whl", hash = "sha256:59f9f9df87ed499136db1c2b6c9efb902f964bed42a582ab7af413b6a293e7b0", size = 208329, upload-time = "2025-11-09T20:49:07.444Z" }, - { url = "https://files.pythonhosted.org/packages/05/ce/50725ec39782d8c973f19ae2d7dd3d192d01332c7cbde48c75e16a3e85a9/jiter-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:d3719596a1ebe7a48a498e8d5d0c4bf7553321d4c3eee1d620628d51351a3928", size = 206557, upload-time = "2025-11-09T20:49:08.888Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/5339ef1ecaa881c6948669956567a64d2670941925f245c434f494ffb0e5/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8", size = 311144, upload-time = "2025-11-09T20:49:10.503Z" }, - { url = "https://files.pythonhosted.org/packages/27/74/3446c652bffbd5e81ab354e388b1b5fc1d20daac34ee0ed11ff096b1b01a/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3", size = 305877, upload-time = "2025-11-09T20:49:12.269Z" }, - { url = "https://files.pythonhosted.org/packages/a1/f4/ed76ef9043450f57aac2d4fbeb27175aa0eb9c38f833be6ef6379b3b9a86/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e", size = 340419, upload-time = "2025-11-09T20:49:13.803Z" }, - { url = "https://files.pythonhosted.org/packages/21/01/857d4608f5edb0664aa791a3d45702e1a5bcfff9934da74035e7b9803846/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d", size = 347212, upload-time = "2025-11-09T20:49:15.643Z" }, - { url = "https://files.pythonhosted.org/packages/cb/f5/12efb8ada5f5c9edc1d4555fe383c1fb2eac05ac5859258a72d61981d999/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb", size = 309974, upload-time = "2025-11-09T20:49:17.187Z" }, - { url = "https://files.pythonhosted.org/packages/85/15/d6eb3b770f6a0d332675141ab3962fd4a7c270ede3515d9f3583e1d28276/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b", size = 304233, upload-time = "2025-11-09T20:49:18.734Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3e/e7e06743294eea2cf02ced6aa0ff2ad237367394e37a0e2b4a1108c67a36/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f", size = 338537, upload-time = "2025-11-09T20:49:20.317Z" }, - { url = "https://files.pythonhosted.org/packages/2f/9c/6753e6522b8d0ef07d3a3d239426669e984fb0eba15a315cdbc1253904e4/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c", size = 346110, upload-time = "2025-11-09T20:49:21.817Z" }, + { url = "https://files.pythonhosted.org/packages/d0/5a/41da76c5ea07bec1b0472b6b2fdb1b651074d504b19374d7e130e0cdfb25/jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e", size = 311164, upload-time = "2026-02-02T12:35:17.688Z" }, + { url = "https://files.pythonhosted.org/packages/40/cb/4a1bf994a3e869f0d39d10e11efb471b76d0ad70ecbfb591427a46c880c2/jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a", size = 320296, upload-time = "2026-02-02T12:35:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/09/82/acd71ca9b50ecebadc3979c541cd717cce2fe2bc86236f4fa597565d8f1a/jiter-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19928b5d1ce0ff8c1ee1b9bdef3b5bfc19e8304f1b904e436caf30bc15dc6cf5", size = 352742, upload-time = "2026-02-02T12:35:21.258Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/d1fc996f3aecfd42eb70922edecfb6dd26421c874503e241153ad41df94f/jiter-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:309549b778b949d731a2f0e1594a3f805716be704a73bf3ad9a807eed5eb5721", size = 363145, upload-time = "2026-02-02T12:35:24.653Z" }, + { url = "https://files.pythonhosted.org/packages/f1/61/a30492366378cc7a93088858f8991acd7d959759fe6138c12a4644e58e81/jiter-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcdabaea26cb04e25df3103ce47f97466627999260290349a88c8136ecae0060", size = 487683, upload-time = "2026-02-02T12:35:26.162Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/4223cffa9dbbbc96ed821c5aeb6bca510848c72c02086d1ed3f1da3d58a7/jiter-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a377af27b236abbf665a69b2bdd680e3b5a0bd2af825cd3b81245279a7606c", size = 373579, upload-time = "2026-02-02T12:35:27.582Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c9/b0489a01329ab07a83812d9ebcffe7820a38163c6d9e7da644f926ff877c/jiter-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe49d3ff6db74321f144dff9addd4a5874d3105ac5ba7c5b77fac099cfae31ae", size = 362904, upload-time = "2026-02-02T12:35:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/05/af/53e561352a44afcba9a9bc67ee1d320b05a370aed8df54eafe714c4e454d/jiter-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2113c17c9a67071b0f820733c0893ed1d467b5fcf4414068169e5c2cabddb1e2", size = 392380, upload-time = "2026-02-02T12:35:30.385Z" }, + { url = "https://files.pythonhosted.org/packages/76/2a/dd805c3afb8ed5b326c5ae49e725d1b1255b9754b1b77dbecdc621b20773/jiter-0.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab1185ca5c8b9491b55ebf6c1e8866b8f68258612899693e24a92c5fdb9455d5", size = 517939, upload-time = "2026-02-02T12:35:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/20/2a/7b67d76f55b8fe14c937e7640389612f05f9a4145fc28ae128aaa5e62257/jiter-0.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9621ca242547edc16400981ca3231e0c91c0c4c1ab8573a596cd9bb3575d5c2b", size = 551696, upload-time = "2026-02-02T12:35:33.306Z" }, + { url = "https://files.pythonhosted.org/packages/85/9c/57cdd64dac8f4c6ab8f994fe0eb04dc9fd1db102856a4458fcf8a99dfa62/jiter-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a7637d92b1c9d7a771e8c56f445c7f84396d48f2e756e5978840ecba2fac0894", size = 204592, upload-time = "2026-02-02T12:35:34.58Z" }, + { url = "https://files.pythonhosted.org/packages/a7/38/f4f3ea5788b8a5bae7510a678cdc747eda0c45ffe534f9878ff37e7cf3b3/jiter-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1b609e5cbd2f52bb74fb721515745b407df26d7b800458bd97cb3b972c29e7d", size = 206016, upload-time = "2026-02-02T12:35:36.435Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/499f8c9eaa8a16751b1c0e45e6f5f1761d180da873d417996cc7bddc8eef/jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096", size = 311157, upload-time = "2026-02-02T12:35:37.758Z" }, + { url = "https://files.pythonhosted.org/packages/50/f6/566364c777d2ab450b92100bea11333c64c38d32caf8dc378b48e5b20c46/jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911", size = 319729, upload-time = "2026-02-02T12:35:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/560f13ec5e4f116d8ad2658781646cca91b617ae3b8758d4a5076b278f70/jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701", size = 354766, upload-time = "2026-02-02T12:35:40.662Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0d/061faffcfe94608cbc28a0d42a77a74222bdf5055ccdbe5fd2292b94f510/jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c", size = 362587, upload-time = "2026-02-02T12:35:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/c66a7864982fd38a9773ec6e932e0398d1262677b8c60faecd02ffb67bf3/jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4", size = 487537, upload-time = "2026-02-02T12:35:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/6c/86/84eb4352cd3668f16d1a88929b5888a3fe0418ea8c1dfc2ad4e7bf6e069a/jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165", size = 373717, upload-time = "2026-02-02T12:35:44.928Z" }, + { url = "https://files.pythonhosted.org/packages/6e/09/9fe4c159358176f82d4390407a03f506a8659ed13ca3ac93a843402acecf/jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018", size = 362683, upload-time = "2026-02-02T12:35:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5e/85f3ab9caca0c1d0897937d378b4a515cae9e119730563572361ea0c48ae/jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411", size = 392345, upload-time = "2026-02-02T12:35:48.088Z" }, + { url = "https://files.pythonhosted.org/packages/12/4c/05b8629ad546191939e6f0c2f17e29f542a398f4a52fb987bc70b6d1eb8b/jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5", size = 517775, upload-time = "2026-02-02T12:35:49.482Z" }, + { url = "https://files.pythonhosted.org/packages/4d/88/367ea2eb6bc582c7052e4baf5ddf57ebe5ab924a88e0e09830dfb585c02d/jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3", size = 551325, upload-time = "2026-02-02T12:35:51.104Z" }, + { url = "https://files.pythonhosted.org/packages/f3/12/fa377ffb94a2f28c41afaed093e0d70cfe512035d5ecb0cad0ae4792d35e/jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1", size = 204709, upload-time = "2026-02-02T12:35:52.467Z" }, + { url = "https://files.pythonhosted.org/packages/cb/16/8e8203ce92f844dfcd3d9d6a5a7322c77077248dbb12da52d23193a839cd/jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654", size = 204560, upload-time = "2026-02-02T12:35:53.925Z" }, + { url = "https://files.pythonhosted.org/packages/44/26/97cc40663deb17b9e13c3a5cf29251788c271b18ee4d262c8f94798b8336/jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5", size = 189608, upload-time = "2026-02-02T12:35:55.304Z" }, + { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, + { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, + { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, + { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, + { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, + { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, + { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, + { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, + { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, + { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, + { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, + { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, + { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, + { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, + { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, + { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, + { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, + { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, + { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, + { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, + { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, + { url = "https://files.pythonhosted.org/packages/41/95/8e6611379c9ce8534ff94dd800c50d6d0061b2c9ae6386fbcd86c7386f0a/jiter-0.13.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:4397ee562b9f69d283e5674445551b47a5e8076fdde75e71bfac5891113dc543", size = 313635, upload-time = "2026-02-02T12:37:23.545Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/17db64dcaf84bbb187874232222030ea4d689e6008f93bda6e7c691bc4c7/jiter-0.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f90023f8f672e13ea1819507d2d21b9d2d1c18920a3b3a5f1541955a85b5504", size = 309761, upload-time = "2026-02-02T12:37:25.075Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/b2e2a7b12b94ecc7248acf2a8fe6288be893d1ebb9728655ceada22f00ad/jiter-0.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed0240dd1536a98c3ab55e929c60dfff7c899fecafcb7d01161b21a99fc8c363", size = 355245, upload-time = "2026-02-02T12:37:26.646Z" }, + { url = "https://files.pythonhosted.org/packages/77/3f/5b159663c5be622daec20074c997bb66bc1fac63c167c02aef3df476fb32/jiter-0.13.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6207fc61c395b26fffdcf637a0b06b4326f35bfa93c6e92fe1a166a21aeb6731", size = 365842, upload-time = "2026-02-02T12:37:28.207Z" }, + { url = "https://files.pythonhosted.org/packages/98/30/76a68fa2c9c815c6b7802a92fc354080d66ffba9acc4690fd85622f77ad4/jiter-0.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00203f47c214156df427b5989de74cb340c65c8180d09be1bf9de81d0abad599", size = 489223, upload-time = "2026-02-02T12:37:29.571Z" }, + { url = "https://files.pythonhosted.org/packages/a3/39/7c5cb85ccd71241513c878054c26a55828ccded6567d931a23ea4be73787/jiter-0.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c26ad6967c9dcedf10c995a21539c3aa57d4abad7001b7a84f621a263a6b605", size = 375762, upload-time = "2026-02-02T12:37:31.186Z" }, + { url = "https://files.pythonhosted.org/packages/a8/6a/381cd18d050b0102e60324e8d3f51f37ef02c56e9f4e5f0b7d26ba18958d/jiter-0.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a576f5dce9ac7de5d350b8e2f552cf364f32975ed84717c35379a51c7cb198bd", size = 364996, upload-time = "2026-02-02T12:37:32.931Z" }, + { url = "https://files.pythonhosted.org/packages/37/1e/d66310f1f7085c13ea6f1119c9566ec5d2cfd1dc90df963118a6869247bb/jiter-0.13.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b22945be8425d161f2e536cdae66da300b6b000f1c0ba3ddf237d1bfd45d21b8", size = 395463, upload-time = "2026-02-02T12:37:34.446Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ab/06ae77cb293f860b152c356c635c15aaa800ce48772865a41704d9fac80d/jiter-0.13.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6eeb7db8bc77dc20476bc2f7407a23dbe3d46d9cc664b166e3d474e1c1de4baa", size = 520944, upload-time = "2026-02-02T12:37:35.987Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8e/57b49b20361c42a80d455a6d83cb38626204508cab4298d6a22880205319/jiter-0.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:19cd6f85e1dc090277c3ce90a5b7d96f32127681d825e71c9dce28788e39fc0c", size = 554955, upload-time = "2026-02-02T12:37:37.656Z" }, + { url = "https://files.pythonhosted.org/packages/79/dd/113489973c3b4256e383321aea11bd57389e401912fa48eb145a99b38767/jiter-0.13.0-cp39-cp39-win32.whl", hash = "sha256:dc3ce84cfd4fa9628fe62c4f85d0d597a4627d4242cfafac32a12cc1455d00f7", size = 206876, upload-time = "2026-02-02T12:37:39.225Z" }, + { url = "https://files.pythonhosted.org/packages/6e/73/2bdfc7133c5ee0c8f18cfe4a7582f3cfbbf3ff672cec1b5f4ca67ff9d041/jiter-0.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:9ffda299e417dc83362963966c50cb76d42da673ee140de8a8ac762d4bb2378b", size = 206404, upload-time = "2026-02-02T12:37:40.632Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" }, + { url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" }, + { url = "https://files.pythonhosted.org/packages/2d/92/015173281f7eb96c0ef580c997da8ef50870d4f7f4c9e03c845a1d62ae04/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d", size = 346395, upload-time = "2026-02-02T12:37:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, ] [[package]] name = "jmespath" -version = "1.0.1" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, ] [[package]] @@ -2362,11 +2365,11 @@ wheels = [ [[package]] name = "logfire-api" -version = "4.17.0" +version = "4.22.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/2a/76d8fbafa881cb03d5ad6e1d67d537e8c308ae7145812b8891f7b8751224/logfire_api-4.17.0.tar.gz", hash = "sha256:4647dad05146a68af441d59a7746a966df4c2581b316616f1210f8cf74931353", size = 58305, upload-time = "2026-01-07T10:52:17.768Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/c2/38871722347486f59f48dbe407541ff1e02398637b800778a2602f2353a7/logfire_api-4.22.0.tar.gz", hash = "sha256:b22a038e76c58cba1ab2ce7ce1a26aa5f3df8c63e84d6ad01fb22f75273ca830", size = 59723, upload-time = "2026-02-04T12:17:59.07Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/bc/3844e103dca998dcc195d6ef09e0f29d9000bac870117db1dd59a29bfeef/logfire_api-4.17.0-py3-none-any.whl", hash = "sha256:80a4b79cd9918934cdf2043d944cfb04182708178d846273484d47f3619a5a39", size = 96146, upload-time = "2026-01-07T10:52:15.088Z" }, + { url = "https://files.pythonhosted.org/packages/1e/93/4e8395f3e4b1951b1de8d06cb7d7ef1105efd294ea3443bf39b3594390af/logfire_api-4.22.0-py3-none-any.whl", hash = "sha256:9fd2a2dac9cc3adf71ad4dac7bc9d26af326a5aa87445be90026048d22b92a8e", size = 98543, upload-time = "2026-02-04T12:17:56.365Z" }, ] [[package]] @@ -2386,15 +2389,15 @@ wheels = [ [[package]] name = "markdown" -version = "3.10" +version = "3.10.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version >= '3.10' and python_full_version < '3.14'", ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, ] [[package]] @@ -2403,7 +2406,7 @@ version = "0.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/47/ec9eae4a6d2f336d95681df43720e2b25b045dc3ed44ae6d30a5ce2f5dff/markdown_include_variants-0.0.8.tar.gz", hash = "sha256:46d812340c64dcd3646b1eaa356bafb31626dd7b4955d15c44ff8c48c6357227", size = 46882, upload-time = "2025-12-12T16:11:04.254Z" } wheels = [ @@ -2548,7 +2551,7 @@ dependencies = [ { name = "jsonschema", marker = "python_full_version >= '3.10'" }, { name = "pydantic", marker = "python_full_version >= '3.10'" }, { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "python-multipart", version = "0.0.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-multipart", version = "0.0.22", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pywin32", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, { name = "sse-starlette", marker = "python_full_version >= '3.10'" }, { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, @@ -2575,7 +2578,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cyclic" }, { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "rcslice" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bf/f0/f395a9cf164471d3c7bbe58cbd64d74289575a8b85a962b49a804ab7ed34/mdx_include-1.4.2.tar.gz", hash = "sha256:992f9fbc492b5cf43f7d8cb4b90b52a4e4c5fdd7fd04570290a83eea5c84f297", size = 15051, upload-time = "2022-07-26T05:46:14.129Z" } @@ -2622,7 +2625,7 @@ dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "jinja2" }, { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "markupsafe" }, { name = "mergedeep" }, { name = "mkdocs-get-deps" }, @@ -2643,7 +2646,7 @@ version = "1.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "markupsafe" }, { name = "mkdocs" }, ] @@ -2700,7 +2703,7 @@ dependencies = [ { name = "colorama" }, { name = "jinja2" }, { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "mkdocs" }, { name = "mkdocs-material-extensions" }, { name = "paginate" }, @@ -2742,7 +2745,7 @@ dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "jinja2" }, { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "markupsafe" }, { name = "mkdocs" }, { name = "mkdocs-autorefs" }, @@ -2851,7 +2854,7 @@ wheels = [ [[package]] name = "openai" -version = "2.14.0" +version = "2.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2863,9 +2866,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/b1/12fe1c196bea326261718eb037307c1c1fe1dedc2d2d4de777df822e6238/openai-2.14.0.tar.gz", hash = "sha256:419357bedde9402d23bf8f2ee372fca1985a73348debba94bddff06f19459952", size = 626938, upload-time = "2025-12-19T03:28:45.742Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/a2/677f22c4b487effb8a09439fb6134034b5f0a39ca27df8b95fac23a93720/openai-2.17.0.tar.gz", hash = "sha256:47224b74bd20f30c6b0a6a329505243cb2f26d5cf84d9f8d0825ff8b35e9c999", size = 631445, upload-time = "2026-02-05T16:27:40.953Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/4b/7c1a00c2c3fbd004253937f7520f692a9650767aa73894d7a34f0d65d3f4/openai-2.14.0-py3-none-any.whl", hash = "sha256:7ea40aca4ffc4c4a776e77679021b47eec1160e341f42ae086ba949c9dcc9183", size = 1067558, upload-time = "2025-12-19T03:28:43.727Z" }, + { url = "https://files.pythonhosted.org/packages/44/97/284535aa75e6e84ab388248b5a323fc296b1f70530130dee37f7f4fbe856/openai-2.17.0-py3-none-any.whl", hash = "sha256:4f393fd886ca35e113aac7ff239bcd578b81d8f104f5aedc7d3693eb2af1d338", size = 1069524, upload-time = "2026-02-05T16:27:38.941Z" }, ] [[package]] @@ -2885,6 +2888,9 @@ wheels = [ name = "orjson" version = "3.11.5" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] sdist = { url = "https://files.pythonhosted.org/packages/04/b8/333fdb27840f3bf04022d21b654a35f58e15407183aeb16f3b41aa053446/orjson-3.11.5.tar.gz", hash = "sha256:82393ab47b4fe44ffd0a7659fa9cfaacc717eb617c93cde83795f14af5c2e9d5", size = 5972347, upload-time = "2025-12-06T15:55:39.458Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/79/19/b22cf9dad4db20c8737041046054cbd4f38bb5a2d0e4bb60487832ce3d76/orjson-3.11.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:df9eadb2a6386d5ea2bfd81309c505e125cfc9ba2b1b99a97e60985b0b3665d1", size = 245719, upload-time = "2025-12-06T15:53:43.877Z" }, @@ -2975,6 +2981,91 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/e6/babf31154e047e465bc194eb72d1326d7c52ad4d7f50bf92b02b3cacda5c/orjson-3.11.5-cp39-cp39-win_amd64.whl", hash = "sha256:09b94b947ac08586af635ef922d69dc9bc63321527a3a04647f4986a73f4bd30", size = 133189, upload-time = "2025-12-06T15:55:38.143Z" }, ] +[[package]] +name = "orjson" +version = "3.11.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1a/a373746fa6d0e116dd9e54371a7b54622c44d12296d5d0f3ad5e3ff33490/orjson-3.11.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a02c833f38f36546ba65a452127633afce4cf0dd7296b753d3bb54e55e5c0174", size = 229140, upload-time = "2026-02-02T15:37:06.082Z" }, + { url = "https://files.pythonhosted.org/packages/52/a2/fa129e749d500f9b183e8a3446a193818a25f60261e9ce143ad61e975208/orjson-3.11.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63c6e6738d7c3470ad01601e23376aa511e50e1f3931395b9f9c722406d1a67", size = 128670, upload-time = "2026-02-02T15:37:08.002Z" }, + { url = "https://files.pythonhosted.org/packages/08/93/1e82011cd1e0bd051ef9d35bed1aa7fb4ea1f0a055dc2c841b46b43a9ebd/orjson-3.11.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:043d3006b7d32c7e233b8cfb1f01c651013ea079e08dcef7189a29abd8befe11", size = 123832, upload-time = "2026-02-02T15:37:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d8/a26b431ef962c7d55736674dddade876822f3e33223c1f47a36879350d04/orjson-3.11.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57036b27ac8a25d81112eb0cc9835cd4833c5b16e1467816adc0015f59e870dc", size = 129171, upload-time = "2026-02-02T15:37:11.112Z" }, + { url = "https://files.pythonhosted.org/packages/a7/19/f47819b84a580f490da260c3ee9ade214cf4cf78ac9ce8c1c758f80fdfc9/orjson-3.11.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:733ae23ada68b804b222c44affed76b39e30806d38660bf1eb200520d259cc16", size = 141967, upload-time = "2026-02-02T15:37:12.282Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cd/37ece39a0777ba077fdcdbe4cccae3be8ed00290c14bf8afdc548befc260/orjson-3.11.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5fdfad2093bdd08245f2e204d977facd5f871c88c4a71230d5bcbd0e43bf6222", size = 130991, upload-time = "2026-02-02T15:37:13.465Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ed/f2b5d66aa9b6b5c02ff5f120efc7b38c7c4962b21e6be0f00fd99a5c348e/orjson-3.11.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cededd6738e1c153530793998e31c05086582b08315db48ab66649768f326baa", size = 133674, upload-time = "2026-02-02T15:37:14.694Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6e/baa83e68d1aa09fa8c3e5b2c087d01d0a0bd45256de719ed7bc22c07052d/orjson-3.11.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:14f440c7268c8f8633d1b3d443a434bd70cb15686117ea6beff8fdc8f5917a1e", size = 138722, upload-time = "2026-02-02T15:37:16.501Z" }, + { url = "https://files.pythonhosted.org/packages/0c/47/7f8ef4963b772cd56999b535e553f7eb5cd27e9dd6c049baee6f18bfa05d/orjson-3.11.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3a2479753bbb95b0ebcf7969f562cdb9668e6d12416a35b0dda79febf89cdea2", size = 409056, upload-time = "2026-02-02T15:37:17.895Z" }, + { url = "https://files.pythonhosted.org/packages/38/eb/2df104dd2244b3618f25325a656f85cc3277f74bbd91224752410a78f3c7/orjson-3.11.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:71924496986275a737f38e3f22b4e0878882b3f7a310d2ff4dc96e812789120c", size = 144196, upload-time = "2026-02-02T15:37:19.349Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2a/ee41de0aa3a6686598661eae2b4ebdff1340c65bfb17fcff8b87138aab21/orjson-3.11.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4a9eefdc70bf8bf9857f0290f973dec534ac84c35cd6a7f4083be43e7170a8f", size = 134979, upload-time = "2026-02-02T15:37:20.906Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fa/92fc5d3d402b87a8b28277a9ed35386218a6a5287c7fe5ee9b9f02c53fb2/orjson-3.11.7-cp310-cp310-win32.whl", hash = "sha256:ae9e0b37a834cef7ce8f99de6498f8fad4a2c0bf6bfc3d02abd8ed56aa15b2de", size = 127968, upload-time = "2026-02-02T15:37:23.178Z" }, + { url = "https://files.pythonhosted.org/packages/07/29/a576bf36d73d60df06904d3844a9df08e25d59eba64363aaf8ec2f9bff41/orjson-3.11.7-cp310-cp310-win_amd64.whl", hash = "sha256:d772afdb22555f0c58cfc741bdae44180122b3616faa1ecadb595cd526e4c993", size = 125128, upload-time = "2026-02-02T15:37:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/37/02/da6cb01fc6087048d7f61522c327edf4250f1683a58a839fdcc435746dd5/orjson-3.11.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9487abc2c2086e7c8eb9a211d2ce8855bae0e92586279d0d27b341d5ad76c85c", size = 228664, upload-time = "2026-02-02T15:37:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c2/5885e7a5881dba9a9af51bc564e8967225a642b3e03d089289a35054e749/orjson-3.11.7-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:79cacb0b52f6004caf92405a7e1f11e6e2de8bdf9019e4f76b44ba045125cd6b", size = 125344, upload-time = "2026-02-02T15:37:26.92Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1d/4e7688de0a92d1caf600dfd5fb70b4c5bfff51dfa61ac555072ef2d0d32a/orjson-3.11.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e85fe4698b6a56d5e2ebf7ae87544d668eb6bde1ad1226c13f44663f20ec9e", size = 128404, upload-time = "2026-02-02T15:37:28.108Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b2/ec04b74ae03a125db7bd69cffd014b227b7f341e3261bf75b5eb88a1aa92/orjson-3.11.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b8d14b71c0b12963fe8a62aac87119f1afdf4cb88a400f61ca5ae581449efcb5", size = 123677, upload-time = "2026-02-02T15:37:30.287Z" }, + { url = "https://files.pythonhosted.org/packages/4c/69/f95bdf960605f08f827f6e3291fe243d8aa9c5c9ff017a8d7232209184c3/orjson-3.11.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91c81ef070c8f3220054115e1ef468b1c9ce8497b4e526cb9f68ab4dc0a7ac62", size = 128950, upload-time = "2026-02-02T15:37:31.595Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1b/de59c57bae1d148ef298852abd31909ac3089cff370dfd4cd84cc99cbc42/orjson-3.11.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:411ebaf34d735e25e358a6d9e7978954a9c9d58cfb47bc6683cdc3964cd2f910", size = 141756, upload-time = "2026-02-02T15:37:32.985Z" }, + { url = "https://files.pythonhosted.org/packages/ee/9e/9decc59f4499f695f65c650f6cfa6cd4c37a3fbe8fa235a0a3614cb54386/orjson-3.11.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a16bcd08ab0bcdfc7e8801d9c4a9cc17e58418e4d48ddc6ded4e9e4b1a94062b", size = 130812, upload-time = "2026-02-02T15:37:34.204Z" }, + { url = "https://files.pythonhosted.org/packages/28/e6/59f932bcabd1eac44e334fe8e3281a92eacfcb450586e1f4bde0423728d8/orjson-3.11.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c0b51672e466fd7e56230ffbae7f1639e18d0ce023351fb75da21b71bc2c960", size = 133444, upload-time = "2026-02-02T15:37:35.446Z" }, + { url = "https://files.pythonhosted.org/packages/f1/36/b0f05c0eaa7ca30bc965e37e6a2956b0d67adb87a9872942d3568da846ae/orjson-3.11.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:136dcd6a2e796dfd9ffca9fc027d778567b0b7c9968d092842d3c323cef88aa8", size = 138609, upload-time = "2026-02-02T15:37:36.657Z" }, + { url = "https://files.pythonhosted.org/packages/b8/03/58ec7d302b8d86944c60c7b4b82975d5161fcce4c9bc8c6cb1d6741b6115/orjson-3.11.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7ba61079379b0ae29e117db13bda5f28d939766e410d321ec1624afc6a0b0504", size = 408918, upload-time = "2026-02-02T15:37:38.076Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/868d65ef9a8b99be723bd510de491349618abd9f62c826cf206d962db295/orjson-3.11.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0527a4510c300e3b406591b0ba69b5dc50031895b0a93743526a3fc45f59d26e", size = 143998, upload-time = "2026-02-02T15:37:39.706Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c7/1e18e1c83afe3349f4f6dc9e14910f0ae5f82eac756d1412ea4018938535/orjson-3.11.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a709e881723c9b18acddcfb8ba357322491ad553e277cf467e1e7e20e2d90561", size = 134802, upload-time = "2026-02-02T15:37:41.002Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0b/ccb7ee1a65b37e8eeb8b267dc953561d72370e85185e459616d4345bab34/orjson-3.11.7-cp311-cp311-win32.whl", hash = "sha256:c43b8b5bab288b6b90dac410cca7e986a4fa747a2e8f94615aea407da706980d", size = 127828, upload-time = "2026-02-02T15:37:42.241Z" }, + { url = "https://files.pythonhosted.org/packages/af/9e/55c776dffda3f381e0f07d010a4f5f3902bf48eaba1bb7684d301acd4924/orjson-3.11.7-cp311-cp311-win_amd64.whl", hash = "sha256:6543001328aa857187f905308a028935864aefe9968af3848401b6fe80dbb471", size = 124941, upload-time = "2026-02-02T15:37:43.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8e/424a620fa7d263b880162505fb107ef5e0afaa765b5b06a88312ac291560/orjson-3.11.7-cp311-cp311-win_arm64.whl", hash = "sha256:1ee5cc7160a821dfe14f130bc8e63e7611051f964b463d9e2a3a573204446a4d", size = 126245, upload-time = "2026-02-02T15:37:45.18Z" }, + { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, + { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, + { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, + { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, + { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, + { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, + { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390, upload-time = "2026-02-02T15:38:06.8Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189, upload-time = "2026-02-02T15:38:08.181Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106, upload-time = "2026-02-02T15:38:09.41Z" }, + { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363, upload-time = "2026-02-02T15:38:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007, upload-time = "2026-02-02T15:38:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667, upload-time = "2026-02-02T15:38:13.398Z" }, + { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832, upload-time = "2026-02-02T15:38:14.698Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373, upload-time = "2026-02-02T15:38:16.109Z" }, + { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307, upload-time = "2026-02-02T15:38:17.453Z" }, + { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695, upload-time = "2026-02-02T15:38:18.831Z" }, + { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099, upload-time = "2026-02-02T15:38:20.28Z" }, + { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806, upload-time = "2026-02-02T15:38:22.186Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914, upload-time = "2026-02-02T15:38:23.511Z" }, + { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986, upload-time = "2026-02-02T15:38:24.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045, upload-time = "2026-02-02T15:38:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1e/745565dca749813db9a093c5ebc4bac1a9475c64d54b95654336ac3ed961/orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0", size = 228391, upload-time = "2026-02-02T15:38:27.757Z" }, + { url = "https://files.pythonhosted.org/packages/46/19/e40f6225da4d3aa0c8dc6e5219c5e87c2063a560fe0d72a88deb59776794/orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0", size = 125188, upload-time = "2026-02-02T15:38:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7e/c4de2babef2c0817fd1f048fd176aa48c37bec8aef53d2fa932983032cce/orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6", size = 128097, upload-time = "2026-02-02T15:38:30.618Z" }, + { url = "https://files.pythonhosted.org/packages/eb/74/233d360632bafd2197f217eee7fb9c9d0229eac0c18128aee5b35b0014fe/orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf", size = 123364, upload-time = "2026-02-02T15:38:32.363Z" }, + { url = "https://files.pythonhosted.org/packages/79/51/af79504981dd31efe20a9e360eb49c15f06df2b40e7f25a0a52d9ae888e8/orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5", size = 129076, upload-time = "2026-02-02T15:38:33.68Z" }, + { url = "https://files.pythonhosted.org/packages/67/e2/da898eb68b72304f8de05ca6715870d09d603ee98d30a27e8a9629abc64b/orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892", size = 141705, upload-time = "2026-02-02T15:38:34.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/89/15364d92acb3d903b029e28d834edb8780c2b97404cbf7929aa6b9abdb24/orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e", size = 130855, upload-time = "2026-02-02T15:38:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8b/ecdad52d0b38d4b8f514be603e69ccd5eacf4e7241f972e37e79792212ec/orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1", size = 133386, upload-time = "2026-02-02T15:38:37.704Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0e/45e1dcf10e17d0924b7c9162f87ec7b4ca79e28a0548acf6a71788d3e108/orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183", size = 138295, upload-time = "2026-02-02T15:38:39.096Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/4d2e8b03561257af0450f2845b91fbd111d7e526ccdf737267108075e0ba/orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650", size = 408720, upload-time = "2026-02-02T15:38:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/78/cf/d45343518282108b29c12a65892445fc51f9319dc3c552ceb51bb5905ed2/orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141", size = 144152, upload-time = "2026-02-02T15:38:42.262Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3a/d6001f51a7275aacd342e77b735c71fa04125a3f93c36fee4526bc8c654e/orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2", size = 134814, upload-time = "2026-02-02T15:38:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997, upload-time = "2026-02-02T15:38:45.06Z" }, + { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985, upload-time = "2026-02-02T15:38:46.388Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038, upload-time = "2026-02-02T15:38:47.703Z" }, +] + [[package]] name = "outcome" version = "1.3.0.post0" @@ -2989,11 +3080,11 @@ wheels = [ [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -3007,11 +3098,11 @@ wheels = [ [[package]] name = "pathspec" -version = "1.0.2" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/b9/6eb731b52f132181a9144bbe77ff82117f6b2d2fbfba49aaab2c014c4760/pathspec-1.0.2.tar.gz", hash = "sha256:fa32b1eb775ed9ba8d599b22c5f906dc098113989da2c00bf8b210078ca7fb92", size = 130502, upload-time = "2026-01-08T04:33:27.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/6b/14fc9049d78435fd29e82846c777bd7ed9c470013dc8d0260fff3ff1c11e/pathspec-1.0.2-py3-none-any.whl", hash = "sha256:62f8558917908d237d399b9b338ef455a814801a4688bc41074b25feefd93472", size = 54844, upload-time = "2026-01-08T04:33:26.4Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] @@ -3154,22 +3245,22 @@ wheels = [ [[package]] name = "playwright" -version = "1.57.0" +version = "1.58.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", version = "3.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "greenlet", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "greenlet", version = "3.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pyee" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/b6/e17543cea8290ae4dced10be21d5a43c360096aa2cce0aa7039e60c50df3/playwright-1.57.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:9351c1ac3dfd9b3820fe7fc4340d96c0d3736bb68097b9b7a69bd45d25e9370c", size = 41985039, upload-time = "2025-12-09T08:06:18.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/04/ef95b67e1ff59c080b2effd1a9a96984d6953f667c91dfe9d77c838fc956/playwright-1.57.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4a9d65027bce48eeba842408bcc1421502dfd7e41e28d207e94260fa93ca67e", size = 40775575, upload-time = "2025-12-09T08:06:22.105Z" }, - { url = "https://files.pythonhosted.org/packages/60/bd/5563850322a663956c927eefcf1457d12917e8f118c214410e815f2147d1/playwright-1.57.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:99104771abc4eafee48f47dac2369e0015516dc1ce8c409807d2dd440828b9a4", size = 41985042, upload-time = "2025-12-09T08:06:25.357Z" }, - { url = "https://files.pythonhosted.org/packages/56/61/3a803cb5ae0321715bfd5247ea871d25b32c8f372aeb70550a90c5f586df/playwright-1.57.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:284ed5a706b7c389a06caa431b2f0ba9ac4130113c3a779767dda758c2497bb1", size = 45975252, upload-time = "2025-12-09T08:06:29.186Z" }, - { url = "https://files.pythonhosted.org/packages/83/d7/b72eb59dfbea0013a7f9731878df8c670f5f35318cedb010c8a30292c118/playwright-1.57.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a1bae6c0a07839cdeaddbc0756b3b2b85e476c07945f64ece08f1f956a86f1", size = 45706917, upload-time = "2025-12-09T08:06:32.549Z" }, - { url = "https://files.pythonhosted.org/packages/e4/09/3fc9ebd7c95ee54ba6a68d5c0bc23e449f7235f4603fc60534a364934c16/playwright-1.57.0-py3-none-win32.whl", hash = "sha256:1dd93b265688da46e91ecb0606d36f777f8eadcf7fbef12f6426b20bf0c9137c", size = 36553860, upload-time = "2025-12-09T08:06:35.864Z" }, - { url = "https://files.pythonhosted.org/packages/58/d4/dcdfd2a33096aeda6ca0d15584800443dd2be64becca8f315634044b135b/playwright-1.57.0-py3-none-win_amd64.whl", hash = "sha256:6caefb08ed2c6f29d33b8088d05d09376946e49a73be19271c8cd5384b82b14c", size = 36553864, upload-time = "2025-12-09T08:06:38.915Z" }, - { url = "https://files.pythonhosted.org/packages/6a/60/fe31d7e6b8907789dcb0584f88be741ba388413e4fbce35f1eba4e3073de/playwright-1.57.0-py3-none-win_arm64.whl", hash = "sha256:5f065f5a133dbc15e6e7c71e7bc04f258195755b1c32a432b792e28338c8335e", size = 32837940, upload-time = "2025-12-09T08:06:42.268Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c9/9c6061d5703267f1baae6a4647bfd1862e386fbfdb97d889f6f6ae9e3f64/playwright-1.58.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:96e3204aac292ee639edbfdef6298b4be2ea0a55a16b7068df91adac077cc606", size = 42251098, upload-time = "2026-01-30T15:09:24.028Z" }, + { url = "https://files.pythonhosted.org/packages/e0/40/59d34a756e02f8c670f0fee987d46f7ee53d05447d43cd114ca015cb168c/playwright-1.58.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70c763694739d28df71ed578b9c8202bb83e8fe8fb9268c04dd13afe36301f71", size = 41039625, upload-time = "2026-01-30T15:09:27.558Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ee/3ce6209c9c74a650aac9028c621f357a34ea5cd4d950700f8e2c4b7fe2c4/playwright-1.58.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:185e0132578733d02802dfddfbbc35f42be23a45ff49ccae5081f25952238117", size = 42251098, upload-time = "2026-01-30T15:09:30.461Z" }, + { url = "https://files.pythonhosted.org/packages/f1/af/009958cbf23fac551a940d34e3206e6c7eed2b8c940d0c3afd1feb0b0589/playwright-1.58.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c95568ba1eda83812598c1dc9be60b4406dffd60b149bc1536180ad108723d6b", size = 46235268, upload-time = "2026-01-30T15:09:33.787Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a6/0e66ad04b6d3440dae73efb39540c5685c5fc95b17c8b29340b62abbd952/playwright-1.58.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f9999948f1ab541d98812de25e3a8c410776aa516d948807140aff797b4bffa", size = 45964214, upload-time = "2026-01-30T15:09:36.751Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/236e60ab9f6d62ed0fd32150d61f1f494cefbf02304c0061e78ed80c1c32/playwright-1.58.0-py3-none-win32.whl", hash = "sha256:1e03be090e75a0fabbdaeab65ce17c308c425d879fa48bb1d7986f96bfad0b99", size = 36815998, upload-time = "2026-01-30T15:09:39.627Z" }, + { url = "https://files.pythonhosted.org/packages/41/f8/5ec599c5e59d2f2f336a05b4f318e733077cd5044f24adb6f86900c3e6a7/playwright-1.58.0-py3-none-win_amd64.whl", hash = "sha256:a2bf639d0ce33b3ba38de777e08697b0d8f3dc07ab6802e4ac53fb65e3907af8", size = 36816005, upload-time = "2026-01-30T15:09:42.449Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c4/cc0229fea55c87d6c9c67fe44a21e2cd28d1d558a5478ed4d617e9fb0c93/playwright-1.58.0-py3-none-win_arm64.whl", hash = "sha256:32ffe5c303901a13a0ecab91d1c3f74baf73b84f4bedbb6b935f5bc11cc98e1b", size = 33085919, upload-time = "2026-01-30T15:09:45.71Z" }, ] [[package]] @@ -3279,11 +3370,27 @@ wheels = [ name = "pycparser" version = "2.23" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + [[package]] name = "pydantic" version = "2.12.5" @@ -3361,7 +3468,7 @@ evals = [ ] google = [ { name = "google-genai", version = "1.47.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "google-genai", version = "1.57.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "google-genai", version = "1.62.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] groq = [ { name = "groq" }, @@ -3379,7 +3486,8 @@ openai = [ { name = "openai" }, ] retries = [ - { name = "tenacity" }, + { name = "tenacity", version = "9.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "tenacity", version = "9.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] vertexai = [ { name = "google-auth" }, @@ -3652,16 +3760,16 @@ crypto = [ [[package]] name = "pymdown-extensions" -version = "10.20" +version = "10.20.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/35/e3814a5b7df295df69d035cfb8aab78b2967cdf11fcfae7faed726b66664/pymdown_extensions-10.20.tar.gz", hash = "sha256:5c73566ab0cf38c6ba084cb7c5ea64a119ae0500cce754ccb682761dfea13a52", size = 852774, upload-time = "2025-12-31T19:59:42.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/6c/9e370934bfa30e889d12e61d0dae009991294f40055c238980066a7fbd83/pymdown_extensions-10.20.1.tar.gz", hash = "sha256:e7e39c865727338d434b55f1dd8da51febcffcaebd6e1a0b9c836243f660740a", size = 852860, upload-time = "2026-01-24T05:56:56.758Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/10/47caf89cbb52e5bb764696fd52a8c591a2f0e851a93270c05a17f36000b5/pymdown_extensions-10.20-py3-none-any.whl", hash = "sha256:ea9e62add865da80a271d00bfa1c0fa085b20d133fb3fc97afdc88e682f60b2f", size = 268733, upload-time = "2025-12-31T19:59:40.652Z" }, + { url = "https://files.pythonhosted.org/packages/40/6d/b6ee155462a0156b94312bdd82d2b92ea56e909740045a87ccb98bf52405/pymdown_extensions-10.20.1-py3-none-any.whl", hash = "sha256:24af7feacbca56504b313b7b418c4f5e1317bb5fea60f03d57be7fcc40912aa0", size = 268768, upload-time = "2026-01-24T05:56:54.537Z" }, ] [[package]] @@ -3782,15 +3890,15 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.21" +version = "0.0.22" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", "python_full_version >= '3.10' and python_full_version < '3.14'", ] -sdist = { url = "https://files.pythonhosted.org/packages/78/96/804520d0850c7db98e5ccb70282e29208723f0964e88ffd9d0da2f52ea09/python_multipart-0.0.21.tar.gz", hash = "sha256:7137ebd4d3bbf70ea1622998f902b97a29434a9e8dc40eb203bbcf7c2a2cba92", size = 37196, upload-time = "2025-12-17T09:24:22.446Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/76/03af049af4dcee5d27442f71b6924f01f3efb5d2bd34f23fcd563f2cc5f5/python_multipart-0.0.21-py3-none-any.whl", hash = "sha256:cf7a6713e01c87aa35387f4774e812c4361150938d20d232800f75ffcf266090", size = 24541, upload-time = "2025-12-17T09:24:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, ] [[package]] @@ -3956,21 +4064,21 @@ wheels = [ [[package]] name = "rich" -version = "14.2.0" +version = "14.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, + { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, ] [[package]] name = "rich-toolkit" -version = "0.17.1" +version = "0.18.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'" }, @@ -3978,9 +4086,9 @@ dependencies = [ { name = "rich" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/09/3f9b8d9daaf235195c626f21e03604c05b987404ee3bcacee0c1f67f2a8e/rich_toolkit-0.17.1.tar.gz", hash = "sha256:5af54df8d1dd9c8530e462e1bdcaed625c9b49f5a55b035aa0ba1c17bdb87c9a", size = 187925, upload-time = "2025-12-17T10:49:22.583Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/f1/bcfbde3ca38db54b5dcf7ee3d0caf3ed9133a169aec5a58ad9ec50ba12e8/rich_toolkit-0.18.1.tar.gz", hash = "sha256:bf104f1945a7252debeda7d7138118eaf848fff5ea81d9eda556cbc5f911122c", size = 192514, upload-time = "2026-02-01T10:56:31.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/7b/15e55fa8a76d0d41bf34d965af78acdaf80a315907adb30de8b63c272694/rich_toolkit-0.17.1-py3-none-any.whl", hash = "sha256:96d24bb921ecd225ffce7c526a9149e74006410c05e6d405bd74ffd54d5631ed", size = 31412, upload-time = "2025-12-17T10:49:21.793Z" }, + { url = "https://files.pythonhosted.org/packages/da/43/6f9860c4bfb1f181c347941542a8955ce24b228f84550253765aa1854d53/rich_toolkit-0.18.1-py3-none-any.whl", hash = "sha256:04011a9751f4c2becdf44bd1aaff8562d4b00caf04f14e483a9873c15fbe3154", size = 32255, upload-time = "2026-02-01T10:56:33.071Z" }, ] [[package]] @@ -4304,16 +4412,16 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.49.0" +version = "2.52.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/94/23ac26616a883f492428d9ee9ad6eee391612125326b784dbfc30e1e7bab/sentry_sdk-2.49.0.tar.gz", hash = "sha256:c1878599cde410d481c04ef50ee3aedd4f600e4d0d253f4763041e468b332c30", size = 387228, upload-time = "2026-01-08T09:56:25.642Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/eb/1b497650eb564701f9a7b8a95c51b2abe9347ed2c0b290ba78f027ebe4ea/sentry_sdk-2.52.0.tar.gz", hash = "sha256:fa0bec872cfec0302970b2996825723d67390cdd5f0229fb9efed93bd5384899", size = 410273, upload-time = "2026-02-04T15:03:54.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/43/1c586f9f413765201234541857cb82fda076f4b0f7bad4a0ec248da39cf3/sentry_sdk-2.49.0-py2.py3-none-any.whl", hash = "sha256:6ea78499133874445a20fe9c826c9e960070abeb7ae0cdf930314ab16bb97aa0", size = 415693, upload-time = "2026-01-08T09:56:21.872Z" }, + { url = "https://files.pythonhosted.org/packages/ca/63/2c6daf59d86b1c30600bff679d039f57fd1932af82c43c0bde1cbc55e8d4/sentry_sdk-2.52.0-py2.py3-none-any.whl", hash = "sha256:931c8f86169fc6f2752cb5c4e6480f0d516112e78750c312e081ababecbaf2ed", size = 435547, upload-time = "2026-02-04T15:03:51.567Z" }, ] [[package]] @@ -4376,59 +4484,66 @@ wheels = [ [[package]] name = "sqlalchemy" -version = "2.0.45" +version = "2.0.46" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", version = "3.2.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.10' and platform_machine == 'AMD64') or (python_full_version < '3.10' and platform_machine == 'WIN32') or (python_full_version < '3.10' and platform_machine == 'aarch64') or (python_full_version < '3.10' and platform_machine == 'amd64') or (python_full_version < '3.10' and platform_machine == 'ppc64le') or (python_full_version < '3.10' and platform_machine == 'win32') or (python_full_version < '3.10' and platform_machine == 'x86_64')" }, - { name = "greenlet", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and platform_machine == 'AMD64') or (python_full_version >= '3.10' and platform_machine == 'WIN32') or (python_full_version >= '3.10' and platform_machine == 'aarch64') or (python_full_version >= '3.10' and platform_machine == 'amd64') or (python_full_version >= '3.10' and platform_machine == 'ppc64le') or (python_full_version >= '3.10' and platform_machine == 'win32') or (python_full_version >= '3.10' and platform_machine == 'x86_64')" }, + { name = "greenlet", version = "3.3.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and platform_machine == 'AMD64') or (python_full_version >= '3.10' and platform_machine == 'WIN32') or (python_full_version >= '3.10' and platform_machine == 'aarch64') or (python_full_version >= '3.10' and platform_machine == 'amd64') or (python_full_version >= '3.10' and platform_machine == 'ppc64le') or (python_full_version >= '3.10' and platform_machine == 'win32') or (python_full_version >= '3.10' and platform_machine == 'x86_64')" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/f9/5e4491e5ccf42f5d9cfc663741d261b3e6e1683ae7812114e7636409fcc6/sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88", size = 9869912, upload-time = "2025-12-09T21:05:16.737Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/70/75b1387d72e2847220441166c5eb4e9846dd753895208c13e6d66523b2d9/sqlalchemy-2.0.45-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c64772786d9eee72d4d3784c28f0a636af5b0a29f3fe26ff11f55efe90c0bd85", size = 2154148, upload-time = "2025-12-10T20:03:21.023Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a4/7805e02323c49cb9d1ae5cd4913b28c97103079765f520043f914fca4cb3/sqlalchemy-2.0.45-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ae64ebf7657395824a19bca98ab10eb9a3ecb026bf09524014f1bb81cb598d4", size = 3233051, upload-time = "2025-12-09T22:06:04.768Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ec/32ae09139f61bef3de3142e85c47abdee8db9a55af2bb438da54a4549263/sqlalchemy-2.0.45-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f02325709d1b1a1489f23a39b318e175a171497374149eae74d612634b234c0", size = 3232781, upload-time = "2025-12-09T22:09:54.435Z" }, - { url = "https://files.pythonhosted.org/packages/ad/bd/bf7b869b6f5585eac34222e1cf4405f4ba8c3b85dd6b1af5d4ce8bca695f/sqlalchemy-2.0.45-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2c3684fca8a05f0ac1d9a21c1f4a266983a7ea9180efb80ffeb03861ecd01a0", size = 3182096, upload-time = "2025-12-09T22:06:06.169Z" }, - { url = "https://files.pythonhosted.org/packages/21/6a/c219720a241bb8f35c88815ccc27761f5af7fdef04b987b0e8a2c1a6dcaa/sqlalchemy-2.0.45-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040f6f0545b3b7da6b9317fc3e922c9a98fc7243b2a1b39f78390fc0942f7826", size = 3205109, upload-time = "2025-12-09T22:09:55.969Z" }, - { url = "https://files.pythonhosted.org/packages/bd/c4/6ccf31b2bc925d5d95fab403ffd50d20d7c82b858cf1a4855664ca054dce/sqlalchemy-2.0.45-cp310-cp310-win32.whl", hash = "sha256:830d434d609fe7bfa47c425c445a8b37929f140a7a44cdaf77f6d34df3a7296a", size = 2114240, upload-time = "2025-12-09T21:29:54.007Z" }, - { url = "https://files.pythonhosted.org/packages/de/29/a27a31fca07316def418db6f7c70ab14010506616a2decef1906050a0587/sqlalchemy-2.0.45-cp310-cp310-win_amd64.whl", hash = "sha256:0209d9753671b0da74da2cfbb9ecf9c02f72a759e4b018b3ab35f244c91842c7", size = 2137615, upload-time = "2025-12-09T21:29:55.85Z" }, - { url = "https://files.pythonhosted.org/packages/a2/1c/769552a9d840065137272ebe86ffbb0bc92b0f1e0a68ee5266a225f8cd7b/sqlalchemy-2.0.45-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e90a344c644a4fa871eb01809c32096487928bd2038bf10f3e4515cb688cc56", size = 2153860, upload-time = "2025-12-10T20:03:23.843Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f8/9be54ff620e5b796ca7b44670ef58bc678095d51b0e89d6e3102ea468216/sqlalchemy-2.0.45-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8c8b41b97fba5f62349aa285654230296829672fc9939cd7f35aab246d1c08b", size = 3309379, upload-time = "2025-12-09T22:06:07.461Z" }, - { url = "https://files.pythonhosted.org/packages/f6/2b/60ce3ee7a5ae172bfcd419ce23259bb874d2cddd44f67c5df3760a1e22f9/sqlalchemy-2.0.45-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12c694ed6468333a090d2f60950e4250b928f457e4962389553d6ba5fe9951ac", size = 3309948, upload-time = "2025-12-09T22:09:57.643Z" }, - { url = "https://files.pythonhosted.org/packages/a3/42/bac8d393f5db550e4e466d03d16daaafd2bad1f74e48c12673fb499a7fc1/sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f7d27a1d977a1cfef38a0e2e1ca86f09c4212666ce34e6ae542f3ed0a33bc606", size = 3261239, upload-time = "2025-12-09T22:06:08.879Z" }, - { url = "https://files.pythonhosted.org/packages/6f/12/43dc70a0528c59842b04ea1c1ed176f072a9b383190eb015384dd102fb19/sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d62e47f5d8a50099b17e2bfc1b0c7d7ecd8ba6b46b1507b58cc4f05eefc3bb1c", size = 3284065, upload-time = "2025-12-09T22:09:59.454Z" }, - { url = "https://files.pythonhosted.org/packages/cf/9c/563049cf761d9a2ec7bc489f7879e9d94e7b590496bea5bbee9ed7b4cc32/sqlalchemy-2.0.45-cp311-cp311-win32.whl", hash = "sha256:3c5f76216e7b85770d5bb5130ddd11ee89f4d52b11783674a662c7dd57018177", size = 2113480, upload-time = "2025-12-09T21:29:57.03Z" }, - { url = "https://files.pythonhosted.org/packages/bc/fa/09d0a11fe9f15c7fa5c7f0dd26be3d235b0c0cbf2f9544f43bc42efc8a24/sqlalchemy-2.0.45-cp311-cp311-win_amd64.whl", hash = "sha256:a15b98adb7f277316f2c276c090259129ee4afca783495e212048daf846654b2", size = 2138407, upload-time = "2025-12-09T21:29:58.556Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c7/1900b56ce19bff1c26f39a4ce427faec7716c81ac792bfac8b6a9f3dca93/sqlalchemy-2.0.45-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3ee2aac15169fb0d45822983631466d60b762085bc4535cd39e66bea362df5f", size = 3333760, upload-time = "2025-12-09T22:11:02.66Z" }, - { url = "https://files.pythonhosted.org/packages/0a/93/3be94d96bb442d0d9a60e55a6bb6e0958dd3457751c6f8502e56ef95fed0/sqlalchemy-2.0.45-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba547ac0b361ab4f1608afbc8432db669bd0819b3e12e29fb5fa9529a8bba81d", size = 3348268, upload-time = "2025-12-09T22:13:49.054Z" }, - { url = "https://files.pythonhosted.org/packages/48/4b/f88ded696e61513595e4a9778f9d3f2bf7332cce4eb0c7cedaabddd6687b/sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215f0528b914e5c75ef2559f69dca86878a3beeb0c1be7279d77f18e8d180ed4", size = 3278144, upload-time = "2025-12-09T22:11:04.14Z" }, - { url = "https://files.pythonhosted.org/packages/ed/6a/310ecb5657221f3e1bd5288ed83aa554923fb5da48d760a9f7622afeb065/sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:107029bf4f43d076d4011f1afb74f7c3e2ea029ec82eb23d8527d5e909e97aa6", size = 3313907, upload-time = "2025-12-09T22:13:50.598Z" }, - { url = "https://files.pythonhosted.org/packages/5c/39/69c0b4051079addd57c84a5bfb34920d87456dd4c90cf7ee0df6efafc8ff/sqlalchemy-2.0.45-cp312-cp312-win32.whl", hash = "sha256:0c9f6ada57b58420a2c0277ff853abe40b9e9449f8d7d231763c6bc30f5c4953", size = 2112182, upload-time = "2025-12-09T21:39:30.824Z" }, - { url = "https://files.pythonhosted.org/packages/f7/4e/510db49dd89fc3a6e994bee51848c94c48c4a00dc905e8d0133c251f41a7/sqlalchemy-2.0.45-cp312-cp312-win_amd64.whl", hash = "sha256:8defe5737c6d2179c7997242d6473587c3beb52e557f5ef0187277009f73e5e1", size = 2139200, upload-time = "2025-12-09T21:39:32.321Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c8/7cc5221b47a54edc72a0140a1efa56e0a2730eefa4058d7ed0b4c4357ff8/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf", size = 3277082, upload-time = "2025-12-09T22:11:06.167Z" }, - { url = "https://files.pythonhosted.org/packages/0e/50/80a8d080ac7d3d321e5e5d420c9a522b0aa770ec7013ea91f9a8b7d36e4a/sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e", size = 3293131, upload-time = "2025-12-09T22:13:52.626Z" }, - { url = "https://files.pythonhosted.org/packages/da/4c/13dab31266fc9904f7609a5dc308a2432a066141d65b857760c3bef97e69/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b", size = 3225389, upload-time = "2025-12-09T22:11:08.093Z" }, - { url = "https://files.pythonhosted.org/packages/74/04/891b5c2e9f83589de202e7abaf24cd4e4fa59e1837d64d528829ad6cc107/sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8", size = 3266054, upload-time = "2025-12-09T22:13:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/f1/24/fc59e7f71b0948cdd4cff7a286210e86b0443ef1d18a23b0d83b87e4b1f7/sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a", size = 2110299, upload-time = "2025-12-09T21:39:33.486Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c5/d17113020b2d43073412aeca09b60d2009442420372123b8d49cc253f8b8/sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee", size = 2136264, upload-time = "2025-12-09T21:39:36.801Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8d/bb40a5d10e7a5f2195f235c0b2f2c79b0bf6e8f00c0c223130a4fbd2db09/sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6", size = 3521998, upload-time = "2025-12-09T22:13:28.622Z" }, - { url = "https://files.pythonhosted.org/packages/75/a5/346128b0464886f036c039ea287b7332a410aa2d3fb0bb5d404cb8861635/sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a", size = 3473434, upload-time = "2025-12-09T22:13:30.188Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/4e1913772646b060b025d3fc52ce91a58967fe58957df32b455de5a12b4f/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774", size = 3272404, upload-time = "2025-12-09T22:11:09.662Z" }, - { url = "https://files.pythonhosted.org/packages/b3/27/caf606ee924282fe4747ee4fd454b335a72a6e018f97eab5ff7f28199e16/sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce", size = 3277057, upload-time = "2025-12-09T22:13:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/85/d0/3d64218c9724e91f3d1574d12eb7ff8f19f937643815d8daf792046d88ab/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33", size = 3222279, upload-time = "2025-12-09T22:11:11.1Z" }, - { url = "https://files.pythonhosted.org/packages/24/10/dd7688a81c5bc7690c2a3764d55a238c524cd1a5a19487928844cb247695/sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74", size = 3244508, upload-time = "2025-12-09T22:13:57.932Z" }, - { url = "https://files.pythonhosted.org/packages/aa/41/db75756ca49f777e029968d9c9fee338c7907c563267740c6d310a8e3f60/sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f", size = 2113204, upload-time = "2025-12-09T21:39:38.365Z" }, - { url = "https://files.pythonhosted.org/packages/89/a2/0e1590e9adb292b1d576dbcf67ff7df8cf55e56e78d2c927686d01080f4b/sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177", size = 2138785, upload-time = "2025-12-09T21:39:39.503Z" }, - { url = "https://files.pythonhosted.org/packages/42/39/f05f0ed54d451156bbed0e23eb0516bcad7cbb9f18b3bf219c786371b3f0/sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b", size = 3522029, upload-time = "2025-12-09T22:13:32.09Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/d15398b98b65c2bce288d5ee3f7d0a81f77ab89d9456994d5c7cc8b2a9db/sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b", size = 3475142, upload-time = "2025-12-09T22:13:33.739Z" }, - { url = "https://files.pythonhosted.org/packages/53/01/a01b9829d146ba59972e6dfc88138142f5ffa4110e492c83326e7d765a17/sqlalchemy-2.0.45-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d29b2b99d527dbc66dd87c3c3248a5dd789d974a507f4653c969999fc7c1191b", size = 2157179, upload-time = "2025-12-10T20:05:13.998Z" }, - { url = "https://files.pythonhosted.org/packages/1f/78/ed43ed8ac27844f129adfc45a8735bab5dcad3e5211f4dc1bd7e676bc3ed/sqlalchemy-2.0.45-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:59a8b8bd9c6bedf81ad07c8bd5543eedca55fe9b8780b2b628d495ba55f8db1e", size = 3233038, upload-time = "2025-12-09T22:06:55.42Z" }, - { url = "https://files.pythonhosted.org/packages/24/1c/721ec797f21431c905ad98cbce66430d72a340935e3b7e3232cf05e015cc/sqlalchemy-2.0.45-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd93c6f5d65f254ceabe97548c709e073d6da9883343adaa51bf1a913ce93f8e", size = 3233117, upload-time = "2025-12-09T22:10:03.143Z" }, - { url = "https://files.pythonhosted.org/packages/52/33/dcfb8dffb2ccd7c6803d63454dc1917ef5ec5b5e281fecbbc0ed1de1f125/sqlalchemy-2.0.45-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d0beadc2535157070c9c17ecf25ecec31e13c229a8f69196d7590bde8082bf1", size = 3182306, upload-time = "2025-12-09T22:06:56.894Z" }, - { url = "https://files.pythonhosted.org/packages/53/76/7cf8ce9e6dcac1d37125425aadec406d8a839dffc1b8763f6e7a56b0bf33/sqlalchemy-2.0.45-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e057f928ffe9c9b246a55b469c133b98a426297e1772ad24ce9f0c47d123bd5b", size = 3205587, upload-time = "2025-12-09T22:10:04.812Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ac/5cd0d14f7830981c06f468507237b0a8205691d626492b5551a67535eb30/sqlalchemy-2.0.45-cp39-cp39-win32.whl", hash = "sha256:c1c2091b1489435ff85728fafeb990f073e64f6f5e81d5cd53059773e8521eb6", size = 2115932, upload-time = "2025-12-09T22:09:17.012Z" }, - { url = "https://files.pythonhosted.org/packages/b9/eb/76f6db8828c6e0cfac89820a07a40a2bab25e82e69827177b942a9bff42a/sqlalchemy-2.0.45-cp39-cp39-win_amd64.whl", hash = "sha256:56ead1f8dfb91a54a28cd1d072c74b3d635bcffbd25e50786533b822d4f2cde2", size = 2139570, upload-time = "2025-12-09T22:09:18.545Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e1/3ccb13c643399d22289c6a9786c1a91e3dcbb68bce4beb44926ac2c557bf/sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0", size = 1936672, upload-time = "2025-12-09T21:54:52.608Z" }, + { url = "https://files.pythonhosted.org/packages/40/26/66ba59328dc25e523bfcb0f8db48bdebe2035e0159d600e1f01c0fc93967/sqlalchemy-2.0.46-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:895296687ad06dc9b11a024cf68e8d9d3943aa0b4964278d2553b86f1b267735", size = 2155051, upload-time = "2026-01-21T18:27:28.965Z" }, + { url = "https://files.pythonhosted.org/packages/21/cd/9336732941df972fbbfa394db9caa8bb0cf9fe03656ec728d12e9cbd6edc/sqlalchemy-2.0.46-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab65cb2885a9f80f979b85aa4e9c9165a31381ca322cbde7c638fe6eefd1ec39", size = 3234666, upload-time = "2026-01-21T18:32:28.72Z" }, + { url = "https://files.pythonhosted.org/packages/38/62/865ae8b739930ec433cd4123760bee7f8dafdc10abefd725a025604fb0de/sqlalchemy-2.0.46-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52fe29b3817bd191cc20bad564237c808967972c97fa683c04b28ec8979ae36f", size = 3232917, upload-time = "2026-01-21T18:44:54.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/38/805904b911857f2b5e00fdea44e9570df62110f834378706939825579296/sqlalchemy-2.0.46-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:09168817d6c19954d3b7655da6ba87fcb3a62bb575fb396a81a8b6a9fadfe8b5", size = 3185790, upload-time = "2026-01-21T18:32:30.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/4f/3260bb53aabd2d274856337456ea52f6a7eccf6cce208e558f870cec766b/sqlalchemy-2.0.46-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be6c0466b4c25b44c5d82b0426b5501de3c424d7a3220e86cd32f319ba56798e", size = 3207206, upload-time = "2026-01-21T18:44:55.93Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b3/67c432d7f9d88bb1a61909b67e29f6354d59186c168fb5d381cf438d3b73/sqlalchemy-2.0.46-cp310-cp310-win32.whl", hash = "sha256:1bc3f601f0a818d27bfe139f6766487d9c88502062a2cd3a7ee6c342e81d5047", size = 2115296, upload-time = "2026-01-21T18:33:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8c/25fb284f570f9d48e6c240f0269a50cec9cf009a7e08be4c0aaaf0654972/sqlalchemy-2.0.46-cp310-cp310-win_amd64.whl", hash = "sha256:e0c05aff5c6b1bb5fb46a87e0f9d2f733f83ef6cbbbcd5c642b6c01678268061", size = 2138540, upload-time = "2026-01-21T18:33:14.22Z" }, + { url = "https://files.pythonhosted.org/packages/69/ac/b42ad16800d0885105b59380ad69aad0cce5a65276e269ce2729a2343b6a/sqlalchemy-2.0.46-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:261c4b1f101b4a411154f1da2b76497d73abbfc42740029205d4d01fa1052684", size = 2154851, upload-time = "2026-01-21T18:27:30.54Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/d8710068cb79f64d002ebed62a7263c00c8fd95f4ebd4b5be8f7ca93f2bc/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:181903fe8c1b9082995325f1b2e84ac078b1189e2819380c2303a5f90e114a62", size = 3311241, upload-time = "2026-01-21T18:32:33.45Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/20c71487c7219ab3aa7421c7c62d93824c97c1460f2e8bb72404b0192d13/sqlalchemy-2.0.46-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:590be24e20e2424a4c3c1b0835e9405fa3d0af5823a1a9fc02e5dff56471515f", size = 3310741, upload-time = "2026-01-21T18:44:57.887Z" }, + { url = "https://files.pythonhosted.org/packages/65/80/d26d00b3b249ae000eee4db206fcfc564bf6ca5030e4747adf451f4b5108/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7568fe771f974abadce52669ef3a03150ff03186d8eb82613bc8adc435a03f01", size = 3263116, upload-time = "2026-01-21T18:32:35.044Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/74dda7506640923821340541e8e45bd3edd8df78664f1f2e0aae8077192b/sqlalchemy-2.0.46-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf7e1e78af38047e08836d33502c7a278915698b7c2145d045f780201679999", size = 3285327, upload-time = "2026-01-21T18:44:59.254Z" }, + { url = "https://files.pythonhosted.org/packages/9f/25/6dcf8abafff1389a21c7185364de145107b7394ecdcb05233815b236330d/sqlalchemy-2.0.46-cp311-cp311-win32.whl", hash = "sha256:9d80ea2ac519c364a7286e8d765d6cd08648f5b21ca855a8017d9871f075542d", size = 2114564, upload-time = "2026-01-21T18:33:15.85Z" }, + { url = "https://files.pythonhosted.org/packages/93/5f/e081490f8523adc0088f777e4ebad3cac21e498ec8a3d4067074e21447a1/sqlalchemy-2.0.46-cp311-cp311-win_amd64.whl", hash = "sha256:585af6afe518732d9ccd3aea33af2edaae4a7aa881af5d8f6f4fe3a368699597", size = 2139233, upload-time = "2026-01-21T18:33:17.528Z" }, + { url = "https://files.pythonhosted.org/packages/b6/35/d16bfa235c8b7caba3730bba43e20b1e376d2224f407c178fbf59559f23e/sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c", size = 2153405, upload-time = "2026-01-21T19:05:54.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/6c/3192e24486749862f495ddc6584ed730c0c994a67550ec395d872a2ad650/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9", size = 3334702, upload-time = "2026-01-21T18:46:45.384Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a2/b9f33c8d68a3747d972a0bb758c6b63691f8fb8a49014bc3379ba15d4274/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b", size = 3347664, upload-time = "2026-01-21T18:40:09.979Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d2/3e59e2a91eaec9db7e8dc6b37b91489b5caeb054f670f32c95bcba98940f/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53", size = 3277372, upload-time = "2026-01-21T18:46:47.168Z" }, + { url = "https://files.pythonhosted.org/packages/dd/dd/67bc2e368b524e2192c3927b423798deda72c003e73a1e94c21e74b20a85/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e", size = 3312425, upload-time = "2026-01-21T18:40:11.548Z" }, + { url = "https://files.pythonhosted.org/packages/43/82/0ecd68e172bfe62247e96cb47867c2d68752566811a4e8c9d8f6e7c38a65/sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb", size = 2113155, upload-time = "2026-01-21T18:42:49.748Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2a/2821a45742073fc0331dc132552b30de68ba9563230853437cac54b2b53e/sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff", size = 2140078, upload-time = "2026-01-21T18:42:51.197Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4b/fa7838fe20bb752810feed60e45625a9a8b0102c0c09971e2d1d95362992/sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00", size = 2150268, upload-time = "2026-01-21T19:05:56.621Z" }, + { url = "https://files.pythonhosted.org/packages/46/c1/b34dccd712e8ea846edf396e00973dda82d598cb93762e55e43e6835eba9/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2", size = 3276511, upload-time = "2026-01-21T18:46:49.022Z" }, + { url = "https://files.pythonhosted.org/packages/96/48/a04d9c94753e5d5d096c628c82a98c4793b9c08ca0e7155c3eb7d7db9f24/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee", size = 3292881, upload-time = "2026-01-21T18:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/be/f4/06eda6e91476f90a7d8058f74311cb65a2fb68d988171aced81707189131/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad", size = 3224559, upload-time = "2026-01-21T18:46:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/d2af04095412ca6345ac22b33b89fe8d6f32a481e613ffcb2377d931d8d0/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e", size = 3262728, upload-time = "2026-01-21T18:40:14.883Z" }, + { url = "https://files.pythonhosted.org/packages/31/48/1980c7caa5978a3b8225b4d230e69a2a6538a3562b8b31cea679b6933c83/sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f", size = 2111295, upload-time = "2026-01-21T18:42:52.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/54/f8d65bbde3d877617c4720f3c9f60e99bb7266df0d5d78b6e25e7c149f35/sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef", size = 2137076, upload-time = "2026-01-21T18:42:53.924Z" }, + { url = "https://files.pythonhosted.org/packages/56/ba/9be4f97c7eb2b9d5544f2624adfc2853e796ed51d2bb8aec90bc94b7137e/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10", size = 3556533, upload-time = "2026-01-21T18:33:06.636Z" }, + { url = "https://files.pythonhosted.org/packages/20/a6/b1fc6634564dbb4415b7ed6419cdfeaadefd2c39cdab1e3aa07a5f2474c2/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764", size = 3523208, upload-time = "2026-01-21T18:45:08.436Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d8/41e0bdfc0f930ff236f86fccd12962d8fa03713f17ed57332d38af6a3782/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b", size = 3464292, upload-time = "2026-01-21T18:33:08.208Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8b/9dcbec62d95bea85f5ecad9b8d65b78cc30fb0ffceeb3597961f3712549b/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447", size = 3473497, upload-time = "2026-01-21T18:45:10.552Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/5ecdfc73383ec496de038ed1614de9e740a82db9ad67e6e4514ebc0708a3/sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada", size = 2152079, upload-time = "2026-01-21T19:05:58.477Z" }, + { url = "https://files.pythonhosted.org/packages/e5/bf/eba3036be7663ce4d9c050bc3d63794dc29fbe01691f2bf5ccb64e048d20/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366", size = 3272216, upload-time = "2026-01-21T18:46:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/05/45/1256fb597bb83b58a01ddb600c59fe6fdf0e5afe333f0456ed75c0f8d7bd/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d", size = 3277208, upload-time = "2026-01-21T18:40:16.38Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a0/2053b39e4e63b5d7ceb3372cface0859a067c1ddbd575ea7e9985716f771/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e", size = 3221994, upload-time = "2026-01-21T18:46:54.622Z" }, + { url = "https://files.pythonhosted.org/packages/1e/87/97713497d9502553c68f105a1cb62786ba1ee91dea3852ae4067ed956a50/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf", size = 3243990, upload-time = "2026-01-21T18:40:18.253Z" }, + { url = "https://files.pythonhosted.org/packages/a8/87/5d1b23548f420ff823c236f8bea36b1a997250fd2f892e44a3838ca424f4/sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908", size = 2114215, upload-time = "2026-01-21T18:42:55.232Z" }, + { url = "https://files.pythonhosted.org/packages/3a/20/555f39cbcf0c10cf452988b6a93c2a12495035f68b3dbd1a408531049d31/sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b", size = 2139867, upload-time = "2026-01-21T18:42:56.474Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f0/f96c8057c982d9d8a7a68f45d69c674bc6f78cad401099692fe16521640a/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa", size = 3561202, upload-time = "2026-01-21T18:33:10.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296, upload-time = "2026-01-21T18:45:12.657Z" }, + { url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008, upload-time = "2026-01-21T18:33:11.725Z" }, + { url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137, upload-time = "2026-01-21T18:45:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/9a/06/a29b51a577cc5746712ed8a2870794659a6bf405264b32dd5ccc380844d1/sqlalchemy-2.0.46-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:90bde6c6b1827565a95fde597da001212ab436f1b2e0c2dcc7246e14db26e2a3", size = 2158097, upload-time = "2026-01-21T18:24:45.892Z" }, + { url = "https://files.pythonhosted.org/packages/be/55/44689ed21b5a82708502243310878cfc76e0f326ed16103f4336f605055b/sqlalchemy-2.0.46-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b1e5f3a5f1ff4f42d5daab047428cd45a3380e51e191360a35cef71c9a7a2a", size = 3233722, upload-time = "2026-01-21T18:30:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/be/11/1d6024d9cdd2108d500b399bdc77a1738119789aa70c83d68e1012d32596/sqlalchemy-2.0.46-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93bb0aae40b52c57fd74ef9c6933c08c040ba98daf23ad33c3f9893494b8d3ce", size = 3233038, upload-time = "2026-01-21T18:32:26.945Z" }, + { url = "https://files.pythonhosted.org/packages/38/6d/f813e3204baea710f2d82a61821bdf7b39cebda6dbba7cdeb976b0552239/sqlalchemy-2.0.46-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4e2cc868b7b5208aec6c960950b7bb821f82c2fe66446c92ee0a571765e91a5", size = 3183163, upload-time = "2026-01-21T18:30:58.647Z" }, + { url = "https://files.pythonhosted.org/packages/8d/5d/32b70643ef73c1bb3723a98316b89182cad2b9a6744d5335f1d69fcdb3f2/sqlalchemy-2.0.46-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:965c62be8256d10c11f8907e7a8d3e18127a4c527a5919d85fa87fd9ecc2cfdc", size = 3205174, upload-time = "2026-01-21T18:32:28.684Z" }, + { url = "https://files.pythonhosted.org/packages/99/a9/b9f7bd299b7550925e1e7d71d634e1eee23c035abed7de125fda7c74b0c8/sqlalchemy-2.0.46-cp39-cp39-win32.whl", hash = "sha256:9397b381dcee8a2d6b99447ae85ea2530dcac82ca494d1db877087a13e38926d", size = 2117095, upload-time = "2026-01-21T18:34:02.596Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/2e376b34a7c2f3d9d40811c3412fdc65cd35c6da2d660c283ad24bd9bab1/sqlalchemy-2.0.46-cp39-cp39-win_amd64.whl", hash = "sha256:4396c948d8217e83e2c202fbdcc0389cf8c93d2c1c5e60fa5c5a955eae0e64be", size = 2140517, upload-time = "2026-01-21T18:34:03.958Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" }, ] [[package]] @@ -4446,15 +4561,15 @@ wheels = [ [[package]] name = "sse-starlette" -version = "3.1.2" +version = "3.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio", marker = "python_full_version >= '3.10'" }, { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/34/f5df66cb383efdbf4f2db23cabb27f51b1dcb737efaf8a558f6f1d195134/sse_starlette-3.1.2.tar.gz", hash = "sha256:55eff034207a83a0eb86de9a68099bd0157838f0b8b999a1b742005c71e33618", size = 26303, upload-time = "2025-12-31T08:02:20.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/8d/00d280c03ffd39aaee0e86ec81e2d3b9253036a0f93f51d10503adef0e65/sse_starlette-3.2.0.tar.gz", hash = "sha256:8127594edfb51abe44eac9c49e59b0b01f1039d0c7461c6fd91d4e03b70da422", size = 27253, upload-time = "2026-01-17T13:11:05.62Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/95/8c4b76eec9ae574474e5d2997557cebf764bcd3586458956c30631ae08f4/sse_starlette-3.1.2-py3-none-any.whl", hash = "sha256:cd800dd349f4521b317b9391d3796fa97b71748a4da9b9e00aafab32dda375c8", size = 12484, upload-time = "2025-12-31T08:02:18.894Z" }, + { url = "https://files.pythonhosted.org/packages/96/7f/832f015020844a8b8f7a9cbc103dd76ba8e3875004c41e08440ea3a2b41a/sse_starlette-3.2.0-py3-none-any.whl", hash = "sha256:5876954bd51920fc2cd51baee47a080eb88a37b5b784e615abb0b283f801cdbf", size = 12763, upload-time = "2026-01-17T13:11:03.775Z" }, ] [[package]] @@ -4511,7 +4626,7 @@ wheels = [ [[package]] name = "strawberry-graphql" -version = "0.288.2" +version = "0.291.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -4524,9 +4639,9 @@ dependencies = [ { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/13/306dd2edb5f0c4aa51e2d071994a91ed1a0304e9feafa79ea0f04da51298/strawberry_graphql-0.288.2.tar.gz", hash = "sha256:853dbab407e3f5099f3a27dbf37786535894a0fbf150df5dde145fc290db607e", size = 215182, upload-time = "2026-01-01T20:01:19.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/18/5a07bc95bdf1cb73e93cd1f1d5b11750ded6f5e4d9407e7d500abc635b36/strawberry_graphql-0.291.1.tar.gz", hash = "sha256:13463d09e042857e4c12eee0eafc2bbdef963e4a30e20ed9a9c9fb0d487d4aac", size = 217765, upload-time = "2026-02-06T10:07:15.861Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/14/15abfa6d289048eeb1b1cca4a582db08a3c1f42784b485c21ef54617e2c7/strawberry_graphql-0.288.2-py3-none-any.whl", hash = "sha256:ad72d7904582db333158568751bb6186a872380a8cc6671159d011d279382542", size = 313137, upload-time = "2026-01-01T20:01:17.32Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7b/47ec56118decc90b9ae85a98180dc326fdad2ee031cba0e05c6c62349b88/strawberry_graphql-0.291.1-py3-none-any.whl", hash = "sha256:09798d3172b42be4d5526771ec581e1b1f93c7020b82a8b07b4c507676f75aa0", size = 316439, upload-time = "2026-02-06T10:07:18.247Z" }, ] [[package]] @@ -4545,11 +4660,27 @@ wheels = [ name = "tenacity" version = "9.1.2" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, ] +[[package]] +name = "tenacity" +version = "9.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/4a/c3357c8742f361785e3702bb4c9c68c4cb37a80aa657640b820669be5af1/tenacity-9.1.3.tar.gz", hash = "sha256:a6724c947aa717087e2531f883bde5c9188f603f6669a9b8d54eb998e604c12a", size = 49002, upload-time = "2026-02-05T06:33:12.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/6b/cdc85edb15e384d8e934aad89638cc8646e118c80de94c60125d0fc0a185/tenacity-9.1.3-py3-none-any.whl", hash = "sha256:51171cfc6b8a7826551e2f029426b10a6af189c5ac6986adcd7eb36d42f17954", size = 28858, upload-time = "2026-02-05T06:33:11.219Z" }, +] + [[package]] name = "termcolor" version = "3.1.0" @@ -4651,63 +4782,68 @@ wheels = [ [[package]] name = "tomli" -version = "2.3.0" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, - { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, - { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, - { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, - { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, - { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] [[package]] name = "tqdm" -version = "4.67.1" +version = "4.67.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] [[package]] @@ -5243,11 +5379,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.2.14" +version = "0.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/62/a7c072fbfefb2980a00f99ca994279cb9ecf310cb2e6b2a4d2a28fe192b3/wcwidth-0.5.3.tar.gz", hash = "sha256:53123b7af053c74e9fe2e92ac810301f6139e64379031f7124574212fb3b4091", size = 157587, upload-time = "2026-01-31T03:52:10.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/3c/c1/d73f12f8cdb1891334a2ccf7389eed244d3941e74d80dd220badb937f3fb/wcwidth-0.5.3-py3-none-any.whl", hash = "sha256:d584eff31cd4753e1e5ff6c12e1edfdb324c995713f75d26c29807bb84bf649e", size = 92981, upload-time = "2026-01-31T03:52:09.14Z" }, ] [[package]] From 8e50c55fd90c4433ddc9096ff439da9498a8314a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Feb 2026 13:45:47 +0000 Subject: [PATCH 039/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c1a563f18..bd62fc063 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* 👷 Run tests with lower bound uv sync, upgrade `fastapi[all]` minimum dependencies: `ujson >=5.8.0`, `orjson >=3.9.3`. PR [#14846](https://github.com/fastapi/fastapi/pull/14846) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.2 ### Features From f9f79926047d7f5d8e0127dd585e7683ee57e9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 6 Feb 2026 07:18:30 -0800 Subject: [PATCH 040/195] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Re-implement=20`on?= =?UTF-8?q?=5Fevent`=20in=20FastAPI=20for=20compatibility=20with=20the=20n?= =?UTF-8?q?ext=20Starlette,=20while=20keeping=20backwards=20compatibility?= =?UTF-8?q?=20(#14851)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/routing.py | 157 +++++++++++++++++++++++++++++++++++- tests/test_router_events.py | 76 +++++++++++++++++ 2 files changed, 229 insertions(+), 4 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 9ca2f4673..c95f624bd 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1,22 +1,31 @@ +import contextlib import email.message import functools import inspect import json +import types from collections.abc import ( AsyncIterator, Awaitable, Collection, Coroutine, + Generator, Mapping, Sequence, ) -from contextlib import AsyncExitStack, asynccontextmanager +from contextlib import ( + AbstractAsyncContextManager, + AbstractContextManager, + AsyncExitStack, + asynccontextmanager, +) from enum import Enum, IntEnum from typing import ( Annotated, Any, Callable, Optional, + TypeVar, Union, ) @@ -143,6 +152,50 @@ def websocket_session( return app +_T = TypeVar("_T") + + +# Vendored from starlette.routing to avoid importing private symbols +class _AsyncLiftContextManager(AbstractAsyncContextManager[_T]): + """ + Wraps a synchronous context manager to make it async. + + This is vendored from Starlette to avoid importing private symbols. + """ + + def __init__(self, cm: AbstractContextManager[_T]) -> None: + self._cm = cm + + async def __aenter__(self) -> _T: + return self._cm.__enter__() + + async def __aexit__( + self, + exc_type: Optional[type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[types.TracebackType], + ) -> Optional[bool]: + return self._cm.__exit__(exc_type, exc_value, traceback) + + +# Vendored from starlette.routing to avoid importing private symbols +def _wrap_gen_lifespan_context( + lifespan_context: Callable[[Any], Generator[Any, Any, Any]], +) -> Callable[[Any], AbstractAsyncContextManager[Any]]: + """ + Wrap a generator-based lifespan context into an async context manager. + + This is vendored from Starlette to avoid importing private symbols. + """ + cmgr = contextlib.contextmanager(lifespan_context) + + @functools.wraps(cmgr) + def wrapper(app: Any) -> _AsyncLiftContextManager[Any]: + return _AsyncLiftContextManager(cmgr(app)) + + return wrapper + + def _merge_lifespan_context( original_context: Lifespan[Any], nested_context: Lifespan[Any] ) -> Lifespan[Any]: @@ -160,6 +213,30 @@ def _merge_lifespan_context( return merged_lifespan # type: ignore[return-value] +class _DefaultLifespan: + """ + Default lifespan context manager that runs on_startup and on_shutdown handlers. + + This is a copy of the Starlette _DefaultLifespan class that was removed + in Starlette. FastAPI keeps it to maintain backward compatibility with + on_startup and on_shutdown event handlers. + + Ref: https://github.com/Kludex/starlette/pull/3117 + """ + + def __init__(self, router: "APIRouter") -> None: + self._router = router + + async def __aenter__(self) -> None: + await self._router._startup() + + async def __aexit__(self, *exc_info: object) -> None: + await self._router._shutdown() + + def __call__(self: _T, app: object) -> _T: + return self + + # Cache for endpoint context to avoid re-extracting on every request _endpoint_context_cache: dict[int, EndpointContext] = {} @@ -903,13 +980,33 @@ class APIRouter(routing.Router): ), ] = Default(generate_unique_id), ) -> None: + # Handle on_startup/on_shutdown locally since Starlette removed support + # Ref: https://github.com/Kludex/starlette/pull/3117 + # TODO: deprecate this once the lifespan (or alternative) interface is improved + self.on_startup: list[Callable[[], Any]] = ( + [] if on_startup is None else list(on_startup) + ) + self.on_shutdown: list[Callable[[], Any]] = ( + [] if on_shutdown is None else list(on_shutdown) + ) + + # Determine the lifespan context to use + if lifespan is None: + # Use the default lifespan that runs on_startup/on_shutdown handlers + lifespan_context: Lifespan[Any] = _DefaultLifespan(self) + elif inspect.isasyncgenfunction(lifespan): + lifespan_context = asynccontextmanager(lifespan) + elif inspect.isgeneratorfunction(lifespan): + lifespan_context = _wrap_gen_lifespan_context(lifespan) + else: + lifespan_context = lifespan + self.lifespan_context = lifespan_context + super().__init__( routes=routes, redirect_slashes=redirect_slashes, default=default, - on_startup=on_startup, - on_shutdown=on_shutdown, - lifespan=lifespan, + lifespan=lifespan_context, ) if prefix: assert prefix.startswith("/"), "A path prefix must start with '/'" @@ -4473,6 +4570,58 @@ class APIRouter(routing.Router): generate_unique_id_function=generate_unique_id_function, ) + # TODO: remove this once the lifespan (or alternative) interface is improved + async def _startup(self) -> None: + """ + Run any `.on_startup` event handlers. + + This method is kept for backward compatibility after Starlette removed + support for on_startup/on_shutdown handlers. + + Ref: https://github.com/Kludex/starlette/pull/3117 + """ + for handler in self.on_startup: + if is_async_callable(handler): + await handler() + else: + handler() + + # TODO: remove this once the lifespan (or alternative) interface is improved + async def _shutdown(self) -> None: + """ + Run any `.on_shutdown` event handlers. + + This method is kept for backward compatibility after Starlette removed + support for on_startup/on_shutdown handlers. + + Ref: https://github.com/Kludex/starlette/pull/3117 + """ + for handler in self.on_shutdown: + if is_async_callable(handler): + await handler() + else: + handler() + + # TODO: remove this once the lifespan (or alternative) interface is improved + def add_event_handler( + self, + event_type: str, + func: Callable[[], Any], + ) -> None: + """ + Add an event handler function for startup or shutdown. + + This method is kept for backward compatibility after Starlette removed + support for on_startup/on_shutdown handlers. + + Ref: https://github.com/Kludex/starlette/pull/3117 + """ + assert event_type in ("startup", "shutdown") + if event_type == "startup": + self.on_startup.append(func) + else: + self.on_shutdown.append(func) + @deprecated( """ on_event is deprecated, use lifespan event handlers instead. diff --git a/tests/test_router_events.py b/tests/test_router_events.py index 9df299cda..65f2f521c 100644 --- a/tests/test_router_events.py +++ b/tests/test_router_events.py @@ -241,3 +241,79 @@ def test_merged_mixed_state_lifespans() -> None: with TestClient(app) as client: assert client.app_state == {"router": True} + + +@pytest.mark.filterwarnings( + r"ignore:\s*on_event is deprecated, use lifespan event handlers instead.*:DeprecationWarning" +) +def test_router_async_shutdown_handler(state: State) -> None: + """Test that async on_shutdown event handlers are called correctly, for coverage.""" + app = FastAPI() + + @app.get("/") + def main() -> dict[str, str]: + return {"message": "Hello World"} + + @app.on_event("shutdown") + async def app_shutdown() -> None: + state.app_shutdown = True + + assert state.app_shutdown is False + with TestClient(app) as client: + assert state.app_shutdown is False + response = client.get("/") + assert response.status_code == 200, response.text + assert state.app_shutdown is True + + +def test_router_sync_generator_lifespan(state: State) -> None: + """Test that a sync generator lifespan works via _wrap_gen_lifespan_context.""" + from collections.abc import Generator + + def lifespan(app: FastAPI) -> Generator[None, None, None]: + state.app_startup = True + yield + state.app_shutdown = True + + app = FastAPI(lifespan=lifespan) # type: ignore[arg-type] + + @app.get("/") + def main() -> dict[str, str]: + return {"message": "Hello World"} + + assert state.app_startup is False + assert state.app_shutdown is False + with TestClient(app) as client: + assert state.app_startup is True + assert state.app_shutdown is False + response = client.get("/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Hello World"} + assert state.app_startup is True + assert state.app_shutdown is True + + +def test_router_async_generator_lifespan(state: State) -> None: + """Test that an async generator lifespan (not wrapped) works.""" + + async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: + state.app_startup = True + yield + state.app_shutdown = True + + app = FastAPI(lifespan=lifespan) # type: ignore[arg-type] + + @app.get("/") + def main() -> dict[str, str]: + return {"message": "Hello World"} + + assert state.app_startup is False + assert state.app_shutdown is False + with TestClient(app) as client: + assert state.app_startup is True + assert state.app_shutdown is False + response = client.get("/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Hello World"} + assert state.app_startup is True + assert state.app_shutdown is True From fe8c33ea64f797ae544e25e7ddb82c8342fd8880 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Feb 2026 15:18:55 +0000 Subject: [PATCH 041/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bd62fc063..823465ddf 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Refactors + +* ♻️ Re-implement `on_event` in FastAPI for compatibility with the next Starlette, while keeping backwards compatibility. PR [#14851](https://github.com/fastapi/fastapi/pull/14851) by [@tiangolo](https://github.com/tiangolo). + ### Internal * 👷 Run tests with lower bound uv sync, upgrade `fastapi[all]` minimum dependencies: `ujson >=5.8.0`, `orjson >=3.9.3`. PR [#14846](https://github.com/fastapi/fastapi/pull/14846) by [@tiangolo](https://github.com/tiangolo). From 08233d7ffca5b967489910f8bb57161c9b52090c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 6 Feb 2026 07:28:59 -0800 Subject: [PATCH 042/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20ru=20(update-outdated)=20(#14834)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- .../ru/docs/advanced/advanced-dependencies.md | 2 +- docs/ru/docs/advanced/wsgi.md | 20 +++++++++++++++++-- docs/ru/docs/deployment/docker.md | 4 +--- docs/ru/docs/index.md | 18 ++++++----------- docs/ru/docs/tutorial/body-multiple-params.md | 8 ++++---- .../tutorial/path-operation-configuration.md | 6 +++--- 6 files changed, 33 insertions(+), 25 deletions(-) diff --git a/docs/ru/docs/advanced/advanced-dependencies.md b/docs/ru/docs/advanced/advanced-dependencies.md index cc6691b30..fb2643cd5 100644 --- a/docs/ru/docs/advanced/advanced-dependencies.md +++ b/docs/ru/docs/advanced/advanced-dependencies.md @@ -48,7 +48,7 @@ checker(q="somequery") ``` -…и передаст возвращённое значение как значение зависимости в нашу *функцию-обработчике пути* в параметр `fixed_content_included`: +…и передаст возвращённое значение как значение зависимости в параметр `fixed_content_included` нашей *функции-обработчика пути*: {* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} diff --git a/docs/ru/docs/advanced/wsgi.md b/docs/ru/docs/advanced/wsgi.md index 64d7c7a28..41d3a169c 100644 --- a/docs/ru/docs/advanced/wsgi.md +++ b/docs/ru/docs/advanced/wsgi.md @@ -6,13 +6,29 @@ ## Использование `WSGIMiddleware` { #using-wsgimiddleware } -Нужно импортировать `WSGIMiddleware`. +/// info | Информация + +Для этого требуется установить `a2wsgi`, например с помощью `pip install a2wsgi`. + +/// + +Нужно импортировать `WSGIMiddleware` из `a2wsgi`. Затем оберните WSGI‑приложение (например, Flask) в middleware (Промежуточный слой). После этого смонтируйте его на путь. -{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *} +{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} + +/// note | Примечание + +Ранее рекомендовалось использовать `WSGIMiddleware` из `fastapi.middleware.wsgi`, но теперь он помечен как устаревший. + +Вместо него рекомендуется использовать пакет `a2wsgi`. Использование остаётся таким же. + +Просто убедитесь, что пакет `a2wsgi` установлен, и импортируйте `WSGIMiddleware` из `a2wsgi`. + +/// ## Проверьте { #check-it } diff --git a/docs/ru/docs/deployment/docker.md b/docs/ru/docs/deployment/docker.md index 3937b0165..9e8562be7 100644 --- a/docs/ru/docs/deployment/docker.md +++ b/docs/ru/docs/deployment/docker.md @@ -145,8 +145,6 @@ Successfully installed fastapi pydantic * Создайте файл `main.py` со следующим содержимым: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -158,7 +156,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` diff --git a/docs/ru/docs/index.md b/docs/ru/docs/index.md index 02b1c9a28..b1a0c9a2e 100644 --- a/docs/ru/docs/index.md +++ b/docs/ru/docs/index.md @@ -161,8 +161,6 @@ $ pip install "fastapi[standard]" Создайте файл `main.py` со следующим содержимым: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -174,7 +172,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -183,9 +181,7 @@ def read_item(item_id: int, q: Union[str, None] = None): Если ваш код использует `async` / `await`, используйте `async def`: -```Python hl_lines="9 14" -from typing import Union - +```Python hl_lines="7 12" from fastapi import FastAPI app = FastAPI() @@ -197,7 +193,7 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): +async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -288,9 +284,7 @@ INFO: Application startup complete. Объявите тело запроса, используя стандартные типы Python, спасибо Pydantic. -```Python hl_lines="4 9-12 25-27" -from typing import Union - +```Python hl_lines="2 7-10 23-25" from fastapi import FastAPI from pydantic import BaseModel @@ -300,7 +294,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Union[bool, None] = None + is_offer: bool | None = None @app.get("/") @@ -309,7 +303,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} diff --git a/docs/ru/docs/tutorial/body-multiple-params.md b/docs/ru/docs/tutorial/body-multiple-params.md index 9ae57a311..9d9400494 100644 --- a/docs/ru/docs/tutorial/body-multiple-params.md +++ b/docs/ru/docs/tutorial/body-multiple-params.md @@ -101,13 +101,13 @@ Поскольку по умолчанию, отдельные значения интерпретируются как query-параметры, вам не нужно явно добавлять `Query`, вы можете просто сделать так: ```Python -q: Union[str, None] = None +q: str | None = None ``` -Или в Python 3.10 и выше: +Или в Python 3.9: ```Python -q: str | None = None +q: Union[str, None] = None ``` Например: @@ -116,7 +116,7 @@ q: str | None = None /// info | Информация -`Body` также имеет все те же дополнительные параметры валидации и метаданных, как у `Query`,`Path` и других, которые вы увидите позже. +`Body` также имеет все те же дополнительные параметры валидации и метаданных, как у `Query`, `Path` и других, которые вы увидите позже. /// diff --git a/docs/ru/docs/tutorial/path-operation-configuration.md b/docs/ru/docs/tutorial/path-operation-configuration.md index 96a54ffea..112a1efca 100644 --- a/docs/ru/docs/tutorial/path-operation-configuration.md +++ b/docs/ru/docs/tutorial/path-operation-configuration.md @@ -52,7 +52,7 @@ Вы можете добавить параметры `summary` и `description`: -{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *} +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} ## Описание из строк документации { #description-from-docstring } @@ -70,7 +70,7 @@ Вы можете указать описание ответа с помощью параметра `response_description`: -{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *} +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} /// info | Дополнительная информация @@ -78,7 +78,7 @@ /// -/// check +/// check | Проверка OpenAPI указывает, что каждой *операции пути* необходимо описание ответа. From 01e85c03bd992ef933fb1203879cd5a234796f78 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Feb 2026 15:29:27 +0000 Subject: [PATCH 043/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 823465ddf..bca40fa32 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -11,6 +11,10 @@ hide: * ♻️ Re-implement `on_event` in FastAPI for compatibility with the next Starlette, while keeping backwards compatibility. PR [#14851](https://github.com/fastapi/fastapi/pull/14851) by [@tiangolo](https://github.com/tiangolo). +### Translations + +* 🌐 Update translations for ru (update-outdated). PR [#14834](https://github.com/fastapi/fastapi/pull/14834) by [@tiangolo](https://github.com/tiangolo). + ### Internal * 👷 Run tests with lower bound uv sync, upgrade `fastapi[all]` minimum dependencies: `ujson >=5.8.0`, `orjson >=3.9.3`. PR [#14846](https://github.com/fastapi/fastapi/pull/14846) by [@tiangolo](https://github.com/tiangolo). From 19f13ead4cf61376e5bc615ebc8253e2ee4104b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 6 Feb 2026 07:30:48 -0800 Subject: [PATCH 044/195] =?UTF-8?q?=F0=9F=91=B7=20Run=20tests=20with=20Sta?= =?UTF-8?q?rlette=20from=20git=20(#14849)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 70710e340..e314b6a04 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,6 +51,9 @@ jobs: python-version: [ "3.14" ] uv-resolution: - highest + starlette-src: + - starlette-pypi + - starlette-git include: - os: ubuntu-latest python-version: "3.9" @@ -78,11 +81,13 @@ jobs: python-version: "3.14" coverage: coverage uv-resolution: highest + starlette-src: starlette-git fail-fast: false runs-on: ${{ matrix.os }} env: UV_PYTHON: ${{ matrix.python-version }} UV_RESOLUTION: ${{ matrix.uv-resolution }} + STARLETTE_SRC: ${{ matrix.starlette-src }} steps: - name: Dump GitHub context env: @@ -102,10 +107,13 @@ jobs: uv.lock - name: Install Dependencies run: uv sync --no-dev --group tests --extra all + - name: Install Starlette from source + if: matrix.starlette-src == 'starlette-git' + run: uv pip install "git+https://github.com/Kludex/starlette@main" - run: mkdir coverage - name: Test if: matrix.codspeed != 'codspeed' - run: uv run bash scripts/test.sh + run: uv run --no-sync bash scripts/test.sh env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} @@ -117,7 +125,7 @@ jobs: CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} with: mode: simulation - run: uv run coverage run -m pytest tests/ --codspeed + run: uv run --no-sync coverage run -m pytest tests/ --codspeed # Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow - name: Store coverage files if: matrix.coverage == 'coverage' From 201feedd681a26e3ed82628f963ad3a9af72d612 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Feb 2026 15:31:16 +0000 Subject: [PATCH 045/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bca40fa32..2dc6c908d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -17,6 +17,7 @@ hide: ### Internal +* 👷 Run tests with Starlette from git. PR [#14849](https://github.com/fastapi/fastapi/pull/14849) by [@tiangolo](https://github.com/tiangolo). * 👷 Run tests with lower bound uv sync, upgrade `fastapi[all]` minimum dependencies: `ujson >=5.8.0`, `orjson >=3.9.3`. PR [#14846](https://github.com/fastapi/fastapi/pull/14846) by [@tiangolo](https://github.com/tiangolo). ## 0.128.2 From f6cc650a127070ff427ef4d7b4666d304b1cc27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 6 Feb 2026 08:37:37 -0800 Subject: [PATCH 046/195] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20Starlett?= =?UTF-8?q?e=20supported=20version=20range=20to=20`starlette>=3D0.40.0,<1.?= =?UTF-8?q?0.0`=20(#14853)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ae9ae891e..519f45e31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ classifiers = [ "Topic :: Internet :: WWW/HTTP", ] dependencies = [ - "starlette>=0.40.0,<0.51.0", + "starlette>=0.40.0,<1.0.0", "pydantic>=2.7.0", "typing-extensions>=4.8.0", "typing-inspection>=0.4.2", diff --git a/uv.lock b/uv.lock index 435dddec5..88b9f28df 100644 --- a/uv.lock +++ b/uv.lock @@ -1198,7 +1198,7 @@ requires-dist = [ { name = "python-multipart", marker = "extra == 'standard'", specifier = ">=0.0.18" }, { name = "python-multipart", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.0.18" }, { name = "pyyaml", marker = "extra == 'all'", specifier = ">=5.3.1" }, - { name = "starlette", specifier = ">=0.40.0,<0.51.0" }, + { name = "starlette", specifier = ">=0.40.0,<1.0.0" }, { name = "typing-extensions", specifier = ">=4.8.0" }, { name = "typing-inspection", specifier = ">=0.4.2" }, { name = "ujson", marker = "extra == 'all'", specifier = ">=5.8.0" }, From 661cdfb8a465ae8f629b2605fc4a9171f19200fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Feb 2026 16:38:15 +0000 Subject: [PATCH 047/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2dc6c908d..c78265512 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -11,6 +11,10 @@ hide: * ♻️ Re-implement `on_event` in FastAPI for compatibility with the next Starlette, while keeping backwards compatibility. PR [#14851](https://github.com/fastapi/fastapi/pull/14851) by [@tiangolo](https://github.com/tiangolo). +### Upgrades + +* ⬆️ Upgrade Starlette supported version range to `starlette>=0.40.0,<1.0.0`. PR [#14853](https://github.com/fastapi/fastapi/pull/14853) by [@tiangolo](https://github.com/tiangolo). + ### Translations * 🌐 Update translations for ru (update-outdated). PR [#14834](https://github.com/fastapi/fastapi/pull/14834) by [@tiangolo](https://github.com/tiangolo). From 36985f5f25a5a706e5c9bd4630a3c74f45f6bfe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 6 Feb 2026 17:44:11 +0100 Subject: [PATCH 048/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?8.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c78265512..a55a15abd 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.128.3 + ### Refactors * ♻️ Re-implement `on_event` in FastAPI for compatibility with the next Starlette, while keeping backwards compatibility. PR [#14851](https://github.com/fastapi/fastapi/pull/14851) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 99b9630d2..b23e3c42f 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.128.2" +__version__ = "0.128.3" from starlette import status as status From 5a31b37cc7f12d006d7daebdc82b623e25b3ed2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 6 Feb 2026 09:18:30 -0800 Subject: [PATCH 049/195] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20developm?= =?UTF-8?q?ent=20dependencies=20(#14854)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 88 +- uv.lock | 2941 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 2705 insertions(+), 324 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 519f45e31..4895c2d34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,62 +129,62 @@ dev = [ { include-group = "tests" }, { include-group = "docs" }, { include-group = "translations" }, - "playwright>=1.57.0", - "prek==0.2.22", + "playwright >=1.57.0", + "prek >=0.2.22", ] docs = [ { include-group = "docs-tests" }, - "black==25.1.0", - "cairosvg==2.8.2", - "griffe-typingdoc==0.3.0", - "griffe-warnings-deprecated==1.1.0", - "jieba==0.42.1", - "markdown-include-variants==0.0.8", - "mdx-include>=1.4.1,<2.0.0", - "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.21.1", + "black >=25.1.0", + "cairosvg >=2.8.2", + "griffe-typingdoc >=0.3.0", + "griffe-warnings-deprecated >=1.1.0", + "jieba >=0.42.1", + "markdown-include-variants >=0.0.8", + "mdx-include >=1.4.1,<2.0.0", + "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.21.1", ] docs-tests = [ - "httpx>=0.23.0,<1.0.0", - "ruff==0.14.14", + "httpx >=0.23.0,<1.0.0", + "ruff >=0.14.14", ] github-actions = [ - "httpx>=0.27.0,<1.0.0", - "pydantic>=2.5.3,<3.0.0", - "pydantic-settings>=2.1.0,<3.0.0", - "pygithub>=2.3.0,<3.0.0", - "pyyaml>=5.3.1,<7.0.0", - "smokeshow>=0.5.0", + "httpx >=0.27.0,<1.0.0", + "pydantic >=2.5.3,<3.0.0", + "pydantic-settings >=2.1.0,<3.0.0", + "pygithub >=2.3.0,<3.0.0", + "pyyaml >=5.3.1,<7.0.0", + "smokeshow >=0.5.0", ] tests = [ { include-group = "docs-tests" }, - "anyio[trio]>=3.2.1,<5.0.0", - "coverage[toml]>=6.5.0,<8.0", - "dirty-equals==0.9.0", - "flask>=3.0.0,<4.0.0", - "inline-snapshot>=0.21.1", - "mypy==1.14.1", - "pwdlib[argon2]>=0.2.1", - "pyjwt==2.9.0", - "pytest>=7.1.3,<9.0.0", - "pytest-codspeed==4.2.0", - "pyyaml>=5.3.1,<7.0.0", - "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", + "anyio[trio] >=3.2.1,<5.0.0", + "coverage[toml] >=6.5.0,<8.0", + "dirty-equals >=0.9.0", + "flask >=3.0.0,<4.0.0", + "inline-snapshot >=0.21.1", + "mypy >=1.14.1", + "pwdlib[argon2] >=0.2.1", + "pyjwt >=2.9.0", + "pytest >=7.1.3,<9.0.0", + "pytest-codspeed >=4.2.0", + "pyyaml >=5.3.1,<7.0.0", + "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.46", - "pydantic-ai==0.4.10", - "pygithub==2.8.1", + "gitpython >=3.1.46", + "pydantic-ai >=0.4.10", + "pygithub >=2.8.1", ] [tool.pdm] diff --git a/uv.lock b/uv.lock index 88b9f28df..9a807f8b4 100644 --- a/uv.lock +++ b/uv.lock @@ -31,6 +31,165 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/78/eb55fabaab41abc53f52c0918a9a8c0f747807e5306273f51120fd695957/ag_ui_protocol-0.1.10-py3-none-any.whl", hash = "sha256:c81e6981f30aabdf97a7ee312bfd4df0cd38e718d9fc10019c7d438128b93ab5", size = 7889, upload-time = "2025-11-06T15:17:15.325Z" }, ] +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/d6/5aec9313ee6ea9c7cde8b891b69f4ff4001416867104580670a31daeba5b/aiohttp-3.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a372fd5afd301b3a89582817fdcdb6c34124787c70dbcc616f259013e7eef7", size = 738950, upload-time = "2026-01-03T17:29:13.002Z" }, + { url = "https://files.pythonhosted.org/packages/68/03/8fa90a7e6d11ff20a18837a8e2b5dd23db01aabc475aa9271c8ad33299f5/aiohttp-3.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:147e422fd1223005c22b4fe080f5d93ced44460f5f9c105406b753612b587821", size = 496099, upload-time = "2026-01-03T17:29:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/b81f744d402510a8366b74eb420fc0cc1170d0c43daca12d10814df85f10/aiohttp-3.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859bd3f2156e81dd01432f5849fc73e2243d4a487c4fd26609b1299534ee1845", size = 491072, upload-time = "2026-01-03T17:29:16.922Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e1/56d1d1c0dd334cd203dd97706ce004c1aa24b34a813b0b8daf3383039706/aiohttp-3.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dca68018bf48c251ba17c72ed479f4dafe9dbd5a73707ad8d28a38d11f3d42af", size = 1671588, upload-time = "2026-01-03T17:29:18.539Z" }, + { url = "https://files.pythonhosted.org/packages/5f/34/8d7f962604f4bc2b4e39eb1220dac7d4e4cba91fb9ba0474b4ecd67db165/aiohttp-3.13.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fee0c6bc7db1de362252affec009707a17478a00ec69f797d23ca256e36d5940", size = 1640334, upload-time = "2026-01-03T17:29:21.028Z" }, + { url = "https://files.pythonhosted.org/packages/94/1d/fcccf2c668d87337ddeef9881537baee13c58d8f01f12ba8a24215f2b804/aiohttp-3.13.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c048058117fd649334d81b4b526e94bde3ccaddb20463a815ced6ecbb7d11160", size = 1722656, upload-time = "2026-01-03T17:29:22.531Z" }, + { url = "https://files.pythonhosted.org/packages/aa/98/c6f3b081c4c606bc1e5f2ec102e87d6411c73a9ef3616fea6f2d5c98c062/aiohttp-3.13.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:215a685b6fbbfcf71dfe96e3eba7a6f58f10da1dfdf4889c7dd856abe430dca7", size = 1817625, upload-time = "2026-01-03T17:29:24.276Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c0/cfcc3d2e11b477f86e1af2863f3858c8850d751ce8dc39c4058a072c9e54/aiohttp-3.13.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2c184bb1fe2cbd2cefba613e9db29a5ab559323f994b6737e370d3da0ac455", size = 1672604, upload-time = "2026-01-03T17:29:26.099Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/6b4ffcbcac4c6a5d041343a756f34a6dd26174ae07f977a64fe028dda5b0/aiohttp-3.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75ca857eba4e20ce9f546cd59c7007b33906a4cd48f2ff6ccf1ccfc3b646f279", size = 1554370, upload-time = "2026-01-03T17:29:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f0/e3ddfa93f17d689dbe014ba048f18e0c9f9b456033b70e94349a2e9048be/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81e97251d9298386c2b7dbeb490d3d1badbdc69107fb8c9299dd04eb39bddc0e", size = 1642023, upload-time = "2026-01-03T17:29:30.002Z" }, + { url = "https://files.pythonhosted.org/packages/eb/45/c14019c9ec60a8e243d06d601b33dcc4fd92379424bde3021725859d7f99/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0e2d366af265797506f0283487223146af57815b388623f0357ef7eac9b209d", size = 1649680, upload-time = "2026-01-03T17:29:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fd/09c9451dae5aa5c5ed756df95ff9ef549d45d4be663bafd1e4954fd836f0/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4e239d501f73d6db1522599e14b9b321a7e3b1de66ce33d53a765d975e9f4808", size = 1692407, upload-time = "2026-01-03T17:29:33.392Z" }, + { url = "https://files.pythonhosted.org/packages/a6/81/938bc2ec33c10efd6637ccb3d22f9f3160d08e8f3aa2587a2c2d5ab578eb/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0db318f7a6f065d84cb1e02662c526294450b314a02bd9e2a8e67f0d8564ce40", size = 1543047, upload-time = "2026-01-03T17:29:34.855Z" }, + { url = "https://files.pythonhosted.org/packages/f7/23/80488ee21c8d567c83045e412e1d9b7077d27171591a4eb7822586e8c06a/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bfc1cc2fe31a6026a8a88e4ecfb98d7f6b1fec150cfd708adbfd1d2f42257c29", size = 1715264, upload-time = "2026-01-03T17:29:36.389Z" }, + { url = "https://files.pythonhosted.org/packages/e2/83/259a8da6683182768200b368120ab3deff5370bed93880fb9a3a86299f34/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af71fff7bac6bb7508956696dce8f6eec2bbb045eceb40343944b1ae62b5ef11", size = 1657275, upload-time = "2026-01-03T17:29:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4f/2c41f800a0b560785c10fb316216ac058c105f9be50bdc6a285de88db625/aiohttp-3.13.3-cp310-cp310-win32.whl", hash = "sha256:37da61e244d1749798c151421602884db5270faf479cf0ef03af0ff68954c9dd", size = 434053, upload-time = "2026-01-03T17:29:40.074Z" }, + { url = "https://files.pythonhosted.org/packages/80/df/29cd63c7ecfdb65ccc12f7d808cac4fa2a19544660c06c61a4a48462de0c/aiohttp-3.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:7e63f210bc1b57ef699035f2b4b6d9ce096b5914414a49b0997c839b2bd2223c", size = 456687, upload-time = "2026-01-03T17:29:41.819Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, + { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, + { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, + { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, + { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, + { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, + { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, + { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, + { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808", size = 737238, upload-time = "2026-01-03T17:31:17.909Z" }, + { url = "https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415", size = 492292, upload-time = "2026-01-03T17:31:19.919Z" }, + { url = "https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f", size = 493021, upload-time = "2026-01-03T17:31:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6", size = 1717263, upload-time = "2026-01-03T17:31:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687", size = 1669107, upload-time = "2026-01-03T17:31:25.334Z" }, + { url = "https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26", size = 1760196, upload-time = "2026-01-03T17:31:27.394Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a", size = 1843591, upload-time = "2026-01-03T17:31:29.238Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1", size = 1720277, upload-time = "2026-01-03T17:31:31.053Z" }, + { url = "https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25", size = 1548575, upload-time = "2026-01-03T17:31:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603", size = 1679455, upload-time = "2026-01-03T17:31:34.76Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a", size = 1687417, upload-time = "2026-01-03T17:31:36.699Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926", size = 1729968, upload-time = "2026-01-03T17:31:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba", size = 1545690, upload-time = "2026-01-03T17:31:40.57Z" }, + { url = "https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c", size = 1746390, upload-time = "2026-01-03T17:31:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43", size = 1702188, upload-time = "2026-01-03T17:31:44.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1", size = 433126, upload-time = "2026-01-03T17:31:47.463Z" }, + { url = "https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984", size = 459128, upload-time = "2026-01-03T17:31:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c", size = 769512, upload-time = "2026-01-03T17:31:51.134Z" }, + { url = "https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592", size = 506444, upload-time = "2026-01-03T17:31:52.85Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f", size = 510798, upload-time = "2026-01-03T17:31:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29", size = 1868835, upload-time = "2026-01-03T17:31:56.733Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc", size = 1720486, upload-time = "2026-01-03T17:31:58.65Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2", size = 1847951, upload-time = "2026-01-03T17:32:00.989Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587", size = 1941001, upload-time = "2026-01-03T17:32:03.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8", size = 1797246, upload-time = "2026-01-03T17:32:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632", size = 1627131, upload-time = "2026-01-03T17:32:07.607Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64", size = 1795196, upload-time = "2026-01-03T17:32:09.59Z" }, + { url = "https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0", size = 1782841, upload-time = "2026-01-03T17:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56", size = 1795193, upload-time = "2026-01-03T17:32:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72", size = 1621979, upload-time = "2026-01-03T17:32:15.965Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df", size = 1822193, upload-time = "2026-01-03T17:32:18.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801, upload-time = "2026-01-03T17:32:20.25Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523, upload-time = "2026-01-03T17:32:22.215Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694, upload-time = "2026-01-03T17:32:24.546Z" }, + { url = "https://files.pythonhosted.org/packages/bf/79/446655656861d3e7e2c32bfcf160c7aa9e9dc63776a691b124dba65cdd77/aiohttp-3.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31a83ea4aead760dfcb6962efb1d861db48c34379f2ff72db9ddddd4cda9ea2e", size = 741433, upload-time = "2026-01-03T17:32:26.453Z" }, + { url = "https://files.pythonhosted.org/packages/cb/49/773c4b310b5140d2fb5e79bb0bf40b7b41dad80a288ca1a8759f5f72bda9/aiohttp-3.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:988a8c5e317544fdf0d39871559e67b6341065b87fceac641108c2096d5506b7", size = 497332, upload-time = "2026-01-03T17:32:28.37Z" }, + { url = "https://files.pythonhosted.org/packages/bc/31/1dcbc4b83a4e6f76a0ad883f07f21ffbfe29750c89db97381701508c9f45/aiohttp-3.13.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b174f267b5cfb9a7dba9ee6859cecd234e9a681841eb85068059bc867fb8f02", size = 492365, upload-time = "2026-01-03T17:32:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b5/b50657496c8754482cd7964e50aaf3aa84b3db61ed45daec4c1aec5b94b4/aiohttp-3.13.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:947c26539750deeaee933b000fb6517cc770bbd064bad6033f1cff4803881e43", size = 1660440, upload-time = "2026-01-03T17:32:32.586Z" }, + { url = "https://files.pythonhosted.org/packages/2a/73/9b69e5139d89d75127569298931444ad78ea86a5befd5599780b1e9a6880/aiohttp-3.13.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9ebf57d09e131f5323464bd347135a88622d1c0976e88ce15b670e7ad57e4bd6", size = 1632740, upload-time = "2026-01-03T17:32:34.793Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fe/3ea9b5af694b4e3aec0d0613a806132ca744747146fca68e96bf056f61a7/aiohttp-3.13.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4ae5b5a0e1926e504c81c5b84353e7a5516d8778fbbff00429fe7b05bb25cbce", size = 1719782, upload-time = "2026-01-03T17:32:37.737Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/46b3b06e60851cbb71efb0f79a3267279cbef7b12c58e68a1e897f269cca/aiohttp-3.13.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ba0eea45eb5cc3172dbfc497c066f19c41bac70963ea1a67d51fc92e4cf9a80", size = 1813527, upload-time = "2026-01-03T17:32:39.973Z" }, + { url = "https://files.pythonhosted.org/packages/36/23/71ceb78c769ed65fe4c697692de232b63dab399210678d2b00961ccb0619/aiohttp-3.13.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bae5c2ed2eae26cc382020edad80d01f36cb8e746da40b292e68fec40421dc6a", size = 1661268, upload-time = "2026-01-03T17:32:42.082Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/86e929523d955e85ebab7c0e2b9e0cb63604cfc27dc3280e10d0063cf682/aiohttp-3.13.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a60e60746623925eab7d25823329941aee7242d559baa119ca2b253c88a7bd6", size = 1552742, upload-time = "2026-01-03T17:32:44.622Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/3f5987cba1bab6bd151f0d97aa60f0ce04d3c83316692a6bb6ba2fb69f92/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e50a2e1404f063427c9d027378472316201a2290959a295169bcf25992d04558", size = 1632918, upload-time = "2026-01-03T17:32:46.749Z" }, + { url = "https://files.pythonhosted.org/packages/be/2c/7e1e85121f2e31ee938cb83a8f32dfafd4908530c10fabd6d46761c12ac7/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9a9dc347e5a3dc7dfdbc1f82da0ef29e388ddb2ed281bfce9dd8248a313e62b7", size = 1644446, upload-time = "2026-01-03T17:32:49.063Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/ce6133d423ad0e8ca976a7c848f7146bca3520eea4ccf6b95e2d077c9d20/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b46020d11d23fe16551466c77823df9cc2f2c1e63cc965daf67fa5eec6ca1877", size = 1689487, upload-time = "2026-01-03T17:32:51.113Z" }, + { url = "https://files.pythonhosted.org/packages/50/f7/ff7a27c15603d460fd1366b3c22054f7ae4fa9310aca40b43bde35867fcd/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:69c56fbc1993fa17043e24a546959c0178fe2b5782405ad4559e6c13975c15e3", size = 1540715, upload-time = "2026-01-03T17:32:53.38Z" }, + { url = "https://files.pythonhosted.org/packages/17/02/053f11346e5b962e6d8a1c4f8c70c29d5970a1b4b8e7894c68e12c27a57f/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b99281b0704c103d4e11e72a76f1b543d4946fea7dd10767e7e1b5f00d4e5704", size = 1711835, upload-time = "2026-01-03T17:32:56.088Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/9b9761ddf276fd6708d13720197cbac19b8d67ecfa9116777924056cfcaa/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:40c5e40ecc29ba010656c18052b877a1c28f84344825efa106705e835c28530f", size = 1649593, upload-time = "2026-01-03T17:32:58.181Z" }, + { url = "https://files.pythonhosted.org/packages/ae/72/5d817e9ea218acae12a5e3b9ad1178cf0c12fc3570c0b47eea2daf95f9ea/aiohttp-3.13.3-cp39-cp39-win32.whl", hash = "sha256:56339a36b9f1fc708260c76c87e593e2afb30d26de9ae1eb445b5e051b98a7a1", size = 434831, upload-time = "2026-01-03T17:33:00.577Z" }, + { url = "https://files.pythonhosted.org/packages/39/cb/22659d9bf3149b7a2927bc2769cc9c8f8f5a80eba098398e03c199a43a85/aiohttp-3.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:c6b8568a3bb5819a0ad087f16d40e5a3fb6099f39ea1d5625a3edc1e923fc538", size = 457697, upload-time = "2026-01-03T17:33:03.167Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -173,6 +332,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, ] +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + [[package]] name = "attrs" version = "25.4.0" @@ -182,6 +350,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] +[[package]] +name = "authlib" +version = "1.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/dc/ed1681bf1339dd6ea1ce56136bad4baabc6f7ad466e375810702b0237047/authlib-1.6.7.tar.gz", hash = "sha256:dbf10100011d1e1b34048c9d120e83f13b35d69a826ae762b93d2fb5aafc337b", size = 164950, upload-time = "2026-02-06T14:04:14.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/00/3ed12264094ec91f534fae429945efbaa9f8c666f3aa7061cc3b2a26a0cd/authlib-1.6.7-py2.py3-none-any.whl", hash = "sha256:c637340d9a02789d2efa1d003a7437d10d3e565237bcb5fcbc6c134c7b95bab0", size = 244115, upload-time = "2026-02-06T14:04:12.141Z" }, +] + [[package]] name = "babel" version = "2.18.0" @@ -191,6 +371,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] +[[package]] +name = "backports-tarfile" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406, upload-time = "2024-05-28T17:01:54.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" }, +] + [[package]] name = "backrefs" version = "6.1" @@ -206,43 +395,106 @@ wheels = [ ] [[package]] -name = "black" -version = "25.1.0" +name = "beartype" +version = "0.22.9" source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, +] + +[[package]] +name = "black" +version = "25.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, + { name = "mypy-extensions", marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pathspec", marker = "python_full_version < '3.10'" }, { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "pytokens", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/ad/33adf4708633d047950ff2dfdea2e215d84ac50ef95aff14a614e4b6e9b2/black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", size = 655669, upload-time = "2025-11-10T01:53:50.558Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" }, - { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" }, - { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" }, - { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, - { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, - { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, - { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, - { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, - { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, - { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b6/ae7507470a4830dbbfe875c701e84a4a5fb9183d1497834871a715716a92/black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", size = 1628593, upload-time = "2025-01-29T05:37:23.672Z" }, - { url = "https://files.pythonhosted.org/packages/24/c1/ae36fa59a59f9363017ed397750a0cd79a470490860bc7713967d89cdd31/black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f", size = 1460000, upload-time = "2025-01-29T05:37:25.829Z" }, - { url = "https://files.pythonhosted.org/packages/ac/b6/98f832e7a6c49aa3a464760c67c7856363aa644f2f3c74cf7d624168607e/black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", size = 1765963, upload-time = "2025-01-29T04:18:38.116Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e9/2cb0a017eb7024f70e0d2e9bdb8c5a5b078c5740c7f8816065d06f04c557/black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", size = 1419419, upload-time = "2025-01-29T04:18:30.191Z" }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d2/6caccbc96f9311e8ec3378c296d4f4809429c43a6cd2394e3c390e86816d/black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e", size = 1743501, upload-time = "2025-11-10T01:59:06.202Z" }, + { url = "https://files.pythonhosted.org/packages/69/35/b986d57828b3f3dccbf922e2864223197ba32e74c5004264b1c62bc9f04d/black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0", size = 1597308, upload-time = "2025-11-10T01:57:58.633Z" }, + { url = "https://files.pythonhosted.org/packages/39/8e/8b58ef4b37073f52b64a7b2dd8c9a96c84f45d6f47d878d0aa557e9a2d35/black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37", size = 1656194, upload-time = "2025-11-10T01:57:10.909Z" }, + { url = "https://files.pythonhosted.org/packages/8d/30/9c2267a7955ecc545306534ab88923769a979ac20a27cf618d370091e5dd/black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03", size = 1347996, upload-time = "2025-11-10T01:57:22.391Z" }, + { url = "https://files.pythonhosted.org/packages/c4/62/d304786b75ab0c530b833a89ce7d997924579fb7484ecd9266394903e394/black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a", size = 1727891, upload-time = "2025-11-10T02:01:40.507Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/ffe8a006aa522c9e3f430e7b93568a7b2163f4b3f16e8feb6d8c3552761a/black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170", size = 1581875, upload-time = "2025-11-10T01:57:51.192Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7c8bda3108d0bb57387ac41b4abb5c08782b26da9f9c4421ef6694dac01a/black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc", size = 1642716, upload-time = "2025-11-10T01:56:51.589Z" }, + { url = "https://files.pythonhosted.org/packages/34/b9/f17dea34eecb7cc2609a89627d480fb6caea7b86190708eaa7eb15ed25e7/black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e", size = 1352904, upload-time = "2025-11-10T01:59:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/7f/12/5c35e600b515f35ffd737da7febdb2ab66bb8c24d88560d5e3ef3d28c3fd/black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac", size = 1772831, upload-time = "2025-11-10T02:03:47Z" }, + { url = "https://files.pythonhosted.org/packages/1a/75/b3896bec5a2bb9ed2f989a970ea40e7062f8936f95425879bbe162746fe5/black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96", size = 1608520, upload-time = "2025-11-10T01:58:46.895Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b5/2bfc18330eddbcfb5aab8d2d720663cd410f51b2ed01375f5be3751595b0/black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd", size = 1682719, upload-time = "2025-11-10T01:56:55.24Z" }, + { url = "https://files.pythonhosted.org/packages/96/fb/f7dc2793a22cdf74a72114b5ed77fe3349a2e09ef34565857a2f917abdf2/black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409", size = 1362684, upload-time = "2025-11-10T01:57:07.639Z" }, + { url = "https://files.pythonhosted.org/packages/ad/47/3378d6a2ddefe18553d1115e36aea98f4a90de53b6a3017ed861ba1bd3bc/black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b", size = 1772446, upload-time = "2025-11-10T02:02:16.181Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4b/0f00bfb3d1f7e05e25bfc7c363f54dc523bb6ba502f98f4ad3acf01ab2e4/black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd", size = 1607983, upload-time = "2025-11-10T02:02:52.502Z" }, + { url = "https://files.pythonhosted.org/packages/99/fe/49b0768f8c9ae57eb74cc10a1f87b4c70453551d8ad498959721cc345cb7/black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993", size = 1682481, upload-time = "2025-11-10T01:57:12.35Z" }, + { url = "https://files.pythonhosted.org/packages/55/17/7e10ff1267bfa950cc16f0a411d457cdff79678fbb77a6c73b73a5317904/black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c", size = 1363869, upload-time = "2025-11-10T01:58:24.608Z" }, + { url = "https://files.pythonhosted.org/packages/67/c0/cc865ce594d09e4cd4dfca5e11994ebb51604328489f3ca3ae7bb38a7db5/black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170", size = 1771358, upload-time = "2025-11-10T02:03:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/37/77/4297114d9e2fd2fc8ab0ab87192643cd49409eb059e2940391e7d2340e57/black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545", size = 1612902, upload-time = "2025-11-10T01:59:33.382Z" }, + { url = "https://files.pythonhosted.org/packages/de/63/d45ef97ada84111e330b2b2d45e1dd163e90bd116f00ac55927fb6bf8adb/black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda", size = 1680571, upload-time = "2025-11-10T01:57:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4b/5604710d61cdff613584028b4cb4607e56e148801ed9b38ee7970799dab6/black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664", size = 1382599, upload-time = "2025-11-10T01:57:57.427Z" }, + { url = "https://files.pythonhosted.org/packages/d5/9a/5b2c0e3215fe748fcf515c2dd34658973a1210bf610e24de5ba887e4f1c8/black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06", size = 1743063, upload-time = "2025-11-10T02:02:43.175Z" }, + { url = "https://files.pythonhosted.org/packages/a1/20/245164c6efc27333409c62ba54dcbfbe866c6d1957c9a6c0647786e950da/black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2", size = 1596867, upload-time = "2025-11-10T02:00:17.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6f/1a3859a7da205f3d50cf3a8bec6bdc551a91c33ae77a045bb24c1f46ab54/black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc", size = 1655678, upload-time = "2025-11-10T01:57:09.028Z" }, + { url = "https://files.pythonhosted.org/packages/56/1a/6dec1aeb7be90753d4fcc273e69bc18bfd34b353223ed191da33f7519410/black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc", size = 1347452, upload-time = "2025-11-10T01:57:01.871Z" }, + { url = "https://files.pythonhosted.org/packages/00/5d/aed32636ed30a6e7f9efd6ad14e2a0b0d687ae7c8c7ec4e4a557174b895c/black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", size = 204918, upload-time = "2025-11-10T01:53:48.917Z" }, +] + +[[package]] +name = "black" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +dependencies = [ + { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mypy-extensions", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pathspec", marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytokens", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/88/560b11e521c522440af991d46848a2bde64b5f7202ec14e1f46f9509d328/black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58", size = 658785, upload-time = "2026-01-18T04:50:11.993Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/1b/523329e713f965ad0ea2b7a047eeb003007792a0353622ac7a8cb2ee6fef/black-26.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ca699710dece84e3ebf6e92ee15f5b8f72870ef984bf944a57a777a48357c168", size = 1849661, upload-time = "2026-01-18T04:59:12.425Z" }, + { url = "https://files.pythonhosted.org/packages/14/82/94c0640f7285fa71c2f32879f23e609dd2aa39ba2641f395487f24a578e7/black-26.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e8e75dabb6eb83d064b0db46392b25cabb6e784ea624219736e8985a6b3675d", size = 1689065, upload-time = "2026-01-18T04:59:13.993Z" }, + { url = "https://files.pythonhosted.org/packages/f0/78/474373cbd798f9291ed8f7107056e343fd39fef42de4a51c7fd0d360840c/black-26.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb07665d9a907a1a645ee41a0df8a25ffac8ad9c26cdb557b7b88eeeeec934e0", size = 1751502, upload-time = "2026-01-18T04:59:15.971Z" }, + { url = "https://files.pythonhosted.org/packages/29/89/59d0e350123f97bc32c27c4d79563432d7f3530dca2bff64d855c178af8b/black-26.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:7ed300200918147c963c87700ccf9966dceaefbbb7277450a8d646fc5646bf24", size = 1400102, upload-time = "2026-01-18T04:59:17.8Z" }, + { url = "https://files.pythonhosted.org/packages/e1/bc/5d866c7ae1c9d67d308f83af5462ca7046760158bbf142502bad8f22b3a1/black-26.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c5b7713daea9bf943f79f8c3b46f361cc5229e0e604dcef6a8bb6d1c37d9df89", size = 1207038, upload-time = "2026-01-18T04:59:19.543Z" }, + { url = "https://files.pythonhosted.org/packages/30/83/f05f22ff13756e1a8ce7891db517dbc06200796a16326258268f4658a745/black-26.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3cee1487a9e4c640dc7467aaa543d6c0097c391dc8ac74eb313f2fbf9d7a7cb5", size = 1831956, upload-time = "2026-01-18T04:59:21.38Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f2/b2c570550e39bedc157715e43927360312d6dd677eed2cc149a802577491/black-26.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d62d14ca31c92adf561ebb2e5f2741bf8dea28aef6deb400d49cca011d186c68", size = 1672499, upload-time = "2026-01-18T04:59:23.257Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d7/990d6a94dc9e169f61374b1c3d4f4dd3037e93c2cc12b6f3b12bc663aa7b/black-26.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1dafbbaa3b1ee8b4550a84425aac8874e5f390200f5502cf3aee4a2acb2f14", size = 1735431, upload-time = "2026-01-18T04:59:24.729Z" }, + { url = "https://files.pythonhosted.org/packages/36/1c/cbd7bae7dd3cb315dfe6eeca802bb56662cc92b89af272e014d98c1f2286/black-26.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:101540cb2a77c680f4f80e628ae98bd2bd8812fb9d72ade4f8995c5ff019e82c", size = 1400468, upload-time = "2026-01-18T04:59:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/59/b1/9fe6132bb2d0d1f7094613320b56297a108ae19ecf3041d9678aec381b37/black-26.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:6f3977a16e347f1b115662be07daa93137259c711e526402aa444d7a88fdc9d4", size = 1207332, upload-time = "2026-01-18T04:59:28.711Z" }, + { url = "https://files.pythonhosted.org/packages/f5/13/710298938a61f0f54cdb4d1c0baeb672c01ff0358712eddaf29f76d32a0b/black-26.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6eeca41e70b5f5c84f2f913af857cf2ce17410847e1d54642e658e078da6544f", size = 1878189, upload-time = "2026-01-18T04:59:30.682Z" }, + { url = "https://files.pythonhosted.org/packages/79/a6/5179beaa57e5dbd2ec9f1c64016214057b4265647c62125aa6aeffb05392/black-26.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd39eef053e58e60204f2cdf059e2442e2eb08f15989eefe259870f89614c8b6", size = 1700178, upload-time = "2026-01-18T04:59:32.387Z" }, + { url = "https://files.pythonhosted.org/packages/8c/04/c96f79d7b93e8f09d9298b333ca0d31cd9b2ee6c46c274fd0f531de9dc61/black-26.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9459ad0d6cd483eacad4c6566b0f8e42af5e8b583cee917d90ffaa3778420a0a", size = 1777029, upload-time = "2026-01-18T04:59:33.767Z" }, + { url = "https://files.pythonhosted.org/packages/49/f9/71c161c4c7aa18bdda3776b66ac2dc07aed62053c7c0ff8bbda8c2624fe2/black-26.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a19915ec61f3a8746e8b10adbac4a577c6ba9851fa4a9e9fbfbcf319887a5791", size = 1406466, upload-time = "2026-01-18T04:59:35.177Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8b/a7b0f974e473b159d0ac1b6bcefffeb6bec465898a516ee5cc989503cbc7/black-26.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:643d27fb5facc167c0b1b59d0315f2674a6e950341aed0fc05cf307d22bf4954", size = 1216393, upload-time = "2026-01-18T04:59:37.18Z" }, + { url = "https://files.pythonhosted.org/packages/79/04/fa2f4784f7237279332aa735cdfd5ae2e7730db0072fb2041dadda9ae551/black-26.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba1d768fbfb6930fc93b0ecc32a43d8861ded16f47a40f14afa9bb04ab93d304", size = 1877781, upload-time = "2026-01-18T04:59:39.054Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ad/5a131b01acc0e5336740a039628c0ab69d60cf09a2c87a4ec49f5826acda/black-26.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b807c240b64609cb0e80d2200a35b23c7df82259f80bef1b2c96eb422b4aac9", size = 1699670, upload-time = "2026-01-18T04:59:41.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/7c/b05f22964316a52ab6b4265bcd52c0ad2c30d7ca6bd3d0637e438fc32d6e/black-26.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1de0f7d01cc894066a1153b738145b194414cc6eeaad8ef4397ac9abacf40f6b", size = 1775212, upload-time = "2026-01-18T04:59:42.545Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a3/e8d1526bea0446e040193185353920a9506eab60a7d8beb062029129c7d2/black-26.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:91a68ae46bf07868963671e4d05611b179c2313301bd756a89ad4e3b3db2325b", size = 1409953, upload-time = "2026-01-18T04:59:44.357Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5a/d62ebf4d8f5e3a1daa54adaab94c107b57be1b1a2f115a0249b41931e188/black-26.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:be5e2fe860b9bd9edbf676d5b60a9282994c03fbbd40fe8f5e75d194f96064ca", size = 1217707, upload-time = "2026-01-18T04:59:45.719Z" }, + { url = "https://files.pythonhosted.org/packages/6a/83/be35a175aacfce4b05584ac415fd317dd6c24e93a0af2dcedce0f686f5d8/black-26.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc8c71656a79ca49b8d3e2ce8103210c9481c57798b48deeb3a8bb02db5f115", size = 1871864, upload-time = "2026-01-18T04:59:47.586Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f5/d33696c099450b1274d925a42b7a030cd3ea1f56d72e5ca8bbed5f52759c/black-26.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b22b3810451abe359a964cc88121d57f7bce482b53a066de0f1584988ca36e79", size = 1701009, upload-time = "2026-01-18T04:59:49.443Z" }, + { url = "https://files.pythonhosted.org/packages/1b/87/670dd888c537acb53a863bc15abbd85b22b429237d9de1b77c0ed6b79c42/black-26.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53c62883b3f999f14e5d30b5a79bd437236658ad45b2f853906c7cbe79de00af", size = 1767806, upload-time = "2026-01-18T04:59:50.769Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9c/cd3deb79bfec5bcf30f9d2100ffeec63eecce826eb63e3961708b9431ff1/black-26.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:f016baaadc423dc960cdddf9acae679e71ee02c4c341f78f3179d7e4819c095f", size = 1433217, upload-time = "2026-01-18T04:59:52.218Z" }, + { url = "https://files.pythonhosted.org/packages/4e/29/f3be41a1cf502a283506f40f5d27203249d181f7a1a2abce1c6ce188035a/black-26.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:66912475200b67ef5a0ab665011964bf924745103f51977a78b4fb92a9fc1bf0", size = 1245773, upload-time = "2026-01-18T04:59:54.457Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3d/51bdb3ecbfadfaf825ec0c75e1de6077422b4afa2091c6c9ba34fbfc0c2d/black-26.1.0-py3-none-any.whl", hash = "sha256:1054e8e47ebd686e078c0bb0eaf31e6ce69c966058d122f2c0c950311f9f3ede", size = 204010, upload-time = "2026-01-18T04:50:09.978Z" }, ] [[package]] @@ -283,6 +535,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/a8/95656f91b795eb47b73a00d36c51c7a5729eafa632c7348caa068ff63e50/botocore-1.42.43-py3-none-any.whl", hash = "sha256:1c0e30f62e274978ac3bcab253e3a859febea634b72b5e343589db7d17f83cd6", size = 14610179, upload-time = "2026-02-05T20:31:32.727Z" }, ] +[[package]] +name = "cachetools" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/af/df70e9b65bc77a1cbe0768c0aa4617147f30f8306ded98c1744bcdc0ae1e/cachetools-7.0.0.tar.gz", hash = "sha256:a9abf18ff3b86c7d05b27ead412e235e16ae045925e531fae38d5fada5ed5b08", size = 35796, upload-time = "2026-02-01T18:59:47.411Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/df/2dd32cce20cbcf6f2ec456b58d44368161ad28320729f64e5e1d5d7bd0ae/cachetools-7.0.0-py3-none-any.whl", hash = "sha256:d52fef60e6e964a1969cfb61ccf6242a801b432790fe520d78720d757c81cbd2", size = 13487, upload-time = "2026-02-01T18:59:45.981Z" }, +] + [[package]] name = "cairocffi" version = "1.7.1" @@ -303,7 +564,8 @@ dependencies = [ { name = "cairocffi" }, { name = "cssselect2" }, { name = "defusedxml" }, - { name = "pillow" }, + { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "tinycss2", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "tinycss2", version = "1.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] @@ -552,6 +814,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + [[package]] name = "cohere" version = "5.20.4" @@ -808,6 +1079,19 @@ toml = [ { name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" }, ] +[[package]] +name = "croniter" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, + { name = "pytz", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/2f/44d1ae153a0e27be56be43465e5cb39b9650c781e001e7864389deb25090/croniter-6.0.0.tar.gz", hash = "sha256:37c504b313956114a983ece2c2b07790b1f1094fe9d81cc94739214748255577", size = 64481, upload-time = "2024-12-17T17:17:47.32Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/4b/290b4c3efd6417a8b0c284896de19b1d5855e6dbdb97d2a35e68fa42de85/croniter-6.0.0-py2.py3-none-any.whl", hash = "sha256:2f878c3856f17896979b2a4379ba1f09c83e374931ea15cc835c5dd2eee9b368", size = 25468, upload-time = "2024-12-17T17:17:45.359Z" }, +] + [[package]] name = "cross-web" version = "0.4.1" @@ -903,6 +1187,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/c0/9f59d2ebd9d585e1681c51767eb138bcd9d0ea770f6fc003cd875c7f5e62/cyclic-1.0.0-py3-none-any.whl", hash = "sha256:32d8181d7698f426bce6f14f4c3921ef95b6a84af9f96192b59beb05bc00c3ed", size = 2547, upload-time = "2018-09-26T16:47:05.609Z" }, ] +[[package]] +name = "cyclopts" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs", marker = "python_full_version >= '3.10'" }, + { name = "docstring-parser", marker = "python_full_version >= '3.10'" }, + { name = "rich", marker = "python_full_version >= '3.10'" }, + { name = "rich-rst", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/93/6085aa89c3fff78a5180987354538d72e43b0db27e66a959302d0c07821a/cyclopts-4.5.1.tar.gz", hash = "sha256:fadc45304763fd9f5d6033727f176898d17a1778e194436964661a005078a3dd", size = 162075, upload-time = "2026-01-25T15:23:54.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/7c/996760c30f1302704af57c66ff2d723f7d656d0d0b93563b5528a51484bb/cyclopts-4.5.1-py3-none-any.whl", hash = "sha256:0642c93601e554ca6b7b9abd81093847ea4448b2616280f2a0952416574e8c7a", size = 199807, upload-time = "2026-01-25T15:23:55.219Z" }, +] + [[package]] name = "defusedxml" version = "0.7.1" @@ -914,11 +1215,20 @@ wheels = [ [[package]] name = "dirty-equals" -version = "0.9.0" +version = "0.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/99/133892f401ced5a27e641a473c547d5fbdb39af8f85dac8a9d633ea3e7a7/dirty_equals-0.9.0.tar.gz", hash = "sha256:17f515970b04ed7900b733c95fd8091f4f85e52f1fb5f268757f25c858eb1f7b", size = 50412, upload-time = "2025-01-11T23:23:40.491Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/1d/c5913ac9d6615515a00f4bdc71356d302437cb74ff2e9aaccd3c14493b78/dirty_equals-0.11.tar.gz", hash = "sha256:f4ac74ee88f2d11e2fa0f65eb30ee4f07105c5f86f4dc92b09eb1138775027c3", size = 128067, upload-time = "2025-11-17T01:51:24.451Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/0c/03cc99bf3b6328604b10829de3460f2b2ad3373200c45665c38508e550c6/dirty_equals-0.9.0-py3-none-any.whl", hash = "sha256:ff4d027f5cfa1b69573af00f7ba9043ea652dbdce3fe5cbe828e478c7346db9c", size = 28226, upload-time = "2025-01-11T23:23:37.489Z" }, + { url = "https://files.pythonhosted.org/packages/bb/8d/dbff05239043271dbeace563a7686212a3dd517864a35623fe4d4a64ca19/dirty_equals-0.11-py3-none-any.whl", hash = "sha256:b1d7093273fc2f9be12f443a8ead954ef6daaf6746fd42ef3a5616433ee85286", size = 28051, upload-time = "2025-11-17T01:51:22.849Z" }, +] + +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, ] [[package]] @@ -964,6 +1274,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, ] +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + [[package]] name = "email-validator" version = "2.3.0" @@ -1008,6 +1327,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] +[[package]] +name = "fakeredis" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "redis", marker = "python_full_version >= '3.10'" }, + { name = "sortedcontainers", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/f9/57464119936414d60697fcbd32f38909bb5688b616ae13de6e98384433e0/fakeredis-2.33.0.tar.gz", hash = "sha256:d7bc9a69d21df108a6451bbffee23b3eba432c21a654afc7ff2d295428ec5770", size = 175187, upload-time = "2025-12-16T19:45:52.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/78/a850fed8aeef96d4a99043c90b818b2ed5419cd5b24a4049fd7cfb9f1471/fakeredis-2.33.0-py3-none-any.whl", hash = "sha256:de535f3f9ccde1c56672ab2fdd6a8efbc4f2619fc2f1acc87b8737177d71c965", size = 119605, upload-time = "2025-12-16T19:45:51.08Z" }, +] + +[package.optional-dependencies] +lua = [ + { name = "lupa", marker = "python_full_version >= '3.10'" }, +] + [[package]] name = "fastapi" source = { editable = "." } @@ -1015,7 +1353,7 @@ dependencies = [ { name = "annotated-doc" }, { name = "pydantic" }, { name = "starlette", version = "0.49.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "starlette", version = "0.52.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "typing-extensions" }, { name = "typing-inspection" }, ] @@ -1070,7 +1408,8 @@ standard-no-fastapi-cloud-cli = [ dev = [ { name = "a2wsgi" }, { name = "anyio", extra = ["trio"] }, - { name = "black" }, + { name = "black", version = "25.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "black", version = "26.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "cairosvg" }, { 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.3", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, @@ -1087,14 +1426,17 @@ dev = [ { name = "mkdocs-macros-plugin" }, { name = "mkdocs-material" }, { name = "mkdocs-redirects" }, - { name = "mkdocstrings", extra = ["python"] }, + { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version < '3.10'" }, + { name = "mkdocstrings", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version >= '3.10'" }, { name = "mypy" }, - { name = "pillow" }, + { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "playwright" }, { name = "prek" }, { name = "pwdlib", version = "0.2.1", source = { registry = "https://pypi.org/simple" }, extra = ["argon2"], marker = "python_full_version < '3.10'" }, { name = "pwdlib", version = "0.3.0", source = { registry = "https://pypi.org/simple" }, extra = ["argon2"], marker = "python_full_version >= '3.10'" }, - { name = "pydantic-ai" }, + { name = "pydantic-ai", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pydantic-ai", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pygithub" }, { name = "pyjwt" }, { name = "pytest" }, @@ -1104,13 +1446,14 @@ dev = [ { name = "ruff" }, { name = "sqlmodel" }, { name = "strawberry-graphql", version = "0.283.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "strawberry-graphql", version = "0.291.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "strawberry-graphql", version = "0.291.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "typer" }, { name = "types-orjson" }, { name = "types-ujson" }, ] docs = [ - { name = "black" }, + { name = "black", version = "25.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "black", version = "26.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "cairosvg" }, { name = "griffe-typingdoc" }, { name = "griffe-warnings-deprecated" }, @@ -1121,8 +1464,10 @@ docs = [ { name = "mkdocs-macros-plugin" }, { name = "mkdocs-material" }, { name = "mkdocs-redirects" }, - { name = "mkdocstrings", extra = ["python"] }, - { name = "pillow" }, + { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version < '3.10'" }, + { name = "mkdocstrings", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version >= '3.10'" }, + { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "python-slugify" }, { name = "pyyaml" }, { name = "ruff" }, @@ -1160,13 +1505,14 @@ tests = [ { name = "ruff" }, { name = "sqlmodel" }, { name = "strawberry-graphql", version = "0.283.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "strawberry-graphql", version = "0.291.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "strawberry-graphql", version = "0.291.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "types-orjson" }, { name = "types-ujson" }, ] translations = [ { name = "gitpython" }, - { name = "pydantic-ai" }, + { name = "pydantic-ai", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pydantic-ai", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pygithub" }, ] @@ -1212,64 +1558,64 @@ provides-extras = ["standard", "standard-no-fastapi-cloud-cli", "all"] 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 = "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 = "dirty-equals", specifier = ">=0.9.0" }, { name = "flask", specifier = ">=3.0.0,<4.0.0" }, - { name = "gitpython", specifier = "==3.1.46" }, - { name = "griffe-typingdoc", specifier = "==0.3.0" }, - { name = "griffe-warnings-deprecated", specifier = "==1.1.0" }, + { 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" }, { name = "inline-snapshot", specifier = ">=0.21.1" }, - { name = "jieba", specifier = "==0.42.1" }, - { name = "markdown-include-variants", specifier = "==0.0.8" }, + { 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.5.0" }, - { name = "mkdocs-material", specifier = "==9.7.0" }, + { 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 = "mypy", specifier = "==1.14.1" }, - { name = "pillow", specifier = "==11.3.0" }, + { name = "mkdocstrings", extras = ["python"], specifier = ">=0.30.1" }, + { name = "mypy", specifier = ">=1.14.1" }, + { name = "pillow", specifier = ">=11.3.0" }, { name = "playwright", specifier = ">=1.57.0" }, - { name = "prek", specifier = "==0.2.22" }, + { name = "prek", specifier = ">=0.2.22" }, { name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" }, - { name = "pydantic-ai", specifier = "==0.4.10" }, - { name = "pygithub", specifier = "==2.8.1" }, - { name = "pyjwt", specifier = "==2.9.0" }, + { name = "pydantic-ai", specifier = ">=0.4.10" }, + { name = "pygithub", specifier = ">=2.8.1" }, + { name = "pyjwt", specifier = ">=2.9.0" }, { name = "pytest", specifier = ">=7.1.3,<9.0.0" }, - { name = "pytest-codspeed", specifier = "==4.2.0" }, - { name = "python-slugify", specifier = "==8.0.4" }, + { 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.14" }, - { name = "sqlmodel", specifier = "==0.0.31" }, + { 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.21.1" }, - { name = "types-orjson", specifier = "==3.6.2" }, - { name = "types-ujson", specifier = "==5.10.0.20240515" }, + { name = "typer", specifier = ">=0.21.1" }, + { name = "types-orjson", specifier = ">=3.6.2" }, + { name = "types-ujson", specifier = ">=5.10.0.20240515" }, ] docs = [ - { name = "black", specifier = "==25.1.0" }, - { name = "cairosvg", specifier = "==2.8.2" }, - { name = "griffe-typingdoc", specifier = "==0.3.0" }, - { name = "griffe-warnings-deprecated", specifier = "==1.1.0" }, + { name = "black", specifier = ">=25.1.0" }, + { name = "cairosvg", specifier = ">=2.8.2" }, + { name = "griffe-typingdoc", specifier = ">=0.3.0" }, + { name = "griffe-warnings-deprecated", specifier = ">=1.1.0" }, { name = "httpx", specifier = ">=0.23.0,<1.0.0" }, - { name = "jieba", specifier = "==0.42.1" }, - { name = "markdown-include-variants", specifier = "==0.0.8" }, + { 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.5.0" }, - { name = "mkdocs-material", specifier = "==9.7.0" }, + { 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 = "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.14" }, - { name = "typer", specifier = "==0.21.1" }, + { 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.14" }, + { name = "ruff", specifier = ">=0.14.14" }, ] github-actions = [ { name = "httpx", specifier = ">=0.27.0,<1.0.0" }, @@ -1283,26 +1629,26 @@ 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" }, + { name = "dirty-equals", specifier = ">=0.9.0" }, { name = "flask", specifier = ">=3.0.0,<4.0.0" }, { name = "httpx", specifier = ">=0.23.0,<1.0.0" }, { name = "inline-snapshot", specifier = ">=0.21.1" }, - { name = "mypy", specifier = "==1.14.1" }, + { name = "mypy", specifier = ">=1.14.1" }, { name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" }, - { name = "pyjwt", specifier = "==2.9.0" }, + { name = "pyjwt", specifier = ">=2.9.0" }, { name = "pytest", specifier = ">=7.1.3,<9.0.0" }, - { name = "pytest-codspeed", specifier = "==4.2.0" }, + { name = "pytest-codspeed", specifier = ">=4.2.0" }, { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" }, - { name = "ruff", specifier = "==0.14.14" }, - { name = "sqlmodel", specifier = "==0.0.31" }, + { 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" }, + { name = "types-orjson", specifier = ">=3.6.2" }, + { name = "types-ujson", specifier = ">=5.10.0.20240515" }, ] translations = [ - { name = "gitpython", specifier = "==3.1.46" }, - { name = "pydantic-ai", specifier = "==0.4.10" }, - { name = "pygithub", specifier = "==2.8.1" }, + { name = "gitpython", specifier = ">=3.1.46" }, + { name = "pydantic-ai", specifier = ">=0.4.10" }, + { name = "pygithub", specifier = ">=2.8.1" }, ] [[package]] @@ -1552,6 +1898,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/ba/b814fb09b32c8f3059451c33bb11d55eb23188e6a499f5dde628d13bc076/fastavro-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:a38607444281619eda3a9c1be9f5397634012d1b237142eee1540e810b30ac8b", size = 510328, upload-time = "2025-10-10T15:42:32.415Z" }, ] +[[package]] +name = "fastmcp" +version = "2.14.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "authlib", marker = "python_full_version >= '3.10'" }, + { name = "cyclopts", marker = "python_full_version >= '3.10'" }, + { name = "exceptiongroup", marker = "python_full_version >= '3.10'" }, + { name = "httpx", marker = "python_full_version >= '3.10'" }, + { name = "jsonref", marker = "python_full_version >= '3.10'" }, + { name = "jsonschema-path", marker = "python_full_version >= '3.10'" }, + { name = "mcp", marker = "python_full_version >= '3.10'" }, + { name = "openapi-pydantic", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"], marker = "python_full_version >= '3.10'" }, + { name = "pydantic", extra = ["email"], marker = "python_full_version >= '3.10'" }, + { name = "pydocket", marker = "python_full_version >= '3.10'" }, + { name = "pyperclip", marker = "python_full_version >= '3.10'" }, + { name = "python-dotenv", marker = "python_full_version >= '3.10'" }, + { name = "rich", marker = "python_full_version >= '3.10'" }, + { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "websockets", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/32/982678d44f13849530a74ab101ed80e060c2ee6cf87471f062dcf61705fd/fastmcp-2.14.5.tar.gz", hash = "sha256:38944dc582c541d55357082bda2241cedb42cd3a78faea8a9d6a2662c62a42d7", size = 8296329, upload-time = "2026-02-03T15:35:21.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/c1/1a35ec68ff76ea8443aa115b18bcdee748a4ada2124537ee90522899ff9f/fastmcp-2.14.5-py3-none-any.whl", hash = "sha256:d81e8ec813f5089d3624bec93944beaefa86c0c3a4ef1111cbef676a761ebccf", size = 417784, upload-time = "2026-02-03T15:35:18.489Z" }, +] + [[package]] name = "filelock" version = "3.19.1" @@ -1596,6 +1971,143 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, ] +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/ae5cdac87a00962122ea37bb346d41b66aec05f9ce328fa2b9e216f8967b/frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47", size = 86967, upload-time = "2025-10-06T05:37:55.607Z" }, + { url = "https://files.pythonhosted.org/packages/8a/10/17059b2db5a032fd9323c41c39e9d1f5f9d0c8f04d1e4e3e788573086e61/frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca", size = 49984, upload-time = "2025-10-06T05:37:57.049Z" }, + { url = "https://files.pythonhosted.org/packages/4b/de/ad9d82ca8e5fa8f0c636e64606553c79e2b859ad253030b62a21fe9986f5/frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068", size = 50240, upload-time = "2025-10-06T05:37:58.145Z" }, + { url = "https://files.pythonhosted.org/packages/4e/45/3dfb7767c2a67d123650122b62ce13c731b6c745bc14424eea67678b508c/frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95", size = 219472, upload-time = "2025-10-06T05:37:59.239Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bf/5bf23d913a741b960d5c1dac7c1985d8a2a1d015772b2d18ea168b08e7ff/frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459", size = 221531, upload-time = "2025-10-06T05:38:00.521Z" }, + { url = "https://files.pythonhosted.org/packages/d0/03/27ec393f3b55860859f4b74cdc8c2a4af3dbf3533305e8eacf48a4fd9a54/frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675", size = 219211, upload-time = "2025-10-06T05:38:01.842Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ad/0fd00c404fa73fe9b169429e9a972d5ed807973c40ab6b3cf9365a33d360/frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61", size = 231775, upload-time = "2025-10-06T05:38:03.384Z" }, + { url = "https://files.pythonhosted.org/packages/8a/c3/86962566154cb4d2995358bc8331bfc4ea19d07db1a96f64935a1607f2b6/frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6", size = 236631, upload-time = "2025-10-06T05:38:04.609Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/6ffad161dbd83782d2c66dc4d378a9103b31770cb1e67febf43aea42d202/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5", size = 218632, upload-time = "2025-10-06T05:38:05.917Z" }, + { url = "https://files.pythonhosted.org/packages/58/b2/4677eee46e0a97f9b30735e6ad0bf6aba3e497986066eb68807ac85cf60f/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3", size = 235967, upload-time = "2025-10-06T05:38:07.614Z" }, + { url = "https://files.pythonhosted.org/packages/05/f3/86e75f8639c5a93745ca7addbbc9de6af56aebb930d233512b17e46f6493/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1", size = 228799, upload-time = "2025-10-06T05:38:08.845Z" }, + { url = "https://files.pythonhosted.org/packages/30/00/39aad3a7f0d98f5eb1d99a3c311215674ed87061aecee7851974b335c050/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178", size = 230566, upload-time = "2025-10-06T05:38:10.52Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4d/aa144cac44568d137846ddc4d5210fb5d9719eb1d7ec6fa2728a54b5b94a/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda", size = 217715, upload-time = "2025-10-06T05:38:11.832Z" }, + { url = "https://files.pythonhosted.org/packages/64/4c/8f665921667509d25a0dd72540513bc86b356c95541686f6442a3283019f/frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087", size = 39933, upload-time = "2025-10-06T05:38:13.061Z" }, + { url = "https://files.pythonhosted.org/packages/79/bd/bcc926f87027fad5e59926ff12d136e1082a115025d33c032d1cd69ab377/frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a", size = 44121, upload-time = "2025-10-06T05:38:14.572Z" }, + { url = "https://files.pythonhosted.org/packages/4c/07/9c2e4eb7584af4b705237b971b89a4155a8e57599c4483a131a39256a9a0/frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103", size = 40312, upload-time = "2025-10-06T05:38:15.699Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + [[package]] name = "fsspec" version = "2025.10.0" @@ -1621,6 +2133,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, ] +[[package]] +name = "genai-prices" +version = "0.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/87/bdc11c1671e3a3fe701c3c4aaae4aa2bb7a84a6bb1812dfb5693c87d3872/genai_prices-0.0.52.tar.gz", hash = "sha256:0df7420b555fa3a48d09e5c7802ba35b5dfa9fd49b0c3bb2c150c59060d83f52", size = 58364, upload-time = "2026-01-28T12:07:49.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/33/6316b4907a0bffc1bcc99074c7e2d01184fdfeee401c864146a40d55ad10/genai_prices-0.0.52-py3-none-any.whl", hash = "sha256:639e7a2ae7eddf5710febb9779b9c9e31ff5acf464b4eb1f6018798ea642e6d3", size = 60937, upload-time = "2026-01-28T12:07:47.921Z" }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -1724,6 +2250,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/09/5f/4645d8a28c6e431d0dd6011003a852563f3da7037d36af53154925b099fd/google_genai-1.62.0-py3-none-any.whl", hash = "sha256:4c3daeff3d05fafee4b9a1a31f9c07f01bc22051081aa58b4d61f58d16d1bcc0", size = 724166, upload-time = "2026-02-04T22:48:39.956Z" }, ] +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf", version = "6.33.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, +] + [[package]] name = "graphql-core" version = "3.2.7" @@ -1951,6 +2489,77 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/88/3175759d2ef30406ea721f4d837bfa1ba4339fde3b81ba8c5640a96ed231/groq-1.0.0-py3-none-any.whl", hash = "sha256:6e22bf92ffad988f01d2d4df7729add66b8fd5dbfb2154b5bbf3af245b72c731", size = 138292, upload-time = "2025-12-17T23:34:21.957Z" }, ] +[[package]] +name = "grpcio" +version = "1.78.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/a8/690a085b4d1fe066130de97a87de32c45062cf2ecd218df9675add895550/grpcio-1.78.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:7cc47943d524ee0096f973e1081cb8f4f17a4615f2116882a5f1416e4cfe92b5", size = 5946986, upload-time = "2026-02-06T09:54:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/c7/1b/e5213c5c0ced9d2d92778d30529ad5bb2dcfb6c48c4e2d01b1f302d33d64/grpcio-1.78.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c3f293fdc675ccba4db5a561048cca627b5e7bd1c8a6973ffedabe7d116e22e2", size = 11816533, upload-time = "2026-02-06T09:54:37.04Z" }, + { url = "https://files.pythonhosted.org/packages/18/37/1ba32dccf0a324cc5ace744c44331e300b000a924bf14840f948c559ede7/grpcio-1.78.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10a9a644b5dd5aec3b82b5b0b90d41c0fa94c85ef42cb42cf78a23291ddb5e7d", size = 6519964, upload-time = "2026-02-06T09:54:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f5/c0e178721b818072f2e8b6fde13faaba942406c634009caf065121ce246b/grpcio-1.78.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4c5533d03a6cbd7f56acfc9cfb44ea64f63d29091e40e44010d34178d392d7eb", size = 7198058, upload-time = "2026-02-06T09:54:42.389Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b2/40d43c91ae9cd667edc960135f9f08e58faa1576dc95af29f66ec912985f/grpcio-1.78.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff870aebe9a93a85283837801d35cd5f8814fe2ad01e606861a7fb47c762a2b7", size = 6727212, upload-time = "2026-02-06T09:54:44.91Z" }, + { url = "https://files.pythonhosted.org/packages/ed/88/9da42eed498f0efcfcd9156e48ae63c0cde3bea398a16c99fb5198c885b6/grpcio-1.78.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:391e93548644e6b2726f1bb84ed60048d4bcc424ce5e4af0843d28ca0b754fec", size = 7300845, upload-time = "2026-02-06T09:54:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/23/3f/1c66b7b1b19a8828890e37868411a6e6925df5a9030bfa87ab318f34095d/grpcio-1.78.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:df2c8f3141f7cbd112a6ebbd760290b5849cda01884554f7c67acc14e7b1758a", size = 8284605, upload-time = "2026-02-06T09:54:50.475Z" }, + { url = "https://files.pythonhosted.org/packages/94/c4/ca1bd87394f7b033e88525384b4d1e269e8424ab441ea2fba1a0c5b50986/grpcio-1.78.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd8cb8026e5f5b50498a3c4f196f57f9db344dad829ffae16b82e4fdbaea2813", size = 7726672, upload-time = "2026-02-06T09:54:53.11Z" }, + { url = "https://files.pythonhosted.org/packages/41/09/f16e487d4cc65ccaf670f6ebdd1a17566b965c74fc3d93999d3b2821e052/grpcio-1.78.0-cp310-cp310-win32.whl", hash = "sha256:f8dff3d9777e5d2703a962ee5c286c239bf0ba173877cc68dc02c17d042e29de", size = 4076715, upload-time = "2026-02-06T09:54:55.549Z" }, + { url = "https://files.pythonhosted.org/packages/2a/32/4ce60d94e242725fd3bcc5673c04502c82a8e87b21ea411a63992dc39f8f/grpcio-1.78.0-cp310-cp310-win_amd64.whl", hash = "sha256:94f95cf5d532d0e717eed4fc1810e8e6eded04621342ec54c89a7c2f14b581bf", size = 4799157, upload-time = "2026-02-06T09:54:59.838Z" }, + { url = "https://files.pythonhosted.org/packages/86/c7/d0b780a29b0837bf4ca9580904dfb275c1fc321ded7897d620af7047ec57/grpcio-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6", size = 5951525, upload-time = "2026-02-06T09:55:01.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b1/96920bf2ee61df85a9503cb6f733fe711c0ff321a5a697d791b075673281/grpcio-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e", size = 11830418, upload-time = "2026-02-06T09:55:04.462Z" }, + { url = "https://files.pythonhosted.org/packages/83/0c/7c1528f098aeb75a97de2bae18c530f56959fb7ad6c882db45d9884d6edc/grpcio-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911", size = 6524477, upload-time = "2026-02-06T09:55:07.111Z" }, + { url = "https://files.pythonhosted.org/packages/8d/52/e7c1f3688f949058e19a011c4e0dec973da3d0ae5e033909677f967ae1f4/grpcio-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e", size = 7198266, upload-time = "2026-02-06T09:55:10.016Z" }, + { url = "https://files.pythonhosted.org/packages/e5/61/8ac32517c1e856677282c34f2e7812d6c328fa02b8f4067ab80e77fdc9c9/grpcio-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303", size = 6730552, upload-time = "2026-02-06T09:55:12.207Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/b8ee0158199250220734f620b12e4a345955ac7329cfd908d0bf0fda77f0/grpcio-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04", size = 7304296, upload-time = "2026-02-06T09:55:15.044Z" }, + { url = "https://files.pythonhosted.org/packages/bd/0f/7b72762e0d8840b58032a56fdbd02b78fc645b9fa993d71abf04edbc54f4/grpcio-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec", size = 8288298, upload-time = "2026-02-06T09:55:17.276Z" }, + { url = "https://files.pythonhosted.org/packages/24/ae/ae4ce56bc5bb5caa3a486d60f5f6083ac3469228faa734362487176c15c5/grpcio-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074", size = 7730953, upload-time = "2026-02-06T09:55:19.545Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6e/8052e3a28eb6a820c372b2eb4b5e32d195c661e137d3eca94d534a4cfd8a/grpcio-1.78.0-cp311-cp311-win32.whl", hash = "sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856", size = 4076503, upload-time = "2026-02-06T09:55:21.521Z" }, + { url = "https://files.pythonhosted.org/packages/08/62/f22c98c5265dfad327251fa2f840b591b1df5f5e15d88b19c18c86965b27/grpcio-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558", size = 4799767, upload-time = "2026-02-06T09:55:24.107Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, + { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, + { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, + { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, + { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, + { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, + { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, + { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, + { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, + { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, + { url = "https://files.pythonhosted.org/packages/58/6c/40a4bba2c753ea8eeb8d776a31e9c54f4e506edf36db93a3db5456725294/grpcio-1.78.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:86f85dd7c947baa707078a236288a289044836d4b640962018ceb9cd1f899af5", size = 5947902, upload-time = "2026-02-06T09:56:48.469Z" }, + { url = "https://files.pythonhosted.org/packages/c0/4c/ed7664a37a7008be41204c77e0d88bbc4ac531bcf0c27668cd066f9ff6e2/grpcio-1.78.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:de8cb00d1483a412a06394b8303feec5dcb3b55f81d83aa216dbb6a0b86a94f5", size = 11824772, upload-time = "2026-02-06T09:56:51.264Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5b/45a5c23ba3c4a0f51352366d9b25369a2a51163ab1c93482cb8408726617/grpcio-1.78.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e888474dee2f59ff68130f8a397792d8cb8e17e6b3434339657ba4ee90845a8c", size = 6521579, upload-time = "2026-02-06T09:56:54.967Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/392e647d918004231e3d1c780ed125c48939bfc8f845adb8b5820410da3e/grpcio-1.78.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:86ce2371bfd7f212cf60d8517e5e854475c2c43ce14aa910e136ace72c6db6c1", size = 7199330, upload-time = "2026-02-06T09:56:57.611Z" }, + { url = "https://files.pythonhosted.org/packages/68/2f/42a52d78bdbdb3f1310ed690a3511cd004740281ca75d300b7bd6d9d3de3/grpcio-1.78.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0c689c02947d636bc7fab3e30cc3a3445cca99c834dfb77cd4a6cabfc1c5597", size = 6726696, upload-time = "2026-02-06T09:57:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/0f/83/b3d932a4fbb2dce3056f6df2926fc2d3ddc5d5acbafbec32c84033cf3f23/grpcio-1.78.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ce7599575eeb25c0f4dc1be59cada6219f3b56176f799627f44088b21381a28a", size = 7299076, upload-time = "2026-02-06T09:57:04.124Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d9/70ea1be55efaf91fd19f7258b1292772a8226cf1b0e237717fba671073cb/grpcio-1.78.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:684083fd383e9dc04c794adb838d4faea08b291ce81f64ecd08e4577c7398adf", size = 8284493, upload-time = "2026-02-06T09:57:06.746Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2f/3dddccf49e3e75564655b84175fca092d3efd81d2979fc89c4b1c1d879dc/grpcio-1.78.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ab399ef5e3cd2a721b1038a0f3021001f19c5ab279f145e1146bb0b9f1b2b12c", size = 7724340, upload-time = "2026-02-06T09:57:09.453Z" }, + { url = "https://files.pythonhosted.org/packages/79/ae/dfdb3183141db787a9363078a98764675996a7c2448883153091fd7c8527/grpcio-1.78.0-cp39-cp39-win32.whl", hash = "sha256:f3d6379493e18ad4d39537a82371c5281e153e963cecb13f953ebac155756525", size = 4077641, upload-time = "2026-02-06T09:57:11.881Z" }, + { url = "https://files.pythonhosted.org/packages/aa/aa/694b2f505345cfdd234cffb2525aa379a81695e6c02fd40d7e9193e871c6/grpcio-1.78.0-cp39-cp39-win_amd64.whl", hash = "sha256:5361a0630a7fdb58a6a97638ab70e1dae2893c4d08d7aba64ded28bb9e7a29df", size = 4799428, upload-time = "2026-02-06T09:57:14.493Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -2087,25 +2696,28 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "1.4.1" +version = "0.36.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "filelock", version = "3.20.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "fsspec", version = "2025.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "fsspec", version = "2026.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "httpx" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, { name = "packaging" }, { name = "pyyaml" }, - { name = "shellingham" }, + { name = "requests" }, { name = "tqdm" }, - { name = "typer-slim" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/fc/eb9bc06130e8bbda6a616e1b80a7aa127681c448d6b49806f61db2670b61/huggingface_hub-1.4.1.tar.gz", hash = "sha256:b41131ec35e631e7383ab26d6146b8d8972abc8b6309b963b306fbcca87f5ed5", size = 642156, upload-time = "2026-02-06T09:20:03.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/ae/2f6d96b4e6c5478d87d606a1934b5d436c4a2bce6bb7c6fdece891c128e3/huggingface_hub-1.4.1-py3-none-any.whl", hash = "sha256:9931d075fb7a79af5abc487106414ec5fba2c0ae86104c0c62fd6cae38873d18", size = 553326, upload-time = "2026-02-06T09:20:00.728Z" }, + { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" }, +] + +[package.optional-dependencies] +inference = [ + { name = "aiohttp" }, ] [[package]] @@ -2188,6 +2800,51 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, ] +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-tarfile", marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/9c/a788f5bb29c61e456b8ee52ce76dbdd32fd72cd73dd67bc95f42c7a8d13c/jaraco_context-6.1.0.tar.gz", hash = "sha256:129a341b0a85a7db7879e22acd66902fda67882db771754574338898b2d5d86f", size = 15850, upload-time = "2026-01-13T02:53:53.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/48/aa685dbf1024c7bd82bede569e3a85f82c32fd3d79ba5fea578f0159571a/jaraco_context-6.1.0-py3-none-any.whl", hash = "sha256:a43b5ed85815223d0d3cfdb6d7ca0d2bc8946f28f30b6f3216bda070f68badda", size = 7065, upload-time = "2026-01-13T02:53:53.031Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + [[package]] name = "jieba" version = "0.42.1" @@ -2324,6 +2981,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, ] +[[package]] +name = "jsonref" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, +] + [[package]] name = "jsonschema" version = "4.26.0" @@ -2339,6 +3005,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, ] +[[package]] +name = "jsonschema-path" +version = "0.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathable", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "referencing", marker = "python_full_version >= '3.10'" }, + { name = "requests", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/45/41ebc679c2a4fced6a722f624c18d658dee42612b83ea24c1caf7c0eb3a8/jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001", size = 11159, upload-time = "2025-01-24T14:33:16.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl", hash = "sha256:f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8", size = 14810, upload-time = "2025-01-24T14:33:14.652Z" }, +] + [[package]] name = "jsonschema-specifications" version = "2025.9.1" @@ -2351,6 +3032,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, + { name = "jaraco-classes", marker = "python_full_version >= '3.10'" }, + { name = "jaraco-context", marker = "python_full_version >= '3.10'" }, + { name = "jaraco-functools", marker = "python_full_version >= '3.10'" }, + { name = "jeepney", marker = "python_full_version >= '3.10' and sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "secretstorage", marker = "python_full_version >= '3.10' and sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + [[package]] name = "lia-web" version = "0.3.1" @@ -2363,6 +3062,113 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/8b/b628fc18658f94b3d094708a18b71083cf47628e85cbc6b9edba54d5b2d7/lia_web-0.3.1-py3-none-any.whl", hash = "sha256:e4e6e7a9381e228aca60a6f3d67dbae9a5f4638eced242d931f95797ddba3f8b", size = 5933, upload-time = "2025-12-25T20:41:52.289Z" }, ] +[[package]] +name = "librt" +version = "0.7.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/24/5f3646ff414285e0f7708fa4e946b9bf538345a41d1c375c439467721a5e/librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862", size = 148323, upload-time = "2026-01-14T12:56:16.876Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/13/57b06758a13550c5f09563893b004f98e9537ee6ec67b7df85c3571c8832/librt-0.7.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b45306a1fc5f53c9330fbee134d8b3227fe5da2ab09813b892790400aa49352d", size = 56521, upload-time = "2026-01-14T12:54:40.066Z" }, + { url = "https://files.pythonhosted.org/packages/c2/24/bbea34d1452a10612fb45ac8356f95351ba40c2517e429602160a49d1fd0/librt-0.7.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:864c4b7083eeee250ed55135d2127b260d7eb4b5e953a9e5df09c852e327961b", size = 58456, upload-time = "2026-01-14T12:54:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/04/72/a168808f92253ec3a810beb1eceebc465701197dbc7e865a1c9ceb3c22c7/librt-0.7.8-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6938cc2de153bc927ed8d71c7d2f2ae01b4e96359126c602721340eb7ce1a92d", size = 164392, upload-time = "2026-01-14T12:54:42.843Z" }, + { url = "https://files.pythonhosted.org/packages/14/5c/4c0d406f1b02735c2e7af8ff1ff03a6577b1369b91aa934a9fa2cc42c7ce/librt-0.7.8-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66daa6ac5de4288a5bbfbe55b4caa7bf0cd26b3269c7a476ffe8ce45f837f87d", size = 172959, upload-time = "2026-01-14T12:54:44.602Z" }, + { url = "https://files.pythonhosted.org/packages/82/5f/3e85351c523f73ad8d938989e9a58c7f59fb9c17f761b9981b43f0025ce7/librt-0.7.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4864045f49dc9c974dadb942ac56a74cd0479a2aafa51ce272c490a82322ea3c", size = 186717, upload-time = "2026-01-14T12:54:45.986Z" }, + { url = "https://files.pythonhosted.org/packages/08/f8/18bfe092e402d00fe00d33aa1e01dda1bd583ca100b393b4373847eade6d/librt-0.7.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a36515b1328dc5b3ffce79fe204985ca8572525452eacabee2166f44bb387b2c", size = 184585, upload-time = "2026-01-14T12:54:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/4e/fc/f43972ff56fd790a9fa55028a52ccea1875100edbb856b705bd393b601e3/librt-0.7.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b7e7f140c5169798f90b80d6e607ed2ba5059784968a004107c88ad61fb3641d", size = 180497, upload-time = "2026-01-14T12:54:48.946Z" }, + { url = "https://files.pythonhosted.org/packages/e1/3a/25e36030315a410d3ad0b7d0f19f5f188e88d1613d7d3fd8150523ea1093/librt-0.7.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff71447cb778a4f772ddc4ce360e6ba9c95527ed84a52096bd1bbf9fee2ec7c0", size = 200052, upload-time = "2026-01-14T12:54:50.382Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b8/f3a5a1931ae2a6ad92bf6893b9ef44325b88641d58723529e2c2935e8abe/librt-0.7.8-cp310-cp310-win32.whl", hash = "sha256:047164e5f68b7a8ebdf9fae91a3c2161d3192418aadd61ddd3a86a56cbe3dc85", size = 43477, upload-time = "2026-01-14T12:54:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/fe/91/c4202779366bc19f871b4ad25db10fcfa1e313c7893feb942f32668e8597/librt-0.7.8-cp310-cp310-win_amd64.whl", hash = "sha256:d6f254d096d84156a46a84861183c183d30734e52383602443292644d895047c", size = 49806, upload-time = "2026-01-14T12:54:53.149Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a3/87ea9c1049f2c781177496ebee29430e4631f439b8553a4969c88747d5d8/librt-0.7.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f", size = 56507, upload-time = "2026-01-14T12:54:54.156Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4a/23bcef149f37f771ad30203d561fcfd45b02bc54947b91f7a9ac34815747/librt-0.7.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac", size = 58455, upload-time = "2026-01-14T12:54:55.978Z" }, + { url = "https://files.pythonhosted.org/packages/22/6e/46eb9b85c1b9761e0f42b6e6311e1cc544843ac897457062b9d5d0b21df4/librt-0.7.8-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c", size = 164956, upload-time = "2026-01-14T12:54:57.311Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3f/aa7c7f6829fb83989feb7ba9aa11c662b34b4bd4bd5b262f2876ba3db58d/librt-0.7.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8", size = 174364, upload-time = "2026-01-14T12:54:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/3f/2d/d57d154b40b11f2cb851c4df0d4c4456bacd9b1ccc4ecb593ddec56c1a8b/librt-0.7.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff", size = 188034, upload-time = "2026-01-14T12:55:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/59/f9/36c4dad00925c16cd69d744b87f7001792691857d3b79187e7a673e812fb/librt-0.7.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3", size = 186295, upload-time = "2026-01-14T12:55:01.303Z" }, + { url = "https://files.pythonhosted.org/packages/23/9b/8a9889d3df5efb67695a67785028ccd58e661c3018237b73ad081691d0cb/librt-0.7.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75", size = 181470, upload-time = "2026-01-14T12:55:02.492Z" }, + { url = "https://files.pythonhosted.org/packages/43/64/54d6ef11afca01fef8af78c230726a9394759f2addfbf7afc5e3cc032a45/librt-0.7.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873", size = 201713, upload-time = "2026-01-14T12:55:03.919Z" }, + { url = "https://files.pythonhosted.org/packages/2d/29/73e7ed2991330b28919387656f54109139b49e19cd72902f466bd44415fd/librt-0.7.8-cp311-cp311-win32.whl", hash = "sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7", size = 43803, upload-time = "2026-01-14T12:55:04.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/de/66766ff48ed02b4d78deea30392ae200bcbd99ae61ba2418b49fd50a4831/librt-0.7.8-cp311-cp311-win_amd64.whl", hash = "sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c", size = 50080, upload-time = "2026-01-14T12:55:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e3/33450438ff3a8c581d4ed7f798a70b07c3206d298cf0b87d3806e72e3ed8/librt-0.7.8-cp311-cp311-win_arm64.whl", hash = "sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232", size = 43383, upload-time = "2026-01-14T12:55:07.49Z" }, + { url = "https://files.pythonhosted.org/packages/56/04/79d8fcb43cae376c7adbab7b2b9f65e48432c9eced62ac96703bcc16e09b/librt-0.7.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63", size = 57472, upload-time = "2026-01-14T12:55:08.528Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ba/60b96e93043d3d659da91752689023a73981336446ae82078cddf706249e/librt-0.7.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93", size = 58986, upload-time = "2026-01-14T12:55:09.466Z" }, + { url = "https://files.pythonhosted.org/packages/7c/26/5215e4cdcc26e7be7eee21955a7e13cbf1f6d7d7311461a6014544596fac/librt-0.7.8-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592", size = 168422, upload-time = "2026-01-14T12:55:10.499Z" }, + { url = "https://files.pythonhosted.org/packages/0f/84/e8d1bc86fa0159bfc24f3d798d92cafd3897e84c7fea7fe61b3220915d76/librt-0.7.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850", size = 177478, upload-time = "2026-01-14T12:55:11.577Z" }, + { url = "https://files.pythonhosted.org/packages/57/11/d0268c4b94717a18aa91df1100e767b010f87b7ae444dafaa5a2d80f33a6/librt-0.7.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62", size = 192439, upload-time = "2026-01-14T12:55:12.7Z" }, + { url = "https://files.pythonhosted.org/packages/8d/56/1e8e833b95fe684f80f8894ae4d8b7d36acc9203e60478fcae599120a975/librt-0.7.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b", size = 191483, upload-time = "2026-01-14T12:55:13.838Z" }, + { url = "https://files.pythonhosted.org/packages/17/48/f11cf28a2cb6c31f282009e2208312aa84a5ee2732859f7856ee306176d5/librt-0.7.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714", size = 185376, upload-time = "2026-01-14T12:55:15.017Z" }, + { url = "https://files.pythonhosted.org/packages/b8/6a/d7c116c6da561b9155b184354a60a3d5cdbf08fc7f3678d09c95679d13d9/librt-0.7.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449", size = 206234, upload-time = "2026-01-14T12:55:16.571Z" }, + { url = "https://files.pythonhosted.org/packages/61/de/1975200bb0285fc921c5981d9978ce6ce11ae6d797df815add94a5a848a3/librt-0.7.8-cp312-cp312-win32.whl", hash = "sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac", size = 44057, upload-time = "2026-01-14T12:55:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/8e/cd/724f2d0b3461426730d4877754b65d39f06a41ac9d0a92d5c6840f72b9ae/librt-0.7.8-cp312-cp312-win_amd64.whl", hash = "sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708", size = 50293, upload-time = "2026-01-14T12:55:19.179Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cf/7e899acd9ee5727ad8160fdcc9994954e79fab371c66535c60e13b968ffc/librt-0.7.8-cp312-cp312-win_arm64.whl", hash = "sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0", size = 43574, upload-time = "2026-01-14T12:55:20.185Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fe/b1f9de2829cf7fc7649c1dcd202cfd873837c5cc2fc9e526b0e7f716c3d2/librt-0.7.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc", size = 57500, upload-time = "2026-01-14T12:55:21.219Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d4/4a60fbe2e53b825f5d9a77325071d61cd8af8506255067bf0c8527530745/librt-0.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2", size = 59019, upload-time = "2026-01-14T12:55:22.256Z" }, + { url = "https://files.pythonhosted.org/packages/6a/37/61ff80341ba5159afa524445f2d984c30e2821f31f7c73cf166dcafa5564/librt-0.7.8-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3", size = 169015, upload-time = "2026-01-14T12:55:23.24Z" }, + { url = "https://files.pythonhosted.org/packages/1c/86/13d4f2d6a93f181ebf2fc953868826653ede494559da8268023fe567fca3/librt-0.7.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:451e7ffcef8f785831fdb791bd69211f47e95dc4c6ddff68e589058806f044c6", size = 178161, upload-time = "2026-01-14T12:55:24.826Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/e24ef01305954fc4d771f1f09f3dd682f9eb610e1bec188ffb719374d26e/librt-0.7.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3469e1af9f1380e093ae06bedcbdd11e407ac0b303a56bbe9afb1d6824d4982d", size = 193015, upload-time = "2026-01-14T12:55:26.04Z" }, + { url = "https://files.pythonhosted.org/packages/88/a0/92b6bd060e720d7a31ed474d046a69bd55334ec05e9c446d228c4b806ae3/librt-0.7.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f11b300027ce19a34f6d24ebb0a25fd0e24a9d53353225a5c1e6cadbf2916b2e", size = 192038, upload-time = "2026-01-14T12:55:27.208Z" }, + { url = "https://files.pythonhosted.org/packages/06/bb/6f4c650253704279c3a214dad188101d1b5ea23be0606628bc6739456624/librt-0.7.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4adc73614f0d3c97874f02f2c7fd2a27854e7e24ad532ea6b965459c5b757eca", size = 186006, upload-time = "2026-01-14T12:55:28.594Z" }, + { url = "https://files.pythonhosted.org/packages/dc/00/1c409618248d43240cadf45f3efb866837fa77e9a12a71481912135eb481/librt-0.7.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60c299e555f87e4c01b2eca085dfccda1dde87f5a604bb45c2906b8305819a93", size = 206888, upload-time = "2026-01-14T12:55:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/d9/83/b2cfe8e76ff5c1c77f8a53da3d5de62d04b5ebf7cf913e37f8bca43b5d07/librt-0.7.8-cp313-cp313-win32.whl", hash = "sha256:b09c52ed43a461994716082ee7d87618096851319bf695d57ec123f2ab708951", size = 44126, upload-time = "2026-01-14T12:55:31.44Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0b/c59d45de56a51bd2d3a401fc63449c0ac163e4ef7f523ea8b0c0dee86ec5/librt-0.7.8-cp313-cp313-win_amd64.whl", hash = "sha256:f8f4a901a3fa28969d6e4519deceab56c55a09d691ea7b12ca830e2fa3461e34", size = 50262, upload-time = "2026-01-14T12:55:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b9/973455cec0a1ec592395250c474164c4a58ebf3e0651ee920fef1a2623f1/librt-0.7.8-cp313-cp313-win_arm64.whl", hash = "sha256:43d4e71b50763fcdcf64725ac680d8cfa1706c928b844794a7aa0fa9ac8e5f09", size = 43600, upload-time = "2026-01-14T12:55:34.054Z" }, + { url = "https://files.pythonhosted.org/packages/1a/73/fa8814c6ce2d49c3827829cadaa1589b0bf4391660bd4510899393a23ebc/librt-0.7.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418", size = 57049, upload-time = "2026-01-14T12:55:35.056Z" }, + { url = "https://files.pythonhosted.org/packages/53/fe/f6c70956da23ea235fd2e3cc16f4f0b4ebdfd72252b02d1164dd58b4e6c3/librt-0.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611", size = 58689, upload-time = "2026-01-14T12:55:36.078Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4d/7a2481444ac5fba63050d9abe823e6bc16896f575bfc9c1e5068d516cdce/librt-0.7.8-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758", size = 166808, upload-time = "2026-01-14T12:55:37.595Z" }, + { url = "https://files.pythonhosted.org/packages/ac/3c/10901d9e18639f8953f57c8986796cfbf4c1c514844a41c9197cf87cb707/librt-0.7.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea", size = 175614, upload-time = "2026-01-14T12:55:38.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/01/5cbdde0951a5090a80e5ba44e6357d375048123c572a23eecfb9326993a7/librt-0.7.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac", size = 189955, upload-time = "2026-01-14T12:55:39.939Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b4/e80528d2f4b7eaf1d437fcbd6fc6ba4cbeb3e2a0cb9ed5a79f47c7318706/librt-0.7.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398", size = 189370, upload-time = "2026-01-14T12:55:41.057Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ab/938368f8ce31a9787ecd4becb1e795954782e4312095daf8fd22420227c8/librt-0.7.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81", size = 183224, upload-time = "2026-01-14T12:55:42.328Z" }, + { url = "https://files.pythonhosted.org/packages/3c/10/559c310e7a6e4014ac44867d359ef8238465fb499e7eb31b6bfe3e3f86f5/librt-0.7.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83", size = 203541, upload-time = "2026-01-14T12:55:43.501Z" }, + { url = "https://files.pythonhosted.org/packages/f8/db/a0db7acdb6290c215f343835c6efda5b491bb05c3ddc675af558f50fdba3/librt-0.7.8-cp314-cp314-win32.whl", hash = "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d", size = 40657, upload-time = "2026-01-14T12:55:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/72/e0/4f9bdc2a98a798511e81edcd6b54fe82767a715e05d1921115ac70717f6f/librt-0.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44", size = 46835, upload-time = "2026-01-14T12:55:45.655Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3d/59c6402e3dec2719655a41ad027a7371f8e2334aa794ed11533ad5f34969/librt-0.7.8-cp314-cp314-win_arm64.whl", hash = "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce", size = 39885, upload-time = "2026-01-14T12:55:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9c/2481d80950b83085fb14ba3c595db56330d21bbc7d88a19f20165f3538db/librt-0.7.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f", size = 59161, upload-time = "2026-01-14T12:55:48.45Z" }, + { url = "https://files.pythonhosted.org/packages/96/79/108df2cfc4e672336765d54e3ff887294c1cc36ea4335c73588875775527/librt-0.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde", size = 61008, upload-time = "2026-01-14T12:55:49.527Z" }, + { url = "https://files.pythonhosted.org/packages/46/f2/30179898f9994a5637459d6e169b6abdc982012c0a4b2d4c26f50c06f911/librt-0.7.8-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e", size = 187199, upload-time = "2026-01-14T12:55:50.587Z" }, + { url = "https://files.pythonhosted.org/packages/b4/da/f7563db55cebdc884f518ba3791ad033becc25ff68eb70902b1747dc0d70/librt-0.7.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b", size = 198317, upload-time = "2026-01-14T12:55:51.991Z" }, + { url = "https://files.pythonhosted.org/packages/b3/6c/4289acf076ad371471fa86718c30ae353e690d3de6167f7db36f429272f1/librt-0.7.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666", size = 210334, upload-time = "2026-01-14T12:55:53.682Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7f/377521ac25b78ac0a5ff44127a0360ee6d5ddd3ce7327949876a30533daa/librt-0.7.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581", size = 211031, upload-time = "2026-01-14T12:55:54.827Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b1/e1e96c3e20b23d00cf90f4aad48f0deb4cdfec2f0ed8380d0d85acf98bbf/librt-0.7.8-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a", size = 204581, upload-time = "2026-01-14T12:55:56.811Z" }, + { url = "https://files.pythonhosted.org/packages/43/71/0f5d010e92ed9747e14bef35e91b6580533510f1e36a8a09eb79ee70b2f0/librt-0.7.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca", size = 224731, upload-time = "2026-01-14T12:55:58.175Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/07fb6ab5c39a4ca9af3e37554f9d42f25c464829254d72e4ebbd81da351c/librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365", size = 41173, upload-time = "2026-01-14T12:55:59.315Z" }, + { url = "https://files.pythonhosted.org/packages/24/d4/7e4be20993dc6a782639625bd2f97f3c66125c7aa80c82426956811cfccf/librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32", size = 47668, upload-time = "2026-01-14T12:56:00.261Z" }, + { url = "https://files.pythonhosted.org/packages/fc/85/69f92b2a7b3c0f88ffe107c86b952b397004b5b8ea5a81da3d9c04c04422/librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06", size = 40550, upload-time = "2026-01-14T12:56:01.542Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9b/2668bb01f568bc89ace53736df950845f8adfcacdf6da087d5cef12110cb/librt-0.7.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c7e8f88f79308d86d8f39c491773cbb533d6cb7fa6476f35d711076ee04fceb6", size = 56680, upload-time = "2026-01-14T12:56:02.602Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d4/dbb3edf2d0ec4ba08dcaf1865833d32737ad208962d4463c022cea6e9d3c/librt-0.7.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:389bd25a0db916e1d6bcb014f11aa9676cedaa485e9ec3752dfe19f196fd377b", size = 58612, upload-time = "2026-01-14T12:56:03.616Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/64b029de4ac9901fcd47832c650a0fd050555a452bd455ce8deddddfbb9f/librt-0.7.8-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73fd300f501a052f2ba52ede721232212f3b06503fa12665408ecfc9d8fd149c", size = 163654, upload-time = "2026-01-14T12:56:04.975Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/95e2abb1b48eb8f8c7fc2ae945321a6b82777947eb544cc785c3f37165b2/librt-0.7.8-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d772edc6a5f7835635c7562f6688e031f0b97e31d538412a852c49c9a6c92d5", size = 172477, upload-time = "2026-01-14T12:56:06.103Z" }, + { url = "https://files.pythonhosted.org/packages/7e/27/9bdf12e05b0eb089dd008d9c8aabc05748aad9d40458ade5e627c9538158/librt-0.7.8-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde8a130bd0f239e45503ab39fab239ace094d63ee1d6b67c25a63d741c0f71", size = 186220, upload-time = "2026-01-14T12:56:09.958Z" }, + { url = "https://files.pythonhosted.org/packages/53/6a/c3774f4cc95e68ed444a39f2c8bd383fd18673db7d6b98cfa709f6634b93/librt-0.7.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fdec6e2368ae4f796fc72fad7fd4bd1753715187e6d870932b0904609e7c878e", size = 183841, upload-time = "2026-01-14T12:56:11.109Z" }, + { url = "https://files.pythonhosted.org/packages/58/6b/48702c61cf83e9c04ad5cec8cad7e5e22a2cde23a13db8ef341598897ddd/librt-0.7.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:00105e7d541a8f2ee5be52caacea98a005e0478cfe78c8080fbb7b5d2b340c63", size = 179751, upload-time = "2026-01-14T12:56:12.278Z" }, + { url = "https://files.pythonhosted.org/packages/35/87/5f607fc73a131d4753f4db948833063c6aad18e18a4e6fbf64316c37ae65/librt-0.7.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c6f8947d3dfd7f91066c5b4385812c18be26c9d5a99ca56667547f2c39149d94", size = 199319, upload-time = "2026-01-14T12:56:13.425Z" }, + { url = "https://files.pythonhosted.org/packages/6e/cc/b7c5ac28ae0f0645a9681248bae4ede665bba15d6f761c291853c5c5b78e/librt-0.7.8-cp39-cp39-win32.whl", hash = "sha256:41d7bb1e07916aeb12ae4a44e3025db3691c4149ab788d0315781b4d29b86afb", size = 43434, upload-time = "2026-01-14T12:56:14.781Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5d/dce0c92f786495adf2c1e6784d9c50a52fb7feb1cfb17af97a08281a6e82/librt-0.7.8-cp39-cp39-win_amd64.whl", hash = "sha256:e90a8e237753c83b8e484d478d9a996dc5e39fd5bd4c6ce32563bc8123f132be", size = 49801, upload-time = "2026-01-14T12:56:15.827Z" }, +] + +[[package]] +name = "logfire" +version = "4.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "executing", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-exporter-otlp-proto-http", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-instrumentation", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-sdk", marker = "python_full_version >= '3.10'" }, + { name = "protobuf", version = "6.33.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "rich", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/29/4386b331b8aecad429c043bbd1b7684cdb56ff9e11a5bd96845483dc0968/logfire-4.22.0.tar.gz", hash = "sha256:284b3b955c7515d4428dbc1e04784c3e652e62acf7597bd64a0aa9ecb6a7dedd", size = 654771, upload-time = "2026-02-04T12:17:57.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/ce/24a598c271d9631b09fb42f86f1e6d63b50647ecf0ce438d368a1e1af66b/logfire-4.22.0-py3-none-any.whl", hash = "sha256:9a0d20885613efe4bc9efcfa19a7943a6c642bc21339d926c51fcc74c0d083d6", size = 242637, upload-time = "2026-02-04T12:17:54.787Z" }, +] + +[package.optional-dependencies] +httpx = [ + { name = "opentelemetry-instrumentation-httpx", marker = "python_full_version >= '3.10'" }, +] + [[package]] name = "logfire-api" version = "4.22.0" @@ -2372,6 +3178,91 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/93/4e8395f3e4b1951b1de8d06cb7d7ef1105efd294ea3443bf39b3594390af/logfire_api-4.22.0-py3-none-any.whl", hash = "sha256:9fd2a2dac9cc3adf71ad4dac7bc9d26af326a5aa87445be90026048d22b92a8e", size = 98543, upload-time = "2026-02-04T12:17:56.365Z" }, ] +[[package]] +name = "lupa" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/1c/191c3e6ec6502e3dbe25a53e27f69a5daeac3e56de1f73c0138224171ead/lupa-2.6.tar.gz", hash = "sha256:9a770a6e89576be3447668d7ced312cd6fd41d3c13c2462c9dc2c2ab570e45d9", size = 7240282, upload-time = "2025-10-24T07:20:29.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/15/713cab5d0dfa4858f83b99b3e0329072df33dc14fc3ebbaa017e0f9755c4/lupa-2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b3dabda836317e63c5ad052826e156610f356a04b3003dfa0dbe66b5d54d671", size = 954828, upload-time = "2025-10-24T07:17:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/2e/71/704740cbc6e587dd6cc8dabf2f04820ac6a671784e57cc3c29db795476db/lupa-2.6-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8726d1c123bbe9fbb974ce29825e94121824e66003038ff4532c14cc2ed0c51c", size = 1919259, upload-time = "2025-10-24T07:17:18.586Z" }, + { url = "https://files.pythonhosted.org/packages/eb/18/f248341c423c5d48837e35584c6c3eb4acab7e722b6057d7b3e28e42dae8/lupa-2.6-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:f4e159e7d814171199b246f9235ca8961f6461ea8c1165ab428afa13c9289a94", size = 984998, upload-time = "2025-10-24T07:17:20.428Z" }, + { url = "https://files.pythonhosted.org/packages/44/1e/8a4bd471e018aad76bcb9455d298c2c96d82eced20f2ae8fcec8cd800948/lupa-2.6-cp310-cp310-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:202160e80dbfddfb79316692a563d843b767e0f6787bbd1c455f9d54052efa6c", size = 1174871, upload-time = "2025-10-24T07:17:22.755Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5c/3a3f23fd6a91b0986eea1ceaf82ad3f9b958fe3515a9981fb9c4eb046c8b/lupa-2.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5deede7c5b36ab64f869dae4831720428b67955b0bb186c8349cf6ea121c852b", size = 1057471, upload-time = "2025-10-24T07:17:24.908Z" }, + { url = "https://files.pythonhosted.org/packages/45/ac/01be1fed778fb0c8f46ee8cbe344e4d782f6806fac12717f08af87aa4355/lupa-2.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86f04901f920bbf7c0cac56807dc9597e42347123e6f1f3ca920f15f54188ce5", size = 2100592, upload-time = "2025-10-24T07:17:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6c/1a05bb873e30830f8574e10cd0b4cdbc72e9dbad2a09e25810b5e3b1f75d/lupa-2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6deef8f851d6afb965c84849aa5b8c38856942df54597a811ce0369ced678610", size = 1081396, upload-time = "2025-10-24T07:17:29.064Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c2/a19dd80d6dc98b39bbf8135b8198e38aa7ca3360b720eac68d1d7e9286b5/lupa-2.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:21f2b5549681c2a13b1170a26159d30875d367d28f0247b81ca347222c755038", size = 1192007, upload-time = "2025-10-24T07:17:31.362Z" }, + { url = "https://files.pythonhosted.org/packages/4f/43/e1b297225c827f55752e46fdbfb021c8982081b0f24490e42776ea69ae3b/lupa-2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:66eea57630eab5e6f49fdc5d7811c0a2a41f2011be4ea56a087ea76112011eb7", size = 2196661, upload-time = "2025-10-24T07:17:33.484Z" }, + { url = "https://files.pythonhosted.org/packages/2e/8f/2272d429a7fa9dc8dbd6e9c5c9073a03af6007eb22a4c78829fec6a34b80/lupa-2.6-cp310-cp310-win32.whl", hash = "sha256:60a403de8cab262a4fe813085dd77010effa6e2eb1886db2181df803140533b1", size = 1412738, upload-time = "2025-10-24T07:17:35.11Z" }, + { url = "https://files.pythonhosted.org/packages/35/2a/1708911271dd49ad87b4b373b5a4b0e0a0516d3d2af7b76355946c7ee171/lupa-2.6-cp310-cp310-win_amd64.whl", hash = "sha256:e4656a39d93dfa947cf3db56dc16c7916cb0cc8024acd3a952071263f675df64", size = 1656898, upload-time = "2025-10-24T07:17:36.949Z" }, + { url = "https://files.pythonhosted.org/packages/ca/29/1f66907c1ebf1881735afa695e646762c674f00738ebf66d795d59fc0665/lupa-2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d988c0f9331b9f2a5a55186701a25444ab10a1432a1021ee58011499ecbbdd5", size = 962875, upload-time = "2025-10-24T07:17:39.107Z" }, + { url = "https://files.pythonhosted.org/packages/e6/67/4a748604be360eb9c1c215f6a0da921cd1a2b44b2c5951aae6fb83019d3a/lupa-2.6-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:ebe1bbf48259382c72a6fe363dea61a0fd6fe19eab95e2ae881e20f3654587bf", size = 1935390, upload-time = "2025-10-24T07:17:41.427Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0c/8ef9ee933a350428b7bdb8335a37ef170ab0bb008bbf9ca8f4f4310116b6/lupa-2.6-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:a8fcee258487cf77cdd41560046843bb38c2e18989cd19671dd1e2596f798306", size = 992193, upload-time = "2025-10-24T07:17:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/65/46/e6c7facebdb438db8a65ed247e56908818389c1a5abbf6a36aab14f1057d/lupa-2.6-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:561a8e3be800827884e767a694727ed8482d066e0d6edfcbf423b05e63b05535", size = 1165844, upload-time = "2025-10-24T07:17:45.437Z" }, + { url = "https://files.pythonhosted.org/packages/1c/26/9f1154c6c95f175ccbf96aa96c8f569c87f64f463b32473e839137601a8b/lupa-2.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af880a62d47991cae78b8e9905c008cbfdc4a3a9723a66310c2634fc7644578c", size = 1048069, upload-time = "2025-10-24T07:17:47.181Z" }, + { url = "https://files.pythonhosted.org/packages/68/67/2cc52ab73d6af81612b2ea24c870d3fa398443af8e2875e5befe142398b1/lupa-2.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80b22923aa4023c86c0097b235615f89d469a0c4eee0489699c494d3367c4c85", size = 2079079, upload-time = "2025-10-24T07:17:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/2e/dc/f843f09bbf325f6e5ee61730cf6c3409fc78c010d968c7c78acba3019ca7/lupa-2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:153d2cc6b643f7efb9cfc0c6bb55ec784d5bac1a3660cfc5b958a7b8f38f4a75", size = 1071428, upload-time = "2025-10-24T07:17:51.991Z" }, + { url = "https://files.pythonhosted.org/packages/2e/60/37533a8d85bf004697449acb97ecdacea851acad28f2ad3803662487dd2a/lupa-2.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3fa8777e16f3ded50b72967dc17e23f5a08e4f1e2c9456aff2ebdb57f5b2869f", size = 1181756, upload-time = "2025-10-24T07:17:53.752Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/cf29b20dbb4927b6a3d27c339ac5d73e74306ecc28c8e2c900b2794142ba/lupa-2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8dbdcbe818c02a2f56f5ab5ce2de374dab03e84b25266cfbaef237829bc09b3f", size = 2175687, upload-time = "2025-10-24T07:17:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/050e02f80c7131b63db1474bff511e63c545b5a8636a24cbef3fc4da20b6/lupa-2.6-cp311-cp311-win32.whl", hash = "sha256:defaf188fde8f7a1e5ce3a5e6d945e533b8b8d547c11e43b96c9b7fe527f56dc", size = 1412592, upload-time = "2025-10-24T07:17:59.062Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/6f2af98aa5d771cea661f66c8eb8f53772ec1ab1dfbce24126cfcd189436/lupa-2.6-cp311-cp311-win_amd64.whl", hash = "sha256:9505ae600b5c14f3e17e70f87f88d333717f60411faca1ddc6f3e61dce85fa9e", size = 1669194, upload-time = "2025-10-24T07:18:01.647Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/ce243390535c39d53ea17ccf0240815e6e457e413e40428a658ea4ee4b8d/lupa-2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47ce718817ef1cc0c40d87c3d5ae56a800d61af00fbc0fad1ca9be12df2f3b56", size = 951707, upload-time = "2025-10-24T07:18:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/cedea5e6cbeb54396fdcc55f6b741696f3f036d23cfaf986d50d680446da/lupa-2.6-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7aba985b15b101495aa4b07112cdc08baa0c545390d560ad5cfde2e9e34f4d58", size = 1916703, upload-time = "2025-10-24T07:18:05.6Z" }, + { url = "https://files.pythonhosted.org/packages/24/be/3d6b5f9a8588c01a4d88129284c726017b2089f3a3fd3ba8bd977292fea0/lupa-2.6-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:b766f62f95b2739f2248977d29b0722e589dcf4f0ccfa827ccbd29f0148bd2e5", size = 985152, upload-time = "2025-10-24T07:18:08.561Z" }, + { url = "https://files.pythonhosted.org/packages/eb/23/9f9a05beee5d5dce9deca4cb07c91c40a90541fc0a8e09db4ee670da550f/lupa-2.6-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:00a934c23331f94cb51760097ebfab14b005d55a6b30a2b480e3c53dd2fa290d", size = 1159599, upload-time = "2025-10-24T07:18:10.346Z" }, + { url = "https://files.pythonhosted.org/packages/40/4e/e7c0583083db9d7f1fd023800a9767d8e4391e8330d56c2373d890ac971b/lupa-2.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21de9f38bd475303e34a042b7081aabdf50bd9bafd36ce4faea2f90fd9f15c31", size = 1038686, upload-time = "2025-10-24T07:18:12.112Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/5a4f7d959d4feba5e203ff0c31889e74d1ca3153122be4a46dca7d92bf7c/lupa-2.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf3bda96d3fc41237e964a69c23647d50d4e28421111360274d4799832c560e9", size = 2071956, upload-time = "2025-10-24T07:18:14.572Z" }, + { url = "https://files.pythonhosted.org/packages/92/34/2f4f13ca65d01169b1720176aedc4af17bc19ee834598c7292db232cb6dc/lupa-2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a76ead245da54801a81053794aa3975f213221f6542d14ec4b859ee2e7e0323", size = 1057199, upload-time = "2025-10-24T07:18:16.379Z" }, + { url = "https://files.pythonhosted.org/packages/35/2a/5f7d2eebec6993b0dcd428e0184ad71afb06a45ba13e717f6501bfed1da3/lupa-2.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8dd0861741caa20886ddbda0a121d8e52fb9b5bb153d82fa9bba796962bf30e8", size = 1173693, upload-time = "2025-10-24T07:18:18.153Z" }, + { url = "https://files.pythonhosted.org/packages/e4/29/089b4d2f8e34417349af3904bb40bec40b65c8731f45e3fd8d497ca573e5/lupa-2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:239e63948b0b23023f81d9a19a395e768ed3da6a299f84e7963b8f813f6e3f9c", size = 2164394, upload-time = "2025-10-24T07:18:20.403Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1b/79c17b23c921f81468a111cad843b076a17ef4b684c4a8dff32a7969c3f0/lupa-2.6-cp312-cp312-win32.whl", hash = "sha256:325894e1099499e7a6f9c351147661a2011887603c71086d36fe0f964d52d1ce", size = 1420647, upload-time = "2025-10-24T07:18:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/b8/15/5121e68aad3584e26e1425a5c9a79cd898f8a152292059e128c206ee817c/lupa-2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c735a1ce8ee60edb0fe71d665f1e6b7c55c6021f1d340eb8c865952c602cd36f", size = 1688529, upload-time = "2025-10-24T07:18:25.523Z" }, + { url = "https://files.pythonhosted.org/packages/28/1d/21176b682ca5469001199d8b95fa1737e29957a3d185186e7a8b55345f2e/lupa-2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:663a6e58a0f60e7d212017d6678639ac8df0119bc13c2145029dcba084391310", size = 947232, upload-time = "2025-10-24T07:18:27.878Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4c/d327befb684660ca13cf79cd1f1d604331808f9f1b6fb6bf57832f8edf80/lupa-2.6-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:d1f5afda5c20b1f3217a80e9bc1b77037f8a6eb11612fd3ada19065303c8f380", size = 1908625, upload-time = "2025-10-24T07:18:29.944Z" }, + { url = "https://files.pythonhosted.org/packages/66/8e/ad22b0a19454dfd08662237a84c792d6d420d36b061f239e084f29d1a4f3/lupa-2.6-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:26f2b3c085fe76e9119e48c1013c1cccdc1f51585d456858290475aa38e7089e", size = 981057, upload-time = "2025-10-24T07:18:31.553Z" }, + { url = "https://files.pythonhosted.org/packages/5c/48/74859073ab276bd0566c719f9ca0108b0cfc1956ca0d68678d117d47d155/lupa-2.6-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:60d2f902c7b96fb8ab98493dcff315e7bb4d0b44dc9dd76eb37de575025d5685", size = 1156227, upload-time = "2025-10-24T07:18:33.981Z" }, + { url = "https://files.pythonhosted.org/packages/09/6c/0e9ded061916877253c2266074060eb71ed99fb21d73c8c114a76725bce2/lupa-2.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a02d25dee3a3250967c36590128d9220ae02f2eda166a24279da0b481519cbff", size = 1035752, upload-time = "2025-10-24T07:18:36.32Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ef/f8c32e454ef9f3fe909f6c7d57a39f950996c37a3deb7b391fec7903dab7/lupa-2.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eae1ee16b886b8914ff292dbefbf2f48abfbdee94b33a88d1d5475e02423203", size = 2069009, upload-time = "2025-10-24T07:18:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/15b80c226a5225815a890ee1c11f07968e0aba7a852df41e8ae6fe285063/lupa-2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0edd5073a4ee74ab36f74fe61450148e6044f3952b8d21248581f3c5d1a58be", size = 1056301, upload-time = "2025-10-24T07:18:40.165Z" }, + { url = "https://files.pythonhosted.org/packages/31/14/2086c1425c985acfb30997a67e90c39457122df41324d3c179d6ee2292c6/lupa-2.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0c53ee9f22a8a17e7d4266ad48e86f43771951797042dd51d1494aaa4f5f3f0a", size = 1170673, upload-time = "2025-10-24T07:18:42.426Z" }, + { url = "https://files.pythonhosted.org/packages/10/e5/b216c054cf86576c0191bf9a9f05de6f7e8e07164897d95eea0078dca9b2/lupa-2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:de7c0f157a9064a400d828789191a96da7f4ce889969a588b87ec80de9b14772", size = 2162227, upload-time = "2025-10-24T07:18:46.112Z" }, + { url = "https://files.pythonhosted.org/packages/59/2f/33ecb5bedf4f3bc297ceacb7f016ff951331d352f58e7e791589609ea306/lupa-2.6-cp313-cp313-win32.whl", hash = "sha256:ee9523941ae0a87b5b703417720c5d78f72d2f5bc23883a2ea80a949a3ed9e75", size = 1419558, upload-time = "2025-10-24T07:18:48.371Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b4/55e885834c847ea610e111d87b9ed4768f0afdaeebc00cd46810f25029f6/lupa-2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b1335a5835b0a25ebdbc75cf0bda195e54d133e4d994877ef025e218c2e59db9", size = 1683424, upload-time = "2025-10-24T07:18:50.976Z" }, + { url = "https://files.pythonhosted.org/packages/66/9d/d9427394e54d22a35d1139ef12e845fd700d4872a67a34db32516170b746/lupa-2.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dcb6d0a3264873e1653bc188499f48c1fb4b41a779e315eba45256cfe7bc33c1", size = 953818, upload-time = "2025-10-24T07:18:53.378Z" }, + { url = "https://files.pythonhosted.org/packages/10/41/27bbe81953fb2f9ecfced5d9c99f85b37964cfaf6aa8453bb11283983721/lupa-2.6-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:a37e01f2128f8c36106726cb9d360bac087d58c54b4522b033cc5691c584db18", size = 1915850, upload-time = "2025-10-24T07:18:55.259Z" }, + { url = "https://files.pythonhosted.org/packages/a3/98/f9ff60db84a75ba8725506bbf448fb085bc77868a021998ed2a66d920568/lupa-2.6-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:458bd7e9ff3c150b245b0fcfbb9bd2593d1152ea7f0a7b91c1d185846da033fe", size = 982344, upload-time = "2025-10-24T07:18:57.05Z" }, + { url = "https://files.pythonhosted.org/packages/41/f7/f39e0f1c055c3b887d86b404aaf0ca197b5edfd235a8b81b45b25bac7fc3/lupa-2.6-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:052ee82cac5206a02df77119c325339acbc09f5ce66967f66a2e12a0f3211cad", size = 1156543, upload-time = "2025-10-24T07:18:59.251Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9c/59e6cffa0d672d662ae17bd7ac8ecd2c89c9449dee499e3eb13ca9cd10d9/lupa-2.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96594eca3c87dd07938009e95e591e43d554c1dbd0385be03c100367141db5a8", size = 1047974, upload-time = "2025-10-24T07:19:01.449Z" }, + { url = "https://files.pythonhosted.org/packages/23/c6/a04e9cef7c052717fcb28fb63b3824802488f688391895b618e39be0f684/lupa-2.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8faddd9d198688c8884091173a088a8e920ecc96cda2ffed576a23574c4b3f6", size = 2073458, upload-time = "2025-10-24T07:19:03.369Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/824173d10f38b51fc77785228f01411b6ca28826ce27404c7c912e0e442c/lupa-2.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:daebb3a6b58095c917e76ba727ab37b27477fb926957c825205fbda431552134", size = 1067683, upload-time = "2025-10-24T07:19:06.2Z" }, + { url = "https://files.pythonhosted.org/packages/b6/dc/9692fbcf3c924d9c4ece2d8d2f724451ac2e09af0bd2a782db1cef34e799/lupa-2.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f3154e68972befe0f81564e37d8142b5d5d79931a18309226a04ec92487d4ea3", size = 1171892, upload-time = "2025-10-24T07:19:08.544Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/e318b628d4643c278c96ab3ddea07fc36b075a57383c837f5b11e537ba9d/lupa-2.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e4dadf77b9fedc0bfa53417cc28dc2278a26d4cbd95c29f8927ad4d8fe0a7ef9", size = 2166641, upload-time = "2025-10-24T07:19:10.485Z" }, + { url = "https://files.pythonhosted.org/packages/12/f7/a6f9ec2806cf2d50826980cdb4b3cffc7691dc6f95e13cc728846d5cb793/lupa-2.6-cp314-cp314-win32.whl", hash = "sha256:cb34169c6fa3bab3e8ac58ca21b8a7102f6a94b6a5d08d3636312f3f02fafd8f", size = 1456857, upload-time = "2025-10-24T07:19:37.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/de/df71896f25bdc18360fdfa3b802cd7d57d7fede41a0e9724a4625b412c85/lupa-2.6-cp314-cp314-win_amd64.whl", hash = "sha256:b74f944fe46c421e25d0f8692aef1e842192f6f7f68034201382ac440ef9ea67", size = 1731191, upload-time = "2025-10-24T07:19:40.281Z" }, + { url = "https://files.pythonhosted.org/packages/47/3c/a1f23b01c54669465f5f4c4083107d496fbe6fb45998771420e9aadcf145/lupa-2.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0e21b716408a21ab65723f8841cf7f2f37a844b7a965eeabb785e27fca4099cf", size = 999343, upload-time = "2025-10-24T07:19:12.519Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6d/501994291cb640bfa2ccf7f554be4e6914afa21c4026bd01bff9ca8aac57/lupa-2.6-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:589db872a141bfff828340079bbdf3e9a31f2689f4ca0d88f97d9e8c2eae6142", size = 2000730, upload-time = "2025-10-24T07:19:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/53/a5/457ffb4f3f20469956c2d4c4842a7675e884efc895b2f23d126d23e126cc/lupa-2.6-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:cd852a91a4a9d4dcbb9a58100f820a75a425703ec3e3f049055f60b8533b7953", size = 1021553, upload-time = "2025-10-24T07:19:17.123Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/36bb5a5d0960f2a5c7c700e0819abb76fd9bf9c1d8a66e5106416d6e9b14/lupa-2.6-cp314-cp314t-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:0334753be028358922415ca97a64a3048e4ed155413fc4eaf87dd0a7e2752983", size = 1133275, upload-time = "2025-10-24T07:19:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/19/86/202ff4429f663013f37d2229f6176ca9f83678a50257d70f61a0a97281bf/lupa-2.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:661d895cd38c87658a34780fac54a690ec036ead743e41b74c3fb81a9e65a6aa", size = 1038441, upload-time = "2025-10-24T07:19:22.509Z" }, + { url = "https://files.pythonhosted.org/packages/a7/42/d8125f8e420714e5b52e9c08d88b5329dfb02dcca731b4f21faaee6cc5b5/lupa-2.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aa58454ccc13878cc177c62529a2056be734da16369e451987ff92784994ca7", size = 2058324, upload-time = "2025-10-24T07:19:24.979Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2c/47bf8b84059876e877a339717ddb595a4a7b0e8740bacae78ba527562e1c/lupa-2.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1425017264e470c98022bba8cff5bd46d054a827f5df6b80274f9cc71dafd24f", size = 1060250, upload-time = "2025-10-24T07:19:27.262Z" }, + { url = "https://files.pythonhosted.org/packages/c2/06/d88add2b6406ca1bdec99d11a429222837ca6d03bea42ca75afa169a78cb/lupa-2.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:224af0532d216e3105f0a127410f12320f7c5f1aa0300bdf9646b8d9afb0048c", size = 1151126, upload-time = "2025-10-24T07:19:29.522Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a0/89e6a024c3b4485b89ef86881c9d55e097e7cb0bdb74efb746f2fa6a9a76/lupa-2.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9abb98d5a8fd27c8285302e82199f0e56e463066f88f619d6594a450bf269d80", size = 2153693, upload-time = "2025-10-24T07:19:31.379Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/a0f007dc58fc1bbf51fb85dcc82fcb1f21b8c4261361de7dab0e3d8521ef/lupa-2.6-cp314-cp314t-win32.whl", hash = "sha256:1849efeba7a8f6fb8aa2c13790bee988fd242ae404bd459509640eeea3d1e291", size = 1590104, upload-time = "2025-10-24T07:19:33.514Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5e/db903ce9cf82c48d6b91bf6d63ae4c8d0d17958939a4e04ba6b9f38b8643/lupa-2.6-cp314-cp314t-win_amd64.whl", hash = "sha256:fc1498d1a4fc028bc521c26d0fad4ca00ed63b952e32fb95949bda76a04bad52", size = 1913818, upload-time = "2025-10-24T07:19:36.039Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c5/918ed6c3af793764bae155d68df47bab2635ab7c94dee3dbb5d9c6d5ba38/lupa-2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8897dc6c3249786b2cdf2f83324febb436193d4581b6a71dea49f77bf8b19bb0", size = 956718, upload-time = "2025-10-24T07:20:03.086Z" }, + { url = "https://files.pythonhosted.org/packages/da/91/0ca797da854478225c0f6a8fc3c500a2f5c11826d732735beb5dffff9e85/lupa-2.6-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:4446396ca3830be0c106c70db4b4f622c37b2d447874c07952cafb9c57949a4a", size = 1923934, upload-time = "2025-10-24T07:20:05.428Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7f/98a6a2535285d43457eb665822ab08447e2196b614db3152772d457ca426/lupa-2.6-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:5826e687c89995a6eaafeae242071ba16448eec1a9ee8e17ed48551b5d1e21c2", size = 987286, upload-time = "2025-10-24T07:20:07.338Z" }, + { url = "https://files.pythonhosted.org/packages/5f/50/edad7c180ab28aa543e6c3895e56a2c7a6ff92140a283316e6086f118552/lupa-2.6-cp39-cp39-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:5871935cb36d1d22f9c04ac0db75c06751bd95edcfa0d9309f732de908e297a9", size = 1176541, upload-time = "2025-10-24T07:20:09.305Z" }, + { url = "https://files.pythonhosted.org/packages/24/b3/27a0ec4c73011e86f9bd2eada010062308a4ed32921898d066ae54e064e1/lupa-2.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:43eb6e43ea8512d0d65b995d36dd9d77aa02598035e25b84c23a1b58700c9fb2", size = 1058981, upload-time = "2025-10-24T07:20:11.7Z" }, + { url = "https://files.pythonhosted.org/packages/a4/12/d55d45a8c253e7981f59ae920bac49dbd49888954b25fd1eb3a7be1321db/lupa-2.6-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:559714053018d9885cc8c36a33c5b7eb9aad30fb6357719cac3ce4dc6b39157e", size = 2103336, upload-time = "2025-10-24T07:20:13.71Z" }, + { url = "https://files.pythonhosted.org/packages/ab/17/058cc212c22d6a25990226afd02c741b2813b5f325396a9180b4accb70ac/lupa-2.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:57ac88a00ce59bd9d4ddcd4fca8e02564765725f5068786b011c9d1be3de20c5", size = 1083209, upload-time = "2025-10-24T07:20:15.587Z" }, + { url = "https://files.pythonhosted.org/packages/e6/01/8ca3a56a4e127784a15f0c7ec1f67e9894c2e9d4bf402389ddda4ea8081b/lupa-2.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:b683fbd867c2e54c44a686361b75eee7e7a790da55afdbe89f1f23b106de0274", size = 1193077, upload-time = "2025-10-24T07:20:17.857Z" }, + { url = "https://files.pythonhosted.org/packages/07/1b/c7fe79bcda6d489306bb7c1a9b4d545b7f0930b9ce80080643fc39b3fdc9/lupa-2.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d2f656903a2ed2e074bf2b7d300968028dfa327a45b055be8e3b51ef0b82f9bf", size = 2198584, upload-time = "2025-10-24T07:20:20.468Z" }, + { url = "https://files.pythonhosted.org/packages/9e/df/3f7631eea3478ac3868cbcb2763c1a4e2f7b875fcb2683f956bf7aabf65f/lupa-2.6-cp39-cp39-win32.whl", hash = "sha256:bf28f68ae231b72008523ab5ac23835ba0f76e0e99ec38b59766080a84eb596a", size = 1414693, upload-time = "2025-10-24T07:20:23.477Z" }, + { url = "https://files.pythonhosted.org/packages/08/e0/3fd9617814663129fa4a9b33a980c3fe344297337cb550c738f87f730f6b/lupa-2.6-cp39-cp39-win_amd64.whl", hash = "sha256:b4b2e9b3795a9897cf6cfcc58d08210fdc0d13ab47c9a0e13858c68932d8353c", size = 1658877, upload-time = "2025-10-24T07:20:27.086Z" }, +] + [[package]] name = "markdown" version = "3.9" @@ -2542,7 +3433,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.19.0" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio", marker = "python_full_version >= '3.10'" }, @@ -2551,15 +3442,18 @@ dependencies = [ { name = "jsonschema", marker = "python_full_version >= '3.10'" }, { name = "pydantic", marker = "python_full_version >= '3.10'" }, { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyjwt", extra = ["crypto"], marker = "python_full_version >= '3.10'" }, { name = "python-multipart", version = "0.0.22", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pywin32", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, { name = "sse-starlette", marker = "python_full_version >= '3.10'" }, - { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "starlette", version = "0.52.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "typing-inspection", marker = "python_full_version >= '3.10'" }, { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/2b/916852a5668f45d8787378461eaa1244876d77575ffef024483c94c0649c/mcp-1.19.0.tar.gz", hash = "sha256:213de0d3cd63f71bc08ffe9cc8d4409cc87acffd383f6195d2ce0457c021b5c1", size = 444163, upload-time = "2025-10-24T01:11:15.839Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/a3/3e71a875a08b6a830b88c40bc413bff01f1650f1efe8a054b5e90a9d4f56/mcp-1.19.0-py3-none-any.whl", hash = "sha256:f5907fe1c0167255f916718f376d05f09a830a215327a3ccdd5ec8a519f2e572", size = 170105, upload-time = "2025-10-24T01:11:14.151Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, ] [[package]] @@ -2695,7 +3589,7 @@ wheels = [ [[package]] name = "mkdocs-material" -version = "9.7.0" +version = "9.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, @@ -2711,9 +3605,9 @@ dependencies = [ { name = "pymdown-extensions" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/3b/111b84cd6ff28d9e955b5f799ef217a17bc1684ac346af333e6100e413cb/mkdocs_material-9.7.0.tar.gz", hash = "sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec", size = 4094546, upload-time = "2025-11-11T08:49:09.73Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/e2/2ffc356cd72f1473d07c7719d82a8f2cbd261666828614ecb95b12169f41/mkdocs_material-9.7.1.tar.gz", hash = "sha256:89601b8f2c3e6c6ee0a918cc3566cb201d40bf37c3cd3c2067e26fadb8cce2b8", size = 4094392, upload-time = "2025-12-18T09:49:00.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/87/eefe8d5e764f4cf50ed91b943f8e8f96b5efd65489d8303b7a36e2e79834/mkdocs_material-9.7.0-py3-none-any.whl", hash = "sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887", size = 9283770, upload-time = "2025-11-11T08:49:06.26Z" }, + { url = "https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl", hash = "sha256:3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c", size = 9297166, upload-time = "2025-12-18T09:48:56.664Z" }, ] [[package]] @@ -2741,15 +3635,17 @@ wheels = [ name = "mkdocstrings" version = "0.30.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, - { name = "jinja2" }, + { name = "jinja2", marker = "python_full_version < '3.10'" }, { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "markupsafe" }, - { name = "mkdocs" }, - { name = "mkdocs-autorefs" }, - { name = "pymdown-extensions" }, + { name = "markupsafe", marker = "python_full_version < '3.10'" }, + { name = "mkdocs", marker = "python_full_version < '3.10'" }, + { name = "mkdocs-autorefs", marker = "python_full_version < '3.10'" }, + { name = "pymdown-extensions", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/33/2fa3243439f794e685d3e694590d28469a9b8ea733af4b48c250a3ffc9a0/mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f", size = 106350, upload-time = "2025-09-19T10:49:26.446Z" } wheels = [ @@ -2759,6 +3655,31 @@ wheels = [ [package.optional-dependencies] python = [ { name = "mkdocstrings-python", version = "1.18.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] + +[[package]] +name = "mkdocstrings" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +dependencies = [ + { name = "jinja2", marker = "python_full_version >= '3.10'" }, + { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markupsafe", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs", marker = "python_full_version >= '3.10'" }, + { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" }, + { name = "pymdown-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/4d/1ca8a9432579184599714aaeb36591414cc3d3bfd9d494f6db540c995ae4/mkdocstrings-1.0.2.tar.gz", hash = "sha256:48edd0ccbcb9e30a3121684e165261a9d6af4d63385fc4f39a54a49ac3b32ea8", size = 101048, upload-time = "2026-01-24T15:57:25.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/32/407a9a5fdd7d8ecb4af8d830b9bcdf47ea68f916869b3f44bac31f081250/mkdocstrings-1.0.2-py3-none-any.whl", hash = "sha256:41897815a8026c3634fe5d51472c3a569f92ded0ad8c7a640550873eea3b6817", size = 35443, upload-time = "2026-01-24T15:57:23.933Z" }, +] + +[package.optional-dependencies] +python = [ { name = "mkdocstrings-python", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] @@ -2772,7 +3693,7 @@ resolution-markers = [ dependencies = [ { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "mkdocs-autorefs", marker = "python_full_version < '3.10'" }, - { name = "mkdocstrings", marker = "python_full_version < '3.10'" }, + { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/ae/58ab2bfbee2792e92a98b97e872f7c003deb903071f75d8d83aa55db28fa/mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323", size = 207972, upload-time = "2025-08-28T16:11:19.847Z" } @@ -2791,7 +3712,7 @@ resolution-markers = [ dependencies = [ { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" }, - { name = "mkdocstrings", marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" } @@ -2800,47 +3721,220 @@ wheels = [ ] [[package]] -name = "mypy" -version = "1.14.1" +name = "more-itertools" +version = "10.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/74525ebe3eb5fddcd6735fc03cbea3feeed4122b53bc798ac32d297ac9ae/multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f", size = 77107, upload-time = "2026-01-26T02:46:12.608Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9a/ce8744e777a74b3050b1bf56be3eed1053b3457302ea055f1ea437200a23/multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358", size = 44943, upload-time = "2026-01-26T02:46:14.016Z" }, + { url = "https://files.pythonhosted.org/packages/83/9c/1d2a283d9c6f31e260cb6c2fccadc3edcf6c4c14ee0929cd2af4d2606dd7/multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5", size = 44603, upload-time = "2026-01-26T02:46:15.391Z" }, + { url = "https://files.pythonhosted.org/packages/87/9d/3b186201671583d8e8d6d79c07481a5aafd0ba7575e3d8566baec80c1e82/multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0", size = 240573, upload-time = "2026-01-26T02:46:16.783Z" }, + { url = "https://files.pythonhosted.org/packages/42/7d/a52f5d4d0754311d1ac78478e34dff88de71259a8585e05ee14e5f877caf/multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8", size = 240106, upload-time = "2026-01-26T02:46:18.432Z" }, + { url = "https://files.pythonhosted.org/packages/84/9f/d80118e6c30ff55b7d171bdc5520aad4b9626e657520b8d7c8ca8c2fad12/multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0", size = 219418, upload-time = "2026-01-26T02:46:20.526Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bd/896e60b3457f194de77c7de64f9acce9f75da0518a5230ce1df534f6747b/multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f", size = 252124, upload-time = "2026-01-26T02:46:22.157Z" }, + { url = "https://files.pythonhosted.org/packages/f4/de/ba6b30447c36a37078d0ba604aa12c1a52887af0c355236ca6e0a9d5286f/multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f", size = 249402, upload-time = "2026-01-26T02:46:23.718Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b2/50a383c96230e432895a2fd3bcfe1b65785899598259d871d5de6b93180c/multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e", size = 240346, upload-time = "2026-01-26T02:46:25.393Z" }, + { url = "https://files.pythonhosted.org/packages/89/37/16d391fd8da544b1489306e38a46785fa41dd0f0ef766837ed7d4676dde0/multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2", size = 237010, upload-time = "2026-01-26T02:46:27.408Z" }, + { url = "https://files.pythonhosted.org/packages/b0/24/3152ee026eda86d5d3e3685182911e6951af7a016579da931080ce6ac9ad/multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8", size = 232018, upload-time = "2026-01-26T02:46:29.941Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1f/48d3c27a72be7fd23a55d8847193c459959bf35a5bb5844530dab00b739b/multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941", size = 241498, upload-time = "2026-01-26T02:46:32.052Z" }, + { url = "https://files.pythonhosted.org/packages/1a/45/413643ae2952d0decdf6c1250f86d08a43e143271441e81027e38d598bd7/multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a", size = 247957, upload-time = "2026-01-26T02:46:33.666Z" }, + { url = "https://files.pythonhosted.org/packages/50/f8/f1d0ac23df15e0470776388bdb261506f63af1f81d28bacb5e262d6e12b6/multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de", size = 241651, upload-time = "2026-01-26T02:46:35.7Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c9/1a2a18f383cf129add66b6c36b75c3911a7ba95cf26cb141482de085cc12/multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5", size = 236371, upload-time = "2026-01-26T02:46:37.37Z" }, + { url = "https://files.pythonhosted.org/packages/bb/aa/77d87e3fca31325b87e0eb72d5fe9a7472dcb51391a42df7ac1f3842f6c0/multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0", size = 41426, upload-time = "2026-01-26T02:46:39.026Z" }, + { url = "https://files.pythonhosted.org/packages/e3/b3/e8863e6a2da15a9d7e98976ff402e871b7352c76566df6c18d0378e0d9cf/multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4", size = 46180, upload-time = "2026-01-26T02:46:40.422Z" }, + { url = "https://files.pythonhosted.org/packages/93/d3/dd4fa951ad5b5fa216bf30054d705683d13405eea7459833d78f31b74c9c/multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9", size = 43231, upload-time = "2026-01-26T02:46:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, + { name = "pathspec" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051, upload-time = "2024-12-30T16:39:07.335Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002, upload-time = "2024-12-30T16:37:22.435Z" }, - { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400, upload-time = "2024-12-30T16:37:53.526Z" }, - { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172, upload-time = "2024-12-30T16:37:50.332Z" }, - { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732, upload-time = "2024-12-30T16:37:29.96Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197, upload-time = "2024-12-30T16:38:05.037Z" }, - { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836, upload-time = "2024-12-30T16:37:19.726Z" }, - { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432, upload-time = "2024-12-30T16:37:11.533Z" }, - { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515, upload-time = "2024-12-30T16:37:40.724Z" }, - { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791, upload-time = "2024-12-30T16:36:58.73Z" }, - { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203, upload-time = "2024-12-30T16:37:03.741Z" }, - { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900, upload-time = "2024-12-30T16:37:57.948Z" }, - { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869, upload-time = "2024-12-30T16:37:33.428Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668, upload-time = "2024-12-30T16:38:02.211Z" }, - { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060, upload-time = "2024-12-30T16:37:46.131Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167, upload-time = "2024-12-30T16:37:43.534Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341, upload-time = "2024-12-30T16:37:36.249Z" }, - { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991, upload-time = "2024-12-30T16:37:06.743Z" }, - { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016, upload-time = "2024-12-30T16:37:15.02Z" }, - { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097, upload-time = "2024-12-30T16:37:25.144Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728, upload-time = "2024-12-30T16:38:08.634Z" }, - { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965, upload-time = "2024-12-30T16:38:12.132Z" }, - { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660, upload-time = "2024-12-30T16:38:17.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198, upload-time = "2024-12-30T16:38:32.839Z" }, - { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276, upload-time = "2024-12-30T16:38:20.828Z" }, - { url = "https://files.pythonhosted.org/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35", size = 11200493, upload-time = "2024-12-30T16:38:26.935Z" }, - { url = "https://files.pythonhosted.org/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc", size = 10357702, upload-time = "2024-12-30T16:38:50.623Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9", size = 12091104, upload-time = "2024-12-30T16:38:53.735Z" }, - { url = "https://files.pythonhosted.org/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb", size = 12830167, upload-time = "2024-12-30T16:38:56.437Z" }, - { url = "https://files.pythonhosted.org/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60", size = 13013834, upload-time = "2024-12-30T16:38:59.204Z" }, - { url = "https://files.pythonhosted.org/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c", size = 9781231, upload-time = "2024-12-30T16:39:05.124Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905, upload-time = "2024-12-30T16:38:42.021Z" }, + { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/88436084550ca9af5e610fa45286be04c3b63374df3e021c762fe8c4369f/mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3", size = 13102606, upload-time = "2025-12-15T05:02:46.833Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a5/43dfad311a734b48a752790571fd9e12d61893849a01bff346a54011957f/mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a", size = 12164496, upload-time = "2025-12-15T05:03:41.947Z" }, + { url = "https://files.pythonhosted.org/packages/88/f0/efbfa391395cce2f2771f937e0620cfd185ec88f2b9cd88711028a768e96/mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67", size = 12772068, upload-time = "2025-12-15T05:02:53.689Z" }, + { url = "https://files.pythonhosted.org/packages/25/05/58b3ba28f5aed10479e899a12d2120d582ba9fa6288851b20bf1c32cbb4f/mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e", size = 13520385, upload-time = "2025-12-15T05:02:38.328Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a0/c006ccaff50b31e542ae69b92fe7e2f55d99fba3a55e01067dd564325f85/mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376", size = 13796221, upload-time = "2025-12-15T05:03:22.147Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ff/8bdb051cd710f01b880472241bd36b3f817a8e1c5d5540d0b761675b6de2/mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24", size = 10055456, upload-time = "2025-12-15T05:03:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] [[package]] @@ -2852,6 +3946,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "nexus-rpc" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/66/540687556bd28cf1ec370cc6881456203dfddb9dab047b8979c6865b5984/nexus_rpc-1.1.0.tar.gz", hash = "sha256:d65ad6a2f54f14e53ebe39ee30555eaeb894102437125733fb13034a04a44553", size = 77383, upload-time = "2025-07-07T19:03:58.368Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/2f/9e9d0dcaa4c6ffa22b7aa31069a8a264c753ff8027b36af602cce038c92f/nexus_rpc-1.1.0-py3-none-any.whl", hash = "sha256:d1b007af2aba186a27e736f8eaae39c03aed05b488084ff6c3d1785c9ba2ad38", size = 27743, upload-time = "2025-07-07T19:03:57.556Z" }, +] + +[[package]] +name = "nexus-rpc" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +dependencies = [ + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/50/95d7bc91f900da5e22662c82d9bf0f72a4b01f2a552708bf2f43807707a1/nexus_rpc-1.2.0.tar.gz", hash = "sha256:b4ddaffa4d3996aaeadf49b80dfcdfbca48fe4cb616defaf3b3c5c2c8fc61890", size = 74142, upload-time = "2025-11-17T19:17:06.798Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/04/eaac430d0e6bf21265ae989427d37e94be5e41dc216879f1fbb6c5339942/nexus_rpc-1.2.0-py3-none-any.whl", hash = "sha256:977876f3af811ad1a09b2961d3d1ac9233bda43ff0febbb0c9906483b9d9f8a3", size = 28166, upload-time = "2025-11-17T19:17:05.64Z" }, +] + [[package]] name = "openai" version = "2.17.0" @@ -2871,6 +3996,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/97/284535aa75e6e84ab388248b5a323fc296b1f70530130dee37f7f4fbe856/openai-2.17.0-py3-none-any.whl", hash = "sha256:4f393fd886ca35e113aac7ff239bcd578b81d8f104f5aedc7d3693eb2af1d338", size = 1069524, upload-time = "2026-02-05T16:27:38.941Z" }, ] +[[package]] +name = "openapi-pydantic" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, +] + [[package]] name = "opentelemetry-api" version = "1.39.1" @@ -2884,6 +4021,115 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, ] +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-exporter-otlp-proto-common", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-proto", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-sdk", marker = "python_full_version >= '3.10'" }, + { name = "requests", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-semantic-conventions", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "wrapt", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-httpx" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-instrumentation", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-semantic-conventions", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-util-http", marker = "python_full_version >= '3.10'" }, + { name = "wrapt", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/08/11208bcfcab4fc2023252c3f322aa397fd9ad948355fea60f5fc98648603/opentelemetry_instrumentation_httpx-0.60b1.tar.gz", hash = "sha256:a506ebaf28c60112cbe70ad4f0338f8603f148938cb7b6794ce1051cd2b270ae", size = 20611, upload-time = "2025-12-11T13:37:01.661Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/59/b98e84eebf745ffc75397eaad4763795bff8a30cbf2373a50ed4e70646c5/opentelemetry_instrumentation_httpx-0.60b1-py3-none-any.whl", hash = "sha256:f37636dd742ad2af83d896ba69601ed28da51fa4e25d1ab62fde89ce413e275b", size = 15701, upload-time = "2025-12-11T13:36:04.56Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf", version = "6.33.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-semantic-conventions", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, +] + +[[package]] +name = "opentelemetry-util-http" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/fc/c47bb04a1d8a941a4061307e1eddfa331ed4d0ab13d8a9781e6db256940a/opentelemetry_util_http-0.60b1.tar.gz", hash = "sha256:0d97152ca8c8a41ced7172d29d3622a219317f74ae6bb3027cfbdcf22c3cc0d6", size = 11053, upload-time = "2025-12-11T13:37:25.115Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/5c/d3f1733665f7cd582ef0842fb1d2ed0bc1fba10875160593342d22bba375/opentelemetry_util_http-0.60b1-py3-none-any.whl", hash = "sha256:66381ba28550c91bee14dcba8979ace443444af1ed609226634596b4b0faf199", size = 8947, upload-time = "2025-12-11T13:36:37.151Z" }, +] + [[package]] name = "orjson" version = "3.11.5" @@ -3080,11 +4326,11 @@ wheels = [ [[package]] name = "packaging" -version = "26.0" +version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] @@ -3096,6 +4342,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, ] +[[package]] +name = "pathable" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124, upload-time = "2025-01-10T18:43:13.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" }, +] + [[package]] name = "pathspec" version = "1.0.4" @@ -3105,10 +4360,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] +[[package]] +name = "pathvalidate" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, +] + [[package]] name = "pillow" version = "11.3.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" }, @@ -3218,6 +4485,108 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, ] +[[package]] +name = "pillow" +version = "12.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" }, + { url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" }, + { url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" }, + { url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, + { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, + { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, + { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" }, + { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" }, + { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" }, + { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" }, + { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" }, + { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" }, + { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" }, + { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" }, + { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" }, + { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" }, + { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" }, + { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" }, + { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" }, + { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" }, + { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" }, + { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" }, + { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" }, + { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" }, + { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" }, + { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, + { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, +] + [[package]] name = "platformdirs" version = "4.4.0" @@ -3274,28 +4643,35 @@ wheels = [ [[package]] name = "prek" -version = "0.2.22" +version = "0.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/a7/1e07536315f77d7b233cbf3dd916dc3424239c435ee0a0110c9b2cbcf6b0/prek-0.2.22.tar.gz", hash = "sha256:5abbda8bae0a63a18d3fe573162e8504a7b100e3603169cc2d06053891a02d7c", size = 267212, upload-time = "2025-12-13T12:57:51.797Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/f5/ee52def928dd1355c20bcfcf765e1e61434635c33f3075e848e7b83a157b/prek-0.3.2.tar.gz", hash = "sha256:dce0074ff1a21290748ca567b4bda7553ee305a8c7b14d737e6c58364a499364", size = 334229, upload-time = "2026-02-06T13:49:47.539Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/fe/ba9a940adc55d78b96b58376a8752e95261402c1e5812acce6ea1a000fb8/prek-0.2.22-py3-none-linux_armv6l.whl", hash = "sha256:d026b2d75529a743466000e8dd058d3d5e7c597c34905b333f2ede3d24cb23f1", size = 4798026, upload-time = "2025-12-13T12:57:45.286Z" }, - { url = "https://files.pythonhosted.org/packages/12/40/459cf510491271b08d19b4ef34f8293440eb472e633f4ffaf34179f39a12/prek-0.2.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:60b5bec94fa9f59fe5a9e90554c7346ceef81ea33d01bb18172d2576b07ac449", size = 4894023, upload-time = "2025-12-13T12:57:40.102Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0b/59e0438b1e7d1b6fa3f14174a916d369e27c421f8876f7ec7c7a52fbfae7/prek-0.2.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a0c7c6ceee536122916d32d26b6fa4fac9e95ba28631901164ffc0b0fed28a9e", size = 4615858, upload-time = "2025-12-13T12:57:57.471Z" }, - { url = "https://files.pythonhosted.org/packages/e9/27/ea40cf715717298fdf802da2b15a2c4445b8c114aae28cab6bf794d65670/prek-0.2.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c90273bef7b638dfc36dede62c494f958456330375ffce891c68321b2a7b46ba", size = 4810206, upload-time = "2025-12-13T12:57:46.534Z" }, - { url = "https://files.pythonhosted.org/packages/b5/12/d1c3db35839492236afb8642a2818d5b413e5fce4ea909bc7ddfb3d4591a/prek-0.2.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d8ecf202073433b87df2671a98bc44d3b68bb5711f7119b50b7bd65c2a67f13", size = 4722439, upload-time = "2025-12-13T12:57:48.106Z" }, - { url = "https://files.pythonhosted.org/packages/64/a0/0f24a9cacd5d78119f47063d860e03fa42b4d7dcf6803a49b0bef51b771b/prek-0.2.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4f26d76247ce7671cf5d9786e7fc86fdb43c065fd5507e8d64b3de7fd5e4447", size = 5037705, upload-time = "2025-12-13T12:57:50.596Z" }, - { url = "https://files.pythonhosted.org/packages/ca/6e/7616f84141755f1d9fe232f0bd06589421ae0dabd99180fdae2840d22ae8/prek-0.2.22-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac1f0ea2c82e35eb0ffc98dfbcd9ee34cfd7350b64f97198da4c311a271cdb8f", size = 5453199, upload-time = "2025-12-13T12:57:38.458Z" }, - { url = "https://files.pythonhosted.org/packages/51/80/542a583db9b27bfd34954243666e451b266513bc742e0491cd61ff1b390e/prek-0.2.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f5d5131b9e57548f64d74665fd4414a8deb603a67d52ee18b3e6540cdb77733", size = 5399635, upload-time = "2025-12-13T12:57:43.359Z" }, - { url = "https://files.pythonhosted.org/packages/49/06/ca4e6fee73e14e1aced90f5c83b9cdf9a8e1c3b1aa1e4f45a2a65de05a28/prek-0.2.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a768484e1c94a33228765f63701261316b64e11c482abe2a35c54045d3f81feb", size = 5498340, upload-time = "2025-12-13T12:57:41.827Z" }, - { url = "https://files.pythonhosted.org/packages/85/a8/9636fc782db9c22d1740a8e5dc4e1ffc3a28099d074f812da46332e7c7a7/prek-0.2.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c941c8503ea537a84ea97074dc97b0f0dfd9861864883eb8b90586ed321847e", size = 5078431, upload-time = "2025-12-13T12:57:31.664Z" }, - { url = "https://files.pythonhosted.org/packages/4f/29/e78d2f444cf1f097aaaefee8910d7b9fe34195f06b086e0d2153b6c66e07/prek-0.2.22-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:946c6cfe18b17a7b53c49a389bf65f1e8e45a1b96bfdaeeacde21f5b5ca2d149", size = 4820871, upload-time = "2025-12-13T12:57:33.074Z" }, - { url = "https://files.pythonhosted.org/packages/fb/ec/779db6c35663e949b3f9989c584297aa115d3cc44822c149dbe40d51cd14/prek-0.2.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2cffd5809cf678b4300378d612b5da12cd2183ddc7aee78178db0b1ea48f0069", size = 4834431, upload-time = "2025-12-13T12:57:34.65Z" }, - { url = "https://files.pythonhosted.org/packages/1d/18/12bb4fece680457f4d4f13d21c5784675ce8b1db5c968261348c52087232/prek-0.2.22-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:f91df793cbc28647863eb54d578f37782736726671838ca92c9d0601329cb928", size = 4709742, upload-time = "2025-12-13T12:57:52.706Z" }, - { url = "https://files.pythonhosted.org/packages/3f/27/de1d9d037f59393568713121f4bfcea11cd546dcf96f214827983b8beccf/prek-0.2.22-py3-none-musllinux_1_1_i686.whl", hash = "sha256:3c40ba36b3e89817b20efe6163fd15387b81caf1f489060265d84103ae6e5184", size = 4925048, upload-time = "2025-12-13T12:57:49.348Z" }, - { url = "https://files.pythonhosted.org/packages/49/bf/d40eef2e5ccbc520da94c2463450d0ecab598c092684002b463fd5491ff6/prek-0.2.22-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:a4154a419581723d12eccaa5b1d27686283c5c78b753c1984270d7e144a15fa7", size = 5192083, upload-time = "2025-12-13T12:57:35.957Z" }, - { url = "https://files.pythonhosted.org/packages/41/ba/11ea837a876dcc7f5df85962bc560c8627a962261f046a1615b0a6016b01/prek-0.2.22-py3-none-win32.whl", hash = "sha256:9fd3d629a256ce3171bebc3183f9c608022fff0db19a511307ab0f4c7682d5e3", size = 4586129, upload-time = "2025-12-13T12:57:54.438Z" }, - { url = "https://files.pythonhosted.org/packages/2f/8c/05ab6d11ac670664c99944e4819a77a63360aab253d8daf4ae411c705bcd/prek-0.2.22-py3-none-win_amd64.whl", hash = "sha256:ad7997ae4bef4fccc0a6761c00479bdd44f2a5bb7eb97aebda3b42fe785e10a1", size = 5273787, upload-time = "2025-12-13T12:57:37.205Z" }, - { url = "https://files.pythonhosted.org/packages/38/7a/53e8a550df705b5bf78a589c4e11d21485ac38c1a65e9c98fc3169a5eb25/prek-0.2.22-py3-none-win_arm64.whl", hash = "sha256:2442c0f12bd57675124542a92f5c799e7ffe52dc7cd98301c43c361849a3aef6", size = 4941186, upload-time = "2025-12-13T12:57:56.214Z" }, + { url = "https://files.pythonhosted.org/packages/76/69/70a5fc881290a63910494df2677c0fb241d27cfaa435bbcd0de5cd2e2443/prek-0.3.2-py3-none-linux_armv6l.whl", hash = "sha256:4f352f9c3fc98aeed4c8b2ec4dbf16fc386e45eea163c44d67e5571489bd8e6f", size = 4614960, upload-time = "2026-02-06T13:50:05.818Z" }, + { url = "https://files.pythonhosted.org/packages/c0/15/a82d5d32a2207ccae5d86ea9e44f2b93531ed000faf83a253e8d1108e026/prek-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4a000cfbc3a6ec7d424f8be3c3e69ccd595448197f92daac8652382d0acc2593", size = 4622889, upload-time = "2026-02-06T13:49:53.662Z" }, + { url = "https://files.pythonhosted.org/packages/89/75/ea833b58a12741397017baef9b66a6e443bfa8286ecbd645d14111446280/prek-0.3.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5436bdc2702cbd7bcf9e355564ae66f8131211e65fefae54665a94a07c3d450a", size = 4239653, upload-time = "2026-02-06T13:50:02.88Z" }, + { url = "https://files.pythonhosted.org/packages/10/b4/d9c3885987afac6e20df4cb7db14e3b0d5a08a77ae4916488254ebac4d0b/prek-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:0161b5f584f9e7f416d6cf40a17b98f17953050ff8d8350ec60f20fe966b86b6", size = 4595101, upload-time = "2026-02-06T13:49:49.813Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/1a06473ed83dbc898de22838abdb13954e2583ce229f857f61828384634c/prek-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e641e8533bca38797eebb49aa89ed0e8db0e61225943b27008c257e3af4d631", size = 4521978, upload-time = "2026-02-06T13:49:41.266Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5e/c38390d5612e6d86b32151c1d2fdab74a57913473193591f0eb00c894c21/prek-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfca1810d49d3f9ef37599c958c4e716bc19a1d78a7e88cbdcb332e0b008994f", size = 4829108, upload-time = "2026-02-06T13:49:44.598Z" }, + { url = "https://files.pythonhosted.org/packages/80/a6/cecce2ab623747ff65ed990bb0d95fa38449ee19b348234862acf9392fff/prek-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d69d754299a95a85dc20196f633232f306bee7e7c8cba61791f49ce70404ec", size = 5357520, upload-time = "2026-02-06T13:49:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/a5/18/d6bcb29501514023c76d55d5cd03bdbc037737c8de8b6bc41cdebfb1682c/prek-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:539dcb90ad9b20837968539855df6a29493b328a1ae87641560768eed4f313b0", size = 4852635, upload-time = "2026-02-06T13:49:58.347Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0a/ae46f34ba27ba87aea5c9ad4ac9cd3e07e014fd5079ae079c84198f62118/prek-0.3.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:1998db3d0cbe243984736c82232be51318f9192e2433919a6b1c5790f600b5fd", size = 4599484, upload-time = "2026-02-06T13:49:43.296Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a9/73bfb5b3f7c3583f9b0d431924873928705cdef6abb3d0461c37254a681b/prek-0.3.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:07ab237a5415a3e8c0db54de9d63899bcd947624bdd8820d26f12e65f8d19eb7", size = 4657694, upload-time = "2026-02-06T13:50:01.074Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/0994bc176e1a80110fad3babce2c98b0ac4007630774c9e18fc200a34781/prek-0.3.2-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:0ced19701d69c14a08125f14a5dd03945982edf59e793c73a95caf4697a7ac30", size = 4509337, upload-time = "2026-02-06T13:49:54.891Z" }, + { url = "https://files.pythonhosted.org/packages/f9/13/e73f85f65ba8f626468e5d1694ab3763111513da08e0074517f40238c061/prek-0.3.2-py3-none-musllinux_1_1_i686.whl", hash = "sha256:ffb28189f976fa111e770ee94e4f298add307714568fb7d610c8a7095cb1ce59", size = 4697350, upload-time = "2026-02-06T13:50:04.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/47/98c46dcd580305b9960252a4eb966f1a7b1035c55c363f378d85662ba400/prek-0.3.2-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:f63134b3eea14421789a7335d86f99aee277cb520427196f2923b9260c60e5c5", size = 4955860, upload-time = "2026-02-06T13:49:56.581Z" }, + { url = "https://files.pythonhosted.org/packages/73/42/1bb4bba3ff47897df11e9dfd774027cdfa135482c961a54e079af0faf45a/prek-0.3.2-py3-none-win32.whl", hash = "sha256:58c806bd1344becd480ef5a5ba348846cc000af0e1fbe854fef91181a2e06461", size = 4267619, upload-time = "2026-02-06T13:49:39.503Z" }, + { url = "https://files.pythonhosted.org/packages/97/11/6665f47a7c350d83de17403c90bbf7a762ef50876ece456a86f64f46fbfb/prek-0.3.2-py3-none-win_amd64.whl", hash = "sha256:70114b48e9eb8048b2c11b4c7715ce618529c6af71acc84dd8877871a2ef71a6", size = 4624324, upload-time = "2026-02-06T13:49:45.922Z" }, + { url = "https://files.pythonhosted.org/packages/22/e7/740997ca82574d03426f897fd88afe3fc8a7306b8c7ea342a8bc1c538488/prek-0.3.2-py3-none-win_arm64.whl", hash = "sha256:9144d176d0daa2469a25c303ef6f6fa95a8df015eb275232f5cb53551ecefef0", size = 4336008, upload-time = "2026-02-06T13:49:52.27Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, ] [[package]] @@ -3310,6 +4686,175 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/0e/934b541323035566a9af292dba85a195f7b78179114f2c6ebb24551118a9/propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db", size = 79534, upload-time = "2025-10-08T19:46:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/db0d03d96726d995dc7171286c6ba9d8d14251f37433890f88368951a44e/propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8", size = 45526, upload-time = "2025-10-08T19:46:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c3/82728404aea669e1600f304f2609cde9e665c18df5a11cdd57ed73c1dceb/propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925", size = 47263, upload-time = "2025-10-08T19:46:05.405Z" }, + { url = "https://files.pythonhosted.org/packages/df/1b/39313ddad2bf9187a1432654c38249bab4562ef535ef07f5eb6eb04d0b1b/propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21", size = 201012, upload-time = "2025-10-08T19:46:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/5b/01/f1d0b57d136f294a142acf97f4ed58c8e5b974c21e543000968357115011/propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5", size = 209491, upload-time = "2025-10-08T19:46:08.909Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c8/038d909c61c5bb039070b3fb02ad5cccdb1dde0d714792e251cdb17c9c05/propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db", size = 215319, upload-time = "2025-10-08T19:46:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/08/57/8c87e93142b2c1fa2408e45695205a7ba05fb5db458c0bf5c06ba0e09ea6/propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7", size = 196856, upload-time = "2025-10-08T19:46:12.003Z" }, + { url = "https://files.pythonhosted.org/packages/42/df/5615fec76aa561987a534759b3686008a288e73107faa49a8ae5795a9f7a/propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4", size = 193241, upload-time = "2025-10-08T19:46:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/62949eb3a7a54afe8327011c90aca7e03547787a88fb8bd9726806482fea/propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60", size = 190552, upload-time = "2025-10-08T19:46:14.938Z" }, + { url = "https://files.pythonhosted.org/packages/30/ee/ab4d727dd70806e5b4de96a798ae7ac6e4d42516f030ee60522474b6b332/propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f", size = 200113, upload-time = "2025-10-08T19:46:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0b/38b46208e6711b016aa8966a3ac793eee0d05c7159d8342aa27fc0bc365e/propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900", size = 200778, upload-time = "2025-10-08T19:46:18.023Z" }, + { url = "https://files.pythonhosted.org/packages/cf/81/5abec54355ed344476bee711e9f04815d4b00a311ab0535599204eecc257/propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c", size = 193047, upload-time = "2025-10-08T19:46:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b6/1f237c04e32063cb034acd5f6ef34ef3a394f75502e72703545631ab1ef6/propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb", size = 38093, upload-time = "2025-10-08T19:46:20.643Z" }, + { url = "https://files.pythonhosted.org/packages/a6/67/354aac4e0603a15f76439caf0427781bcd6797f370377f75a642133bc954/propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37", size = 41638, upload-time = "2025-10-08T19:46:21.935Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e1/74e55b9fd1a4c209ff1a9a824bf6c8b3d1fc5a1ac3eabe23462637466785/propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581", size = 38229, upload-time = "2025-10-08T19:46:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/9b/01/0ebaec9003f5d619a7475165961f8e3083cf8644d704b60395df3601632d/propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff", size = 80277, upload-time = "2025-10-08T19:48:36.647Z" }, + { url = "https://files.pythonhosted.org/packages/34/58/04af97ac586b4ef6b9026c3fd36ee7798b737a832f5d3440a4280dcebd3a/propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb", size = 45865, upload-time = "2025-10-08T19:48:37.859Z" }, + { url = "https://files.pythonhosted.org/packages/7c/19/b65d98ae21384518b291d9939e24a8aeac4fdb5101b732576f8f7540e834/propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac", size = 47636, upload-time = "2025-10-08T19:48:39.038Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/317048c6d91c356c7154dca5af019e6effeb7ee15fa6a6db327cc19e12b4/propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888", size = 201126, upload-time = "2025-10-08T19:48:40.774Z" }, + { url = "https://files.pythonhosted.org/packages/71/69/0b2a7a5a6ee83292b4b997dbd80549d8ce7d40b6397c1646c0d9495f5a85/propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc", size = 209837, upload-time = "2025-10-08T19:48:42.167Z" }, + { url = "https://files.pythonhosted.org/packages/a5/92/c699ac495a6698df6e497fc2de27af4b6ace10d8e76528357ce153722e45/propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a", size = 215578, upload-time = "2025-10-08T19:48:43.56Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ee/14de81c5eb02c0ee4f500b4e39c4e1bd0677c06e72379e6ab18923c773fc/propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88", size = 197187, upload-time = "2025-10-08T19:48:45.309Z" }, + { url = "https://files.pythonhosted.org/packages/1d/94/48dce9aaa6d8dd5a0859bad75158ec522546d4ac23f8e2f05fac469477dd/propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00", size = 193478, upload-time = "2025-10-08T19:48:47.743Z" }, + { url = "https://files.pythonhosted.org/packages/60/b5/0516b563e801e1ace212afde869a0596a0d7115eec0b12d296d75633fb29/propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0", size = 190650, upload-time = "2025-10-08T19:48:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/24/89/e0f7d4a5978cd56f8cd67735f74052f257dc471ec901694e430f0d1572fe/propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e", size = 200251, upload-time = "2025-10-08T19:48:51.4Z" }, + { url = "https://files.pythonhosted.org/packages/06/7d/a1fac863d473876ed4406c914f2e14aa82d2f10dd207c9e16fc383cc5a24/propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781", size = 200919, upload-time = "2025-10-08T19:48:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/c3/4e/f86a256ff24944cf5743e4e6c6994e3526f6acfcfb55e21694c2424f758c/propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183", size = 193211, upload-time = "2025-10-08T19:48:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/6e/3f/3fbad5f4356b068f1b047d300a6ff2c66614d7030f078cd50be3fec04228/propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19", size = 38314, upload-time = "2025-10-08T19:48:56.792Z" }, + { url = "https://files.pythonhosted.org/packages/a4/45/d78d136c3a3d215677abb886785aae744da2c3005bcb99e58640c56529b1/propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f", size = 41912, upload-time = "2025-10-08T19:48:57.995Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2a/b0632941f25139f4e58450b307242951f7c2717a5704977c6d5323a800af/propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938", size = 38450, upload-time = "2025-10-08T19:48:59.349Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "protobuf" +version = "5.29.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/57/394a763c103e0edf87f0938dafcd918d53b4c011dfc5c8ae80f3b0452dbb/protobuf-5.29.6.tar.gz", hash = "sha256:da9ee6a5424b6b30fd5e45c5ea663aef540ca95f9ad99d1e887e819cdf9b8723", size = 425623, upload-time = "2026-02-04T22:54:40.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/88/9ee58ff7863c479d6f8346686d4636dd4c415b0cbeed7a6a7d0617639c2a/protobuf-5.29.6-cp310-abi3-win32.whl", hash = "sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1", size = 423357, upload-time = "2026-02-04T22:54:25.805Z" }, + { url = "https://files.pythonhosted.org/packages/1c/66/2dc736a4d576847134fb6d80bd995c569b13cdc7b815d669050bf0ce2d2c/protobuf-5.29.6-cp310-abi3-win_amd64.whl", hash = "sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda", size = 435175, upload-time = "2026-02-04T22:54:28.592Z" }, + { url = "https://files.pythonhosted.org/packages/06/db/49b05966fd208ae3f44dcd33837b6243b4915c57561d730a43f881f24dea/protobuf-5.29.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269", size = 418619, upload-time = "2026-02-04T22:54:30.266Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d7/48cbf6b0c3c39761e47a99cb483405f0fde2be22cf00d71ef316ce52b458/protobuf-5.29.6-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6", size = 320284, upload-time = "2026-02-04T22:54:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dd/cadd6ec43069247d91f6345fa7a0d2858bef6af366dbd7ba8f05d2c77d3b/protobuf-5.29.6-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9", size = 320478, upload-time = "2026-02-04T22:54:32.909Z" }, + { url = "https://files.pythonhosted.org/packages/30/a4/ff263f5687815e1a10a9243a3a6463af42ca251224bf4b8fc4c93b9f5b80/protobuf-5.29.6-cp39-cp39-win32.whl", hash = "sha256:cb4c86de9cd8a7f3a256b9744220d87b847371c6b2f10bde87768918ef33ba49", size = 423352, upload-time = "2026-02-04T22:54:37.375Z" }, + { url = "https://files.pythonhosted.org/packages/5c/64/e943206d3b5069050d570a2c53a90631240d99adcc9a91c6ff7b41876f4d/protobuf-5.29.6-cp39-cp39-win_amd64.whl", hash = "sha256:76e07e6567f8baf827137e8d5b8204b6c7b6488bbbff1bf0a72b383f77999c18", size = 435222, upload-time = "2026-02-04T22:54:38.418Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cb/e3065b447186cb70aa65acc70c86baf482d82bf75625bf5a2c4f6919c6a3/protobuf-5.29.6-py3-none-any.whl", hash = "sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86", size = 173126, upload-time = "2026-02-04T22:54:39.462Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/84d5f6dcda9165e4d6a56ac8433c9f40a8906bf2966150b8a0cfde097d78/protobuf-6.33.5-cp39-cp39-win32.whl", hash = "sha256:a3157e62729aafb8df6da2c03aa5c0937c7266c626ce11a278b6eb7963c4e37c", size = 425892, upload-time = "2026-01-29T21:51:30.382Z" }, + { url = "https://files.pythonhosted.org/packages/68/19/33d7dc2dc84439587fa1e21e1c0026c01ad2af0a62f58fd54002a7546307/protobuf-6.33.5-cp39-cp39-win_amd64.whl", hash = "sha256:8f04fa32763dcdb4973d537d6b54e615cc61108c7cb38fe59310c3192d29510a", size = 437137, upload-time = "2026-01-29T21:51:31.456Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, +] + [[package]] name = "pwdlib" version = "0.2.1" @@ -3345,6 +4890,47 @@ argon2 = [ { name = "argon2-cffi", version = "25.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] +[[package]] +name = "py-key-value-aio" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype", marker = "python_full_version >= '3.10'" }, + { name = "py-key-value-shared", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/ce/3136b771dddf5ac905cc193b461eb67967cf3979688c6696e1f2cdcde7ea/py_key_value_aio-0.3.0.tar.gz", hash = "sha256:858e852fcf6d696d231266da66042d3355a7f9871650415feef9fca7a6cd4155", size = 50801, upload-time = "2025-11-17T16:50:04.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl", hash = "sha256:1c781915766078bfd608daa769fefb97e65d1d73746a3dfb640460e322071b64", size = 96342, upload-time = "2025-11-17T16:50:03.801Z" }, +] + +[package.optional-dependencies] +disk = [ + { name = "diskcache", marker = "python_full_version >= '3.10'" }, + { name = "pathvalidate", marker = "python_full_version >= '3.10'" }, +] +keyring = [ + { name = "keyring", marker = "python_full_version >= '3.10'" }, +] +memory = [ + { name = "cachetools", marker = "python_full_version >= '3.10'" }, +] +redis = [ + { name = "redis", marker = "python_full_version >= '3.10'" }, +] + +[[package]] +name = "py-key-value-shared" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beartype", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/e4/1971dfc4620a3a15b4579fe99e024f5edd6e0967a71154771a059daff4db/py_key_value_shared-0.3.0.tar.gz", hash = "sha256:8fdd786cf96c3e900102945f92aa1473138ebe960ef49da1c833790160c28a4b", size = 11666, upload-time = "2025-11-17T16:50:06.849Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl", hash = "sha256:5b0efba7ebca08bb158b1e93afc2f07d30b8f40c2fc12ce24a4c0d84f42f9298", size = 19560, upload-time = "2025-11-17T16:50:05.954Z" }, +] + [[package]] name = "pyasn1" version = "0.6.2" @@ -3413,85 +4999,193 @@ email = [ [[package]] name = "pydantic-ai" -version = "0.4.10" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "mcp", "mistral", "openai", "retries", "vertexai"] }, +resolution-markers = [ + "python_full_version < '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/67/eef2d1d64579c20ff732244c8f46f5b10401a47122b2404fd1a786860e0e/pydantic_ai-0.4.10.tar.gz", hash = "sha256:21cda3b62706dfdd5b9d662bb9a2ead50d51951f6ee0bbee45320b8752a95055", size = 43555371, upload-time = "2025-07-30T23:03:50.744Z" } +dependencies = [ + { name = "pydantic-ai-slim", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "mistral", "openai", "retries", "temporal", "vertexai"], marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/d7/fcc18ce80008e888404a3615f973aa3f39b98384d61b03621144c9f4c2d4/pydantic_ai-0.8.1.tar.gz", hash = "sha256:05974382082ee4f3706909d06bdfcc5e95f39e29230cc4d00e47429080099844", size = 43772581, upload-time = "2025-08-29T14:46:23.201Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/9c/3ea9ce322448e963fd84abb61985fa2d2ea0cb3f09bf0634cf43b2f49de6/pydantic_ai-0.4.10-py3-none-any.whl", hash = "sha256:4822325c92a57c9b45bdfd993106558e2397362b98ec211670d106c4b073ddd8", size = 10194, upload-time = "2025-07-30T23:03:42.435Z" }, + { url = "https://files.pythonhosted.org/packages/f9/04/802b8cf834dffcda8baabb3b76c549243694a83346c3f54e47a3a4d519fb/pydantic_ai-0.8.1-py3-none-any.whl", hash = "sha256:5fa923097132aa69b4d6a310b462dc091009c7b87705edf4443d37b887d5ef9a", size = 10188, upload-time = "2025-08-29T14:46:11.137Z" }, +] + +[[package]] +name = "pydantic-ai" +version = "1.56.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +dependencies = [ + { name = "pydantic-ai-slim", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "fastmcp", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "temporal", "ui", "vertexai", "xai"], marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/1a/800a1e02b259152a49d4c11d9103784a7482c7e9b067eeea23e949d3d80f/pydantic_ai-1.56.0.tar.gz", hash = "sha256:643ff71612df52315b3b4c4b41543657f603f567223eb33245dc8098f005bdc4", size = 11795, upload-time = "2026-02-06T01:13:21.122Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/35/f4a7fd2b9962ddb9b021f76f293e74fda71da190bb74b57ed5b343c93022/pydantic_ai-1.56.0-py3-none-any.whl", hash = "sha256:b6b3ac74bdc004693834750da4420ea2cde0d3cbc3f134c0b7544f98f1c00859", size = 7222, upload-time = "2026-02-06T01:13:11.755Z" }, ] [[package]] name = "pydantic-ai-slim" -version = "0.4.10" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "eval-type-backport" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "httpx" }, - { name = "opentelemetry-api" }, - { name = "pydantic" }, - { name = "pydantic-graph" }, - { name = "typing-inspection" }, +resolution-markers = [ + "python_full_version < '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/fc/4123faf9372807e487c83acc858482f6d5a2f39ee6c1e25a895f23f700b6/pydantic_ai_slim-0.4.10.tar.gz", hash = "sha256:c9f6904aaa91c0309f91cc3a5617c570e153afbbb9888ee52190f58f029640f0", size = 189595, upload-time = "2025-07-30T23:03:55.44Z" } +dependencies = [ + { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "genai-prices", marker = "python_full_version < '3.10'" }, + { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "httpx", marker = "python_full_version < '3.10'" }, + { name = "opentelemetry-api", marker = "python_full_version < '3.10'" }, + { name = "pydantic", marker = "python_full_version < '3.10'" }, + { name = "pydantic-graph", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "typing-inspection", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/91/08137459b3745900501b3bd11852ced6c81b7ce6e628696d75b09bb786c5/pydantic_ai_slim-0.8.1.tar.gz", hash = "sha256:12ef3dcbe5e1dad195d5e256746ef960f6e59aeddda1a55bdd553ee375ff53ae", size = 218906, upload-time = "2025-08-29T14:46:27.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/d7/b1f6cf2eb9d24086de8a7f9e08956421fde514bcb7a4a3594b108e12636e/pydantic_ai_slim-0.4.10-py3-none-any.whl", hash = "sha256:49a386d6a900b4c1a81bca3469522a4c0eaab5a46a3953d1ffda8f0f2865ed97", size = 254994, upload-time = "2025-07-30T23:03:45.03Z" }, + { url = "https://files.pythonhosted.org/packages/11/ce/8dbadd04f578d02a9825a46e931005743fe223736296f30b55846c084fab/pydantic_ai_slim-0.8.1-py3-none-any.whl", hash = "sha256:fc7edc141b21fe42bc54a2d92c1127f8a75160c5e57a168dba154d3f4adb963f", size = 297821, upload-time = "2025-08-29T14:46:14.647Z" }, ] [package.optional-dependencies] ag-ui = [ - { name = "ag-ui-protocol" }, + { name = "ag-ui-protocol", marker = "python_full_version < '3.10'" }, { name = "starlette", version = "0.49.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] anthropic = [ - { name = "anthropic" }, + { name = "anthropic", marker = "python_full_version < '3.10'" }, ] bedrock = [ - { name = "boto3" }, + { name = "boto3", marker = "python_full_version < '3.10'" }, ] cli = [ - { name = "argcomplete" }, - { name = "prompt-toolkit" }, - { name = "rich" }, + { name = "argcomplete", marker = "python_full_version < '3.10'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.10'" }, + { name = "pyperclip", marker = "python_full_version < '3.10'" }, + { name = "rich", marker = "python_full_version < '3.10'" }, ] cohere = [ - { name = "cohere", marker = "sys_platform != 'emscripten'" }, + { name = "cohere", marker = "python_full_version < '3.10' and sys_platform != 'emscripten'" }, ] evals = [ - { name = "pydantic-evals" }, + { name = "pydantic-evals", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, ] google = [ { name = "google-genai", version = "1.47.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +groq = [ + { name = "groq", marker = "python_full_version < '3.10'" }, +] +huggingface = [ + { name = "huggingface-hub", extra = ["inference"], marker = "python_full_version < '3.10'" }, +] +mistral = [ + { name = "mistralai", marker = "python_full_version < '3.10'" }, +] +openai = [ + { name = "openai", marker = "python_full_version < '3.10'" }, +] +retries = [ + { name = "tenacity", version = "9.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +temporal = [ + { name = "temporalio", version = "1.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +vertexai = [ + { name = "google-auth", marker = "python_full_version < '3.10'" }, + { name = "requests", marker = "python_full_version < '3.10'" }, +] + +[[package]] +name = "pydantic-ai-slim" +version = "1.56.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "genai-prices", marker = "python_full_version >= '3.10'" }, + { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "httpx", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, + { name = "pydantic", marker = "python_full_version >= '3.10'" }, + { name = "pydantic-graph", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-inspection", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/5c/3a577825b9c1da8f287be7f2ee6fe9aab48bc8a80e65c8518052c589f51c/pydantic_ai_slim-1.56.0.tar.gz", hash = "sha256:9f9f9c56b1c735837880a515ae5661b465b40207b25f3a3434178098b2137f05", size = 415265, upload-time = "2026-02-06T01:13:23.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/4b/34682036528eeb9aaf093c2073540ddf399ab37b99d282a69ca41356f1aa/pydantic_ai_slim-1.56.0-py3-none-any.whl", hash = "sha256:d657e4113485020500b23b7390b0066e2a0277edc7577eaad2290735ca5dd7d5", size = 542270, upload-time = "2026-02-06T01:13:14.918Z" }, +] + +[package.optional-dependencies] +ag-ui = [ + { name = "ag-ui-protocol", marker = "python_full_version >= '3.10'" }, + { name = "starlette", version = "0.52.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +anthropic = [ + { name = "anthropic", marker = "python_full_version >= '3.10'" }, +] +bedrock = [ + { name = "boto3", marker = "python_full_version >= '3.10'" }, +] +cli = [ + { name = "argcomplete", marker = "python_full_version >= '3.10'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.10'" }, + { name = "pyperclip", marker = "python_full_version >= '3.10'" }, + { name = "rich", marker = "python_full_version >= '3.10'" }, +] +cohere = [ + { name = "cohere", marker = "python_full_version >= '3.10' and sys_platform != 'emscripten'" }, +] +evals = [ + { name = "pydantic-evals", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +fastmcp = [ + { name = "fastmcp", marker = "python_full_version >= '3.10'" }, +] +google = [ { name = "google-genai", version = "1.62.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] groq = [ - { name = "groq" }, + { name = "groq", marker = "python_full_version >= '3.10'" }, ] huggingface = [ - { name = "huggingface-hub" }, + { name = "huggingface-hub", extra = ["inference"], marker = "python_full_version >= '3.10'" }, +] +logfire = [ + { name = "logfire", extra = ["httpx"], marker = "python_full_version >= '3.10'" }, ] mcp = [ { name = "mcp", marker = "python_full_version >= '3.10'" }, ] mistral = [ - { name = "mistralai" }, + { name = "mistralai", marker = "python_full_version >= '3.10'" }, ] openai = [ - { name = "openai" }, + { name = "openai", marker = "python_full_version >= '3.10'" }, + { name = "tiktoken", marker = "python_full_version >= '3.10'" }, ] retries = [ - { name = "tenacity", version = "9.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "tenacity", version = "9.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] +temporal = [ + { name = "temporalio", version = "1.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +ui = [ + { name = "starlette", version = "0.52.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] vertexai = [ - { name = "google-auth" }, - { name = "requests" }, + { name = "google-auth", marker = "python_full_version >= '3.10'" }, + { name = "requests", marker = "python_full_version >= '3.10'" }, +] +xai = [ + { name = "xai-sdk", marker = "python_full_version >= '3.10'" }, ] [[package]] @@ -3627,20 +5321,44 @@ wheels = [ [[package]] name = "pydantic-evals" -version = "0.4.10" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "eval-type-backport", marker = "python_full_version < '3.11'" }, - { name = "logfire-api" }, - { name = "pydantic" }, - { name = "pydantic-ai-slim" }, - { name = "pyyaml" }, - { name = "rich" }, +resolution-markers = [ + "python_full_version < '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/98/b7/3f925d6ec9a2627e3310e87c2131514c3b44427c8975fafe83453825cfca/pydantic_evals-0.4.10.tar.gz", hash = "sha256:310acc7559d601743a101c606e50c0c5a9592bfc53cb733c7e54143b39e0fc97", size = 43729, upload-time = "2025-07-30T23:03:56.943Z" } +dependencies = [ + { name = "anyio", marker = "python_full_version < '3.10'" }, + { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, + { name = "logfire-api", marker = "python_full_version < '3.10'" }, + { name = "pydantic", marker = "python_full_version < '3.10'" }, + { name = "pydantic-ai-slim", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pyyaml", marker = "python_full_version < '3.10'" }, + { name = "rich", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/9d/460a1f2c9f5f263e9d8e9661acbd654ccc81ad3373ea43048d914091a817/pydantic_evals-0.8.1.tar.gz", hash = "sha256:c398a623c31c19ce70e346ad75654fcb1517c3f6a821461f64fe5cbbe0813023", size = 43933, upload-time = "2025-08-29T14:46:28.903Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/9f/7972c450d74c2b0f6d9f9de644ac33c4c63f0f2156f8acd37a274b1f43a3/pydantic_evals-0.4.10-py3-none-any.whl", hash = "sha256:5fda10c5ced2c99f03b407bd56645574598e6daab0e5be2ed7056e815e6037f6", size = 52511, upload-time = "2025-07-30T23:03:46.717Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f9/1d21c4687167c4fa76fd3b1ed47f9bc2d38fd94cbacd9aa3f19e82e59830/pydantic_evals-0.8.1-py3-none-any.whl", hash = "sha256:6c76333b1d79632f619eb58a24ac656e9f402c47c75ad750ba0230d7f5514344", size = 52602, upload-time = "2025-08-29T14:46:16.602Z" }, +] + +[[package]] +name = "pydantic-evals" +version = "1.56.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +dependencies = [ + { name = "anyio", marker = "python_full_version >= '3.10'" }, + { name = "logfire-api", marker = "python_full_version >= '3.10'" }, + { name = "pydantic", marker = "python_full_version >= '3.10'" }, + { name = "pydantic-ai-slim", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "rich", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f2/8c59284a2978af3fbda45ae3217218eaf8b071207a9290b54b7613983e5d/pydantic_evals-1.56.0.tar.gz", hash = "sha256:206635107127af6a3ee4b1fc8f77af6afb14683615a2d6b3609f79467c1c0d28", size = 47210, upload-time = "2026-02-06T01:13:25.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/51/9875d19ff6d584aaeb574aba76b49d931b822546fc60b29c4fc0da98170d/pydantic_evals-1.56.0-py3-none-any.whl", hash = "sha256:d1efb410c97135aabd2a22453b10c981b2b9851985e9354713af67ae0973b7a9", size = 56407, upload-time = "2026-02-06T01:13:17.098Z" }, ] [[package]] @@ -3658,17 +5376,39 @@ wheels = [ [[package]] name = "pydantic-graph" -version = "0.4.10" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx" }, - { name = "logfire-api" }, - { name = "pydantic" }, - { name = "typing-inspection" }, +resolution-markers = [ + "python_full_version < '3.10'", ] -sdist = { url = "https://files.pythonhosted.org/packages/04/46/62b83bd471a8743c41aba6693f55b746b8ee1294f64b7e9f42db7c3fd4b5/pydantic_graph-0.4.10.tar.gz", hash = "sha256:034063ac0ce2143a877a4fac563520492e70dde42d262b22c928f081d7759c5b", size = 21978, upload-time = "2025-07-30T23:03:57.986Z" } +dependencies = [ + { name = "httpx", marker = "python_full_version < '3.10'" }, + { name = "logfire-api", marker = "python_full_version < '3.10'" }, + { name = "pydantic", marker = "python_full_version < '3.10'" }, + { name = "typing-inspection", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/97/b35b7cb82d9f1bb6d5c6d21bba54f6196a3a5f593373f3a9c163a3821fd7/pydantic_graph-0.8.1.tar.gz", hash = "sha256:c61675a05c74f661d4ff38d04b74bd652c1e0959467801986f2f85dc7585410d", size = 21675, upload-time = "2025-08-29T14:46:29.839Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/55/0026897b1cd6ab6b179b25c06b1dff496a3ebb83e30c6acb1a5a40e80cd1/pydantic_graph-0.4.10-py3-none-any.whl", hash = "sha256:e5128d5e370a6391aa6441eaefff6b3a139e583e0ac6755d83b88c9df9c6c6a7", size = 27578, upload-time = "2025-07-30T23:03:47.925Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e3/5908643b049bb2384d143885725cbeb0f53707d418357d4d1ac8d2c82629/pydantic_graph-0.8.1-py3-none-any.whl", hash = "sha256:f1dd5db0fe22f4e3323c04c65e2f0013846decc312b3efc3196666764556b765", size = 27239, upload-time = "2025-08-29T14:46:18.317Z" }, +] + +[[package]] +name = "pydantic-graph" +version = "1.56.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +dependencies = [ + { name = "httpx", marker = "python_full_version >= '3.10'" }, + { name = "logfire-api", marker = "python_full_version >= '3.10'" }, + { name = "pydantic", marker = "python_full_version >= '3.10'" }, + { name = "typing-inspection", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/03/f92881cdb12d6f43e60e9bfd602e41c95408f06e2324d3729f7a194e2bcd/pydantic_graph-1.56.0.tar.gz", hash = "sha256:5e22972dbb43dbc379ab9944252ff864019abf3c7d465dcdf572fc8aec9a44a1", size = 58460, upload-time = "2026-02-06T01:13:26.708Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/07/8c823eb4d196137c123d4d67434e185901d3cbaea3b0c2b7667da84e72c1/pydantic_graph-1.56.0-py3-none-any.whl", hash = "sha256:ec3f0a1d6fcedd4eb9c59fef45079c2ee4d4185878d70dae26440a9c974c6bb3", size = 72346, upload-time = "2026-02-06T01:13:18.792Z" }, ] [[package]] @@ -3706,6 +5446,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, ] +[[package]] +name = "pydocket" +version = "0.17.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle", marker = "python_full_version >= '3.10'" }, + { name = "croniter", marker = "python_full_version >= '3.10'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "fakeredis", extra = ["lua"], marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, + { name = "prometheus-client", marker = "python_full_version >= '3.10'" }, + { name = "py-key-value-aio", extra = ["memory", "redis"], marker = "python_full_version >= '3.10'" }, + { name = "python-json-logger", marker = "python_full_version >= '3.10'" }, + { name = "redis", marker = "python_full_version >= '3.10'" }, + { name = "rich", marker = "python_full_version >= '3.10'" }, + { name = "taskgroup", marker = "python_full_version == '3.10.*'" }, + { name = "typer", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/26/ac23ead3725475468b50b486939bf5feda27180050a614a7407344a0af0e/pydocket-0.17.5.tar.gz", hash = "sha256:19a6976d8fd11c1acf62feb0291a339e06beaefa100f73dd38c6499760ad3e62", size = 334829, upload-time = "2026-01-30T18:44:39.702Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/98/73427d065c067a99de6afbe24df3d90cf20d63152ceb42edff2b6e829d4c/pydocket-0.17.5-py3-none-any.whl", hash = "sha256:544d7c2625a33e52528ac24db25794841427dfc2cf30b9c558ac387c77746241", size = 93355, upload-time = "2026-01-30T18:44:37.972Z" }, +] + [[package]] name = "pyee" version = "13.0.0" @@ -3746,11 +5510,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.9.0" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825, upload-time = "2024-08-01T15:01:08.445Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344, upload-time = "2024-08-01T15:01:06.481Z" }, + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, ] [package.optional-dependencies] @@ -3807,6 +5571,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, ] +[[package]] +name = "pyperclip" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, +] + [[package]] name = "pytest" version = "8.4.2" @@ -3876,6 +5649,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, ] +[[package]] +name = "python-json-logger" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, +] + [[package]] name = "python-multipart" version = "0.0.20" @@ -3913,6 +5695,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, ] +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, + { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, + { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/51/2a/f125667ce48105bf1f4e50e03cfa7b24b8c4f47684d7f1cf4dcb6f6b1c15/pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3", size = 161464, upload-time = "2026-01-30T01:03:39.11Z" }, + { url = "https://files.pythonhosted.org/packages/40/df/065a30790a7ca6bb48ad9018dd44668ed9135610ebf56a2a4cb8e513fd5c/pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1", size = 246159, upload-time = "2026-01-30T01:03:40.131Z" }, + { url = "https://files.pythonhosted.org/packages/a5/1c/fd09976a7e04960dabc07ab0e0072c7813d566ec67d5490a4c600683c158/pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db", size = 259120, upload-time = "2026-01-30T01:03:41.233Z" }, + { url = "https://files.pythonhosted.org/packages/52/49/59fdc6fc5a390ae9f308eadeb97dfc70fc2d804ffc49dd39fc97604622ec/pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1", size = 262196, upload-time = "2026-01-30T01:03:42.696Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/d6734dccf0080e3dc00a55b0827ab5af30c886f8bc127bbc04bc3445daec/pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a", size = 103510, upload-time = "2026-01-30T01:03:43.915Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + [[package]] name = "pywin32" version = "311" @@ -3938,6 +5773,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/60/22/e0e8d802f124772cec9c75430b01a212f86f9de7546bda715e54140d5aeb/pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d", size = 8778162, upload-time = "2025-07-14T20:13:03.544Z" }, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -4032,18 +5876,168 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/96/7935186fba032312eb8a75e6503440b0e6de76c901421f791408e4debd93/rcslice-1.1.0-py3-none-any.whl", hash = "sha256:1b12fc0c0ca452e8a9fd2b56ac008162f19e250906a4290a7e7a98be3200c2a6", size = 5180, upload-time = "2018-09-27T12:44:05.197Z" }, ] +[[package]] +name = "redis" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version >= '3.10' and python_full_version < '3.11.3'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/f0/8956f8a86b20d7bb9d6ac0187cf4cd54d8065bc9a1a09eb8011d4d326596/redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b", size = 354159, upload-time = "2025-11-19T15:54:38.064Z" }, +] + [[package]] name = "referencing" -version = "0.37.0" +version = "0.36.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs", marker = "python_full_version >= '3.10'" }, { name = "rpds-py", marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "regex" +version = "2026.1.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/86/07d5056945f9ec4590b518171c4254a5925832eb727b56d3c38a7476f316/regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5", size = 414811, upload-time = "2026-01-14T23:18:02.775Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/d2/e6ee96b7dff201a83f650241c52db8e5bd080967cb93211f57aa448dc9d6/regex-2026.1.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e3dd93c8f9abe8aa4b6c652016da9a3afa190df5ad822907efe6b206c09896e", size = 488166, upload-time = "2026-01-14T23:13:46.408Z" }, + { url = "https://files.pythonhosted.org/packages/23/8a/819e9ce14c9f87af026d0690901b3931f3101160833e5d4c8061fa3a1b67/regex-2026.1.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97499ff7862e868b1977107873dd1a06e151467129159a6ffd07b66706ba3a9f", size = 290632, upload-time = "2026-01-14T23:13:48.688Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c3/23dfe15af25d1d45b07dfd4caa6003ad710dcdcb4c4b279909bdfe7a2de8/regex-2026.1.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bda75ebcac38d884240914c6c43d8ab5fb82e74cde6da94b43b17c411aa4c2b", size = 288500, upload-time = "2026-01-14T23:13:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/c6/31/1adc33e2f717df30d2f4d973f8776d2ba6ecf939301efab29fca57505c95/regex-2026.1.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7dcc02368585334f5bc81fc73a2a6a0bbade60e7d83da21cead622faf408f32c", size = 781670, upload-time = "2026-01-14T23:13:52.453Z" }, + { url = "https://files.pythonhosted.org/packages/23/ce/21a8a22d13bc4adcb927c27b840c948f15fc973e21ed2346c1bd0eae22dc/regex-2026.1.15-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:693b465171707bbe882a7a05de5e866f33c76aa449750bee94a8d90463533cc9", size = 850820, upload-time = "2026-01-14T23:13:54.894Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/3eeacdf587a4705a44484cd0b30e9230a0e602811fb3e2cc32268c70d509/regex-2026.1.15-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b0d190e6f013ea938623a58706d1469a62103fb2a241ce2873a9906e0386582c", size = 898777, upload-time = "2026-01-14T23:13:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/79/a9/1898a077e2965c35fc22796488141a22676eed2d73701e37c73ad7c0b459/regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ff818702440a5878a81886f127b80127f5d50563753a28211482867f8318106", size = 791750, upload-time = "2026-01-14T23:13:58.527Z" }, + { url = "https://files.pythonhosted.org/packages/4c/84/e31f9d149a178889b3817212827f5e0e8c827a049ff31b4b381e76b26e2d/regex-2026.1.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f052d1be37ef35a54e394de66136e30fa1191fab64f71fc06ac7bc98c9a84618", size = 782674, upload-time = "2026-01-14T23:13:59.874Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ff/adf60063db24532add6a1676943754a5654dcac8237af024ede38244fd12/regex-2026.1.15-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6bfc31a37fd1592f0c4fc4bfc674b5c42e52efe45b4b7a6a14f334cca4bcebe4", size = 767906, upload-time = "2026-01-14T23:14:01.298Z" }, + { url = "https://files.pythonhosted.org/packages/af/3e/e6a216cee1e2780fec11afe7fc47b6f3925d7264e8149c607ac389fd9b1a/regex-2026.1.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d6ce5ae80066b319ae3bc62fd55a557c9491baa5efd0d355f0de08c4ba54e79", size = 774798, upload-time = "2026-01-14T23:14:02.715Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/23a4a8378a9208514ed3efc7e7850c27fa01e00ed8557c958df0335edc4a/regex-2026.1.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1704d204bd42b6bb80167df0e4554f35c255b579ba99616def38f69e14a5ccb9", size = 845861, upload-time = "2026-01-14T23:14:04.824Z" }, + { url = "https://files.pythonhosted.org/packages/f8/57/d7605a9d53bd07421a8785d349cd29677fe660e13674fa4c6cbd624ae354/regex-2026.1.15-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e3174a5ed4171570dc8318afada56373aa9289eb6dc0d96cceb48e7358b0e220", size = 755648, upload-time = "2026-01-14T23:14:06.371Z" }, + { url = "https://files.pythonhosted.org/packages/6f/76/6f2e24aa192da1e299cc1101674a60579d3912391867ce0b946ba83e2194/regex-2026.1.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:87adf5bd6d72e3e17c9cb59ac4096b1faaf84b7eb3037a5ffa61c4b4370f0f13", size = 836250, upload-time = "2026-01-14T23:14:08.343Z" }, + { url = "https://files.pythonhosted.org/packages/11/3a/1f2a1d29453299a7858eab7759045fc3d9d1b429b088dec2dc85b6fa16a2/regex-2026.1.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e85dc94595f4d766bd7d872a9de5ede1ca8d3063f3bdf1e2c725f5eb411159e3", size = 779919, upload-time = "2026-01-14T23:14:09.954Z" }, + { url = "https://files.pythonhosted.org/packages/c0/67/eab9bc955c9dcc58e9b222c801e39cff7ca0b04261792a2149166ce7e792/regex-2026.1.15-cp310-cp310-win32.whl", hash = "sha256:21ca32c28c30d5d65fc9886ff576fc9b59bbca08933e844fa2363e530f4c8218", size = 265888, upload-time = "2026-01-14T23:14:11.35Z" }, + { url = "https://files.pythonhosted.org/packages/1d/62/31d16ae24e1f8803bddb0885508acecaec997fcdcde9c243787103119ae4/regex-2026.1.15-cp310-cp310-win_amd64.whl", hash = "sha256:3038a62fc7d6e5547b8915a3d927a0fbeef84cdbe0b1deb8c99bbd4a8961b52a", size = 277830, upload-time = "2026-01-14T23:14:12.908Z" }, + { url = "https://files.pythonhosted.org/packages/e5/36/5d9972bccd6417ecd5a8be319cebfd80b296875e7f116c37fb2a2deecebf/regex-2026.1.15-cp310-cp310-win_arm64.whl", hash = "sha256:505831646c945e3e63552cc1b1b9b514f0e93232972a2d5bedbcc32f15bc82e3", size = 270376, upload-time = "2026-01-14T23:14:14.782Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c9/0c80c96eab96948363d270143138d671d5731c3a692b417629bf3492a9d6/regex-2026.1.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ae6020fb311f68d753b7efa9d4b9a5d47a5d6466ea0d5e3b5a471a960ea6e4a", size = 488168, upload-time = "2026-01-14T23:14:16.129Z" }, + { url = "https://files.pythonhosted.org/packages/17/f0/271c92f5389a552494c429e5cc38d76d1322eb142fb5db3c8ccc47751468/regex-2026.1.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eddf73f41225942c1f994914742afa53dc0d01a6e20fe14b878a1b1edc74151f", size = 290636, upload-time = "2026-01-14T23:14:17.715Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f9/5f1fd077d106ca5655a0f9ff8f25a1ab55b92128b5713a91ed7134ff688e/regex-2026.1.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e8cd52557603f5c66a548f69421310886b28b7066853089e1a71ee710e1cdc1", size = 288496, upload-time = "2026-01-14T23:14:19.326Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e1/8f43b03a4968c748858ec77f746c286d81f896c2e437ccf050ebc5d3128c/regex-2026.1.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5170907244b14303edc5978f522f16c974f32d3aa92109fabc2af52411c9433b", size = 793503, upload-time = "2026-01-14T23:14:20.922Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4e/a39a5e8edc5377a46a7c875c2f9a626ed3338cb3bb06931be461c3e1a34a/regex-2026.1.15-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2748c1ec0663580b4510bd89941a31560b4b439a0b428b49472a3d9944d11cd8", size = 860535, upload-time = "2026-01-14T23:14:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1c/9dce667a32a9477f7a2869c1c767dc00727284a9fa3ff5c09a5c6c03575e/regex-2026.1.15-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f2775843ca49360508d080eaa87f94fa248e2c946bbcd963bb3aae14f333413", size = 907225, upload-time = "2026-01-14T23:14:23.897Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3c/87ca0a02736d16b6262921425e84b48984e77d8e4e572c9072ce96e66c30/regex-2026.1.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9ea2604370efc9a174c1b5dcc81784fb040044232150f7f33756049edfc9026", size = 800526, upload-time = "2026-01-14T23:14:26.039Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/647d5715aeea7c87bdcbd2f578f47b415f55c24e361e639fe8c0cc88878f/regex-2026.1.15-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcd31594264029b57bf16f37fd7248a70b3b764ed9e0839a8f271b2d22c0785", size = 773446, upload-time = "2026-01-14T23:14:28.109Z" }, + { url = "https://files.pythonhosted.org/packages/af/89/bf22cac25cb4ba0fe6bff52ebedbb65b77a179052a9d6037136ae93f42f4/regex-2026.1.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c08c1f3e34338256732bd6938747daa3c0d5b251e04b6e43b5813e94d503076e", size = 783051, upload-time = "2026-01-14T23:14:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f4/6ed03e71dca6348a5188363a34f5e26ffd5db1404780288ff0d79513bce4/regex-2026.1.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e43a55f378df1e7a4fa3547c88d9a5a9b7113f653a66821bcea4718fe6c58763", size = 854485, upload-time = "2026-01-14T23:14:31.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/8e8560bd78caded8eb137e3e47612430a05b9a772caf60876435192d670a/regex-2026.1.15-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f82110ab962a541737bd0ce87978d4c658f06e7591ba899192e2712a517badbb", size = 762195, upload-time = "2026-01-14T23:14:32.802Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/61fc710f9aa8dfcd764fe27d37edfaa023b1a23305a0d84fccd5adb346ea/regex-2026.1.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:27618391db7bdaf87ac6c92b31e8f0dfb83a9de0075855152b720140bda177a2", size = 845986, upload-time = "2026-01-14T23:14:34.898Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2e/fbee4cb93f9d686901a7ca8d94285b80405e8c34fe4107f63ffcbfb56379/regex-2026.1.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bfb0d6be01fbae8d6655c8ca21b3b72458606c4aec9bbc932db758d47aba6db1", size = 788992, upload-time = "2026-01-14T23:14:37.116Z" }, + { url = "https://files.pythonhosted.org/packages/ed/14/3076348f3f586de64b1ab75a3fbabdaab7684af7f308ad43be7ef1849e55/regex-2026.1.15-cp311-cp311-win32.whl", hash = "sha256:b10e42a6de0e32559a92f2f8dc908478cc0fa02838d7dbe764c44dca3fa13569", size = 265893, upload-time = "2026-01-14T23:14:38.426Z" }, + { url = "https://files.pythonhosted.org/packages/0f/19/772cf8b5fc803f5c89ba85d8b1870a1ca580dc482aa030383a9289c82e44/regex-2026.1.15-cp311-cp311-win_amd64.whl", hash = "sha256:e9bf3f0bbdb56633c07d7116ae60a576f846efdd86a8848f8d62b749e1209ca7", size = 277840, upload-time = "2026-01-14T23:14:39.785Z" }, + { url = "https://files.pythonhosted.org/packages/78/84/d05f61142709474da3c0853222d91086d3e1372bcdab516c6fd8d80f3297/regex-2026.1.15-cp311-cp311-win_arm64.whl", hash = "sha256:41aef6f953283291c4e4e6850607bd71502be67779586a61472beacb315c97ec", size = 270374, upload-time = "2026-01-14T23:14:41.592Z" }, + { url = "https://files.pythonhosted.org/packages/92/81/10d8cf43c807d0326efe874c1b79f22bfb0fb226027b0b19ebc26d301408/regex-2026.1.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c8fcc5793dde01641a35905d6731ee1548f02b956815f8f1cab89e515a5bdf1", size = 489398, upload-time = "2026-01-14T23:14:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/90/b0/7c2a74e74ef2a7c32de724658a69a862880e3e4155cba992ba04d1c70400/regex-2026.1.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bfd876041a956e6a90ad7cdb3f6a630c07d491280bfeed4544053cd434901681", size = 291339, upload-time = "2026-01-14T23:14:45.183Z" }, + { url = "https://files.pythonhosted.org/packages/19/4d/16d0773d0c818417f4cc20aa0da90064b966d22cd62a8c46765b5bd2d643/regex-2026.1.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9250d087bc92b7d4899ccd5539a1b2334e44eee85d848c4c1aef8e221d3f8c8f", size = 289003, upload-time = "2026-01-14T23:14:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e4/1fc4599450c9f0863d9406e944592d968b8d6dfd0d552a7d569e43bceada/regex-2026.1.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8a154cf6537ebbc110e24dabe53095e714245c272da9c1be05734bdad4a61aa", size = 798656, upload-time = "2026-01-14T23:14:48.77Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e6/59650d73a73fa8a60b3a590545bfcf1172b4384a7df2e7fe7b9aab4e2da9/regex-2026.1.15-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8050ba2e3ea1d8731a549e83c18d2f0999fbc99a5f6bd06b4c91449f55291804", size = 864252, upload-time = "2026-01-14T23:14:50.528Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ab/1d0f4d50a1638849a97d731364c9a80fa304fec46325e48330c170ee8e80/regex-2026.1.15-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf065240704cb8951cc04972cf107063917022511273e0969bdb34fc173456c", size = 912268, upload-time = "2026-01-14T23:14:52.952Z" }, + { url = "https://files.pythonhosted.org/packages/dd/df/0d722c030c82faa1d331d1921ee268a4e8fb55ca8b9042c9341c352f17fa/regex-2026.1.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32bef3e7aeee75746748643667668ef941d28b003bfc89994ecf09a10f7a1b5", size = 803589, upload-time = "2026-01-14T23:14:55.182Z" }, + { url = "https://files.pythonhosted.org/packages/66/23/33289beba7ccb8b805c6610a8913d0131f834928afc555b241caabd422a9/regex-2026.1.15-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5eaa4a4c5b1906bd0d2508d68927f15b81821f85092e06f1a34a4254b0e1af3", size = 775700, upload-time = "2026-01-14T23:14:56.707Z" }, + { url = "https://files.pythonhosted.org/packages/e7/65/bf3a42fa6897a0d3afa81acb25c42f4b71c274f698ceabd75523259f6688/regex-2026.1.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:86c1077a3cc60d453d4084d5b9649065f3bf1184e22992bd322e1f081d3117fb", size = 787928, upload-time = "2026-01-14T23:14:58.312Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f5/13bf65864fc314f68cdd6d8ca94adcab064d4d39dbd0b10fef29a9da48fc/regex-2026.1.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2b091aefc05c78d286657cd4db95f2e6313375ff65dcf085e42e4c04d9c8d410", size = 858607, upload-time = "2026-01-14T23:15:00.657Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/040e589834d7a439ee43fb0e1e902bc81bd58a5ba81acffe586bb3321d35/regex-2026.1.15-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:57e7d17f59f9ebfa9667e6e5a1c0127b96b87cb9cede8335482451ed00788ba4", size = 763729, upload-time = "2026-01-14T23:15:02.248Z" }, + { url = "https://files.pythonhosted.org/packages/9b/84/6921e8129687a427edf25a34a5594b588b6d88f491320b9de5b6339a4fcb/regex-2026.1.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6c4dcdfff2c08509faa15d36ba7e5ef5fcfab25f1e8f85a0c8f45bc3a30725d", size = 850697, upload-time = "2026-01-14T23:15:03.878Z" }, + { url = "https://files.pythonhosted.org/packages/8a/87/3d06143d4b128f4229158f2de5de6c8f2485170c7221e61bf381313314b2/regex-2026.1.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf8ff04c642716a7f2048713ddc6278c5fd41faa3b9cab12607c7abecd012c22", size = 789849, upload-time = "2026-01-14T23:15:06.102Z" }, + { url = "https://files.pythonhosted.org/packages/77/69/c50a63842b6bd48850ebc7ab22d46e7a2a32d824ad6c605b218441814639/regex-2026.1.15-cp312-cp312-win32.whl", hash = "sha256:82345326b1d8d56afbe41d881fdf62f1926d7264b2fc1537f99ae5da9aad7913", size = 266279, upload-time = "2026-01-14T23:15:07.678Z" }, + { url = "https://files.pythonhosted.org/packages/f2/36/39d0b29d087e2b11fd8191e15e81cce1b635fcc845297c67f11d0d19274d/regex-2026.1.15-cp312-cp312-win_amd64.whl", hash = "sha256:4def140aa6156bc64ee9912383d4038f3fdd18fee03a6f222abd4de6357ce42a", size = 277166, upload-time = "2026-01-14T23:15:09.257Z" }, + { url = "https://files.pythonhosted.org/packages/28/32/5b8e476a12262748851fa8ab1b0be540360692325975b094e594dfebbb52/regex-2026.1.15-cp312-cp312-win_arm64.whl", hash = "sha256:c6c565d9a6e1a8d783c1948937ffc377dd5771e83bd56de8317c450a954d2056", size = 270415, upload-time = "2026-01-14T23:15:10.743Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2e/6870bb16e982669b674cce3ee9ff2d1d46ab80528ee6bcc20fb2292efb60/regex-2026.1.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e69d0deeb977ffe7ed3d2e4439360089f9c3f217ada608f0f88ebd67afb6385e", size = 489164, upload-time = "2026-01-14T23:15:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/9774542e203849b0286badf67199970a44ebdb0cc5fb739f06e47ada72f8/regex-2026.1.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3601ffb5375de85a16f407854d11cca8fe3f5febbe3ac78fb2866bb220c74d10", size = 291218, upload-time = "2026-01-14T23:15:15.647Z" }, + { url = "https://files.pythonhosted.org/packages/b2/87/b0cda79f22b8dee05f774922a214da109f9a4c0eca5da2c9d72d77ea062c/regex-2026.1.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4c5ef43b5c2d4114eb8ea424bb8c9cec01d5d17f242af88b2448f5ee81caadbc", size = 288895, upload-time = "2026-01-14T23:15:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/3b/6a/0041f0a2170d32be01ab981d6346c83a8934277d82c780d60b127331f264/regex-2026.1.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:968c14d4f03e10b2fd960f1d5168c1f0ac969381d3c1fcc973bc45fb06346599", size = 798680, upload-time = "2026-01-14T23:15:19.342Z" }, + { url = "https://files.pythonhosted.org/packages/58/de/30e1cfcdbe3e891324aa7568b7c968771f82190df5524fabc1138cb2d45a/regex-2026.1.15-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56a5595d0f892f214609c9f76b41b7428bed439d98dc961efafdd1354d42baae", size = 864210, upload-time = "2026-01-14T23:15:22.005Z" }, + { url = "https://files.pythonhosted.org/packages/64/44/4db2f5c5ca0ccd40ff052ae7b1e9731352fcdad946c2b812285a7505ca75/regex-2026.1.15-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf650f26087363434c4e560011f8e4e738f6f3e029b85d4904c50135b86cfa5", size = 912358, upload-time = "2026-01-14T23:15:24.569Z" }, + { url = "https://files.pythonhosted.org/packages/79/b6/e6a5665d43a7c42467138c8a2549be432bad22cbd206f5ec87162de74bd7/regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18388a62989c72ac24de75f1449d0fb0b04dfccd0a1a7c1c43af5eb503d890f6", size = 803583, upload-time = "2026-01-14T23:15:26.526Z" }, + { url = "https://files.pythonhosted.org/packages/e7/53/7cd478222169d85d74d7437e74750005e993f52f335f7c04ff7adfda3310/regex-2026.1.15-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d220a2517f5893f55daac983bfa9fe998a7dbcaee4f5d27a88500f8b7873788", size = 775782, upload-time = "2026-01-14T23:15:29.352Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b5/75f9a9ee4b03a7c009fe60500fe550b45df94f0955ca29af16333ef557c5/regex-2026.1.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9c08c2fbc6120e70abff5d7f28ffb4d969e14294fb2143b4b5c7d20e46d1714", size = 787978, upload-time = "2026-01-14T23:15:31.295Z" }, + { url = "https://files.pythonhosted.org/packages/72/b3/79821c826245bbe9ccbb54f6eadb7879c722fd3e0248c17bfc90bf54e123/regex-2026.1.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ef7d5d4bd49ec7364315167a4134a015f61e8266c6d446fc116a9ac4456e10d", size = 858550, upload-time = "2026-01-14T23:15:33.558Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/2ab5f77a1c465745bfbfcb3ad63178a58337ae8d5274315e2cc623a822fa/regex-2026.1.15-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e42844ad64194fa08d5ccb75fe6a459b9b08e6d7296bd704460168d58a388f3", size = 763747, upload-time = "2026-01-14T23:15:35.206Z" }, + { url = "https://files.pythonhosted.org/packages/6d/84/c27df502d4bfe2873a3e3a7cf1bdb2b9cc10284d1a44797cf38bed790470/regex-2026.1.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cfecdaa4b19f9ca534746eb3b55a5195d5c95b88cac32a205e981ec0a22b7d31", size = 850615, upload-time = "2026-01-14T23:15:37.523Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b7/658a9782fb253680aa8ecb5ccbb51f69e088ed48142c46d9f0c99b46c575/regex-2026.1.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08df9722d9b87834a3d701f3fca570b2be115654dbfd30179f30ab2f39d606d3", size = 789951, upload-time = "2026-01-14T23:15:39.582Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2a/5928af114441e059f15b2f63e188bd00c6529b3051c974ade7444b85fcda/regex-2026.1.15-cp313-cp313-win32.whl", hash = "sha256:d426616dae0967ca225ab12c22274eb816558f2f99ccb4a1d52ca92e8baf180f", size = 266275, upload-time = "2026-01-14T23:15:42.108Z" }, + { url = "https://files.pythonhosted.org/packages/4f/16/5bfbb89e435897bff28cf0352a992ca719d9e55ebf8b629203c96b6ce4f7/regex-2026.1.15-cp313-cp313-win_amd64.whl", hash = "sha256:febd38857b09867d3ed3f4f1af7d241c5c50362e25ef43034995b77a50df494e", size = 277145, upload-time = "2026-01-14T23:15:44.244Z" }, + { url = "https://files.pythonhosted.org/packages/56/c1/a09ff7392ef4233296e821aec5f78c51be5e91ffde0d163059e50fd75835/regex-2026.1.15-cp313-cp313-win_arm64.whl", hash = "sha256:8e32f7896f83774f91499d239e24cebfadbc07639c1494bb7213983842348337", size = 270411, upload-time = "2026-01-14T23:15:45.858Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/0cfd5a78e5c6db00e6782fdae70458f89850ce95baa5e8694ab91d89744f/regex-2026.1.15-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ec94c04149b6a7b8120f9f44565722c7ae31b7a6d2275569d2eefa76b83da3be", size = 492068, upload-time = "2026-01-14T23:15:47.616Z" }, + { url = "https://files.pythonhosted.org/packages/50/72/6c86acff16cb7c959c4355826bbf06aad670682d07c8f3998d9ef4fee7cd/regex-2026.1.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40c86d8046915bb9aeb15d3f3f15b6fd500b8ea4485b30e1bbc799dab3fe29f8", size = 292756, upload-time = "2026-01-14T23:15:49.307Z" }, + { url = "https://files.pythonhosted.org/packages/4e/58/df7fb69eadfe76526ddfce28abdc0af09ffe65f20c2c90932e89d705153f/regex-2026.1.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:726ea4e727aba21643205edad8f2187ec682d3305d790f73b7a51c7587b64bdd", size = 291114, upload-time = "2026-01-14T23:15:51.484Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6c/a4011cd1cf96b90d2cdc7e156f91efbd26531e822a7fbb82a43c1016678e/regex-2026.1.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cb740d044aff31898804e7bf1181cc72c03d11dfd19932b9911ffc19a79070a", size = 807524, upload-time = "2026-01-14T23:15:53.102Z" }, + { url = "https://files.pythonhosted.org/packages/1d/25/a53ffb73183f69c3e9f4355c4922b76d2840aee160af6af5fac229b6201d/regex-2026.1.15-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05d75a668e9ea16f832390d22131fe1e8acc8389a694c8febc3e340b0f810b93", size = 873455, upload-time = "2026-01-14T23:15:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/66/0b/8b47fc2e8f97d9b4a851736f3890a5f786443aa8901061c55f24c955f45b/regex-2026.1.15-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d991483606f3dbec93287b9f35596f41aa2e92b7c2ebbb935b63f409e243c9af", size = 915007, upload-time = "2026-01-14T23:15:57.041Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/97de0d681e6d26fabe71968dbee06dd52819e9a22fdce5dac7256c31ed84/regex-2026.1.15-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:194312a14819d3e44628a44ed6fea6898fdbecb0550089d84c403475138d0a09", size = 812794, upload-time = "2026-01-14T23:15:58.916Z" }, + { url = "https://files.pythonhosted.org/packages/22/38/e752f94e860d429654aa2b1c51880bff8dfe8f084268258adf9151cf1f53/regex-2026.1.15-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe2fda4110a3d0bc163c2e0664be44657431440722c5c5315c65155cab92f9e5", size = 781159, upload-time = "2026-01-14T23:16:00.817Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a7/d739ffaef33c378fc888302a018d7f81080393d96c476b058b8c64fd2b0d/regex-2026.1.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:124dc36c85d34ef2d9164da41a53c1c8c122cfb1f6e1ec377a1f27ee81deb794", size = 795558, upload-time = "2026-01-14T23:16:03.267Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c4/542876f9a0ac576100fc73e9c75b779f5c31e3527576cfc9cb3009dcc58a/regex-2026.1.15-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1774cd1981cd212506a23a14dba7fdeaee259f5deba2df6229966d9911e767a", size = 868427, upload-time = "2026-01-14T23:16:05.646Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0f/d5655bea5b22069e32ae85a947aa564912f23758e112cdb74212848a1a1b/regex-2026.1.15-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b5f7d8d2867152cdb625e72a530d2ccb48a3d199159144cbdd63870882fb6f80", size = 769939, upload-time = "2026-01-14T23:16:07.542Z" }, + { url = "https://files.pythonhosted.org/packages/20/06/7e18a4fa9d326daeda46d471a44ef94201c46eaa26dbbb780b5d92cbfdda/regex-2026.1.15-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:492534a0ab925d1db998defc3c302dae3616a2fc3fe2e08db1472348f096ddf2", size = 854753, upload-time = "2026-01-14T23:16:10.395Z" }, + { url = "https://files.pythonhosted.org/packages/3b/67/dc8946ef3965e166f558ef3b47f492bc364e96a265eb4a2bb3ca765c8e46/regex-2026.1.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c661fc820cfb33e166bf2450d3dadbda47c8d8981898adb9b6fe24e5e582ba60", size = 799559, upload-time = "2026-01-14T23:16:12.347Z" }, + { url = "https://files.pythonhosted.org/packages/a5/61/1bba81ff6d50c86c65d9fd84ce9699dd106438ee4cdb105bf60374ee8412/regex-2026.1.15-cp313-cp313t-win32.whl", hash = "sha256:99ad739c3686085e614bf77a508e26954ff1b8f14da0e3765ff7abbf7799f952", size = 268879, upload-time = "2026-01-14T23:16:14.049Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/cef7d4c5fb0ea3ac5c775fd37db5747f7378b29526cc83f572198924ff47/regex-2026.1.15-cp313-cp313t-win_amd64.whl", hash = "sha256:32655d17905e7ff8ba5c764c43cb124e34a9245e45b83c22e81041e1071aee10", size = 280317, upload-time = "2026-01-14T23:16:15.718Z" }, + { url = "https://files.pythonhosted.org/packages/b4/52/4317f7a5988544e34ab57b4bde0f04944c4786128c933fb09825924d3e82/regex-2026.1.15-cp313-cp313t-win_arm64.whl", hash = "sha256:b2a13dd6a95e95a489ca242319d18fc02e07ceb28fa9ad146385194d95b3c829", size = 271551, upload-time = "2026-01-14T23:16:17.533Z" }, + { url = "https://files.pythonhosted.org/packages/52/0a/47fa888ec7cbbc7d62c5f2a6a888878e76169170ead271a35239edd8f0e8/regex-2026.1.15-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d920392a6b1f353f4aa54328c867fec3320fa50657e25f64abf17af054fc97ac", size = 489170, upload-time = "2026-01-14T23:16:19.835Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c4/d000e9b7296c15737c9301708e9e7fbdea009f8e93541b6b43bdb8219646/regex-2026.1.15-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b5a28980a926fa810dbbed059547b02783952e2efd9c636412345232ddb87ff6", size = 291146, upload-time = "2026-01-14T23:16:21.541Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b6/921cc61982e538682bdf3bdf5b2c6ab6b34368da1f8e98a6c1ddc503c9cf/regex-2026.1.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:621f73a07595d83f28952d7bd1e91e9d1ed7625fb7af0064d3516674ec93a2a2", size = 288986, upload-time = "2026-01-14T23:16:23.381Z" }, + { url = "https://files.pythonhosted.org/packages/ca/33/eb7383dde0bbc93f4fb9d03453aab97e18ad4024ac7e26cef8d1f0a2cff0/regex-2026.1.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d7d92495f47567a9b1669c51fc8d6d809821849063d168121ef801bbc213846", size = 799098, upload-time = "2026-01-14T23:16:25.088Z" }, + { url = "https://files.pythonhosted.org/packages/27/56/b664dccae898fc8d8b4c23accd853f723bde0f026c747b6f6262b688029c/regex-2026.1.15-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dd16fba2758db7a3780a051f245539c4451ca20910f5a5e6ea1c08d06d4a76b", size = 864980, upload-time = "2026-01-14T23:16:27.297Z" }, + { url = "https://files.pythonhosted.org/packages/16/40/0999e064a170eddd237bae9ccfcd8f28b3aa98a38bf727a086425542a4fc/regex-2026.1.15-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e1808471fbe44c1a63e5f577a1d5f02fe5d66031dcbdf12f093ffc1305a858e", size = 911607, upload-time = "2026-01-14T23:16:29.235Z" }, + { url = "https://files.pythonhosted.org/packages/07/78/c77f644b68ab054e5a674fb4da40ff7bffb2c88df58afa82dbf86573092d/regex-2026.1.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0751a26ad39d4f2ade8fe16c59b2bf5cb19eb3d2cd543e709e583d559bd9efde", size = 803358, upload-time = "2026-01-14T23:16:31.369Z" }, + { url = "https://files.pythonhosted.org/packages/27/31/d4292ea8566eaa551fafc07797961c5963cf5235c797cc2ae19b85dfd04d/regex-2026.1.15-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0c7684c7f9ca241344ff95a1de964f257a5251968484270e91c25a755532c5", size = 775833, upload-time = "2026-01-14T23:16:33.141Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b2/cff3bf2fea4133aa6fb0d1e370b37544d18c8350a2fa118c7e11d1db0e14/regex-2026.1.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74f45d170a21df41508cb67165456538425185baaf686281fa210d7e729abc34", size = 788045, upload-time = "2026-01-14T23:16:35.005Z" }, + { url = "https://files.pythonhosted.org/packages/8d/99/2cb9b69045372ec877b6f5124bda4eb4253bc58b8fe5848c973f752bc52c/regex-2026.1.15-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f1862739a1ffb50615c0fde6bae6569b5efbe08d98e59ce009f68a336f64da75", size = 859374, upload-time = "2026-01-14T23:16:36.919Z" }, + { url = "https://files.pythonhosted.org/packages/09/16/710b0a5abe8e077b1729a562d2f297224ad079f3a66dce46844c193416c8/regex-2026.1.15-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:453078802f1b9e2b7303fb79222c054cb18e76f7bdc220f7530fdc85d319f99e", size = 763940, upload-time = "2026-01-14T23:16:38.685Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/7585c8e744e40eb3d32f119191969b91de04c073fca98ec14299041f6e7e/regex-2026.1.15-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:a30a68e89e5a218b8b23a52292924c1f4b245cb0c68d1cce9aec9bbda6e2c160", size = 850112, upload-time = "2026-01-14T23:16:40.646Z" }, + { url = "https://files.pythonhosted.org/packages/af/d6/43e1dd85df86c49a347aa57c1f69d12c652c7b60e37ec162e3096194a278/regex-2026.1.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9479cae874c81bf610d72b85bb681a94c95722c127b55445285fb0e2c82db8e1", size = 789586, upload-time = "2026-01-14T23:16:42.799Z" }, + { url = "https://files.pythonhosted.org/packages/93/38/77142422f631e013f316aaae83234c629555729a9fbc952b8a63ac91462a/regex-2026.1.15-cp314-cp314-win32.whl", hash = "sha256:d639a750223132afbfb8f429c60d9d318aeba03281a5f1ab49f877456448dcf1", size = 271691, upload-time = "2026-01-14T23:16:44.671Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a9/ab16b4649524ca9e05213c1cdbb7faa85cc2aa90a0230d2f796cbaf22736/regex-2026.1.15-cp314-cp314-win_amd64.whl", hash = "sha256:4161d87f85fa831e31469bfd82c186923070fc970b9de75339b68f0c75b51903", size = 280422, upload-time = "2026-01-14T23:16:46.607Z" }, + { url = "https://files.pythonhosted.org/packages/be/2a/20fd057bf3521cb4791f69f869635f73e0aaf2b9ad2d260f728144f9047c/regex-2026.1.15-cp314-cp314-win_arm64.whl", hash = "sha256:91c5036ebb62663a6b3999bdd2e559fd8456d17e2b485bf509784cd31a8b1705", size = 273467, upload-time = "2026-01-14T23:16:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/ad/77/0b1e81857060b92b9cad239104c46507dd481b3ff1fa79f8e7f865aae38a/regex-2026.1.15-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ee6854c9000a10938c79238de2379bea30c82e4925a371711af45387df35cab8", size = 492073, upload-time = "2026-01-14T23:16:51.154Z" }, + { url = "https://files.pythonhosted.org/packages/70/f3/f8302b0c208b22c1e4f423147e1913fd475ddd6230565b299925353de644/regex-2026.1.15-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c2b80399a422348ce5de4fe40c418d6299a0fa2803dd61dc0b1a2f28e280fcf", size = 292757, upload-time = "2026-01-14T23:16:53.08Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f0/ef55de2460f3b4a6da9d9e7daacd0cb79d4ef75c64a2af316e68447f0df0/regex-2026.1.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dca3582bca82596609959ac39e12b7dad98385b4fefccb1151b937383cec547d", size = 291122, upload-time = "2026-01-14T23:16:55.383Z" }, + { url = "https://files.pythonhosted.org/packages/cf/55/bb8ccbacabbc3a11d863ee62a9f18b160a83084ea95cdfc5d207bfc3dd75/regex-2026.1.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71d476caa6692eea743ae5ea23cde3260677f70122c4d258ca952e5c2d4e84", size = 807761, upload-time = "2026-01-14T23:16:57.251Z" }, + { url = "https://files.pythonhosted.org/packages/8f/84/f75d937f17f81e55679a0509e86176e29caa7298c38bd1db7ce9c0bf6075/regex-2026.1.15-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c243da3436354f4af6c3058a3f81a97d47ea52c9bd874b52fd30274853a1d5df", size = 873538, upload-time = "2026-01-14T23:16:59.349Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d9/0da86327df70349aa8d86390da91171bd3ca4f0e7c1d1d453a9c10344da3/regex-2026.1.15-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8355ad842a7c7e9e5e55653eade3b7d1885ba86f124dd8ab1f722f9be6627434", size = 915066, upload-time = "2026-01-14T23:17:01.607Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5e/f660fb23fc77baa2a61aa1f1fe3a4eea2bbb8a286ddec148030672e18834/regex-2026.1.15-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f192a831d9575271a22d804ff1a5355355723f94f31d9eef25f0d45a152fdc1a", size = 812938, upload-time = "2026-01-14T23:17:04.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/a47a29bfecebbbfd1e5cd3f26b28020a97e4820f1c5148e66e3b7d4b4992/regex-2026.1.15-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:166551807ec20d47ceaeec380081f843e88c8949780cd42c40f18d16168bed10", size = 781314, upload-time = "2026-01-14T23:17:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/65/ec/7ec2bbfd4c3f4e494a24dec4c6943a668e2030426b1b8b949a6462d2c17b/regex-2026.1.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9ca1cbdc0fbfe5e6e6f8221ef2309988db5bcede52443aeaee9a4ad555e0dac", size = 795652, upload-time = "2026-01-14T23:17:08.521Z" }, + { url = "https://files.pythonhosted.org/packages/46/79/a5d8651ae131fe27d7c521ad300aa7f1c7be1dbeee4d446498af5411b8a9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b30bcbd1e1221783c721483953d9e4f3ab9c5d165aa709693d3f3946747b1aea", size = 868550, upload-time = "2026-01-14T23:17:10.573Z" }, + { url = "https://files.pythonhosted.org/packages/06/b7/25635d2809664b79f183070786a5552dd4e627e5aedb0065f4e3cf8ee37d/regex-2026.1.15-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2a8d7b50c34578d0d3bf7ad58cde9652b7d683691876f83aedc002862a35dc5e", size = 769981, upload-time = "2026-01-14T23:17:12.871Z" }, + { url = "https://files.pythonhosted.org/packages/16/8b/fc3fcbb2393dcfa4a6c5ffad92dc498e842df4581ea9d14309fcd3c55fb9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9d787e3310c6a6425eb346be4ff2ccf6eece63017916fd77fe8328c57be83521", size = 854780, upload-time = "2026-01-14T23:17:14.837Z" }, + { url = "https://files.pythonhosted.org/packages/d0/38/dde117c76c624713c8a2842530be9c93ca8b606c0f6102d86e8cd1ce8bea/regex-2026.1.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:619843841e220adca114118533a574a9cd183ed8a28b85627d2844c500a2b0db", size = 799778, upload-time = "2026-01-14T23:17:17.369Z" }, + { url = "https://files.pythonhosted.org/packages/e3/0d/3a6cfa9ae99606afb612d8fb7a66b245a9d5ff0f29bb347c8a30b6ad561b/regex-2026.1.15-cp314-cp314t-win32.whl", hash = "sha256:e90b8db97f6f2c97eb045b51a6b2c5ed69cedd8392459e0642d4199b94fabd7e", size = 274667, upload-time = "2026-01-14T23:17:19.301Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b2/297293bb0742fd06b8d8e2572db41a855cdf1cae0bf009b1cb74fe07e196/regex-2026.1.15-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef19071f4ac9f0834793af85bd04a920b4407715624e40cb7a0631a11137cdf", size = 284386, upload-time = "2026-01-14T23:17:21.231Z" }, + { url = "https://files.pythonhosted.org/packages/95/e4/a3b9480c78cf8ee86626cb06f8d931d74d775897d44201ccb813097ae697/regex-2026.1.15-cp314-cp314t-win_arm64.whl", hash = "sha256:ca89c5e596fc05b015f27561b3793dc2fa0917ea0d7507eebb448efd35274a70", size = 274837, upload-time = "2026-01-14T23:17:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e7/0e1913dc52eee9c5cf8417c9813c4c55972a3f37d27cfa2e623b79b63dbc/regex-2026.1.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:55b4ea996a8e4458dd7b584a2f89863b1655dd3d17b88b46cbb9becc495a0ec5", size = 488185, upload-time = "2026-01-14T23:17:25.2Z" }, + { url = "https://files.pythonhosted.org/packages/78/df/c52c1ff4221529faad0953e197982fe9508c6dbb42327e31bf98ea07472a/regex-2026.1.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e1e28be779884189cdd57735e997f282b64fd7ccf6e2eef3e16e57d7a34a815", size = 290628, upload-time = "2026-01-14T23:17:27.125Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d2/a2fef3717deaff647d7de2bccf899a576c7eaf042b6b271fc4474515fe97/regex-2026.1.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0057de9eaef45783ff69fa94ae9f0fd906d629d0bd4c3217048f46d1daa32e9b", size = 288509, upload-time = "2026-01-14T23:17:29.017Z" }, + { url = "https://files.pythonhosted.org/packages/70/89/faf5ee5c69168753c845a3d58b4683f61c899d162bfe1264fca88d5b3924/regex-2026.1.15-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc7cd0b2be0f0269283a45c0d8b2c35e149d1319dcb4a43c9c3689fa935c1ee6", size = 781088, upload-time = "2026-01-14T23:17:30.961Z" }, + { url = "https://files.pythonhosted.org/packages/7d/2c/707e5c380ad547c93686e21144e7e24dc2064dd84ec5b751b6dbdfc9be2b/regex-2026.1.15-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8db052bbd981e1666f09e957f3790ed74080c2229007c1dd67afdbf0b469c48b", size = 850516, upload-time = "2026-01-14T23:17:32.946Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3b/baa816cdcad1c0f8195f9f40ab2b2a2246c8a2989dcd90641c0c6559e3fd/regex-2026.1.15-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:343db82cb3712c31ddf720f097ef17c11dab2f67f7a3e7be976c4f82eba4e6df", size = 898124, upload-time = "2026-01-14T23:17:36.019Z" }, + { url = "https://files.pythonhosted.org/packages/e7/74/1eb46bde30899825ed9fdf645eba16b7b97c49d12d300f5177989b9a09a4/regex-2026.1.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55e9d0118d97794367309635df398bdfd7c33b93e2fdfa0b239661cd74b4c14e", size = 791290, upload-time = "2026-01-14T23:17:38.097Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5d/b72e176fb21e2ec248baed01151a342d1f44dd43c2b6bb6a41ad183b274e/regex-2026.1.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:008b185f235acd1e53787333e5690082e4f156c44c87d894f880056089e9bc7c", size = 781996, upload-time = "2026-01-14T23:17:40.109Z" }, + { url = "https://files.pythonhosted.org/packages/61/0e/d3b3710eaafd994a4a71205d114abc38cda8691692a2ce2313abe68e7eb7/regex-2026.1.15-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fd65af65e2aaf9474e468f9e571bd7b189e1df3a61caa59dcbabd0000e4ea839", size = 767578, upload-time = "2026-01-14T23:17:42.134Z" }, + { url = "https://files.pythonhosted.org/packages/09/51/c6a6311833e040f95d229a34d82ac1cec2af8a5c00d58b244f2fceecef87/regex-2026.1.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f42e68301ff4afee63e365a5fc302b81bb8ba31af625a671d7acb19d10168a8c", size = 774354, upload-time = "2026-01-14T23:17:44.392Z" }, + { url = "https://files.pythonhosted.org/packages/cc/97/c522d1f19fb2c549aaf680b115c110cd124c02062bc8c95f33db8583b4bb/regex-2026.1.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f7792f27d3ee6e0244ea4697d92b825f9a329ab5230a78c1a68bd274e64b5077", size = 845297, upload-time = "2026-01-14T23:17:47.145Z" }, + { url = "https://files.pythonhosted.org/packages/99/a0/99468c386ab68a5e24c946c5c353c29c33a95523e275c17839f2446db15d/regex-2026.1.15-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:dbaf3c3c37ef190439981648ccbf0c02ed99ae066087dd117fcb616d80b010a4", size = 755132, upload-time = "2026-01-14T23:17:49.796Z" }, + { url = "https://files.pythonhosted.org/packages/70/33/d5748c7b6c9d3621f12570583561ba529e2d1b12e4f70b8f17979b133e65/regex-2026.1.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:adc97a9077c2696501443d8ad3fa1b4fc6d131fc8fd7dfefd1a723f89071cf0a", size = 835662, upload-time = "2026-01-14T23:17:52.559Z" }, + { url = "https://files.pythonhosted.org/packages/ad/15/1986972c276672505437f1ba3c9706c2d91f321cfb9b2f4d06e8bff1b999/regex-2026.1.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:069f56a7bf71d286a6ff932a9e6fb878f151c998ebb2519a9f6d1cee4bffdba3", size = 779513, upload-time = "2026-01-14T23:17:54.711Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f9/124f6a5cb3969d8e30471ed4f46cfc17c47aef1a9863ee8b4ba1d98b1bc4/regex-2026.1.15-cp39-cp39-win32.whl", hash = "sha256:ea4e6b3566127fda5e007e90a8fd5a4169f0cf0619506ed426db647f19c8454a", size = 265923, upload-time = "2026-01-14T23:17:56.69Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c2/bb8fad7d27f1d71fc9772befd544bccd22eddc62a6735f57b003b4aff005/regex-2026.1.15-cp39-cp39-win_amd64.whl", hash = "sha256:cda1ed70d2b264952e88adaa52eea653a33a1b98ac907ae2f86508eb44f65cdc", size = 277900, upload-time = "2026-01-14T23:17:58.72Z" }, + { url = "https://files.pythonhosted.org/packages/f7/fa/4e033327c1d8350bc812cac906d873984d3d4b39529252f392a47ccc356d/regex-2026.1.15-cp39-cp39-win_arm64.whl", hash = "sha256:b325d4714c3c48277bfea1accd94e193ad6ed42b4bad79ad64f3b8f8a31260a5", size = 270413, upload-time = "2026-01-14T23:18:00.764Z" }, ] [[package]] @@ -4076,6 +6070,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, ] +[[package]] +name = "rich-rst" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils", marker = "python_full_version >= '3.10'" }, + { name = "rich", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/6d/a506aaa4a9eaa945ed8ab2b7347859f53593864289853c5d6d62b77246e0/rich_rst-1.3.2.tar.gz", hash = "sha256:a1196fdddf1e364b02ec68a05e8ff8f6914fee10fbca2e6b6735f166bb0da8d4", size = 14936, upload-time = "2025-10-14T16:49:45.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl", hash = "sha256:a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a", size = 12567, upload-time = "2025-10-14T16:49:42.953Z" }, +] + [[package]] name = "rich-toolkit" version = "0.18.1" @@ -4374,28 +6381,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.14" +version = "0.15.0" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, + { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, + { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, + { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, + { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, + { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, + { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, ] [[package]] @@ -4410,6 +6416,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, ] +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography", marker = "python_full_version >= '3.10'" }, + { name = "jeepney", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + [[package]] name = "sentry-sdk" version = "2.52.0" @@ -4548,15 +6567,15 @@ wheels = [ [[package]] name = "sqlmodel" -version = "0.0.31" +version = "0.0.32" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "sqlalchemy" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/89/67f8964f3b2ed073fa4e95201e708291935d00e3600f36f09c1be3e279fe/sqlmodel-0.0.32.tar.gz", hash = "sha256:48e8fe4c8c3d7d8bf8468db17fa92ca680421e86cfec8b352217ef40736767be", size = 94140, upload-time = "2026-02-01T18:19:14.752Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/ed/de/d9b40ed2c570fd612c2abd57e4d9084a9d8eb1797447e2ce897b77b1c4b2/sqlmodel-0.0.32-py3-none-any.whl", hash = "sha256:d62f0702599592046c1a136d3512feab3d5a80e2988642ef0ed2c89b9b8b297b", size = 27416, upload-time = "2026-02-01T18:19:15.992Z" }, ] [[package]] @@ -4565,7 +6584,7 @@ version = "3.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio", marker = "python_full_version >= '3.10'" }, - { name = "starlette", version = "0.50.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "starlette", version = "0.52.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8b/8d/00d280c03ffd39aaee0e86ec81e2d3b9253036a0f93f51d10503adef0e65/sse_starlette-3.2.0.tar.gz", hash = "sha256:8127594edfb51abe44eac9c49e59b0b01f1039d0c7461c6fd91d4e03b70da422", size = 27253, upload-time = "2026-01-17T13:11:05.62Z" } wheels = [ @@ -4590,7 +6609,7 @@ wheels = [ [[package]] name = "starlette" -version = "0.50.0" +version = "0.52.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -4600,9 +6619,9 @@ dependencies = [ { name = "anyio", marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, ] [[package]] @@ -4626,7 +6645,7 @@ wheels = [ [[package]] name = "strawberry-graphql" -version = "0.291.1" +version = "0.291.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -4639,9 +6658,9 @@ dependencies = [ { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/18/5a07bc95bdf1cb73e93cd1f1d5b11750ded6f5e4d9407e7d500abc635b36/strawberry_graphql-0.291.1.tar.gz", hash = "sha256:13463d09e042857e4c12eee0eafc2bbdef963e4a30e20ed9a9c9fb0d487d4aac", size = 217765, upload-time = "2026-02-06T10:07:15.861Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/dd/e0e68f4b17da6ff5773fcd4bebf86fc4ff8620c854be816d047e9af8c4aa/strawberry_graphql-0.291.2.tar.gz", hash = "sha256:e6076604a786e8437bc64a27348584c082113442f072daf757b56e4863543a97", size = 217730, upload-time = "2026-02-06T14:40:51.173Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/7b/47ec56118decc90b9ae85a98180dc326fdad2ee031cba0e05c6c62349b88/strawberry_graphql-0.291.1-py3-none-any.whl", hash = "sha256:09798d3172b42be4d5526771ec581e1b1f93c7020b82a8b07b4c507676f75aa0", size = 316439, upload-time = "2026-02-06T10:07:18.247Z" }, + { url = "https://files.pythonhosted.org/packages/6f/14/93908a029605955e3411cdb0f2e8cfe170e5331da23357ed71d5c51b15bc/strawberry_graphql-0.291.2-py3-none-any.whl", hash = "sha256:f71d3669086c6747fd4760e6fafe3605d9a33f7d168886e5edd2b61a04972e56", size = 316389, upload-time = "2026-02-06T14:40:53.482Z" }, ] [[package]] @@ -4656,6 +6675,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/43/47c7cf84b3bd74a8631b02d47db356656bb8dff6f2e61a4c749963814d0d/super_collections-0.6.2-py3-none-any.whl", hash = "sha256:291b74d26299e9051d69ad9d89e61b07b6646f86a57a2f5ab3063d206eee9c56", size = 16173, upload-time = "2025-09-30T00:37:07.104Z" }, ] +[[package]] +name = "taskgroup" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version >= '3.10' and python_full_version < '3.14'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.14'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/8d/e218e0160cc1b692e6e0e5ba34e8865dbb171efeb5fc9a704544b3020605/taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d", size = 11504, upload-time = "2025-01-03T09:24:13.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/b1/74babcc824a57904e919f3af16d86c08b524c0691504baf038ef2d7f655c/taskgroup-0.2.2-py2.py3-none-any.whl", hash = "sha256:e2c53121609f4ae97303e9ea1524304b4de6faf9eb2c9280c7f87976479a52fb", size = 14237, upload-time = "2025-01-03T09:24:11.41Z" }, +] + +[[package]] +name = "temporalio" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "nexus-rpc", version = "1.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "protobuf", version = "5.29.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "python-dateutil", marker = "python_full_version < '3.10'" }, + { name = "types-protobuf", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/32/375ab75d0ebb468cf9c8abbc450a03d3a8c66401fc320b338bd8c00d36b4/temporalio-1.16.0.tar.gz", hash = "sha256:dd926f3e30626fd4edf5e0ce596b75ecb5bbe0e4a0281e545ac91b5577967c91", size = 1733873, upload-time = "2025-08-21T22:12:50.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/36/12bb7234c83ddca4b8b032c8f1a9e07a03067c6ed6d2ddb39c770a4c87c6/temporalio-1.16.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:547c0853310350d3e5b5b9c806246cbf2feb523f685b05bf14ec1b0ece8a7bb6", size = 12540769, upload-time = "2025-08-21T22:11:24.551Z" }, + { url = "https://files.pythonhosted.org/packages/3c/16/a7d402435b8f994979abfeffd3f5ffcaaeada467ac16438e61c51c9f7abe/temporalio-1.16.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b05bb0d06025645aed6f936615311a6774eb8dc66280f32a810aac2283e1258", size = 12968631, upload-time = "2025-08-21T22:11:48.375Z" }, + { url = "https://files.pythonhosted.org/packages/11/6f/16663eef877b61faa5fd917b3a63497416ec4319195af75f6169a1594479/temporalio-1.16.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a08aed4e0f6c2b6bfc779b714e91dfe8c8491a0ddb4c4370627bb07f9bddcfd", size = 13164612, upload-time = "2025-08-21T22:12:16.366Z" }, + { url = "https://files.pythonhosted.org/packages/af/0e/8c6704ca7033aa09dc084f285d70481d758972cc341adc3c84d5f82f7b01/temporalio-1.16.0-cp39-abi3-win_amd64.whl", hash = "sha256:7c190362b0d7254f1f93fb71456063e7b299ac85a89f6227758af82c6a5aa65b", size = 13177058, upload-time = "2025-08-21T22:12:44.239Z" }, +] + +[[package]] +name = "temporalio" +version = "1.20.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version >= '3.10' and python_full_version < '3.14'", +] +dependencies = [ + { name = "nexus-rpc", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "protobuf", version = "6.33.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil", marker = "python_full_version == '3.10.*'" }, + { name = "types-protobuf", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/db/7d5118d28b0918888e1ec98f56f659fdb006351e06d95f30f4274962a76f/temporalio-1.20.0.tar.gz", hash = "sha256:5a6a85b7d298b7359bffa30025f7deac83c74ac095a4c6952fbf06c249a2a67c", size = 1850498, upload-time = "2025-11-25T21:25:20.225Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/1b/e69052aa6003eafe595529485d9c62d1382dd5e671108f1bddf544fb6032/temporalio-1.20.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:fba70314b4068f8b1994bddfa0e2ad742483f0ae714d2ef52e63013ccfd7042e", size = 12061638, upload-time = "2025-11-25T21:24:57.918Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/3e8c67ed7f23bedfa231c6ac29a7a9c12b89881da7694732270f3ecd6b0c/temporalio-1.20.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffc5bb6cabc6ae67f0bfba44de6a9c121603134ae18784a2ff3a7f230ad99080", size = 11562603, upload-time = "2025-11-25T21:25:01.721Z" }, + { url = "https://files.pythonhosted.org/packages/6d/be/ed0cc11702210522a79e09703267ebeca06eb45832b873a58de3ca76b9d0/temporalio-1.20.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1e80c1e4cdf88fa8277177f563edc91466fe4dc13c0322f26e55c76b6a219e6", size = 11824016, upload-time = "2025-11-25T21:25:06.771Z" }, + { url = "https://files.pythonhosted.org/packages/9d/97/09c5cafabc80139d97338a2bdd8ec22e08817dfd2949ab3e5b73565006eb/temporalio-1.20.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba92d909188930860c9d89ca6d7a753bc5a67e4e9eac6cea351477c967355eed", size = 12189521, upload-time = "2025-11-25T21:25:12.091Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/5689c014a76aff3b744b3ee0d80815f63b1362637814f5fbb105244df09b/temporalio-1.20.0-cp310-abi3-win_amd64.whl", hash = "sha256:eacfd571b653e0a0f4aa6593f4d06fc628797898f0900d400e833a1f40cad03a", size = 12745027, upload-time = "2025-11-25T21:25:16.827Z" }, +] + [[package]] name = "tenacity" version = "9.1.2" @@ -4715,6 +6793,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, ] +[[package]] +name = "tiktoken" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex", marker = "python_full_version >= '3.10'" }, + { name = "requests", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/b3/2cb7c17b6c4cf8ca983204255d3f1d95eda7213e247e6947a0ee2c747a2c/tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970", size = 1051991, upload-time = "2025-10-06T20:21:34.098Z" }, + { url = "https://files.pythonhosted.org/packages/27/0f/df139f1df5f6167194ee5ab24634582ba9a1b62c6b996472b0277ec80f66/tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16", size = 995798, upload-time = "2025-10-06T20:21:35.579Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5d/26a691f28ab220d5edc09b9b787399b130f24327ef824de15e5d85ef21aa/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030", size = 1129865, upload-time = "2025-10-06T20:21:36.675Z" }, + { url = "https://files.pythonhosted.org/packages/b2/94/443fab3d4e5ebecac895712abd3849b8da93b7b7dec61c7db5c9c7ebe40c/tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134", size = 1152856, upload-time = "2025-10-06T20:21:37.873Z" }, + { url = "https://files.pythonhosted.org/packages/54/35/388f941251b2521c70dd4c5958e598ea6d2c88e28445d2fb8189eecc1dfc/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a", size = 1195308, upload-time = "2025-10-06T20:21:39.577Z" }, + { url = "https://files.pythonhosted.org/packages/f8/00/c6681c7f833dd410576183715a530437a9873fa910265817081f65f9105f/tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892", size = 1255697, upload-time = "2025-10-06T20:21:41.154Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d2/82e795a6a9bafa034bf26a58e68fe9a89eeaaa610d51dbeb22106ba04f0a/tiktoken-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1", size = 879375, upload-time = "2025-10-06T20:21:43.201Z" }, + { url = "https://files.pythonhosted.org/packages/de/46/21ea696b21f1d6d1efec8639c204bdf20fde8bafb351e1355c72c5d7de52/tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb", size = 1051565, upload-time = "2025-10-06T20:21:44.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d9/35c5d2d9e22bb2a5f74ba48266fb56c63d76ae6f66e02feb628671c0283e/tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa", size = 995284, upload-time = "2025-10-06T20:21:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/01/84/961106c37b8e49b9fdcf33fe007bb3a8fdcc380c528b20cc7fbba80578b8/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc", size = 1129201, upload-time = "2025-10-06T20:21:47.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d0/3d9275198e067f8b65076a68894bb52fd253875f3644f0a321a720277b8a/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded", size = 1152444, upload-time = "2025-10-06T20:21:48.139Z" }, + { url = "https://files.pythonhosted.org/packages/78/db/a58e09687c1698a7c592e1038e01c206569b86a0377828d51635561f8ebf/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd", size = 1195080, upload-time = "2025-10-06T20:21:49.246Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/a9e4d2bf91d515c0f74afc526fd773a812232dd6cda33ebea7f531202325/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967", size = 1255240, upload-time = "2025-10-06T20:21:50.274Z" }, + { url = "https://files.pythonhosted.org/packages/9d/15/963819345f1b1fb0809070a79e9dd96938d4ca41297367d471733e79c76c/tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def", size = 879422, upload-time = "2025-10-06T20:21:51.734Z" }, + { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" }, + { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" }, + { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" }, + { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" }, + { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, + { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" }, + { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" }, + { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" }, + { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" }, + { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" }, + { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" }, + { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" }, + { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, + { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, + { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d1/7507bfb9c2ceef52ae3ae813013215c185648e21127538aae66dedd3af9c/tiktoken-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d51d75a5bffbf26f86554d28e78bfb921eae998edc2675650fd04c7e1f0cdc1e", size = 1053407, upload-time = "2025-10-06T20:22:35.492Z" }, + { url = "https://files.pythonhosted.org/packages/ee/4a/8ea1da602ac39dee4356b4cd6040a2325507482c36043044b6f581597b4f/tiktoken-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:09eb4eae62ae7e4c62364d9ec3a57c62eea707ac9a2b2c5d6bd05de6724ea179", size = 997150, upload-time = "2025-10-06T20:22:37.286Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1a/62d1d36b167eccd441aff2f0091551ca834295541b949d161021aa658167/tiktoken-0.12.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:df37684ace87d10895acb44b7f447d4700349b12197a526da0d4a4149fde074c", size = 1131575, upload-time = "2025-10-06T20:22:39.023Z" }, + { url = "https://files.pythonhosted.org/packages/f7/16/544207d63c8c50edd2321228f21d236e4e49d235128bb7e3e0f69eed0807/tiktoken-0.12.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4c9614597ac94bb294544345ad8cf30dac2129c05e2db8dc53e082f355857af7", size = 1154920, upload-time = "2025-10-06T20:22:40.175Z" }, + { url = "https://files.pythonhosted.org/packages/99/4c/0a3504157c81364fc0c64cada54efef0567961357e786706ea63bc8946e1/tiktoken-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:20cf97135c9a50de0b157879c3c4accbb29116bcf001283d26e073ff3b345946", size = 1196766, upload-time = "2025-10-06T20:22:41.365Z" }, + { url = "https://files.pythonhosted.org/packages/d4/46/8e6a258ae65447c75770fe5ea8968acab369e8c9f537f727c91f83772325/tiktoken-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:15d875454bbaa3728be39880ddd11a5a2a9e548c29418b41e8fd8a767172b5ec", size = 1258278, upload-time = "2025-10-06T20:22:42.846Z" }, + { url = "https://files.pythonhosted.org/packages/35/43/3b95de4f5e76f3cafc70dac9b1b9cfe759ff3bfd494ac91a280e93772e90/tiktoken-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cff3688ba3c639ebe816f8d58ffbbb0aa7433e23e08ab1cade5d175fc973fb3", size = 881888, upload-time = "2025-10-06T20:22:44.059Z" }, +] + [[package]] name = "tinycss2" version = "1.4.0" @@ -4905,20 +7051,6 @@ wheels = [ { 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]] -name = "typer-slim" -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'" }, - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/17/d4/064570dec6358aa9049d4708e4a10407d74c99258f8b2136bb8702303f1a/typer_slim-0.21.1.tar.gz", hash = "sha256:73495dd08c2d0940d611c5a8c04e91c2a0a98600cbd4ee19192255a233b6dbfd", size = 110478, upload-time = "2026-01-06T11:21:11.176Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/0a/4aca634faf693e33004796b6cee0ae2e1dba375a800c16ab8d3eff4bb800/typer_slim-0.21.1-py3-none-any.whl", hash = "sha256:6e6c31047f171ac93cc5a973c9e617dbc5ab2bddc4d0a3135dc161b4e2020e0d", size = 47444, upload-time = "2026-01-06T11:21:12.441Z" }, -] - [[package]] name = "types-orjson" version = "3.6.2" @@ -4928,6 +7060,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/84/b34abd2d08381c5113e475908a1d79d27dc9a15f669213cee4ca03d1a891/types_orjson-3.6.2-py3-none-any.whl", hash = "sha256:22ee9a79236b6b0bfb35a0684eded62ad930a88a56797fa3c449b026cf7dbfe4", size = 2224, upload-time = "2022-01-07T11:31:09.271Z" }, ] +[[package]] +name = "types-protobuf" +version = "6.32.1.20251210" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/59/c743a842911887cd96d56aa8936522b0cd5f7a7f228c96e81b59fced45be/types_protobuf-6.32.1.20251210.tar.gz", hash = "sha256:c698bb3f020274b1a2798ae09dc773728ce3f75209a35187bd11916ebfde6763", size = 63900, upload-time = "2025-12-10T03:14:25.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/43/58e75bac4219cbafee83179505ff44cae3153ec279be0e30583a73b8f108/types_protobuf-6.32.1.20251210-py3-none-any.whl", hash = "sha256:2641f78f3696822a048cfb8d0ff42ccd85c25f12f871fbebe86da63793692140", size = 77921, upload-time = "2025-12-10T03:14:24.477Z" }, +] + [[package]] name = "types-requests" version = "2.31.0.6" @@ -4961,11 +7102,11 @@ wheels = [ [[package]] name = "types-ujson" -version = "5.10.0.20240515" +version = "5.10.0.20250822" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/49/abb4bcb9f2258f785edbf236b517c3e7ba8a503a8cbce6b5895930586cc0/types-ujson-5.10.0.20240515.tar.gz", hash = "sha256:ceae7127f0dafe4af5dd0ecf98ee13e9d75951ef963b5c5a9b7ea92e0d71f0d7", size = 3571, upload-time = "2024-05-15T02:24:43.704Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/bd/d372d44534f84864a96c19a7059d9b4d29db8541828b8b9dc3040f7a46d0/types_ujson-5.10.0.20250822.tar.gz", hash = "sha256:0a795558e1f78532373cf3f03f35b1f08bc60d52d924187b97995ee3597ba006", size = 8437, upload-time = "2025-08-22T03:02:19.433Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/1f/9d018cee3d09ab44a5211f0b5ed9b0422ad9a8c226bf3967f5884498d8f0/types_ujson-5.10.0.20240515-py3-none-any.whl", hash = "sha256:02bafc36b3a93d2511757a64ff88bd505e0a57fba08183a9150fbcfcb2015310", size = 2757, upload-time = "2024-05-15T02:24:42.315Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f2/d812543c350674d8b3f6e17c8922248ee3bb752c2a76f64beb8c538b40cf/types_ujson-5.10.0.20250822-py3-none-any.whl", hash = "sha256:3e9e73a6dc62ccc03449d9ac2c580cd1b7a8e4873220db498f7dd056754be080", size = 7657, upload-time = "2025-08-22T03:02:18.699Z" }, ] [[package]] @@ -5483,6 +7624,246 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ad/e4/8d97cca767bcc1be76d16fb76951608305561c6e056811587f36cb1316a8/werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc", size = 225025, upload-time = "2026-01-08T17:49:21.859Z" }, ] +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04", size = 53482, upload-time = "2025-08-12T05:51:44.467Z" }, + { url = "https://files.pythonhosted.org/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2", size = 38676, upload-time = "2025-08-12T05:51:32.636Z" }, + { url = "https://files.pythonhosted.org/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c", size = 38957, upload-time = "2025-08-12T05:51:54.655Z" }, + { url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775", size = 81975, upload-time = "2025-08-12T05:52:30.109Z" }, + { url = "https://files.pythonhosted.org/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd", size = 83149, upload-time = "2025-08-12T05:52:09.316Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05", size = 82209, upload-time = "2025-08-12T05:52:10.331Z" }, + { url = "https://files.pythonhosted.org/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418", size = 81551, upload-time = "2025-08-12T05:52:31.137Z" }, + { url = "https://files.pythonhosted.org/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390", size = 36464, upload-time = "2025-08-12T05:53:01.204Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6", size = 38748, upload-time = "2025-08-12T05:53:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18", size = 36810, upload-time = "2025-08-12T05:52:51.906Z" }, + { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, + { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, + { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, + { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/41/be/be9b3b0a461ee3e30278706f3f3759b9b69afeedef7fe686036286c04ac6/wrapt-1.17.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc", size = 53485, upload-time = "2025-08-12T05:51:53.11Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a8/8f61d6b8f526efc8c10e12bf80b4206099fea78ade70427846a37bc9cbea/wrapt-1.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9", size = 38675, upload-time = "2025-08-12T05:51:42.885Z" }, + { url = "https://files.pythonhosted.org/packages/48/f1/23950c29a25637b74b322f9e425a17cc01a478f6afb35138ecb697f9558d/wrapt-1.17.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d", size = 38956, upload-time = "2025-08-12T05:52:03.149Z" }, + { url = "https://files.pythonhosted.org/packages/43/46/dd0791943613885f62619f18ee6107e6133237a6b6ed8a9ecfac339d0b4f/wrapt-1.17.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a", size = 81745, upload-time = "2025-08-12T05:52:49.62Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ec/bb2d19bd1a614cc4f438abac13ae26c57186197920432d2a915183b15a8b/wrapt-1.17.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139", size = 82833, upload-time = "2025-08-12T05:52:27.738Z" }, + { url = "https://files.pythonhosted.org/packages/8d/eb/66579aea6ad36f07617fedca8e282e49c7c9bab64c63b446cfe4f7f47a49/wrapt-1.17.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df", size = 81889, upload-time = "2025-08-12T05:52:29.023Z" }, + { url = "https://files.pythonhosted.org/packages/04/9c/a56b5ac0e2473bdc3fb11b22dd69ff423154d63861cf77911cdde5e38fd2/wrapt-1.17.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b", size = 81344, upload-time = "2025-08-12T05:52:50.869Z" }, + { url = "https://files.pythonhosted.org/packages/93/4c/9bd735c42641d81cb58d7bfb142c58f95c833962d15113026705add41a07/wrapt-1.17.3-cp39-cp39-win32.whl", hash = "sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81", size = 36462, upload-time = "2025-08-12T05:53:19.623Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ea/0b72f29cb5ebc16eb55c57dc0c98e5de76fc97f435fd407f7d409459c0a6/wrapt-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f", size = 38740, upload-time = "2025-08-12T05:53:18.271Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8b/9eae65fb92321e38dbfec7719b87d840a4b92fde83fd1bbf238c5488d055/wrapt-1.17.3-cp39-cp39-win_arm64.whl", hash = "sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f", size = 36806, upload-time = "2025-08-12T05:52:58.765Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + +[[package]] +name = "xai-sdk" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp", marker = "python_full_version >= '3.10'" }, + { name = "googleapis-common-protos", marker = "python_full_version >= '3.10'" }, + { name = "grpcio", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-sdk", marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "protobuf", version = "6.33.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pydantic", marker = "python_full_version >= '3.10'" }, + { name = "requests", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/66/1e0163eac090733d0ed0836a0cd3c14f5b59abeaa6fdba71c7b56b1916e4/xai_sdk-1.6.1.tar.gz", hash = "sha256:b55528df188f8c8448484021d735f75b0e7d71719ddeb432c5f187ac67e3c983", size = 388223, upload-time = "2026-01-29T03:13:07.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/98/8b4019b35f2200295c5eec8176da4b779ec3a0fd60eba7196b618f437e1f/xai_sdk-1.6.1-py3-none-any.whl", hash = "sha256:f478dee9bd8839b8d341bd075277d0432aff5cd7120a4284547d25c6c9e7ab3b", size = 240917, upload-time = "2026-01-29T03:13:05.626Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/43/a2204825342f37c337f5edb6637040fa14e365b2fcc2346960201d457579/yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e", size = 140517, upload-time = "2025-10-06T14:08:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/674f3e6f02266428c56f704cd2501c22f78e8b2eeb23f153117cc86fb28a/yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f", size = 93495, upload-time = "2025-10-06T14:08:46.2Z" }, + { url = "https://files.pythonhosted.org/packages/b8/12/5b274d8a0f30c07b91b2f02cba69152600b47830fcfb465c108880fcee9c/yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf", size = 94400, upload-time = "2025-10-06T14:08:47.855Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7f/df1b6949b1fa1aa9ff6de6e2631876ad4b73c4437822026e85d8acb56bb1/yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a", size = 347545, upload-time = "2025-10-06T14:08:49.683Z" }, + { url = "https://files.pythonhosted.org/packages/84/09/f92ed93bd6cd77872ab6c3462df45ca45cd058d8f1d0c9b4f54c1704429f/yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c", size = 319598, upload-time = "2025-10-06T14:08:51.215Z" }, + { url = "https://files.pythonhosted.org/packages/c3/97/ac3f3feae7d522cf7ccec3d340bb0b2b61c56cb9767923df62a135092c6b/yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147", size = 363893, upload-time = "2025-10-06T14:08:53.144Z" }, + { url = "https://files.pythonhosted.org/packages/06/49/f3219097403b9c84a4d079b1d7bda62dd9b86d0d6e4428c02d46ab2c77fc/yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb", size = 371240, upload-time = "2025-10-06T14:08:55.036Z" }, + { url = "https://files.pythonhosted.org/packages/35/9f/06b765d45c0e44e8ecf0fe15c9eacbbde342bb5b7561c46944f107bfb6c3/yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6", size = 346965, upload-time = "2025-10-06T14:08:56.722Z" }, + { url = "https://files.pythonhosted.org/packages/c5/69/599e7cea8d0fcb1694323b0db0dda317fa3162f7b90166faddecf532166f/yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0", size = 342026, upload-time = "2025-10-06T14:08:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/95/6f/9dfd12c8bc90fea9eab39832ee32ea48f8e53d1256252a77b710c065c89f/yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda", size = 335637, upload-time = "2025-10-06T14:09:00.506Z" }, + { url = "https://files.pythonhosted.org/packages/57/2e/34c5b4eb9b07e16e873db5b182c71e5f06f9b5af388cdaa97736d79dd9a6/yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc", size = 359082, upload-time = "2025-10-06T14:09:01.936Z" }, + { url = "https://files.pythonhosted.org/packages/31/71/fa7e10fb772d273aa1f096ecb8ab8594117822f683bab7d2c5a89914c92a/yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737", size = 357811, upload-time = "2025-10-06T14:09:03.445Z" }, + { url = "https://files.pythonhosted.org/packages/26/da/11374c04e8e1184a6a03cf9c8f5688d3e5cec83ed6f31ad3481b3207f709/yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467", size = 351223, upload-time = "2025-10-06T14:09:05.401Z" }, + { url = "https://files.pythonhosted.org/packages/82/8f/e2d01f161b0c034a30410e375e191a5d27608c1f8693bab1a08b089ca096/yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea", size = 82118, upload-time = "2025-10-06T14:09:11.148Z" }, + { url = "https://files.pythonhosted.org/packages/62/46/94c76196642dbeae634c7a61ba3da88cd77bed875bf6e4a8bed037505aa6/yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca", size = 86852, upload-time = "2025-10-06T14:09:12.958Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/7df4f179d3b1a6dcb9a4bd2ffbc67642746fcafdb62580e66876ce83fff4/yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b", size = 82012, upload-time = "2025-10-06T14:09:14.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/94/fd/6480106702a79bcceda5fd9c63cb19a04a6506bd5ce7fd8d9b63742f0021/yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748", size = 141301, upload-time = "2025-10-06T14:12:19.01Z" }, + { url = "https://files.pythonhosted.org/packages/42/e1/6d95d21b17a93e793e4ec420a925fe1f6a9342338ca7a563ed21129c0990/yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859", size = 93864, upload-time = "2025-10-06T14:12:21.05Z" }, + { url = "https://files.pythonhosted.org/packages/32/58/b8055273c203968e89808413ea4c984988b6649baabf10f4522e67c22d2f/yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9", size = 94706, upload-time = "2025-10-06T14:12:23.287Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/d7bfbc28a88c2895ecd0da6a874def0c147de78afc52c773c28e1aa233a3/yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054", size = 347100, upload-time = "2025-10-06T14:12:28.527Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e8/37a1e7b99721c0564b1fc7b0a4d1f595ef6fb8060d82ca61775b644185f7/yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b", size = 318902, upload-time = "2025-10-06T14:12:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ef/34724449d7ef2db4f22df644f2dac0b8a275d20f585e526937b3ae47b02d/yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60", size = 363302, upload-time = "2025-10-06T14:12:32.295Z" }, + { url = "https://files.pythonhosted.org/packages/8a/04/88a39a5dad39889f192cce8d66cc4c58dbeca983e83f9b6bf23822a7ed91/yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890", size = 370816, upload-time = "2025-10-06T14:12:34.01Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1f/5e895e547129413f56c76be2c3ce4b96c797d2d0ff3e16a817d9269b12e6/yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba", size = 346465, upload-time = "2025-10-06T14:12:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/11/13/a750e9fd6f9cc9ed3a52a70fe58ffe505322f0efe0d48e1fd9ffe53281f5/yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca", size = 341506, upload-time = "2025-10-06T14:12:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/3c/67/bb6024de76e7186611ebe626aec5b71a2d2ecf9453e795f2dbd80614784c/yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba", size = 335030, upload-time = "2025-10-06T14:12:39.775Z" }, + { url = "https://files.pythonhosted.org/packages/a2/be/50b38447fd94a7992996a62b8b463d0579323fcfc08c61bdba949eef8a5d/yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b", size = 358560, upload-time = "2025-10-06T14:12:41.547Z" }, + { url = "https://files.pythonhosted.org/packages/e2/89/c020b6f547578c4e3dbb6335bf918f26e2f34ad0d1e515d72fd33ac0c635/yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e", size = 357290, upload-time = "2025-10-06T14:12:43.861Z" }, + { url = "https://files.pythonhosted.org/packages/8c/52/c49a619ee35a402fa3a7019a4fa8d26878fec0d1243f6968bbf516789578/yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8", size = 350700, upload-time = "2025-10-06T14:12:46.868Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c9/f5042d87777bf6968435f04a2bbb15466b2f142e6e47fa4f34d1a3f32f0c/yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b", size = 82323, upload-time = "2025-10-06T14:12:48.633Z" }, + { url = "https://files.pythonhosted.org/packages/fd/58/d00f7cad9eba20c4eefac2682f34661d1d1b3a942fc0092eb60e78cfb733/yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed", size = 87145, upload-time = "2025-10-06T14:12:50.241Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a3/70904f365080780d38b919edd42d224b8c4ce224a86950d2eaa2a24366ad/yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2", size = 82173, upload-time = "2025-10-06T14:12:51.869Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + [[package]] name = "zipp" version = "3.23.0" From 266a3138b5332de819606d339db2392f1595fe68 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Feb 2026 17:18:53 +0000 Subject: [PATCH 050/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a55a15abd..689610049 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* ⬆️ Upgrade development dependencies. PR [#14854](https://github.com/fastapi/fastapi/pull/14854) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.3 ### Refactors From 0f5987b560fbf8fac9ed30ed3477f2acc97d9ae9 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 6 Feb 2026 20:23:48 +0300 Subject: [PATCH 051/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20tr=20(outdated=20and=20missing)=20(#14838)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tr/docs/advanced/advanced-dependencies.md | 8 +- docs/tr/docs/alternatives.md | 610 +++++++++--------- docs/tr/docs/deployment/docker.md | 8 +- docs/tr/docs/history-design-future.md | 14 +- docs/tr/docs/translation-banner.md | 11 + docs/tr/docs/tutorial/body-multiple-params.md | 6 +- .../tutorial/path-operation-configuration.md | 8 +- 7 files changed, 337 insertions(+), 328 deletions(-) create mode 100644 docs/tr/docs/translation-banner.md diff --git a/docs/tr/docs/advanced/advanced-dependencies.md b/docs/tr/docs/advanced/advanced-dependencies.md index 48c0b6211..8afb544bd 100644 --- a/docs/tr/docs/advanced/advanced-dependencies.md +++ b/docs/tr/docs/advanced/advanced-dependencies.md @@ -78,13 +78,13 @@ Bu detaylar, özellikle 0.121.0'dan eski bir FastAPI uygulamanız varsa ve `yiel ### `yield` ve `scope` ile dependency'ler { #dependencies-with-yield-and-scope } -0.121.0 sürümünde FastAPI, `yield` kullanan dependency'ler için `Depends(scope="function")` desteğini ekledi. +0.121.0 sürümünde FastAPI, `Depends(scope="function")` desteğini ekledi. `Depends(scope="function")` kullanıldığında, `yield` sonrasındaki çıkış kodu, *path operation function* biter bitmez, response client'a geri gönderilmeden önce çalıştırılır. `Depends(scope="request")` (varsayılan) kullanıldığında ise `yield` sonrasındaki çıkış kodu, response gönderildikten sonra çalıştırılır. -Daha fazlasını [Dependencies with `yield` - Early exit and `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope) dokümantasyonunda okuyabilirsiniz. +Daha fazlasını [`yield` ile Dependency'ler - Erken çıkış ve `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope) dokümantasyonunda okuyabilirsiniz. ### `yield` ve `StreamingResponse` ile dependency'ler, Teknik Detaylar { #dependencies-with-yield-and-streamingresponse-technical-details } @@ -132,7 +132,7 @@ SQLModel (veya SQLAlchemy) kullanarak bu spesifik senaryoya sahipseniz, session' Böylece session veritabanı bağlantısını serbest bırakır ve diğer request'ler bunu kullanabilir. -`yield` kullanan bir dependency'den erken çıkış gerektiren farklı bir kullanım senaryonuz varsa, lütfen kullanım senaryonuzla birlikte ve `yield` kullanan dependency'ler için erken kapatmadan neden fayda göreceğinizi açıklayarak bir GitHub Discussion Question oluşturun. +`yield` kullanan bir dependency'den erken çıkış gerektiren farklı bir kullanım senaryonuz varsa, lütfen kullanım senaryonuzla birlikte ve `yield` kullanan dependency'ler için erken kapatmadan neden fayda göreceğinizi açıklayarak bir GitHub Discussion Sorusu oluşturun. `yield` kullanan dependency'lerde erken kapatma için ikna edici kullanım senaryoları varsa, erken kapatmayı seçmeli (opt-in) hale getiren yeni bir yöntem eklemeyi düşünebilirim. @@ -144,7 +144,7 @@ Bu davranış 0.110.0 sürümünde değiştirildi. Amaç, handler olmayan (inter ### Background Tasks ve `yield` ile dependency'ler, Teknik Detaylar { #background-tasks-and-dependencies-with-yield-technical-details } -FastAPI 0.106.0 öncesinde, `yield` sonrasında exception raise etmek mümkün değildi; çünkü `yield` kullanan dependency'lerdeki çıkış kodu response gönderildikten *sonra* çalıştırılıyordu. Bu nedenle [Exception Handlers](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} zaten çalışmış olurdu. +FastAPI 0.106.0 öncesinde, `yield` sonrasında exception raise etmek mümkün değildi; çünkü `yield` kullanan dependency'lerdeki çıkış kodu response gönderildikten *sonra* çalıştırılıyordu. Bu nedenle [Exception Handler'ları](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} zaten çalışmış olurdu. Bu tasarımın ana sebeplerinden biri, background task'lerin içinde dependency'lerin "yield ettiği" aynı objeleri kullanmaya izin vermekti; çünkü çıkış kodu, background task'ler bittikten sonra çalıştırılıyordu. diff --git a/docs/tr/docs/alternatives.md b/docs/tr/docs/alternatives.md index 9b603ea81..afc1a45ef 100644 --- a/docs/tr/docs/alternatives.md +++ b/docs/tr/docs/alternatives.md @@ -1,483 +1,483 @@ -# Alternatifler, İlham Kaynakları ve Karşılaştırmalar +# Alternatifler, İlham Kaynakları ve Karşılaştırmalar { #alternatives-inspiration-and-comparisons } -**FastAPI**'ya neler ilham verdi? Diğer alternatiflerle karşılaştırıldığında farkları neler? **FastAPI** diğer alternatiflerinden neler öğrendi? +**FastAPI**'a nelerin ilham verdiği, alternatiflerle nasıl karşılaştırıldığı ve onlardan neler öğrendiği. -## Giriş +## Giriş { #intro } Başkalarının daha önceki çalışmaları olmasaydı, **FastAPI** var olmazdı. -Geçmişte oluşturulan pek çok araç **FastAPI**'a ilham kaynağı olmuştur. +Önceden oluşturulan birçok araç, ortaya çıkışına ilham verdi. -Yıllardır yeni bir framework oluşturmaktan kaçınıyordum. Başlangıçta **FastAPI**'ın çözdüğü sorunları çözebilmek için pek çok farklı framework, eklenti ve araç kullanmayı denedim. +Yıllarca yeni bir framework oluşturmaktan kaçındım. Önce **FastAPI**’ın bugün kapsadığı özelliklerin tamamını, birçok farklı framework, eklenti ve araçla çözmeyi denedim. -Ancak bir noktada, geçmişteki diğer araçlardan en iyi fikirleri alarak bütün bu çözümleri kapsayan, ayrıca bütün bunları Python'ın daha önce mevcut olmayan özelliklerini (Python 3.6+ ile gelen tip belirteçleri) kullanarak yapan bir şey üretmekten başka seçenek kalmamıştı. +Ancak bir noktada, geçmişteki araçlardan en iyi fikirleri alıp, mümkün olan en iyi şekilde birleştiren ve daha önce mevcut olmayan dil özelliklerini (Python 3.6+ tip belirteçleri) kullanarak tüm bu özellikleri sağlayan bir şey geliştirmekten başka seçenek kalmadı. -## Daha Önce Geliştirilen Araçlar +## Daha Önce Geliştirilen Araçlar { #previous-tools } -### Django +### Django { #django } -Django geniş çapta güvenilen, Python ekosistemindeki en popüler web framework'üdür. Instagram gibi sistemleri geliştirmede kullanılmıştır. +Python ekosistemindeki en popüler ve yaygın olarak güvenilen web framework’üdür. Instagram gibi sistemleri geliştirmede kullanılmıştır. -MySQL ve PostgreSQL gibi ilişkisel veritabanlarıyla nispeten sıkı bir şekilde bağlantılıdır. Bu nedenle Couchbase, MongoDB ve Cassandra gibi NoSQL veritabanlarını ana veritabanı motoru olarak kullanmak pek de kolay değil. +MySQL veya PostgreSQL gibi ilişkisel veritabanlarıyla nispeten sıkı bağlıdır, bu nedenle Couchbase, MongoDB, Cassandra vb. gibi bir NoSQL veritabanını ana depolama motoru olarak kullanmak pek kolay değildir. -Modern ön uçlarda (React, Vue.js ve Angular gibi) veya diğer sistemler (örneğin nesnelerin interneti cihazları) tarafından kullanılan API'lar yerine arka uçta HTML üretmek için oluşturuldu. +Modern bir ön uç (React, Vue.js, Angular gibi) veya onunla haberleşen diğer sistemler (ör. IoT cihazları) tarafından tüketilen API’lar üretmekten ziyade, arka uçta HTML üretmek için oluşturulmuştur. -### Django REST Framework +### Django REST Framework { #django-rest-framework } -Django REST framework'ü, Django'nun API kabiliyetlerini arttırmak için arka planda Django kullanan esnek bir araç grubu olarak oluşturuldu. +Django REST Framework, Django üzerine kurulu esnek bir araç takımı olarak, Web API’lar geliştirmeyi ve Django’nun API kabiliyetlerini artırmayı hedefler. -Üstelik Mozilla, Red Hat ve Eventbrite gibi pek çok şirket tarafından kullanılıyor. +Mozilla, Red Hat ve Eventbrite gibi birçok şirket tarafından kullanılmaktadır. -**Otomatik API dökümantasyonu**nun ilk örneklerinden biri olduğu için, **FastAPI** arayışına ilham veren ilk fikirlerden biri oldu. +**Otomatik API dökümantasyonu**nun ilk örneklerinden biriydi ve bu, “**FastAPI** arayışına” ilham veren ilk fikirlerden biriydi. /// note | Not -Django REST Framework'ü, aynı zamanda **FastAPI**'ın dayandığı Starlette ve Uvicorn'un da yaratıcısı olan Tom Christie tarafından geliştirildi. +Django REST Framework, **FastAPI**'ın üzerine inşa edildiği Starlette ve Uvicorn'un da yaratıcısı olan Tom Christie tarafından geliştirildi. /// -/// check | **FastAPI**'a nasıl ilham verdi? +/// check | **FastAPI**'a ilham olan -Kullanıcılar için otomatik API dökümantasyonu sunan bir web arayüzüne sahip olmalı. +Otomatik API dökümantasyonu sağlayan bir web arayüzü sunmak. /// -### Flask +### Flask { #flask } -Flask bir mikro framework olduğundan Django gibi framework'lerin aksine veritabanı entegrasyonu gibi Django ile gelen pek çok özelliği direkt barındırmaz. +Flask bir “mikroframework”tür, Django’da varsayılan gelen pek çok özelliği (veritabanı entegrasyonları vb.) içermez. -Sağladığı basitlik ve esneklik NoSQL veritabanlarını ana veritabanı sistemi olarak kullanmak gibi şeyler yapmaya olanak sağlar. +Bu basitlik ve esneklik, NoSQL veritabanlarını ana veri depolama sistemi olarak kullanmak gibi şeyleri mümkün kılar. -Yapısı oldukça basit olduğundan öğrenmesi de nispeten basittir, tabii dökümantasyonu bazı noktalarda biraz teknik hale geliyor. +Çok basit olduğu için öğrenmesi nispeten sezgiseldir, ancak dökümantasyon bazı noktalarda biraz teknikleşebilir. -Ayrıca Django ile birlikte gelen veritabanı, kullanıcı yönetimi ve diğer pek çok özelliğe ihtiyaç duymayan uygulamalarda da yaygın olarak kullanılıyor. Ancak bu tür özelliklerin pek çoğu eklentiler ile eklenebiliyor. +Ayrıca veritabanı, kullanıcı yönetimi veya Django’da önceden gelen pek çok özelliğe ihtiyaç duymayan uygulamalar için de yaygın olarak kullanılır. Yine de bu özelliklerin çoğu eklentilerle eklenebilir. -Uygulama parçalarının böyle ayrılıyor oluşu ve istenilen özelliklerle genişletilebilecek bir mikro framework olmak tam da benim istediğim bir özellikti. +Bileşenlerin ayrık olması ve gerekeni tam olarak kapsayacak şekilde genişletilebilen bir “mikroframework” olması, özellikle korumak istediğim bir özelliktir. -Flask'ın basitliği göz önünde bulundurulduğu zaman, API geliştirmek için iyi bir seçim gibi görünüyordu. Sıradaki şey ise Flask için bir "Django REST Framework"! +Flask’ın sadeliği göz önüne alındığında, API geliştirmek için iyi bir aday gibi görünüyordu. Sırada, Flask için bir “Django REST Framework” bulmak vardı. -/// check | **FastAPI**'a nasıl ilham verdi? +/// check | **FastAPI**'a ilham olan -Gereken araçları ve parçaları birleştirip eşleştirmeyi kolaylaştıracak bir mikro framework olmalı. +Gereken araç ve parçaları kolayca eşleştirip birleştirmeyi sağlayan bir mikroframework olmak. -Basit ve kullanması kolay bir yönlendirme sistemine sahip olmalı. +Basit ve kullanımı kolay bir yönlendirme (routing) sistemine sahip olmak. /// -### Requests +### Requests { #requests } -**FastAPI** aslında **Requests**'in bir alternatifi değil. İkisininde kapsamı oldukça farklı. +**FastAPI** aslında **Requests**’in bir alternatifi değildir. Kapsamları çok farklıdır. -Aslında Requests'i bir FastAPI uygulamasının *içinde* kullanmak daha olağan olurdu. +Hatta bir FastAPI uygulamasının içinde Requests kullanmak yaygındır. -Ama yine de, FastAPI, Requests'ten oldukça ilham aldı. +Yine de FastAPI, Requests’ten epey ilham almıştır. -**Requests**, API'lar ile bir istemci olarak *etkileşime geçmeyi* sağlayan bir kütüphaneyken **FastAPI** bir sunucu olarak API'lar oluşturmaya yarar. +**Requests** bir kütüphane olarak API’larla (istemci olarak) etkileşime geçmeye yararken, **FastAPI** API’lar (sunucu olarak) geliştirmeye yarar. -Öyle ya da böyle zıt uçlarda olmalarına rağmen birbirlerini tamamlıyorlar. +Yani daha çok zıt uçlardadırlar ama birbirlerini tamamlarlar. -Requests oldukça basit ve sezgisel bir tasarıma sahip, kullanması da mantıklı varsayılan değerlerle oldukça kolay. Ama aynı zamanda çok güçlü ve gayet özelleştirilebilir. +Requests çok basit ve sezgisel bir tasarıma sahiptir, mantıklı varsayılanlarla kullanımı çok kolaydır. Aynı zamanda çok güçlü ve özelleştirilebilirdir. -Bu yüzden resmi web sitede de söylendiği gibi: +Bu yüzden resmi web sitesinde de söylendiği gibi: -> Requests, tüm zamanların en çok indirilen Python paketlerinden biridir. +> Requests, tüm zamanların en çok indirilen Python paketlerinden biridir -Kullanım şekli oldukça basit. Örneğin bir `GET` isteği yapmak için aşağıdaki yeterli: +Kullanımı çok basittir. Örneğin bir `GET` isteği yapmak için: ```Python response = requests.get("http://example.com/some/url") ``` -Bunun FastAPI'deki API *yol işlemi* şöyle görünür: +Buna karşılık bir FastAPI API *path operation*’ı şöyle olabilir: ```Python hl_lines="1" @app.get("/some/url") def read_url(): - return {"message": "Hello World!"} + return {"message": "Hello World"} ``` `requests.get(...)` ile `@app.get(...)` arasındaki benzerliklere bakın. -/// check | **FastAPI**'a nasıl ilham verdi? +/// check | **FastAPI**'a ilham olan -* Basit ve sezgisel bir API'ya sahip olmalı. -* HTTP metot isimlerini (işlemlerini) anlaşılır olacak bir şekilde, direkt kullanmalı. -* Mantıklı varsayılan değerlere ve buna rağmen güçlü bir özelleştirme desteğine sahip olmalı. +* Basit ve sezgisel bir API’ya sahip olmak. +* HTTP metot isimlerini (işlemlerini) doğrudan, anlaşılır ve sezgisel bir şekilde kullanmak. +* Mantıklı varsayılanlara sahip olmak ama güçlü özelleştirmeler de sunmak. /// -### Swagger / OpenAPI +### Swagger / OpenAPI { #swagger-openapi } -Benim Django REST Framework'ünden istediğim ana özellik otomatik API dökümantasyonuydu. +Django REST Framework’ünden istediğim ana özellik otomatik API dökümantasyonuydu. -Daha sonra API'ları dökümanlamak için Swagger adında JSON (veya JSON'un bir uzantısı olan YAML'ı) kullanan bir standart olduğunu buldum. +Sonra API’ları JSON (veya JSON’un bir uzantısı olan YAML) kullanarak dökümante etmek için Swagger adlı bir standart olduğunu gördüm. -Üstelik Swagger API'ları için zaten halihazırda oluşturulmuş bir web arayüzü vardı. Yani, bir API için Swagger dökümantasyonu oluşturmak bu arayüzü otomatik olarak kullanabilmek demekti. +Ve Swagger API’ları için zaten oluşturulmuş bir web arayüzü vardı. Yani bir API için Swagger dökümantasyonu üretebilmek, bu web arayüzünü otomatik kullanabilmek demekti. -Swagger bir noktada Linux Foundation'a verildi ve adı OpenAPI olarak değiştirildi. +Bir noktada Swagger, Linux Foundation’a devredildi ve OpenAPI olarak yeniden adlandırıldı. -İşte bu yüzden versiyon 2.0 hakkında konuşurken "Swagger", versiyon 3 ve üzeri için ise "OpenAPI" adını kullanmak daha yaygın. +Bu yüzden, 2.0 sürümü söz konusu olduğunda “Swagger”, 3+ sürümler için ise “OpenAPI” denmesi yaygındır. -/// check | **FastAPI**'a nasıl ilham verdi? +/// check | **FastAPI**'a ilham olan -API spesifikasyonları için özel bir şema yerine bir açık standart benimseyip kullanmalı. +API spesifikasyonları için özel bir şema yerine açık bir standart benimsemek ve kullanmak. -Ayrıca standarda bağlı kullanıcı arayüzü araçlarını entegre etmeli: +Ve standartlara dayalı kullanıcı arayüzü araçlarını entegre etmek: * Swagger UI * ReDoc -Yukarıdaki ikisi oldukça popüler ve istikrarlı olduğu için seçildi, ancak hızlı bir araştırma yaparak **FastAPI** ile kullanabileceğiniz pek çok OpenAPI alternatifi arayüz bulabilirsiniz. +Bu ikisi oldukça popüler ve istikrarlı oldukları için seçildi; hızlı bir aramayla OpenAPI için onlarca alternatif kullanıcı arayüzü bulabilirsiniz (**FastAPI** ile de kullanabilirsiniz). /// -### Flask REST framework'leri +### Flask REST framework’leri { #flask-rest-frameworks } -Pek çok Flask REST framework'ü var, fakat bunları biraz araştırdıktan sonra pek çoğunun artık geliştirilmediğini ve göze batan bazı sorunlarının olduğunu gördüm. +Birçok Flask REST framework’ü var; ancak zaman ayırıp inceledikten sonra çoğunun artık sürdürülmediğini veya bazı kritik sorunlar nedeniyle uygun olmadıklarını gördüm. -### Marshmallow +### Marshmallow { #marshmallow } -API sistemlerine gereken ana özelliklerden biri de koddan veriyi alıp ağ üzerinde gönderilebilecek bir şeye çevirmek, yani veri dönüşümü. Bu işleme veritabanındaki veriyi içeren bir objeyi JSON objesine çevirmek, `datetime` objelerini metinlere çevirmek gibi örnekler verilebilir. +API sistemlerinin ihtiyaç duyduğu temel özelliklerden biri, koddan (Python) veriyi alıp ağ üzerinden gönderilebilecek bir şeye dönüştürmek, yani veri “dönüşüm”üdür. Örneğin, bir veritabanından gelen verileri içeren bir objeyi JSON objesine dönüştürmek, `datetime` objelerini string’e çevirmek vb. -API'lara gereken bir diğer büyük özellik ise veri doğrulamadır, yani verinin çeşitli parametrelere bağlı olarak doğru ve tutarlı olduğundan emin olmaktır. Örneğin bir alanın `int` olmasına karar verdiniz, daha sonra rastgele bir metin değeri almasını istemezsiniz. Bu özellikle sisteme dışarıdan gelen veri için kullanışlı bir özellik oluyor. +API’ların ihtiyaç duyduğu bir diğer önemli özellik, veri doğrulamadır; belirli parametreler göz önüne alındığında verinin geçerli olduğundan emin olmak. Örneğin, bir alanın `int` olması ve rastgele bir metin olmaması. Bu özellikle dışarıdan gelen veriler için kullanışlıdır. -Bir veri doğrulama sistemi olmazsa bütün bu kontrolleri koda dökerek kendiniz yapmak zorunda kalırdınız. +Bir veri doğrulama sistemi olmadan, tüm bu kontrolleri kod içinde el ile yapmanız gerekir. -Marshmallow bu özellikleri sağlamak için geliştirilmişti. Benim de geçmişte oldukça sık kullandığım harika bir kütüphanedir. +Marshmallow, bu özellikleri sağlamak için inşa edildi. Harika bir kütüphanedir ve geçmişte çok kullandım. -Ama... Python'un tip belirteçleri gelmeden önce oluşturulmuştu. Yani her şemayı tanımlamak için Marshmallow'un sunduğu spesifik araçları ve sınıfları kullanmanız gerekiyordu. +Ancak Python tip belirteçlerinden önce yazılmıştır. Dolayısıyla her şemayı tanımlamak için Marshmallow’un sağladığı belirli yardımcılar ve sınıflar kullanılır. -/// check | **FastAPI**'a nasıl ilham verdi? +/// check | **FastAPI**'a ilham olan -Kod kullanarak otomatik olarak veri tipini ve veri doğrulamayı belirten "şemalar" tanımlamalı. +Kodla, veri tiplerini ve doğrulamayı otomatik sağlayan “şemalar” tanımlamak. /// -### Webargs +### Webargs { #webargs } -API'ların ihtiyacı olan bir diğer önemli özellik ise gelen isteklerdeki verileri Python objelerine ayrıştırabilmektir. +API’ların ihtiyaç duyduğu bir diğer büyük özellik, gelen isteklerden veriyi ayrıştırmadır. -Webargs, Flask gibi bir kaç framework'ün üzerinde bunu sağlamak için geliştirilen bir araçtır. +Webargs, Flask dahil birkaç framework’ün üzerinde bunu sağlamak için geliştirilmiş bir araçtır. -Veri doğrulama için arka planda Marshmallow kullanıyor, hatta aynı geliştiriciler tarafından oluşturuldu. +Veri doğrulama için arka planda Marshmallow’u kullanır. Aynı geliştiriciler tarafından yazılmıştır. -Webargs da harika bir araç ve onu da geçmişte henüz **FastAPI** yokken çok kullandım. +**FastAPI**’dan önce benim de çok kullandığım harika bir araçtır. /// info | Bilgi -Webargs aynı Marshmallow geliştirileri tarafından oluşturuldu. +Webargs, Marshmallow geliştiricileri tarafından oluşturuldu. /// -/// check | **FastAPI**'a nasıl ilham verdi? +/// check | **FastAPI**'a ilham olan -Gelen istek verisi için otomatik veri doğrulamaya sahip olmalı. +Gelen istek verisini otomatik doğrulamak. /// -### APISpec +### APISpec { #apispec } -Marshmallow ve Webargs eklentiler olarak; veri doğrulama, ayrıştırma ve dönüştürmeyi sağlıyor. +Marshmallow ve Webargs; doğrulama, ayrıştırma ve dönüşümü eklenti olarak sağlar. -Ancak dökümantasyondan hala ses seda yok. Daha sonrasında ise APISpec geldi. +Ama dökümantasyon eksikti. Sonra APISpec geliştirildi. -APISpec pek çok framework için bir eklenti olarak kullanılıyor (Starlette için de bir eklentisi var). +Birçok framework için bir eklentidir (Starlette için de bir eklenti vardır). -Şemanın tanımını yol'a istek geldiğinde çalışan her bir fonksiyonun döküman dizesinin içine YAML formatında olacak şekilde yazıyorsunuz o da OpenAPI şemaları üretiyor. +Çalışma şekli: Her bir route’u işleyen fonksiyonun docstring’i içine YAML formatında şema tanımı yazarsınız. -Flask, Starlette, Responder ve benzerlerinde bu şekilde çalışıyor. +Ve OpenAPI şemaları üretir. -Fakat sonrasında yine mikro sözdizimi problemiyle karşılaşıyoruz. Python metinlerinin içinde koskoca bir YAML oluyor. +Flask, Starlette, Responder vb. için çalışma şekli böyledir. -Editör bu konuda pek yardımcı olamaz. Üstelik eğer parametreleri ya da Marshmallow şemalarını değiştirip YAML kodunu güncellemeyi unutursak artık döküman geçerliliğini yitiriyor. +Ancak yine, Python metni içinde (kocaman bir YAML) mikro bir söz dizimi sorunu ortaya çıkar. + +Editör bu konuda pek yardımcı olamaz. Parametreleri veya Marshmallow şemalarını değiştirip docstring’teki YAML’ı güncellemeyi unutursak, üretilen şema geçerliliğini yitirir. /// info | Bilgi -APISpec de aynı Marshmallow geliştiricileri tarafından oluşturuldu. +APISpec, Marshmallow geliştiricileri tarafından oluşturuldu. /// -/// check | **FastAPI**'a nasıl ilham verdi? +/// check | **FastAPI**'a ilham olan -API'lar için açık standart desteği olmalı (OpenAPI gibi). +API’lar için açık standart olan OpenAPI’ı desteklemek. /// -### Flask-apispec +### Flask-apispec { #flask-apispec } -Flask-apispec ise Webargs, Marshmallow ve APISpec'i birbirine bağlayan bir Flask eklentisi. +Webargs, Marshmallow ve APISpec’i bir araya getiren bir Flask eklentisidir. -Webargs ve Marshmallow'daki bilgiyi APISpec ile otomatik OpenAPI şemaları üretmek için kullanıyor. +Webargs ve Marshmallow’dan aldığı bilgiyi kullanarak, APISpec ile otomatik OpenAPI şemaları üretir. -Hak ettiği değeri görmeyen, harika bir araç. Piyasadaki çoğu Flask eklentisinden çok daha popüler olmalı. Hak ettiği değeri görmüyor oluşunun sebebi ise dökümantasyonun çok kısa ve soyut olması olabilir. +Harika ama yeterince değer görmeyen bir araçtır. Mevcut birçok Flask eklentisinden çok daha popüler olmalıydı. Muhtemelen dökümantasyonunun fazla kısa ve soyut olmasından kaynaklanıyor olabilir. -Böylece Flask-apispec, Python döküman dizilerine YAML gibi farklı bir syntax yazma sorununu çözmüş oldu. +Python docstring’leri içine YAML (farklı bir söz dizimi) yazma ihtiyacını ortadan kaldırdı. -**FastAPI**'ı geliştirene dek benim favori arka uç kombinasyonum Flask'in yanında Marshmallow ve Webargs ile birlikte Flask-apispec idi. +**FastAPI**’yı inşa edene kadar, Flask + Flask-apispec + Marshmallow + Webargs kombinasyonu benim favori arka uç stack’imdi. -Bunu kullanmak, bir kaç full-stack Flask projesi oluşturucusunun yaratılmasına yol açtı. Bunlar benim (ve bir kaç harici ekibin de) şimdiye kadar kullandığı asıl stackler: +Bunu kullanmak, birkaç Flask full‑stack üreticisinin ortaya çıkmasına yol açtı. Şu ana kadar benim (ve birkaç harici ekibin) kullandığı ana stack’ler: * https://github.com/tiangolo/full-stack * https://github.com/tiangolo/full-stack-flask-couchbase * https://github.com/tiangolo/full-stack-flask-couchdb -Aynı full-stack üreticiler [**FastAPI** Proje Üreticileri](project-generation.md){.internal-link target=_blank}'nin de temelini oluşturdu. +Aynı full‑stack üreticiler, [**FastAPI** Proje Üreticileri](project-generation.md){.internal-link target=_blank}’nin de temelini oluşturdu. /// info | Bilgi -Flask-apispec de aynı Marshmallow geliştiricileri tarafından üretildi. +Flask-apispec, Marshmallow geliştiricileri tarafından oluşturuldu. /// -/// check | **FastAPI**'a nasıl ilham oldu? +/// check | **FastAPI**'a ilham olan -Veri dönüşümü ve veri doğrulamayı tanımlayan kodu kullanarak otomatik olarak OpenAPI şeması oluşturmalı. +Veri dönüşümü ve doğrulamayı tanımlayan aynı koddan, OpenAPI şemasını otomatik üretmek. /// -### NestJS (and Angular) +### NestJS (ve Angular) { #nestjs-and-angular } -Bu Python teknolojisi bile değil. NestJS, Angulardan ilham almış olan bir JavaScript (TypeScript) NodeJS framework'ü. +Bu Python bile değil; NestJS, Angular’dan ilham alan bir JavaScript (TypeScript) NodeJS framework’üdür. -Flask-apispec ile yapılabileceklere nispeten benzeyen bir şey elde ediyor. +Flask-apispec ile yapılabilene kısmen benzer bir şey başarır. -Angular 2'den ilham alan, içine gömülü bir bağımlılık enjeksiyonu sistemi var. Bildiğim diğer tüm bağımlılık enjeksiyonu sistemlerinde olduğu gibi"bağımlılık"ları önceden kaydetmenizi gerektiriyor. Böylece projeyi daha detaylı hale getiriyor ve kod tekrarını da arttırıyor. +Angular 2’den esinlenen, entegre bir bağımlılık enjeksiyonu sistemi vardır. “Injectable”ları önceden kaydetmeyi gerektirir (bildiğim diğer bağımlılık enjeksiyonu sistemlerinde olduğu gibi), bu da ayrıntıyı ve kod tekrarını artırır. -Parametreler TypeScript tipleri (Python tip belirteçlerine benzer) ile açıklandığından editör desteği oldukça iyi. +Parametreler TypeScript tipleriyle (Python tip belirteçlerine benzer) açıklandığından, editör desteği oldukça iyidir. -Ama TypeScript verileri kod JavaScript'e derlendikten sonra korunmadığından, bunlara dayanarak aynı anda veri doğrulaması, veri dönüşümü ve dökümantasyon tanımlanamıyor. Bundan ve bazı tasarım tercihlerinden dolayı veri doğrulaması, dönüşümü ve otomatik şema üretimi için pek çok yere dekorator eklemek gerekiyor. Bu da projeyi oldukça detaylandırıyor. +Ancak TypeScript tip bilgisi JavaScript’e derlemeden sonra korunmadığından, aynı anda tiplere dayanarak doğrulama, dönüşüm ve dökümantasyon tanımlanamaz. Bu ve bazı tasarım kararları nedeniyle doğrulama, dönüşüm ve otomatik şema üretimi için birçok yere dekoratör eklemek gerekir; proje oldukça ayrıntılı hâle gelir. -İç içe geçen derin modelleri pek iyi işleyemiyor. Yani eğer istekteki JSON gövdesi derin bir JSON objesiyse düzgün bir şekilde dökümante edilip doğrulanamıyor. +İçiçe modelleri çok iyi işleyemez. Yani istek gövdesindeki JSON, içinde başka alanları ve onlar da içiçe JSON objelerini içeriyorsa, doğru şekilde dökümante edilip doğrulanamaz. -/// check | **FastAPI**'a nasıl ilham oldu? +/// check | **FastAPI**'a ilham olan -Güzel bir editör desteği için Python tiplerini kullanmalı. +Harika editör desteği için Python tiplerini kullanmak. -Güçlü bir bağımlılık enjeksiyon sistemine sahip olmalı. Kod tekrarını minimuma indirecek bir yol bulmalı. +Güçlü bir bağımlılık enjeksiyonu sistemine sahip olmak. Kod tekrarını en aza indirmenin bir yolunu bulmak. /// -### Sanic +### Sanic { #sanic } -Sanic, `asyncio`'ya dayanan son derece hızlı Python kütüphanelerinden biriydi. Flask'a epey benzeyecek şekilde geliştirilmişti. - -/// note | Teknik detaylar - -İçerisinde standart Python `asyncio` döngüsü yerine `uvloop` kullanıldı. Hızının asıl kaynağı buydu. - -Uvicorn ve Starlette'e ilham kaynağı olduğu oldukça açık, şu anda ikisi de açık karşılaştırmalarda Sanicten daha hızlı gözüküyor. - -/// - -/// check | **FastAPI**'a nasıl ilham oldu? - -Uçuk performans sağlayacak bir yol bulmalı. - -Tam da bu yüzden **FastAPI** Starlette'e dayanıyor, çünkü Starlette şu anda kullanılabilir en hızlı framework. (üçüncü parti karşılaştırmalı testlerine göre) - -/// - -### Falcon - -Falcon ise bir diğer yüksek performanslı Python framework'ü. Minimal olacak şekilde Hug gibi diğer framework'lerin temeli olabilmek için tasarlandı. - -İki parametre kabul eden fonksiyonlar şeklinde tasarlandı, biri "istek" ve diğeri ise "cevap". Sonra isteğin çeşitli kısımlarını **okuyor**, cevaba ise bir şeyler **yazıyorsunuz**. Bu tasarımdan dolayı istek parametrelerini ve gövdelerini standart Python tip belirteçlerini kullanarak fonksiyon parametreleriyle belirtmek mümkün değil. - -Yani veri doğrulama, veri dönüştürme ve dökümantasyonun hepsi kodda yer almalı, otomatik halledemiyoruz. Ya da Falcon üzerine bir framework olarak uygulanmaları gerekiyor, aynı Hug'da olduğu gibi. Bu ayrım Falcon'un tasarımından esinlenen, istek ve cevap objelerini parametre olarak işleyen diğer kütüphanelerde de yer alıyor. - -/// check | **FastAPI**'a nasıl ilham oldu? - -Harika bir performans'a sahip olmanın yollarını bulmalı. - -Hug ile birlikte (Hug zaten Falcon'a dayandığından) **FastAPI**'ın fonksiyonlarda `cevap` parametresi belirtmesinde ilham kaynağı oldu. - -FastAPI'da opsiyonel olmasına rağmen, daha çok header'lar, çerezler ve alternatif durum kodları belirlemede kullanılıyor. - -/// - -### Molten - -**FastAPI**'ı geliştirmenin ilk aşamalarında Molten'ı keşfettim. Pek çok ortak fikrimiz vardı: - -* Python'daki tip belirteçlerini baz alıyordu. -* Bunlara bağlı olarak veri doğrulaması ve dökümantasyon sağlıyordu. -* Bir bağımlılık enjeksiyonu sistemi vardı. - -Veri doğrulama, veri dönüştürme ve dökümantasyon için Pydantic gibi bir üçüncü parti kütüphane kullanmıyor, kendi içerisinde bunlara sahip. Yani bu veri tipi tanımlarını tekrar kullanmak pek de kolay değil. - -Biraz daha detaylı ayarlamalara gerek duyuyor. Ayrıca ASGI yerine WSGI'a dayanıyor. Yani Uvicorn, Starlette ve Sanic gibi araçların yüksek performansından faydalanacak şekilde tasarlanmamış. - -Bağımlılık enjeksiyonu sistemi bağımlılıkların önceden kaydedilmesini ve sonrasında belirlenen veri tiplerine göre çözülmesini gerektiriyor. Yani spesifik bir tip, birden fazla bileşen ile belirlenemiyor. - -Yol'lar fonksiyonun üstünde endpoint'i işleyen dekoratörler yerine farklı yerlerde tanımlanan fonksiyonlarla belirlenir. Bu Flask (ve Starlette) yerine daha çok Django'nun yaklaşımına daha yakın bir metot. Bu, kodda nispeten birbiriyle sıkı ilişkili olan şeyleri ayırmaya sebep oluyor. - -/// check | **FastAPI**'a nasıl ilham oldu? - -Model özelliklerinin "standart" değerlerini kullanarak veri tipleri için ekstra veri doğrulama koşulları tanımlamalı. Bu editör desteğini geliştiriyor ve daha önceden Pydantic'te yoktu. - -Bu aslında Pydantic'in de aynı doğrulama stiline geçmesinde ilham kaynağı oldu. Şu anda bütün bu özellikler Pydantic'in yapısında yer alıyor. - -/// - -### Hug - -Hug, Python tip belirteçlerini kullanarak API parametrelerinin tipini belirlemeyi uygulayan ilk framework'lerdendi. Bu, diğer araçlara da ilham kaynağı olan harika bir fikirdi. - -Tip belirlerken standart Python veri tipleri yerine kendi özel tiplerini kullandı, yine de bu ileriye dönük devasa bir adımdı. - -Hug ayrıca tüm API'ı JSON ile ifade eden özel bir şema oluşturan ilk framework'lerdendir. - -OpenAPI veya JSON Şeması gibi bir standarda bağlı değildi. Yani Swagger UI gibi diğer araçlarla entegre etmek kolay olmazdı. Ama yine de, bu oldukça yenilikçi bir fikirdi. - -Ayrıca ilginç ve çok rastlanmayan bir özelliği vardı: aynı framework'ü kullanarak hem API'lar hem de CLI'lar oluşturmak mümkündü. - -Senkron çalışan Python web framework'lerinin standardına (WSGI) dayandığından dolayı Websocket'leri ve diğer şeyleri işleyemiyor, ancak yine de yüksek performansa sahip. - -/// info | Bilgi - -Hug, Python dosyalarında bulunan dahil etme satırlarını otomatik olarak sıralayan harika bir araç olan `isort`'un geliştiricisi Timothy Crosley tarafından geliştirildi. - -/// - -/// check | **FastAPI**'a nasıl ilham oldu? - -Hug, APIStar'ın çeşitli kısımlarında esin kaynağı oldu ve APIStar'la birlikte en umut verici bulduğum araçlardan biriydi. - -**FastAPI**, Python tip belirteçlerini kullanarak parametre belirlemede ve API'ı otomatık tanımlayan bir şema üretmede de Hug'a esinlendi. - -**FastAPI**'ın header ve çerez tanımlamak için fonksiyonlarda `response` parametresini belirtmesinde de Hug'dan ilham alındı. - -/// - -### APIStar (<= 0.5) - -**FastAPI**'ı geliştirmeye başlamadan hemen önce **APIStar** sunucusunu buldum. Benim aradığım şeylerin neredeyse hepsine sahipti ve harika bir tasarımı vardı. - -Benim şimdiye kadar gördüğüm Python tip belirteçlerini kullanarak parametre ve istekler belirlemeyi uygulayan ilk framework'lerdendi (Molten ve NestJS'den önce). APIStar'ı da aşağı yukarı Hug ile aynı zamanlarda buldum. Fakat APIStar OpenAPI standardını kullanıyordu. - -Farklı yerlerdeki tip belirteçlerine bağlı olarak otomatik veri doğrulama, veri dönüştürme ve OpenAPI şeması oluşturma desteği sunuyordu. - -Gövde şema tanımları Pydantic ile aynı Python tip belirteçlerini kullanmıyordu, biraz daha Marsmallow'a benziyordu. Dolayısıyla editör desteği de o kadar iyi olmazdı ama APIStar eldeki en iyi seçenekti. - -O dönemlerde karşılaştırmalarda en iyi performansa sahipti (yalnızca Starlette'e kaybediyordu). - -Başlangıçta otomatik API dökümantasyonu sunan bir web arayüzü yoktu, ama ben ona Swagger UI ekleyebileceğimi biliyordum. - -Bağımlılık enjeksiyon sistemi vardı. Yukarıda bahsettiğim diğer araçlar gibi bundaki sistem de bileşenlerin önceden kaydedilmesini gerektiriyordu. Yine de harika bir özellikti. - -Güvenlik entegrasyonu olmadığından dolayı APIStar'ı hiç bir zaman tam bir projede kullanamadım. Bu yüzden Flask-apispec'e bağlı full-stack proje üreticilerde sahip olduğum özellikleri tamamen değiştiremedim. Bu güvenlik entegrasyonunu ekleyen bir PR oluşturmak da projelerim arasında yer alıyordu. - -Sonrasında ise projenin odağı değişti. - -Geliştiricinin Starlette'e odaklanması gerekince proje de artık bir API web framework'ü olmayı bıraktı. - -Artık APIStar, OpenAPI özelliklerini doğrulamak için bir dizi araç sunan bir proje haline geldi. - -/// info | Bilgi - -APIStar, aşağıdaki projeleri de üreten Tom Christie tarafından geliştirildi: - -* Django REST Framework -* **FastAPI**'ın da dayandığı Starlette -* Starlette ve **FastAPI** tarafından da kullanılan Uvicorn - -/// - -/// check | **FastAPI**'a nasıl ilham oldu? - -Var oldu. - -Aynı Python veri tipleriyle birden fazla şeyi belirleme (veri doğrulama, veri dönüştürme ve dökümantasyon), bir yandan da harika bir editör desteği sunma, benim muhteşem bulduğum bir fikirdi. - -Uzunca bir süre boyunca benzer bir framework arayıp pek çok farklı alternatifi denedikten sonra, APIStar en iyi seçenekti. - -Sonra APIStar bir sunucu olmayı bıraktı ve Starlette oluşturuldu. Starlette, böyle bir sunucu sistemi için daha iyi bir temel sunuyordu. Bu da **FastAPI**'ın son esin kaynağıydı. - -Ben bu önceki araçlardan öğrendiklerime dayanarak **FastAPI**'ın özelliklerini arttırıp geliştiriyor, tip desteği sistemi ve diğer kısımları iyileştiriyorum ancak yine de **FastAPI**'ı APIStar'ın "ruhani varisi" olarak görüyorum. - -/// - -## **FastAPI** Tarafından Kullanılanlar - -### Pydantic - -Pydantic Python tip belirteçlerine dayanan; veri doğrulama, veri dönüştürme ve dökümantasyon tanımlamak (JSON Şema kullanarak) için bir kütüphanedir. - -Tip belirteçleri kullanıyor olması onu aşırı sezgisel yapıyor. - -Marshmallow ile karşılaştırılabilir. Ancak karşılaştırmalarda Marshmallowdan daha hızlı görünüyor. Aynı Python tip belirteçlerine dayanıyor ve editör desteği de harika. - -/// check | **FastAPI** nerede kullanıyor? - -Bütün veri doğrulama, veri dönüştürme ve JSON Şemasına bağlı otomatik model dökümantasyonunu halletmek için! - -**FastAPI** yaptığı her şeyin yanı sıra bu JSON Şema verisini alıp daha sonra OpenAPI'ya yerleştiriyor. - -/// - -### Starlette - -Starlette hafif bir ASGI framework'ü ve yüksek performanslı asyncio servisleri oluşturmak için ideal. - -Kullanımı çok kolay ve sezgisel, kolaylıkla genişletilebilecek ve modüler bileşenlere sahip olacak şekilde tasarlandı. - -Sahip olduğu bir kaç özellik: - -* Cidden etkileyici bir performans. -* WebSocket desteği. -* İşlem-içi arka plan görevleri. -* Başlatma ve kapatma olayları. -* HTTPX ile geliştirilmiş bir test istemcisi. -* CORS, GZip, Static Files ve Streaming cevapları desteği. -* Session ve çerez desteği. -* Kodun %100'ü test kapsamında. -* Kodun %100'ü tip belirteçleriyle desteklenmiştir. -* Yalnızca bir kaç zorunlu bağımlılığa sahip. - -Starlette şu anda test edilen en hızlı Python framework'ü. Yalnızca bir sunucu olan Uvicorn'a yeniliyor, o da zaten bir framework değil. - -Starlette bütün temel web mikro framework işlevselliğini sağlıyor. - -Ancak otomatik veri doğrulama, veri dönüştürme ve dökümantasyon sağlamyor. - -Bu, **FastAPI**'ın onun üzerine tamamen Python tip belirteçlerine bağlı olarak eklediği (Pydantic ile) ana şeylerden biri. **FastAPI** bunun yanında artı olarak bağımlılık enjeksiyonu sistemi, güvenlik araçları, OpenAPI şema üretimi ve benzeri özellikler de ekliyor. +`asyncio` tabanlı, son derece hızlı ilk Python framework’lerinden biriydi. Flask’a oldukça benzer olacak şekilde geliştirilmişti. /// note | Teknik Detaylar -ASGI, Django'nun ana ekibi tarafından geliştirilen yeni bir "standart". Bir "Python standardı" (PEP) olma sürecinde fakat henüz bir standart değil. +Varsayılan Python `asyncio` döngüsü yerine `uvloop` kullanır; hızını esasen bu sağlar. -Bununla birlikte, halihazırda birçok araç tarafından bir "standart" olarak kullanılmakta. Bu, Uvicorn'u farklı ASGI sunucularıyla (Daphne veya Hypercorn gibi) değiştirebileceğiniz veya `python-socketio` gibi ASGI ile uyumlu araçları ekleyebileciğiniz için birlikte çalışılabilirliği büyük ölçüde arttırıyor. +Açık kıyaslamalarda, bugün Uvicorn ve Starlette’in Sanic’ten daha hızlı olduğu görülür; Sanic bu ikisine ilham vermiştir. /// -/// check | **FastAPI** nerede kullanıyor? +/// check | **FastAPI**'a ilham olan -Tüm temel web kısımlarında üzerine özellikler eklenerek kullanılmakta. +Çok yüksek performans elde etmenin bir yolunu bulmak. -`FastAPI` sınıfının kendisi direkt olarak `Starlette` sınıfını miras alıyor! - -Yani, Starlette ile yapabileceğiniz her şeyi, Starlette'in bir nevi güçlendirilmiş hali olan **FastAPI** ile doğrudan yapabilirsiniz. +Bu yüzden **FastAPI**, en hızlı framework olduğu için (üçüncü parti kıyaslamalara göre) Starlette üzerine kuruludur. /// -### Uvicorn +### Falcon { #falcon } -Uvicorn, uvlook ile httptools üzerine kurulu ışık hzında bir ASGI sunucusudur. +Falcon, başka bir yüksek performanslı Python framework’üdür; minimal olacak şekilde tasarlanmış ve Hug gibi diğer framework’lere temel olmuştur. -Bir web framework'ünden ziyade bir sunucudur, yani yollara bağlı yönlendirme yapmanızı sağlayan araçları yoktur. Bu daha çok Starlette (ya da **FastAPI**) gibi bir framework'ün sunucuya ek olarak sağladığı bir şeydir. +İki parametre alan fonksiyonlar etrafında tasarlanmıştır: “request” ve “response”. İstekten parçalar “okur”, cevaba parçalar “yazarsınız”. Bu tasarım nedeniyle, fonksiyon parametreleriyle standart Python tip belirteçlerini kullanarak istek parametrelerini ve gövdelerini ilan etmek mümkün değildir. -Starlette ve **FastAPI** için tavsiye edilen sunucu Uvicorndur. +Dolayısıyla veri doğrulama, dönüşüm ve dökümantasyon kodda yapılmalı; otomatik olmaz. Ya da Hug’da olduğu gibi Falcon’un üzerine bir framework olarak uygulanmalıdır. Falcon’un tasarımından etkilenen ve tek bir request objesi ile response objesini parametre olarak alan diğer framework’lerde de aynı ayrım vardır. -/// check | **FastAPI** neden tavsiye ediyor? +/// check | **FastAPI**'a ilham olan -**FastAPI** uygulamalarını çalıştırmak için ana web sunucusu Uvicorn! +Harika performans elde etmenin yollarını bulmak. -Gunicorn ile birleştirdiğinizde asenkron ve çoklu işlem destekleyen bir sunucu elde ediyorsunuz! - -Daha fazla detay için [Deployment](deployment/index.md){.internal-link target=_blank} bölümünü inceleyebilirsiniz. +Hug ile birlikte (Hug, Falcon’a dayanır) **FastAPI**’da fonksiyonlarda opsiyonel bir `response` parametresi ilan edilmesi fikrine ilham vermek. FastAPI’da bu parametre çoğunlukla header, cookie ve alternatif durum kodlarını ayarlamak için kullanılır. /// -## Karşılaştırma ve Hız +### Molten { #molten } -Uvicorn, Starlette ve FastAPI arasındakı farkı daha iyi anlamak ve karşılaştırma yapmak için [Kıyaslamalar](benchmarks.md){.internal-link target=_blank} bölümüne bakın! +**FastAPI**’ı geliştirmenin ilk aşamalarında Molten’ı keşfettim. Oldukça benzer fikirleri vardı: + +* Python tip belirteçlerine dayanır. +* Bu tiplere bağlı doğrulama ve dökümantasyon sağlar. +* Bağımlılık enjeksiyonu sistemi vardır. + +Pydantic gibi doğrulama, dönüşüm ve dökümantasyon için üçüncü parti bir kütüphane kullanmaz; kendi içinde sağlar. Bu yüzden bu veri tipi tanımlarını tekrar kullanmak o kadar kolay olmaz. + +Biraz daha ayrıntılı yapılandırma ister. Ve ASGI yerine WSGI tabanlı olduğundan, Uvicorn, Starlette ve Sanic gibi araçların yüksek performansından faydalanmaya yönelik tasarlanmamıştır. + +Bağımlılık enjeksiyonu sistemi, bağımlılıkların önceden kaydedilmesini ve tiplerine göre çözülmesini gerektirir. Yani belirli bir tipi sağlayan birden fazla “bileşen” tanımlanamaz. + +Route’lar, endpoint’i işleyen fonksiyonun üstüne konan dekoratörlerle değil, tek bir yerde, farklı yerlerde tanımlanmış fonksiyonlar kullanılarak ilan edilir. Bu yaklaşım, Flask (ve Starlette) yerine Django’ya daha yakındır; kodda aslında birbirine sıkı bağlı olan şeyleri ayırır. + +/// check | **FastAPI**'a ilham olan + +Model özelliklerinin “varsayılan” değerlerini kullanarak veri tiplerine ekstra doğrulamalar tanımlamak. Bu, editör desteğini iyileştirir ve Pydantic’te daha önce yoktu. + +Bu yaklaşım, Pydantic’te de aynı doğrulama beyan stilinin desteklenmesine ilham verdi (bu işlevselliklerin tamamı artık Pydantic’te mevcut). + +/// + +### Hug { #hug } + +Hug, Python tip belirteçlerini kullanarak API parametre tiplerini ilan etmeyi uygulayan ilk framework’lerden biriydi. Diğer araçlara da ilham veren harika bir fikirdi. + +Standart Python tipleri yerine kendi özel tiplerini kullansa da büyük bir adımdı. + +JSON ile tüm API’ı beyan eden özel bir şema üreten ilk framework’lerden biriydi. + +OpenAPI veya JSON Schema gibi bir standarda dayanmadığı için Swagger UI gibi diğer araçlarla doğrudan entegre edilemezdi. Yine de oldukça yenilikçiydi. + +Nadir bir özelliği daha vardı: aynı framework ile hem API’lar hem de CLI’lar oluşturmak mümkündü. + +Senkron Python web framework’leri için önceki standart olan WSGI’ye dayandığından, WebSocket vb. şeyleri işleyemez, ancak yine de yüksek performansa sahiptir. + +/// info | Bilgi + +Hug, Python dosyalarındaki import’ları otomatik sıralayan harika bir araç olan `isort`’un geliştiricisi Timothy Crosley tarafından geliştirildi. + +/// + +/// check | **FastAPI**'a ilham olan fikirler + +Hug, APIStar’ın bazı kısımlarına ilham verdi ve APIStar ile birlikte en umut verici bulduğum araçlardandı. + +**FastAPI**, parametreleri ilan etmek ve API’ı otomatik tanımlayan bir şema üretmek için Python tip belirteçlerini kullanma fikrini Hug’dan ilhamla benimsedi. + +Ayrıca header ve cookie ayarlamak için fonksiyonlarda `response` parametresi ilan etme fikrine de Hug ilham verdi. + +/// + +### APIStar (<= 0.5) { #apistar-0-5 } + +**FastAPI**’yi inşa etmeye karar vermeden hemen önce **APIStar** sunucusunu buldum. Aradığım şeylerin neredeyse hepsine sahipti ve harika bir tasarımı vardı. + +Python tip belirteçleriyle parametreleri ve istekleri ilan eden bir framework’ün gördüğüm ilk örneklerindendi (NestJS ve Molten’dan önce). Aşağı yukarı Hug ile aynı zamanlarda buldum; ancak APIStar, OpenAPI standardını kullanıyordu. + +Farklı yerlerdeki aynı tip belirteçlerine dayanarak otomatik veri doğrulama, veri dönüşümü ve OpenAPI şeması üretimi vardı. + +Gövde şema tanımları Pydantic’tekiyle aynı Python tip belirteçlerini kullanmıyordu; biraz daha Marshmallow’a benziyordu. Bu yüzden editör desteği o kadar iyi olmazdı; yine de APIStar mevcut en iyi seçenekti. + +O dönem kıyaslamalarda en iyi performansa sahipti (sadece Starlette tarafından geçiliyordu). + +Başta otomatik API dökümantasyonu sunan bir web arayüzü yoktu ama Swagger UI ekleyebileceğimi biliyordum. + +Bağımlılık enjeksiyonu sistemi vardı. Diğer araçlarda olduğu gibi bileşenlerin önceden kaydedilmesini gerektiriyordu. Yine de harika bir özellikti. + +Güvenlik entegrasyonu olmadığından tam bir projede kullanamadım; bu yüzden Flask-apispec tabanlı full‑stack üreticilerle sahip olduğum özelliklerin tamamını ikame edemedim. Bu işlevi ekleyen bir pull request’i yapılacaklar listeme almıştım. + +Sonra projenin odağı değişti. + +Artık bir API web framework’ü değildi; geliştirici Starlette’e odaklanmak zorundaydı. + +Şimdi APIStar, bir web framework’ü değil, OpenAPI spesifikasyonlarını doğrulamak için araçlar takımından ibaret. + +/// info | Bilgi + +APIStar, aşağıdakilerin de yaratıcısı olan Tom Christie tarafından geliştirildi: + +* Django REST Framework +* **FastAPI**’ın üzerine kurulu Starlette +* Starlette ve **FastAPI** tarafından kullanılan Uvicorn + +/// + +/// check | **FastAPI**'a ilham olan + +Var olmak. + +Aynı Python tipleriyle (hem veri doğrulama, dönüşüm ve dökümantasyon) birden çok şeyi ilan etmek ve aynı anda harika editör desteği sağlamak, bence dahiyane bir fikirdi. + +Uzun süre benzer bir framework arayıp birçok alternatifi denedikten sonra, APIStar mevcut en iyi seçenekti. + +Sonra APIStar bir sunucu olarak var olmaktan çıktı ve Starlette oluşturuldu; böyle bir sistem için daha iyi bir temel oldu. Bu, **FastAPI**’yi inşa etmek için son ilhamdı. + +Önceki bu araçlardan edinilen deneyimler üzerine özellikleri, tip sistemi ve diğer kısımları geliştirip artırırken, **FastAPI**’yi APIStar’ın “ruhani varisi” olarak görüyorum. + +/// + +## **FastAPI** Tarafından Kullanılanlar { #used-by-fastapi } + +### Pydantic { #pydantic } + +Pydantic, Python tip belirteçlerine dayalı olarak veri doğrulama, dönüşüm ve dökümantasyon (JSON Schema kullanarak) tanımlamak için bir kütüphanedir. + +Bu onu aşırı sezgisel kılar. + +Marshmallow ile karşılaştırılabilir. Kıyaslamalarda Marshmallow’dan daha hızlıdır. Aynı Python tip belirteçlerine dayandığı için editör desteği harikadır. + +/// check | **FastAPI** bunu şurada kullanır + +Tüm veri doğrulama, veri dönüşümü ve JSON Schema tabanlı otomatik model dökümantasyonunu halletmekte. + +**FastAPI** daha sonra bu JSON Schema verisini alır ve (yaptığı diğer şeylerin yanı sıra) OpenAPI içine yerleştirir. + +/// + +### Starlette { #starlette } + +Starlette, yüksek performanslı asyncio servisleri oluşturmak için ideal, hafif bir ASGI framework’ü/araç takımıdır. + +Çok basit ve sezgiseldir. Kolayca genişletilebilir ve modüler bileşenlere sahip olacak şekilde tasarlanmıştır. + +Şunlara sahiptir: + +* Cidden etkileyici performans. +* WebSocket desteği. +* Süreç içi arka plan görevleri. +* Başlatma ve kapatma olayları. +* HTTPX üzerinde geliştirilmiş test istemcisi. +* CORS, GZip, Statik Dosyalar, Streaming cevaplar. +* Oturum (Session) ve Cookie desteği. +* %100 test kapsamı. +* %100 tip anotasyonlu kod tabanı. +* Az sayıda zorunlu bağımlılık. + +Starlette, şu anda test edilen en hızlı Python framework’üdür. Yalnızca bir framework değil, bir sunucu olan Uvicorn tarafından geçilir. + +Starlette, temel web mikroframework işlevselliğinin tamamını sağlar. + +Ancak otomatik veri doğrulama, dönüşüm veya dökümantasyon sağlamaz. + +**FastAPI**’nin bunun üzerine eklediği ana şeylerden biri, Pydantic kullanarak, bütünüyle Python tip belirteçlerine dayalı bu özelliklerdir. Buna ek olarak bağımlılık enjeksiyonu sistemi, güvenlik yardımcıları, OpenAPI şema üretimi vb. gelir. + +/// note | Teknik Detaylar + +ASGI, Django çekirdek ekip üyeleri tarafından geliştirilen yeni bir “standart”tır. Hâlâ resmi bir “Python standardı” (PEP) değildir, ancak bu süreç üzerindedirler. + +Buna rağmen, şimdiden birçok araç tarafından bir “standart” olarak kullanılmaktadır. Bu, birlikte çalışabilirliği büyük ölçüde artırır; örneğin Uvicorn’u başka bir ASGI sunucusuyla (Daphne veya Hypercorn gibi) değiştirebilir ya da `python-socketio` gibi ASGI uyumlu araçlar ekleyebilirsiniz. + +/// + +/// check | **FastAPI** bunu şurada kullanır + +Tüm temel web kısımlarını ele almak; üzerine özellikler eklemek. + +`FastAPI` sınıfı, doğrudan `Starlette` sınıfından miras alır. + +Dolayısıyla Starlette ile yapabildiğiniz her şeyi, adeta “turbo şarjlı Starlette” olan **FastAPI** ile de doğrudan yapabilirsiniz. + +/// + +### Uvicorn { #uvicorn } + +Uvicorn, uvloop ve httptools üzerinde inşa edilmiş, ışık hızında bir ASGI sunucusudur. + +Bir web framework’ü değil, bir sunucudur. Örneğin path’lere göre yönlendirme araçları sağlamaz; bunu Starlette (veya **FastAPI**) gibi bir framework üstte sağlar. + +Starlette ve **FastAPI** için önerilen sunucudur. + +/// check | **FastAPI** bunu şöyle önerir + +**FastAPI** uygulamalarını çalıştırmak için ana web sunucusu. + +Komut satırında `--workers` seçeneğini kullanarak asenkron çok süreçli (multi‑process) bir sunucu da elde edebilirsiniz. + +Daha fazla detay için [Dağıtım](deployment/index.md){.internal-link target=_blank} bölümüne bakın. + +/// + +## Kıyaslamalar ve Hız { #benchmarks-and-speed } + +Uvicorn, Starlette ve FastAPI arasındaki farkı anlamak ve karşılaştırmak için [Kıyaslamalar](benchmarks.md){.internal-link target=_blank} bölümüne göz atın. diff --git a/docs/tr/docs/deployment/docker.md b/docs/tr/docs/deployment/docker.md index b48d2ddf5..6c8f74c77 100644 --- a/docs/tr/docs/deployment/docker.md +++ b/docs/tr/docs/deployment/docker.md @@ -145,8 +145,6 @@ Paket bağımlılıklarını tanımlamak ve yüklemek için başka formatlar ve * Aşağıdakilerle bir `main.py` dosyası oluşturun: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -158,7 +156,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` @@ -260,7 +258,7 @@ FastAPI'nin düzgün şekilde kapanabilmesi ve [lifespan event](../advanced/even Detaylar için shell ve exec form için Docker dokümanlarına bakabilirsiniz. -Bu durum `docker compose` kullanırken oldukça belirgin olabilir. Daha teknik detaylar için şu Docker Compose FAQ bölümüne bakın: Why do my services take 10 seconds to recreate or stop?. +Bu durum `docker compose` kullanırken oldukça belirgin olabilir. Daha teknik detaylar için şu Docker Compose FAQ bölümüne bakın: Hizmetlerimin yeniden oluşturulması veya durması neden 10 saniye sürüyor?. #### Dizin Yapısı { #directory-structure } @@ -456,7 +454,7 @@ Container kullanmadan, uygulamaları startup'ta çalıştırmak ve restart mekan ## Replication - Process Sayısı { #replication-number-of-processes } -Kubernetes, Docker Swarm Mode, Nomad veya benzeri, birden fazla makinede dağıtık container'ları yöneten karmaşık bir sistemle kurulmuş bir cluster'ınız varsa, replication'ı her container içinde bir **process manager** (ör. worker'lı Uvicorn) kullanarak yönetmek yerine, muhtemelen **cluster seviyesinde** ele almak istersiniz. +Kubernetes, Docker Swarm Mode, Nomad veya benzeri, birden fazla makinede dağıtık container'ları yöneten karmaşık bir sistemle kurulmuş bir cluster'ınız varsa, replication'ı her container içinde bir **process manager** (ör. worker'lı Uvicorn) kullanarak yönetmek yerine, muhtemelen **cluster seviyesinde** ele almak istersiniz. Kubernetes gibi dağıtık container yönetim sistemleri, gelen request'ler için **load balancing** desteği sunarken aynı zamanda **container replication**'ını yönetmek için entegre mekanizmalara sahiptir. Hepsi **cluster seviyesinde**. diff --git a/docs/tr/docs/history-design-future.md b/docs/tr/docs/history-design-future.md index cad290828..764a51957 100644 --- a/docs/tr/docs/history-design-future.md +++ b/docs/tr/docs/history-design-future.md @@ -1,4 +1,4 @@ -# Geçmişi, Tasarımı ve Geleceği +# Geçmişi, Tasarımı ve Geleceği { #history-design-and-future } Bir süre önce, bir **FastAPI** kullanıcısı sordu: @@ -6,7 +6,7 @@ Bir süre önce, **Pydantic**'i kullanmaya karar verdim. @@ -60,11 +60,11 @@ Sonra, JSON Schema ile tamamen uyumlu olmasını sağlamak, kısıtlama bildirim Geliştirme sırasında, diğer ana gereksinim olan **Starlette**'e de katkıda bulundum. -## Geliştirme +## Geliştirme { #development } **FastAPI**'ı oluşturmaya başladığımda, parçaların çoğu zaten yerindeydi, tasarım tanımlanmıştı, gereksinimler ve araçlar hazırdı, standartlar ve tanımlamalar hakkındaki bilgi net ve tazeydi. -## Gelecek +## Gelecek { #future } Şimdiye kadar, **FastAPI**'ın fikirleriyle birçok kişiye faydalı olduğu apaçık ortada. diff --git a/docs/tr/docs/translation-banner.md b/docs/tr/docs/translation-banner.md new file mode 100644 index 000000000..b52578f71 --- /dev/null +++ b/docs/tr/docs/translation-banner.md @@ -0,0 +1,11 @@ +/// details | 🌐 Yapay Zekâ ve İnsanlar Tarafından Çeviri + +Bu çeviri, insanlar tarafından yönlendirilen bir yapay zekâ ile oluşturuldu. 🤝 + +Orijinal anlamın yanlış anlaşılması ya da kulağa doğal gelmeme gibi hatalar içerebilir. 🤖 + +[Yapay zekâyı daha iyi yönlendirmemize yardımcı olarak](https://fastapi.tiangolo.com/tr/contributing/#translations) bu çeviriyi iyileştirebilirsiniz. + +[İngilizce sürüm](ENGLISH_VERSION_URL) + +/// diff --git a/docs/tr/docs/tutorial/body-multiple-params.md b/docs/tr/docs/tutorial/body-multiple-params.md index 3bac80d6a..29970ca40 100644 --- a/docs/tr/docs/tutorial/body-multiple-params.md +++ b/docs/tr/docs/tutorial/body-multiple-params.md @@ -103,13 +103,13 @@ Elbette ihtiyaç duyduğunuzda, body parametrelerine ek olarak query parametrele Varsayılan olarak tekil değerler query parametresi olarak yorumlandığı için, ayrıca `Query` eklemeniz gerekmez; şöyle yazmanız yeterlidir: ```Python -q: Union[str, None] = None +q: str | None = None ``` -Ya da Python 3.10 ve üzeri için: +Ya da Python 3.9'da: ```Python -q: str | None = None +q: Union[str, None] = None ``` Örneğin: diff --git a/docs/tr/docs/tutorial/path-operation-configuration.md b/docs/tr/docs/tutorial/path-operation-configuration.md index 610b863a4..3fe24dc0a 100644 --- a/docs/tr/docs/tutorial/path-operation-configuration.md +++ b/docs/tr/docs/tutorial/path-operation-configuration.md @@ -52,11 +52,11 @@ Bu durumlarda tag’leri bir `Enum` içinde tutmak mantıklı olabilir. Bir `summary` ve `description` ekleyebilirsiniz: -{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *} +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} ## Docstring’den Description { #description-from-docstring } -Açıklamalar genelde uzun olur ve birden fazla satıra yayılır; bu yüzden *path operation* açıklamasını, fonksiyonun içinde docstring olarak tanımlayabilirsiniz; **FastAPI** de onu buradan okur. +Açıklamalar genelde uzun olur ve birden fazla satıra yayılır; bu yüzden *path operation* açıklamasını, fonksiyonun içinde docstring olarak tanımlayabilirsiniz; **FastAPI** de onu buradan okur. Docstring içinde Markdown yazabilirsiniz; doğru şekilde yorumlanır ve gösterilir (docstring girintisi dikkate alınarak). @@ -70,7 +70,7 @@ Interactive docs’ta şöyle kullanılacaktır: `response_description` parametresi ile response açıklamasını belirtebilirsiniz: -{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[19] *} +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} /// info | Bilgi @@ -90,7 +90,7 @@ Bu yüzden siz sağlamazsanız, **FastAPI** otomatik olarak "Successful response ## Bir *path operation*’ı Deprecate Etmek { #deprecate-a-path-operation } -Bir *path operation*’ı kaldırmadan, deprecated olarak işaretlemeniz gerekiyorsa `deprecated` parametresini verin: +Bir *path operation*’ı kaldırmadan, deprecated olarak işaretlemeniz gerekiyorsa `deprecated` parametresini verin: {* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} From 2eb454ab04e81325ed430a036103e4aede299d61 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Feb 2026 17:24:15 +0000 Subject: [PATCH 052/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 689610049..4ba0491a5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Translations + +* 🌐 Update translations for tr (outdated and missing). PR [#14838](https://github.com/fastapi/fastapi/pull/14838) by [@YuriiMotov](https://github.com/YuriiMotov). + ### Internal * ⬆️ Upgrade development dependencies. PR [#14854](https://github.com/fastapi/fastapi/pull/14854) by [@tiangolo](https://github.com/tiangolo). From cba537ab717ff205857a0eb877c69e79cfd06a51 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 6 Feb 2026 20:24:25 +0300 Subject: [PATCH 053/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20fr=20(outdated=20pages)=20(#14839)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/fr/docs/tutorial/body-multiple-params.md | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/fr/docs/tutorial/body-multiple-params.md b/docs/fr/docs/tutorial/body-multiple-params.md index 9aed24e98..92ca2afc3 100644 --- a/docs/fr/docs/tutorial/body-multiple-params.md +++ b/docs/fr/docs/tutorial/body-multiple-params.md @@ -2,23 +2,23 @@ Maintenant que nous avons vu comment utiliser `Path` et `Query`, voyons des usages plus avancés des déclarations de paramètres du corps de la requête. -## Mélanger les paramètres `Path`, `Query` et body { #mix-path-query-and-body-parameters } +## Mélanger les paramètres `Path`, `Query` et du corps de la requête { #mix-path-query-and-body-parameters } -Tout d'abord, sachez que vous pouvez mélanger librement les déclarations des paramètres `Path`, `Query` et du body, **FastAPI** saura quoi faire. +Tout d'abord, sachez que vous pouvez mélanger librement les déclarations des paramètres `Path`, `Query` et du corps de la requête, **FastAPI** saura quoi faire. -Et vous pouvez également déclarer des paramètres du body comme étant optionnels, en leur assignant une valeur par défaut à `None` : +Et vous pouvez également déclarer des paramètres du corps de la requête comme étant optionnels, en leur assignant une valeur par défaut à `None` : {* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} /// note | Remarque -Notez que, dans ce cas, l'élément `item` récupéré depuis le body est optionnel. Comme sa valeur par défaut est `None`. +Notez que, dans ce cas, l'élément `item` récupéré depuis le corps de la requête est optionnel. Comme sa valeur par défaut est `None`. /// -## Paramètres multiples du body { #multiple-body-parameters } +## Paramètres multiples du corps de la requête { #multiple-body-parameters } -Dans l'exemple précédent, les chemins d'accès attendraient un body JSON avec les attributs d'un `Item`, par exemple : +Dans l'exemple précédent, les chemins d'accès attendraient un corps de la requête JSON avec les attributs d'un `Item`, par exemple : ```JSON { @@ -29,13 +29,13 @@ Dans l'exemple précédent, les chemins d'accès attendraient un body JSON avec } ``` -Mais vous pouvez également déclarer plusieurs paramètres provenant du body, par exemple `item` et `user` : +Mais vous pouvez également déclarer plusieurs paramètres provenant du corps de la requête, par exemple `item` et `user` : {* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *} -Dans ce cas, **FastAPI** détectera qu'il y a plus d'un paramètre du body dans la fonction (il y a deux paramètres qui sont des modèles Pydantic). +Dans ce cas, **FastAPI** détectera qu'il y a plus d'un paramètre du corps de la requête dans la fonction (il y a deux paramètres qui sont des modèles Pydantic). -Il utilisera alors les noms des paramètres comme clés (noms de champs) dans le body, et s'attendra à recevoir un body semblable à : +Il utilisera alors les noms des paramètres comme clés (noms de champs) dans le corps de la requête, et s'attendra à recevoir un corps de la requête semblable à : ```JSON { @@ -54,7 +54,7 @@ Il utilisera alors les noms des paramètres comme clés (noms de champs) dans le /// note | Remarque -Notez que, bien que `item` ait été déclaré de la même manière qu'auparavant, il est désormais attendu à l'intérieur du body sous la clé `item`. +Notez que, bien que `item` ait été déclaré de la même manière qu'auparavant, il est désormais attendu à l'intérieur du corps de la requête sous la clé `item`. /// @@ -62,19 +62,19 @@ Notez que, bien que `item` ait été déclaré de la même manière qu'auparavan Il effectuera la validation des données composées, et les documentera ainsi pour le schéma OpenAPI et la documentation automatique. -## Valeurs singulières dans le body { #singular-values-in-body } +## Valeurs singulières dans le corps de la requête { #singular-values-in-body } De la même façon qu'il existe `Query` et `Path` pour définir des données supplémentaires pour les paramètres de requête et de chemin, **FastAPI** fournit un équivalent `Body`. -Par exemple, en étendant le modèle précédent, vous pourriez décider d'avoir une autre clé `importance` dans le même body, en plus de `item` et `user`. +Par exemple, en étendant le modèle précédent, vous pourriez décider d'avoir une autre clé `importance` dans le même corps de la requête, en plus de `item` et `user`. Si vous le déclarez tel quel, comme c'est une valeur singulière, **FastAPI** supposera qu'il s'agit d'un paramètre de requête. -Mais vous pouvez indiquer à **FastAPI** de la traiter comme une autre clé du body en utilisant `Body` : +Mais vous pouvez indiquer à **FastAPI** de la traiter comme une autre clé du corps de la requête en utilisant `Body` : {* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *} -Dans ce cas, **FastAPI** s'attendra à un body semblable à : +Dans ce cas, **FastAPI** s'attendra à un corps de la requête semblable à : ```JSON { @@ -94,9 +94,9 @@ Dans ce cas, **FastAPI** s'attendra à un body semblable à : Encore une fois, il convertira les types de données, validera, documentera, etc. -## Paramètres multiples du body et paramètres de requête { #multiple-body-params-and-query } +## Paramètres multiples du corps de la requête et paramètres de requête { #multiple-body-params-and-query } -Bien entendu, vous pouvez également déclarer des paramètres de requête supplémentaires quand vous en avez besoin, en plus de tout paramètre du body. +Bien entendu, vous pouvez également déclarer des paramètres de requête supplémentaires quand vous en avez besoin, en plus de tout paramètre du corps de la requête. Comme, par défaut, les valeurs singulières sont interprétées comme des paramètres de requête, vous n'avez pas besoin d'ajouter explicitement `Query`, vous pouvez simplement écrire : @@ -120,13 +120,13 @@ Par exemple : /// -## Intégrer un seul paramètre du body { #embed-a-single-body-parameter } +## Intégrer un seul paramètre du corps de la requête { #embed-a-single-body-parameter } -Supposons que vous n'ayez qu'un seul paramètre `item` dans le body, provenant d'un modèle Pydantic `Item`. +Supposons que vous n'ayez qu'un seul paramètre `item` dans le corps de la requête, provenant d'un modèle Pydantic `Item`. Par défaut, **FastAPI** attendra alors son contenu directement. -Mais si vous voulez qu'il attende un JSON avec une clé `item` contenant le contenu du modèle, comme lorsqu'on déclare des paramètres supplémentaires du body, vous pouvez utiliser le paramètre spécial `embed` de `Body` : +Mais si vous voulez qu'il attende un JSON avec une clé `item` contenant le contenu du modèle, comme lorsqu'on déclare des paramètres supplémentaires du corps de la requête, vous pouvez utiliser le paramètre spécial `embed` de `Body` : ```Python item: Item = Body(embed=True) @@ -136,7 +136,7 @@ comme dans : {* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *} -Dans ce cas **FastAPI** s'attendra à un body semblable à : +Dans ce cas **FastAPI** s'attendra à un corps de la requête semblable à : ```JSON hl_lines="2" { @@ -162,10 +162,10 @@ au lieu de : ## Récapitulatif { #recap } -Vous pouvez ajouter plusieurs paramètres du body à votre fonction de chemin d'accès, même si une requête ne peut avoir qu'un seul body. +Vous pouvez ajouter plusieurs paramètres du corps de la requête à votre fonction de chemin d'accès, même si une requête ne peut avoir qu'un seul corps de la requête. Mais **FastAPI** s'en chargera, vous fournira les bonnes données dans votre fonction, et validera et documentera le schéma correct dans le chemin d'accès. -Vous pouvez également déclarer des valeurs singulières à recevoir dans le body. +Vous pouvez également déclarer des valeurs singulières à recevoir dans le corps de la requête. -Et vous pouvez indiquer à **FastAPI** d'intégrer le body sous une clé même lorsqu'un seul paramètre est déclaré. +Et vous pouvez indiquer à **FastAPI** d'intégrer le corps de la requête sous une clé même lorsqu'un seul paramètre est déclaré. From 512c3ad88c8f03a03154df317c65aa0ca4f1f78d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Feb 2026 17:24:56 +0000 Subject: [PATCH 054/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4ba0491a5..75e0a5f56 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🌐 Update translations for fr (outdated pages). PR [#14839](https://github.com/fastapi/fastapi/pull/14839) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for tr (outdated and missing). PR [#14838](https://github.com/fastapi/fastapi/pull/14838) by [@YuriiMotov](https://github.com/YuriiMotov). ### Internal From 3c49346238643287780d9f0d673f88b0d02df109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 6 Feb 2026 10:01:05 -0800 Subject: [PATCH 055/195] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20interna?= =?UTF-8?q?ls,=20cleanup=20unneeded=20Pydantic=20v1=20specific=20logic=20(?= =?UTF-8?q?#14856)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/__init__.py | 1 - fastapi/_compat/shared.py | 1 - fastapi/_compat/v2.py | 10 ++-------- fastapi/routing.py | 15 +-------------- fastapi/utils.py | 16 ---------------- 5 files changed, 3 insertions(+), 40 deletions(-) diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index 3dfaf9b71..b7701bd1a 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -1,4 +1,3 @@ -from .shared import PYDANTIC_V2 as PYDANTIC_V2 from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1 from .shared import field_annotation_is_scalar as field_annotation_is_scalar diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py index 68b9bbdf1..fdda481b8 100644 --- a/fastapi/_compat/shared.py +++ b/fastapi/_compat/shared.py @@ -28,7 +28,6 @@ else: ) # pyright: ignore[reportAttributeAccessIssue] PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2]) -PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2 sequence_annotation_to_type = { diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index dae78a32e..6c2eec58b 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -500,14 +500,8 @@ def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap: - 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) - - model_name_map = get_model_name_map(all_flat_models) - return model_name_map + flat_models = get_flat_models_from_fields(fields, known_models=set()) + return get_model_name_map(flat_models) def get_flat_models_from_model( diff --git a/fastapi/routing.py b/fastapi/routing.py index c95f624bd..b8d1f7624 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -59,7 +59,6 @@ from fastapi.exceptions import ( ) from fastapi.types import DecoratedCallable, IncEx from fastapi.utils import ( - create_cloned_field, create_model_field, generate_unique_id, get_value_or_default, @@ -652,20 +651,8 @@ class APIRoute(routing.Route): type_=self.response_model, mode="serialization", ) - # Create a clone of the field, so that a Pydantic submodel is not returned - # as is just because it's an instance of a subclass of a more limited class - # e.g. UserInDB (containing hashed_password) could be a subclass of User - # that doesn't have the hashed_password. But because it's a subclass, it - # would pass the validation and be returned as is. - # By being a new field, no inheritance will be passed as is. A new model - # will always be created. - # TODO: remove when deprecating Pydantic v1 - self.secure_cloned_response_field: Optional[ModelField] = ( - create_cloned_field(self.response_field) - ) else: self.response_field = None # type: ignore - self.secure_cloned_response_field = None self.dependencies = list(dependencies or []) self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "") # if a "form feed" character (page break) is found in the description text, @@ -720,7 +707,7 @@ class APIRoute(routing.Route): body_field=self.body_field, status_code=self.status_code, response_class=self.response_class, - response_field=self.secure_cloned_response_field, + response_field=self.response_field, response_model_include=self.response_model_include, response_model_exclude=self.response_model_exclude, response_model_by_alias=self.response_model_by_alias, diff --git a/fastapi/utils.py b/fastapi/utils.py index 1c3a0881f..da11fe2c7 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -1,13 +1,11 @@ import re import warnings -from collections.abc import MutableMapping from typing import ( TYPE_CHECKING, Any, Optional, Union, ) -from weakref import WeakKeyDictionary import fastapi from fastapi._compat import ( @@ -21,7 +19,6 @@ from fastapi._compat import ( ) from fastapi.datastructures import DefaultPlaceholder, DefaultType from fastapi.exceptions import FastAPIDeprecationWarning, PydanticV1NotSupportedError -from pydantic import BaseModel from pydantic.fields import FieldInfo from typing_extensions import Literal @@ -30,11 +27,6 @@ from ._compat import v2 if TYPE_CHECKING: # pragma: nocover from .routing import APIRoute -# Cache for `create_cloned_field` -_CLONED_TYPES_CACHE: MutableMapping[type[BaseModel], type[BaseModel]] = ( - WeakKeyDictionary() -) - def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool: if status_code is None: @@ -97,14 +89,6 @@ def create_model_field( ) from None -def create_cloned_field( - field: ModelField, - *, - cloned_types: Optional[MutableMapping[type[BaseModel], type[BaseModel]]] = None, -) -> ModelField: - return field - - def generate_operation_id_for_path( *, name: str, path: str, method: str ) -> str: # pragma: nocover From ac8362c447b94247e9f7268c3d1e2807818647ae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Feb 2026 18:01:30 +0000 Subject: [PATCH 056/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 75e0a5f56..d442ef9a1 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Refactors + +* ♻️ Refactor internals, cleanup unneeded Pydantic v1 specific logic. PR [#14856](https://github.com/fastapi/fastapi/pull/14856) by [@tiangolo](https://github.com/tiangolo). + ### Translations * 🌐 Update translations for fr (outdated pages). PR [#14839](https://github.com/fastapi/fastapi/pull/14839) by [@YuriiMotov](https://github.com/YuriiMotov). From cf55bade7ea6e4bf85a167e8aed1c6167e7a4196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 6 Feb 2026 11:04:24 -0800 Subject: [PATCH 057/195] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Simplify=20interna?= =?UTF-8?q?ls,=20remove=20Pydantic=20v1=20only=20logic,=20no=20longer=20ne?= =?UTF-8?q?eded=20(#14857)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/__init__.py | 1 - fastapi/_compat/v2.py | 107 ++++------------------------------ fastapi/dependencies/utils.py | 8 +-- fastapi/openapi/utils.py | 8 ++- fastapi/routing.py | 7 +-- 5 files changed, 19 insertions(+), 112 deletions(-) diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index b7701bd1a..22bc28dec 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -25,7 +25,6 @@ from .v2 import copy_field_info as copy_field_info from .v2 import create_body_model as create_body_model from .v2 import evaluate_forwardref as evaluate_forwardref from .v2 import get_cached_model_fields as get_cached_model_fields -from .v2 import get_compat_model_name_map as get_compat_model_name_map from .v2 import get_definitions as get_definitions from .v2 import get_missing_field_error as get_missing_field_error from .v2 import get_schema_from_model_field as get_schema_from_model_field diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 6c2eec58b..57b3d94ff 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -1,7 +1,7 @@ import re import warnings from collections.abc import Sequence -from copy import copy, deepcopy +from copy import copy from dataclasses import dataclass, is_dataclass from enum import Enum from functools import lru_cache @@ -169,11 +169,11 @@ class ModelField: values: dict[str, Any] = {}, # noqa: B006 *, loc: tuple[Union[int, str], ...] = (), - ) -> tuple[Any, Union[list[dict[str, Any]], None]]: + ) -> tuple[Any, list[dict[str, Any]]]: try: return ( self._type_adapter.validate_python(value, from_attributes=True), - None, + [], ) except ValidationError as exc: return None, _regenerate_error_with_loc( @@ -305,94 +305,12 @@ def get_definitions( if "description" in item_def: item_description = cast(str, item_def["description"]).split("\f")[0] item_def["description"] = item_description - new_mapping, new_definitions = _remap_definitions_and_field_mappings( - model_name_map=model_name_map, - definitions=definitions, # type: ignore[arg-type] - field_mapping=field_mapping, - ) - return new_mapping, new_definitions - - -def _replace_refs( - *, - schema: dict[str, Any], - old_name_to_new_name_map: dict[str, str], -) -> dict[str, Any]: - new_schema = deepcopy(schema) - for key, value in new_schema.items(): - if key == "$ref": - value = schema["$ref"] - if isinstance(value, str): - ref_name = schema["$ref"].split("/")[-1] - if ref_name in old_name_to_new_name_map: - new_name = old_name_to_new_name_map[ref_name] - new_schema["$ref"] = REF_TEMPLATE.format(model=new_name) - continue - if isinstance(value, dict): - new_schema[key] = _replace_refs( - schema=value, - old_name_to_new_name_map=old_name_to_new_name_map, - ) - elif isinstance(value, list): - new_value = [] - for item in value: - if isinstance(item, dict): - new_item = _replace_refs( - schema=item, - old_name_to_new_name_map=old_name_to_new_name_map, - ) - new_value.append(new_item) - - else: - new_value.append(item) - new_schema[key] = new_value - return new_schema - - -def _remap_definitions_and_field_mappings( - *, - model_name_map: ModelNameMap, - definitions: dict[str, Any], - field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue - ], -) -> tuple[ - dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], - dict[str, Any], -]: - old_name_to_new_name_map = {} - for field_key, schema in field_mapping.items(): - model = field_key[0].type_ - if model not in model_name_map or "$ref" not in schema: - continue - new_name = model_name_map[model] - old_name = schema["$ref"].split("/")[-1] - if old_name in {f"{new_name}-Input", f"{new_name}-Output"}: - continue - old_name_to_new_name_map[old_name] = new_name - - new_field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue - ] = {} - for field_key, schema in field_mapping.items(): - new_schema = _replace_refs( - schema=schema, - old_name_to_new_name_map=old_name_to_new_name_map, - ) - new_field_mapping[field_key] = new_schema - - new_definitions = {} - for key, value in definitions.items(): - if key in old_name_to_new_name_map: - new_key = old_name_to_new_name_map[key] - else: - new_key = key - new_value = _replace_refs( - schema=value, - old_name_to_new_name_map=old_name_to_new_name_map, - ) - new_definitions[new_key] = new_value - return new_field_mapping, new_definitions + # definitions: dict[DefsRef, dict[str, Any]] + # but mypy complains about general str in other places that are not declared as + # DefsRef, although DefsRef is just str: + # DefsRef = NewType('DefsRef', str) + # So, a cast to simplify the types here + return field_mapping, cast(dict[str, dict[str, Any]], definitions) def is_scalar_field(field: ModelField) -> bool: @@ -441,7 +359,7 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return,index] -def get_missing_field_error(loc: tuple[str, ...]) -> dict[str, Any]: +def get_missing_field_error(loc: tuple[Union[int, str], ...]) -> dict[str, Any]: error = ValidationError.from_exception_data( "Field required", [{"type": "missing", "loc": loc, "input": {}}] ).errors(include_url=False)[0] @@ -499,11 +417,6 @@ def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str return {v: k for k, v in name_model_map.items()} -def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap: - flat_models = get_flat_models_from_fields(fields, known_models=set()) - return get_model_name_map(flat_models) - - def get_flat_models_from_model( model: type["BaseModel"], known_models: Union[TypeModelSet, None] = None ) -> TypeModelSet: diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index b8f7f948c..dd42371ec 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -21,7 +21,6 @@ from fastapi._compat import ( ModelField, RequiredParam, Undefined, - _regenerate_error_with_loc, copy_field_info, create_body_model, evaluate_forwardref, @@ -718,12 +717,7 @@ def _validate_value_with_model_field( return None, [get_missing_field_error(loc=loc)] else: return deepcopy(field.default), [] - v_, errors_ = field.validate(value, values, loc=loc) - if isinstance(errors_, list): - new_errors = _regenerate_error_with_loc(errors=errors_, loc_prefix=()) - return None, new_errors - else: - return v_, [] + return field.validate(value, values, loc=loc) def _is_json_field(field: ModelField) -> bool: diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 5736af3b7..c9b006a71 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -9,11 +9,14 @@ from fastapi import routing from fastapi._compat import ( ModelField, Undefined, - get_compat_model_name_map, get_definitions, get_schema_from_model_field, lenient_issubclass, ) +from fastapi._compat.v2 import ( + get_flat_models_from_fields, + get_model_name_map, +) from fastapi.datastructures import DefaultPlaceholder from fastapi.dependencies.models import Dependant from fastapi.dependencies.utils import ( @@ -512,7 +515,8 @@ def get_openapi( webhook_paths: dict[str, dict[str, Any]] = {} operation_ids: set[str] = set() all_fields = get_fields_from_routes(list(routes or []) + list(webhooks or [])) - model_name_map = get_compat_model_name_map(all_fields) + flat_models = get_flat_models_from_fields(all_fields, known_models=set()) + model_name_map = get_model_name_map(flat_models) field_mapping, definitions = get_definitions( fields=all_fields, model_name_map=model_name_map, diff --git a/fastapi/routing.py b/fastapi/routing.py index b8d1f7624..fe8d88609 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -277,15 +277,12 @@ async def serialize_response( endpoint_ctx: Optional[EndpointContext] = None, ) -> Any: if field: - errors = [] if is_coroutine: - value, errors_ = field.validate(response_content, {}, loc=("response",)) + value, errors = field.validate(response_content, {}, loc=("response",)) else: - value, errors_ = await run_in_threadpool( + value, errors = await run_in_threadpool( field.validate, response_content, {}, loc=("response",) ) - if isinstance(errors_, list): - errors.extend(errors_) if errors: ctx = endpoint_ctx or EndpointContext() raise ResponseValidationError( From cc6ced6345f6be18038437b73866294697294f3e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Feb 2026 19:04:48 +0000 Subject: [PATCH 058/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d442ef9a1..b9982e8c9 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Refactors +* ♻️ Simplify internals, remove Pydantic v1 only logic, no longer needed. PR [#14857](https://github.com/fastapi/fastapi/pull/14857) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor internals, cleanup unneeded Pydantic v1 specific logic. PR [#14856](https://github.com/fastapi/fastapi/pull/14856) by [@tiangolo](https://github.com/tiangolo). ### Translations From d59fbc3494940c9afe675cc89271bd0fa744574f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 7 Feb 2026 00:08:07 -0800 Subject: [PATCH 059/195] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20interna?= =?UTF-8?q?ls,=20simplify=20Pydantic=20v2/v1=20utils,=20`create=5Fmodel=5F?= =?UTF-8?q?field`,=20better=20types=20for=20`lenient=5Fissubclass`=20(#148?= =?UTF-8?q?60)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/__init__.py | 7 ++---- fastapi/_compat/shared.py | 37 ++++++++++++++++++++------------ fastapi/_compat/v2.py | 40 +++++++++++------------------------ fastapi/dependencies/utils.py | 8 +------ fastapi/openapi/models.py | 5 +---- fastapi/openapi/utils.py | 6 ++---- fastapi/routing.py | 12 ----------- fastapi/utils.py | 12 +---------- 8 files changed, 43 insertions(+), 84 deletions(-) diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index 22bc28dec..62681696e 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -1,7 +1,6 @@ from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1 from .shared import field_annotation_is_scalar as field_annotation_is_scalar -from .shared import is_pydantic_v1_model_class as is_pydantic_v1_model_class from .shared import is_pydantic_v1_model_instance as is_pydantic_v1_model_instance from .shared import ( is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation, @@ -12,21 +11,19 @@ from .shared import ( from .shared import lenient_issubclass as lenient_issubclass from .shared import sequence_types as sequence_types from .shared import value_is_sequence as value_is_sequence -from .v2 import BaseConfig as BaseConfig from .v2 import ModelField as ModelField from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError from .v2 import RequiredParam as RequiredParam from .v2 import Undefined as Undefined -from .v2 import UndefinedType as UndefinedType from .v2 import Url as Url -from .v2 import Validator as Validator -from .v2 import _regenerate_error_with_loc as _regenerate_error_with_loc from .v2 import copy_field_info as copy_field_info from .v2 import create_body_model as create_body_model from .v2 import evaluate_forwardref as evaluate_forwardref from .v2 import get_cached_model_fields as get_cached_model_fields from .v2 import get_definitions as get_definitions +from .v2 import get_flat_models_from_fields as get_flat_models_from_fields from .v2 import get_missing_field_error as get_missing_field_error +from .v2 import get_model_name_map as get_model_name_map from .v2 import get_schema_from_model_field as get_schema_from_model_field from .v2 import is_bytes_field as is_bytes_field from .v2 import is_bytes_sequence_field as is_bytes_sequence_field diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py index fdda481b8..c009da8fd 100644 --- a/fastapi/_compat/shared.py +++ b/fastapi/_compat/shared.py @@ -8,6 +8,7 @@ from dataclasses import is_dataclass from typing import ( Annotated, Any, + TypeVar, Union, ) @@ -15,7 +16,9 @@ from fastapi.types import UnionType from pydantic import BaseModel from pydantic.version import VERSION as PYDANTIC_VERSION from starlette.datastructures import UploadFile -from typing_extensions import get_args, get_origin +from typing_extensions import TypeGuard, get_args, get_origin + +_T = TypeVar("_T") # Copy from Pydantic: pydantic/_internal/_typing_extra.py if sys.version_info < (3, 10): @@ -39,15 +42,13 @@ sequence_annotation_to_type = { deque: deque, } -sequence_types = tuple(sequence_annotation_to_type.keys()) - -Url: type[Any] +sequence_types: tuple[type[Any], ...] = tuple(sequence_annotation_to_type.keys()) -# Copy of Pydantic: pydantic/_internal/_utils.py +# Copy of Pydantic: pydantic/_internal/_utils.py with added TypeGuard def lenient_issubclass( - cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...], None] -) -> bool: + cls: Any, class_or_tuple: Union[type[_T], tuple[type[_T], ...], None] +) -> TypeGuard[type[_T]]: try: return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type] except TypeError: # pragma: no cover @@ -177,16 +178,26 @@ def is_uploadfile_sequence_annotation(annotation: Any) -> bool: def is_pydantic_v1_model_instance(obj: Any) -> bool: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", UserWarning) - from pydantic import v1 + # TODO: remove this function once the required version of Pydantic fully + # removes pydantic.v1 + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", UserWarning) + from pydantic import v1 + except ImportError: # pragma: no cover + return False return isinstance(obj, v1.BaseModel) def is_pydantic_v1_model_class(cls: Any) -> bool: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", UserWarning) - from pydantic import v1 + # TODO: remove this function once the required version of Pydantic fully + # removes pydantic.v1 + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", UserWarning) + from pydantic import v1 + except ImportError: # pragma: no cover + return False return lenient_issubclass(cls, v1.BaseModel) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 57b3d94ff..efc20fa58 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -12,7 +12,7 @@ from typing import ( cast, ) -from fastapi._compat import shared +from fastapi._compat import lenient_issubclass, shared from fastapi.openapi.constants import REF_TEMPLATE from fastapi.types import IncEx, ModelNameMap, UnionType from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model @@ -23,29 +23,20 @@ from pydantic._internal._schema_generation_shared import ( # type: ignore[attr- GetJsonSchemaHandler as GetJsonSchemaHandler, ) from pydantic._internal._typing_extra import eval_type_lenient -from pydantic._internal._utils import lenient_issubclass as lenient_issubclass from pydantic.fields import FieldInfo as FieldInfo from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue from pydantic_core import CoreSchema as CoreSchema -from pydantic_core import PydanticUndefined, PydanticUndefinedType +from pydantic_core import PydanticUndefined from pydantic_core import Url as Url +from pydantic_core.core_schema import ( + with_info_plain_validator_function as with_info_plain_validator_function, +) from typing_extensions import Literal, get_args, get_origin -try: - from pydantic_core.core_schema import ( - with_info_plain_validator_function as with_info_plain_validator_function, - ) -except ImportError: # pragma: no cover - from pydantic_core.core_schema import ( - general_plain_validator_function as with_info_plain_validator_function, # noqa: F401 - ) - RequiredParam = PydanticUndefined Undefined = PydanticUndefined -UndefinedType = PydanticUndefinedType evaluate_forwardref = eval_type_lenient -Validator = Any # TODO: remove when dropping support for Pydantic < v2.12.3 _Attrs = { @@ -87,14 +78,6 @@ def asdict(field_info: FieldInfo) -> dict[str, Any]: } -class BaseConfig: - pass - - -class ErrorWrapper(Exception): - pass - - @dataclass class ModelField: field_info: FieldInfo @@ -143,8 +126,8 @@ class ModelField: warnings.simplefilter( "ignore", category=UnsupportedFieldAttributeWarning ) - # TODO: remove after dropping support for Python 3.8 and - # setting the min Pydantic to v2.12.3 that adds asdict() + # TODO: remove after setting the min Pydantic to v2.12.3 + # that adds asdict(), and use self.field_info.asdict() instead field_dict = asdict(self.field_info) annotated_args = ( field_dict["annotation"], @@ -432,10 +415,11 @@ def get_flat_models_from_annotation( origin = get_origin(annotation) if origin is not None: for arg in get_args(annotation): - if lenient_issubclass(arg, (BaseModel, Enum)) and arg not in known_models: - known_models.add(arg) - if lenient_issubclass(arg, BaseModel): - get_flat_models_from_model(arg, known_models=known_models) + if lenient_issubclass(arg, (BaseModel, Enum)): + if arg not in known_models: + known_models.add(arg) # type: ignore[arg-type] + if lenient_issubclass(arg, BaseModel): + get_flat_models_from_model(arg, known_models=known_models) else: get_flat_models_from_annotation(arg, known_models=known_models) return known_models diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index dd42371ec..1e334c429 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -512,7 +512,6 @@ def analyze_param( type_=use_annotation_from_field_info, default=field_info.default, alias=alias, - required=field_info.default in (RequiredParam, Undefined), field_info=field_info, ) if is_path_param: @@ -523,11 +522,7 @@ def analyze_param( assert ( is_scalar_field(field) or is_scalar_sequence_field(field) - or ( - lenient_issubclass(field.type_, BaseModel) - # For Pydantic v1 - and getattr(field, "shape", 1) == 1 - ) + or lenient_issubclass(field.type_, BaseModel) ), f"Query parameter {param_name!r} must be one of the supported types" return ParamDetails(type_annotation=type_annotation, depends=depends, field=field) @@ -1021,7 +1016,6 @@ def get_body_field( final_field = create_model_field( name="body", type_=BodyModel, - required=required, alias="body", field_info=BodyFieldInfo(**BodyFieldInfo_kwargs), ) diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index ac6a6d52c..095990639 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -143,10 +143,7 @@ class Schema(BaseModelWithConfig): else_: Optional["SchemaOrBool"] = Field(default=None, alias="else") dependentSchemas: Optional[dict[str, "SchemaOrBool"]] = None prefixItems: Optional[list["SchemaOrBool"]] = None - # TODO: uncomment and remove below when deprecating Pydantic v1 - # It generates a list of schemas for tuples, before prefixItems was available - # items: Optional["SchemaOrBool"] = None - items: Optional[Union["SchemaOrBool", list["SchemaOrBool"]]] = None + items: Optional["SchemaOrBool"] = None contains: Optional["SchemaOrBool"] = None properties: Optional[dict[str, "SchemaOrBool"]] = None patternProperties: Optional[dict[str, "SchemaOrBool"]] = None diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index c9b006a71..9cff35981 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -10,12 +10,10 @@ from fastapi._compat import ( ModelField, Undefined, get_definitions, - get_schema_from_model_field, - lenient_issubclass, -) -from fastapi._compat.v2 import ( get_flat_models_from_fields, get_model_name_map, + get_schema_from_model_field, + lenient_issubclass, ) from fastapi.datastructures import DefaultPlaceholder from fastapi.dependencies.models import Dependant diff --git a/fastapi/routing.py b/fastapi/routing.py index fe8d88609..0b4d28873 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -34,7 +34,6 @@ from fastapi import params from fastapi._compat import ( ModelField, Undefined, - annotation_is_pydantic_v1, lenient_issubclass, ) from fastapi.datastructures import Default, DefaultPlaceholder @@ -52,7 +51,6 @@ from fastapi.encoders import jsonable_encoder from fastapi.exceptions import ( EndpointContext, FastAPIError, - PydanticV1NotSupportedError, RequestValidationError, ResponseValidationError, WebSocketRequestValidationError, @@ -638,11 +636,6 @@ class APIRoute(routing.Route): f"Status code {status_code} must not have a response body" ) response_name = "Response_" + self.unique_id - if annotation_is_pydantic_v1(self.response_model): - raise PydanticV1NotSupportedError( - "pydantic.v1 models are no longer supported by FastAPI." - f" Please update the response model {self.response_model!r}." - ) self.response_field = create_model_field( name=response_name, type_=self.response_model, @@ -664,11 +657,6 @@ class APIRoute(routing.Route): f"Status code {additional_status_code} must not have a response body" ) response_name = f"Response_{additional_status_code}_{self.unique_id}" - if annotation_is_pydantic_v1(model): - raise PydanticV1NotSupportedError( - "pydantic.v1 models are no longer supported by FastAPI." - f" In responses={{}}, please update {model}." - ) response_field = create_model_field( name=response_name, type_=model, mode="serialization" ) diff --git a/fastapi/utils.py b/fastapi/utils.py index da11fe2c7..28c7cdfcc 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -9,12 +9,9 @@ from typing import ( import fastapi from fastapi._compat import ( - BaseConfig, ModelField, PydanticSchemaGenerationError, Undefined, - UndefinedType, - Validator, annotation_is_pydantic_v1, ) from fastapi.datastructures import DefaultPlaceholder, DefaultType @@ -63,26 +60,19 @@ _invalid_args_message = ( def create_model_field( name: str, type_: Any, - class_validators: Optional[dict[str, Validator]] = None, default: Optional[Any] = Undefined, - required: Union[bool, UndefinedType] = Undefined, - model_config: Union[type[BaseConfig], None] = None, field_info: Optional[FieldInfo] = None, alias: Optional[str] = None, mode: Literal["validation", "serialization"] = "validation", - version: Literal["1", "auto"] = "auto", ) -> ModelField: if annotation_is_pydantic_v1(type_): raise PydanticV1NotSupportedError( "pydantic.v1 models are no longer supported by FastAPI." f" Please update the response model {type_!r}." ) - class_validators = class_validators or {} - 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[arg-type] + return v2.ModelField(mode=mode, name=name, field_info=field_info) except PydanticSchemaGenerationError: raise fastapi.exceptions.FastAPIError( _invalid_args_message.format(type_=type_) From 58cdfc7f4b1ad22a4e6450319affb42d185775d9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 7 Feb 2026 08:08:31 +0000 Subject: [PATCH 060/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b9982e8c9..1c640aba8 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Refactors +* ♻️ Refactor internals, simplify Pydantic v2/v1 utils, `create_model_field`, better types for `lenient_issubclass`. PR [#14860](https://github.com/fastapi/fastapi/pull/14860) by [@tiangolo](https://github.com/tiangolo). * ♻️ Simplify internals, remove Pydantic v1 only logic, no longer needed. PR [#14857](https://github.com/fastapi/fastapi/pull/14857) by [@tiangolo](https://github.com/tiangolo). * ♻️ Refactor internals, cleanup unneeded Pydantic v1 specific logic. PR [#14856](https://github.com/fastapi/fastapi/pull/14856) by [@tiangolo](https://github.com/tiangolo). From 8eac94bd91c212a2aab676acd7f8e94cd3097dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 7 Feb 2026 09:12:54 +0100 Subject: [PATCH 061/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?8.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1c640aba8..08cd77aeb 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.128.4 + ### Refactors * ♻️ Refactor internals, simplify Pydantic v2/v1 utils, `create_model_field`, better types for `lenient_issubclass`. PR [#14860](https://github.com/fastapi/fastapi/pull/14860) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index b23e3c42f..580919a81 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.128.3" +__version__ = "0.128.4" from starlette import status as status From 2e7d3754cd1c85feb4dfa14ee29a509133cfc203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 7 Feb 2026 00:34:32 -0800 Subject: [PATCH 062/195] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20and=20s?= =?UTF-8?q?implify=20Pydantic=20v2=20(and=20v1)=20compatibility=20internal?= =?UTF-8?q?=20utils=20(#14862)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/__init__.py | 12 ++++++--- fastapi/_compat/v2.py | 30 +++------------------ fastapi/dependencies/utils.py | 51 ++++++++++++++++++++--------------- fastapi/openapi/utils.py | 6 ++--- 4 files changed, 44 insertions(+), 55 deletions(-) diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index 62681696e..4581c38c8 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -1,6 +1,14 @@ from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1 from .shared import field_annotation_is_scalar as field_annotation_is_scalar +from .shared import ( + field_annotation_is_scalar_sequence as field_annotation_is_scalar_sequence, +) +from .shared import field_annotation_is_sequence as field_annotation_is_sequence +from .shared import ( + is_bytes_or_nonable_bytes_annotation as is_bytes_or_nonable_bytes_annotation, +) +from .shared import is_bytes_sequence_annotation as is_bytes_sequence_annotation from .shared import is_pydantic_v1_model_instance as is_pydantic_v1_model_instance from .shared import ( is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation, @@ -25,11 +33,7 @@ from .v2 import get_flat_models_from_fields as get_flat_models_from_fields from .v2 import get_missing_field_error as get_missing_field_error from .v2 import get_model_name_map as get_model_name_map from .v2 import get_schema_from_model_field as get_schema_from_model_field -from .v2 import is_bytes_field as is_bytes_field -from .v2 import is_bytes_sequence_field as is_bytes_sequence_field from .v2 import is_scalar_field as is_scalar_field -from .v2 import is_scalar_sequence_field as is_scalar_sequence_field -from .v2 import is_sequence_field as is_sequence_field from .v2 import serialize_sequence_value as serialize_sequence_value from .v2 import ( with_info_plain_validator_function as with_info_plain_validator_function, diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index efc20fa58..87b9fb47f 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -102,18 +102,10 @@ class ModelField: sa = self.field_info.serialization_alias return sa or None - @property - def required(self) -> bool: - return self.field_info.is_required() - @property def default(self) -> Any: return self.get_default() - @property - def type_(self) -> Any: - return self.field_info.annotation - def __post_init__(self) -> None: with warnings.catch_warnings(): # Pydantic >= 2.12.0 warns about field specific metadata that is unused @@ -267,9 +259,9 @@ def get_definitions( for model in flat_serialization_models ] flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields - input_types = {f.type_ for f in fields} + input_types = {f.field_info.annotation for f in fields} unique_flat_model_fields = { - f for f in flat_model_fields if f.type_ not in input_types + f for f in flat_model_fields if f.field_info.annotation not in input_types } inputs = [ ( @@ -304,22 +296,6 @@ def is_scalar_field(field: ModelField) -> bool: ) and not isinstance(field.field_info, params.Body) -def is_sequence_field(field: ModelField) -> bool: - return shared.field_annotation_is_sequence(field.field_info.annotation) - - -def is_scalar_sequence_field(field: ModelField) -> bool: - return shared.field_annotation_is_scalar_sequence(field.field_info.annotation) - - -def is_bytes_field(field: ModelField) -> bool: - return shared.is_bytes_or_nonable_bytes_annotation(field.type_) - - -def is_bytes_sequence_field(field: ModelField) -> bool: - return shared.is_bytes_sequence_annotation(field.type_) - - def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: cls = type(field_info) merged_field_info = cls.from_annotation(annotation) @@ -428,7 +404,7 @@ def get_flat_models_from_annotation( def get_flat_models_from_field( field: ModelField, known_models: TypeModelSet ) -> TypeModelSet: - field_type = field.type_ + field_type = field.field_info.annotation if lenient_issubclass(field_type, BaseModel): if field_type in known_models: return known_models diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 1e334c429..80f9c76e9 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -25,13 +25,13 @@ from fastapi._compat import ( create_body_model, evaluate_forwardref, field_annotation_is_scalar, + field_annotation_is_scalar_sequence, + field_annotation_is_sequence, get_cached_model_fields, get_missing_field_error, - is_bytes_field, - is_bytes_sequence_field, + is_bytes_or_nonable_bytes_annotation, + is_bytes_sequence_annotation, is_scalar_field, - is_scalar_sequence_field, - is_sequence_field, is_uploadfile_or_nonable_uploadfile_annotation, is_uploadfile_sequence_annotation, lenient_issubclass, @@ -182,8 +182,10 @@ def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]: if not fields: return fields first_field = fields[0] - if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel): - fields_to_extract = get_cached_model_fields(first_field.type_) + if len(fields) == 1 and lenient_issubclass( + first_field.field_info.annotation, BaseModel + ): + fields_to_extract = get_cached_model_fields(first_field.field_info.annotation) return fields_to_extract return fields @@ -521,8 +523,8 @@ def analyze_param( elif isinstance(field_info, params.Query): assert ( is_scalar_field(field) - or is_scalar_sequence_field(field) - or lenient_issubclass(field.type_, BaseModel) + or field_annotation_is_scalar_sequence(field.field_info.annotation) + or lenient_issubclass(field.field_info.annotation, BaseModel) ), f"Query parameter {param_name!r} must be one of the supported types" return ParamDetails(type_annotation=type_annotation, depends=depends, field=field) @@ -708,7 +710,7 @@ def _validate_value_with_model_field( *, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...] ) -> tuple[Any, list[Any]]: if value is None: - if field.required: + if field.field_info.is_required(): return None, [get_missing_field_error(loc=loc)] else: return deepcopy(field.default), [] @@ -725,7 +727,7 @@ def _get_multidict_value( alias = alias or get_validation_alias(field) if ( (not _is_json_field(field)) - and is_sequence_field(field) + and field_annotation_is_sequence(field.field_info.annotation) and isinstance(values, (ImmutableMultiDict, Headers)) ): value = values.getlist(alias) @@ -738,9 +740,12 @@ def _get_multidict_value( and isinstance(value, str) # For type checks and value == "" ) - or (is_sequence_field(field) and len(value) == 0) + or ( + field_annotation_is_sequence(field.field_info.annotation) + and len(value) == 0 + ) ): - if field.required: + if field.field_info.is_required(): return else: return deepcopy(field.default) @@ -761,8 +766,10 @@ def request_params_to_args( fields_to_extract = fields single_not_embedded_field = False default_convert_underscores = True - if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel): - fields_to_extract = get_cached_model_fields(first_field.type_) + if len(fields) == 1 and lenient_issubclass( + first_field.field_info.annotation, BaseModel + ): + fields_to_extract = get_cached_model_fields(first_field.field_info.annotation) single_not_embedded_field = True # If headers are in a Pydantic model, the way to disable convert_underscores # would be with Header(convert_underscores=False) at the Pydantic model level @@ -866,8 +873,8 @@ def _should_embed_body_fields(fields: list[ModelField]) -> bool: # otherwise it has to be embedded, so that the key value pair can be extracted if ( isinstance(first_field.field_info, params.Form) - and not lenient_issubclass(first_field.type_, BaseModel) - and not is_union_of_base_models(first_field.type_) + and not lenient_issubclass(first_field.field_info.annotation, BaseModel) + and not is_union_of_base_models(first_field.field_info.annotation) ): return True return False @@ -884,12 +891,12 @@ async def _extract_form_body( field_info = field.field_info if ( isinstance(field_info, params.File) - and is_bytes_field(field) + and is_bytes_or_nonable_bytes_annotation(field.field_info.annotation) and isinstance(value, UploadFile) ): value = await value.read() elif ( - is_bytes_sequence_field(field) + is_bytes_sequence_annotation(field.field_info.annotation) and isinstance(field_info, params.File) and value_is_sequence(value) ): @@ -936,10 +943,10 @@ async def request_body_to_args( if ( single_not_embedded_field - and lenient_issubclass(first_field.type_, BaseModel) + and lenient_issubclass(first_field.field_info.annotation, BaseModel) and isinstance(received_body, FormData) ): - fields_to_extract = get_cached_model_fields(first_field.type_) + fields_to_extract = get_cached_model_fields(first_field.field_info.annotation) if isinstance(received_body, FormData): body_to_process = await _extract_form_body(fields_to_extract, received_body) @@ -992,7 +999,9 @@ def get_body_field( BodyModel = create_body_model( fields=flat_dependant.body_params, model_name=model_name ) - required = any(True for f in flat_dependant.body_params if f.required) + required = any( + True for f in flat_dependant.body_params if f.field_info.is_required() + ) BodyFieldInfo_kwargs: dict[str, Any] = { "annotation": BodyModel, "alias": "body", diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 9cff35981..bcad0be75 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -129,7 +129,7 @@ def _get_openapi_operation_parameters( default_convert_underscores = True if len(flat_dependant.header_params) == 1: first_field = flat_dependant.header_params[0] - if lenient_issubclass(first_field.type_, BaseModel): + if lenient_issubclass(first_field.field_info.annotation, BaseModel): default_convert_underscores = getattr( first_field.field_info, "convert_underscores", True ) @@ -161,7 +161,7 @@ def _get_openapi_operation_parameters( parameter = { "name": name, "in": param_type.value, - "required": param.required, + "required": param.field_info.is_required(), "schema": param_schema, } if field_info.description: @@ -198,7 +198,7 @@ def get_openapi_operation_request_body( ) field_info = cast(Body, body_field.field_info) request_media_type = field_info.media_type - required = body_field.required + required = body_field.field_info.is_required() request_body_oai: dict[str, Any] = {} if required: request_body_oai["required"] = required From c48539f4c60e3e63af34fded4d9eb0d27ac34eeb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 7 Feb 2026 08:34:59 +0000 Subject: [PATCH 063/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 08cd77aeb..5d93b684a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Refactors + +* ♻️ Refactor and simplify Pydantic v2 (and v1) compatibility internal utils. PR [#14862](https://github.com/fastapi/fastapi/pull/14862) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.4 ### Refactors From 9f4ecf562cffd79a2d6bff0eb333df5669bd70e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 8 Feb 2026 02:18:38 -0800 Subject: [PATCH 064/195] =?UTF-8?q?=E2=9C=85=20Add=20inline=20snapshot=20t?= =?UTF-8?q?ests=20for=20OpenAPI=20before=20changes=20from=20Pydantic=20v2?= =?UTF-8?q?=20(#14864)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_additional_properties.py | 147 +- tests/test_additional_properties_bool.py | 147 +- tests/test_additional_response_extra.py | 37 +- ...tional_responses_custom_validationerror.py | 113 +- ...ional_responses_default_validationerror.py | 127 +- ...est_additional_responses_response_class.py | 131 +- tests/test_additional_responses_router.py | 175 +- ...itional_responses_union_duplicate_anyof.py | 145 +- tests/test_annotated.py | 331 +- tests/test_application.py | 2447 +-- tests/test_computed_fields.py | 109 +- tests/test_custom_route_class.py | 85 +- tests/test_dependency_duplicates.py | 273 +- tests/test_deprecated_openapi_prefix.py | 39 +- tests/test_duplicate_models_openapi.py | 85 +- tests/test_enforce_once_required_parameter.py | 156 +- tests/test_extra_routes.py | 493 +- tests/test_forms_single_param.py | 145 +- tests/test_generate_unique_id_function.py | 2712 +-- tests/test_generic_parameterless_depends.py | 61 +- tests/test_get_request_body.py | 149 +- .../test_include_router_defaults_overrides.py | 13697 ++++++++-------- tests/test_infer_param_optionality.py | 445 +- .../test_modules_same_name_body/test_main.py | 203 +- tests/test_multi_body_errors.py | 170 +- tests/test_multi_query_errors.py | 135 +- tests/test_openapi_examples.py | 525 +- .../test_openapi_query_parameter_extension.py | 163 +- tests/test_openapi_route_extensions.py | 39 +- ...t_openapi_separate_input_output_schemas.py | 404 +- tests/test_param_in_path_and_dependency.py | 136 +- tests/test_param_include_in_schema.py | 272 +- tests/test_put_no_body.py | 127 +- tests/test_regex_deprecated_params.py | 144 +- tests/test_repeated_dependency_schema.py | 161 +- tests/test_repeated_parameter_alias.py | 146 +- tests/test_reponse_set_reponse_code_empty.py | 127 +- ...test_request_body_parameters_media_type.py | 265 +- .../test_cookie/test_optional_str.py | 97 +- .../test_cookie/test_required_str.py | 73 +- .../test_header/test_list.py | 105 +- .../test_header/test_optional_list.py | 121 +- .../test_header/test_optional_str.py | 97 +- .../test_header/test_required_str.py | 73 +- .../test_path/test_required_str.py | 19 +- .../test_query/test_list.py | 105 +- .../test_query/test_optional_list.py | 121 +- .../test_query/test_optional_str.py | 97 +- .../test_query/test_required_str.py | 73 +- tests/test_response_by_alias.py | 359 +- tests/test_response_class_no_mediatype.py | 127 +- tests/test_response_code_no_body.py | 113 +- ...est_response_model_as_return_annotation.py | 1205 +- tests/test_response_model_sub_types.py | 195 +- tests/test_schema_extra_examples.py | 1151 +- tests/test_security_api_key_cookie.py | 49 +- ...est_security_api_key_cookie_description.py | 57 +- .../test_security_api_key_cookie_optional.py | 49 +- tests/test_security_api_key_header.py | 49 +- ...est_security_api_key_header_description.py | 57 +- .../test_security_api_key_header_optional.py | 49 +- tests/test_security_api_key_query.py | 49 +- ...test_security_api_key_query_description.py | 57 +- tests/test_security_api_key_query_optional.py | 49 +- tests/test_security_http_base.py | 45 +- tests/test_security_http_base_description.py | 55 +- tests/test_security_http_base_optional.py | 45 +- tests/test_security_http_basic_optional.py | 45 +- tests/test_security_http_basic_realm.py | 45 +- ...t_security_http_basic_realm_description.py | 55 +- tests/test_security_http_bearer.py | 45 +- .../test_security_http_bearer_description.py | 55 +- tests/test_security_http_bearer_optional.py | 45 +- tests/test_security_http_digest.py | 45 +- .../test_security_http_digest_description.py | 55 +- tests/test_security_http_digest_optional.py | 45 +- tests/test_security_oauth2.py | 219 +- ...curity_oauth2_authorization_code_bearer.py | 65 +- ...2_authorization_code_bearer_description.py | 67 +- tests/test_security_oauth2_optional.py | 219 +- ...st_security_oauth2_optional_description.py | 221 +- ...ecurity_oauth2_password_bearer_optional.py | 53 +- ...h2_password_bearer_optional_description.py | 55 +- tests/test_security_openid_connect.py | 53 +- ...est_security_openid_connect_description.py | 55 +- .../test_security_openid_connect_optional.py | 53 +- tests/test_starlette_exception.py | 245 +- tests/test_sub_callbacks.py | 399 +- tests/test_tuples.py | 323 +- .../test_tutorial001.py | 187 +- .../test_tutorial002.py | 171 +- .../test_tutorial003.py | 195 +- .../test_tutorial004.py | 177 +- .../test_behind_a_proxy/test_tutorial001.py | 39 +- .../test_behind_a_proxy/test_tutorial002.py | 39 +- .../test_bigger_applications/test_main.py | 621 +- .../test_body/test_tutorial001.py | 155 +- .../test_body/test_tutorial002.py | 155 +- .../test_body/test_tutorial003.py | 175 +- .../test_body/test_tutorial004.py | 193 +- .../test_body_fields/test_tutorial001.py | 197 +- .../test_tutorial001.py | 203 +- .../test_tutorial002.py | 293 +- .../test_tutorial003.py | 213 +- .../test_tutorial004.py | 235 +- .../test_tutorial005.py | 243 +- ...est_tutorial001_tutorial002_tutorial003.py | 195 +- .../test_tutorial004.py | 249 +- .../test_tutorial005.py | 255 +- .../test_tutorial006.py | 271 +- .../test_tutorial007.py | 299 +- .../test_tutorial008.py | 161 +- .../test_tutorial009.py | 125 +- .../test_body_updates/test_tutorial001.py | 251 +- .../test_body_updates/test_tutorial002.py | 251 +- .../test_tutorial001.py | 37 +- .../test_cookie_params/test_tutorial001.py | 133 +- .../test_custom_response/test_tutorial001.py | 37 +- .../test_custom_response/test_tutorial001b.py | 37 +- ...est_tutorial002_tutorial003_tutorial004.py | 37 +- .../test_custom_response/test_tutorial005.py | 39 +- .../test_custom_response/test_tutorial006.py | 37 +- .../test_custom_response/test_tutorial006b.py | 27 +- .../test_custom_response/test_tutorial006c.py | 27 +- .../test_dataclasses/test_tutorial001.py | 163 +- .../test_dataclasses/test_tutorial003.py | 255 +- .../test_debugging/test_tutorial001.py | 35 +- .../test_tutorial001_tutorial001_02.py | 283 +- ...est_tutorial002_tutorial003_tutorial004.py | 167 +- .../test_dependencies/test_tutorial005.py | 147 +- .../test_dependencies/test_tutorial006.py | 139 +- .../test_dependencies/test_tutorial011.py | 133 +- .../test_dependencies/test_tutorial012.py | 217 +- .../test_encoder/test_tutorial001.py | 217 +- .../test_events/test_tutorial001.py | 132 +- .../test_events/test_tutorial002.py | 37 +- .../test_events/test_tutorial003.py | 140 +- .../test_tutorial001.py | 51 +- .../test_tutorial001_tutorial002.py | 203 +- .../test_extra_models/test_tutorial004.py | 77 +- .../test_extra_models/test_tutorial005.py | 51 +- ...est_tutorial001_tutorial002_tutorial003.py | 37 +- .../test_generate_clients/test_tutorial001.py | 191 +- .../test_generate_clients/test_tutorial002.py | 273 +- .../test_generate_clients/test_tutorial003.py | 273 +- .../test_generate_clients/test_tutorial004.py | 366 +- .../test_graphql/test_tutorial001.py | 67 +- .../test_handling_errors/test_tutorial001.py | 127 +- .../test_handling_errors/test_tutorial002.py | 127 +- .../test_handling_errors/test_tutorial003.py | 127 +- .../test_handling_errors/test_tutorial004.py | 127 +- .../test_handling_errors/test_tutorial005.py | 147 +- .../test_handling_errors/test_tutorial006.py | 127 +- .../test_header_params/test_tutorial001.py | 133 +- .../test_header_params/test_tutorial002.py | 133 +- .../test_header_params/test_tutorial003.py | 143 +- .../test_metadata/test_tutorial001.py | 65 +- .../test_metadata/test_tutorial001_1.py | 65 +- .../test_metadata/test_tutorial002.py | 43 +- .../test_metadata/test_tutorial003.py | 43 +- .../test_metadata/test_tutorial004.py | 91 +- .../test_middleware/test_tutorial001.py | 19 +- .../test_tutorial001.py | 307 +- .../test_openapi_webhooks/test_tutorial001.py | 181 +- .../test_tutorial001.py | 37 +- .../test_tutorial002.py | 37 +- .../test_tutorial003.py | 13 +- .../test_tutorial004.py | 175 +- .../test_tutorial005.py | 39 +- .../test_tutorial006.py | 63 +- .../test_tutorial007.py | 69 +- .../test_tutorial001.py | 231 +- .../test_tutorial002.py | 275 +- .../test_tutorial002b.py | 65 +- .../test_tutorial003_tutorial004.py | 233 +- .../test_tutorial005.py | 175 +- .../test_tutorial006.py | 93 +- .../test_path_params/test_tutorial001.py | 157 +- .../test_path_params/test_tutorial002.py | 159 +- .../test_path_params/test_tutorial003.py | 201 +- .../test_path_params/test_tutorial003b.py | 35 +- .../test_path_params/test_tutorial004.py | 127 +- .../test_path_params/test_tutorial005.py | 146 +- .../test_tutorial001.py | 197 +- .../test_tutorial002_tutorial003.py | 183 +- .../test_tutorial004.py | 185 +- .../test_tutorial005.py | 187 +- .../test_tutorial006.py | 209 +- .../test_query_params/test_tutorial001.py | 155 +- .../test_query_params/test_tutorial002.py | 161 +- .../test_query_params/test_tutorial003.py | 179 +- .../test_query_params/test_tutorial004.py | 191 +- .../test_query_params/test_tutorial005.py | 139 +- .../test_query_params/test_tutorial006.py | 173 +- .../test_tutorial001.py | 139 +- .../test_tutorial002.py | 145 +- .../test_tutorial003.py | 147 +- .../test_tutorial004.py | 149 +- .../test_tutorial005.py | 137 +- .../test_tutorial006.py | 135 +- .../test_tutorial006c.py | 139 +- .../test_tutorial007.py | 145 +- .../test_tutorial008.py | 147 +- .../test_tutorial009.py | 139 +- .../test_tutorial010.py | 165 +- .../test_tutorial011.py | 139 +- .../test_tutorial012.py | 137 +- .../test_tutorial013.py | 137 +- .../test_tutorial014.py | 119 +- .../test_request_files/test_tutorial001.py | 213 +- .../test_request_files/test_tutorial001_03.py | 245 +- .../test_request_files/test_tutorial002.py | 243 +- .../test_request_files/test_tutorial003.py | 255 +- .../test_tutorial001.py | 143 +- .../test_tutorial002.py | 145 +- .../test_request_forms/test_tutorial001.py | 141 +- .../test_tutorial001.py | 155 +- .../test_tutorial001.py | 183 +- .../test_tutorial002.py | 41 +- .../test_tutorial001_tutorial001_01.py | 207 +- .../test_response_model/test_tutorial002.py | 163 +- .../test_tutorial003_02.py | 139 +- .../test_tutorial003_03.py | 37 +- .../test_tutorial003_05.py | 139 +- .../test_tutorial001_tutorial002.py | 135 +- .../test_tutorial001.py | 194 +- .../test_tutorial002.py | 198 +- .../test_tutorial003.py | 196 +- .../test_tutorial004.py | 203 +- .../test_tutorial005.py | 223 +- .../test_security/test_tutorial001.py | 53 +- .../test_security/test_tutorial002.py | 53 +- .../test_security/test_tutorial003.py | 217 +- .../test_security/test_tutorial004.py | 323 +- .../test_security/test_tutorial006.py | 45 +- .../test_security/test_tutorial007.py | 45 +- .../test_tutorial001.py | 181 +- .../test_tutorial002.py | 181 +- .../test_tutorial/test_settings/test_app01.py | 37 +- .../test_static_files/test_tutorial001.py | 13 +- .../test_sub_applications/test_tutorial001.py | 102 +- .../test_tutorial/test_testing/test_main_a.py | 38 +- .../test_testing/test_tutorial001.py | 38 +- .../test_tutorial001.py | 187 +- tests/test_union_body.py | 161 +- tests/test_union_forms.py | 178 +- tests/test_union_inherited_body.py | 177 +- tests/test_webhooks_security.py | 162 +- 248 files changed, 28752 insertions(+), 27532 deletions(-) diff --git a/tests/test_additional_properties.py b/tests/test_additional_properties.py index 935acca42..130a6662a 100644 --- a/tests/test_additional_properties.py +++ b/tests/test_additional_properties.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -26,84 +27,88 @@ def test_additional_properties_post(): 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": { - "/foo": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/foo": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Foo", + "operationId": "foo_foo_post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Items"} } }, + "required": True, }, - }, - "summary": "Foo", - "operationId": "foo_foo_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Items"} + } + } + }, + "components": { + "schemas": { + "Items": { + "title": "Items", + "required": ["items"], + "type": "object", + "properties": { + "items": { + "title": "Items", + "type": "object", + "additionalProperties": {"type": "integer"}, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "Items": { - "title": "Items", - "required": ["items"], - "type": "object", - "properties": { - "items": { - "title": "Items", - "type": "object", - "additionalProperties": {"type": "integer"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_additional_properties_bool.py b/tests/test_additional_properties_bool.py index 3b323ad46..c02841cde 100644 --- a/tests/test_additional_properties_bool.py +++ b/tests/test_additional_properties_bool.py @@ -2,6 +2,7 @@ from typing import Union from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, ConfigDict @@ -40,83 +41,87 @@ def test_call_valid(): 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": { - "/": { - "post": { - "summary": "Post", - "operationId": "post__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "anyOf": [ - {"$ref": "#/components/schemas/Foo"}, - {"type": "null"}, - ], - "title": "Foo", - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "post": { + "summary": "Post", + "operationId": "post__post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "anyOf": [ + {"$ref": "#/components/schemas/Foo"}, + {"type": "null"}, + ], + "title": "Foo", } } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, + } + } + }, + "components": { + "schemas": { + "Foo": { + "properties": {}, + "additionalProperties": False, + "type": "object", + "title": "Foo", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } - } - }, - "components": { - "schemas": { - "Foo": { - "properties": {}, - "additionalProperties": False, - "type": "object", - "title": "Foo", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + }, + } + ) diff --git a/tests/test_additional_response_extra.py b/tests/test_additional_response_extra.py index 55be19bad..70a9a7331 100644 --- a/tests/test_additional_response_extra.py +++ b/tests/test_additional_response_extra.py @@ -1,5 +1,6 @@ from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot router = APIRouter() @@ -29,21 +30,23 @@ def test_path_operation(): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Item", - "operationId": "read_item_items__get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Item", + "operationId": "read_item_items__get", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_additional_responses_custom_validationerror.py b/tests/test_additional_responses_custom_validationerror.py index 8724e5ecb..9b468c621 100644 --- a/tests/test_additional_responses_custom_validationerror.py +++ b/tests/test_additional_responses_custom_validationerror.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -34,64 +35,66 @@ 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": { - "/a/{id}": { - "get": { - "responses": { - "422": { - "description": "Error", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/JsonApiError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a/{id}": { + "get": { + "responses": { + "422": { + "description": "Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + } } - } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/vnd.api+json": {"schema": {}}}, }, }, - "200": { - "description": "Successful Response", - "content": {"application/vnd.api+json": {"schema": {}}}, + "summary": "A", + "operationId": "a_a__id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Id"}, + "name": "id", + "in": "path", + } + ], + } + } + }, + "components": { + "schemas": { + "Error": { + "title": "Error", + "required": ["status", "title"], + "type": "object", + "properties": { + "status": {"title": "Status", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + }, + }, + "JsonApiError": { + "title": "JsonApiError", + "required": ["errors"], + "type": "object", + "properties": { + "errors": { + "title": "Errors", + "type": "array", + "items": {"$ref": "#/components/schemas/Error"}, + } }, }, - "summary": "A", - "operationId": "a_a__id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Id"}, - "name": "id", - "in": "path", - } - ], } - } - }, - "components": { - "schemas": { - "Error": { - "title": "Error", - "required": ["status", "title"], - "type": "object", - "properties": { - "status": {"title": "Status", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - }, - }, - "JsonApiError": { - "title": "JsonApiError", - "required": ["errors"], - "type": "object", - "properties": { - "errors": { - "title": "Errors", - "type": "array", - "items": {"$ref": "#/components/schemas/Error"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_additional_responses_default_validationerror.py b/tests/test_additional_responses_default_validationerror.py index acc081fb1..09e03959e 100644 --- a/tests/test_additional_responses_default_validationerror.py +++ b/tests/test_additional_responses_default_validationerror.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -15,72 +16,76 @@ 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": { - "/a/{id}": { - "get": { - "responses": { - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a/{id}": { + "get": { + "responses": { + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, }, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "A", - "operationId": "a_a__id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Id"}, - "name": "id", - "in": "path", - } - ], + "summary": "A", + "operationId": "a_a__id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Id"}, + "name": "id", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_additional_responses_response_class.py b/tests/test_additional_responses_response_class.py index fecc3ee16..3faab2797 100644 --- a/tests/test_additional_responses_response_class.py +++ b/tests/test_additional_responses_response_class.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -39,76 +40,78 @@ 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": { - "/a": { - "get": { - "responses": { - "500": { - "description": "Error", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/JsonApiError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a": { + "get": { + "responses": { + "500": { + "description": "Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + } } - } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/vnd.api+json": {"schema": {}}}, }, }, - "200": { - "description": "Successful Response", - "content": {"application/vnd.api+json": {"schema": {}}}, - }, - }, - "summary": "A", - "operationId": "a_a_get", - } - }, - "/b": { - "get": { - "responses": { - "500": { - "description": "Error", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Error"} - } + "summary": "A", + "operationId": "a_a_get", + } + }, + "/b": { + "get": { + "responses": { + "500": { + "description": "Error", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, }, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + "summary": "B", + "operationId": "b_b_get", + } + }, + }, + "components": { + "schemas": { + "Error": { + "title": "Error", + "required": ["status", "title"], + "type": "object", + "properties": { + "status": {"title": "Status", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + }, + }, + "JsonApiError": { + "title": "JsonApiError", + "required": ["errors"], + "type": "object", + "properties": { + "errors": { + "title": "Errors", + "type": "array", + "items": {"$ref": "#/components/schemas/Error"}, + } }, }, - "summary": "B", - "operationId": "b_b_get", } }, - }, - "components": { - "schemas": { - "Error": { - "title": "Error", - "required": ["status", "title"], - "type": "object", - "properties": { - "status": {"title": "Status", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - }, - }, - "JsonApiError": { - "title": "JsonApiError", - "required": ["errors"], - "type": "object", - "properties": { - "errors": { - "title": "Errors", - "type": "array", - "items": {"$ref": "#/components/schemas/Error"}, - } - }, - }, - } - }, - } + } + ) diff --git a/tests/test_additional_responses_router.py b/tests/test_additional_responses_router.py index 71cabc7c3..b6c359ba8 100644 --- a/tests/test_additional_responses_router.py +++ b/tests/test_additional_responses_router.py @@ -1,5 +1,6 @@ from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel @@ -84,94 +85,98 @@ def test_d(): 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": { - "/a": { - "get": { - "responses": { - "501": {"description": "Error 1"}, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "A", - "operationId": "a_a_get", - } - }, - "/b": { - "get": { - "responses": { - "502": {"description": "Error 2"}, - "4XX": {"description": "Error with range, upper"}, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "B", - "operationId": "b_b_get", - } - }, - "/c": { - "get": { - "responses": { - "400": {"description": "Error with str"}, - "5XX": {"description": "Error with range, lower"}, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "default": {"description": "A default response"}, - }, - "summary": "C", - "operationId": "c_c_get", - } - }, - "/d": { - "get": { - "responses": { - "400": {"description": "Error with str"}, - "5XX": { - "description": "Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseModel" - } - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a": { + "get": { + "responses": { + "501": {"description": "Error 1"}, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, }, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "default": { - "description": "Default Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseModel" - } - } + "summary": "A", + "operationId": "a_a_get", + } + }, + "/b": { + "get": { + "responses": { + "502": {"description": "Error 2"}, + "4XX": {"description": "Error with range, upper"}, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, }, - }, - "summary": "D", - "operationId": "d_d_get", + "summary": "B", + "operationId": "b_b_get", + } + }, + "/c": { + "get": { + "responses": { + "400": {"description": "Error with str"}, + "5XX": {"description": "Error with range, lower"}, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "default": {"description": "A default response"}, + }, + "summary": "C", + "operationId": "c_c_get", + } + }, + "/d": { + "get": { + "responses": { + "400": {"description": "Error with str"}, + "5XX": { + "description": "Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "default": { + "description": "Default Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + } + }, + }, + }, + "summary": "D", + "operationId": "d_d_get", + } + }, + }, + "components": { + "schemas": { + "ResponseModel": { + "title": "ResponseModel", + "required": ["message"], + "type": "object", + "properties": { + "message": {"title": "Message", "type": "string"} + }, + } } }, - }, - "components": { - "schemas": { - "ResponseModel": { - "title": "ResponseModel", - "required": ["message"], - "type": "object", - "properties": {"message": {"title": "Message", "type": "string"}}, - } - } - }, - } + } + ) diff --git a/tests/test_additional_responses_union_duplicate_anyof.py b/tests/test_additional_responses_union_duplicate_anyof.py index f5d987ca3..5d833fce4 100644 --- a/tests/test_additional_responses_union_duplicate_anyof.py +++ b/tests/test_additional_responses_union_duplicate_anyof.py @@ -8,6 +8,7 @@ from typing import Union from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel @@ -45,79 +46,81 @@ 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"}}, - } + assert response.json() == snapshot( + { + "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", }, } }, - "/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_annotated.py b/tests/test_annotated.py index 99bd03ae6..68e2ea884 100644 --- a/tests/test_annotated.py +++ b/tests/test_annotated.py @@ -3,6 +3,7 @@ from typing import Annotated import pytest from fastapi import APIRouter, FastAPI, Query from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -120,174 +121,178 @@ def test_nested_router(): def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/default": { - "get": { - "summary": "Default", - "operationId": "default_default_get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Foo", - "type": "string", - "default": "foo", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/default": { + "get": { + "summary": "Default", + "operationId": "default_default_get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Foo", + "type": "string", + "default": "foo", + }, + "name": "foo", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, - "name": "foo", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - } - }, - "/required": { - "get": { - "summary": "Required", - "operationId": "required_required_get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Foo", - "minLength": 1, - "type": "string", - }, - "name": "foo", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/multiple": { - "get": { - "summary": "Multiple", - "operationId": "multiple_multiple_get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Foo", - "minLength": 1, - "type": "string", - }, - "name": "foo", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/unrelated": { - "get": { - "summary": "Unrelated", - "operationId": "unrelated_unrelated_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Foo", "type": "string"}, - "name": "foo", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, + } }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "/required": { + "get": { + "summary": "Required", + "operationId": "required_required_get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Foo", + "minLength": 1, + "type": "string", + }, + "name": "foo", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, + } }, - } - }, - } + "/multiple": { + "get": { + "summary": "Multiple", + "operationId": "multiple_multiple_get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Foo", + "minLength": 1, + "type": "string", + }, + "name": "foo", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/unrelated": { + "get": { + "summary": "Unrelated", + "operationId": "unrelated_unrelated_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Foo", "type": "string"}, + "name": "foo", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + } + }, + } + ) diff --git a/tests/test_application.py b/tests/test_application.py index fe97e674c..675866298 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -1,5 +1,6 @@ import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from .main import app @@ -54,1227 +55,1231 @@ def test_enum_status_code_response(): 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"}, - "externalDocs": { - "description": "External API documentation.", - "url": "https://docs.example.com/api-general", - }, - "paths": { - "/api_route": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Non Operation", - "operationId": "non_operation_api_route_get", - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "externalDocs": { + "description": "External API documentation.", + "url": "https://docs.example.com/api-general", }, - "/non_decorated_route": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Non Decorated Route", - "operationId": "non_decorated_route_non_decorated_route_get", - } - }, - "/text": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Get Text", - "operationId": "get_text_text_get", - } - }, - "/path/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Id", - "operationId": "get_id_path__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/str/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Str Id", - "operationId": "get_str_id_path_str__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Int Id", - "operationId": "get_int_id_path_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/float/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Float Id", - "operationId": "get_float_id_path_float__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "number"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/bool/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Bool Id", - "operationId": "get_bool_id_path_bool__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "boolean"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Id", - "operationId": "get_path_param_id_path_param__item_id__get", - "parameters": [ - { - "name": "item_id", - "in": "path", - "required": True, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Item Id", - }, - } - ], - } - }, - "/path/param-minlength/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Min Length", - "operationId": "get_path_param_min_length_path_param_minlength__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "minLength": 3, - "type": "string", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-maxlength/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Max Length", - "operationId": "get_path_param_max_length_path_param_maxlength__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maxLength": 3, - "type": "string", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-min_maxlength/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Min Max Length", - "operationId": "get_path_param_min_max_length_path_param_min_maxlength__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maxLength": 3, - "minLength": 2, - "type": "string", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-gt/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Gt", - "operationId": "get_path_param_gt_path_param_gt__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMinimum": 3.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-gt0/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Gt0", - "operationId": "get_path_param_gt0_path_param_gt0__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMinimum": 0.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-ge/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Ge", - "operationId": "get_path_param_ge_path_param_ge__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "minimum": 3.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-lt/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Lt", - "operationId": "get_path_param_lt_path_param_lt__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMaximum": 3.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-lt0/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Lt0", - "operationId": "get_path_param_lt0_path_param_lt0__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMaximum": 0.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-le/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Le", - "operationId": "get_path_param_le_path_param_le__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maximum": 3.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-lt-gt/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Lt Gt", - "operationId": "get_path_param_lt_gt_path_param_lt_gt__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMaximum": 3.0, - "exclusiveMinimum": 1.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-le-ge/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Le Ge", - "operationId": "get_path_param_le_ge_path_param_le_ge__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maximum": 3.0, - "minimum": 1.0, - "type": "number", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-lt-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Lt Int", - "operationId": "get_path_param_lt_int_path_param_lt_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMaximum": 3.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-gt-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Gt Int", - "operationId": "get_path_param_gt_int_path_param_gt_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMinimum": 3.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-le-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Le Int", - "operationId": "get_path_param_le_int_path_param_le_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maximum": 3.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-ge-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Ge Int", - "operationId": "get_path_param_ge_int_path_param_ge_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "minimum": 3.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-lt-gt-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Lt Gt Int", - "operationId": "get_path_param_lt_gt_int_path_param_lt_gt_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "exclusiveMaximum": 3.0, - "exclusiveMinimum": 1.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/path/param-le-ge-int/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Path Param Le Ge Int", - "operationId": "get_path_param_le_ge_int_path_param_le_ge_int__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "maximum": 3.0, - "minimum": 1.0, - "type": "integer", - }, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/query": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query", - "operationId": "get_query_query_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Query"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/optional": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Optional", - "operationId": "get_query_optional_query_optional_get", - "parameters": [ - { - "required": False, - "schema": {"title": "Query"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/int": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Type", - "operationId": "get_query_type_query_int_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Query", "type": "integer"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/int/optional": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Type Optional", - "operationId": "get_query_type_optional_query_int_optional_get", - "parameters": [ - { - "name": "query", - "in": "query", - "required": False, - "schema": { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "title": "Query", - }, - } - ], - } - }, - "/query/int/default": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Type Int Default", - "operationId": "get_query_type_int_default_query_int_default_get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Query", - "type": "integer", - "default": 10, - }, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/param": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Param", - "operationId": "get_query_param_query_param_get", - "parameters": [ - { - "required": False, - "schema": {"title": "Query"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/param-required": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Param Required", - "operationId": "get_query_param_required_query_param_required_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Query"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/query/param-required/int": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Get Query Param Required Type", - "operationId": "get_query_param_required_type_query_param_required_int_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Query", "type": "integer"}, - "name": "query", - "in": "query", - } - ], - } - }, - "/enum-status-code": { - "get": { - "responses": { - "201": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - }, - "summary": "Get Enum Status Code", - "operationId": "get_enum_status_code_enum_status_code_get", - } - }, - "/query/frozenset": { - "get": { - "summary": "Get Query Type Frozenset", - "operationId": "get_query_type_frozenset_query_frozenset_get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Query", - "uniqueItems": True, - "type": "array", - "items": {"type": "integer"}, - }, - "name": "query", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/query/list": { - "get": { - "summary": "Get Query List", - "operationId": "get_query_list_query_list_get", - "parameters": [ - { - "name": "device_ids", - "in": "query", - "required": True, - "schema": { - "type": "array", - "items": {"type": "integer"}, - "title": "Device Ids", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": {"type": "integer"}, - "title": "Response Get Query List Query List Get", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/query/list-default": { - "get": { - "summary": "Get Query List Default", - "operationId": "get_query_list_default_query_list_default_get", - "parameters": [ - { - "name": "device_ids", - "in": "query", - "required": False, - "schema": { - "type": "array", - "items": {"type": "integer"}, - "default": [], - "title": "Device Ids", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": {"type": "integer"}, - "title": "Response Get Query List Default Query List Default Get", - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, + "paths": { + "/api_route": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Non Operation", + "operationId": "non_operation_api_route_get", + } }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, + "/non_decorated_route": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Non Decorated Route", + "operationId": "non_decorated_route_non_decorated_route_get", + } }, - } - }, - } + "/text": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Get Text", + "operationId": "get_text_text_get", + } + }, + "/path/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Id", + "operationId": "get_id_path__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/str/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Str Id", + "operationId": "get_str_id_path_str__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Int Id", + "operationId": "get_int_id_path_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/float/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Float Id", + "operationId": "get_float_id_path_float__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "number"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/bool/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Bool Id", + "operationId": "get_bool_id_path_bool__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "boolean"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Id", + "operationId": "get_path_param_id_path_param__item_id__get", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Item Id", + }, + } + ], + } + }, + "/path/param-minlength/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Min Length", + "operationId": "get_path_param_min_length_path_param_minlength__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "minLength": 3, + "type": "string", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-maxlength/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Max Length", + "operationId": "get_path_param_max_length_path_param_maxlength__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maxLength": 3, + "type": "string", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-min_maxlength/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Min Max Length", + "operationId": "get_path_param_min_max_length_path_param_min_maxlength__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maxLength": 3, + "minLength": 2, + "type": "string", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-gt/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Gt", + "operationId": "get_path_param_gt_path_param_gt__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMinimum": 3.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-gt0/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Gt0", + "operationId": "get_path_param_gt0_path_param_gt0__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMinimum": 0.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-ge/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Ge", + "operationId": "get_path_param_ge_path_param_ge__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "minimum": 3.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-lt/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Lt", + "operationId": "get_path_param_lt_path_param_lt__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMaximum": 3.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-lt0/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Lt0", + "operationId": "get_path_param_lt0_path_param_lt0__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMaximum": 0.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-le/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Le", + "operationId": "get_path_param_le_path_param_le__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maximum": 3.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-lt-gt/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Lt Gt", + "operationId": "get_path_param_lt_gt_path_param_lt_gt__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMaximum": 3.0, + "exclusiveMinimum": 1.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-le-ge/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Le Ge", + "operationId": "get_path_param_le_ge_path_param_le_ge__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maximum": 3.0, + "minimum": 1.0, + "type": "number", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-lt-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Lt Int", + "operationId": "get_path_param_lt_int_path_param_lt_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMaximum": 3.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-gt-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Gt Int", + "operationId": "get_path_param_gt_int_path_param_gt_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMinimum": 3.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-le-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Le Int", + "operationId": "get_path_param_le_int_path_param_le_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maximum": 3.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-ge-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Ge Int", + "operationId": "get_path_param_ge_int_path_param_ge_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "minimum": 3.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-lt-gt-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Lt Gt Int", + "operationId": "get_path_param_lt_gt_int_path_param_lt_gt_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "exclusiveMaximum": 3.0, + "exclusiveMinimum": 1.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/path/param-le-ge-int/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Path Param Le Ge Int", + "operationId": "get_path_param_le_ge_int_path_param_le_ge_int__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "maximum": 3.0, + "minimum": 1.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/query": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query", + "operationId": "get_query_query_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Query"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/optional": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Optional", + "operationId": "get_query_optional_query_optional_get", + "parameters": [ + { + "required": False, + "schema": {"title": "Query"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/int": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Type", + "operationId": "get_query_type_query_int_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Query", "type": "integer"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/int/optional": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Type Optional", + "operationId": "get_query_type_optional_query_int_optional_get", + "parameters": [ + { + "name": "query", + "in": "query", + "required": False, + "schema": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Query", + }, + } + ], + } + }, + "/query/int/default": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Type Int Default", + "operationId": "get_query_type_int_default_query_int_default_get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Query", + "type": "integer", + "default": 10, + }, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/param": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Param", + "operationId": "get_query_param_query_param_get", + "parameters": [ + { + "required": False, + "schema": {"title": "Query"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/param-required": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Param Required", + "operationId": "get_query_param_required_query_param_required_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Query"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/query/param-required/int": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Query Param Required Type", + "operationId": "get_query_param_required_type_query_param_required_int_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Query", "type": "integer"}, + "name": "query", + "in": "query", + } + ], + } + }, + "/enum-status-code": { + "get": { + "responses": { + "201": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + }, + "summary": "Get Enum Status Code", + "operationId": "get_enum_status_code_enum_status_code_get", + } + }, + "/query/frozenset": { + "get": { + "summary": "Get Query Type Frozenset", + "operationId": "get_query_type_frozenset_query_frozenset_get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Query", + "uniqueItems": True, + "type": "array", + "items": {"type": "integer"}, + }, + "name": "query", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/query/list": { + "get": { + "summary": "Get Query List", + "operationId": "get_query_list_query_list_get", + "parameters": [ + { + "name": "device_ids", + "in": "query", + "required": True, + "schema": { + "type": "array", + "items": {"type": "integer"}, + "title": "Device Ids", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": {"type": "integer"}, + "title": "Response Get Query List Query List Get", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/query/list-default": { + "get": { + "summary": "Get Query List Default", + "operationId": "get_query_list_default_query_list_default_get", + "parameters": [ + { + "name": "device_ids", + "in": "query", + "required": False, + "schema": { + "type": "array", + "items": {"type": "integer"}, + "default": [], + "title": "Device Ids", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": {"type": "integer"}, + "title": "Response Get Query List Default Query List Default Get", + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py index e7f969f7c..af6654a63 100644 --- a/tests/test_computed_fields.py +++ b/tests/test_computed_fields.py @@ -1,6 +1,7 @@ import pytest from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture(name="client") @@ -43,55 +44,65 @@ def test_get(client: TestClient, path: str): def test_openapi_schema(client: TestClient): 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": { - "/": { - "get": { - "summary": "Read Root", - "operationId": "read_root__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Rectangle"} - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Read Root", + "operationId": "read_root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Rectangle" + } + } + }, + } + }, + } + }, + "/responses": { + "get": { + "summary": "Read Responses", + "operationId": "read_responses_responses_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Rectangle" + } + } + }, + } + }, + } + }, + }, + "components": { + "schemas": { + "Rectangle": { + "properties": { + "width": {"type": "integer", "title": "Width"}, + "length": {"type": "integer", "title": "Length"}, + "area": { + "type": "integer", + "title": "Area", + "readOnly": True, }, - } - }, + }, + "type": "object", + "required": ["width", "length", "area"], + "title": "Rectangle", + } } }, - "/responses": { - "get": { - "summary": "Read Responses", - "operationId": "read_responses_responses_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Rectangle"} - } - }, - } - }, - } - }, - }, - "components": { - "schemas": { - "Rectangle": { - "properties": { - "width": {"type": "integer", "title": "Width"}, - "length": {"type": "integer", "title": "Length"}, - "area": {"type": "integer", "title": "Area", "readOnly": True}, - }, - "type": "object", - "required": ["width", "length", "area"], - "title": "Rectangle", - } - } - }, - } + } + ) diff --git a/tests/test_custom_route_class.py b/tests/test_custom_route_class.py index 55374584b..786c1efc3 100644 --- a/tests/test_custom_route_class.py +++ b/tests/test_custom_route_class.py @@ -2,6 +2,7 @@ import pytest from fastapi import APIRouter, FastAPI from fastapi.routing import APIRoute from fastapi.testclient import TestClient +from inline_snapshot import snapshot from starlette.routing import Route app = FastAPI() @@ -74,45 +75,47 @@ def test_route_classes(): 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": { - "/a/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Get A", - "operationId": "get_a_a__get", - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Get A", + "operationId": "get_a_a__get", + } + }, + "/a/b/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Get B", + "operationId": "get_b_a_b__get", + } + }, + "/a/b/c/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Get C", + "operationId": "get_c_a_b_c__get", + } + }, }, - "/a/b/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Get B", - "operationId": "get_b_a_b__get", - } - }, - "/a/b/c/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Get C", - "operationId": "get_c_a_b_c__get", - } - }, - }, - } + } + ) diff --git a/tests/test_dependency_duplicates.py b/tests/test_dependency_duplicates.py index 3ca6a3e89..81490e68b 100644 --- a/tests/test_dependency_duplicates.py +++ b/tests/test_dependency_duplicates.py @@ -1,5 +1,6 @@ from fastapi import Depends, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -84,149 +85,153 @@ def test_sub_duplicates(): 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": { - "/with-duplicates": { - "post": { - "summary": "With Duplicates", - "operationId": "with_duplicates_with_duplicates_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/with-duplicates": { + "post": { + "summary": "With Duplicates", + "operationId": "with_duplicates_with_duplicates_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + } + }, + "/no-duplicates": { + "post": { + "summary": "No Duplicates", + "operationId": "no_duplicates_no_duplicates_post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_no_duplicates_no_duplicates_post" } } }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/with-duplicates-sub": { + "post": { + "summary": "No Duplicates Sub", + "operationId": "no_duplicates_sub_with_duplicates_sub_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_no_duplicates_no_duplicates_post": { + "title": "Body_no_duplicates_no_duplicates_post", + "required": ["item", "item2"], + "type": "object", + "properties": { + "item": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "Item": { + "title": "Item", + "required": ["data"], + "type": "object", + "properties": {"data": {"title": "Data", "type": "string"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } }, - "/no-duplicates": { - "post": { - "summary": "No Duplicates", - "operationId": "no_duplicates_no_duplicates_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_no_duplicates_no_duplicates_post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/with-duplicates-sub": { - "post": { - "summary": "No Duplicates Sub", - "operationId": "no_duplicates_sub_with_duplicates_sub_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_no_duplicates_no_duplicates_post": { - "title": "Body_no_duplicates_no_duplicates_post", - "required": ["item", "item2"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["data"], - "type": "object", - "properties": {"data": {"title": "Data", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + } + ) diff --git a/tests/test_deprecated_openapi_prefix.py b/tests/test_deprecated_openapi_prefix.py index ec7366d2a..24198af34 100644 --- a/tests/test_deprecated_openapi_prefix.py +++ b/tests/test_deprecated_openapi_prefix.py @@ -1,5 +1,6 @@ from fastapi import FastAPI, Request from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI(openapi_prefix="/api/v1") @@ -21,22 +22,24 @@ def test_main(): def test_openapi(): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/app": { - "get": { - "summary": "Read Main", - "operationId": "read_main_app_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/app": { + "get": { + "summary": "Read Main", + "operationId": "read_main_app_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - "servers": [{"url": "/api/v1"}], - } + }, + "servers": [{"url": "/api/v1"}], + } + ) diff --git a/tests/test_duplicate_models_openapi.py b/tests/test_duplicate_models_openapi.py index 83e86d231..d5a6d1fec 100644 --- a/tests/test_duplicate_models_openapi.py +++ b/tests/test_duplicate_models_openapi.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -35,45 +36,49 @@ def test_get_api_route(): 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": { - "/": { - "get": { - "summary": "F", - "operationId": "f__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model3"} - } - }, - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "F", + "operationId": "f__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Model3" + } + } + }, + } + }, + } + } + }, + "components": { + "schemas": { + "Model": {"title": "Model", "type": "object", "properties": {}}, + "Model2": { + "title": "Model2", + "required": ["a"], + "type": "object", + "properties": {"a": {"$ref": "#/components/schemas/Model"}}, + }, + "Model3": { + "title": "Model3", + "required": ["c", "d"], + "type": "object", + "properties": { + "c": {"$ref": "#/components/schemas/Model"}, + "d": {"$ref": "#/components/schemas/Model2"}, + }, }, } - } - }, - "components": { - "schemas": { - "Model": {"title": "Model", "type": "object", "properties": {}}, - "Model2": { - "title": "Model2", - "required": ["a"], - "type": "object", - "properties": {"a": {"$ref": "#/components/schemas/Model"}}, - }, - "Model3": { - "title": "Model3", - "required": ["c", "d"], - "type": "object", - "properties": { - "c": {"$ref": "#/components/schemas/Model"}, - "d": {"$ref": "#/components/schemas/Model2"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_enforce_once_required_parameter.py b/tests/test_enforce_once_required_parameter.py index c46a54357..0dee15c25 100644 --- a/tests/test_enforce_once_required_parameter.py +++ b/tests/test_enforce_once_required_parameter.py @@ -1,7 +1,8 @@ from typing import Optional -from fastapi import Depends, FastAPI, Query, status +from fastapi import Depends, FastAPI, Query from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -26,81 +27,6 @@ def foo_handler( client = TestClient(app) -expected_schema = { - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "title": "Detail", - "type": "array", - } - }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - "title": "Location", - "type": "array", - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - "required": ["loc", "msg", "type"], - "title": "ValidationError", - "type": "object", - }, - } - }, - "info": {"title": "FastAPI", "version": "0.1.0"}, - "openapi": "3.1.0", - "paths": { - "/foo": { - "get": { - "operationId": "foo_handler_foo_get", - "parameters": [ - { - "in": "query", - "name": "client_id", - "required": True, - "schema": {"title": "Client Id", "type": "string"}, - }, - ], - "responses": { - "200": { - "content": {"application/json": {"schema": {}}}, - "description": "Successful Response", - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error", - }, - }, - "summary": "Foo Handler", - } - } - }, -} - - -def test_schema(): - response = client.get("/openapi.json") - assert response.status_code == status.HTTP_200_OK - actual_schema = response.json() - assert actual_schema == expected_schema - def test_get_invalid(): response = client.get("/foo") @@ -111,3 +37,81 @@ def test_get_valid(): response = client.get("/foo", params={"client_id": "bar"}) assert response.status_code == 200 assert response.json() == {"client_id": "bar_key", "client_tag": "bar_tag"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "title": "Detail", + "type": "array", + } + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "title": "Location", + "type": "array", + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + "required": ["loc", "msg", "type"], + "title": "ValidationError", + "type": "object", + }, + } + }, + "info": {"title": "FastAPI", "version": "0.1.0"}, + "openapi": "3.1.0", + "paths": { + "/foo": { + "get": { + "operationId": "foo_handler_foo_get", + "parameters": [ + { + "in": "query", + "name": "client_id", + "required": True, + "schema": {"title": "Client Id", "type": "string"}, + }, + ], + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error", + }, + }, + "summary": "Foo Handler", + } + } + }, + } + ) diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py index 251af4a59..96f85b446 100644 --- a/tests/test_extra_routes.py +++ b/tests/test_extra_routes.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -98,270 +99,274 @@ def test_trace(): 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": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Get Items", - "operationId": "get_items_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "delete": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Delete Item", - "operationId": "delete_item_items__item_id__delete", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + "summary": "Get Items", + "operationId": "get_items_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", } - }, - "required": True, + ], }, - }, - "options": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "delete": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Options Item", - "operationId": "options_item_items__item_id__options", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "head": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Head Item", - "operationId": "head_item_items__item_id__head", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "patch": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Patch Item", - "operationId": "patch_item_items__item_id__patch", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + "summary": "Delete Item", + "operationId": "delete_item_items__item_id__delete", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", } - }, - "required": True, - }, - }, - "trace": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + ], + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, }, - "summary": "Trace Item", - "operationId": "trace_item_items__item_id__trace", - "parameters": [ - { + "options": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Options Item", + "operationId": "options_item_items__item_id__options", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + "head": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Head Item", + "operationId": "head_item_items__item_id__head", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + "patch": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Patch Item", + "operationId": "patch_item_items__item_id__patch", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], + }, + }, + "trace": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Trace Item", + "operationId": "trace_item_items__item_id__trace", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + }, + "/items-not-decorated/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Get Not Decorated", + "operationId": "get_not_decorated_items_not_decorated__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } }, }, - "/items-not-decorated/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": { + "title": "Price", + "anyOf": [{"type": "number"}, {"type": "null"}], }, }, }, - "summary": "Get Not Decorated", - "operationId": "get_not_decorated_items_not_decorated__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, } }, - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": { - "title": "Price", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + } + ) diff --git a/tests/test_forms_single_param.py b/tests/test_forms_single_param.py index fc163cb1e..e759b006a 100644 --- a/tests/test_forms_single_param.py +++ b/tests/test_forms_single_param.py @@ -2,6 +2,7 @@ from typing import Annotated from fastapi import FastAPI, Form from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -23,80 +24,86 @@ def test_single_form_field(): 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": { - "/form/": { - "post": { - "summary": "Post Form", - "operationId": "post_form_form__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_post_form_form__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/form/": { + "post": { + "summary": "Post Form", + "operationId": "post_form_form__post", + "requestBody": { "content": { - "application/json": { + "application/x-www-form-urlencoded": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_post_form_form__post" } } }, + "required": True, }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "Body_post_form_form__post": { + "properties": { + "username": {"type": "string", "title": "Username"} + }, + "type": "object", + "required": ["username"], + "title": "Body_post_form_form__post", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } - } - }, - "components": { - "schemas": { - "Body_post_form_form__post": { - "properties": {"username": {"type": "string", "title": "Username"}}, - "type": "object", - "required": ["username"], - "title": "Body_post_form_form__post", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + }, + } + ) diff --git a/tests/test_generate_unique_id_function.py b/tests/test_generate_unique_id_function.py index 49510d08a..c56e6d579 100644 --- a/tests/test_generate_unique_id_function.py +++ b/tests/test_generate_unique_id_function.py @@ -3,6 +3,7 @@ import warnings from fastapi import APIRouter, FastAPI from fastapi.routing import APIRoute from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel @@ -45,190 +46,197 @@ def test_top_level_generate_unique_id(): app.include_router(router) client = TestClient(app) response = client.get("/openapi.json") - data = response.json() - assert data == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "post": { - "summary": "Post Root", - "operationId": "foo_post_root", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_foo_post_root" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "post": { + "summary": "Post Root", + "operationId": "foo_post_root", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_foo_post_root" + } } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Foo Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Foo Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/router": { + "post": { + "summary": "Post Router", + "operationId": "foo_post_router", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_foo_post_router" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Foo Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Foo Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_foo_post_root": { + "title": "Body_foo_post_root", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "Body_foo_post_router": { + "title": "Body_foo_post_router", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Foo Post Root", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Foo Post Root", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, + }, + "Message": { + "title": "Message", + "required": ["title", "description"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, }, } }, - "/router": { - "post": { - "summary": "Post Router", - "operationId": "foo_post_router", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_foo_post_router" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Foo Post Router", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Foo Post Router", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_foo_post_root": { - "title": "Body_foo_post_root", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "Body_foo_post_router": { - "title": "Body_foo_post_router", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "Message": { - "title": "Message", - "required": ["title", "description"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + } + ) def test_router_overrides_generate_unique_id(): @@ -248,190 +256,197 @@ def test_router_overrides_generate_unique_id(): app.include_router(router) client = TestClient(app) response = client.get("/openapi.json") - data = response.json() - assert data == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "post": { - "summary": "Post Root", - "operationId": "foo_post_root", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_foo_post_root" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "post": { + "summary": "Post Root", + "operationId": "foo_post_root", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_foo_post_root" + } } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Foo Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Foo Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/router": { + "post": { + "summary": "Post Router", + "operationId": "bar_post_router", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_bar_post_router" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Bar Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Bar Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_bar_post_router": { + "title": "Body_bar_post_router", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "Body_foo_post_root": { + "title": "Body_foo_post_root", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Foo Post Root", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Foo Post Root", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, + }, + "Message": { + "title": "Message", + "required": ["title", "description"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, }, } }, - "/router": { - "post": { - "summary": "Post Router", - "operationId": "bar_post_router", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_bar_post_router" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Bar Post Router", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Bar Post Router", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_bar_post_router": { - "title": "Body_bar_post_router", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "Body_foo_post_root": { - "title": "Body_foo_post_root", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "Message": { - "title": "Message", - "required": ["title", "description"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + } + ) def test_router_include_overrides_generate_unique_id(): @@ -451,190 +466,197 @@ def test_router_include_overrides_generate_unique_id(): app.include_router(router, generate_unique_id_function=custom_generate_unique_id3) client = TestClient(app) response = client.get("/openapi.json") - data = response.json() - assert data == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "post": { - "summary": "Post Root", - "operationId": "foo_post_root", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_foo_post_root" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "post": { + "summary": "Post Root", + "operationId": "foo_post_root", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_foo_post_root" + } } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Foo Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Foo Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/router": { + "post": { + "summary": "Post Router", + "operationId": "bar_post_router", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_bar_post_router" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Bar Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Bar Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_bar_post_router": { + "title": "Body_bar_post_router", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "Body_foo_post_root": { + "title": "Body_foo_post_root", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Foo Post Root", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Foo Post Root", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, + }, + "Message": { + "title": "Message", + "required": ["title", "description"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, }, } }, - "/router": { - "post": { - "summary": "Post Router", - "operationId": "bar_post_router", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_bar_post_router" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Bar Post Router", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Bar Post Router", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_bar_post_router": { - "title": "Body_bar_post_router", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "Body_foo_post_root": { - "title": "Body_foo_post_root", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "Message": { - "title": "Message", - "required": ["title", "description"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + } + ) def test_subrouter_top_level_include_overrides_generate_unique_id(): @@ -664,253 +686,262 @@ def test_subrouter_top_level_include_overrides_generate_unique_id(): app.include_router(router, generate_unique_id_function=custom_generate_unique_id3) client = TestClient(app) response = client.get("/openapi.json") - data = response.json() - assert data == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "post": { - "summary": "Post Root", - "operationId": "foo_post_root", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_foo_post_root" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "post": { + "summary": "Post Root", + "operationId": "foo_post_root", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_foo_post_root" + } } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Foo Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Foo Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/router": { + "post": { + "summary": "Post Router", + "operationId": "baz_post_router", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_baz_post_router" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Baz Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Baz Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/subrouter": { + "post": { + "summary": "Post Subrouter", + "operationId": "bar_post_subrouter", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_bar_post_subrouter" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Bar Post Subrouter", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Bar Post Subrouter", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_bar_post_subrouter": { + "title": "Body_bar_post_subrouter", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "Body_baz_post_router": { + "title": "Body_baz_post_router", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "Body_foo_post_root": { + "title": "Body_foo_post_root", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Foo Post Root", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Foo Post Root", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, + }, + "Message": { + "title": "Message", + "required": ["title", "description"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, }, } }, - "/router": { - "post": { - "summary": "Post Router", - "operationId": "baz_post_router", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_baz_post_router" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Baz Post Router", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Baz Post Router", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/subrouter": { - "post": { - "summary": "Post Subrouter", - "operationId": "bar_post_subrouter", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_bar_post_subrouter" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Bar Post Subrouter", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Bar Post Subrouter", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_bar_post_subrouter": { - "title": "Body_bar_post_subrouter", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "Body_baz_post_router": { - "title": "Body_baz_post_router", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "Body_foo_post_root": { - "title": "Body_foo_post_root", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "Message": { - "title": "Message", - "required": ["title", "description"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + } + ) def test_router_path_operation_overrides_generate_unique_id(): @@ -933,190 +964,197 @@ def test_router_path_operation_overrides_generate_unique_id(): app.include_router(router) client = TestClient(app) response = client.get("/openapi.json") - data = response.json() - assert data == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "post": { - "summary": "Post Root", - "operationId": "foo_post_root", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_foo_post_root" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "post": { + "summary": "Post Root", + "operationId": "foo_post_root", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_foo_post_root" + } } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Foo Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Foo Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/router": { + "post": { + "summary": "Post Router", + "operationId": "baz_post_router", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_baz_post_router" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Baz Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Baz Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_baz_post_router": { + "title": "Body_baz_post_router", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "Body_foo_post_root": { + "title": "Body_foo_post_root", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Foo Post Root", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Foo Post Root", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, + }, + "Message": { + "title": "Message", + "required": ["title", "description"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, }, } }, - "/router": { - "post": { - "summary": "Post Router", - "operationId": "baz_post_router", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_baz_post_router" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Baz Post Router", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Baz Post Router", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_baz_post_router": { - "title": "Body_baz_post_router", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "Body_foo_post_root": { - "title": "Body_foo_post_root", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "Message": { - "title": "Message", - "required": ["title", "description"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + } + ) def test_app_path_operation_overrides_generate_unique_id(): @@ -1143,190 +1181,197 @@ def test_app_path_operation_overrides_generate_unique_id(): app.include_router(router) client = TestClient(app) response = client.get("/openapi.json") - data = response.json() - assert data == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "post": { - "summary": "Post Root", - "operationId": "baz_post_root", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_baz_post_root" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "post": { + "summary": "Post Root", + "operationId": "baz_post_root", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_baz_post_root" + } } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Baz Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Baz Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/router": { + "post": { + "summary": "Post Router", + "operationId": "bar_post_router", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_bar_post_router" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Bar Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Bar Post Router", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_bar_post_router": { + "title": "Body_bar_post_router", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "Body_baz_post_root": { + "title": "Body_baz_post_root", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Baz Post Root", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Baz Post Root", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, + }, + "Message": { + "title": "Message", + "required": ["title", "description"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, }, } }, - "/router": { - "post": { - "summary": "Post Router", - "operationId": "bar_post_router", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_bar_post_router" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Bar Post Router", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Bar Post Router", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_bar_post_router": { - "title": "Body_bar_post_router", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "Body_baz_post_root": { - "title": "Body_baz_post_root", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "Message": { - "title": "Message", - "required": ["title", "description"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + } + ) def test_callback_override_generate_unique_id(): @@ -1362,259 +1407,266 @@ def test_callback_override_generate_unique_id(): client = TestClient(app) response = client.get("/openapi.json") - data = response.json() - assert data == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "post": { - "summary": "Post Root", - "operationId": "baz_post_root", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_baz_post_root" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "post": { + "summary": "Post Root", + "operationId": "baz_post_root", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_baz_post_root" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Baz Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Baz Post Root", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "callbacks": { + "post_callback": { + "/post-callback": { + "post": { + "summary": "Post Callback", + "operationId": "baz_post_callback", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_baz_post_callback" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Baz Post Callback", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } + } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Baz Post Callback", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } } } }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", + } + }, + "/tocallback": { + "post": { + "summary": "Post With Callback", + "operationId": "foo_post_with_callback", + "requestBody": { "content": { "application/json": { "schema": { - "title": "Response Baz Post Root", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, + "$ref": "#/components/schemas/Body_foo_post_with_callback" } } }, + "required": True, }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Baz Post Root", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Foo Post With Callback", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } } - } + }, + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "title": "Response 404 Foo Post With Callback", + "type": "array", + "items": { + "$ref": "#/components/schemas/Message" + }, + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "callbacks": { - "post_callback": { - "/post-callback": { - "post": { - "summary": "Post Callback", - "operationId": "baz_post_callback", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_baz_post_callback" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Baz Post Callback", - "type": "array", - "items": { - "$ref": "#/components/schemas/Item" - }, - } - } - }, - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Baz Post Callback", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - } - }, - } + } + }, }, - "/tocallback": { - "post": { - "summary": "Post With Callback", - "operationId": "foo_post_with_callback", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_foo_post_with_callback" - } + "components": { + "schemas": { + "Body_baz_post_callback": { + "title": "Body_baz_post_callback", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "Body_baz_post_root": { + "title": "Body_baz_post_root", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "Body_foo_post_with_callback": { + "title": "Body_foo_post_with_callback", + "required": ["item1", "item2"], + "type": "object", + "properties": { + "item1": {"$ref": "#/components/schemas/Item"}, + "item2": {"$ref": "#/components/schemas/Item"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Foo Post With Callback", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "title": "Response 404 Foo Post With Callback", - "type": "array", - "items": { - "$ref": "#/components/schemas/Message" - }, - } - } - }, + }, + "Message": { + "title": "Message", + "required": ["title", "description"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "description": {"title": "Description", "type": "string"}, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, }, } }, - }, - "components": { - "schemas": { - "Body_baz_post_callback": { - "title": "Body_baz_post_callback", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "Body_baz_post_root": { - "title": "Body_baz_post_root", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "Body_foo_post_with_callback": { - "title": "Body_foo_post_with_callback", - "required": ["item1", "item2"], - "type": "object", - "properties": { - "item1": {"$ref": "#/components/schemas/Item"}, - "item2": {"$ref": "#/components/schemas/Item"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "Message": { - "title": "Message", - "required": ["title", "description"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + } + ) def test_warn_duplicate_operation_id(): diff --git a/tests/test_generic_parameterless_depends.py b/tests/test_generic_parameterless_depends.py index 93b72ad24..96c7a7126 100644 --- a/tests/test_generic_parameterless_depends.py +++ b/tests/test_generic_parameterless_depends.py @@ -2,6 +2,7 @@ from typing import Annotated, TypeVar from fastapi import Depends, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -44,33 +45,35 @@ def test_generic_parameterless_depends(): def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == { - "info": {"title": "FastAPI", "version": "0.1.0"}, - "openapi": "3.1.0", - "paths": { - "/a": { - "get": { - "operationId": "a_a_get", - "responses": { - "200": { - "content": {"application/json": {"schema": {}}}, - "description": "Successful Response", - } - }, - "summary": "A", - } + assert response.json() == snapshot( + { + "info": {"title": "FastAPI", "version": "0.1.0"}, + "openapi": "3.1.0", + "paths": { + "/a": { + "get": { + "operationId": "a_a_get", + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful Response", + } + }, + "summary": "A", + } + }, + "/b": { + "get": { + "operationId": "b_b_get", + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful Response", + } + }, + "summary": "B", + } + }, }, - "/b": { - "get": { - "operationId": "b_b_get", - "responses": { - "200": { - "content": {"application/json": {"schema": {}}}, - "description": "Successful Response", - } - }, - "summary": "B", - } - }, - }, - } + } + ) diff --git a/tests/test_get_request_body.py b/tests/test_get_request_body.py index b21889e30..1b0f4970b 100644 --- a/tests/test_get_request_body.py +++ b/tests/test_get_request_body.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -28,82 +29,86 @@ def test_get_with_body(): 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": { - "/product": { - "get": { - "summary": "Create Item", - "operationId": "create_item_product_get", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Product"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/product": { + "get": { + "summary": "Create Item", + "operationId": "create_item_product_get", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Product"} } }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "Product": { + "title": "Product", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Product": { - "title": "Product", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_include_router_defaults_overrides.py b/tests/test_include_router_defaults_overrides.py index 6d2ffc44a..e8303c821 100644 --- a/tests/test_include_router_defaults_overrides.py +++ b/tests/test_include_router_defaults_overrides.py @@ -4,6 +4,7 @@ import pytest from fastapi import APIRouter, Depends, FastAPI, Response from fastapi.responses import JSONResponse from fastapi.testclient import TestClient +from inline_snapshot import snapshot class ResponseLevel0(JSONResponse): @@ -442,6858 +443,6862 @@ def test_openapi(): response = client.get("/openapi.json") assert issubclass(w[-1].category, UserWarning) assert "Duplicate Operation ID" in str(w[-1].message) - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/override1": { - "get": { - "tags": ["path1a", "path1b"], - "summary": "Path1 Override", - "operationId": "path1_override_override1_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level1", "type": "string"}, - "name": "level1", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-1": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/override1": { + "get": { + "tags": ["path1a", "path1b"], + "summary": "Path1 Override", + "operationId": "path1_override_override1_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level1", "type": "string"}, + "name": "level1", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-1": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, } } }, }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/default1": { - "get": { - "summary": "Path1 Default", - "operationId": "path1_default_default1_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level1", "type": "string"}, - "name": "level1", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-0": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - } - }, - } - }, - "/level1/level2/override3": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "path3a", - "path3b", - ], - "summary": "Path3 Override Router2 Override", - "operationId": "path3_override_router2_override_level1_level2_override3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/default3": { - "get": { - "tags": ["level1a", "level1b", "level2a", "level2b"], - "summary": "Path3 Default Router2 Override", - "operationId": "path3_default_router2_override_level1_level2_default3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-2": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level3/level4/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level3a", - "level3b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level1_level2_level3_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level3/level4/default5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level3a", - "level3b", - "level4a", - "level4b", - ], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level1_level2_level3_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level3/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level3a", - "level3b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level1_level2_level3_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level3/default5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level3a", - "level3b", - ], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level1_level2_level3_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level4/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level1_level2_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/level4/default5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "level4a", - "level4b", - ], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level1_level2_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level2a", - "level2b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level1_level2_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level2/default5": { - "get": { - "tags": ["level1a", "level1b", "level2a", "level2b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level1_level2_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-2": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "402": {"description": "Client error level 2"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "502": {"description": "Server error level 2"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/override3": { - "get": { - "tags": ["level1a", "level1b", "path3a", "path3b"], - "summary": "Path3 Override Router2 Default", - "operationId": "path3_override_router2_default_level1_override3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/default3": { - "get": { - "tags": ["level1a", "level1b"], - "summary": "Path3 Default Router2 Default", - "operationId": "path3_default_router2_default_level1_default3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-1": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - } - }, - "/level1/level3/level4/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level3a", - "level3b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level1_level3_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level3/level4/default5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level3a", - "level3b", - "level4a", - "level4b", - ], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level1_level3_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level3/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level3a", - "level3b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level1_level3_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "403": {"description": "Client error level 3"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "503": {"description": "Server error level 3"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level3/default5": { - "get": { - "tags": ["level1a", "level1b", "level3a", "level3b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level1_level3_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - } - }, - "/level1/level4/override5": { - "get": { - "tags": [ - "level1a", - "level1b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level1_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/level4/default5": { - "get": { - "tags": ["level1a", "level1b", "level4a", "level4b"], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level1_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/override5": { - "get": { - "tags": ["level1a", "level1b", "path5a", "path5b"], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level1_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level1/default5": { - "get": { - "tags": ["level1a", "level1b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level1_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-1": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "401": {"description": "Client error level 1"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "501": {"description": "Server error level 1"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback1": { - "/": { - "get": { - "summary": "Callback1", - "operationId": "callback1__get", - "parameters": [ - { - "name": "level1", - "in": "query", - "required": True, - "schema": { - "title": "Level1", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - } - }, - "/level2/override3": { - "get": { - "tags": ["level2a", "level2b", "path3a", "path3b"], - "summary": "Path3 Override Router2 Override", - "operationId": "path3_override_router2_override_level2_override3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/default3": { - "get": { - "tags": ["level2a", "level2b"], - "summary": "Path3 Default Router2 Override", - "operationId": "path3_default_router2_override_level2_default3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-2": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level3/level4/override5": { - "get": { - "tags": [ - "level2a", - "level2b", - "level3a", - "level3b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level2_level3_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level3/level4/default5": { - "get": { - "tags": [ - "level2a", - "level2b", - "level3a", - "level3b", - "level4a", - "level4b", - ], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level2_level3_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level3/override5": { - "get": { - "tags": [ - "level2a", - "level2b", - "level3a", - "level3b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level2_level3_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level3/default5": { - "get": { - "tags": ["level2a", "level2b", "level3a", "level3b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level2_level3_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level4/override5": { - "get": { - "tags": [ - "level2a", - "level2b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level2_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/level4/default5": { - "get": { - "tags": ["level2a", "level2b", "level4a", "level4b"], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level2_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/override5": { - "get": { - "tags": ["level2a", "level2b", "path5a", "path5b"], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level2_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level2/default5": { - "get": { - "tags": ["level2a", "level2b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level2_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-2": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "402": {"description": "Client error level 2"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "502": {"description": "Server error level 2"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback2": { - "/": { - "get": { - "summary": "Callback2", - "operationId": "callback2__get", - "parameters": [ - { - "name": "level2", - "in": "query", - "required": True, - "schema": { - "title": "Level2", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/override3": { - "get": { - "tags": ["path3a", "path3b"], - "summary": "Path3 Override Router2 Default", - "operationId": "path3_override_router2_default_override3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/default3": { - "get": { - "summary": "Path3 Default Router2 Default", - "operationId": "path3_default_router2_default_default3_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level3", "type": "string"}, - "name": "level3", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-0": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - } - }, - } - }, - "/level3/level4/override5": { - "get": { - "tags": [ - "level3a", - "level3b", - "level4a", - "level4b", - "path5a", - "path5b", - ], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level3_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level3/level4/default5": { - "get": { - "tags": ["level3a", "level3b", "level4a", "level4b"], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level3_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "403": {"description": "Client error level 3"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "503": {"description": "Server error level 3"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level3/override5": { - "get": { - "tags": ["level3a", "level3b", "path5a", "path5b"], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_level3_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "403": {"description": "Client error level 3"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "503": {"description": "Server error level 3"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level3/default5": { - "get": { - "tags": ["level3a", "level3b"], - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_level3_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-3": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "403": {"description": "Client error level 3"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "503": {"description": "Server error level 3"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback3": { - "/": { - "get": { - "summary": "Callback3", - "operationId": "callback3__get", - "parameters": [ - { - "name": "level3", - "in": "query", - "required": True, - "schema": { - "title": "Level3", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - } - }, - "/level4/override5": { - "get": { - "tags": ["level4a", "level4b", "path5a", "path5b"], - "summary": "Path5 Override Router4 Override", - "operationId": "path5_override_router4_override_level4_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "404": {"description": "Client error level 4"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "504": {"description": "Server error level 4"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/level4/default5": { - "get": { - "tags": ["level4a", "level4b"], - "summary": "Path5 Default Router4 Override", - "operationId": "path5_default_router4_override_level4_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-4": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "404": {"description": "Client error level 4"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "504": {"description": "Server error level 4"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback4": { - "/": { - "get": { - "summary": "Callback4", - "operationId": "callback4__get", - "parameters": [ - { - "name": "level4", - "in": "query", - "required": True, - "schema": { - "title": "Level4", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/override5": { - "get": { - "tags": ["path5a", "path5b"], - "summary": "Path5 Override Router4 Default", - "operationId": "path5_override_router4_default_override5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-5": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "405": {"description": "Client error level 5"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - "505": {"description": "Server error level 5"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - "callback5": { - "/": { - "get": { - "summary": "Callback5", - "operationId": "callback5__get", - "parameters": [ - { - "name": "level5", - "in": "query", - "required": True, - "schema": { - "title": "Level5", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - }, - }, - "deprecated": True, - } - }, - "/default5": { - "get": { - "summary": "Path5 Default Router4 Default", - "operationId": "path5_default_router4_default_default5_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Level5", "type": "string"}, - "name": "level5", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/x-level-0": {"schema": {}}}, - }, - "400": {"description": "Client error level 0"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - "500": {"description": "Server error level 0"}, - }, - "callbacks": { - "callback0": { - "/": { - "get": { - "summary": "Callback0", - "operationId": "callback0__get", - "parameters": [ - { - "name": "level0", - "in": "query", - "required": True, - "schema": { - "title": "Level0", - "type": "string", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - } - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, + "deprecated": True, + } }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "/default1": { + "get": { + "summary": "Path1 Default", + "operationId": "path1_default_default1_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level1", "type": "string"}, + "name": "level1", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-0": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + } + }, + } + }, + "/level1/level2/override3": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "path3a", + "path3b", + ], + "summary": "Path3 Override Router2 Override", + "operationId": "path3_override_router2_override_level1_level2_override3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, + "deprecated": True, + } }, - } - }, - } + "/level1/level2/default3": { + "get": { + "tags": ["level1a", "level1b", "level2a", "level2b"], + "summary": "Path3 Default Router2 Override", + "operationId": "path3_default_router2_override_level1_level2_default3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-2": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level3/level4/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level3a", + "level3b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level1_level2_level3_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level3/level4/default5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level3a", + "level3b", + "level4a", + "level4b", + ], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level1_level2_level3_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level3/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level3a", + "level3b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level1_level2_level3_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level3/default5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level3a", + "level3b", + ], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level1_level2_level3_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level4/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level1_level2_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/level4/default5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "level4a", + "level4b", + ], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level1_level2_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level2a", + "level2b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level1_level2_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level2/default5": { + "get": { + "tags": ["level1a", "level1b", "level2a", "level2b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level1_level2_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-2": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "402": {"description": "Client error level 2"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "502": {"description": "Server error level 2"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/override3": { + "get": { + "tags": ["level1a", "level1b", "path3a", "path3b"], + "summary": "Path3 Override Router2 Default", + "operationId": "path3_override_router2_default_level1_override3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/default3": { + "get": { + "tags": ["level1a", "level1b"], + "summary": "Path3 Default Router2 Default", + "operationId": "path3_default_router2_default_level1_default3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-1": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + } + }, + "/level1/level3/level4/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level3a", + "level3b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level1_level3_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level3/level4/default5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level3a", + "level3b", + "level4a", + "level4b", + ], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level1_level3_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level3/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level3a", + "level3b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level1_level3_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "403": {"description": "Client error level 3"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "503": {"description": "Server error level 3"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level3/default5": { + "get": { + "tags": ["level1a", "level1b", "level3a", "level3b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level1_level3_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + } + }, + "/level1/level4/override5": { + "get": { + "tags": [ + "level1a", + "level1b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level1_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/level4/default5": { + "get": { + "tags": ["level1a", "level1b", "level4a", "level4b"], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level1_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/override5": { + "get": { + "tags": ["level1a", "level1b", "path5a", "path5b"], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level1_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level1/default5": { + "get": { + "tags": ["level1a", "level1b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level1_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-1": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "401": {"description": "Client error level 1"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "501": {"description": "Server error level 1"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback1": { + "/": { + "get": { + "summary": "Callback1", + "operationId": "callback1__get", + "parameters": [ + { + "name": "level1", + "in": "query", + "required": True, + "schema": { + "title": "Level1", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + } + }, + "/level2/override3": { + "get": { + "tags": ["level2a", "level2b", "path3a", "path3b"], + "summary": "Path3 Override Router2 Override", + "operationId": "path3_override_router2_override_level2_override3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/default3": { + "get": { + "tags": ["level2a", "level2b"], + "summary": "Path3 Default Router2 Override", + "operationId": "path3_default_router2_override_level2_default3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-2": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level3/level4/override5": { + "get": { + "tags": [ + "level2a", + "level2b", + "level3a", + "level3b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level2_level3_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level3/level4/default5": { + "get": { + "tags": [ + "level2a", + "level2b", + "level3a", + "level3b", + "level4a", + "level4b", + ], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level2_level3_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level3/override5": { + "get": { + "tags": [ + "level2a", + "level2b", + "level3a", + "level3b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level2_level3_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level3/default5": { + "get": { + "tags": ["level2a", "level2b", "level3a", "level3b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level2_level3_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level4/override5": { + "get": { + "tags": [ + "level2a", + "level2b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level2_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/level4/default5": { + "get": { + "tags": ["level2a", "level2b", "level4a", "level4b"], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level2_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/override5": { + "get": { + "tags": ["level2a", "level2b", "path5a", "path5b"], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level2_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level2/default5": { + "get": { + "tags": ["level2a", "level2b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level2_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-2": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "402": {"description": "Client error level 2"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "502": {"description": "Server error level 2"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback2": { + "/": { + "get": { + "summary": "Callback2", + "operationId": "callback2__get", + "parameters": [ + { + "name": "level2", + "in": "query", + "required": True, + "schema": { + "title": "Level2", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/override3": { + "get": { + "tags": ["path3a", "path3b"], + "summary": "Path3 Override Router2 Default", + "operationId": "path3_override_router2_default_override3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/default3": { + "get": { + "summary": "Path3 Default Router2 Default", + "operationId": "path3_default_router2_default_default3_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level3", "type": "string"}, + "name": "level3", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-0": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + } + }, + } + }, + "/level3/level4/override5": { + "get": { + "tags": [ + "level3a", + "level3b", + "level4a", + "level4b", + "path5a", + "path5b", + ], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level3_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level3/level4/default5": { + "get": { + "tags": ["level3a", "level3b", "level4a", "level4b"], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level3_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "403": {"description": "Client error level 3"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "503": {"description": "Server error level 3"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level3/override5": { + "get": { + "tags": ["level3a", "level3b", "path5a", "path5b"], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_level3_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "403": {"description": "Client error level 3"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "503": {"description": "Server error level 3"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level3/default5": { + "get": { + "tags": ["level3a", "level3b"], + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_level3_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-3": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "403": {"description": "Client error level 3"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "503": {"description": "Server error level 3"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback3": { + "/": { + "get": { + "summary": "Callback3", + "operationId": "callback3__get", + "parameters": [ + { + "name": "level3", + "in": "query", + "required": True, + "schema": { + "title": "Level3", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + } + }, + "/level4/override5": { + "get": { + "tags": ["level4a", "level4b", "path5a", "path5b"], + "summary": "Path5 Override Router4 Override", + "operationId": "path5_override_router4_override_level4_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "404": {"description": "Client error level 4"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "504": {"description": "Server error level 4"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/level4/default5": { + "get": { + "tags": ["level4a", "level4b"], + "summary": "Path5 Default Router4 Override", + "operationId": "path5_default_router4_override_level4_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-4": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "404": {"description": "Client error level 4"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "504": {"description": "Server error level 4"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback4": { + "/": { + "get": { + "summary": "Callback4", + "operationId": "callback4__get", + "parameters": [ + { + "name": "level4", + "in": "query", + "required": True, + "schema": { + "title": "Level4", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/override5": { + "get": { + "tags": ["path5a", "path5b"], + "summary": "Path5 Override Router4 Default", + "operationId": "path5_override_router4_default_override5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-5": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "405": {"description": "Client error level 5"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + "505": {"description": "Server error level 5"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "callback5": { + "/": { + "get": { + "summary": "Callback5", + "operationId": "callback5__get", + "parameters": [ + { + "name": "level5", + "in": "query", + "required": True, + "schema": { + "title": "Level5", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + }, + "deprecated": True, + } + }, + "/default5": { + "get": { + "summary": "Path5 Default Router4 Default", + "operationId": "path5_default_router4_default_default5_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Level5", "type": "string"}, + "name": "level5", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/x-level-0": {"schema": {}}}, + }, + "400": {"description": "Client error level 0"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + "500": {"description": "Server error level 0"}, + }, + "callbacks": { + "callback0": { + "/": { + "get": { + "summary": "Callback0", + "operationId": "callback0__get", + "parameters": [ + { + "name": "level0", + "in": "query", + "required": True, + "schema": { + "title": "Level0", + "type": "string", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": {"schema": {}} + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + } + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + } + }, + } + ) diff --git a/tests/test_infer_param_optionality.py b/tests/test_infer_param_optionality.py index b11a1ca43..bb20a4a1a 100644 --- a/tests/test_infer_param_optionality.py +++ b/tests/test_infer_param_optionality.py @@ -2,6 +2,7 @@ from typing import Optional from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -107,228 +108,232 @@ def test_get_users_item(): 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": { - "/users/": { - "get": { - "summary": "Get Users", - "operationId": "get_users_users__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/users/{user_id}": { - "get": { - "summary": "Get User", - "operationId": "get_user_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "string"}, - "name": "user_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "summary": "Get Users", + "operationId": "get_users_users__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/items/": { - "get": { - "summary": "Get Items", - "operationId": "get_items_items__get", - "parameters": [ - { - "required": False, - "name": "user_id", - "in": "query", - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User Id", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/items/{item_id}": { - "get": { - "summary": "Get Item", - "operationId": "get_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "name": "user_id", - "in": "query", - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User Id", - }, - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/users/{user_id}/items/": { - "get": { - "summary": "Get Items", - "operationId": "get_items_users__user_id__items__get", - "parameters": [ - { - "required": True, - "name": "user_id", - "in": "path", - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User Id", - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/users/{user_id}/items/{item_id}": { - "get": { - "summary": "Get Item", - "operationId": "get_item_users__user_id__items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "name": "user_id", - "in": "path", - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User Id", - }, - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, + } }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "/users/{user_id}": { + "get": { + "summary": "Get User", + "operationId": "get_user_users__user_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "string"}, + "name": "user_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, + } }, - } - }, - } + "/items/": { + "get": { + "summary": "Get Items", + "operationId": "get_items_items__get", + "parameters": [ + { + "required": False, + "name": "user_id", + "in": "query", + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "User Id", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/{item_id}": { + "get": { + "summary": "Get Item", + "operationId": "get_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "name": "user_id", + "in": "query", + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "User Id", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/{user_id}/items/": { + "get": { + "summary": "Get Items", + "operationId": "get_items_users__user_id__items__get", + "parameters": [ + { + "required": True, + "name": "user_id", + "in": "path", + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "User Id", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/{user_id}/items/{item_id}": { + "get": { + "summary": "Get Item", + "operationId": "get_item_users__user_id__items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "name": "user_id", + "in": "path", + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "User Id", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + } + }, + } + ) diff --git a/tests/test_modules_same_name_body/test_main.py b/tests/test_modules_same_name_body/test_main.py index 276de539d..72707993e 100644 --- a/tests/test_modules_same_name_body/test_main.py +++ b/tests/test_modules_same_name_body/test_main.py @@ -1,5 +1,6 @@ import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from .app.main import app @@ -28,124 +29,128 @@ def test_post_invalid(path): 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": { - "/a/compute": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a/compute": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Compute", + "operationId": "compute_a_compute_post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_compute_a_compute_post" } } }, + "required": True, }, - }, - "summary": "Compute", - "operationId": "compute_a_compute_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_compute_a_compute_post" - } - } + } + }, + "/b/compute/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "required": True, - }, - } - }, - "/b/compute/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "summary": "Compute", + "operationId": "compute_b_compute__post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_compute_b_compute__post" } } }, + "required": True, + }, + } + }, + }, + "components": { + "schemas": { + "Body_compute_b_compute__post": { + "title": "Body_compute_b_compute__post", + "required": ["a", "b"], + "type": "object", + "properties": { + "a": {"title": "A", "type": "integer"}, + "b": {"title": "B", "type": "string"}, }, }, - "summary": "Compute", - "operationId": "compute_b_compute__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_compute_b_compute__post" - } + "Body_compute_a_compute_post": { + "title": "Body_compute_a_compute_post", + "required": ["a", "b"], + "type": "object", + "properties": { + "a": {"title": "A", "type": "integer"}, + "b": {"title": "B", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } }, - }, - "components": { - "schemas": { - "Body_compute_b_compute__post": { - "title": "Body_compute_b_compute__post", - "required": ["a", "b"], - "type": "object", - "properties": { - "a": {"title": "A", "type": "integer"}, - "b": {"title": "B", "type": "string"}, - }, - }, - "Body_compute_a_compute_post": { - "title": "Body_compute_a_compute_post", - "required": ["a", "b"], - "type": "object", - "properties": { - "a": {"title": "A", "type": "integer"}, - "b": {"title": "B", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + } + ) diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py index fa3e0c635..792471b5c 100644 --- a/tests/test_multi_body_errors.py +++ b/tests/test_multi_body_errors.py @@ -89,99 +89,103 @@ def test_put_incorrect_body_multiple(): 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": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Save Item No Body", + "operationId": "save_item_no_body_items__post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "title": "Item", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, } } }, + "required": True, }, - }, - "summary": "Save Item No Body", - "operationId": "save_item_no_body_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Item", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "age"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "age": { - "title": "Age", - "anyOf": [ - {"exclusiveMinimum": 0.0, "type": "number"}, - IsOneOf( - # pydantic < 2.12.0 - {"type": "string"}, - # pydantic >= 2.12.0 - { - "type": "string", - "pattern": r"^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$", - }, - ), - ], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "age"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "age": { + "title": "Age", + "anyOf": [ + {"exclusiveMinimum": 0.0, "type": "number"}, + IsOneOf( + # pydantic < 2.12.0 + {"type": "string"}, + # pydantic >= 2.12.0 + { + "type": "string", + "pattern": r"^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$", + }, + ), + ], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - }, - } - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_multi_query_errors.py b/tests/test_multi_query_errors.py index 7540367a6..060951efa 100644 --- a/tests/test_multi_query_errors.py +++ b/tests/test_multi_query_errors.py @@ -1,5 +1,6 @@ from fastapi import FastAPI, Query from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -42,76 +43,80 @@ def test_multi_query_incorrect(): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "integer"}, - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "integer"}, + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_openapi_examples.py b/tests/test_openapi_examples.py index 93e5b366f..deb74d8a0 100644 --- a/tests/test_openapi_examples.py +++ b/tests/test_openapi_examples.py @@ -2,6 +2,7 @@ from typing import Union from fastapi import Body, Cookie, FastAPI, Header, Path, Query from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -143,277 +144,281 @@ def test_call_api(): 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": { - "/examples/": { - "post": { - "summary": "Examples", - "operationId": "examples_examples__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - "examples": [ - {"data": "Data in Body examples, example1"} - ], - }, - "examples": { - "Example One": { - "summary": "Example One Summary", - "description": "Example One Description", - "value": { - "data": "Data in Body examples, example1" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/examples/": { + "post": { + "summary": "Examples", + "operationId": "examples_examples__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item", + "examples": [ + {"data": "Data in Body examples, example1"} + ], + }, + "examples": { + "Example One": { + "summary": "Example One Summary", + "description": "Example One Description", + "value": { + "data": "Data in Body examples, example1" + }, + }, + "Example Two": { + "value": { + "data": "Data in Body examples, example2" + } }, }, - "Example Two": { - "value": { - "data": "Data in Body examples, example2" + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" } + } + }, + }, + }, + } + }, + "/path_examples/{item_id}": { + "get": { + "summary": "Path Examples", + "operationId": "path_examples_path_examples__item_id__get", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": { + "type": "string", + "examples": [ + "json_schema_item_1", + "json_schema_item_2", + ], + "title": "Item Id", + }, + "examples": { + "Path One": { + "summary": "Path One Summary", + "description": "Path One Description", + "value": "item_1", }, + "Path Two": {"value": "item_2"}, }, } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } - }, - }, - }, - } - }, - "/path_examples/{item_id}": { - "get": { - "summary": "Path Examples", - "operationId": "path_examples_path_examples__item_id__get", - "parameters": [ - { - "name": "item_id", - "in": "path", - "required": True, - "schema": { - "type": "string", - "examples": [ - "json_schema_item_1", - "json_schema_item_2", - ], - "title": "Item Id", - }, - "examples": { - "Path One": { - "summary": "Path One Summary", - "description": "Path One Description", - "value": "item_1", }, - "Path Two": {"value": "item_2"}, - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } }, }, - }, - } - }, - "/query_examples/": { - "get": { - "summary": "Query Examples", - "operationId": "query_examples_query_examples__get", - "parameters": [ - { - "name": "data", - "in": "query", - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "examples": [ - "json_schema_query1", - "json_schema_query2", - ], - "title": "Data", - }, - "examples": { - "Query One": { - "summary": "Query One Summary", - "description": "Query One Description", - "value": "query1", - }, - "Query Two": {"value": "query2"}, - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/header_examples/": { - "get": { - "summary": "Header Examples", - "operationId": "header_examples_header_examples__get", - "parameters": [ - { - "name": "data", - "in": "header", - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "examples": [ - "json_schema_header1", - "json_schema_header2", - ], - "title": "Data", - }, - "examples": { - "Header One": { - "summary": "Header One Summary", - "description": "Header One Description", - "value": "header1", - }, - "Header Two": {"value": "header2"}, - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/cookie_examples/": { - "get": { - "summary": "Cookie Examples", - "operationId": "cookie_examples_cookie_examples__get", - "parameters": [ - { - "name": "data", - "in": "cookie", - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "examples": [ - "json_schema_cookie1", - "json_schema_cookie2", - ], - "title": "Data", - }, - "examples": { - "Cookie One": { - "summary": "Cookie One Summary", - "description": "Cookie One Description", - "value": "cookie1", - }, - "Cookie Two": {"value": "cookie2"}, - }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", + } }, - "Item": { - "properties": {"data": {"type": "string", "title": "Data"}}, - "type": "object", - "required": ["data"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "/query_examples/": { + "get": { + "summary": "Query Examples", + "operationId": "query_examples_query_examples__get", + "parameters": [ + { + "name": "data", + "in": "query", + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "examples": [ + "json_schema_query1", + "json_schema_query2", + ], + "title": "Data", + }, + "examples": { + "Query One": { + "summary": "Query One Summary", + "description": "Query One Description", + "value": "query1", + }, + "Query Two": {"value": "query2"}, + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, - "type": "array", - "title": "Location", }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", + } }, - } - }, - } + "/header_examples/": { + "get": { + "summary": "Header Examples", + "operationId": "header_examples_header_examples__get", + "parameters": [ + { + "name": "data", + "in": "header", + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "examples": [ + "json_schema_header1", + "json_schema_header2", + ], + "title": "Data", + }, + "examples": { + "Header One": { + "summary": "Header One Summary", + "description": "Header One Description", + "value": "header1", + }, + "Header Two": {"value": "header2"}, + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/cookie_examples/": { + "get": { + "summary": "Cookie Examples", + "operationId": "cookie_examples_cookie_examples__get", + "parameters": [ + { + "name": "data", + "in": "cookie", + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "examples": [ + "json_schema_cookie1", + "json_schema_cookie2", + ], + "title": "Data", + }, + "examples": { + "Cookie One": { + "summary": "Cookie One Summary", + "description": "Cookie One Description", + "value": "cookie1", + }, + "Cookie Two": {"value": "cookie2"}, + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": {"data": {"type": "string", "title": "Data"}}, + "type": "object", + "required": ["data"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_openapi_query_parameter_extension.py b/tests/test_openapi_query_parameter_extension.py index b6c3c3d8d..836a0a7ee 100644 --- a/tests/test_openapi_query_parameter_extension.py +++ b/tests/test_openapi_query_parameter_extension.py @@ -2,6 +2,7 @@ from typing import Optional from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -41,88 +42,92 @@ def test_get_route(): def test_openapi(): 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": { - "/": { - "get": { - "summary": "Route With Extra Query Parameters", - "operationId": "route_with_extra_query_parameters__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "default": 50, - "title": "Standard Query Param", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Route With Extra Query Parameters", + "operationId": "route_with_extra_query_parameters__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "default": 50, + "title": "Standard Query Param", + }, + "name": "standard_query_param", + "in": "query", }, - "name": "standard_query_param", - "in": "query", - }, - { - "required": False, - "schema": {"title": "Extra Param 1"}, - "name": "extra_param_1", - "in": "query", - }, - { - "required": True, - "schema": {"title": "Extra Param 2"}, - "name": "extra_param_2", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + { + "required": False, + "schema": {"title": "Extra Param 1"}, + "name": "extra_param_1", + "in": "query", + }, + { + "required": True, + "schema": {"title": "Extra Param 2"}, + "name": "extra_param_2", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_openapi_route_extensions.py b/tests/test_openapi_route_extensions.py index 3a3099436..fc11b69c8 100644 --- a/tests/test_openapi_route_extensions.py +++ b/tests/test_openapi_route_extensions.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -18,25 +19,27 @@ def test_get_route(): assert response.json() == {} -def test_openapi(): +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": { - "/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, }, - }, - "summary": "Route With Extras", - "operationId": "route_with_extras__get", - "x-custom-extension": "value", - } + "summary": "Route With Extras", + "operationId": "route_with_extras__get", + "x-custom-extension": "value", + } + }, }, - }, - } + } + ) diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py index f941e323b..0efeece01 100644 --- a/tests/test_openapi_separate_input_output_schemas.py +++ b/tests/test_openapi_separate_input_output_schemas.py @@ -444,229 +444,235 @@ def test_openapi_schema_no_separate(): client = get_app_client(separate_input_output_schemas=False) 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": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item" + }, + "type": "array", + "title": "Response Read Items Items Get", + } + } + }, + } + }, + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "402": { + "description": "Payment Required", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/items-list/": { + "post": { + "summary": "Create Item List", + "operationId": "create_item_list_items_list__post", + "requestBody": { "content": { "application/json": { "schema": { "items": {"$ref": "#/components/schemas/Item"}, "type": "array", - "title": "Response Read Items Items Get", + "title": "Item", } } }, - } - }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } }, - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "402": { - "description": "Payment Required", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", + "/with-computed-field/": { + "post": { + "summary": "Create With Computed Field", + "operationId": "create_with_computed_field_with_computed_field__post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/WithComputedField-Input" } } }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithComputedField-Output" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, }, }, }, - "/items-list/": { - "post": { - "summary": "Create Item List", - "operationId": "create_item_list_items_list__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Item", - } + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", } }, - "required": True, + "type": "object", + "title": "HTTPValidationError", }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "sub": { + "anyOf": [ + {"$ref": "#/components/schemas/SubItem"}, + {"type": "null"}, + ] }, }, + "type": "object", + "required": ["name"], + "title": "Item", + }, + "SubItem": { + "properties": { + "subname": {"type": "string", "title": "Subname"}, + "sub_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Sub Description", + }, + "tags": { + "items": {"type": "string"}, + "type": "array", + "title": "Tags", + "default": [], + }, + }, + "type": "object", + "required": ["subname"], + "title": "SubItem", + }, + "WithComputedField-Input": { + "properties": {"name": {"type": "string", "title": "Name"}}, + "type": "object", + "required": ["name"], + "title": "WithComputedField", + }, + "WithComputedField-Output": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "computed_field": { + "type": "string", + "title": "Computed Field", + "readOnly": True, + }, + }, + "type": "object", + "required": ["name", "computed_field"], + "title": "WithComputedField", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } }, - "/with-computed-field/": { - "post": { - "summary": "Create With Computed Field", - "operationId": "create_with_computed_field_with_computed_field__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WithComputedField-Input" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/WithComputedField-Output" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - }, - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "sub": { - "anyOf": [ - {"$ref": "#/components/schemas/SubItem"}, - {"type": "null"}, - ] - }, - }, - "type": "object", - "required": ["name"], - "title": "Item", - }, - "SubItem": { - "properties": { - "subname": {"type": "string", "title": "Subname"}, - "sub_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Sub Description", - }, - "tags": { - "items": {"type": "string"}, - "type": "array", - "title": "Tags", - "default": [], - }, - }, - "type": "object", - "required": ["subname"], - "title": "SubItem", - }, - "WithComputedField-Input": { - "properties": {"name": {"type": "string", "title": "Name"}}, - "type": "object", - "required": ["name"], - "title": "WithComputedField", - }, - "WithComputedField-Output": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "computed_field": { - "type": "string", - "title": "Computed Field", - "readOnly": True, - }, - }, - "type": "object", - "required": ["name", "computed_field"], - "title": "WithComputedField", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + } + ) diff --git a/tests/test_param_in_path_and_dependency.py b/tests/test_param_in_path_and_dependency.py index 6b1f660cb..81178924b 100644 --- a/tests/test_param_in_path_and_dependency.py +++ b/tests/test_param_in_path_and_dependency.py @@ -1,5 +1,6 @@ from fastapi import Depends, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -23,73 +24,76 @@ def test_read_users(): def test_openapi_schema(): response = client.get("/openapi.json") - data = response.json() - assert data == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/{user_id}": { - "get": { - "summary": "Read Users", - "operationId": "read_users_users__user_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/{user_id}": { + "get": { + "summary": "Read Users", + "operationId": "read_users_users__user_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py index 5060920f1..463fb51b1 100644 --- a/tests/test_param_include_in_schema.py +++ b/tests/test_param_include_in_schema.py @@ -3,6 +3,7 @@ from typing import Optional import pytest from fastapi import Cookie, FastAPI, Header, Path, Query from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -33,140 +34,6 @@ async def hidden_query( return {"hidden_query": hidden_query} -openapi_schema = { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/hidden_cookie": { - "get": { - "summary": "Hidden Cookie", - "operationId": "hidden_cookie_hidden_cookie_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/hidden_header": { - "get": { - "summary": "Hidden Header", - "operationId": "hidden_header_hidden_header_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/hidden_path/{hidden_path}": { - "get": { - "summary": "Hidden Path", - "operationId": "hidden_path_hidden_path__hidden_path__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/hidden_query": { - "get": { - "summary": "Hidden Query", - "operationId": "hidden_query_hidden_query_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, -} - - -def test_openapi_schema(): - client = TestClient(app) - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == openapi_schema - - @pytest.mark.parametrize( "path,cookies,expected_status,expected_response", [ @@ -242,3 +109,140 @@ def test_hidden_query(path, expected_status, expected_response): response = client.get(path) assert response.status_code == expected_status assert response.json() == expected_response + + +def test_openapi_schema(): + client = TestClient(app) + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/hidden_cookie": { + "get": { + "summary": "Hidden Cookie", + "operationId": "hidden_cookie_hidden_cookie_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/hidden_header": { + "get": { + "summary": "Hidden Header", + "operationId": "hidden_header_hidden_header_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/hidden_path/{hidden_path}": { + "get": { + "summary": "Hidden Path", + "operationId": "hidden_path_hidden_path__hidden_path__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/hidden_query": { + "get": { + "summary": "Hidden Query", + "operationId": "hidden_query_hidden_query_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + } + }, + } + ) diff --git a/tests/test_put_no_body.py b/tests/test_put_no_body.py index 2b9299bc5..5759a3f9f 100644 --- a/tests/test_put_no_body.py +++ b/tests/test_put_no_body.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -27,72 +28,76 @@ def test_put_no_body_with_body(): 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": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Save Item No Body", - "operationId": "save_item_no_body_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], + "summary": "Save Item No Body", + "operationId": "save_item_no_body_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_regex_deprecated_params.py b/tests/test_regex_deprecated_params.py index 6074b6282..2069004f3 100644 --- a/tests/test_regex_deprecated_params.py +++ b/tests/test_regex_deprecated_params.py @@ -4,6 +4,7 @@ import pytest from fastapi import FastAPI, Query from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient +from inline_snapshot import snapshot from .utils import needs_py310 @@ -64,79 +65,82 @@ def test_openapi_schema(): client = get_client() response = client.get("/openapi.json") assert response.status_code == 200, response.text - # insert_assert(response.json()) - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "name": "q", - "in": "query", - "required": False, - "schema": { - "anyOf": [ - {"type": "string", "pattern": "^fixedquery$"}, - {"type": "null"}, - ], - "title": "Q", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "name": "q", + "in": "query", + "required": False, + "schema": { + "anyOf": [ + {"type": "string", "pattern": "^fixedquery$"}, + {"type": "null"}, + ], + "title": "Q", + }, + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + }, + } + ) diff --git a/tests/test_repeated_dependency_schema.py b/tests/test_repeated_dependency_schema.py index 0fc7e3d3e..304052dd1 100644 --- a/tests/test_repeated_dependency_schema.py +++ b/tests/test_repeated_dependency_schema.py @@ -1,5 +1,6 @@ from fastapi import Depends, FastAPI, Header, status from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -19,86 +20,90 @@ def get_deps(dep1: str = Depends(get_header), dep2: str = Depends(get_something_ client = TestClient(app) -schema = { - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "title": "Detail", - "type": "array", - } - }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - "title": "Location", - "type": "array", - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - "required": ["loc", "msg", "type"], - "title": "ValidationError", - "type": "object", - }, - } - }, - "info": {"title": "FastAPI", "version": "0.1.0"}, - "openapi": "3.1.0", - "paths": { - "/": { - "get": { - "operationId": "get_deps__get", - "parameters": [ - { - "in": "header", - "name": "someheader", - "required": True, - "schema": {"title": "Someheader", "type": "string"}, - } - ], - "responses": { - "200": { - "content": {"application/json": {"schema": {}}}, - "description": "Successful Response", - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error", - }, - }, - "summary": "Get Deps", - } - } - }, -} - - -def test_schema(): - response = client.get("/openapi.json") - assert response.status_code == status.HTTP_200_OK - actual_schema = response.json() - assert actual_schema == schema - assert ( - len(actual_schema["paths"]["/"]["get"]["parameters"]) == 1 - ) # primary goal of this test - def test_response(): response = client.get("/", headers={"someheader": "hello"}) assert response.status_code == status.HTTP_200_OK assert response.json() == {"dep1": "hello", "dep2": "hello123"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == status.HTTP_200_OK + actual_schema = response.json() + assert ( + len(actual_schema["paths"]["/"]["get"]["parameters"]) == 1 + ) # primary goal of this test + assert actual_schema == snapshot( + { + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "title": "Detail", + "type": "array", + } + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "title": "Location", + "type": "array", + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + "required": ["loc", "msg", "type"], + "title": "ValidationError", + "type": "object", + }, + } + }, + "info": {"title": "FastAPI", "version": "0.1.0"}, + "openapi": "3.1.0", + "paths": { + "/": { + "get": { + "operationId": "get_deps__get", + "parameters": [ + { + "in": "header", + "name": "someheader", + "required": True, + "schema": {"title": "Someheader", "type": "string"}, + } + ], + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error", + }, + }, + "summary": "Get Deps", + } + } + }, + } + ) diff --git a/tests/test_repeated_parameter_alias.py b/tests/test_repeated_parameter_alias.py index 49e4ad4a2..32cd55ec3 100644 --- a/tests/test_repeated_parameter_alias.py +++ b/tests/test_repeated_parameter_alias.py @@ -1,5 +1,6 @@ from fastapi import FastAPI, Path, Query, status from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -24,79 +25,82 @@ def test_get_parameters(): def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == status.HTTP_200_OK - actual_schema = response.json() - assert actual_schema == { - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "title": "Detail", - "type": "array", - } + assert response.json() == snapshot( + { + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "title": "Detail", + "type": "array", + } + }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "title": "Location", + "type": "array", }, - "title": "Location", - "type": "array", + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, + "required": ["loc", "msg", "type"], + "title": "ValidationError", + "type": "object", }, - "required": ["loc", "msg", "type"], - "title": "ValidationError", - "type": "object", - }, - } - }, - "info": {"title": "FastAPI", "version": "0.1.0"}, - "openapi": "3.1.0", - "paths": { - "/{repeated_alias}": { - "get": { - "operationId": "get_parameters_with_repeated_aliases__repeated_alias__get", - "parameters": [ - { - "in": "path", - "name": "repeated_alias", - "required": True, - "schema": {"title": "Repeated Alias", "type": "string"}, - }, - { - "in": "query", - "name": "repeated_alias", - "required": True, - "schema": {"title": "Repeated Alias", "type": "string"}, - }, - ], - "responses": { - "200": { - "content": {"application/json": {"schema": {}}}, - "description": "Successful Response", - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error", - }, - }, - "summary": "Get Parameters With Repeated Aliases", } - } - }, - } + }, + "info": {"title": "FastAPI", "version": "0.1.0"}, + "openapi": "3.1.0", + "paths": { + "/{repeated_alias}": { + "get": { + "operationId": "get_parameters_with_repeated_aliases__repeated_alias__get", + "parameters": [ + { + "in": "path", + "name": "repeated_alias", + "required": True, + "schema": {"title": "Repeated Alias", "type": "string"}, + }, + { + "in": "query", + "name": "repeated_alias", + "required": True, + "schema": {"title": "Repeated Alias", "type": "string"}, + }, + ], + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error", + }, + }, + "summary": "Get Parameters With Repeated Aliases", + } + } + }, + } + ) diff --git a/tests/test_reponse_set_reponse_code_empty.py b/tests/test_reponse_set_reponse_code_empty.py index b31aefa47..77da6aafc 100644 --- a/tests/test_reponse_set_reponse_code_empty.py +++ b/tests/test_reponse_set_reponse_code_empty.py @@ -2,6 +2,7 @@ from typing import Any from fastapi import FastAPI, Response from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -31,69 +32,73 @@ def test_dependency_set_status_code(): 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": { - "/{id}": { - "delete": { - "summary": "Delete Deployment", - "operationId": "delete_deployment__id__delete", - "parameters": [ - { - "required": True, - "schema": {"title": "Id", "type": "integer"}, - "name": "id", - "in": "path", - } - ], - "responses": { - "204": {"description": "Successful Response"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/{id}": { + "delete": { + "summary": "Delete Deployment", + "operationId": "delete_deployment__id__delete", + "parameters": [ + { + "required": True, + "schema": {"title": "Id", "type": "integer"}, + "name": "id", + "in": "path", + } + ], + "responses": { + "204": {"description": "Successful Response"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_request_body_parameters_media_type.py b/tests/test_request_body_parameters_media_type.py index 8731c3e5d..10e7be50c 100644 --- a/tests/test_request_body_parameters_media_type.py +++ b/tests/test_request_body_parameters_media_type.py @@ -1,5 +1,6 @@ from fastapi import Body, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -37,141 +38,147 @@ 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": { - "/products": { - "post": { - "summary": "Create Product", - "operationId": "create_product_products_post", - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/Body_create_product_products_post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/products": { + "post": { + "summary": "Create Product", + "operationId": "create_product_products_post", + "requestBody": { "content": { - "application/json": { + "application/vnd.api+json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_create_product_products_post" } } }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/shops": { + "post": { + "summary": "Create Shop", + "operationId": "create_shop_shops_post", + "requestBody": { + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/Body_create_shop_shops_post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_product_products_post": { + "title": "Body_create_product_products_post", + "required": ["data"], + "type": "object", + "properties": { + "data": {"$ref": "#/components/schemas/Product"} + }, + }, + "Body_create_shop_shops_post": { + "title": "Body_create_shop_shops_post", + "required": ["data"], + "type": "object", + "properties": { + "data": {"$ref": "#/components/schemas/Shop"}, + "included": { + "title": "Included", + "type": "array", + "items": {"$ref": "#/components/schemas/Product"}, + "default": [], + }, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "Product": { + "title": "Product", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + }, + }, + "Shop": { + "title": "Shop", + "required": ["name"], + "type": "object", + "properties": {"name": {"title": "Name", "type": "string"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } }, - "/shops": { - "post": { - "summary": "Create Shop", - "operationId": "create_shop_shops_post", - "requestBody": { - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/Body_create_shop_shops_post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_product_products_post": { - "title": "Body_create_product_products_post", - "required": ["data"], - "type": "object", - "properties": {"data": {"$ref": "#/components/schemas/Product"}}, - }, - "Body_create_shop_shops_post": { - "title": "Body_create_shop_shops_post", - "required": ["data"], - "type": "object", - "properties": { - "data": {"$ref": "#/components/schemas/Shop"}, - "included": { - "title": "Included", - "type": "array", - "items": {"$ref": "#/components/schemas/Product"}, - "default": [], - }, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Product": { - "title": "Product", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "Shop": { - "title": "Shop", - "required": ["name"], - "type": "object", - "properties": {"name": {"title": "Name", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + } + ) diff --git a/tests/test_request_params/test_cookie/test_optional_str.py b/tests/test_request_params/test_cookie/test_optional_str.py index 6f381c8b8..1b2a18b07 100644 --- a/tests/test_request_params/test_cookie/test_optional_str.py +++ b/tests/test_request_params/test_cookie/test_optional_str.py @@ -3,6 +3,7 @@ from typing import Annotated, Optional import pytest from fastapi import Cookie, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, Field app = FastAPI() @@ -30,17 +31,19 @@ async def read_model_optional_str(p: Annotated[CookieModelOptionalStr, Cookie()] ["/optional-str", "/model-optional-str"], ) def test_optional_str_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P", - }, - "name": "p", - "in": "cookie", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + "name": "p", + "in": "cookie", + } + ] + ) @pytest.mark.parametrize( @@ -91,17 +94,19 @@ async def read_model_optional_alias(p: Annotated[CookieModelOptionalAlias, Cooki ["/optional-alias", "/model-optional-alias"], ) def test_optional_str_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Alias", - }, - "name": "p_alias", - "in": "cookie", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + "name": "p_alias", + "in": "cookie", + } + ] + ) @pytest.mark.parametrize( @@ -169,17 +174,19 @@ def read_model_optional_validation_alias( ["/optional-validation-alias", "/model-optional-validation-alias"], ) def test_optional_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Val Alias", - }, - "name": "p_val_alias", - "in": "cookie", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "cookie", + } + ] + ) @pytest.mark.parametrize( @@ -255,17 +262,19 @@ def read_model_optional_alias_and_validation_alias( ], ) def test_optional_alias_and_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Val Alias", - }, - "name": "p_val_alias", - "in": "cookie", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "cookie", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_cookie/test_required_str.py b/tests/test_request_params/test_cookie/test_required_str.py index 3e877b3e3..25f93bba6 100644 --- a/tests/test_request_params/test_cookie/test_required_str.py +++ b/tests/test_request_params/test_cookie/test_required_str.py @@ -4,6 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi import Cookie, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, Field app = FastAPI() @@ -31,14 +32,16 @@ async def read_model_required_str(p: Annotated[CookieModelRequiredStr, Cookie()] ["/required-str", "/model-required-str"], ) def test_required_str_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P", "type": "string"}, - "name": "p", - "in": "cookie", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P", "type": "string"}, + "name": "p", + "in": "cookie", + } + ] + ) @pytest.mark.parametrize( @@ -96,14 +99,16 @@ async def read_model_required_alias(p: Annotated[CookieModelRequiredAlias, Cooki ["/required-alias", "/model-required-alias"], ) def test_required_str_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P Alias", "type": "string"}, - "name": "p_alias", - "in": "cookie", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P Alias", "type": "string"}, + "name": "p_alias", + "in": "cookie", + } + ] + ) @pytest.mark.parametrize( @@ -195,14 +200,16 @@ def read_model_required_validation_alias( ["/required-validation-alias", "/model-required-validation-alias"], ) def test_required_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P Val Alias", "type": "string"}, - "name": "p_val_alias", - "in": "cookie", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "cookie", + } + ] + ) @pytest.mark.parametrize( @@ -302,14 +309,16 @@ def read_model_required_alias_and_validation_alias( ], ) def test_required_alias_and_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P Val Alias", "type": "string"}, - "name": "p_val_alias", - "in": "cookie", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "cookie", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_header/test_list.py b/tests/test_request_params/test_header/test_list.py index 489a6b3e7..4b8e2adb4 100644 --- a/tests/test_request_params/test_header/test_list.py +++ b/tests/test_request_params/test_header/test_list.py @@ -4,6 +4,7 @@ import pytest from dirty_equals import AnyThing, IsOneOf, IsPartialDict from fastapi import FastAPI, Header from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, Field app = FastAPI() @@ -31,18 +32,20 @@ def read_model_required_list_str(p: Annotated[HeaderModelRequiredListStr, Header ["/required-list-str", "/model-required-list-str"], ) def test_required_list_str_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": { - "title": "P", - "type": "array", - "items": {"type": "string"}, - }, - "name": "p", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": { + "title": "P", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -101,18 +104,20 @@ async def read_model_required_list_alias( ["/required-list-alias", "/model-required-list-alias"], ) def test_required_list_str_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": { - "title": "P Alias", - "type": "array", - "items": {"type": "string"}, - }, - "name": "p_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": { + "title": "P Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -199,18 +204,20 @@ async def read_model_required_list_validation_alias( ["/required-list-validation-alias", "/model-required-list-validation-alias"], ) def test_required_list_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": { - "title": "P Val Alias", - "type": "array", - "items": {"type": "string"}, - }, - "name": "p_val_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": { + "title": "P Val Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_val_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -307,18 +314,20 @@ def read_model_required_list_alias_and_validation_alias( ], ) def test_required_list_alias_and_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": { - "title": "P Val Alias", - "type": "array", - "items": {"type": "string"}, - }, - "name": "p_val_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": { + "title": "P Val Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_val_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_header/test_optional_list.py b/tests/test_request_params/test_header/test_optional_list.py index 5dd4ea9ad..3bbb73d54 100644 --- a/tests/test_request_params/test_header/test_optional_list.py +++ b/tests/test_request_params/test_header/test_optional_list.py @@ -3,6 +3,7 @@ from typing import Annotated, Optional import pytest from fastapi import FastAPI, Header from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, Field app = FastAPI() @@ -34,20 +35,22 @@ async def read_model_optional_list_str( ["/optional-list-str", "/model-optional-list-str"], ) def test_optional_list_str_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P", - }, - "name": "p", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P", + }, + "name": "p", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -99,20 +102,22 @@ async def read_model_optional_list_alias( ["/optional-list-alias", "/model-optional-list-alias"], ) def test_optional_list_str_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Alias", - }, - "name": "p_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Alias", + }, + "name": "p_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -178,20 +183,22 @@ def read_model_optional_list_validation_alias( ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], ) def test_optional_list_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Val Alias", - }, - "name": "p_val_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -266,20 +273,22 @@ def read_model_optional_list_alias_and_validation_alias( ], ) def test_optional_list_alias_and_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Val Alias", - }, - "name": "p_val_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_header/test_optional_str.py b/tests/test_request_params/test_header/test_optional_str.py index 0bd0eddc1..a5174e59a 100644 --- a/tests/test_request_params/test_header/test_optional_str.py +++ b/tests/test_request_params/test_header/test_optional_str.py @@ -3,6 +3,7 @@ from typing import Annotated, Optional import pytest from fastapi import FastAPI, Header from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, Field app = FastAPI() @@ -30,17 +31,19 @@ async def read_model_optional_str(p: Annotated[HeaderModelOptionalStr, Header()] ["/optional-str", "/model-optional-str"], ) def test_optional_str_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P", - }, - "name": "p", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + "name": "p", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -90,17 +93,19 @@ async def read_model_optional_alias(p: Annotated[HeaderModelOptionalAlias, Heade ["/optional-alias", "/model-optional-alias"], ) def test_optional_str_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Alias", - }, - "name": "p_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + "name": "p_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -166,17 +171,19 @@ def read_model_optional_validation_alias( ["/optional-validation-alias", "/model-optional-validation-alias"], ) def test_optional_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Val Alias", - }, - "name": "p_val_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -250,17 +257,19 @@ def read_model_optional_alias_and_validation_alias( ], ) def test_optional_alias_and_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Val Alias", - }, - "name": "p_val_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_header/test_required_str.py b/tests/test_request_params/test_header/test_required_str.py index 20dd29657..2df9b5f2f 100644 --- a/tests/test_request_params/test_header/test_required_str.py +++ b/tests/test_request_params/test_header/test_required_str.py @@ -4,6 +4,7 @@ import pytest from dirty_equals import AnyThing, IsOneOf, IsPartialDict from fastapi import FastAPI, Header from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, Field app = FastAPI() @@ -31,14 +32,16 @@ async def read_model_required_str(p: Annotated[HeaderModelRequiredStr, Header()] ["/required-str", "/model-required-str"], ) def test_required_str_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P", "type": "string"}, - "name": "p", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P", "type": "string"}, + "name": "p", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -95,14 +98,16 @@ async def read_model_required_alias(p: Annotated[HeaderModelRequiredAlias, Heade ["/required-alias", "/model-required-alias"], ) def test_required_str_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P Alias", "type": "string"}, - "name": "p_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P Alias", "type": "string"}, + "name": "p_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -189,14 +194,16 @@ def read_model_required_validation_alias( ["/required-validation-alias", "/model-required-validation-alias"], ) def test_required_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P Val Alias", "type": "string"}, - "name": "p_val_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( @@ -294,14 +301,16 @@ def read_model_required_alias_and_validation_alias( ], ) def test_required_alias_and_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P Val Alias", "type": "string"}, - "name": "p_val_alias", - "in": "header", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "header", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_path/test_required_str.py b/tests/test_request_params/test_path/test_required_str.py index b2d63667e..5add058c2 100644 --- a/tests/test_request_params/test_path/test_required_str.py +++ b/tests/test_request_params/test_path/test_required_str.py @@ -3,6 +3,7 @@ from typing import Annotated import pytest from fastapi import FastAPI, Path from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -53,14 +54,16 @@ def read_required_alias_and_validation_alias( ], ) def test_schema(path: str, expected_name: str, expected_title: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": expected_title, "type": "string"}, - "name": expected_name, - "in": "path", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": expected_title, "type": "string"}, + "name": expected_name, + "in": "path", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_query/test_list.py b/tests/test_request_params/test_query/test_list.py index e933da214..a5cd03021 100644 --- a/tests/test_request_params/test_query/test_list.py +++ b/tests/test_request_params/test_query/test_list.py @@ -4,6 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi import FastAPI, Query from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, Field app = FastAPI() @@ -31,18 +32,20 @@ def read_model_required_list_str(p: Annotated[QueryModelRequiredListStr, Query() ["/required-list-str", "/model-required-list-str"], ) def test_required_list_str_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": { - "title": "P", - "type": "array", - "items": {"type": "string"}, - }, - "name": "p", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": { + "title": "P", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -101,18 +104,20 @@ async def read_model_required_list_alias( ["/required-list-alias", "/model-required-list-alias"], ) def test_required_list_str_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": { - "title": "P Alias", - "type": "array", - "items": {"type": "string"}, - }, - "name": "p_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": { + "title": "P Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -199,18 +204,20 @@ async def read_model_required_list_validation_alias( ["/required-list-validation-alias", "/model-required-list-validation-alias"], ) def test_required_list_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": { - "title": "P Val Alias", - "type": "array", - "items": {"type": "string"}, - }, - "name": "p_val_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": { + "title": "P Val Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_val_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -305,18 +312,20 @@ def read_model_required_list_alias_and_validation_alias( ], ) def test_required_list_alias_and_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": { - "title": "P Val Alias", - "type": "array", - "items": {"type": "string"}, - }, - "name": "p_val_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": { + "title": "P Val Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_val_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_query/test_optional_list.py b/tests/test_request_params/test_query/test_optional_list.py index 351e03a71..5608c6499 100644 --- a/tests/test_request_params/test_query/test_optional_list.py +++ b/tests/test_request_params/test_query/test_optional_list.py @@ -3,6 +3,7 @@ from typing import Annotated, Optional import pytest from fastapi import FastAPI, Query from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, Field app = FastAPI() @@ -34,20 +35,22 @@ async def read_model_optional_list_str( ["/optional-list-str", "/model-optional-list-str"], ) def test_optional_list_str_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P", - }, - "name": "p", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P", + }, + "name": "p", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -99,20 +102,22 @@ async def read_model_optional_list_alias( ["/optional-list-alias", "/model-optional-list-alias"], ) def test_optional_list_str_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Alias", - }, - "name": "p_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Alias", + }, + "name": "p_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -178,20 +183,22 @@ def read_model_optional_list_validation_alias( ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], ) def test_optional_list_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Val Alias", - }, - "name": "p_val_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -264,20 +271,22 @@ def read_model_optional_list_alias_and_validation_alias( ], ) def test_optional_list_alias_and_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [ - {"items": {"type": "string"}, "type": "array"}, - {"type": "null"}, - ], - "title": "P Val Alias", - }, - "name": "p_val_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_query/test_optional_str.py b/tests/test_request_params/test_query/test_optional_str.py index 12e1b465a..b181686b0 100644 --- a/tests/test_request_params/test_query/test_optional_str.py +++ b/tests/test_request_params/test_query/test_optional_str.py @@ -3,6 +3,7 @@ from typing import Annotated, Optional import pytest from fastapi import FastAPI, Query from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, Field app = FastAPI() @@ -30,17 +31,19 @@ async def read_model_optional_str(p: Annotated[QueryModelOptionalStr, Query()]): ["/optional-str", "/model-optional-str"], ) def test_optional_str_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P", - }, - "name": "p", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + "name": "p", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -90,17 +93,19 @@ async def read_model_optional_alias(p: Annotated[QueryModelOptionalAlias, Query( ["/optional-alias", "/model-optional-alias"], ) def test_optional_str_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Alias", - }, - "name": "p_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + "name": "p_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -166,17 +171,19 @@ def read_model_optional_validation_alias( ["/optional-validation-alias", "/model-optional-validation-alias"], ) def test_optional_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Val Alias", - }, - "name": "p_val_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -250,17 +257,19 @@ def read_model_optional_alias_and_validation_alias( ], ) def test_optional_alias_and_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "P Val Alias", - }, - "name": "p_val_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_request_params/test_query/test_required_str.py b/tests/test_request_params/test_query/test_required_str.py index 9e7b96145..c60e5ca02 100644 --- a/tests/test_request_params/test_query/test_required_str.py +++ b/tests/test_request_params/test_query/test_required_str.py @@ -4,6 +4,7 @@ import pytest from dirty_equals import IsOneOf from fastapi import FastAPI, Query from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, Field app = FastAPI() @@ -31,14 +32,16 @@ async def read_model_required_str(p: Annotated[QueryModelRequiredStr, Query()]): ["/required-str", "/model-required-str"], ) def test_required_str_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P", "type": "string"}, - "name": "p", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P", "type": "string"}, + "name": "p", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -95,14 +98,16 @@ async def read_model_required_alias(p: Annotated[QueryModelRequiredAlias, Query( ["/required-alias", "/model-required-alias"], ) def test_required_str_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P Alias", "type": "string"}, - "name": "p_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P Alias", "type": "string"}, + "name": "p_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -192,14 +197,16 @@ def read_model_required_validation_alias( ["/required-validation-alias", "/model-required-validation-alias"], ) def test_required_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P Val Alias", "type": "string"}, - "name": "p_val_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( @@ -297,14 +304,16 @@ def read_model_required_alias_and_validation_alias( ], ) def test_required_alias_and_validation_alias_schema(path: str): - assert app.openapi()["paths"][path]["get"]["parameters"] == [ - { - "required": True, - "schema": {"title": "P Val Alias", "type": "string"}, - "name": "p_val_alias", - "in": "query", - } - ] + assert app.openapi()["paths"][path]["get"]["parameters"] == snapshot( + [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "query", + } + ] + ) @pytest.mark.parametrize( diff --git a/tests/test_response_by_alias.py b/tests/test_response_by_alias.py index 807d2600b..cf97934a9 100644 --- a/tests/test_response_by_alias.py +++ b/tests/test_response_by_alias.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, ConfigDict, Field app = FastAPI() @@ -136,188 +137,194 @@ def test_read_list_no_alias(): 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": { - "/dict": { - "get": { - "summary": "Read Dict", - "operationId": "read_dict_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model"} - } - }, - } - }, - } - }, - "/model": { - "get": { - "summary": "Read Model", - "operationId": "read_model_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model"} - } - }, - } - }, - } - }, - "/list": { - "get": { - "summary": "Read List", - "operationId": "read_list_list_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read List List Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Model"}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/dict": { + "get": { + "summary": "Read Dict", + "operationId": "read_dict_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model"} } - } - }, - } - }, - } - }, - "/by-alias/dict": { - "get": { - "summary": "By Alias Dict", - "operationId": "by_alias_dict_by_alias_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model"} - } - }, - } - }, - } - }, - "/by-alias/model": { - "get": { - "summary": "By Alias Model", - "operationId": "by_alias_model_by_alias_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model"} - } - }, - } - }, - } - }, - "/by-alias/list": { - "get": { - "summary": "By Alias List", - "operationId": "by_alias_list_by_alias_list_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response By Alias List By Alias List Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Model"}, + }, + } + }, + } + }, + "/model": { + "get": { + "summary": "Read Model", + "operationId": "read_model_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model"} } - } - }, - } - }, - } - }, - "/no-alias/dict": { - "get": { - "summary": "No Alias Dict", - "operationId": "no_alias_dict_no_alias_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ModelNoAlias" + }, + } + }, + } + }, + "/list": { + "get": { + "summary": "Read List", + "operationId": "read_list_list_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read List List Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Model" + }, + } } - } - }, - } - }, - } - }, - "/no-alias/model": { - "get": { - "summary": "No Alias Model", - "operationId": "no_alias_model_no_alias_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ModelNoAlias" + }, + } + }, + } + }, + "/by-alias/dict": { + "get": { + "summary": "By Alias Dict", + "operationId": "by_alias_dict_by_alias_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model"} } - } - }, - } - }, - } - }, - "/no-alias/list": { - "get": { - "summary": "No Alias List", - "operationId": "no_alias_list_no_alias_list_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response No Alias List No Alias List Get", - "type": "array", - "items": { + }, + } + }, + } + }, + "/by-alias/model": { + "get": { + "summary": "By Alias Model", + "operationId": "by_alias_model_by_alias_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model"} + } + }, + } + }, + } + }, + "/by-alias/list": { + "get": { + "summary": "By Alias List", + "operationId": "by_alias_list_by_alias_list_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response By Alias List By Alias List Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Model" + }, + } + } + }, + } + }, + } + }, + "/no-alias/dict": { + "get": { + "summary": "No Alias Dict", + "operationId": "no_alias_dict_no_alias_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ModelNoAlias" - }, + } } - } - }, - } + }, + } + }, + } + }, + "/no-alias/model": { + "get": { + "summary": "No Alias Model", + "operationId": "no_alias_model_no_alias_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelNoAlias" + } + } + }, + } + }, + } + }, + "/no-alias/list": { + "get": { + "summary": "No Alias List", + "operationId": "no_alias_list_no_alias_list_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response No Alias List No Alias List Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/ModelNoAlias" + }, + } + } + }, + } + }, + } + }, + }, + "components": { + "schemas": { + "Model": { + "title": "Model", + "required": ["alias"], + "type": "object", + "properties": {"alias": {"title": "Alias", "type": "string"}}, + }, + "ModelNoAlias": { + "title": "ModelNoAlias", + "required": ["name"], + "type": "object", + "properties": {"name": {"title": "Name", "type": "string"}}, + "description": "response_model_by_alias=False is basically a quick hack, to support proper OpenAPI use another model with the correct field names", }, } }, - }, - "components": { - "schemas": { - "Model": { - "title": "Model", - "required": ["alias"], - "type": "object", - "properties": {"alias": {"title": "Alias", "type": "string"}}, - }, - "ModelNoAlias": { - "title": "ModelNoAlias", - "required": ["name"], - "type": "object", - "properties": {"name": {"title": "Name", "type": "string"}}, - "description": "response_model_by_alias=False is basically a quick hack, to support proper OpenAPI use another model with the correct field names", - }, - } - }, - } + } + ) diff --git a/tests/test_response_class_no_mediatype.py b/tests/test_response_class_no_mediatype.py index 4dc164bf9..75582cd60 100644 --- a/tests/test_response_class_no_mediatype.py +++ b/tests/test_response_class_no_mediatype.py @@ -1,6 +1,7 @@ from fastapi import FastAPI, Response from fastapi.responses import JSONResponse from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -39,73 +40,75 @@ 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": { - "/a": { - "get": { - "responses": { - "500": { - "description": "Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/JsonApiError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a": { + "get": { + "responses": { + "500": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + } } - } + }, + }, + "200": {"description": "Successful Response"}, + }, + "summary": "A", + "operationId": "a_a_get", + } + }, + "/b": { + "get": { + "responses": { + "500": { + "description": "Error", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Error"} + } + }, + }, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, }, - "200": {"description": "Successful Response"}, - }, - "summary": "A", - "operationId": "a_a_get", - } + "summary": "B", + "operationId": "b_b_get", + } + }, }, - "/b": { - "get": { - "responses": { - "500": { - "description": "Error", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Error"} - } - }, - }, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + "components": { + "schemas": { + "Error": { + "title": "Error", + "required": ["status", "title"], + "type": "object", + "properties": { + "status": {"title": "Status", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + }, + }, + "JsonApiError": { + "title": "JsonApiError", + "required": ["errors"], + "type": "object", + "properties": { + "errors": { + "title": "Errors", + "type": "array", + "items": {"$ref": "#/components/schemas/Error"}, + } }, }, - "summary": "B", - "operationId": "b_b_get", } }, - }, - "components": { - "schemas": { - "Error": { - "title": "Error", - "required": ["status", "title"], - "type": "object", - "properties": { - "status": {"title": "Status", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - }, - }, - "JsonApiError": { - "title": "JsonApiError", - "required": ["errors"], - "type": "object", - "properties": { - "errors": { - "title": "Errors", - "type": "array", - "items": {"$ref": "#/components/schemas/Error"}, - } - }, - }, - } - }, - } + } + ) diff --git a/tests/test_response_code_no_body.py b/tests/test_response_code_no_body.py index 70456a746..215621065 100644 --- a/tests/test_response_code_no_body.py +++ b/tests/test_response_code_no_body.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -47,66 +48,68 @@ def test_get_response(): 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": { - "/a": { - "get": { - "responses": { - "500": { - "description": "Error", - "content": { - "application/vnd.api+json": { - "schema": { - "$ref": "#/components/schemas/JsonApiError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/a": { + "get": { + "responses": { + "500": { + "description": "Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + } } - } + }, + }, + "204": {"description": "Successful Response"}, + }, + "summary": "A", + "operationId": "a_a_get", + } + }, + "/b": { + "get": { + "responses": { + "204": {"description": "No Content"}, + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, }, - "204": {"description": "Successful Response"}, - }, - "summary": "A", - "operationId": "a_a_get", - } + "summary": "B", + "operationId": "b_b_get", + } + }, }, - "/b": { - "get": { - "responses": { - "204": {"description": "No Content"}, - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + "components": { + "schemas": { + "Error": { + "title": "Error", + "required": ["status", "title"], + "type": "object", + "properties": { + "status": {"title": "Status", "type": "string"}, + "title": {"title": "Title", "type": "string"}, + }, + }, + "JsonApiError": { + "title": "JsonApiError", + "required": ["errors"], + "type": "object", + "properties": { + "errors": { + "title": "Errors", + "type": "array", + "items": {"$ref": "#/components/schemas/Error"}, + } }, }, - "summary": "B", - "operationId": "b_b_get", } }, - }, - "components": { - "schemas": { - "Error": { - "title": "Error", - "required": ["status", "title"], - "type": "object", - "properties": { - "status": {"title": "Status", "type": "string"}, - "title": {"title": "Title", "type": "string"}, - }, - }, - "JsonApiError": { - "title": "JsonApiError", - "required": ["errors"], - "type": "object", - "properties": { - "errors": { - "title": "Errors", - "type": "array", - "items": {"$ref": "#/components/schemas/Error"}, - } - }, - }, - } - }, - } + } + ) diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py index 58fba89f1..ded597102 100644 --- a/tests/test_response_model_as_return_annotation.py +++ b/tests/test_response_model_as_return_annotation.py @@ -5,6 +5,7 @@ from fastapi import FastAPI from fastapi.exceptions import FastAPIError, ResponseValidationError from fastapi.responses import JSONResponse, Response from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel @@ -512,603 +513,611 @@ def test_invalid_response_model_field(): 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": { - "/no_response_model-no_annotation-return_model": { - "get": { - "summary": "No Response Model No Annotation Return Model", - "operationId": "no_response_model_no_annotation_return_model_no_response_model_no_annotation_return_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/no_response_model-no_annotation-return_dict": { - "get": { - "summary": "No Response Model No Annotation Return Dict", - "operationId": "no_response_model_no_annotation_return_dict_no_response_model_no_annotation_return_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/response_model-no_annotation-return_same_model": { - "get": { - "summary": "Response Model No Annotation Return Same Model", - "operationId": "response_model_no_annotation_return_same_model_response_model_no_annotation_return_same_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model-no_annotation-return_exact_dict": { - "get": { - "summary": "Response Model No Annotation Return Exact Dict", - "operationId": "response_model_no_annotation_return_exact_dict_response_model_no_annotation_return_exact_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model-no_annotation-return_invalid_dict": { - "get": { - "summary": "Response Model No Annotation Return Invalid Dict", - "operationId": "response_model_no_annotation_return_invalid_dict_response_model_no_annotation_return_invalid_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model-no_annotation-return_invalid_model": { - "get": { - "summary": "Response Model No Annotation Return Invalid Model", - "operationId": "response_model_no_annotation_return_invalid_model_response_model_no_annotation_return_invalid_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model-no_annotation-return_dict_with_extra_data": { - "get": { - "summary": "Response Model No Annotation Return Dict With Extra Data", - "operationId": "response_model_no_annotation_return_dict_with_extra_data_response_model_no_annotation_return_dict_with_extra_data_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model-no_annotation-return_submodel_with_extra_data": { - "get": { - "summary": "Response Model No Annotation Return Submodel With Extra Data", - "operationId": "response_model_no_annotation_return_submodel_with_extra_data_response_model_no_annotation_return_submodel_with_extra_data_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/no_response_model-annotation-return_same_model": { - "get": { - "summary": "No Response Model Annotation Return Same Model", - "operationId": "no_response_model_annotation_return_same_model_no_response_model_annotation_return_same_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/no_response_model-annotation-return_exact_dict": { - "get": { - "summary": "No Response Model Annotation Return Exact Dict", - "operationId": "no_response_model_annotation_return_exact_dict_no_response_model_annotation_return_exact_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/no_response_model-annotation-return_invalid_dict": { - "get": { - "summary": "No Response Model Annotation Return Invalid Dict", - "operationId": "no_response_model_annotation_return_invalid_dict_no_response_model_annotation_return_invalid_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/no_response_model-annotation-return_invalid_model": { - "get": { - "summary": "No Response Model Annotation Return Invalid Model", - "operationId": "no_response_model_annotation_return_invalid_model_no_response_model_annotation_return_invalid_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/no_response_model-annotation-return_dict_with_extra_data": { - "get": { - "summary": "No Response Model Annotation Return Dict With Extra Data", - "operationId": "no_response_model_annotation_return_dict_with_extra_data_no_response_model_annotation_return_dict_with_extra_data_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/no_response_model-annotation-return_submodel_with_extra_data": { - "get": { - "summary": "No Response Model Annotation Return Submodel With Extra Data", - "operationId": "no_response_model_annotation_return_submodel_with_extra_data_no_response_model_annotation_return_submodel_with_extra_data_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model_none-annotation-return_same_model": { - "get": { - "summary": "Response Model None Annotation Return Same Model", - "operationId": "response_model_none_annotation_return_same_model_response_model_none_annotation_return_same_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/response_model_none-annotation-return_exact_dict": { - "get": { - "summary": "Response Model None Annotation Return Exact Dict", - "operationId": "response_model_none_annotation_return_exact_dict_response_model_none_annotation_return_exact_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/response_model_none-annotation-return_invalid_dict": { - "get": { - "summary": "Response Model None Annotation Return Invalid Dict", - "operationId": "response_model_none_annotation_return_invalid_dict_response_model_none_annotation_return_invalid_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/response_model_none-annotation-return_invalid_model": { - "get": { - "summary": "Response Model None Annotation Return Invalid Model", - "operationId": "response_model_none_annotation_return_invalid_model_response_model_none_annotation_return_invalid_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/response_model_none-annotation-return_dict_with_extra_data": { - "get": { - "summary": "Response Model None Annotation Return Dict With Extra Data", - "operationId": "response_model_none_annotation_return_dict_with_extra_data_response_model_none_annotation_return_dict_with_extra_data_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/response_model_none-annotation-return_submodel_with_extra_data": { - "get": { - "summary": "Response Model None Annotation Return Submodel With Extra Data", - "operationId": "response_model_none_annotation_return_submodel_with_extra_data_response_model_none_annotation_return_submodel_with_extra_data_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/response_model_model1-annotation_model2-return_same_model": { - "get": { - "summary": "Response Model Model1 Annotation Model2 Return Same Model", - "operationId": "response_model_model1_annotation_model2_return_same_model_response_model_model1_annotation_model2_return_same_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model_model1-annotation_model2-return_exact_dict": { - "get": { - "summary": "Response Model Model1 Annotation Model2 Return Exact Dict", - "operationId": "response_model_model1_annotation_model2_return_exact_dict_response_model_model1_annotation_model2_return_exact_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model_model1-annotation_model2-return_invalid_dict": { - "get": { - "summary": "Response Model Model1 Annotation Model2 Return Invalid Dict", - "operationId": "response_model_model1_annotation_model2_return_invalid_dict_response_model_model1_annotation_model2_return_invalid_dict_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model_model1-annotation_model2-return_invalid_model": { - "get": { - "summary": "Response Model Model1 Annotation Model2 Return Invalid Model", - "operationId": "response_model_model1_annotation_model2_return_invalid_model_response_model_model1_annotation_model2_return_invalid_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model_model1-annotation_model2-return_dict_with_extra_data": { - "get": { - "summary": "Response Model Model1 Annotation Model2 Return Dict With Extra Data", - "operationId": "response_model_model1_annotation_model2_return_dict_with_extra_data_response_model_model1_annotation_model2_return_dict_with_extra_data_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model_model1-annotation_model2-return_submodel_with_extra_data": { - "get": { - "summary": "Response Model Model1 Annotation Model2 Return Submodel With Extra Data", - "operationId": "response_model_model1_annotation_model2_return_submodel_with_extra_data_response_model_model1_annotation_model2_return_submodel_with_extra_data_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model_filtering_model-annotation_submodel-return_submodel": { - "get": { - "summary": "Response Model Filtering Model Annotation Submodel Return Submodel", - "operationId": "response_model_filtering_model_annotation_submodel_return_submodel_response_model_filtering_model_annotation_submodel_return_submodel_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } - }, - } - }, - } - }, - "/response_model_list_of_model-no_annotation": { - "get": { - "summary": "Response Model List Of Model No Annotation", - "operationId": "response_model_list_of_model_no_annotation_response_model_list_of_model_no_annotation_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Response Model List Of Model No Annotation Response Model List Of Model No Annotation Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - } - }, - } - }, - "/no_response_model-annotation_list_of_model": { - "get": { - "summary": "No Response Model Annotation List Of Model", - "operationId": "no_response_model_annotation_list_of_model_no_response_model_annotation_list_of_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response No Response Model Annotation List Of Model No Response Model Annotation List Of Model Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - } - }, - } - }, - "/no_response_model-annotation_forward_ref_list_of_model": { - "get": { - "summary": "No Response Model Annotation Forward Ref List Of Model", - "operationId": "no_response_model_annotation_forward_ref_list_of_model_no_response_model_annotation_forward_ref_list_of_model_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response No Response Model Annotation Forward Ref List Of Model No Response Model Annotation Forward Ref List Of Model Get", - "type": "array", - "items": {"$ref": "#/components/schemas/User"}, - } - } - }, - } - }, - } - }, - "/response_model_union-no_annotation-return_model1": { - "get": { - "summary": "Response Model Union No Annotation Return Model1", - "operationId": "response_model_union_no_annotation_return_model1_response_model_union_no_annotation_return_model1_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Response Model Union No Annotation Return Model1 Response Model Union No Annotation Return Model1 Get", - "anyOf": [ - {"$ref": "#/components/schemas/User"}, - {"$ref": "#/components/schemas/Item"}, - ], - } - } - }, - } - }, - } - }, - "/response_model_union-no_annotation-return_model2": { - "get": { - "summary": "Response Model Union No Annotation Return Model2", - "operationId": "response_model_union_no_annotation_return_model2_response_model_union_no_annotation_return_model2_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Response Model Union No Annotation Return Model2 Response Model Union No Annotation Return Model2 Get", - "anyOf": [ - {"$ref": "#/components/schemas/User"}, - {"$ref": "#/components/schemas/Item"}, - ], - } - } - }, - } - }, - } - }, - "/no_response_model-annotation_union-return_model1": { - "get": { - "summary": "No Response Model Annotation Union Return Model1", - "operationId": "no_response_model_annotation_union_return_model1_no_response_model_annotation_union_return_model1_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response No Response Model Annotation Union Return Model1 No Response Model Annotation Union Return Model1 Get", - "anyOf": [ - {"$ref": "#/components/schemas/User"}, - {"$ref": "#/components/schemas/Item"}, - ], - } - } - }, - } - }, - } - }, - "/no_response_model-annotation_union-return_model2": { - "get": { - "summary": "No Response Model Annotation Union Return Model2", - "operationId": "no_response_model_annotation_union_return_model2_no_response_model_annotation_union_return_model2_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response No Response Model Annotation Union Return Model2 No Response Model Annotation Union Return Model2 Get", - "anyOf": [ - {"$ref": "#/components/schemas/User"}, - {"$ref": "#/components/schemas/Item"}, - ], - } - } - }, - } - }, - } - }, - "/no_response_model-annotation_response_class": { - "get": { - "summary": "No Response Model Annotation Response Class", - "operationId": "no_response_model_annotation_response_class_no_response_model_annotation_response_class_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/no_response_model-annotation_json_response_class": { - "get": { - "summary": "No Response Model Annotation Json Response Class", - "operationId": "no_response_model_annotation_json_response_class_no_response_model_annotation_json_response_class_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/no_response_model-no_annotation-return_model": { + "get": { + "summary": "No Response Model No Annotation Return Model", + "operationId": "no_response_model_no_annotation_return_model_no_response_model_no_annotation_return_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } }, - "User": { - "title": "User", - "required": ["name", "surname"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "surname": {"title": "Surname", "type": "string"}, - }, + "/no_response_model-no_annotation-return_dict": { + "get": { + "summary": "No Response Model No Annotation Return Dict", + "operationId": "no_response_model_no_annotation_return_dict_no_response_model_no_annotation_return_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } }, - } - }, - } + "/response_model-no_annotation-return_same_model": { + "get": { + "summary": "Response Model No Annotation Return Same Model", + "operationId": "response_model_no_annotation_return_same_model_response_model_no_annotation_return_same_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model-no_annotation-return_exact_dict": { + "get": { + "summary": "Response Model No Annotation Return Exact Dict", + "operationId": "response_model_no_annotation_return_exact_dict_response_model_no_annotation_return_exact_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model-no_annotation-return_invalid_dict": { + "get": { + "summary": "Response Model No Annotation Return Invalid Dict", + "operationId": "response_model_no_annotation_return_invalid_dict_response_model_no_annotation_return_invalid_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model-no_annotation-return_invalid_model": { + "get": { + "summary": "Response Model No Annotation Return Invalid Model", + "operationId": "response_model_no_annotation_return_invalid_model_response_model_no_annotation_return_invalid_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model-no_annotation-return_dict_with_extra_data": { + "get": { + "summary": "Response Model No Annotation Return Dict With Extra Data", + "operationId": "response_model_no_annotation_return_dict_with_extra_data_response_model_no_annotation_return_dict_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model-no_annotation-return_submodel_with_extra_data": { + "get": { + "summary": "Response Model No Annotation Return Submodel With Extra Data", + "operationId": "response_model_no_annotation_return_submodel_with_extra_data_response_model_no_annotation_return_submodel_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_same_model": { + "get": { + "summary": "No Response Model Annotation Return Same Model", + "operationId": "no_response_model_annotation_return_same_model_no_response_model_annotation_return_same_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_exact_dict": { + "get": { + "summary": "No Response Model Annotation Return Exact Dict", + "operationId": "no_response_model_annotation_return_exact_dict_no_response_model_annotation_return_exact_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_invalid_dict": { + "get": { + "summary": "No Response Model Annotation Return Invalid Dict", + "operationId": "no_response_model_annotation_return_invalid_dict_no_response_model_annotation_return_invalid_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_invalid_model": { + "get": { + "summary": "No Response Model Annotation Return Invalid Model", + "operationId": "no_response_model_annotation_return_invalid_model_no_response_model_annotation_return_invalid_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_dict_with_extra_data": { + "get": { + "summary": "No Response Model Annotation Return Dict With Extra Data", + "operationId": "no_response_model_annotation_return_dict_with_extra_data_no_response_model_annotation_return_dict_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/no_response_model-annotation-return_submodel_with_extra_data": { + "get": { + "summary": "No Response Model Annotation Return Submodel With Extra Data", + "operationId": "no_response_model_annotation_return_submodel_with_extra_data_no_response_model_annotation_return_submodel_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_none-annotation-return_same_model": { + "get": { + "summary": "Response Model None Annotation Return Same Model", + "operationId": "response_model_none_annotation_return_same_model_response_model_none_annotation_return_same_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_none-annotation-return_exact_dict": { + "get": { + "summary": "Response Model None Annotation Return Exact Dict", + "operationId": "response_model_none_annotation_return_exact_dict_response_model_none_annotation_return_exact_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_none-annotation-return_invalid_dict": { + "get": { + "summary": "Response Model None Annotation Return Invalid Dict", + "operationId": "response_model_none_annotation_return_invalid_dict_response_model_none_annotation_return_invalid_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_none-annotation-return_invalid_model": { + "get": { + "summary": "Response Model None Annotation Return Invalid Model", + "operationId": "response_model_none_annotation_return_invalid_model_response_model_none_annotation_return_invalid_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_none-annotation-return_dict_with_extra_data": { + "get": { + "summary": "Response Model None Annotation Return Dict With Extra Data", + "operationId": "response_model_none_annotation_return_dict_with_extra_data_response_model_none_annotation_return_dict_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_none-annotation-return_submodel_with_extra_data": { + "get": { + "summary": "Response Model None Annotation Return Submodel With Extra Data", + "operationId": "response_model_none_annotation_return_submodel_with_extra_data_response_model_none_annotation_return_submodel_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_same_model": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Same Model", + "operationId": "response_model_model1_annotation_model2_return_same_model_response_model_model1_annotation_model2_return_same_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_exact_dict": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Exact Dict", + "operationId": "response_model_model1_annotation_model2_return_exact_dict_response_model_model1_annotation_model2_return_exact_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_invalid_dict": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Invalid Dict", + "operationId": "response_model_model1_annotation_model2_return_invalid_dict_response_model_model1_annotation_model2_return_invalid_dict_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_invalid_model": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Invalid Model", + "operationId": "response_model_model1_annotation_model2_return_invalid_model_response_model_model1_annotation_model2_return_invalid_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_dict_with_extra_data": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Dict With Extra Data", + "operationId": "response_model_model1_annotation_model2_return_dict_with_extra_data_response_model_model1_annotation_model2_return_dict_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_model1-annotation_model2-return_submodel_with_extra_data": { + "get": { + "summary": "Response Model Model1 Annotation Model2 Return Submodel With Extra Data", + "operationId": "response_model_model1_annotation_model2_return_submodel_with_extra_data_response_model_model1_annotation_model2_return_submodel_with_extra_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_filtering_model-annotation_submodel-return_submodel": { + "get": { + "summary": "Response Model Filtering Model Annotation Submodel Return Submodel", + "operationId": "response_model_filtering_model_annotation_submodel_return_submodel_response_model_filtering_model_annotation_submodel_return_submodel_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + } + }, + } + }, + "/response_model_list_of_model-no_annotation": { + "get": { + "summary": "Response Model List Of Model No Annotation", + "operationId": "response_model_list_of_model_no_annotation_response_model_list_of_model_no_annotation_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Response Model List Of Model No Annotation Response Model List Of Model No Annotation Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + }, + } + } + }, + } + }, + } + }, + "/no_response_model-annotation_list_of_model": { + "get": { + "summary": "No Response Model Annotation List Of Model", + "operationId": "no_response_model_annotation_list_of_model_no_response_model_annotation_list_of_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response No Response Model Annotation List Of Model No Response Model Annotation List Of Model Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + }, + } + } + }, + } + }, + } + }, + "/no_response_model-annotation_forward_ref_list_of_model": { + "get": { + "summary": "No Response Model Annotation Forward Ref List Of Model", + "operationId": "no_response_model_annotation_forward_ref_list_of_model_no_response_model_annotation_forward_ref_list_of_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response No Response Model Annotation Forward Ref List Of Model No Response Model Annotation Forward Ref List Of Model Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + }, + } + } + }, + } + }, + } + }, + "/response_model_union-no_annotation-return_model1": { + "get": { + "summary": "Response Model Union No Annotation Return Model1", + "operationId": "response_model_union_no_annotation_return_model1_response_model_union_no_annotation_return_model1_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Response Model Union No Annotation Return Model1 Response Model Union No Annotation Return Model1 Get", + "anyOf": [ + {"$ref": "#/components/schemas/User"}, + {"$ref": "#/components/schemas/Item"}, + ], + } + } + }, + } + }, + } + }, + "/response_model_union-no_annotation-return_model2": { + "get": { + "summary": "Response Model Union No Annotation Return Model2", + "operationId": "response_model_union_no_annotation_return_model2_response_model_union_no_annotation_return_model2_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Response Model Union No Annotation Return Model2 Response Model Union No Annotation Return Model2 Get", + "anyOf": [ + {"$ref": "#/components/schemas/User"}, + {"$ref": "#/components/schemas/Item"}, + ], + } + } + }, + } + }, + } + }, + "/no_response_model-annotation_union-return_model1": { + "get": { + "summary": "No Response Model Annotation Union Return Model1", + "operationId": "no_response_model_annotation_union_return_model1_no_response_model_annotation_union_return_model1_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response No Response Model Annotation Union Return Model1 No Response Model Annotation Union Return Model1 Get", + "anyOf": [ + {"$ref": "#/components/schemas/User"}, + {"$ref": "#/components/schemas/Item"}, + ], + } + } + }, + } + }, + } + }, + "/no_response_model-annotation_union-return_model2": { + "get": { + "summary": "No Response Model Annotation Union Return Model2", + "operationId": "no_response_model_annotation_union_return_model2_no_response_model_annotation_union_return_model2_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response No Response Model Annotation Union Return Model2 No Response Model Annotation Union Return Model2 Get", + "anyOf": [ + {"$ref": "#/components/schemas/User"}, + {"$ref": "#/components/schemas/Item"}, + ], + } + } + }, + } + }, + } + }, + "/no_response_model-annotation_response_class": { + "get": { + "summary": "No Response Model Annotation Response Class", + "operationId": "no_response_model_annotation_response_class_no_response_model_annotation_response_class_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/no_response_model-annotation_json_response_class": { + "get": { + "summary": "No Response Model Annotation Json Response Class", + "operationId": "no_response_model_annotation_json_response_class_no_response_model_annotation_json_response_class_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + }, + }, + "User": { + "title": "User", + "required": ["name", "surname"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "surname": {"title": "Surname", "type": "string"}, + }, + }, + } + }, + } + ) diff --git a/tests/test_response_model_sub_types.py b/tests/test_response_model_sub_types.py index 8036bcb07..9c7096c5f 100644 --- a/tests/test_response_model_sub_types.py +++ b/tests/test_response_model_sub_types.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel @@ -47,110 +48,114 @@ def test_path_operations(): 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": { - "/valid1": { - "get": { - "summary": "Valid1", - "operationId": "valid1_valid1_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "title": "Response 500 Valid1 Valid1 Get", - "type": "integer", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/valid1": { + "get": { + "summary": "Valid1", + "operationId": "valid1_valid1_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "title": "Response 500 Valid1 Valid1 Get", + "type": "integer", + } } - } + }, }, }, - }, - } - }, - "/valid2": { - "get": { - "summary": "Valid2", - "operationId": "valid2_valid2_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "title": "Response 500 Valid2 Valid2 Get", - "type": "array", - "items": {"type": "integer"}, + } + }, + "/valid2": { + "get": { + "summary": "Valid2", + "operationId": "valid2_valid2_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "title": "Response 500 Valid2 Valid2 Get", + "type": "array", + "items": {"type": "integer"}, + } } - } + }, }, }, - }, - } - }, - "/valid3": { - "get": { - "summary": "Valid3", - "operationId": "valid3_valid3_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Model"} - } + } + }, + "/valid3": { + "get": { + "summary": "Valid3", + "operationId": "valid3_valid3_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, - }, - }, - } - }, - "/valid4": { - "get": { - "summary": "Valid4", - "operationId": "valid4_valid4_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "title": "Response 500 Valid4 Valid4 Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Model"}, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Model"} } - } + }, }, }, - }, + } + }, + "/valid4": { + "get": { + "summary": "Valid4", + "operationId": "valid4_valid4_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "title": "Response 500 Valid4 Valid4 Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Model" + }, + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Model": { + "title": "Model", + "required": ["name"], + "type": "object", + "properties": {"name": {"title": "Name", "type": "string"}}, + } } }, - }, - "components": { - "schemas": { - "Model": { - "title": "Model", - "required": ["name"], - "type": "object", - "properties": {"name": {"title": "Name", "type": "string"}}, - } - } - }, - } + } + ) diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py index 8caf6ce7a..9ec41e7e8 100644 --- a/tests/test_schema_extra_examples.py +++ b/tests/test_schema_extra_examples.py @@ -4,6 +4,7 @@ import pytest from fastapi import Body, Cookie, FastAPI, Header, Path, Query from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, ConfigDict @@ -263,592 +264,596 @@ def test_openapi_schema(): client = TestClient(app) 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": { - "/schema_extra/": { - "post": { - "summary": "Schema Extra", - "operationId": "schema_extra_schema_extra__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/schema_extra/": { + "post": { + "summary": "Schema Extra", + "operationId": "schema_extra_schema_extra__post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - }, - } - }, - "/example/": { - "post": { - "summary": "Example", - "operationId": "example_example__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "example": {"data": "Data in Body example"}, - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, - }, - }, - } - }, - "/examples/": { - "post": { - "summary": "Examples", - "operationId": "examples_examples__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - "examples": [ - {"data": "Data in Body examples, example1"}, - {"data": "Data in Body examples, example2"}, - ], - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } - }, - }, - }, - } - }, - "/example_examples/": { - "post": { - "summary": "Example Examples", - "operationId": "example_examples_example_examples__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - "examples": [ - {"data": "examples example_examples 1"}, - {"data": "examples example_examples 2"}, - ], }, - "example": {"data": "Overridden example"}, + }, + }, + } + }, + "/example/": { + "post": { + "summary": "Example", + "operationId": "example_example__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "example": {"data": "Data in Body example"}, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/examples/": { + "post": { + "summary": "Examples", + "operationId": "examples_examples__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item", + "examples": [ + {"data": "Data in Body examples, example1"}, + {"data": "Data in Body examples, example2"}, + ], + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/example_examples/": { + "post": { + "summary": "Example Examples", + "operationId": "example_examples_example_examples__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item", + "examples": [ + {"data": "examples example_examples 1"}, + {"data": "examples example_examples 2"}, + ], + }, + "example": {"data": "Overridden example"}, + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/path_example/{item_id}": { + "get": { + "summary": "Path Example", + "operationId": "path_example_path_example__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "example": "item_1", + "name": "item_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/path_examples/{item_id}": { + "get": { + "summary": "Path Examples", + "operationId": "path_examples_path_examples__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "type": "string", + "examples": ["item_1", "item_2"], + }, + "name": "item_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/path_example_examples/{item_id}": { + "get": { + "summary": "Path Example Examples", + "operationId": "path_example_examples_path_example_examples__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "Item Id", + "type": "string", + "examples": ["item_1", "item_2"], + }, + "example": "item_overridden", + "name": "item_id", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/query_example/": { + "get": { + "summary": "Query Example", + "operationId": "query_example_query_example__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + }, + "example": "query1", + "name": "data", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/query_examples/": { + "get": { + "summary": "Query Examples", + "operationId": "query_examples_query_examples__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["query1", "query2"], + }, + "name": "data", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/query_example_examples/": { + "get": { + "summary": "Query Example Examples", + "operationId": "query_example_examples_query_example_examples__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["query1", "query2"], + }, + "example": "query_overridden", + "name": "data", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/header_example/": { + "get": { + "summary": "Header Example", + "operationId": "header_example_header_example__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + }, + "example": "header1", + "name": "data", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/header_examples/": { + "get": { + "summary": "Header Examples", + "operationId": "header_examples_header_examples__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["header1", "header2"], + }, + "name": "data", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/header_example_examples/": { + "get": { + "summary": "Header Example Examples", + "operationId": "header_example_examples_header_example_examples__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["header1", "header2"], + }, + "example": "header_overridden", + "name": "data", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/cookie_example/": { + "get": { + "summary": "Cookie Example", + "operationId": "cookie_example_cookie_example__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + }, + "example": "cookie1", + "name": "data", + "in": "cookie", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/cookie_examples/": { + "get": { + "summary": "Cookie Examples", + "operationId": "cookie_examples_cookie_examples__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["cookie1", "cookie2"], + }, + "name": "data", + "in": "cookie", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/cookie_example_examples/": { + "get": { + "summary": "Cookie Example Examples", + "operationId": "cookie_example_examples_cookie_example_examples__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Data", + "examples": ["cookie1", "cookie2"], + }, + "example": "cookie_overridden", + "name": "data", + "in": "cookie", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "Item": { + "title": "Item", + "required": ["data"], + "type": "object", + "properties": {"data": {"title": "Data", "type": "string"}}, + "example": {"data": "Data in schema_extra"}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } }, - "/path_example/{item_id}": { - "get": { - "summary": "Path Example", - "operationId": "path_example_path_example__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "example": "item_1", - "name": "item_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/path_examples/{item_id}": { - "get": { - "summary": "Path Examples", - "operationId": "path_examples_path_examples__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "type": "string", - "examples": ["item_1", "item_2"], - }, - "name": "item_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/path_example_examples/{item_id}": { - "get": { - "summary": "Path Example Examples", - "operationId": "path_example_examples_path_example_examples__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "Item Id", - "type": "string", - "examples": ["item_1", "item_2"], - }, - "example": "item_overridden", - "name": "item_id", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/query_example/": { - "get": { - "summary": "Query Example", - "operationId": "query_example_query_example__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - }, - "example": "query1", - "name": "data", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/query_examples/": { - "get": { - "summary": "Query Examples", - "operationId": "query_examples_query_examples__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["query1", "query2"], - }, - "name": "data", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/query_example_examples/": { - "get": { - "summary": "Query Example Examples", - "operationId": "query_example_examples_query_example_examples__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["query1", "query2"], - }, - "example": "query_overridden", - "name": "data", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/header_example/": { - "get": { - "summary": "Header Example", - "operationId": "header_example_header_example__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - }, - "example": "header1", - "name": "data", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/header_examples/": { - "get": { - "summary": "Header Examples", - "operationId": "header_examples_header_examples__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["header1", "header2"], - }, - "name": "data", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/header_example_examples/": { - "get": { - "summary": "Header Example Examples", - "operationId": "header_example_examples_header_example_examples__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["header1", "header2"], - }, - "example": "header_overridden", - "name": "data", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/cookie_example/": { - "get": { - "summary": "Cookie Example", - "operationId": "cookie_example_cookie_example__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - }, - "example": "cookie1", - "name": "data", - "in": "cookie", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/cookie_examples/": { - "get": { - "summary": "Cookie Examples", - "operationId": "cookie_examples_cookie_examples__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["cookie1", "cookie2"], - }, - "name": "data", - "in": "cookie", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/cookie_example_examples/": { - "get": { - "summary": "Cookie Example Examples", - "operationId": "cookie_example_examples_cookie_example_examples__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Data", - "examples": ["cookie1", "cookie2"], - }, - "example": "cookie_overridden", - "name": "data", - "in": "cookie", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["data"], - "type": "object", - "properties": {"data": {"title": "Data", "type": "string"}}, - "example": {"data": "Data in schema_extra"}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + } + ) diff --git a/tests/test_security_api_key_cookie.py b/tests/test_security_api_key_cookie.py index 9bacfc56e..e37e113b8 100644 --- a/tests/test_security_api_key_cookie.py +++ b/tests/test_security_api_key_cookie.py @@ -1,6 +1,7 @@ from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyCookie from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -41,27 +42,29 @@ def test_openapi_schema(): client = TestClient(app) 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyCookie": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyCookie": []}], + } } - } - }, - "components": { - "securitySchemes": { - "APIKeyCookie": {"type": "apiKey", "name": "key", "in": "cookie"} - } - }, - } + }, + "components": { + "securitySchemes": { + "APIKeyCookie": {"type": "apiKey", "name": "key", "in": "cookie"} + } + }, + } + ) diff --git a/tests/test_security_api_key_cookie_description.py b/tests/test_security_api_key_cookie_description.py index d0cab324e..b12dce4bf 100644 --- a/tests/test_security_api_key_cookie_description.py +++ b/tests/test_security_api_key_cookie_description.py @@ -1,6 +1,7 @@ from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyCookie from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -41,32 +42,34 @@ def test_openapi_schema(): client = TestClient(app) 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyCookie": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyCookie": []}], + } } - } - }, - "components": { - "securitySchemes": { - "APIKeyCookie": { - "type": "apiKey", - "name": "key", - "in": "cookie", - "description": "An API Cookie Key", + }, + "components": { + "securitySchemes": { + "APIKeyCookie": { + "type": "apiKey", + "name": "key", + "in": "cookie", + "description": "An API Cookie Key", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_api_key_cookie_optional.py b/tests/test_security_api_key_cookie_optional.py index cb5590168..7988d8044 100644 --- a/tests/test_security_api_key_cookie_optional.py +++ b/tests/test_security_api_key_cookie_optional.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyCookie from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -47,27 +48,29 @@ def test_openapi_schema(): client = TestClient(app) 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyCookie": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyCookie": []}], + } } - } - }, - "components": { - "securitySchemes": { - "APIKeyCookie": {"type": "apiKey", "name": "key", "in": "cookie"} - } - }, - } + }, + "components": { + "securitySchemes": { + "APIKeyCookie": {"type": "apiKey", "name": "key", "in": "cookie"} + } + }, + } + ) diff --git a/tests/test_security_api_key_header.py b/tests/test_security_api_key_header.py index 3e761b150..a53a2a227 100644 --- a/tests/test_security_api_key_header.py +++ b/tests/test_security_api_key_header.py @@ -1,6 +1,7 @@ from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyHeader from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -41,27 +42,29 @@ def test_security_api_key_no_key(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyHeader": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyHeader": []}], + } } - } - }, - "components": { - "securitySchemes": { - "APIKeyHeader": {"type": "apiKey", "name": "key", "in": "header"} - } - }, - } + }, + "components": { + "securitySchemes": { + "APIKeyHeader": {"type": "apiKey", "name": "key", "in": "header"} + } + }, + } + ) diff --git a/tests/test_security_api_key_header_description.py b/tests/test_security_api_key_header_description.py index 38a1a8881..0052dbcfc 100644 --- a/tests/test_security_api_key_header_description.py +++ b/tests/test_security_api_key_header_description.py @@ -1,6 +1,7 @@ from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyHeader from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -41,32 +42,34 @@ def test_security_api_key_no_key(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyHeader": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyHeader": []}], + } } - } - }, - "components": { - "securitySchemes": { - "APIKeyHeader": { - "type": "apiKey", - "name": "key", - "in": "header", - "description": "An API Key Header", + }, + "components": { + "securitySchemes": { + "APIKeyHeader": { + "type": "apiKey", + "name": "key", + "in": "header", + "description": "An API Key Header", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_api_key_header_optional.py b/tests/test_security_api_key_header_optional.py index 6f9682a64..51abd0bb9 100644 --- a/tests/test_security_api_key_header_optional.py +++ b/tests/test_security_api_key_header_optional.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyHeader from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -46,27 +47,29 @@ def test_security_api_key_no_key(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyHeader": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyHeader": []}], + } } - } - }, - "components": { - "securitySchemes": { - "APIKeyHeader": {"type": "apiKey", "name": "key", "in": "header"} - } - }, - } + }, + "components": { + "securitySchemes": { + "APIKeyHeader": {"type": "apiKey", "name": "key", "in": "header"} + } + }, + } + ) diff --git a/tests/test_security_api_key_query.py b/tests/test_security_api_key_query.py index 11ed19468..c3cb855fc 100644 --- a/tests/test_security_api_key_query.py +++ b/tests/test_security_api_key_query.py @@ -1,6 +1,7 @@ from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyQuery from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -41,27 +42,29 @@ def test_security_api_key_no_key(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyQuery": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyQuery": []}], + } } - } - }, - "components": { - "securitySchemes": { - "APIKeyQuery": {"type": "apiKey", "name": "key", "in": "query"} - } - }, - } + }, + "components": { + "securitySchemes": { + "APIKeyQuery": {"type": "apiKey", "name": "key", "in": "query"} + } + }, + } + ) diff --git a/tests/test_security_api_key_query_description.py b/tests/test_security_api_key_query_description.py index 658798326..f775a4fea 100644 --- a/tests/test_security_api_key_query_description.py +++ b/tests/test_security_api_key_query_description.py @@ -1,6 +1,7 @@ from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyQuery from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -41,32 +42,34 @@ def test_security_api_key_no_key(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyQuery": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyQuery": []}], + } } - } - }, - "components": { - "securitySchemes": { - "APIKeyQuery": { - "type": "apiKey", - "name": "key", - "in": "query", - "description": "API Key Query", + }, + "components": { + "securitySchemes": { + "APIKeyQuery": { + "type": "apiKey", + "name": "key", + "in": "query", + "description": "API Key Query", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_api_key_query_optional.py b/tests/test_security_api_key_query_optional.py index 4cc134bd4..26fbb9ee4 100644 --- a/tests/test_security_api_key_query_optional.py +++ b/tests/test_security_api_key_query_optional.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyQuery from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -46,27 +47,29 @@ def test_security_api_key_no_key(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"APIKeyQuery": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"APIKeyQuery": []}], + } } - } - }, - "components": { - "securitySchemes": { - "APIKeyQuery": {"type": "apiKey", "name": "key", "in": "query"} - } - }, - } + }, + "components": { + "securitySchemes": { + "APIKeyQuery": {"type": "apiKey", "name": "key", "in": "query"} + } + }, + } + ) diff --git a/tests/test_security_http_base.py b/tests/test_security_http_base.py index ac38ee718..fff6f0ee5 100644 --- a/tests/test_security_http_base.py +++ b/tests/test_security_http_base.py @@ -1,6 +1,7 @@ from fastapi import FastAPI, Security from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBase from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -37,25 +38,27 @@ def test_security_http_base_no_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBase": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBase": []}], + } } - } - }, - "components": { - "securitySchemes": {"HTTPBase": {"type": "http", "scheme": "Other"}} - }, - } + }, + "components": { + "securitySchemes": {"HTTPBase": {"type": "http", "scheme": "Other"}} + }, + } + ) diff --git a/tests/test_security_http_base_description.py b/tests/test_security_http_base_description.py index 791ea59f4..b03636a61 100644 --- a/tests/test_security_http_base_description.py +++ b/tests/test_security_http_base_description.py @@ -1,6 +1,7 @@ from fastapi import FastAPI, Security from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBase from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -31,31 +32,33 @@ def test_security_http_base_no_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBase": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBase": []}], + } } - } - }, - "components": { - "securitySchemes": { - "HTTPBase": { - "type": "http", - "scheme": "Other", - "description": "Other Security Scheme", + }, + "components": { + "securitySchemes": { + "HTTPBase": { + "type": "http", + "scheme": "Other", + "description": "Other Security Scheme", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_http_base_optional.py b/tests/test_security_http_base_optional.py index dd4d76843..612a7909f 100644 --- a/tests/test_security_http_base_optional.py +++ b/tests/test_security_http_base_optional.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import FastAPI, Security from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBase from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -36,25 +37,27 @@ def test_security_http_base_no_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBase": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBase": []}], + } } - } - }, - "components": { - "securitySchemes": {"HTTPBase": {"type": "http", "scheme": "Other"}} - }, - } + }, + "components": { + "securitySchemes": {"HTTPBase": {"type": "http", "scheme": "Other"}} + }, + } + ) diff --git a/tests/test_security_http_basic_optional.py b/tests/test_security_http_basic_optional.py index 7071f381a..e94565c7b 100644 --- a/tests/test_security_http_basic_optional.py +++ b/tests/test_security_http_basic_optional.py @@ -4,6 +4,7 @@ from typing import Optional from fastapi import FastAPI, Security from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -53,25 +54,27 @@ def test_security_http_basic_non_basic_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBasic": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } } - } - }, - "components": { - "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} - }, - } + }, + "components": { + "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} + }, + } + ) diff --git a/tests/test_security_http_basic_realm.py b/tests/test_security_http_basic_realm.py index ec7371f90..0e328cdd9 100644 --- a/tests/test_security_http_basic_realm.py +++ b/tests/test_security_http_basic_realm.py @@ -3,6 +3,7 @@ from base64 import b64encode from fastapi import FastAPI, Security from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -51,25 +52,27 @@ def test_security_http_basic_non_basic_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBasic": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } } - } - }, - "components": { - "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} - }, - } + }, + "components": { + "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} + }, + } + ) diff --git a/tests/test_security_http_basic_realm_description.py b/tests/test_security_http_basic_realm_description.py index a93d5fc86..5423d9b8d 100644 --- a/tests/test_security_http_basic_realm_description.py +++ b/tests/test_security_http_basic_realm_description.py @@ -3,6 +3,7 @@ from base64 import b64encode from fastapi import FastAPI, Security from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -51,31 +52,33 @@ def test_security_http_basic_non_basic_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBasic": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } } - } - }, - "components": { - "securitySchemes": { - "HTTPBasic": { - "type": "http", - "scheme": "basic", - "description": "HTTPBasic scheme", + }, + "components": { + "securitySchemes": { + "HTTPBasic": { + "type": "http", + "scheme": "basic", + "description": "HTTPBasic scheme", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_http_bearer.py b/tests/test_security_http_bearer.py index 961b42f4d..eb94ddc3d 100644 --- a/tests/test_security_http_bearer.py +++ b/tests/test_security_http_bearer.py @@ -1,6 +1,7 @@ from fastapi import FastAPI, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -38,25 +39,27 @@ def test_security_http_bearer_incorrect_scheme_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBearer": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBearer": []}], + } } - } - }, - "components": { - "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}} - }, - } + }, + "components": { + "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}} + }, + } + ) diff --git a/tests/test_security_http_bearer_description.py b/tests/test_security_http_bearer_description.py index e16994abc..47fc14107 100644 --- a/tests/test_security_http_bearer_description.py +++ b/tests/test_security_http_bearer_description.py @@ -1,6 +1,7 @@ from fastapi import FastAPI, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -38,31 +39,33 @@ def test_security_http_bearer_incorrect_scheme_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBearer": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBearer": []}], + } } - } - }, - "components": { - "securitySchemes": { - "HTTPBearer": { - "type": "http", - "scheme": "bearer", - "description": "HTTP Bearer token scheme", + }, + "components": { + "securitySchemes": { + "HTTPBearer": { + "type": "http", + "scheme": "bearer", + "description": "HTTP Bearer token scheme", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_http_bearer_optional.py b/tests/test_security_http_bearer_optional.py index 943da2ee2..b49a6593e 100644 --- a/tests/test_security_http_bearer_optional.py +++ b/tests/test_security_http_bearer_optional.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import FastAPI, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -42,25 +43,27 @@ def test_security_http_bearer_incorrect_scheme_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBearer": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBearer": []}], + } } - } - }, - "components": { - "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}} - }, - } + }, + "components": { + "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}} + }, + } + ) diff --git a/tests/test_security_http_digest.py b/tests/test_security_http_digest.py index 3fad4c7a5..345ae9c17 100644 --- a/tests/test_security_http_digest.py +++ b/tests/test_security_http_digest.py @@ -1,6 +1,7 @@ from fastapi import FastAPI, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -40,25 +41,27 @@ def test_security_http_digest_incorrect_scheme_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPDigest": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPDigest": []}], + } } - } - }, - "components": { - "securitySchemes": {"HTTPDigest": {"type": "http", "scheme": "digest"}} - }, - } + }, + "components": { + "securitySchemes": {"HTTPDigest": {"type": "http", "scheme": "digest"}} + }, + } + ) diff --git a/tests/test_security_http_digest_description.py b/tests/test_security_http_digest_description.py index 319416a07..1219bd40e 100644 --- a/tests/test_security_http_digest_description.py +++ b/tests/test_security_http_digest_description.py @@ -1,6 +1,7 @@ from fastapi import FastAPI, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -40,31 +41,33 @@ def test_security_http_digest_incorrect_scheme_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPDigest": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPDigest": []}], + } } - } - }, - "components": { - "securitySchemes": { - "HTTPDigest": { - "type": "http", - "scheme": "digest", - "description": "HTTPDigest scheme", + }, + "components": { + "securitySchemes": { + "HTTPDigest": { + "type": "http", + "scheme": "digest", + "description": "HTTPDigest scheme", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_http_digest_optional.py b/tests/test_security_http_digest_optional.py index 0d66f9c72..97e62634d 100644 --- a/tests/test_security_http_digest_optional.py +++ b/tests/test_security_http_digest_optional.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import FastAPI, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -44,25 +45,27 @@ def test_security_http_digest_incorrect_scheme_credentials(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPDigest": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPDigest": []}], + } } - } - }, - "components": { - "securitySchemes": {"HTTPDigest": {"type": "http", "scheme": "digest"}} - }, - } + }, + "components": { + "securitySchemes": {"HTTPDigest": {"type": "http", "scheme": "digest"}} + }, + } + ) diff --git a/tests/test_security_oauth2.py b/tests/test_security_oauth2.py index bff1226ad..1c216e95d 100644 --- a/tests/test_security_oauth2.py +++ b/tests/test_security_oauth2.py @@ -2,6 +2,7 @@ import pytest from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -148,124 +149,132 @@ def test_strict_login_correct_grant_type(): 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": { - "/login": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/login": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Login", + "operationId": "login_login_post", + "requestBody": { "content": { - "application/json": { + "application/x-www-form-urlencoded": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_login_login_post" } } }, + "required": True, }, - }, - "summary": "Login", - "operationId": "login_login_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_login_post" - } + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, } }, - "required": True, - }, - } - }, - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"OAuth2": []}], - } - }, - }, - "components": { - "schemas": { - "Body_login_login_post": { - "title": "Body_login_login_post", - "required": ["grant_type", "username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "^password$", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "client_secret": { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"OAuth2": []}], + } }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Body_login_login_post": { + "title": "Body_login_login_post", + "required": ["grant_type", "username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "^password$", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": { + "title": "Scope", + "type": "string", + "default": "", + }, + "client_id": { + "title": "Client Id", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "client_secret": { + "title": "Client Secret", + "anyOf": [{"type": "string"}, {"type": "null"}], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - "read:users": "Read the users", - "write:users": "Create users", + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, - "tokenUrl": "token", - } + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + }, + "securitySchemes": { + "OAuth2": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "read:users": "Read the users", + "write:users": "Create users", + }, + "tokenUrl": "token", + } + }, + } + }, }, - }, - } + } + ) diff --git a/tests/test_security_oauth2_authorization_code_bearer.py b/tests/test_security_oauth2_authorization_code_bearer.py index 66f53ab00..1ba577e9f 100644 --- a/tests/test_security_oauth2_authorization_code_bearer.py +++ b/tests/test_security_oauth2_authorization_code_bearer.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -46,36 +47,38 @@ def test_token_with_whitespaces(): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "security": [{"OAuth2AuthorizationCodeBearer": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2AuthorizationCodeBearer": []}], + } } - } - }, - "components": { - "securitySchemes": { - "OAuth2AuthorizationCodeBearer": { - "type": "oauth2", - "flows": { - "authorizationCode": { - "authorizationUrl": "authorize", - "tokenUrl": "token", - "scopes": {}, - } - }, + }, + "components": { + "securitySchemes": { + "OAuth2AuthorizationCodeBearer": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "authorize", + "tokenUrl": "token", + "scopes": {}, + } + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_oauth2_authorization_code_bearer_description.py b/tests/test_security_oauth2_authorization_code_bearer_description.py index 5386fbbd9..73807c31a 100644 --- a/tests/test_security_oauth2_authorization_code_bearer_description.py +++ b/tests/test_security_oauth2_authorization_code_bearer_description.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -43,37 +44,39 @@ def test_token(): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "security": [{"OAuth2AuthorizationCodeBearer": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2AuthorizationCodeBearer": []}], + } } - } - }, - "components": { - "securitySchemes": { - "OAuth2AuthorizationCodeBearer": { - "type": "oauth2", - "flows": { - "authorizationCode": { - "authorizationUrl": "authorize", - "tokenUrl": "token", - "scopes": {}, - } - }, - "description": "OAuth2 Code Bearer", + }, + "components": { + "securitySchemes": { + "OAuth2AuthorizationCodeBearer": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "authorizationUrl": "authorize", + "tokenUrl": "token", + "scopes": {}, + } + }, + "description": "OAuth2 Code Bearer", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py index 5bcd5040f..cb79afdb8 100644 --- a/tests/test_security_oauth2_optional.py +++ b/tests/test_security_oauth2_optional.py @@ -4,6 +4,7 @@ import pytest from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -151,124 +152,132 @@ def test_strict_login_correct_data(): 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": { - "/login": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/login": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Login", + "operationId": "login_login_post", + "requestBody": { "content": { - "application/json": { + "application/x-www-form-urlencoded": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_login_login_post" } } }, + "required": True, }, - }, - "summary": "Login", - "operationId": "login_login_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_login_post" - } + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, } }, - "required": True, - }, - } - }, - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me_get", - "security": [{"OAuth2": []}], - } - }, - }, - "components": { - "schemas": { - "Body_login_login_post": { - "title": "Body_login_login_post", - "required": ["grant_type", "username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "^password$", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "client_secret": { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2": []}], + } }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Body_login_login_post": { + "title": "Body_login_login_post", + "required": ["grant_type", "username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "^password$", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": { + "title": "Scope", + "type": "string", + "default": "", + }, + "client_id": { + "title": "Client Id", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "client_secret": { + "title": "Client Secret", + "anyOf": [{"type": "string"}, {"type": "null"}], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - "read:users": "Read the users", - "write:users": "Create users", + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, - "tokenUrl": "token", - } + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + }, + "securitySchemes": { + "OAuth2": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "read:users": "Read the users", + "write:users": "Create users", + }, + "tokenUrl": "token", + } + }, + } + }, }, - }, - } + } + ) diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py index 0353ba4c2..b3fae37a1 100644 --- a/tests/test_security_oauth2_optional_description.py +++ b/tests/test_security_oauth2_optional_description.py @@ -4,6 +4,7 @@ import pytest from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -152,125 +153,133 @@ def test_strict_login_correct_correct_grant_type(): 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": { - "/login": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/login": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Login", + "operationId": "login_login_post", + "requestBody": { "content": { - "application/json": { + "application/x-www-form-urlencoded": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_login_login_post" } } }, + "required": True, }, - }, - "summary": "Login", - "operationId": "login_login_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_login_post" - } + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, } }, - "required": True, - }, - } - }, - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me_get", - "security": [{"OAuth2": []}], - } - }, - }, - "components": { - "schemas": { - "Body_login_login_post": { - "title": "Body_login_login_post", - "required": ["grant_type", "username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "pattern": "^password$", - "type": "string", - }, - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "client_secret": { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2": []}], + } }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Body_login_login_post": { + "title": "Body_login_login_post", + "required": ["grant_type", "username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "pattern": "^password$", + "type": "string", + }, + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "scope": { + "title": "Scope", + "type": "string", + "default": "", + }, + "client_id": { + "title": "Client Id", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "client_secret": { + "title": "Client Secret", + "anyOf": [{"type": "string"}, {"type": "null"}], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - }, - "securitySchemes": { - "OAuth2": { - "type": "oauth2", - "flows": { - "password": { - "scopes": { - "read:users": "Read the users", - "write:users": "Create users", + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, - "tokenUrl": "token", - } + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - "description": "OAuth2 security scheme", - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + }, + "securitySchemes": { + "OAuth2": { + "type": "oauth2", + "flows": { + "password": { + "scopes": { + "read:users": "Read the users", + "write:users": "Create users", + }, + "tokenUrl": "token", + } + }, + "description": "OAuth2 security scheme", + } + }, }, - }, - } + } + ) diff --git a/tests/test_security_oauth2_password_bearer_optional.py b/tests/test_security_oauth2_password_bearer_optional.py index 4c9362c3e..01e2f65ed 100644 --- a/tests/test_security_oauth2_password_bearer_optional.py +++ b/tests/test_security_oauth2_password_bearer_optional.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import FastAPI, Security from fastapi.security import OAuth2PasswordBearer from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -40,30 +41,32 @@ def test_incorrect_token(): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "security": [{"OAuth2PasswordBearer": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2PasswordBearer": []}], + } } - } - }, - "components": { - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": {"password": {"scopes": {}, "tokenUrl": "/token"}}, + }, + "components": { + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "/token"}}, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_oauth2_password_bearer_optional_description.py b/tests/test_security_oauth2_password_bearer_optional_description.py index 6e6ea846c..fec8d03a7 100644 --- a/tests/test_security_oauth2_password_bearer_optional_description.py +++ b/tests/test_security_oauth2_password_bearer_optional_description.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import FastAPI, Security from fastapi.security import OAuth2PasswordBearer from fastapi.testclient import TestClient +from inline_snapshot import snapshot app = FastAPI() @@ -44,31 +45,33 @@ def test_incorrect_token(): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "security": [{"OAuth2PasswordBearer": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2PasswordBearer": []}], + } } - } - }, - "components": { - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": {"password": {"scopes": {}, "tokenUrl": "/token"}}, - "description": "OAuth2PasswordBearer security scheme", + }, + "components": { + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "/token"}}, + "description": "OAuth2PasswordBearer security scheme", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_openid_connect.py b/tests/test_security_openid_connect.py index c9a0a8db7..2c8bcd494 100644 --- a/tests/test_security_openid_connect.py +++ b/tests/test_security_openid_connect.py @@ -1,6 +1,7 @@ from fastapi import Depends, FastAPI, Security from fastapi.security.open_id_connect_url import OpenIdConnect from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -47,30 +48,32 @@ def test_security_oauth2_password_bearer_no_header(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"OpenIdConnect": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"OpenIdConnect": []}], + } } - } - }, - "components": { - "securitySchemes": { - "OpenIdConnect": { - "type": "openIdConnect", - "openIdConnectUrl": "/openid", + }, + "components": { + "securitySchemes": { + "OpenIdConnect": { + "type": "openIdConnect", + "openIdConnectUrl": "/openid", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_openid_connect_description.py b/tests/test_security_openid_connect_description.py index d008cbc63..4f69c82ff 100644 --- a/tests/test_security_openid_connect_description.py +++ b/tests/test_security_openid_connect_description.py @@ -1,6 +1,7 @@ from fastapi import Depends, FastAPI, Security from fastapi.security.open_id_connect_url import OpenIdConnect from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -49,31 +50,33 @@ def test_security_oauth2_password_bearer_no_header(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"OpenIdConnect": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"OpenIdConnect": []}], + } } - } - }, - "components": { - "securitySchemes": { - "OpenIdConnect": { - "type": "openIdConnect", - "openIdConnectUrl": "/openid", - "description": "OpenIdConnect security scheme", + }, + "components": { + "securitySchemes": { + "OpenIdConnect": { + "type": "openIdConnect", + "openIdConnectUrl": "/openid", + "description": "OpenIdConnect security scheme", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_security_openid_connect_optional.py b/tests/test_security_openid_connect_optional.py index e817434b0..ebaf394dc 100644 --- a/tests/test_security_openid_connect_optional.py +++ b/tests/test_security_openid_connect_optional.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import Depends, FastAPI, Security from fastapi.security.open_id_connect_url import OpenIdConnect from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -52,30 +53,32 @@ def test_security_oauth2_password_bearer_no_header(): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"OpenIdConnect": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"OpenIdConnect": []}], + } } - } - }, - "components": { - "securitySchemes": { - "OpenIdConnect": { - "type": "openIdConnect", - "openIdConnectUrl": "/openid", + }, + "components": { + "securitySchemes": { + "OpenIdConnect": { + "type": "openIdConnect", + "openIdConnectUrl": "/openid", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_starlette_exception.py b/tests/test_starlette_exception.py index 2be37b8bb..95c780c0d 100644 --- a/tests/test_starlette_exception.py +++ b/tests/test_starlette_exception.py @@ -1,5 +1,6 @@ from fastapi import FastAPI, HTTPException from fastapi.testclient import TestClient +from inline_snapshot import snapshot from starlette.exceptions import HTTPException as StarletteHTTPException app = FastAPI() @@ -79,126 +80,130 @@ def test_no_body_status_code_with_detail_exception_handlers(): 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": { - "/http-no-body-statuscode-exception": { - "get": { - "operationId": "no_body_status_code_exception_http_no_body_statuscode_exception_get", - "responses": { - "200": { - "content": {"application/json": {"schema": {}}}, - "description": "Successful Response", - } - }, - "summary": "No Body Status Code Exception", - } - }, - "/http-no-body-statuscode-with-detail-exception": { - "get": { - "operationId": "no_body_status_code_with_detail_exception_http_no_body_statuscode_with_detail_exception_get", - "responses": { - "200": { - "content": {"application/json": {"schema": {}}}, - "description": "Successful Response", - } - }, - "summary": "No Body Status Code With Detail Exception", - } - }, - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/http-no-body-statuscode-exception": { + "get": { + "operationId": "no_body_status_code_exception_http_no_body_statuscode_exception_get", + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful Response", + } }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - "/starlette-items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Starlette Item", - "operationId": "read_starlette_item_starlette_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, + "summary": "No Body Status Code Exception", + } }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, + "/http-no-body-statuscode-with-detail-exception": { + "get": { + "operationId": "no_body_status_code_with_detail_exception_http_no_body_statuscode_with_detail_exception_get", + "responses": { + "200": { + "content": {"application/json": {"schema": {}}}, + "description": "Successful Response", + } + }, + "summary": "No Body Status Code With Detail Exception", + } }, - } - }, - } + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + "/starlette-items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Starlette Item", + "operationId": "read_starlette_item_starlette_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } + }, + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_sub_callbacks.py b/tests/test_sub_callbacks.py index 442e709fb..86dc4d00e 100644 --- a/tests/test_sub_callbacks.py +++ b/tests/test_sub_callbacks.py @@ -2,6 +2,7 @@ from typing import Optional from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel, HttpUrl app = FastAPI() @@ -86,210 +87,222 @@ def test_get(): def test_openapi_schema(): with client: response = client.get("/openapi.json") - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/invoices/": { - "post": { - "summary": "Create Invoice", - "description": 'Create an invoice.\n\nThis will (let\'s imagine) let the API user (some external developer) create an\ninvoice.\n\nAnd this path operation will:\n\n* Send the invoice to the client.\n* Collect the money from the client.\n* Send a notification back to the API user (the external developer), as a callback.\n * At this point is that the API will somehow send a POST request to the\n external API with the notification of the invoice event\n (e.g. "payment successful").', - "operationId": "create_invoice_invoices__post", - "parameters": [ - { - "required": False, - "schema": { - "title": "Callback Url", - "anyOf": [ - { - "type": "string", - "format": "uri", - "minLength": 1, - "maxLength": 2083, - }, - {"type": "null"}, - ], - }, - "name": "callback_url", - "in": "query", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Invoice"} + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/invoices/": { + "post": { + "summary": "Create Invoice", + "description": 'Create an invoice.\n\nThis will (let\'s imagine) let the API user (some external developer) create an\ninvoice.\n\nAnd this path operation will:\n\n* Send the invoice to the client.\n* Collect the money from the client.\n* Send a notification back to the API user (the external developer), as a callback.\n * At this point is that the API will somehow send a POST request to the\n external API with the notification of the invoice event\n (e.g. "payment successful").', + "operationId": "create_invoice_invoices__post", + "parameters": [ + { + "required": False, + "schema": { + "title": "Callback Url", + "anyOf": [ + { + "type": "string", + "format": "uri", + "minLength": 1, + "maxLength": 2083, + }, + {"type": "null"}, + ], + }, + "name": "callback_url", + "in": "query", } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Invoice" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "callbacks": { + "event_callback": { + "{$callback_url}/events/{$request.body.title}": { + "get": { + "summary": "Event Callback", + "operationId": "event_callback__callback_url__events___request_body_title__get", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Event" + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "invoice_notification": { + "{$callback_url}/invoices/{$request.body.id}": { + "post": { + "summary": "Invoice Notification", + "operationId": "invoice_notification__callback_url__invoices___request_body_id__post", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceEvent" + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceEventReceived" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, } } }, }, + } + } + }, + "components": { + "schemas": { + "Event": { + "title": "Event", + "required": ["name", "total"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "total": {"title": "Total", "type": "number"}, + }, }, - "callbacks": { - "event_callback": { - "{$callback_url}/events/{$request.body.title}": { - "get": { - "summary": "Event Callback", - "operationId": "event_callback__callback_url__events___request_body_title__get", - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Event" - } - } - }, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": {"schema": {}} - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "invoice_notification": { - "{$callback_url}/invoices/{$request.body.id}": { - "post": { - "summary": "Invoice Notification", - "operationId": "invoice_notification__callback_url__invoices___request_body_id__post", - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvoiceEvent" - } - } - }, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvoiceEventReceived" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } + }, + "Invoice": { + "title": "Invoice", + "required": ["id", "customer", "total"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "title": { + "title": "Title", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "customer": {"title": "Customer", "type": "string"}, + "total": {"title": "Total", "type": "number"}, + }, + }, + "InvoiceEvent": { + "title": "InvoiceEvent", + "required": ["description", "paid"], + "type": "object", + "properties": { + "description": { + "title": "Description", + "type": "string", + }, + "paid": {"title": "Paid", "type": "boolean"}, + }, + }, + "InvoiceEventReceived": { + "title": "InvoiceEventReceived", + "required": ["ok"], + "type": "object", + "properties": {"ok": {"title": "Ok", "type": "boolean"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [ + {"type": "string"}, + {"type": "integer"}, + ] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, }, } - } - }, - "components": { - "schemas": { - "Event": { - "title": "Event", - "required": ["name", "total"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "total": {"title": "Total", "type": "number"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - } - }, - }, - "Invoice": { - "title": "Invoice", - "required": ["id", "customer", "total"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "title": { - "title": "Title", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "customer": {"title": "Customer", "type": "string"}, - "total": {"title": "Total", "type": "number"}, - }, - }, - "InvoiceEvent": { - "title": "InvoiceEvent", - "required": ["description", "paid"], - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "paid": {"title": "Paid", "type": "boolean"}, - }, - }, - "InvoiceEventReceived": { - "title": "InvoiceEventReceived", - "required": ["ok"], - "type": "object", - "properties": {"ok": {"title": "Ok", "type": "boolean"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tuples.py b/tests/test_tuples.py index de9487df2..265388587 100644 --- a/tests/test_tuples.py +++ b/tests/test_tuples.py @@ -1,5 +1,6 @@ from fastapi import FastAPI, Form from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -83,189 +84,193 @@ def test_tuple_form_invalid(): 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": { - "/model-with-tuple/": { - "post": { - "summary": "Post Model With Tuple", - "operationId": "post_model_with_tuple_model_with_tuple__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ItemGroup"} - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/model-with-tuple/": { + "post": { + "summary": "Post Model With Tuple", + "operationId": "post_model_with_tuple_model_with_tuple__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ItemGroup"} + } + }, + "required": True, }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + } + }, + "/tuple-of-models/": { + "post": { + "summary": "Post Tuple Of Models", + "operationId": "post_tuple_of_models_tuple_of_models__post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "title": "Square", + "maxItems": 2, + "minItems": 2, + "type": "array", + "prefixItems": [ + {"$ref": "#/components/schemas/Coordinate"}, + {"$ref": "#/components/schemas/Coordinate"}, + ], } } }, + "required": True, }, - }, - } - }, - "/tuple-of-models/": { - "post": { - "summary": "Post Tuple Of Models", - "operationId": "post_tuple_of_models_tuple_of_models__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Square", - "maxItems": 2, - "minItems": 2, - "type": "array", - "prefixItems": [ - {"$ref": "#/components/schemas/Coordinate"}, - {"$ref": "#/components/schemas/Coordinate"}, - ], - } - } + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + } + }, + "/tuple-form/": { + "post": { + "summary": "Hello", + "operationId": "hello_tuple_form__post", + "requestBody": { "content": { - "application/json": { + "application/x-www-form-urlencoded": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_hello_tuple_form__post" } } }, + "required": True, }, - }, - } - }, - "/tuple-form/": { - "post": { - "summary": "Hello", - "operationId": "hello_tuple_form__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_hello_tuple_form__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - } + } + }, }, - }, - "components": { - "schemas": { - "Body_hello_tuple_form__post": { - "title": "Body_hello_tuple_form__post", - "required": ["values"], - "type": "object", - "properties": { - "values": { - "title": "Values", - "maxItems": 2, - "minItems": 2, - "type": "array", - "prefixItems": [ - {"type": "integer"}, - {"type": "integer"}, - ], - } - }, - }, - "Coordinate": { - "title": "Coordinate", - "required": ["x", "y"], - "type": "object", - "properties": { - "x": {"title": "X", "type": "number"}, - "y": {"title": "Y", "type": "number"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ItemGroup": { - "title": "ItemGroup", - "required": ["items"], - "type": "object", - "properties": { - "items": { - "title": "Items", - "type": "array", - "items": { + "components": { + "schemas": { + "Body_hello_tuple_form__post": { + "title": "Body_hello_tuple_form__post", + "required": ["values"], + "type": "object", + "properties": { + "values": { + "title": "Values", "maxItems": 2, "minItems": 2, "type": "array", "prefixItems": [ - {"type": "string"}, - {"type": "string"}, + {"type": "integer"}, + {"type": "integer"}, ], - }, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, + } }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - } - }, - } + "Coordinate": { + "title": "Coordinate", + "required": ["x", "y"], + "type": "object", + "properties": { + "x": {"title": "X", "type": "number"}, + "y": {"title": "Y", "type": "number"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ItemGroup": { + "title": "ItemGroup", + "required": ["items"], + "type": "object", + "properties": { + "items": { + "title": "Items", + "type": "array", + "items": { + "maxItems": 2, + "minItems": 2, + "type": "array", + "prefixItems": [ + {"type": "string"}, + {"type": "string"}, + ], + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial001.py b/tests/test_tutorial/test_additional_responses/test_tutorial001.py index 78ccb8442..d8d9d4130 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial001.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial001.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.additional_responses.tutorial001_py39 import app @@ -20,99 +21,107 @@ def test_path_operation_not_found(): 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": { - "/items/{item_id}": { - "get": { - "responses": { - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Message"} - } - }, - }, - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } } - } + }, + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["id", "value"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "value": {"title": "Value", "type": "string"}, - }, - }, - "Message": { - "title": "Message", - "required": ["message"], - "type": "object", - "properties": {"message": {"title": "Message", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["id", "value"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "value": {"title": "Value", "type": "string"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "Message": { + "title": "Message", + "required": ["message"], + "type": "object", + "properties": { + "message": {"title": "Message", "type": "string"} + }, }, - }, - } - }, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py index cdab56d7a..4fae59d22 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py @@ -4,6 +4,7 @@ import shutil import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from tests.utils import needs_py310 @@ -41,95 +42,99 @@ def test_path_operation_img(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Return the JSON item or an image.", - "content": { - "image/png": {}, - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Return the JSON item or an image.", + "content": { + "image/png": {}, + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + }, + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } }, }, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": { - "anyOf": [{"type": "boolean"}, {"type": "null"}], - "title": "Img", + { + "required": False, + "schema": { + "anyOf": [{"type": "boolean"}, {"type": "null"}], + "title": "Img", + }, + "name": "img", + "in": "query", }, - "name": "img", - "in": "query", - }, - ], + ], + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["id", "value"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "value": {"title": "Value", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["id", "value"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "value": {"title": "Value", "type": "string"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - }, - } - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial003.py b/tests/test_tutorial/test_additional_responses/test_tutorial003.py index fda786b39..e888819df 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial003.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial003.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.additional_responses.tutorial003_py39 import app @@ -20,103 +21,111 @@ def test_path_operation_not_found(): 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": { - "/items/{item_id}": { - "get": { - "responses": { - "404": { - "description": "The item was not found", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Message"} - } - }, - }, - "200": { - "description": "Item requested by ID", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "example": { - "id": "bar", - "value": "The bar tenders", - }, - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "404": { + "description": "The item was not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } } - } + }, + }, + "200": { + "description": "Item requested by ID", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "example": { + "id": "bar", + "value": "The bar tenders", + }, + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["id", "value"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "value": {"title": "Value", "type": "string"}, - }, - }, - "Message": { - "title": "Message", - "required": ["message"], - "type": "object", - "properties": {"message": {"title": "Message", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["id", "value"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "value": {"title": "Value", "type": "string"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "Message": { + "title": "Message", + "required": ["message"], + "type": "object", + "properties": { + "message": {"title": "Message", "type": "string"} + }, }, - }, - } - }, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py index f36d3d79c..9df326a5c 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py @@ -4,6 +4,7 @@ import shutil import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from tests.utils import needs_py310 @@ -41,98 +42,102 @@ def test_path_operation_img(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "get": { - "responses": { - "404": {"description": "Item not found"}, - "302": {"description": "The item was moved"}, - "403": {"description": "Not enough privileges"}, - "200": { - "description": "Successful Response", - "content": { - "image/png": {}, - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "404": {"description": "Item not found"}, + "302": {"description": "The item was moved"}, + "403": {"description": "Not enough privileges"}, + "200": { + "description": "Successful Response", + "content": { + "image/png": {}, + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + }, + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } }, }, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": { - "anyOf": [{"type": "boolean"}, {"type": "null"}], - "title": "Img", + { + "required": False, + "schema": { + "anyOf": [{"type": "boolean"}, {"type": "null"}], + "title": "Img", + }, + "name": "img", + "in": "query", }, - "name": "img", - "in": "query", - }, - ], + ], + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["id", "value"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "value": {"title": "Value", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["id", "value"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "value": {"title": "Value", "type": "string"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - }, - } - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py index 00574b5b0..31adaa56a 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.behind_a_proxy.tutorial001_py39 import app @@ -14,22 +15,24 @@ def test_main(): def test_openapi(): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/app": { - "get": { - "summary": "Read Main", - "operationId": "read_main_app_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/app": { + "get": { + "summary": "Read Main", + "operationId": "read_main_app_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - "servers": [{"url": "/api/v1"}], - } + }, + "servers": [{"url": "/api/v1"}], + } + ) diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py index 1a49c0dfe..56e6f2f9d 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.behind_a_proxy.tutorial002_py39 import app @@ -14,22 +15,24 @@ def test_main(): def test_openapi(): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/app": { - "get": { - "summary": "Read Main", - "operationId": "read_main_app_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/app": { + "get": { + "summary": "Read Main", + "operationId": "read_main_app_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - "servers": [{"url": "/api/v1"}], - } + }, + "servers": [{"url": "/api/v1"}], + } + ) diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py index f80563d14..18845e470 100644 --- a/tests/test_tutorial/test_bigger_applications/test_main.py +++ b/tests/test_tutorial/test_bigger_applications/test_main.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -269,334 +270,338 @@ def test_admin_invalid_header(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/users/": { - "get": { - "tags": ["users"], - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "tags": ["users"], + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, + }, + }, + } + }, + "/users/me": { + "get": { + "tags": ["users"], + "summary": "Read User Me", + "operationId": "read_user_me_users_me_get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/{username}": { + "get": { + "tags": ["users"], + "summary": "Read User", + "operationId": "read_user_users__username__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Username", "type": "string"}, + "name": "username", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/": { + "get": { + "tags": ["items"], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/{item_id}": { + "get": { + "tags": ["items"], + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, }, - } - }, - "/users/me": { - "get": { - "tags": ["users"], - "summary": "Read User Me", - "operationId": "read_user_me_users_me_get", - "parameters": [ - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "put": { + "tags": ["items", "custom"], + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", }, - }, - }, - } - }, - "/users/{username}": { - "get": { - "tags": ["users"], - "summary": "Read User", - "operationId": "read_user_users__username__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Username", "type": "string"}, - "name": "username", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", }, - }, - }, - } - }, - "/items/": { - "get": { - "tags": ["items"], - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - }, - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "404": {"description": "Not found"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", }, - }, - }, - } - }, - "/items/{item_id}": { - "get": { - "tags": ["items"], - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - }, - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "404": {"description": "Not found"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "404": {"description": "Not found"}, + "403": {"description": "Operation forbidden"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, }, }, - "put": { - "tags": ["items", "custom"], - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - }, - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "404": {"description": "Not found"}, - "403": {"description": "Operation forbidden"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "/admin/": { + "post": { + "tags": ["admin"], + "summary": "Update Admin", + "operationId": "update_admin_admin__post", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + }, + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "418": {"description": "I'm a teapot"}, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, + } + }, + "/": { + "get": { + "summary": "Root", + "operationId": "root__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Token", "type": "string"}, + "name": "token", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } }, }, - "/admin/": { - "post": { - "tags": ["admin"], - "summary": "Update Admin", - "operationId": "update_admin_admin__post", - "parameters": [ - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } }, - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "418": {"description": "I'm a teapot"}, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } }, - "/": { - "get": { - "summary": "Root", - "operationId": "root__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Token", "type": "string"}, - "name": "token", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + } + ) diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py index 9a837483f..c1877a72e 100644 --- a/tests/test_tutorial/test_body/test_tutorial001.py +++ b/tests/test_tutorial/test_body/test_tutorial001.py @@ -3,6 +3,7 @@ from unittest.mock import patch import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -260,89 +261,93 @@ def test_other_exceptions(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - }, - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - }, - } - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body/test_tutorial002.py b/tests/test_tutorial/test_body/test_tutorial002.py index e8b23e8f6..94bf213e3 100644 --- a/tests/test_tutorial/test_body/test_tutorial002.py +++ b/tests/test_tutorial/test_body/test_tutorial002.py @@ -3,6 +3,7 @@ from typing import Union import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -75,89 +76,93 @@ def test_post_with_no_data(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - }, - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - }, - } - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body/test_tutorial003.py b/tests/test_tutorial/test_body/test_tutorial003.py index 7b8b7ea89..832c211f6 100644 --- a/tests/test_tutorial/test_body/test_tutorial003.py +++ b/tests/test_tutorial/test_body/test_tutorial003.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -74,100 +75,104 @@ def test_put_with_no_data(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "put": { - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", - "type": "integer", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, + "schema": { + "title": "Item Id", + "type": "integer", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - }, - } - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body/test_tutorial004.py b/tests/test_tutorial/test_body/test_tutorial004.py index d78c760f5..1019a168c 100644 --- a/tests/test_tutorial/test_body/test_tutorial004.py +++ b/tests/test_tutorial/test_body/test_tutorial004.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -76,109 +77,113 @@ def test_put_with_no_data(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "put": { - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", - "type": "integer", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, + "schema": { + "title": "Item Id", + "type": "integer", + }, + }, + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, + "name": "q", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - }, - "name": "q", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - }, - } - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py index cb6da2908..0116dcb09 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -74,113 +75,117 @@ def test_invalid_price(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" } } }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "The description of the item", - "anyOf": [ - {"maxLength": 300, "type": "string"}, - {"type": "null"}, - ], - }, - "price": { - "title": "Price", - "exclusiveMinimum": 0.0, - "type": "number", - "description": "The price must be greater than zero", - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item"], - "type": "object", - "properties": {"item": {"$ref": "#/components/schemas/Item"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "The description of the item", + "anyOf": [ + {"maxLength": 300, "type": "string"}, + {"type": "null"}, + ], + }, + "price": { + "title": "Price", + "exclusiveMinimum": 0.0, + "type": "number", + "description": "The price must be greater than zero", + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item"], + "type": "object", + "properties": {"item": {"$ref": "#/components/schemas/Item"}}, }, - }, - } - }, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py index a4f24627b..2e8ba457b 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -67,116 +68,120 @@ def test_post_id_foo(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "maximum": 1000.0, + "minimum": 0.0, + "type": "integer", + }, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, + "name": "q", + "in": "query", + }, + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "anyOf": [ + {"$ref": "#/components/schemas/Item"}, + {"type": "null"}, + ], + "title": "Item", } } + } + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": {"title": "Price", "type": "number"}, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], }, }, }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "maximum": 1000.0, - "minimum": 0.0, - "type": "integer", + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, - "name": "item_id", - "in": "path", + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - }, - "name": "q", - "in": "query", - }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "anyOf": [ - {"$ref": "#/components/schemas/Item"}, - {"type": "null"}, - ], - "title": "Item", - } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } - } + }, }, } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py index 155bda0c9..0c94e9dd1 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -197,167 +198,169 @@ def test_post_id_foo(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == { - "info": { - "title": "FastAPI", - "version": "0.1.0", - }, - "openapi": "3.1.0", - "paths": { - "/items/{item_id}": { - "put": { - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", - "type": "integer", - }, - }, - ], - "requestBody": { - "content": { - "application/json": { + assert response.json() == snapshot( + { + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "openapi": "3.1.0", + "paths": { + "/items/{item_id}": { + "put": { + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put", + "title": "Item Id", + "type": "integer", }, }, - }, - "required": True, - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, - }, - }, - "description": "Successful Response", - }, - "422": { + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + "$ref": "#/components/schemas/Body_update_item_items__item_id__put", }, }, }, - "description": "Validation Error", + "required": True, }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Update Item", }, - "summary": "Update Item", }, }, - }, - "components": { - "schemas": { - "Body_update_item_items__item_id__put": { - "properties": { - "item": { - "$ref": "#/components/schemas/Item", - }, - "user": { - "$ref": "#/components/schemas/User", - }, - }, - "required": [ - "item", - "user", - ], - "title": "Body_update_item_items__item_id__put", - "type": "object", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + "components": { + "schemas": { + "Body_update_item_items__item_id__put": { + "properties": { + "item": { + "$ref": "#/components/schemas/Item", }, - "title": "Detail", - "type": "array", - }, - }, - "title": "HTTPValidationError", - "type": "object", - }, - "Item": { - "properties": { - "name": { - "title": "Name", - "type": "string", - }, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "User": { - "properties": { - "username": { - "title": "Username", - "type": "string", - }, - "full_name": { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - }, - "required": [ - "username", - ], - "title": "User", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], + "user": { + "$ref": "#/components/schemas/User", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "item", + "user", + ], + "title": "Body_update_item_items__item_id__put", + "type": "object", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "Item": { + "properties": { + "name": { + "title": "Name", + "type": "string", + }, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": {"title": "Price", "type": "number"}, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", + }, + "User": { + "properties": { + "username": { + "title": "Username", + "type": "string", + }, + "full_name": { + "title": "Full Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + }, + "required": [ + "username", + ], + "title": "User", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", + }, + }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py index 2f403797f..c27f3d5ba 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -102,121 +103,125 @@ def test_post_body_empty_list(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" } } }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - } }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": {"title": "Importance", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": {"title": "Price", "type": "number"}, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "full_name": { + "title": "Full Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + }, }, - }, - } - }, - } + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item", "user", "importance"], + "type": "object", + "properties": { + "item": {"$ref": "#/components/schemas/Item"}, + "user": {"$ref": "#/components/schemas/User"}, + "importance": {"title": "Importance", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py index 506e55eeb..2a39f3d71 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -159,134 +160,138 @@ def test_put_invalid_importance(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, + "name": "q", + "in": "query", + }, + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_update_item_items__item_id__put" } } }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": {"title": "Price", "type": "number"}, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, }, }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": { + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "full_name": { + "title": "Full Name", "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", }, - "name": "q", - "in": "query", }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } + }, + "Body_update_item_items__item_id__put": { + "title": "Body_update_item_items__item_id__put", + "required": ["item", "user", "importance"], + "type": "object", + "properties": { + "item": {"$ref": "#/components/schemas/Item"}, + "user": {"$ref": "#/components/schemas/User"}, + "importance": { + "title": "Importance", + "type": "integer", + "exclusiveMinimum": 0.0, + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "full_name": { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - }, - }, - "Body_update_item_items__item_id__put": { - "title": "Body_update_item_items__item_id__put", - "required": ["item", "user", "importance"], - "type": "object", - "properties": { - "item": {"$ref": "#/components/schemas/Item"}, - "user": {"$ref": "#/components/schemas/User"}, - "importance": { - "title": "Importance", - "type": "integer", - "exclusiveMinimum": 0.0, - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py index 20859d12c..d600e0767 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -131,144 +132,146 @@ def test_post_missing_required_field_in_item(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == { - "info": { - "title": "FastAPI", - "version": "0.1.0", - }, - "openapi": "3.1.0", - "paths": { - "/items/{item_id}": { - "put": { - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", - "type": "integer", - }, - }, - ], - "requestBody": { - "content": { - "application/json": { + assert response.json() == snapshot( + { + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "openapi": "3.1.0", + "paths": { + "/items/{item_id}": { + "put": { + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, "schema": { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put", + "title": "Item Id", + "type": "integer", }, }, - }, - "required": True, - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, - }, - }, - "description": "Successful Response", - }, - "422": { + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + "$ref": "#/components/schemas/Body_update_item_items__item_id__put", }, }, }, - "description": "Validation Error", + "required": True, }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Update Item", }, - "summary": "Update Item", }, }, - }, - "components": { - "schemas": { - "Body_update_item_items__item_id__put": { - "properties": { - "item": { - "$ref": "#/components/schemas/Item", - }, - }, - "required": ["item"], - "title": "Body_update_item_items__item_id__put", - "type": "object", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + "components": { + "schemas": { + "Body_update_item_items__item_id__put": { + "properties": { + "item": { + "$ref": "#/components/schemas/Item", }, - "title": "Detail", - "type": "array", }, + "required": ["item"], + "title": "Body_update_item_items__item_id__put", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "Item": { - "properties": { - "name": { - "title": "Name", - "type": "string", - }, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "title": "HTTPValidationError", + "type": "object", + }, + "Item": { + "properties": { + "name": { + "title": "Name", + "type": "string", + }, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": {"title": "Price", "type": "number"}, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", + }, + }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py index ae494350b..78e5e53a2 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py @@ -3,6 +3,7 @@ import importlib import pytest from dirty_equals import IsList from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -142,112 +143,116 @@ def test_openapi_schema(client: TestClient, mod_name: str): 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": { - "/items/{item_id}": { - "put": { - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", - "type": "integer", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, + "schema": { + "title": "Item Id", + "type": "integer", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Item", } } }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Item": { + "properties": { + "name": { + "title": "Name", + "type": "string", + }, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": { + "title": "Price", + "type": "number", + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + "tags": tags_schema, + }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "Item": { - "properties": { - "name": { - "title": "Name", - "type": "string", - }, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": { - "title": "Price", - "type": "number", - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - "tags": tags_schema, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial004.py b/tests/test_tutorial/test_body_nested_models/test_tutorial004.py index c1410330c..6a70779b6 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial004.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial004.py @@ -3,6 +3,7 @@ import importlib import pytest from dirty_equals import IsList from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -139,139 +140,143 @@ def test_put_missing_required_in_image(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "put": { - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", - "type": "integer", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, + "schema": { + "title": "Item Id", + "type": "integer", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Item", } } }, + "required": True, }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - } - } - }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Image": { - "properties": { - "url": { - "title": "Url", - "type": "string", - }, - "name": { - "title": "Name", - "type": "string", - }, - }, - "required": ["url", "name"], - "title": "Image", - "type": "object", - }, - "Item": { - "properties": { - "name": { - "title": "Name", - "type": "string", - }, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": { - "title": "Price", - "type": "number", - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - "tags": { - "title": "Tags", - "default": [], - "type": "array", - "items": {"type": "string"}, - "uniqueItems": True, - }, - "image": { - "anyOf": [ - {"$ref": "#/components/schemas/Image"}, - {"type": "null"}, - ], - }, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Image": { + "properties": { + "url": { + "title": "Url", + "type": "string", + }, + "name": { + "title": "Name", + "type": "string", }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, + "required": ["url", "name"], + "title": "Image", + "type": "object", }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "Item": { + "properties": { + "name": { + "title": "Name", + "type": "string", + }, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": { + "title": "Price", + "type": "number", + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + "tags": { + "title": "Tags", + "default": [], + "type": "array", + "items": {"type": "string"}, + "uniqueItems": True, + }, + "image": { + "anyOf": [ + {"$ref": "#/components/schemas/Image"}, + {"type": "null"}, + ], + }, + }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", }, - }, - } - }, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial005.py b/tests/test_tutorial/test_body_nested_models/test_tutorial005.py index c09e0c1b1..2ff3d4f22 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial005.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial005.py @@ -3,6 +3,7 @@ import importlib import pytest from dirty_equals import IsList from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -162,142 +163,146 @@ def test_put_wrong_url(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "put": { - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", - "type": "integer", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, + "schema": { + "title": "Item Id", + "type": "integer", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Item", } } }, + "required": True, }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - } - } - }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Image": { - "properties": { - "url": { - "title": "Url", - "type": "string", - "format": "uri", - "maxLength": 2083, - "minLength": 1, - }, - "name": { - "title": "Name", - "type": "string", - }, - }, - "required": ["url", "name"], - "title": "Image", - "type": "object", - }, - "Item": { - "properties": { - "name": { - "title": "Name", - "type": "string", - }, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": { - "title": "Price", - "type": "number", - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - "tags": { - "title": "Tags", - "default": [], - "type": "array", - "items": {"type": "string"}, - "uniqueItems": True, - }, - "image": { - "anyOf": [ - {"$ref": "#/components/schemas/Image"}, - {"type": "null"}, - ], - }, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Image": { + "properties": { + "url": { + "title": "Url", + "type": "string", + "format": "uri", + "maxLength": 2083, + "minLength": 1, + }, + "name": { + "title": "Name", + "type": "string", }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, + "required": ["url", "name"], + "title": "Image", + "type": "object", }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "Item": { + "properties": { + "name": { + "title": "Name", + "type": "string", + }, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": { + "title": "Price", + "type": "number", + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + "tags": { + "title": "Tags", + "default": [], + "type": "array", + "items": {"type": "string"}, + "uniqueItems": True, + }, + "image": { + "anyOf": [ + {"$ref": "#/components/schemas/Image"}, + {"type": "null"}, + ], + }, + }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", }, - }, - } - }, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial006.py b/tests/test_tutorial/test_body_nested_models/test_tutorial006.py index f26c50167..229216fc5 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial006.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial006.py @@ -3,6 +3,7 @@ import importlib import pytest from dirty_equals import IsList from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -122,150 +123,154 @@ def test_put_images_not_list(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "put": { - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", - "type": "integer", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, + "schema": { + "title": "Item Id", + "type": "integer", + }, + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Item", } } }, + "required": True, }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - } - } - }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Image": { - "properties": { - "url": { - "title": "Url", - "type": "string", - "format": "uri", - "maxLength": 2083, - "minLength": 1, - }, - "name": { - "title": "Name", - "type": "string", - }, - }, - "required": ["url", "name"], - "title": "Image", - "type": "object", - }, - "Item": { - "properties": { - "name": { - "title": "Name", - "type": "string", - }, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": { - "title": "Price", - "type": "number", - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - "tags": { - "title": "Tags", - "default": [], - "type": "array", - "items": {"type": "string"}, - "uniqueItems": True, - }, - "images": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/Image", - }, - "type": "array", - }, - { - "type": "null", - }, - ], - "title": "Images", - }, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Image": { + "properties": { + "url": { + "title": "Url", + "type": "string", + "format": "uri", + "maxLength": 2083, + "minLength": 1, + }, + "name": { + "title": "Name", + "type": "string", }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, + "required": ["url", "name"], + "title": "Image", + "type": "object", }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "Item": { + "properties": { + "name": { + "title": "Name", + "type": "string", + }, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": { + "title": "Price", + "type": "number", + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + "tags": { + "title": "Tags", + "default": [], + "type": "array", + "items": {"type": "string"}, + "uniqueItems": True, + }, + "images": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Image", + }, + "type": "array", + }, + { + "type": "null", + }, + ], + "title": "Images", + }, + }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", }, - }, - } - }, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial007.py b/tests/test_tutorial/test_body_nested_models/test_tutorial007.py index dac168e24..5a7763f59 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial007.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial007.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -184,163 +185,167 @@ def test_put_missing_required_in_images(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/offers/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/offers/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Create Offer", + "operationId": "create_offer_offers__post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Offer", } } }, + "required": True, }, - }, - "summary": "Create Offer", - "operationId": "create_offer_offers__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Offer", - } - } - }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Image": { - "properties": { - "url": { - "title": "Url", - "type": "string", - "format": "uri", - "maxLength": 2083, - "minLength": 1, - }, - "name": { - "title": "Name", - "type": "string", - }, - }, - "required": ["url", "name"], - "title": "Image", - "type": "object", - }, - "Item": { - "properties": { - "name": { - "title": "Name", - "type": "string", - }, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": { - "title": "Price", - "type": "number", - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - "tags": { - "title": "Tags", - "default": [], - "type": "array", - "items": {"type": "string"}, - "uniqueItems": True, - }, - "images": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/Image", - }, - "type": "array", - }, - { - "type": "null", - }, - ], - "title": "Images", - }, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "Offer": { - "properties": { - "name": { - "title": "Name", - "type": "string", - }, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": { - "title": "Price", - "type": "number", - }, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - }, - }, - "required": ["name", "price", "items"], - "title": "Offer", - "type": "object", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Image": { + "properties": { + "url": { + "title": "Url", + "type": "string", + "format": "uri", + "maxLength": 2083, + "minLength": 1, + }, + "name": { + "title": "Name", + "type": "string", }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, + "required": ["url", "name"], + "title": "Image", + "type": "object", }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "Item": { + "properties": { + "name": { + "title": "Name", + "type": "string", + }, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": { + "title": "Price", + "type": "number", + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + "tags": { + "title": "Tags", + "default": [], + "type": "array", + "items": {"type": "string"}, + "uniqueItems": True, + }, + "images": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Image", + }, + "type": "array", + }, + { + "type": "null", + }, + ], + "title": "Images", + }, + }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", }, - }, - } - }, - } + "Offer": { + "properties": { + "name": { + "title": "Name", + "type": "string", + }, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": { + "title": "Price", + "type": "number", + }, + "items": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + }, + }, + "required": ["name", "price", "items"], + "title": "Offer", + "type": "object", + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial008.py b/tests/test_tutorial/test_body_nested_models/test_tutorial008.py index 2101b7bbe..26f48f1d5 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial008.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial008.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -66,94 +67,98 @@ def test_post_not_a_list(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/images/multiple/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/images/multiple/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Create Multiple Images", + "operationId": "create_multiple_images_images_multiple__post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "title": "Images", + "type": "array", + "items": {"$ref": "#/components/schemas/Image"}, } } }, + "required": True, }, - }, - "summary": "Create Multiple Images", - "operationId": "create_multiple_images_images_multiple__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Images", - "type": "array", - "items": {"$ref": "#/components/schemas/Image"}, - } - } - }, - "required": True, - }, + } } - } - }, - "components": { - "schemas": { - "Image": { - "properties": { - "url": { - "title": "Url", - "type": "string", - "format": "uri", - "maxLength": 2083, - "minLength": 1, - }, - "name": { - "title": "Name", - "type": "string", - }, - }, - "required": ["url", "name"], - "title": "Image", - "type": "object", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Image": { + "properties": { + "url": { + "title": "Url", + "type": "string", + "format": "uri", + "maxLength": 2083, + "minLength": 1, + }, + "name": { + "title": "Name", + "type": "string", }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, + "required": ["url", "name"], + "title": "Image", + "type": "object", }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, - }, - } - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py index f7481a5f7..492dcecd2 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -43,76 +44,80 @@ def test_post_invalid_body(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/index-weights/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/index-weights/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Create Index Weights", + "operationId": "create_index_weights_index_weights__post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "title": "Weights", + "type": "object", + "additionalProperties": {"type": "number"}, } } }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, - "summary": "Create Index Weights", - "operationId": "create_index_weights_index_weights__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Weights", - "type": "object", - "additionalProperties": {"type": "number"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py index 9c6a90576..feb07b859 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -48,141 +49,145 @@ def test_put(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + "put": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + } + }, + "components": { + "schemas": { + "Item": { + "type": "object", + "title": "Item", + "properties": { + "name": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Name", + }, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "price": { + "anyOf": [{"type": "number"}, {"type": "null"}], + "title": "Price", + }, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + "default": [], }, }, }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - }, - } - }, - "components": { - "schemas": { - "Item": { - "type": "object", - "title": "Item", - "properties": { - "name": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Name", - }, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "price": { - "anyOf": [{"type": "number"}, {"type": "null"}], - "title": "Price", - }, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_body_updates/test_tutorial002.py b/tests/test_tutorial/test_body_updates/test_tutorial002.py index 7d79cf5e6..a34d08b52 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial002.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -69,141 +70,145 @@ def test_patch_name(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + }, + "patch": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Update Item", + "operationId": "update_item_items__item_id__patch", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + } + }, + "components": { + "schemas": { + "Item": { + "type": "object", + "title": "Item", + "properties": { + "name": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Name", + }, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "price": { + "anyOf": [{"type": "number"}, {"type": "null"}], + "title": "Price", + }, + "tax": {"title": "Tax", "type": "number", "default": 10.5}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + "default": [], }, }, }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "patch": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__patch", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - }, - } - }, - "components": { - "schemas": { - "Item": { - "type": "object", - "title": "Item", - "properties": { - "name": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Name", - }, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "price": { - "anyOf": [{"type": "number"}, {"type": "null"}], - "title": "Price", - }, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py index ddc282d85..644b82ad9 100644 --- a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py @@ -1,6 +1,7 @@ import importlib from fastapi.testclient import TestClient +from inline_snapshot import snapshot def get_client() -> TestClient: @@ -38,21 +39,23 @@ def test_default_openapi(): response = client.get("/redoc") assert response.status_code == 200, response.text response = client.get("/openapi.json") - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "get": { - "summary": "Root", - "operationId": "root__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Root", + "operationId": "root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py index ab7114876..4d7adf0d6 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py @@ -3,6 +3,7 @@ from types import ModuleType import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -47,75 +48,79 @@ def test_openapi_schema(mod: ModuleType): client = TestClient(mod.app) response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Ads Id", - }, - "name": "ads_id", - "in": "cookie", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Ads Id", + }, + "name": "ads_id", + "in": "cookie", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py index f1d2accef..20244bef4 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -26,21 +27,23 @@ def test_get_custom_response(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001b.py b/tests/test_tutorial/test_custom_response/test_tutorial001b.py index 3337f47d5..746801df1 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001b.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.custom_response.tutorial001b_py39 import app @@ -14,21 +15,23 @@ def test_get_custom_response(): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py index 22e2e0254..f79d38b83 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -48,21 +49,23 @@ def test_openapi_schema(client: TestClient, mod_name: str): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": response_content, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": response_content, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial005.py b/tests/test_tutorial/test_custom_response/test_tutorial005.py index fea105c28..b4919af6c 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial005.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial005.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.custom_response.tutorial005_py39 import app @@ -14,21 +15,25 @@ def test_get(): 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": { - "/": { - "get": { - "summary": "Main", - "operationId": "main__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"text/plain": {"schema": {"type": "string"}}}, - } - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Main", + "operationId": "main__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/plain": {"schema": {"type": "string"}} + }, + } + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006.py b/tests/test_tutorial/test_custom_response/test_tutorial006.py index e9e6ca108..ea2d366aa 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.custom_response.tutorial006_py39 import app @@ -14,21 +15,23 @@ def test_get(): 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": { - "/typer": { - "get": { - "summary": "Redirect Typer", - "operationId": "redirect_typer_typer_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/typer": { + "get": { + "summary": "Redirect Typer", + "operationId": "redirect_typer_typer_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006b.py b/tests/test_tutorial/test_custom_response/test_tutorial006b.py index 7ca727d2c..133a591b1 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006b.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.custom_response.tutorial006b_py39 import app @@ -14,16 +15,18 @@ def test_redirect_response_class(): 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": { - "/fastapi": { - "get": { - "summary": "Redirect Fastapi", - "operationId": "redirect_fastapi_fastapi_get", - "responses": {"307": {"description": "Successful Response"}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/fastapi": { + "get": { + "summary": "Redirect Fastapi", + "operationId": "redirect_fastapi_fastapi_get", + "responses": {"307": {"description": "Successful Response"}}, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006c.py b/tests/test_tutorial/test_custom_response/test_tutorial006c.py index e3f76c840..5b17d815d 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006c.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006c.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.custom_response.tutorial006c_py39 import app @@ -14,16 +15,18 @@ def test_redirect_status_code(): 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": { - "/pydantic": { - "get": { - "summary": "Redirect Pydantic", - "operationId": "redirect_pydantic_pydantic_get", - "responses": {"302": {"description": "Successful Response"}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/pydantic": { + "get": { + "summary": "Redirect Pydantic", + "operationId": "redirect_pydantic_pydantic_get", + "responses": {"302": {"description": "Successful Response"}}, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py index 756eacf23..a3d2cf515 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from tests.utils import needs_py310 @@ -50,89 +51,93 @@ def test_post_invalid_item(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py index de63a9476..e023271bc 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -70,135 +71,141 @@ def test_get_authors(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/authors/{author_id}/items/": { - "post": { - "summary": "Create Author Items", - "operationId": "create_author_items_authors__author_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "Author Id", "type": "string"}, - "name": "author_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/authors/{author_id}/items/": { + "post": { + "summary": "Create Author Items", + "operationId": "create_author_items_authors__author_id__items__post", + "parameters": [ + { + "required": True, + "schema": {"title": "Author Id", "type": "string"}, + "name": "author_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, + } } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Author" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/authors/": { + "get": { + "summary": "Get Authors", + "operationId": "get_authors_authors__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Get Authors Authors Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Author" + }, + } + } + }, } }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Author"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } + } + }, }, - "/authors/": { - "get": { - "summary": "Get Authors", - "operationId": "get_authors_authors__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Get Authors Authors Get", - "type": "array", - "items": { - "$ref": "#/components/schemas/Author" - }, - } - } - }, - } - }, - } - }, - }, - "components": { - "schemas": { - "Author": { - "title": "Author", - "required": ["name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - }, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", + "components": { + "schemas": { + "Author": { + "title": "Author", + "required": ["name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "title": "Items", + "type": "array", + "items": {"$ref": "#/components/schemas/Item"}, }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - } - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_debugging/test_tutorial001.py b/tests/test_tutorial/test_debugging/test_tutorial001.py index cf62c3b19..1a0ca6cad 100644 --- a/tests/test_tutorial/test_debugging/test_tutorial001.py +++ b/tests/test_tutorial/test_debugging/test_tutorial001.py @@ -5,6 +5,7 @@ import unittest import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot MOD_NAME = "docs_src.debugging.tutorial001_py39" @@ -44,21 +45,23 @@ def test_uvicorn_run_called_when_run_as_main(): # Just for coverage def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "get": { - "summary": "Root", - "operationId": "root__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Root", + "operationId": "root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, }, - }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py b/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py index 15919c63f..74f4a8f3a 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -43,148 +44,152 @@ def test_get(path, expected_status, expected_response, client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", + }, + ], + } + }, + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - }, - "name": "q", - "in": "query", + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], + }, } }, - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", - }, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, - }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, - }, - "name": "limit", - "in": "query", - }, - ], - } - }, - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + } + ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py index 96300a259..6e1cea5a0 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -87,95 +88,99 @@ def test_get(path, expected_status, expected_response, client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, + "name": "q", + "in": "query", }, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 100, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 100, + }, + "name": "limit", + "in": "query", }, - "name": "limit", - "in": "query", - }, - ], + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial005.py b/tests/test_tutorial/test_dependencies/test_tutorial005.py index e595859cb..31054744a 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial005.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial005.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -58,84 +59,88 @@ def test_get(path, cookie, expected_status, expected_response, client: TestClien def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Query", - "operationId": "read_query_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Q", + "summary": "Read Query", + "operationId": "read_query_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + }, + "name": "q", + "in": "query", }, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Last Query", + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Last Query", + }, + "name": "last_query", + "in": "cookie", }, - "name": "last_query", - "in": "cookie", - }, - ], + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py index cdea27b7c..8d6cfa2d6 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial006.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -68,78 +69,82 @@ def test_get_valid_headers(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial011.py b/tests/test_tutorial/test_dependencies/test_tutorial011.py index 3374c54b5..383422a7e 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial011.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial011.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -47,76 +48,80 @@ def test_get(path, expected_status, expected_response, client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/query-checker/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/query-checker/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Query Check", - "operationId": "read_query_check_query_checker__get", - "parameters": [ - { - "required": False, - "schema": { - "type": "string", - "default": "", - "title": "Q", + "summary": "Read Query Check", + "operationId": "read_query_check_query_checker__get", + "parameters": [ + { + "required": False, + "schema": { + "type": "string", + "default": "", + "title": "Q", + }, + "name": "q", + "in": "query", }, - "name": "q", - "in": "query", - }, - ], + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012.py b/tests/test_tutorial/test_dependencies/test_tutorial012.py index f342ff842..a15688924 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial012.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial012.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -115,114 +116,118 @@ def test_get_valid_headers_users(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/users/": { + "get": { + "summary": "Read Users", + "operationId": "read_users_users__get", + "parameters": [ + { + "required": True, + "schema": {"title": "X-Token", "type": "string"}, + "name": "x-token", + "in": "header", + }, + { + "required": True, + "schema": {"title": "X-Key", "type": "string"}, + "name": "x-key", + "in": "header", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } }, - "/users/": { - "get": { - "summary": "Read Users", - "operationId": "read_users_users__get", - "parameters": [ - { - "required": True, - "schema": {"title": "X-Token", "type": "string"}, - "name": "x-token", - "in": "header", - }, - { - "required": True, - "schema": {"title": "X-Key", "type": "string"}, - "name": "x-key", - "in": "header", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + } + ) diff --git a/tests/test_tutorial/test_encoder/test_tutorial001.py b/tests/test_tutorial/test_encoder/test_tutorial001.py index 5a4edbc66..a3d672333 100644 --- a/tests/test_tutorial/test_encoder/test_tutorial001.py +++ b/tests/test_tutorial/test_encoder/test_tutorial001.py @@ -3,6 +3,7 @@ from types import ModuleType import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -73,138 +74,140 @@ def test_put_invalid_data(client: TestClient, mod: ModuleType): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{id}": { - "put": { - "operationId": "update_item_items__id__put", - "parameters": [ - { - "in": "path", - "name": "id", - "required": True, - "schema": { - "title": "Id", - "type": "string", - }, - }, - ], - "requestBody": { - "content": { - "application/json": { + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{id}": { + "put": { + "operationId": "update_item_items__id__put", + "parameters": [ + { + "in": "path", + "name": "id", + "required": True, "schema": { - "$ref": "#/components/schemas/Item", + "title": "Id", + "type": "string", }, }, - }, - "required": True, - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, - }, - }, - "description": "Successful Response", - }, - "422": { + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + "$ref": "#/components/schemas/Item", }, }, }, - "description": "Validation Error", + "required": True, }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Update Item", }, - "summary": "Update Item", }, }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "Item": { - "properties": { - "description": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], - "title": "Description", - }, - "timestamp": { - "format": "date-time", - "title": "Timestamp", - "type": "string", - }, - "title": { - "title": "Title", - "type": "string", - }, - }, - "required": [ - "title", - "timestamp", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { + "Item": { + "properties": { + "description": { "anyOf": [ { "type": "string", }, { - "type": "integer", + "type": "null", }, ], + "title": "Description", + }, + "timestamp": { + "format": "date-time", + "title": "Timestamp", + "type": "string", + }, + "title": { + "title": "Title", + "type": "string", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "title", + "timestamp", + ], + "title": "Item", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", + }, + }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_events/test_tutorial001.py b/tests/test_tutorial/test_events/test_tutorial001.py index 48b838d5a..63215c00d 100644 --- a/tests/test_tutorial/test_events/test_tutorial001.py +++ b/tests/test_tutorial/test_events/test_tutorial001.py @@ -1,6 +1,7 @@ import pytest from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture(name="app", scope="module") @@ -21,74 +22,79 @@ def test_openapi_schema(app: FastAPI): with TestClient(app) as client: 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": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [ + {"type": "string"}, + {"type": "integer"}, + ] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_events/test_tutorial002.py b/tests/test_tutorial/test_events/test_tutorial002.py index 0612899b5..f98d8921f 100644 --- a/tests/test_tutorial/test_events/test_tutorial002.py +++ b/tests/test_tutorial/test_events/test_tutorial002.py @@ -1,6 +1,7 @@ import pytest from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture(name="app", scope="module") @@ -23,21 +24,23 @@ def test_openapi_schema(app: FastAPI): with TestClient(app) as client: 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_events/test_tutorial003.py b/tests/test_tutorial/test_events/test_tutorial003.py index aed9def7a..4fc848e11 100644 --- a/tests/test_tutorial/test_events/test_tutorial003.py +++ b/tests/test_tutorial/test_events/test_tutorial003.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.events.tutorial003_py39 import ( app, @@ -21,74 +22,79 @@ def test_openapi_schema(): with TestClient(app) as client: 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": { - "/predict": { - "get": { - "summary": "Predict", - "operationId": "predict_predict_get", - "parameters": [ - { - "required": True, - "schema": {"title": "X", "type": "number"}, - "name": "x", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/predict": { + "get": { + "summary": "Predict", + "operationId": "predict_predict_get", + "parameters": [ + { + "required": True, + "schema": {"title": "X", "type": "number"}, + "name": "x", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [ + {"type": "string"}, + {"type": "integer"}, + ] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py index 83ecb300e..bc10c888c 100644 --- a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.extending_openapi.tutorial001_py39 import app @@ -14,32 +15,34 @@ def test(): 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": "Custom title", - "summary": "This is a very custom OpenAPI schema", - "description": "Here's a longer description of the custom **OpenAPI** schema", - "version": "2.5.0", - "x-logo": { - "url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": { + "title": "Custom title", + "summary": "This is a very custom OpenAPI schema", + "description": "Here's a longer description of the custom **OpenAPI** schema", + "version": "2.5.0", + "x-logo": { + "url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" + }, }, - }, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } } - } - }, - } + }, + } + ) openapi_schema = response.json() # Request again to test the custom cache response = client.get("/openapi.json") diff --git a/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py index 998169986..4248878b5 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py @@ -3,6 +3,7 @@ import importlib import pytest from dirty_equals import IsList from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -44,115 +45,119 @@ def test_post(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/user/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserOut", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/user/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserOut", + } } - } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - "422": { - "description": "Validation Error", + "summary": "Create User", + "operationId": "create_user_user__post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/UserIn"} } }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "UserIn": { + "title": "UserIn", + "required": IsList( + "username", "password", "email", check_order=False + ), + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": { + "title": "Full Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, }, }, - "summary": "Create User", - "operationId": "create_user_user__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserIn"} + "UserOut": { + "title": "UserOut", + "required": ["username", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": { + "title": "Full Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "UserIn": { - "title": "UserIn", - "required": IsList( - "username", "password", "email", check_order=False - ), - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "email": { - "title": "Email", - "type": "string", - "format": "email", - }, - "full_name": { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - }, - }, - "UserOut": { - "title": "UserOut", - "required": ["username", "email"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": { - "title": "Email", - "type": "string", - "format": "email", - }, - "full_name": { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004.py b/tests/test_tutorial/test_extra_models/test_tutorial004.py index 073375ccc..c64371e3b 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial004.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial004.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -29,42 +30,46 @@ def test_get_items(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Items Items Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } } - } - }, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", + }, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "description"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "description"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005.py b/tests/test_tutorial/test_extra_models/test_tutorial005.py index 8e52d8d4b..43b8bae8e 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial005.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial005.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -26,29 +27,31 @@ def test_get_items(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/keyword-weights/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Read Keyword Weights Keyword Weights Get", - "type": "object", - "additionalProperties": {"type": "number"}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/keyword-weights/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Read Keyword Weights Keyword Weights Get", + "type": "object", + "additionalProperties": {"type": "number"}, + } } - } - }, - } - }, - "summary": "Read Keyword Weights", - "operationId": "read_keyword_weights_keyword_weights__get", + }, + } + }, + "summary": "Read Keyword Weights", + "operationId": "read_keyword_weights_keyword_weights__get", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py b/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py index aa65218cd..5457ad132 100644 --- a/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py +++ b/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -33,21 +34,23 @@ def test_get_path(client: TestClient, path, expected_status, expected_response): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Root", - "operationId": "root__get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Root", + "operationId": "root__get", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial001.py b/tests/test_tutorial/test_generate_clients/test_tutorial001.py index 83ae38c5b..62799d259 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial001.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -34,111 +35,119 @@ def test_get_items(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "summary": "Get Items", - "operationId": "get_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Get Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Get Items", + "operationId": "get_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Get Items Items Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } } - } - }, - } - }, - }, - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseMessage" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseMessage" + } } - } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, }, }, }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "ResponseMessage": { - "title": "ResponseMessage", - "required": ["message"], - "type": "object", - "properties": {"message": {"title": "Message", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - } - }, - } + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + }, + }, + "ResponseMessage": { + "title": "ResponseMessage", + "required": ["message"], + "type": "object", + "properties": { + "message": {"title": "Message", "type": "string"} + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial002.py b/tests/test_tutorial/test_generate_clients/test_tutorial002.py index b9255325a..f64f5f866 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial002.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial002.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.generate_clients.tutorial002_py39 import app @@ -31,159 +32,167 @@ def test_get_items(): 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": { - "/items/": { - "get": { - "tags": ["items"], - "summary": "Get Items", - "operationId": "get_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Get Items Items Get", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "tags": ["items"], + "summary": "Get Items", + "operationId": "get_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Get Items Items Get", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } } - } - }, - } - }, - }, - "post": { - "tags": ["items"], - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", + "post": { + "tags": ["items"], + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseMessage" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseMessage" + } } - } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, }, }, + "/users/": { + "post": { + "tags": ["users"], + "summary": "Create User", + "operationId": "create_user_users__post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseMessage" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, }, - "/users/": { - "post": { - "tags": ["users"], - "summary": "Create User", - "operationId": "create_user_users__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseMessage" - } - } - }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + "ResponseMessage": { + "title": "ResponseMessage", + "required": ["message"], + "type": "object", + "properties": { + "message": {"title": "Message", "type": "string"} + }, + }, + "User": { + "title": "User", + "required": ["username", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": {"title": "Email", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "ResponseMessage": { - "title": "ResponseMessage", - "required": ["message"], - "type": "object", - "properties": {"message": {"title": "Message", "type": "string"}}, - }, - "User": { - "title": "User", - "required": ["username", "email"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": {"title": "Email", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + } + ) diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial003.py b/tests/test_tutorial/test_generate_clients/test_tutorial003.py index d05484587..34ede6194 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial003.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial003.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.generate_clients.tutorial003_py39 import app @@ -31,159 +32,167 @@ def test_get_items(): 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": { - "/items/": { - "get": { - "tags": ["items"], - "summary": "Get Items", - "operationId": "items-get_items", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Items-Get Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "tags": ["items"], + "summary": "Get Items", + "operationId": "items-get_items", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "title": "Response Items-Get Items", + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + } } - } - }, - } - }, - }, - "post": { - "tags": ["items"], - "summary": "Create Item", - "operationId": "items-create_item", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", + "post": { + "tags": ["items"], + "summary": "Create Item", + "operationId": "items-create_item", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseMessage" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseMessage" + } } - } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, }, }, + "/users/": { + "post": { + "tags": ["users"], + "summary": "Create User", + "operationId": "users-create_user", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseMessage" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, }, - "/users/": { - "post": { - "tags": ["users"], - "summary": "Create User", - "operationId": "users-create_user", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseMessage" - } - } - }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + }, + "ResponseMessage": { + "title": "ResponseMessage", + "required": ["message"], + "type": "object", + "properties": { + "message": {"title": "Message", "type": "string"} + }, + }, + "User": { + "title": "User", + "required": ["username", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": {"title": "Email", "type": "string"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - }, - }, - "ResponseMessage": { - "title": "ResponseMessage", - "required": ["message"], - "type": "object", - "properties": {"message": {"title": "Message", "type": "string"}}, - }, - "User": { - "title": "User", - "required": ["username", "email"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": {"title": "Email", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + } + ) diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial004.py b/tests/test_tutorial/test_generate_clients/test_tutorial004.py index eea60e342..d04bfbfe8 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial004.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial004.py @@ -3,6 +3,8 @@ import json import pathlib from unittest.mock import patch +from inline_snapshot import snapshot + from docs_src.generate_clients import tutorial003_py39 @@ -15,218 +17,220 @@ def test_remove_tags(tmp_path: pathlib.Path): importlib.import_module("docs_src.generate_clients.tutorial004_py39") modified_openapi = json.loads(tmp_file.read_text()) - assert modified_openapi == { - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + assert modified_openapi == snapshot( + { + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "Item": { - "properties": { - "name": { - "title": "Name", - "type": "string", - }, - "price": { - "title": "Price", - "type": "number", - }, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "ResponseMessage": { - "properties": { - "message": { - "title": "Message", - "type": "string", - }, - }, - "required": [ - "message", - ], - "title": "ResponseMessage", - "type": "object", - }, - "User": { - "properties": { - "email": { - "title": "Email", - "type": "string", - }, - "username": { - "title": "Username", - "type": "string", - }, - }, - "required": [ - "username", - "email", - ], - "title": "User", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], + "Item": { + "properties": { + "name": { + "title": "Name", + "type": "string", + }, + "price": { + "title": "Price", + "type": "number", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", - }, - }, - }, - "info": { - "title": "FastAPI", - "version": "0.1.0", - }, - "openapi": "3.1.0", - "paths": { - "/items/": { - "get": { - "operationId": "get_items", - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/Item", + "ResponseMessage": { + "properties": { + "message": { + "title": "Message", + "type": "string", + }, + }, + "required": [ + "message", + ], + "title": "ResponseMessage", + "type": "object", + }, + "User": { + "properties": { + "email": { + "title": "Email", + "type": "string", + }, + "username": { + "title": "Username", + "type": "string", + }, + }, + "required": [ + "username", + "email", + ], + "title": "User", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", }, - "title": "Response Items-Get Items", - "type": "array", - }, + { + "type": "integer", + }, + ], }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", }, - "description": "Successful Response", }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "summary": "Get Items", - "tags": [ - "items", - ], - }, - "post": { - "operationId": "create_item", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - }, - }, - }, - "required": True, - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ResponseMessage", - }, - }, - }, - "description": "Successful Response", - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", - }, - }, - }, - "description": "Validation Error", - }, - }, - "summary": "Create Item", - "tags": [ - "items", - ], }, }, - "/users/": { - "post": { - "operationId": "create_user", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/User", + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "openapi": "3.1.0", + "paths": { + "/items/": { + "get": { + "operationId": "get_items", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item", + }, + "title": "Response Items-Get Items", + "type": "array", + }, + }, }, + "description": "Successful Response", }, }, - "required": True, + "summary": "Get Items", + "tags": [ + "items", + ], }, - "responses": { - "200": { + "post": { + "operationId": "create_item", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ResponseMessage", + "$ref": "#/components/schemas/Item", }, }, }, - "description": "Successful Response", + "required": True, }, - "422": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseMessage", + }, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Create Item", + "tags": [ + "items", + ], + }, + }, + "/users/": { + "post": { + "operationId": "create_user", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + "$ref": "#/components/schemas/User", }, }, }, - "description": "Validation Error", + "required": True, }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseMessage", + }, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Create User", + "tags": [ + "users", + ], }, - "summary": "Create User", - "tags": [ - "users", - ], }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_graphql/test_tutorial001.py b/tests/test_tutorial/test_graphql/test_tutorial001.py index 9ba7147b5..cc3be6a19 100644 --- a/tests/test_tutorial/test_graphql/test_tutorial001.py +++ b/tests/test_tutorial/test_graphql/test_tutorial001.py @@ -1,6 +1,7 @@ import warnings import pytest +from inline_snapshot import snapshot from starlette.testclient import TestClient warnings.filterwarnings( @@ -26,45 +27,47 @@ def test_query(client: TestClient): def test_openapi(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "info": { - "title": "FastAPI", - "version": "0.1.0", - }, - "openapi": "3.1.0", - "paths": { - "/graphql": { - "get": { - "operationId": "handle_http_get_graphql_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, + assert response.json() == snapshot( + { + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "openapi": "3.1.0", + "paths": { + "/graphql": { + "get": { + "operationId": "handle_http_get_graphql_get", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, }, + "description": "The GraphiQL integrated development environment.", + }, + "404": { + "description": "Not found if GraphiQL or query via GET are not enabled.", }, - "description": "The GraphiQL integrated development environment.", - }, - "404": { - "description": "Not found if GraphiQL or query via GET are not enabled.", }, + "summary": "Handle Http Get", }, - "summary": "Handle Http Get", - }, - "post": { - "operationId": "handle_http_post_graphql_post", - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, + "post": { + "operationId": "handle_http_post_graphql_post", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, }, + "description": "Successful Response", }, - "description": "Successful Response", }, + "summary": "Handle Http Post", }, - "summary": "Handle Http Post", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial001.py b/tests/test_tutorial/test_handling_errors/test_tutorial001.py index e22f1dafd..8283cdc73 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial001.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial001.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.handling_errors.tutorial001_py39 import app @@ -21,72 +22,76 @@ def test_get_item_not_found(): 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": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial002.py b/tests/test_tutorial/test_handling_errors/test_tutorial002.py index 991478a0f..c437693d3 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial002.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial002.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.handling_errors.tutorial002_py39 import app @@ -21,72 +22,76 @@ def test_get_item_not_found_header(): 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": { - "/items-header/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items-header/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Item Header", - "operationId": "read_item_header_items_header__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], + "summary": "Read Item Header", + "operationId": "read_item_header_items_header__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial003.py b/tests/test_tutorial/test_handling_errors/test_tutorial003.py index c303960bd..959729e53 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial003.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial003.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.handling_errors.tutorial003_py39 import app @@ -22,72 +23,76 @@ def test_get_exception(): 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": { - "/unicorns/{name}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/unicorns/{name}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Unicorn", - "operationId": "read_unicorn_unicorns__name__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Name", "type": "string"}, - "name": "name", - "in": "path", - } - ], + "summary": "Read Unicorn", + "operationId": "read_unicorn_unicorns__name__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Name", "type": "string"}, + "name": "name", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial004.py b/tests/test_tutorial/test_handling_errors/test_tutorial004.py index f6ec59b4d..16165bb3d 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial004.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial004.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.handling_errors.tutorial004_py39 import app @@ -27,72 +28,76 @@ def test_get(): 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": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial005.py b/tests/test_tutorial/test_handling_errors/test_tutorial005.py index a7fa4f0b6..af924a9f7 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial005.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial005.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.handling_errors.tutorial005_py39 import app @@ -31,81 +32,85 @@ def test_post(): 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": { - "/items/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "Item": { + "title": "Item", + "required": ["title", "size"], + "type": "object", + "properties": { + "title": {"title": "Title", "type": "string"}, + "size": {"title": "Size", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["title", "size"], - "type": "object", - "properties": { - "title": {"title": "Title", "type": "string"}, - "size": {"title": "Size", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial006.py b/tests/test_tutorial/test_handling_errors/test_tutorial006.py index 9cb57d857..4c069d81e 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial006.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial006.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.handling_errors.tutorial006_py39 import app @@ -35,72 +36,76 @@ def test_get(): 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": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_header_params/test_tutorial001.py b/tests/test_tutorial/test_header_params/test_tutorial001.py index 60342f70a..2e97a8322 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -39,75 +40,79 @@ def test(path, headers, expected_status, expected_response, client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "User-Agent", - }, - "name": "user-agent", - "in": "header", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "User-Agent", + }, + "name": "user-agent", + "in": "header", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_header_params/test_tutorial002.py b/tests/test_tutorial/test_header_params/test_tutorial002.py index f1ced99b1..ba86c55bf 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial002.py +++ b/tests/test_tutorial/test_header_params/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -50,75 +51,79 @@ def test(path, headers, expected_status, expected_response, client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Strange Header", - }, - "name": "strange_header", - "in": "header", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Strange Header", + }, + "name": "strange_header", + "in": "header", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_header_params/test_tutorial003.py b/tests/test_tutorial/test_header_params/test_tutorial003.py index 382c3ae19..30e5133c0 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial003.py +++ b/tests/test_tutorial/test_header_params/test_tutorial003.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -44,78 +45,82 @@ def test(path, headers, expected_status, expected_response, client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "X-Token", - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "X-Token", + "anyOf": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "null"}, + ], + }, + "name": "x-token", + "in": "header", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, - "name": "x-token", - "in": "header", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_metadata/test_tutorial001.py b/tests/test_tutorial/test_metadata/test_tutorial001.py index ead48577d..ba3c09ce4 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial001.py +++ b/tests/test_tutorial/test_metadata/test_tutorial001.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.metadata.tutorial001_py39 import app @@ -14,36 +15,38 @@ def test_items(): 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": "ChimichangApp", - "summary": "Deadpool's favorite app. Nuff said.", - "description": "\nChimichangApp API helps you do awesome stuff. 🚀\n\n## Items\n\nYou can **read items**.\n\n## Users\n\nYou will be able to:\n\n* **Create users** (_not implemented_).\n* **Read users** (_not implemented_).\n", - "termsOfService": "http://example.com/terms/", - "contact": { - "name": "Deadpoolio the Amazing", - "url": "http://x-force.example.com/contact/", - "email": "dp@x-force.example.com", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": { + "title": "ChimichangApp", + "summary": "Deadpool's favorite app. Nuff said.", + "description": "\nChimichangApp API helps you do awesome stuff. 🚀\n\n## Items\n\nYou can **read items**.\n\n## Users\n\nYou will be able to:\n\n* **Create users** (_not implemented_).\n* **Read users** (_not implemented_).\n", + "termsOfService": "http://example.com/terms/", + "contact": { + "name": "Deadpoolio the Amazing", + "url": "http://x-force.example.com/contact/", + "email": "dp@x-force.example.com", + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html", + }, + "version": "0.0.1", }, - "license": { - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html", - }, - "version": "0.0.1", - }, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_metadata/test_tutorial001_1.py b/tests/test_tutorial/test_metadata/test_tutorial001_1.py index 10cb35c54..933954973 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial001_1.py +++ b/tests/test_tutorial/test_metadata/test_tutorial001_1.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.metadata.tutorial001_1_py39 import app @@ -14,36 +15,38 @@ def test_items(): 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": "ChimichangApp", - "summary": "Deadpool's favorite app. Nuff said.", - "description": "\nChimichangApp API helps you do awesome stuff. 🚀\n\n## Items\n\nYou can **read items**.\n\n## Users\n\nYou will be able to:\n\n* **Create users** (_not implemented_).\n* **Read users** (_not implemented_).\n", - "termsOfService": "http://example.com/terms/", - "contact": { - "name": "Deadpoolio the Amazing", - "url": "http://x-force.example.com/contact/", - "email": "dp@x-force.example.com", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": { + "title": "ChimichangApp", + "summary": "Deadpool's favorite app. Nuff said.", + "description": "\nChimichangApp API helps you do awesome stuff. 🚀\n\n## Items\n\nYou can **read items**.\n\n## Users\n\nYou will be able to:\n\n* **Create users** (_not implemented_).\n* **Read users** (_not implemented_).\n", + "termsOfService": "http://example.com/terms/", + "contact": { + "name": "Deadpoolio the Amazing", + "url": "http://x-force.example.com/contact/", + "email": "dp@x-force.example.com", + }, + "license": { + "name": "Apache 2.0", + "identifier": "Apache-2.0", + }, + "version": "0.0.1", }, - "license": { - "name": "Apache 2.0", - "identifier": "Apache-2.0", - }, - "version": "0.0.1", - }, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_metadata/test_tutorial002.py b/tests/test_tutorial/test_metadata/test_tutorial002.py index e2814c88f..5a252475e 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial002.py +++ b/tests/test_tutorial/test_metadata/test_tutorial002.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.metadata.tutorial002_py39 import app @@ -19,24 +20,26 @@ def test_get_openapi_json_default_url(): def test_openapi_schema(): response = client.get("/api/v1/openapi.json") assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": { - "title": "FastAPI", - "version": "0.1.0", - }, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_metadata/test_tutorial003.py b/tests/test_tutorial/test_metadata/test_tutorial003.py index 085c271cd..778781e66 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial003.py +++ b/tests/test_tutorial/test_metadata/test_tutorial003.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.metadata.tutorial003_py39 import app @@ -14,27 +15,29 @@ def test_items(): 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": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - } + }, + } + ) def test_swagger_ui_default_url(): diff --git a/tests/test_tutorial/test_metadata/test_tutorial004.py b/tests/test_tutorial/test_metadata/test_tutorial004.py index 4ef93fd5e..5c309a830 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial004.py +++ b/tests/test_tutorial/test_metadata/test_tutorial004.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.metadata.tutorial004_py39 import app @@ -15,49 +16,51 @@ def test_path_operations(): 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": { - "/users/": { - "get": { - "tags": ["users"], - "summary": "Get Users", - "operationId": "get_users_users__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - "/items/": { - "get": { - "tags": ["items"], - "summary": "Get Items", - "operationId": "get_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - }, - "tags": [ - { - "name": "users", - "description": "Operations with users. The **login** logic is also here.", - }, - { - "name": "items", - "description": "Manage items. So _fancy_ they have their own docs.", - "externalDocs": { - "description": "Items external docs", - "url": "https://fastapi.tiangolo.com/", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "tags": ["users"], + "summary": "Get Users", + "operationId": "get_users_users__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/items/": { + "get": { + "tags": ["items"], + "summary": "Get Items", + "operationId": "get_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } }, }, - ], - } + "tags": [ + { + "name": "users", + "description": "Operations with users. The **login** logic is also here.", + }, + { + "name": "items", + "description": "Manage items. So _fancy_ they have their own docs.", + "externalDocs": { + "description": "Items external docs", + "url": "https://fastapi.tiangolo.com/", + }, + }, + ], + } + ) diff --git a/tests/test_tutorial/test_middleware/test_tutorial001.py b/tests/test_tutorial/test_middleware/test_tutorial001.py index cbcfd4146..18047f343 100644 --- a/tests/test_tutorial/test_middleware/test_tutorial001.py +++ b/tests/test_tutorial/test_middleware/test_tutorial001.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.middleware.tutorial001_py39 import app @@ -14,11 +15,13 @@ def test_response_headers(): 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": {}, - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "paths": {}, + } + ) diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py index e8c98e806..1e937d0c9 100644 --- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py +++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py @@ -3,6 +3,7 @@ from types import ModuleType import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from tests.utils import needs_py310 @@ -42,163 +43,167 @@ def test_dummy_callback(mod: ModuleType): def test_openapi_schema(client: TestClient): 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": { - "/invoices/": { - "post": { - "summary": "Create Invoice", - "description": 'Create an invoice.\n\nThis will (let\'s imagine) let the API user (some external developer) create an\ninvoice.\n\nAnd this path operation will:\n\n* Send the invoice to the client.\n* Collect the money from the client.\n* Send a notification back to the API user (the external developer), as a callback.\n * At this point is that the API will somehow send a POST request to the\n external API with the notification of the invoice event\n (e.g. "payment successful").', - "operationId": "create_invoice_invoices__post", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [ - { - "type": "string", - "format": "uri", - "minLength": 1, - "maxLength": 2083, - }, - {"type": "null"}, - ], - "title": "Callback Url", - }, - "name": "callback_url", - "in": "query", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Invoice"} + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/invoices/": { + "post": { + "summary": "Create Invoice", + "description": 'Create an invoice.\n\nThis will (let\'s imagine) let the API user (some external developer) create an\ninvoice.\n\nAnd this path operation will:\n\n* Send the invoice to the client.\n* Collect the money from the client.\n* Send a notification back to the API user (the external developer), as a callback.\n * At this point is that the API will somehow send a POST request to the\n external API with the notification of the invoice event\n (e.g. "payment successful").', + "operationId": "create_invoice_invoices__post", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [ + { + "type": "string", + "format": "uri", + "minLength": 1, + "maxLength": 2083, + }, + {"type": "null"}, + ], + "title": "Callback Url", + }, + "name": "callback_url", + "in": "query", } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + ], + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "schema": {"$ref": "#/components/schemas/Invoice"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "callbacks": { + "invoice_notification": { + "{$callback_url}/invoices/{$request.body.id}": { + "post": { + "summary": "Invoice Notification", + "operationId": "invoice_notification__callback_url__invoices___request_body_id__post", + "requestBody": { + "required": True, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceEvent" + } + } + }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvoiceEventReceived" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, } } - }, + } + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } }, }, - "callbacks": { - "invoice_notification": { - "{$callback_url}/invoices/{$request.body.id}": { - "post": { - "summary": "Invoice Notification", - "operationId": "invoice_notification__callback_url__invoices___request_body_id__post", - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvoiceEvent" - } - } - }, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvoiceEventReceived" - } - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - } - } + "Invoice": { + "title": "Invoice", + "required": ["id", "customer", "total"], + "type": "object", + "properties": { + "id": {"title": "Id", "type": "string"}, + "title": { + "title": "Title", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "customer": {"title": "Customer", "type": "string"}, + "total": {"title": "Total", "type": "number"}, + }, + }, + "InvoiceEvent": { + "title": "InvoiceEvent", + "required": ["description", "paid"], + "type": "object", + "properties": { + "description": {"title": "Description", "type": "string"}, + "paid": {"title": "Paid", "type": "boolean"}, + }, + }, + "InvoiceEventReceived": { + "title": "InvoiceEventReceived", + "required": ["ok"], + "type": "object", + "properties": {"ok": {"title": "Ok", "type": "boolean"}}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Invoice": { - "title": "Invoice", - "required": ["id", "customer", "total"], - "type": "object", - "properties": { - "id": {"title": "Id", "type": "string"}, - "title": { - "title": "Title", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "customer": {"title": "Customer", "type": "string"}, - "total": {"title": "Total", "type": "number"}, - }, - }, - "InvoiceEvent": { - "title": "InvoiceEvent", - "required": ["description", "paid"], - "type": "object", - "properties": { - "description": {"title": "Description", "type": "string"}, - "paid": {"title": "Paid", "type": "boolean"}, - }, - }, - "InvoiceEventReceived": { - "title": "InvoiceEventReceived", - "required": ["ok"], - "type": "object", - "properties": {"ok": {"title": "Ok", "type": "boolean"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py b/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py index c58e0fd02..0482c94bf 100644 --- a/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py +++ b/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.openapi_webhooks.tutorial001_py39 import app @@ -19,101 +20,107 @@ def test_dummy_webhook(): 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": { - "/users/": { - "get": { - "summary": "Read Users", - "operationId": "read_users_users__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - } - }, - "webhooks": { - "new-subscription": { - "post": { - "summary": "New Subscription", - "description": "When a new user subscribes to your service we'll send you a POST request with this\ndata to the URL that you register for the event `new-subscription` in the dashboard.", - "operationId": "new_subscriptionnew_subscription_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Subscription"} + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/": { + "get": { + "summary": "Read Users", + "operationId": "read_users_users__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, } }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + } + } + }, + "webhooks": { + "new-subscription": { + "post": { + "summary": "New Subscription", + "description": "When a new user subscribes to your service we'll send you a POST request with this\ndata to the URL that you register for the event `new-subscription` in the dashboard.", + "operationId": "new_subscriptionnew_subscription_post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Subscription" } } }, + "required": True, }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Subscription": { + "properties": { + "username": {"type": "string", "title": "Username"}, + "monthly_fee": {"type": "number", "title": "Monthly Fee"}, + "start_date": { + "type": "string", + "format": "date-time", + "title": "Start Date", + }, + }, + "type": "object", + "required": ["username", "monthly_fee", "start_date"], + "title": "Subscription", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Subscription": { - "properties": { - "username": {"type": "string", "title": "Username"}, - "monthly_fee": {"type": "number", "title": "Monthly Fee"}, - "start_date": { - "type": "string", - "format": "date-time", - "title": "Start Date", - }, - }, - "type": "object", - "required": ["username", "monthly_fee", "start_date"], - "title": "Subscription", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py index ee0b70710..3a4f64824 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_operation_advanced_configuration.tutorial001_py39 import app @@ -14,21 +15,23 @@ def test_get(): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "some_specific_id_you_define", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "some_specific_id_you_define", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py index f6580d72e..c26c36030 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_operation_advanced_configuration.tutorial002_py39 import app @@ -14,21 +15,23 @@ def test_get(): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py index 104554fce..f060647b2 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_operation_advanced_configuration.tutorial003_py39 import app @@ -14,8 +15,10 @@ def test_get(): 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": {}, - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": {}, + } + ) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py index 75b08a4e7..bf7934544 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -38,101 +39,105 @@ def test_query_params_str_validations(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create an item", + "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": {"title": "Price", "type": "number"}, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + "tags": { + "title": "Tags", + "uniqueItems": True, + "type": "array", + "items": {"type": "string"}, + "default": [], }, }, }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py index e2a71236f..779e54f9e 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_operation_advanced_configuration.tutorial005_py39 import app @@ -13,22 +14,24 @@ def test_get(): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "x-aperture-labs-portal": "blue", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "x-aperture-labs-portal": "blue", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py index 9484f7f57..d63b5f912 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_operation_advanced_configuration.tutorial006_py39 import app @@ -21,37 +22,39 @@ def test_post(): 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": { - "/items/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"type": "string"}, - "price": {"type": "number"}, - "description": {"type": "string"}, - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"type": "string"}, + "price": {"type": "number"}, + "description": {"type": "string"}, + }, + } } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, } }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py index d5f284e3b..ec0c91bda 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -75,41 +76,43 @@ def test_post_invalid(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/x-yaml": { - "schema": { - "title": "Item", - "required": ["name", "tags"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/x-yaml": { + "schema": { + "title": "Item", + "required": ["name", "tags"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + }, }, - }, + } } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, } }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py index de81251d0..500cb057c 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py @@ -3,6 +3,7 @@ import importlib import pytest from dirty_equals import IsList from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -45,144 +46,146 @@ def test_post_items(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "201": { - "description": "Successful Response", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "responses": { + "201": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} } - } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, }, }, }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "Item": { - "properties": { - "description": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], - "title": "Description", - }, - "name": { - "title": "Name", - "type": "string", - }, - "price": { - "title": "Price", - "type": "number", - }, - "tags": { - "default": [], - "items": { - "type": "string", - }, - "title": "Tags", - "type": "array", - "uniqueItems": True, - }, - "tax": { - "anyOf": [ - { - "type": "number", - }, - { - "type": "null", - }, - ], - "title": "Tax", - }, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { + "Item": { + "properties": { + "description": { "anyOf": [ { "type": "string", }, { - "type": "integer", + "type": "null", }, ], + "title": "Description", + }, + "name": { + "title": "Name", + "type": "string", + }, + "price": { + "title": "Price", + "type": "number", + }, + "tags": { + "default": [], + "items": { + "type": "string", + }, + "title": "Tags", + "type": "array", + "uniqueItems": True, + }, + "tax": { + "anyOf": [ + { + "type": "number", + }, + { + "type": "null", + }, + ], + "title": "Tax", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", + }, + }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py index 28e5e7d8d..1cc560cb9 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py @@ -3,6 +3,7 @@ import importlib import pytest from dirty_equals import IsList from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -57,169 +58,171 @@ def test_get_users(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "tags": ["items"], - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - }, - "post": { - "tags": ["items"], - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "tags": ["items"], + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", + "post": { + "tags": ["items"], + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} } - } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, }, }, + "/users/": { + "get": { + "tags": ["users"], + "summary": "Read Users", + "operationId": "read_users_users__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, }, - "/users/": { - "get": { - "tags": ["users"], - "summary": "Read Users", - "operationId": "read_users_users__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "Item": { - "properties": { - "description": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], - "title": "Description", - }, - "name": { - "title": "Name", - "type": "string", - }, - "price": { - "title": "Price", - "type": "number", - }, - "tags": { - "default": [], - "items": { - "type": "string", - }, - "title": "Tags", - "type": "array", - "uniqueItems": True, - }, - "tax": { - "anyOf": [ - { - "type": "number", - }, - { - "type": "null", - }, - ], - "title": "Tax", - }, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { + "Item": { + "properties": { + "description": { "anyOf": [ { "type": "string", }, { - "type": "integer", + "type": "null", }, ], + "title": "Description", + }, + "name": { + "title": "Name", + "type": "string", + }, + "price": { + "title": "Price", + "type": "number", + }, + "tags": { + "default": [], + "items": { + "type": "string", + }, + "title": "Tags", + "type": "array", + "uniqueItems": True, + }, + "tax": { + "anyOf": [ + { + "type": "number", + }, + { + "type": "null", + }, + ], + "title": "Tax", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", + }, + }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py index 5a0193adf..98319645c 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_operation_configuration.tutorial002b_py39 import app @@ -20,35 +21,37 @@ def test_get_users(): 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": { - "/items/": { - "get": { - "tags": ["items"], - "summary": "Get Items", - "operationId": "get_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "tags": ["items"], + "summary": "Get Items", + "operationId": "get_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + "/users/": { + "get": { + "tags": ["users"], + "summary": "Read Users", + "operationId": "read_users_users__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, }, - "/users/": { - "get": { - "tags": ["users"], - "summary": "Read Users", - "operationId": "read_users_users__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py index e42c3e2b7..8e86cd8a5 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py @@ -4,6 +4,7 @@ from textwrap import dedent import pytest from dirty_equals import IsList from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -66,145 +67,147 @@ def test_openapi_schema(client: TestClient, mod_name: str): 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": { - "/items/": { - "post": { - "summary": "Create an item", - "description": DESCRIPTIONS[mod_name], - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create an item", + "description": DESCRIPTIONS[mod_name], + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} } - } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, }, }, }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "Item": { - "properties": { - "description": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], - "title": "Description", - }, - "name": { - "title": "Name", - "type": "string", - }, - "price": { - "title": "Price", - "type": "number", - }, - "tags": { - "default": [], - "items": { - "type": "string", - }, - "title": "Tags", - "type": "array", - "uniqueItems": True, - }, - "tax": { - "anyOf": [ - { - "type": "number", - }, - { - "type": "null", - }, - ], - "title": "Tax", - }, - }, - "required": [ - "name", - "price", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { + "Item": { + "properties": { + "description": { "anyOf": [ { "type": "string", }, { - "type": "integer", + "type": "null", }, ], + "title": "Description", + }, + "name": { + "title": "Name", + "type": "string", + }, + "price": { + "title": "Price", + "type": "number", + }, + "tags": { + "default": [], + "items": { + "type": "string", + }, + "title": "Tags", + "type": "array", + "uniqueItems": True, + }, + "tax": { + "anyOf": [ + { + "type": "number", + }, + { + "type": "null", + }, + ], + "title": "Tax", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "name", + "price", + ], + "title": "Item", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", + }, + }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py index b684c9f5c..07152755f 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -37,101 +38,105 @@ def test_query_params_str_validations(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "The created item", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "The created item", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create an item", + "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": {"title": "Price", "type": "number"}, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + "tags": { + "title": "Tags", + "uniqueItems": True, + "type": "array", + "items": {"type": "string"}, + "default": [], }, }, }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py index 5d9c55675..5f56ab929 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py @@ -1,5 +1,6 @@ import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_operation_configuration.tutorial006_py39 import app @@ -23,49 +24,51 @@ def test_query_params_str_validations(path, expected_status, expected_response): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "tags": ["items"], - "summary": "Read Items", - "operationId": "read_items_items__get", - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "tags": ["items"], + "summary": "Read Items", + "operationId": "read_items_items__get", + } + }, + "/users/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "tags": ["users"], + "summary": "Read Users", + "operationId": "read_users_users__get", + } + }, + "/elements/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "tags": ["items"], + "summary": "Read Elements", + "operationId": "read_elements_elements__get", + "deprecated": True, + } + }, }, - "/users/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "tags": ["users"], - "summary": "Read Users", - "operationId": "read_users_users__get", - } - }, - "/elements/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "tags": ["items"], - "summary": "Read Elements", - "operationId": "read_elements_elements__get", - "deprecated": True, - } - }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_params/test_tutorial001.py b/tests/test_tutorial/test_path_params/test_tutorial001.py index f54626f33..ec598832b 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial001.py +++ b/tests/test_tutorial/test_path_params/test_tutorial001.py @@ -1,5 +1,6 @@ import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_params.tutorial001_py39 import app @@ -22,97 +23,99 @@ def test_get_items(item_id, expected_response): 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": { - "/items/{item_id}": { - "get": { - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", - }, - }, - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, + "schema": { + "title": "Item Id", }, }, - "description": "Successful Response", - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, }, }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", }, - "description": "Validation Error", }, + "summary": "Read Item", }, - "summary": "Read Item", }, }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_params/test_tutorial002.py b/tests/test_tutorial/test_path_params/test_tutorial002.py index 46da41b48..8384ec8ef 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial002.py +++ b/tests/test_tutorial/test_path_params/test_tutorial002.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_params.tutorial002_py39 import app @@ -29,98 +30,100 @@ def test_get_items_invalid_id(): 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": { - "/items/{item_id}": { - "get": { - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", - "type": "integer", - }, - }, - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, + "schema": { + "title": "Item Id", + "type": "integer", }, }, - "description": "Successful Response", - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, }, }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", }, - "description": "Validation Error", }, + "summary": "Read Item", }, - "summary": "Read Item", }, }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_params/test_tutorial003.py b/tests/test_tutorial/test_path_params/test_tutorial003.py index 6ac92c87e..432ccde49 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial003.py +++ b/tests/test_tutorial/test_path_params/test_tutorial003.py @@ -1,5 +1,6 @@ import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_params.tutorial003_py39 import app @@ -22,114 +23,116 @@ def test_get_users(user_id: str, expected_response: dict): 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": { - "/users/me": { - "get": { - "operationId": "read_user_me_users_me_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "operationId": "read_user_me_users_me_get", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, + }, + "description": "Successful Response", + }, + }, + "summary": "Read User Me", + }, + }, + "/users/{user_id}": { + "get": { + "operationId": "read_user_users__user_id__get", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": True, + "schema": { + "title": "User Id", + "type": "string", }, }, - "description": "Successful Response", + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, }, + "summary": "Read User", }, - "summary": "Read User Me", }, }, - "/users/{user_id}": { - "get": { - "operationId": "read_user_users__user_id__get", - "parameters": [ - { - "in": "path", - "name": "user_id", - "required": True, - "schema": { - "title": "User Id", + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", "type": "string", }, }, - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, - }, - }, - "description": "Successful Response", - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", - }, - }, - }, - "description": "Validation Error", - }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "summary": "Read User", }, }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", - }, - "title": "Detail", - "type": "array", - }, - }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], - }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", - }, - }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", - }, - }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_params/test_tutorial003b.py b/tests/test_tutorial/test_path_params/test_tutorial003b.py index 8e4a26a1c..1cf39eca9 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial003b.py +++ b/tests/test_tutorial/test_path_params/test_tutorial003b.py @@ -1,6 +1,7 @@ import asyncio from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_params.tutorial003b_py39 import app, read_users2 @@ -20,25 +21,27 @@ def test_read_users2(): # Just for coverage 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": { - "/users": { - "get": { - "operationId": "read_users2_users_get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users": { + "get": { + "operationId": "read_users2_users_get", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, }, + "description": "Successful Response", }, - "description": "Successful Response", }, + "summary": "Read Users2", }, - "summary": "Read Users2", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_params/test_tutorial004.py b/tests/test_tutorial/test_path_params/test_tutorial004.py index 8f460fb69..b691e821d 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial004.py +++ b/tests/test_tutorial/test_path_params/test_tutorial004.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_params.tutorial004_py39 import app @@ -22,72 +23,76 @@ def test_root_file_path(): 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": { - "/files/{file_path}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/{file_path}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read File", - "operationId": "read_file_files__file_path__get", - "parameters": [ - { - "required": True, - "schema": {"title": "File Path", "type": "string"}, - "name": "file_path", - "in": "path", - } - ], + "summary": "Read File", + "operationId": "read_file_files__file_path__get", + "parameters": [ + { + "required": True, + "schema": {"title": "File Path", "type": "string"}, + "name": "file_path", + "in": "path", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_path_params/test_tutorial005.py b/tests/test_tutorial/test_path_params/test_tutorial005.py index 3e3766e84..c60fee3f0 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial005.py +++ b/tests/test_tutorial/test_path_params/test_tutorial005.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.path_params.tutorial005_py39 import app @@ -42,78 +43,81 @@ def test_get_enums_invalid(): def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - data = response.json() - assert data == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/models/{model_name}": { - "get": { - "summary": "Get Model", - "operationId": "get_model_models__model_name__get", - "parameters": [ - { - "required": True, - "schema": {"$ref": "#/components/schemas/ModelName"}, - "name": "model_name", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/models/{model_name}": { + "get": { + "summary": "Get Model", + "operationId": "get_model_models__model_name__get", + "parameters": [ + { + "required": True, + "schema": {"$ref": "#/components/schemas/ModelName"}, + "name": "model_name", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ModelName": { + "title": "ModelName", + "enum": ["alexnet", "resnet", "lenet"], + "type": "string", + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ModelName": { - "title": "ModelName", - "enum": ["alexnet", "resnet", "lenet"], - "type": "string", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py index a4d68d01b..b43de7032 100644 --- a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -53,114 +54,116 @@ def test_read_items_invalid_item_id(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "type": "integer", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "type": "integer", + }, + "name": "item_id", + "in": "path", }, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], - "title": "Item-Query", + { + "required": False, + "schema": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "null", + }, + ], + "title": "Item-Query", + }, + "name": "item-query", + "in": "query", }, - "name": "item-query", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {}, - } - }, - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", - }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {}, + } }, }, - "description": "Validation Error", + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, }, - }, + } } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py index 37533bd22..1bb2a3ea8 100644 --- a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -66,107 +67,109 @@ def test_read_items_missing_q(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "type": "integer", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "type": "integer", + }, + "name": "item_id", + "in": "path", }, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": { - "type": "string", - "title": "Q", + { + "required": True, + "schema": { + "type": "string", + "title": "Q", + }, + "name": "q", + "in": "query", }, - "name": "q", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {}, - } - }, - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", - }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {}, + } }, }, - "description": "Validation Error", + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, }, - }, + } } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py index a9c111a59..37c16d295 100644 --- a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -80,108 +81,110 @@ def test_read_items_missing_q(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "type": "integer", - "minimum": 1, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "type": "integer", + "minimum": 1, + }, + "name": "item_id", + "in": "path", }, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": { - "type": "string", - "title": "Q", + { + "required": True, + "schema": { + "type": "string", + "title": "Q", + }, + "name": "q", + "in": "query", }, - "name": "q", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {}, - } - }, - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", - }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {}, + } }, }, - "description": "Validation Error", + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, }, - }, + } } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py index e0e976d6f..0afc9dcbc 100644 --- a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -96,109 +97,111 @@ def test_read_items_missing_q(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "type": "integer", - "exclusiveMinimum": 0, - "maximum": 1000, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 1000, + }, + "name": "item_id", + "in": "path", }, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": { - "type": "string", - "title": "Q", + { + "required": True, + "schema": { + "type": "string", + "title": "Q", + }, + "name": "q", + "in": "query", }, - "name": "q", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {}, - } - }, - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", - }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {}, + } }, }, - "description": "Validation Error", + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, }, - }, + } } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py index 2004ad1d2..e0a9694c1 100644 --- a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -104,120 +105,122 @@ def test_read_items_size_too_large(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": { - "title": "The ID of the item to get", - "type": "integer", - "minimum": 0, - "maximum": 1000, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": { + "title": "The ID of the item to get", + "type": "integer", + "minimum": 0, + "maximum": 1000, + }, + "name": "item_id", + "in": "path", }, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": { - "type": "string", - "title": "Q", + { + "required": True, + "schema": { + "type": "string", + "title": "Q", + }, + "name": "q", + "in": "query", }, - "name": "q", - "in": "query", - }, - { - "in": "query", - "name": "size", - "required": True, - "schema": { - "exclusiveMaximum": 10.5, - "exclusiveMinimum": 0, - "title": "Size", - "type": "number", - }, - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {}, - } - }, - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", - }, + { + "in": "query", + "name": "size", + "required": True, + "schema": { + "exclusiveMaximum": 10.5, + "exclusiveMinimum": 0, + "title": "Size", + "type": "number", }, }, - "description": "Validation Error", + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {}, + } + }, + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, }, - }, + } } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial001.py b/tests/test_tutorial/test_query_params/test_tutorial001.py index 84e455727..40ada0e8e 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial001.py +++ b/tests/test_tutorial/test_query_params/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -43,86 +44,90 @@ def test_read_user_item(client: TestClient, path, expected_json): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "get": { - "summary": "Read Item", - "operationId": "read_item_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Item", + "operationId": "read_item_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Limit", - "type": "integer", - "default": 10, + { + "required": False, + "schema": { + "title": "Limit", + "type": "integer", + "default": 10, + }, + "name": "limit", + "in": "query", }, - "name": "limit", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, }, }, + "description": "Validation Error", }, - "description": "Validation Error", + }, + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } }, }, } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial002.py b/tests/test_tutorial/test_query_params/test_tutorial002.py index f725c80b3..1da308a7e 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial002.py +++ b/tests/test_tutorial/test_query_params/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -42,88 +43,92 @@ def test_read_user_item(client: TestClient, path, expected_json): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": { - "title": "Q", - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", }, - "name": "q", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + { + "required": False, + "schema": { + "title": "Q", + "anyOf": [ + { + "type": "string", + }, + { + "type": "null", + }, + ], + }, + "name": "q", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, }, }, + "description": "Validation Error", }, - "description": "Validation Error", + }, + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } }, }, } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial003.py b/tests/test_tutorial/test_query_params/test_tutorial003.py index 9f1f2e6e4..9bb58ff9f 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial003.py +++ b/tests/test_tutorial/test_query_params/test_tutorial003.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -53,98 +54,102 @@ def test_read_user_item(client: TestClient, path, expected_json): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": { - "title": "Q", - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "summary": "Read Item", + "operationId": "read_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", }, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Short", - "type": "boolean", - "default": False, + { + "required": False, + "schema": { + "title": "Q", + "anyOf": [ + { + "type": "string", + }, + { + "type": "null", + }, + ], + }, + "name": "q", + "in": "query", }, - "name": "short", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + { + "required": False, + "schema": { + "title": "Short", + "type": "boolean", + "default": False, + }, + "name": "short", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, }, }, + "description": "Validation Error", }, - "description": "Validation Error", + }, + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } }, }, } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial004.py b/tests/test_tutorial/test_query_params/test_tutorial004.py index e834f973a..20aadb3ac 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial004.py +++ b/tests/test_tutorial/test_query_params/test_tutorial004.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -55,104 +56,108 @@ def test_read_user_item(client: TestClient, path, expected_json): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/users/{user_id}/items/{item_id}": { - "get": { - "summary": "Read User Item", - "operationId": "read_user_item_users__user_id__items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "User Id", "type": "integer"}, - "name": "user_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": False, - "schema": { - "title": "Q", - "anyOf": [ - { - "type": "string", - }, - { - "type": "null", - }, - ], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/{user_id}/items/{item_id}": { + "get": { + "summary": "Read User Item", + "operationId": "read_user_item_users__user_id__items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "User Id", "type": "integer"}, + "name": "user_id", + "in": "path", }, - "name": "q", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Short", - "type": "boolean", - "default": False, + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", }, - "name": "short", - "in": "query", - }, - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + { + "required": False, + "schema": { + "title": "Q", + "anyOf": [ + { + "type": "string", + }, + { + "type": "null", + }, + ], + }, + "name": "q", + "in": "query", + }, + { + "required": False, + "schema": { + "title": "Short", + "type": "boolean", + "default": False, + }, + "name": "short", + "in": "query", + }, + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, }, }, + "description": "Validation Error", }, - "description": "Validation Error", + }, + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } }, }, } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial005.py b/tests/test_tutorial/test_query_params/test_tutorial005.py index 36129dbc9..e023fe6d8 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial005.py +++ b/tests/test_tutorial/test_query_params/test_tutorial005.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.query_params.tutorial005_py39 import app @@ -29,78 +30,82 @@ def test_foo_no_needy(): def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read User Item", - "operationId": "read_user_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Needy", "type": "string"}, - "name": "needy", - "in": "query", - }, - ], + "summary": "Read User Item", + "operationId": "read_user_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", + }, + { + "required": True, + "schema": {"title": "Needy", "type": "string"}, + "name": "needy", + "in": "query", + }, + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py index 473dc3366..b28e12655 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial006.py +++ b/tests/test_tutorial/test_query_params/test_tutorial006.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -61,97 +62,101 @@ def test_foo_no_needy(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read User Item", - "operationId": "read_user_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - }, - { - "required": True, - "schema": {"title": "Needy", "type": "string"}, - "name": "needy", - "in": "query", - }, - { - "required": False, - "schema": { - "title": "Skip", - "type": "integer", - "default": 0, + "summary": "Read User Item", + "operationId": "read_user_item_items__item_id__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "string"}, + "name": "item_id", + "in": "path", }, - "name": "skip", - "in": "query", - }, - { - "required": False, - "schema": { - "anyOf": [{"type": "integer"}, {"type": "null"}], - "title": "Limit", + { + "required": True, + "schema": {"title": "Needy", "type": "string"}, + "name": "needy", + "in": "query", }, - "name": "limit", - "in": "query", - }, - ], + { + "required": False, + "schema": { + "title": "Skip", + "type": "integer", + "default": 0, + }, + "name": "skip", + "in": "query", + }, + { + "required": False, + "schema": { + "anyOf": [{"type": "integer"}, {"type": "null"}], + "title": "Limit", + }, + "name": "limit", + "in": "query", + }, + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py index 069921629..ed73b7329 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -46,78 +47,82 @@ def test_query_params_str_validations_q_query(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [ - {"type": "string"}, - {"type": "null"}, - ], - "title": "Q", - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [ + {"type": "string"}, + {"type": "null"}, + ], + "title": "Q", + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py index a043b5b2e..3eac1f2b3 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -64,81 +65,85 @@ def test_query_params_str_validations_q_too_long(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [ - { - "type": "string", - "maxLength": 50, - }, - {"type": "null"}, - ], - "title": "Q", - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [ + { + "type": "string", + "maxLength": 50, + }, + {"type": "null"}, + ], + "title": "Q", + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py index 68c6e6174..59d5160ac 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -74,82 +75,86 @@ def test_query_params_str_validations_q_too_long(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - }, - {"type": "null"}, - ], - "title": "Q", - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [ + { + "type": "string", + "minLength": 3, + "maxLength": 50, + }, + {"type": "null"}, + ], + "title": "Q", + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py index 79538f952..abf08c932 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -67,83 +68,87 @@ def test_query_params_str_validations_q_nonregexquery(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Q", - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [ + { + "type": "string", + "minLength": 3, + "maxLength": 50, + "pattern": "^fixedquery$", + }, + {"type": "null"}, + ], + "title": "Q", + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py index fafbd0a7d..7b5368abc 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -57,77 +58,81 @@ def test_query_params_str_validations_q_short(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "type": "string", - "default": "fixedquery", - "minLength": 3, - "title": "Q", - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "type": "string", + "default": "fixedquery", + "minLength": 3, + "title": "Q", + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py index 1d01492c6..2c1df2c08 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -63,76 +64,80 @@ def test_query_params_str_validations_q_fixedquery_too_short(client: TestClient) def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": { - "type": "string", - "minLength": 3, - "title": "Q", - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": { + "type": "string", + "minLength": 3, + "title": "Q", + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py index d31cb5036..3df9efa83 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -73,78 +74,82 @@ def test_query_params_str_validations_q_short(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": True, - "schema": { - "anyOf": [ - {"type": "string", "minLength": 3}, - {"type": "null"}, - ], - "title": "Q", - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": True, + "schema": { + "anyOf": [ + {"type": "string", "minLength": 3}, + {"type": "null"}, + ], + "title": "Q", + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py index e03090245..874002b17 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -58,81 +59,85 @@ def test_query_params_str_validations_q_fixedquery_too_short(client: TestClient) def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [ - { - "type": "string", - "minLength": 3, - }, - {"type": "null"}, - ], - "title": "Query string", - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [ + { + "type": "string", + "minLength": 3, + }, + {"type": "null"}, + ], + "title": "Query string", + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py index 186de5e06..b9613a17b 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -58,83 +59,87 @@ def test_query_params_str_validations_q_fixedquery_too_short(client: TestClient) def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "schema": { - "anyOf": [ - { - "type": "string", - "minLength": 3, - }, - {"type": "null"}, - ], - "title": "Query string", + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { "description": "Query string for the items to search in the database that have a good match", - }, - "name": "q", - "in": "query", - } - ], + "required": False, + "schema": { + "anyOf": [ + { + "type": "string", + "minLength": 3, + }, + {"type": "null"}, + ], + "title": "Query string", + "description": "Query string for the items to search in the database that have a good match", + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py index b242a75c9..d749d85f7 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -48,78 +49,82 @@ def test_query_params_str_validations_q_fixedquery(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "schema": { - "anyOf": [ - {"type": "string"}, - {"type": "null"}, - ], - "title": "Item-Query", - }, - "required": False, - "name": "item-query", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "schema": { + "anyOf": [ + {"type": "string"}, + {"type": "null"}, + ], + "title": "Item-Query", + }, + "required": False, + "name": "item-query", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py index 6a39130af..395f79518 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py @@ -3,6 +3,7 @@ import importlib import pytest from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -65,92 +66,96 @@ def test_query_params_str_validations_item_query_nonregexquery(client: TestClien def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "description": "Query string for the items to search in the database that have a good match", - "required": False, - "deprecated": True, - "schema": { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { "description": "Query string for the items to search in the database that have a good match", - # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. - **( - {"deprecated": True} - if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) - else {} - ), - }, - "name": "item-query", - "in": "query", - } - ], + "required": False, + "deprecated": True, + "schema": { + "anyOf": [ + { + "type": "string", + "minLength": 3, + "maxLength": 50, + "pattern": "^fixedquery$", + }, + {"type": "null"}, + ], + "title": "Query string", + "description": "Query string for the items to search in the database that have a good match", + # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. + **( + {"deprecated": True} + if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) + else {} + ), + }, + "name": "item-query", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py index 6ab279bf3..c25357fdd 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -41,78 +42,82 @@ def test_query_no_values(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "anyOf": [ - {"type": "array", "items": {"type": "string"}}, - {"type": "null"}, - ], - "title": "Q", - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "anyOf": [ + {"type": "array", "items": {"type": "string"}}, + {"type": "null"}, + ], + "title": "Q", + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py index 41bfeb3a7..c3d01e90e 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -37,77 +38,81 @@ def test_multi_query_values(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {"type": "string"}, - "default": ["foo", "bar"], - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {"type": "string"}, + "default": ["foo", "bar"], + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py index 52c8147ff..74efa4695 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -37,77 +38,81 @@ def test_query_no_values(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Q", - "type": "array", - "items": {}, - "default": [], - }, - "name": "q", - "in": "query", - } - ], + "summary": "Read Items", + "operationId": "read_items_items__get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Q", + "type": "array", + "items": {}, + "default": [], + }, + "name": "q", + "in": "query", + } + ], + } } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py index bb168f0fc..6dff18b78 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -39,64 +40,68 @@ def test_no_hidden_query(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py index db9b83b31..fd5c3b055 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -82,122 +83,134 @@ def test_post_upload_file(tmp_path, client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { "content": { - "application/json": { + "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_create_file_files__post" } } }, + "required": True, }, - }, - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } + } + }, + "/uploadfile/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "required": True, - }, - } - }, - "/uploadfile/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { "content": { - "application/json": { + "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" } } }, + "required": True, }, - }, - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } + } + }, + }, + "components": { + "schemas": { + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "format": "binary", + } + }, + }, + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "format": "binary", + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } }, - }, - "components": { - "schemas": { - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"} - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + } + ) diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py index 903452ac7..5ef8df178 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -41,132 +42,136 @@ def test_post_upload_file(tmp_path, client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/files/": { - "post": { - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { "content": { - "application/json": { + "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_create_file_files__post" } } }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/uploadfile/": { + "post": { + "summary": "Create Upload File", + "operationId": "create_upload_file_uploadfile__post", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "description": "A file read as bytes", + "format": "binary", + } + }, + }, + "Body_create_upload_file_uploadfile__post": { + "title": "Body_create_upload_file_uploadfile__post", + "required": ["file"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "description": "A file read as UploadFile", + "format": "binary", + } + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } }, - "/uploadfile/": { - "post": { - "summary": "Create Upload File", - "operationId": "create_upload_file_uploadfile__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_file_uploadfile__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as bytes", - "format": "binary", - } - }, - }, - "Body_create_upload_file_uploadfile__post": { - "title": "Body_create_upload_file_uploadfile__post", - "required": ["file"], - "type": "object", - "properties": { - "file": { - "title": "File", - "type": "string", - "description": "A file read as UploadFile", - "format": "binary", - } - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + } + ) diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py index 4d9ff0e93..52655fd63 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002.py @@ -3,6 +3,7 @@ import importlib import pytest from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -102,142 +103,146 @@ def test_get_root(app: FastAPI): def test_openapi_schema(client: TestClient): 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": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Create Files", + "operationId": "create_files_files__post", + "requestBody": { "content": { - "application/json": { + "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_create_files_files__post" } } }, + "required": True, }, - }, - "summary": "Create Files", - "operationId": "create_files_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_files_files__post" - } - } + } + }, + "/uploadfiles/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "required": True, - }, - } - }, - "/uploadfiles/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "summary": "Create Upload Files", + "operationId": "create_upload_files_uploadfiles__post", + "requestBody": { "content": { - "application/json": { + "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_create_upload_files_uploadfiles__post" } } }, + "required": True, }, - }, - "summary": "Create Upload Files", - "operationId": "create_upload_files_uploadfiles__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_files_uploadfiles__post" - } + } + }, + "/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, } }, - "required": True, - }, - } - }, - "/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Main", - "operationId": "main__get", - } - }, - }, - "components": { - "schemas": { - "Body_create_upload_files_uploadfiles__post": { - "title": "Body_create_upload_files_uploadfiles__post", - "required": ["files"], - "type": "object", - "properties": { - "files": { - "title": "Files", - "type": "array", - "items": {"type": "string", "format": "binary"}, - } - }, + "summary": "Main", + "operationId": "main__get", + } }, - "Body_create_files_files__post": { - "title": "Body_create_files_files__post", - "required": ["files"], - "type": "object", - "properties": { - "files": { - "title": "Files", - "type": "array", - "items": {"type": "string", "format": "binary"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, + }, + "components": { + "schemas": { + "Body_create_upload_files_uploadfiles__post": { + "title": "Body_create_upload_files_uploadfiles__post", + "required": ["files"], + "type": "object", + "properties": { + "files": { + "title": "Files", + "type": "array", + "items": {"type": "string", "format": "binary"}, + } }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "Body_create_files_files__post": { + "title": "Body_create_files_files__post", + "required": ["files"], + "type": "object", + "properties": { + "files": { + "title": "Files", + "type": "array", + "items": {"type": "string", "format": "binary"}, + } + }, }, - }, - } - }, - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_request_files/test_tutorial003.py b/tests/test_tutorial/test_request_files/test_tutorial003.py index c9f7f0994..a1425ba33 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial003.py +++ b/tests/test_tutorial/test_request_files/test_tutorial003.py @@ -3,6 +3,7 @@ import importlib import pytest from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -72,144 +73,148 @@ def test_get_root(app: FastAPI): def test_openapi_schema(client: TestClient): 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": { - "/files/": { - "post": { - "summary": "Create Files", - "operationId": "create_files_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_files_files__post" - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "summary": "Create Files", + "operationId": "create_files_files__post", + "requestBody": { "content": { - "application/json": { + "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_create_files_files__post" } } }, + "required": True, }, - }, - } - }, - "/uploadfiles/": { - "post": { - "summary": "Create Upload Files", - "operationId": "create_upload_files_uploadfiles__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_upload_files_uploadfiles__post" - } - } + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + } + }, + "/uploadfiles/": { + "post": { + "summary": "Create Upload Files", + "operationId": "create_upload_files_uploadfiles__post", + "requestBody": { "content": { - "application/json": { + "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_create_upload_files_uploadfiles__post" } } }, + "required": True, }, - }, - } - }, - "/": { - "get": { - "summary": "Main", - "operationId": "main__get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - } - }, - }, - "components": { - "schemas": { - "Body_create_files_files__post": { - "title": "Body_create_files_files__post", - "required": ["files"], - "type": "object", - "properties": { - "files": { - "title": "Files", - "type": "array", - "items": {"type": "string", "format": "binary"}, - "description": "Multiple files as bytes", - } - }, - }, - "Body_create_upload_files_uploadfiles__post": { - "title": "Body_create_upload_files_uploadfiles__post", - "required": ["files"], - "type": "object", - "properties": { - "files": { - "title": "Files", - "type": "array", - "items": {"type": "string", "format": "binary"}, - "description": "Multiple files as UploadFile", - } - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, + } }, - } - }, - } + "/": { + "get": { + "summary": "Main", + "operationId": "main__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } + }, + }, + "components": { + "schemas": { + "Body_create_files_files__post": { + "title": "Body_create_files_files__post", + "required": ["files"], + "type": "object", + "properties": { + "files": { + "title": "Files", + "type": "array", + "items": {"type": "string", "format": "binary"}, + "description": "Multiple files as bytes", + } + }, + }, + "Body_create_upload_files_uploadfiles__post": { + "title": "Body_create_upload_files_uploadfiles__post", + "required": ["files"], + "type": "object", + "properties": { + "files": { + "title": "Files", + "type": "array", + "items": {"type": "string", "format": "binary"}, + "description": "Multiple files as UploadFile", + } + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001.py b/tests/test_tutorial/test_request_form_models/test_tutorial001.py index c4740ee72..68fa074dc 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial001.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -99,81 +100,85 @@ def test_post_body_json(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/login/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/login/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } + }, + }, + }, + "summary": "Login", + "operationId": "login_login__post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": {"$ref": "#/components/schemas/FormData"} } }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "FormData": { + "properties": { + "username": {"type": "string", "title": "Username"}, + "password": {"type": "string", "title": "Password"}, + }, + "type": "object", + "required": ["username", "password"], + "title": "FormData", + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, - "summary": "Login", - "operationId": "login_login__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": {"$ref": "#/components/schemas/FormData"} + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "FormData": { - "properties": { - "username": {"type": "string", "title": "Username"}, - "password": {"type": "string", "title": "Password"}, - }, - "type": "object", - "required": ["username", "password"], - "title": "FormData", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002.py b/tests/test_tutorial/test_request_form_models/test_tutorial002.py index b07fce432..ed5ae109a 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial002.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -116,82 +117,86 @@ def test_post_body_json(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/login/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/login/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } + }, + }, + }, + "summary": "Login", + "operationId": "login_login__post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": {"$ref": "#/components/schemas/FormData"} } }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "FormData": { + "properties": { + "username": {"type": "string", "title": "Username"}, + "password": {"type": "string", "title": "Password"}, + }, + "additionalProperties": False, + "type": "object", + "required": ["username", "password"], + "title": "FormData", + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, - "summary": "Login", - "operationId": "login_login__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": {"$ref": "#/components/schemas/FormData"} + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "FormData": { - "properties": { - "username": {"type": "string", "title": "Username"}, - "password": {"type": "string", "title": "Password"}, - }, - "additionalProperties": False, - "type": "object", - "required": ["username", "password"], - "title": "FormData", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_request_forms/test_tutorial001.py b/tests/test_tutorial/test_request_forms/test_tutorial001.py index f5f76306e..d1596f137 100644 --- a/tests/test_tutorial/test_request_forms/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -99,83 +100,87 @@ def test_post_body_json(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/login/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/login/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Login", + "operationId": "login_login__post", + "requestBody": { "content": { - "application/json": { + "application/x-www-form-urlencoded": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_login_login__post" } } }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Body_login_login__post": { + "title": "Body_login_login__post", + "required": ["username", "password"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, }, }, - "summary": "Login", - "operationId": "login_login__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_login__post" - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "Body_login_login__post": { - "title": "Body_login_login__post", - "required": ["username", "password"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py index cd05a1ccf..6e9165c42 100644 --- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py @@ -3,6 +3,7 @@ import importlib import pytest from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -149,88 +150,96 @@ def test_post_files_and_token(tmp_path, app: FastAPI): def test_openapi_schema(client: TestClient): 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": { - "/files/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/files/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Create File", + "operationId": "create_file_files__post", + "requestBody": { "content": { - "application/json": { + "multipart/form-data": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_create_file_files__post" } } }, + "required": True, + }, + } + } + }, + "components": { + "schemas": { + "Body_create_file_files__post": { + "title": "Body_create_file_files__post", + "required": ["file", "fileb", "token"], + "type": "object", + "properties": { + "file": { + "title": "File", + "type": "string", + "format": "binary", + }, + "fileb": { + "title": "Fileb", + "type": "string", + "format": "binary", + }, + "token": {"title": "Token", "type": "string"}, }, }, - "summary": "Create File", - "operationId": "create_file_files__post", - "requestBody": { - "content": { - "multipart/form-data": { - "schema": { - "$ref": "#/components/schemas/Body_create_file_files__post" - } + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "Body_create_file_files__post": { - "title": "Body_create_file_files__post", - "required": ["file", "fileb", "token"], - "type": "object", - "properties": { - "file": {"title": "File", "type": "string", "format": "binary"}, - "fileb": { - "title": "Fileb", - "type": "string", - "format": "binary", - }, - "token": {"title": "Token", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_response_directly/test_tutorial001.py b/tests/test_tutorial/test_response_directly/test_tutorial001.py index 76e7143bd..2b034f1c9 100644 --- a/tests/test_tutorial/test_response_directly/test_tutorial001.py +++ b/tests/test_tutorial/test_response_directly/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -40,115 +41,117 @@ def test_path_operation(client: TestClient): def test_openapi_schema_pv2(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == { - "info": { - "title": "FastAPI", - "version": "0.1.0", - }, - "openapi": "3.1.0", - "paths": { - "/items/{id}": { - "put": { - "operationId": "update_item_items__id__put", - "parameters": [ - { - "in": "path", - "name": "id", - "required": True, - "schema": {"title": "Id", "type": "string"}, - }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - }, + assert response.json() == snapshot( + { + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "openapi": "3.1.0", + "paths": { + "/items/{id}": { + "put": { + "operationId": "update_item_items__id__put", + "parameters": [ + { + "in": "path", + "name": "id", + "required": True, + "schema": {"title": "Id", "type": "string"}, }, - }, - "required": True, - }, - "responses": { - "200": { - "content": { - "application/json": {"schema": {}}, - }, - "description": "Successful Response", - }, - "422": { + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError", + "$ref": "#/components/schemas/Item", }, }, }, - "description": "Validation Error", + "required": True, }, + "responses": { + "200": { + "content": { + "application/json": {"schema": {}}, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Update Item", }, - "summary": "Update Item", }, }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", }, - "title": "Detail", - "type": "array", }, + "title": "HTTPValidationError", + "type": "object", }, - "title": "HTTPValidationError", - "type": "object", - }, - "Item": { - "properties": { - "description": { - "anyOf": [ - {"type": "string"}, - {"type": "null"}, - ], - "title": "Description", - }, - "timestamp": { - "format": "date-time", - "title": "Timestamp", - "type": "string", - }, - "title": {"title": "Title", "type": "string"}, - }, - "required": [ - "title", - "timestamp", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { + "Item": { + "properties": { + "description": { "anyOf": [ {"type": "string"}, - {"type": "integer"}, + {"type": "null"}, ], + "title": "Description", }, - "title": "Location", - "type": "array", + "timestamp": { + "format": "date-time", + "title": "Timestamp", + "type": "string", + }, + "title": {"title": "Title", "type": "string"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, + "required": [ + "title", + "timestamp", + ], + "title": "Item", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + {"type": "string"}, + {"type": "integer"}, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + "required": ["loc", "msg", "type"], + "title": "ValidationError", + "type": "object", }, - "required": ["loc", "msg", "type"], - "title": "ValidationError", - "type": "object", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_response_directly/test_tutorial002.py b/tests/test_tutorial/test_response_directly/test_tutorial002.py index ef8457572..047e82c14 100644 --- a/tests/test_tutorial/test_response_directly/test_tutorial002.py +++ b/tests/test_tutorial/test_response_directly/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -38,28 +39,30 @@ def test_path_operation(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == { - "info": { - "title": "FastAPI", - "version": "0.1.0", - }, - "openapi": "3.1.0", - "paths": { - "/legacy/": { - "get": { - "operationId": "get_legacy_data_legacy__get", - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, + assert response.json() == snapshot( + { + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "openapi": "3.1.0", + "paths": { + "/legacy/": { + "get": { + "operationId": "get_legacy_data_legacy__get", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, }, + "description": "Successful Response", }, - "description": "Successful Response", }, + "summary": "Get Legacy Data", }, - "summary": "Get Legacy Data", }, }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py b/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py index 265162f15..914f53783 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py +++ b/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -77,119 +78,125 @@ def test_create_item_only_required(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - "title": "Response Read Items Items Get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Item" + }, + "title": "Response Read Items Items Get", + } } - } - }, - }, - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - }, - "post": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", }, }, }, - "required": True, + "summary": "Read Items", + "operationId": "read_items_items__get", }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - } - }, - }, - "422": { - "description": "Validation Error", + "post": { + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Item", + }, + }, + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, } - } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create Item", + "operationId": "create_item_items__post", + }, + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "price": {"title": "Price", "type": "number"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + "tags": { + "title": "Tags", + "type": "array", + "items": {"type": "string"}, + "default": [], }, }, }, - "summary": "Create Item", - "operationId": "create_item_items__post", - }, - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, - }, - } - }, - } + } + }, + } + ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial002.py b/tests/test_tutorial/test_response_model/test_tutorial002.py index 17027d3c1..10a4e37cd 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial002.py +++ b/tests/test_tutorial/test_response_model/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -38,94 +39,100 @@ def test_post_user(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/user/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/user/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserIn" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + "summary": "Create User", + "operationId": "create_user_user__post", + "requestBody": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/UserIn"} } }, + "required": True, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + } + } + }, + "components": { + "schemas": { + "UserIn": { + "title": "UserIn", + "required": ["username", "password", "email"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "password": {"title": "Password", "type": "string"}, + "email": { + "title": "Email", + "type": "string", + "format": "email", + }, + "full_name": { + "title": "Full Name", + "anyOf": [{"type": "string"}, {"type": "null"}], }, }, }, - "summary": "Create User", - "operationId": "create_user_user__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/UserIn"} + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "UserIn": { - "title": "UserIn", - "required": ["username", "password", "email"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "password": {"title": "Password", "type": "string"}, - "email": { - "title": "Email", - "type": "string", - "format": "email", - }, - "full_name": { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_02.py b/tests/test_tutorial/test_response_model/test_tutorial003_02.py index fcd5f9a1d..460c88251 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_02.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_02.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.response_model.tutorial003_02_py39 import app @@ -20,76 +21,80 @@ def test_get_redirect(): 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": { - "/portal": { - "get": { - "summary": "Get Portal", - "operationId": "get_portal_portal_get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Teleport", - "type": "boolean", - "default": False, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/portal": { + "get": { + "summary": "Get Portal", + "operationId": "get_portal_portal_get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Teleport", + "type": "boolean", + "default": False, + }, + "name": "teleport", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, - "name": "teleport", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_03.py b/tests/test_tutorial/test_response_model/test_tutorial003_03.py index ea3c733b2..c83d78c4f 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_03.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_03.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.response_model.tutorial003_03_py39 import app @@ -14,21 +15,23 @@ def test_get_portal(): 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": { - "/teleport": { - "get": { - "summary": "Get Teleport", - "operationId": "get_teleport_teleport_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/teleport": { + "get": { + "summary": "Get Teleport", + "operationId": "get_teleport_teleport_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05.py b/tests/test_tutorial/test_response_model/test_tutorial003_05.py index e64ed1a80..187b6491f 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_05.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_05.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -35,76 +36,80 @@ def test_get_redirect(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/portal": { - "get": { - "summary": "Get Portal", - "operationId": "get_portal_portal_get", - "parameters": [ - { - "required": False, - "schema": { - "title": "Teleport", - "type": "boolean", - "default": False, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/portal": { + "get": { + "summary": "Get Portal", + "operationId": "get_portal_portal_get", + "parameters": [ + { + "required": False, + "schema": { + "title": "Teleport", + "type": "boolean", + "default": False, + }, + "name": "teleport", + "in": "query", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, - "name": "teleport", - "in": "query", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } } - } + }, }, }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py index 8b6213e33..7199ceed6 100644 --- a/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py +++ b/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -27,72 +28,76 @@ def test_create_item(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "post": { - "parameters": [ - { - "name": "name", - "in": "query", - "required": True, - "schema": {"title": "Name", "type": "string"}, - } - ], - "summary": "Create Item", - "operationId": "create_item_items__post", - "responses": { - "201": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "parameters": [ + { + "name": "name", + "in": "query", + "required": True, + "schema": {"title": "Name", "type": "string"}, + } + ], + "summary": "Create Item", + "operationId": "create_item_items__post", + "responses": { + "201": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } }, }, } - } - }, - "components": { - "schemas": { - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py index 7f0105a26..f19346a3a 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -36,106 +37,109 @@ def test_post_body_example(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text - # insert_assert(response.json()) - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "name": "item_id", - "in": "path", - "required": True, - "schema": {"type": "integer", "title": "Item Id"}, - } - ], - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Item Id"}, } - }, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + ], + "requestBody": { + "required": True, "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "price": {"type": "number", "title": "Price"}, + "tax": { + "anyOf": [{"type": "number"}, {"type": "null"}], + "title": "Tax", + }, + }, + "type": "object", + "required": ["name", "price"], + "title": "Item", + "examples": [ + { + "description": "A very nice Item", + "name": "Foo", + "price": 35.4, + "tax": 3.2, + } + ], + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "price": {"type": "number", "title": "Price"}, - "tax": { - "anyOf": [{"type": "number"}, {"type": "null"}], - "title": "Tax", - }, - }, - "type": "object", - "required": ["name", "price"], - "title": "Item", - "examples": [ - { - "description": "A very nice Item", - "name": "Foo", - "price": 35.4, - "tax": 3.2, - } - ], - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py index 32707c299..8313cb14f 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -36,108 +37,111 @@ def test_post_body_example(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text - # insert_assert(response.json()) - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "name": "item_id", - "in": "path", - "required": True, - "schema": {"type": "integer", "title": "Item Id"}, - } - ], - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Item Id"}, } - }, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + ], + "requestBody": { + "required": True, "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "examples": ["Foo"], + }, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + "examples": ["A very nice Item"], + }, + "price": { + "type": "number", + "title": "Price", + "examples": [35.4], + }, + "tax": { + "anyOf": [{"type": "number"}, {"type": "null"}], + "title": "Tax", + "examples": [3.2], + }, + }, + "type": "object", + "required": ["name", "price"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": { - "type": "string", - "title": "Name", - "examples": ["Foo"], - }, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - "examples": ["A very nice Item"], - }, - "price": { - "type": "number", - "title": "Price", - "examples": [35.4], - }, - "tax": { - "anyOf": [{"type": "number"}, {"type": "null"}], - "title": "Tax", - "examples": [3.2], - }, - }, - "type": "object", - "required": ["name", "price"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py index 4f8f1394c..3c45a64bc 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -38,108 +39,111 @@ def test_post_body_example(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text - # insert_assert(response.json()) - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "name": "item_id", - "in": "path", - "required": True, - "schema": {"type": "integer", "title": "Item Id"}, - } - ], - "requestBody": { - "required": True, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - "examples": [ - { - "description": "A very nice Item", - "name": "Foo", - "price": 35.4, - "tax": 3.2, - } - ], - }, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "name": "item_id", + "in": "path", + "required": True, + "schema": {"type": "integer", "title": "Item Id"}, } - }, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + ], + "requestBody": { + "required": True, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "$ref": "#/components/schemas/Item", + "examples": [ + { + "description": "A very nice Item", + "name": "Foo", + "price": 35.4, + "tax": 3.2, + } + ], + }, } }, }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "price": {"type": "number", "title": "Price"}, + "tax": { + "anyOf": [{"type": "number"}, {"type": "null"}], + "title": "Tax", + }, + }, + "type": "object", + "required": ["name", "price"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - "price": {"type": "number", "title": "Price"}, - "tax": { - "anyOf": [{"type": "number"}, {"type": "null"}], - "title": "Tax", - }, - }, - "type": "object", - "required": ["name", "price"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py index 3a0a7704b..4b8e15fa1 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -38,112 +39,116 @@ def test_post_body_example(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/{item_id}": { - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - {"name": "Bar", "price": "35.4"}, - { - "name": "Baz", - "price": "thirty five point four", - }, - ], - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + {"name": "Bar", "price": "35.4"}, + { + "name": "Baz", + "price": "thirty five point four", + }, + ], } } }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "price": {"title": "Price", "type": "number"}, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py index b10f25e26..7f8e36df2 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -38,121 +39,125 @@ def test_post_body_example(client: TestClient): def test_openapi_schema(client: TestClient) -> None: 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": { - "/items/{item_id}": { - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"}, + "examples": { + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "converted": { + "summary": "An example with converted data", + "description": "FastAPI can convert price `strings` to actual `numbers` automatically", + "value": {"name": "Bar", "price": "35.4"}, + }, + "invalid": { + "summary": "Invalid data is rejected with an error", + "value": { + "name": "Baz", + "price": "thirty five point four", + }, + }, + }, + } + }, "required": True, - "schema": {"title": "Item Id", "type": "integer"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"}, - "examples": { - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": {"name": "Bar", "price": "35.4"}, - }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, - }, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], }, + "price": {"title": "Price", "type": "number"}, + "tax": { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, }, }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "price": {"title": "Price", "type": "number"}, - "tax": { - "title": "Tax", - "anyOf": [{"type": "number"}, {"type": "null"}], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_security/test_tutorial001.py b/tests/test_tutorial/test_security/test_tutorial001.py index cdaa50b19..5742b51bf 100644 --- a/tests/test_tutorial/test_security/test_tutorial001.py +++ b/tests/test_tutorial/test_security/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -41,30 +42,32 @@ def test_incorrect_token(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Items", - "operationId": "read_items_items__get", - "security": [{"OAuth2PasswordBearer": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + "security": [{"OAuth2PasswordBearer": []}], + } } - } - }, - "components": { - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + }, + "components": { + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_security/test_tutorial002.py b/tests/test_tutorial/test_security/test_tutorial002.py index 85c076b1d..1e8c6e018 100644 --- a/tests/test_tutorial/test_security/test_tutorial002.py +++ b/tests/test_tutorial/test_security/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -42,30 +43,32 @@ def test_token(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me_get", - "security": [{"OAuth2PasswordBearer": []}], - } - } - }, - "components": { - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2PasswordBearer": []}], + } } }, - }, - } + "components": { + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } + }, + }, + } + ) diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py index 924b36b3a..acd910c71 100644 --- a/tests/test_tutorial/test_security/test_tutorial003.py +++ b/tests/test_tutorial/test_security/test_tutorial003.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -86,123 +87,131 @@ def test_inactive_user(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/token": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Login", + "operationId": "login_token_post", + "requestBody": { "content": { - "application/json": { + "application/x-www-form-urlencoded": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_login_token_post" } } }, + "required": True, }, - }, - "summary": "Login", - "operationId": "login_token_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_token_post" - } + } + }, + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, } }, - "required": True, - }, - } - }, - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me_get", - "security": [{"OAuth2PasswordBearer": []}], - } - }, - }, - "components": { - "schemas": { - "Body_login_token_post": { - "title": "Body_login_token_post", - "required": ["username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "anyOf": [ - {"pattern": "^password$", "type": "string"}, - {"type": "null"}, - ], - }, - "username": {"title": "Username", "type": "string"}, - "password": { - "title": "Password", - "type": "string", - "format": "password", - }, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "client_secret": { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - "format": "password", - }, - }, + "summary": "Read Users Me", + "operationId": "read_users_me_users_me_get", + "security": [{"OAuth2PasswordBearer": []}], + } }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "components": { + "schemas": { + "Body_login_token_post": { + "title": "Body_login_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "anyOf": [ + {"pattern": "^password$", "type": "string"}, + {"type": "null"}, + ], + }, + "username": {"title": "Username", "type": "string"}, + "password": { + "title": "Password", + "type": "string", + "format": "password", + }, + "scope": { + "title": "Scope", + "type": "string", + "default": "", + }, + "client_id": { + "title": "Client Id", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "client_secret": { + "title": "Client Secret", + "anyOf": [{"type": "string"}, {"type": "null"}], + "format": "password", }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, + } }, }, - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": {"password": {"scopes": {}, "tokenUrl": "token"}}, - } - }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_security/test_tutorial004.py b/tests/test_tutorial/test_security/test_tutorial004.py index 2b0df66a2..e842c92a1 100644 --- a/tests/test_tutorial/test_security/test_tutorial004.py +++ b/tests/test_tutorial/test_security/test_tutorial004.py @@ -4,6 +4,7 @@ from unittest.mock import patch import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -188,178 +189,186 @@ def test_openapi_schema(mod: ModuleType): client = TestClient(mod.app) 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": { - "/token": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Token"} - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/token": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Token"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - "422": { - "description": "Validation Error", + "summary": "Login For Access Token", + "operationId": "login_for_access_token_token_post", + "requestBody": { "content": { - "application/json": { + "application/x-www-form-urlencoded": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Body_login_for_access_token_token_post" } } }, + "required": True, }, - }, - "summary": "Login For Access Token", - "operationId": "login_for_access_token_token_post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "$ref": "#/components/schemas/Body_login_for_access_token_token_post" - } + } + }, + "/users/me/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/User"} + } + }, } }, - "required": True, - }, - } + "summary": "Read Users Me", + "operationId": "read_users_me_users_me__get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, + "/users/me/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Own Items", + "operationId": "read_own_items_users_me_items__get", + "security": [{"OAuth2PasswordBearer": []}], + } + }, }, - "/users/me/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/User"} - } + "components": { + "schemas": { + "User": { + "title": "User", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"}, + "email": { + "title": "Email", + "anyOf": [{"type": "string"}, {"type": "null"}], }, - } - }, - "summary": "Read Users Me", - "operationId": "read_users_me_users_me__get", - "security": [{"OAuth2PasswordBearer": []}], - } - }, - "/users/me/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Own Items", - "operationId": "read_own_items_users_me_items__get", - "security": [{"OAuth2PasswordBearer": []}], - } - }, - }, - "components": { - "schemas": { - "User": { - "title": "User", - "required": ["username"], - "type": "object", - "properties": { - "username": {"title": "Username", "type": "string"}, - "email": { - "title": "Email", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "full_name": { - "title": "Full Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "disabled": { - "title": "Disabled", - "anyOf": [{"type": "boolean"}, {"type": "null"}], - }, - }, - }, - "Token": { - "title": "Token", - "required": ["access_token", "token_type"], - "type": "object", - "properties": { - "access_token": {"title": "Access Token", "type": "string"}, - "token_type": {"title": "Token Type", "type": "string"}, - }, - }, - "Body_login_for_access_token_token_post": { - "title": "Body_login_for_access_token_token_post", - "required": ["username", "password"], - "type": "object", - "properties": { - "grant_type": { - "title": "Grant Type", - "anyOf": [ - {"pattern": "^password$", "type": "string"}, - {"type": "null"}, - ], - }, - "username": {"title": "Username", "type": "string"}, - "password": { - "title": "Password", - "type": "string", - "format": "password", - }, - "scope": {"title": "Scope", "type": "string", "default": ""}, - "client_id": { - "title": "Client Id", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "client_secret": { - "title": "Client Secret", - "anyOf": [{"type": "string"}, {"type": "null"}], - "format": "password", - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "full_name": { + "title": "Full Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "disabled": { + "title": "Disabled", + "anyOf": [{"type": "boolean"}, {"type": "null"}], }, }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, + }, + "Token": { + "title": "Token", + "required": ["access_token", "token_type"], + "type": "object", + "properties": { + "access_token": {"title": "Access Token", "type": "string"}, + "token_type": {"title": "Token Type", "type": "string"}, + }, + }, + "Body_login_for_access_token_token_post": { + "title": "Body_login_for_access_token_token_post", + "required": ["username", "password"], + "type": "object", + "properties": { + "grant_type": { + "title": "Grant Type", + "anyOf": [ + {"pattern": "^password$", "type": "string"}, + {"type": "null"}, + ], + }, + "username": {"title": "Username", "type": "string"}, + "password": { + "title": "Password", + "type": "string", + "format": "password", + }, + "scope": { + "title": "Scope", + "type": "string", + "default": "", + }, + "client_id": { + "title": "Client Id", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "client_secret": { + "title": "Client Secret", + "anyOf": [{"type": "string"}, {"type": "null"}], + "format": "password", + }, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, }, }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": {}, + "tokenUrl": "token", + } + }, + } }, }, - "securitySchemes": { - "OAuth2PasswordBearer": { - "type": "oauth2", - "flows": { - "password": { - "scopes": {}, - "tokenUrl": "token", - } - }, - } - }, - }, - } + } + ) diff --git a/tests/test_tutorial/test_security/test_tutorial006.py b/tests/test_tutorial/test_security/test_tutorial006.py index a4b3104bb..1c96f62ae 100644 --- a/tests/test_tutorial/test_security/test_tutorial006.py +++ b/tests/test_tutorial/test_security/test_tutorial006.py @@ -3,6 +3,7 @@ from base64 import b64encode import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -53,25 +54,27 @@ def test_security_http_basic_non_basic_credentials(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBasic": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } } - } - }, - "components": { - "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} - }, - } + }, + "components": { + "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} + }, + } + ) diff --git a/tests/test_tutorial/test_security/test_tutorial007.py b/tests/test_tutorial/test_security/test_tutorial007.py index 28b70a2d4..24667422f 100644 --- a/tests/test_tutorial/test_security/test_tutorial007.py +++ b/tests/test_tutorial/test_security/test_tutorial007.py @@ -3,6 +3,7 @@ from base64 import b64encode import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture( @@ -65,25 +66,27 @@ def test_security_http_basic_invalid_password(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/users/me": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Current User", - "operationId": "read_current_user_users_me_get", - "security": [{"HTTPBasic": []}], + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/users/me": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Current User", + "operationId": "read_current_user_users_me_get", + "security": [{"HTTPBasic": []}], + } } - } - }, - "components": { - "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} - }, - } + }, + "components": { + "securitySchemes": {"HTTPBasic": {"type": "http", "scheme": "basic"}} + }, + } + ) diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py index d0a0d5d38..0be3b59f9 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -41,102 +42,108 @@ def test_read_items(client: TestClient) -> None: def test_openapi_schema(client: TestClient) -> None: 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": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Response Read Items Items Get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item" + }, + "type": "array", + "title": "Response Read Items Items Get", + } } - } - }, - } - }, - }, - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - }, - }, - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - }, - "type": "object", - "required": ["name"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, - "type": "array", - "title": "Location", }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + }, + "type": "object", + "required": ["name"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py index a2fa56f93..87c63f69e 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310 @@ -41,102 +42,108 @@ def test_read_items(client: TestClient) -> None: def test_openapi_schema(client: TestClient) -> None: 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": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "items": {"$ref": "#/components/schemas/Item"}, - "type": "array", - "title": "Response Read Items Items Get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Item" + }, + "type": "array", + "title": "Response Read Items Items Get", + } } - } - }, - } - }, - }, - "post": { - "summary": "Create Item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + }, } }, - "required": True, }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { "content": { "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + "schema": {"$ref": "#/components/schemas/Item"} } }, + "required": True, }, - }, - }, - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Description", - }, - }, - "type": "object", - "required": ["name"], - "title": "Item", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, - "type": "array", - "title": "Location", }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + }, + "type": "object", + "required": ["name"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_settings/test_app01.py b/tests/test_tutorial/test_settings/test_app01.py index 0c5e440f1..52b9b1e62 100644 --- a/tests/test_tutorial/test_settings/test_app01.py +++ b/tests/test_tutorial/test_settings/test_app01.py @@ -4,6 +4,7 @@ import sys import pytest from dirty_equals import IsAnyStr from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import ValidationError from pytest import MonkeyPatch @@ -58,21 +59,23 @@ def test_app(client: TestClient): def test_openapi_schema(client: TestClient): 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": { - "/info": { - "get": { - "operationId": "info_info_get", - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Info", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/info": { + "get": { + "operationId": "info_info_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Info", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_static_files/test_tutorial001.py b/tests/test_tutorial/test_static_files/test_tutorial001.py index 4fbf19ae8..dd93fee79 100644 --- a/tests/test_tutorial/test_static_files/test_tutorial001.py +++ b/tests/test_tutorial/test_static_files/test_tutorial001.py @@ -3,6 +3,7 @@ from pathlib import Path import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot @pytest.fixture(scope="module") @@ -33,8 +34,10 @@ def test_static_files_not_found(client: TestClient): def test_openapi_schema(client: TestClient): 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": {}, - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": {}, + } + ) diff --git a/tests/test_tutorial/test_sub_applications/test_tutorial001.py b/tests/test_tutorial/test_sub_applications/test_tutorial001.py index ef1f80164..a8fb3dc36 100644 --- a/tests/test_tutorial/test_sub_applications/test_tutorial001.py +++ b/tests/test_tutorial/test_sub_applications/test_tutorial001.py @@ -1,53 +1,10 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.sub_applications.tutorial001_py39 import app client = TestClient(app) -openapi_schema_main = { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/app": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Main", - "operationId": "read_main_app_get", - } - } - }, -} -openapi_schema_sub = { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/sub": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Sub", - "operationId": "read_sub_sub_get", - } - } - }, - "servers": [{"url": "/subapi"}], -} - - -def test_openapi_schema_main(): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema_main - def test_main(): response = client.get("/app") @@ -55,13 +12,58 @@ def test_main(): assert response.json() == {"message": "Hello World from main app"} -def test_openapi_schema_sub(): - response = client.get("/subapi/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == openapi_schema_sub - - def test_sub(): response = client.get("/subapi/sub") assert response.status_code == 200, response.text assert response.json() == {"message": "Hello World from sub API"} + + +def test_openapi_schema_main(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/app": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Main", + "operationId": "read_main_app_get", + } + } + }, + } + ) + + +def test_openapi_schema_sub(): + response = client.get("/subapi/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/sub": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Sub", + "operationId": "read_sub_sub_get", + } + } + }, + "servers": [{"url": "/subapi"}], + } + ) diff --git a/tests/test_tutorial/test_testing/test_main_a.py b/tests/test_tutorial/test_testing/test_main_a.py index 9b3c796bd..528488805 100644 --- a/tests/test_tutorial/test_testing/test_main_a.py +++ b/tests/test_tutorial/test_testing/test_main_a.py @@ -1,3 +1,5 @@ +from inline_snapshot import snapshot + from docs_src.app_testing.app_a_py39.test_main import client, test_read_main @@ -8,21 +10,23 @@ def test_main(): 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": { - "/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Main", - "operationId": "read_main__get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Main", + "operationId": "read_main__get", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_testing/test_tutorial001.py b/tests/test_tutorial/test_testing/test_tutorial001.py index 5f6533306..2b501d36a 100644 --- a/tests/test_tutorial/test_testing/test_tutorial001.py +++ b/tests/test_tutorial/test_testing/test_tutorial001.py @@ -1,3 +1,5 @@ +from inline_snapshot import snapshot + from docs_src.app_testing.tutorial001_py39 import client, test_read_main @@ -8,21 +10,23 @@ def test_main(): 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": { - "/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - } - }, - "summary": "Read Main", - "operationId": "read_main__get", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "summary": "Read Main", + "operationId": "read_main__get", + } } - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_using_request_directly/test_tutorial001.py b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py index b55bfb456..cb8ae8b05 100644 --- a/tests/test_tutorial/test_using_request_directly/test_tutorial001.py +++ b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py @@ -1,4 +1,5 @@ from fastapi.testclient import TestClient +from inline_snapshot import snapshot from docs_src.using_request_directly.tutorial001_py39 import app @@ -14,101 +15,103 @@ def test_path_operation(): def test_openapi(): response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == { - "info": { - "title": "FastAPI", - "version": "0.1.0", - }, - "openapi": "3.1.0", - "paths": { - "/items/{item_id}": { - "get": { - "operationId": "read_root_items__item_id__get", - "parameters": [ - { - "in": "path", - "name": "item_id", - "required": True, - "schema": { - "title": "Item Id", + assert response.json() == snapshot( + { + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "openapi": "3.1.0", + "paths": { + "/items/{item_id}": { + "get": { + "operationId": "read_root_items__item_id__get", + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, + "schema": { + "title": "Item Id", + "type": "string", + }, + }, + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Read Root", + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", "type": "string", }, }, - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, - }, - }, - "description": "Successful Response", - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", - }, - }, - }, - "description": "Validation Error", - }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", }, - "summary": "Read Root", }, }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", - }, - "title": "Detail", - "type": "array", - }, - }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], - }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", - }, - }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", - }, - }, - }, - } + } + ) diff --git a/tests/test_union_body.py b/tests/test_union_body.py index ee56bb6eb..e333e2499 100644 --- a/tests/test_union_body.py +++ b/tests/test_union_body.py @@ -2,6 +2,7 @@ from typing import Optional, Union from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -38,94 +39,98 @@ def test_post_item(): 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": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Save Union Body", + "operationId": "save_union_body_items__post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "title": "Item", + "anyOf": [ + {"$ref": "#/components/schemas/OtherItem"}, + {"$ref": "#/components/schemas/Item"}, + ], } } }, + "required": True, }, + } + } + }, + "components": { + "schemas": { + "OtherItem": { + "title": "OtherItem", + "required": ["price"], + "type": "object", + "properties": {"price": {"title": "Price", "type": "integer"}}, }, - "summary": "Save Union Body", - "operationId": "save_union_body_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Item", - "anyOf": [ - {"$ref": "#/components/schemas/OtherItem"}, - {"$ref": "#/components/schemas/Item"}, - ], - } + "Item": { + "title": "Item", + "type": "object", + "properties": { + "name": { + "title": "Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + } + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "OtherItem": { - "title": "OtherItem", - "required": ["price"], - "type": "object", - "properties": {"price": {"title": "Price", "type": "integer"}}, - }, - "Item": { - "title": "Item", - "type": "object", - "properties": { - "name": { - "title": "Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_union_forms.py b/tests/test_union_forms.py index d90d0753a..f6c2658f9 100644 --- a/tests/test_union_forms.py +++ b/tests/test_union_forms.py @@ -2,6 +2,7 @@ from typing import Annotated, Union from fastapi import FastAPI, Form from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -61,97 +62,102 @@ def test_empty_form(): 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": { - "/form-union/": { - "post": { - "summary": "Post Union Form", - "operationId": "post_union_form_form_union__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "anyOf": [ - {"$ref": "#/components/schemas/UserForm"}, - {"$ref": "#/components/schemas/CompanyForm"}, - ], - "title": "Data", - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/form-union/": { + "post": { + "summary": "Post Union Form", + "operationId": "post_union_form_form_union__post", + "requestBody": { "content": { - "application/json": { + "application/x-www-form-urlencoded": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "anyOf": [ + {"$ref": "#/components/schemas/UserForm"}, + { + "$ref": "#/components/schemas/CompanyForm" + }, + ], + "title": "Data", } } }, + "required": True, }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "CompanyForm": { + "properties": { + "company_name": {"type": "string", "title": "Company Name"}, + "industry": {"type": "string", "title": "Industry"}, + }, + "type": "object", + "required": ["company_name", "industry"], + "title": "CompanyForm", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "UserForm": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "email": {"type": "string", "title": "Email"}, + }, + "type": "object", + "required": ["name", "email"], + "title": "UserForm", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } - } - }, - "components": { - "schemas": { - "CompanyForm": { - "properties": { - "company_name": {"type": "string", "title": "Company Name"}, - "industry": {"type": "string", "title": "Industry"}, - }, - "type": "object", - "required": ["company_name", "industry"], - "title": "CompanyForm", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "UserForm": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "email": {"type": "string", "title": "Email"}, - }, - "type": "object", - "required": ["name", "email"], - "title": "UserForm", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + }, + } + ) diff --git a/tests/test_union_inherited_body.py b/tests/test_union_inherited_body.py index 6b284c68c..5378880a4 100644 --- a/tests/test_union_inherited_body.py +++ b/tests/test_union_inherited_body.py @@ -2,6 +2,7 @@ from typing import Optional, Union from fastapi import FastAPI from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -38,100 +39,106 @@ def test_post_item(): 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": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, }, - "422": { - "description": "Validation Error", + "summary": "Save Union Different Body", + "operationId": "save_union_different_body_items__post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "title": "Item", + "anyOf": [ + { + "$ref": "#/components/schemas/ExtendedItem" + }, + {"$ref": "#/components/schemas/Item"}, + ], } } }, + "required": True, }, - }, - "summary": "Save Union Different Body", - "operationId": "save_union_different_body_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Item", - "anyOf": [ - {"$ref": "#/components/schemas/ExtendedItem"}, - {"$ref": "#/components/schemas/Item"}, - ], - } + } + } + }, + "components": { + "schemas": { + "Item": { + "title": "Item", + "type": "object", + "properties": { + "name": { + "title": "Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + } + }, + }, + "ExtendedItem": { + "title": "ExtendedItem", + "required": ["age"], + "type": "object", + "properties": { + "name": { + "title": "Name", + "anyOf": [{"type": "string"}, {"type": "null"}], + }, + "age": {"title": "Age", "type": "integer"}, + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + "input": {"title": "Input"}, + "ctx": {"title": "Context", "type": "object"}, + }, + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, } }, - "required": True, }, } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "type": "object", - "properties": { - "name": { - "title": "Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - }, - }, - "ExtendedItem": { - "title": "ExtendedItem", - "required": ["age"], - "type": "object", - "properties": { - "name": { - "title": "Name", - "anyOf": [{"type": "string"}, {"type": "null"}], - }, - "age": {"title": "Age", "type": "integer"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_webhooks_security.py b/tests/test_webhooks_security.py index c2c2809b2..267e450d1 100644 --- a/tests/test_webhooks_security.py +++ b/tests/test_webhooks_security.py @@ -4,6 +4,7 @@ from typing import Annotated from fastapi import FastAPI, Security from fastapi.security import HTTPBearer from fastapi.testclient import TestClient +from inline_snapshot import snapshot from pydantic import BaseModel app = FastAPI() @@ -38,91 +39,96 @@ def test_dummy_webhook(): def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text - # insert_assert(response.json()) - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": {}, - "webhooks": { - "new-subscription": { - "post": { - "summary": "New Subscription", - "description": "When a new user subscribes to your service we'll send you a POST request with this\ndata to the URL that you register for the event `new-subscription` in the dashboard.", - "operationId": "new_subscriptionnew_subscription_post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Subscription"} - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": {}, + "webhooks": { + "new-subscription": { + "post": { + "summary": "New Subscription", + "description": "When a new user subscribes to your service we'll send you a POST request with this\ndata to the URL that you register for the event `new-subscription` in the dashboard.", + "operationId": "new_subscriptionnew_subscription_post", + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "$ref": "#/components/schemas/Subscription" } } }, + "required": True, }, - }, - "security": [{"HTTPBearer": []}], - } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Subscription": { - "properties": { - "username": {"type": "string", "title": "Username"}, - "monthly_fee": {"type": "number", "title": "Monthly Fee"}, - "start_date": { - "type": "string", - "format": "date-time", - "title": "Start Date", - }, - }, - "type": "object", - "required": ["username", "monthly_fee", "start_date"], - "title": "Subscription", - }, - "ValidationError": { - "properties": { - "ctx": {"title": "Context", "type": "object"}, - "input": {"title": "Input"}, - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, - "type": "array", - "title": "Location", }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, + "security": [{"HTTPBearer": []}], + } + } }, - "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}}, - }, - } + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Subscription": { + "properties": { + "username": {"type": "string", "title": "Username"}, + "monthly_fee": {"type": "number", "title": "Monthly Fee"}, + "start_date": { + "type": "string", + "format": "date-time", + "title": "Start Date", + }, + }, + "type": "object", + "required": ["username", "monthly_fee", "start_date"], + "title": "Subscription", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + }, + "securitySchemes": {"HTTPBearer": {"type": "http", "scheme": "bearer"}}, + }, + } + ) From 79d4dfb37fd2e67090b29ed770bd096fd861f3cc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 8 Feb 2026 10:19:07 +0000 Subject: [PATCH 065/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5d93b684a..a0234366e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -11,6 +11,10 @@ hide: * ♻️ Refactor and simplify Pydantic v2 (and v1) compatibility internal utils. PR [#14862](https://github.com/fastapi/fastapi/pull/14862) by [@tiangolo](https://github.com/tiangolo). +### Internal + +* ✅ Add inline snapshot tests for OpenAPI before changes from Pydantic v2. PR [#14864](https://github.com/fastapi/fastapi/pull/14864) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.4 ### Refactors From dedf1409fe8c095b8881ab732e132e66e9eae997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 8 Feb 2026 11:20:22 +0100 Subject: [PATCH 066/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?8.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a0234366e..5a714d1a0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.128.5 + ### Refactors * ♻️ Refactor and simplify Pydantic v2 (and v1) compatibility internal utils. PR [#14862](https://github.com/fastapi/fastapi/pull/14862) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 580919a81..117685689 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.128.4" +__version__ = "0.128.5" from starlette import status as status From 376e1085807ddf6f899189340eb0d0217dbb74f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 8 Feb 2026 02:39:41 -0800 Subject: [PATCH 067/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20zh=20(update-outdated)=20(#14843)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Co-authored-by: Yurii Motov --- docs/en/mkdocs.yml | 2 + docs/zh/docs/advanced/additional-responses.md | 152 ++-- .../docs/advanced/additional-status-codes.md | 14 +- .../zh/docs/advanced/advanced-dependencies.md | 156 ++++- docs/zh/docs/advanced/async-tests.md | 32 +- docs/zh/docs/advanced/behind-a-proxy.md | 287 +++++--- docs/zh/docs/advanced/custom-response.md | 194 ++++-- docs/zh/docs/advanced/dataclasses.md | 98 ++- docs/zh/docs/advanced/events.md | 104 ++- docs/zh/docs/advanced/generate-clients.md | 293 ++++---- docs/zh/docs/advanced/index.md | 20 +- docs/zh/docs/advanced/middleware.md | 36 +- docs/zh/docs/advanced/openapi-callbacks.md | 56 +- docs/zh/docs/advanced/openapi-webhooks.md | 16 +- .../path-operation-advanced-configuration.md | 148 +++- .../advanced/response-change-status-code.md | 10 +- docs/zh/docs/advanced/response-cookies.md | 18 +- docs/zh/docs/advanced/response-directly.md | 29 +- docs/zh/docs/advanced/response-headers.md | 36 +- .../docs/advanced/security/http-basic-auth.md | 26 +- docs/zh/docs/advanced/security/index.md | 16 +- .../docs/advanced/security/oauth2-scopes.md | 258 +++---- docs/zh/docs/advanced/settings.md | 309 +++------ docs/zh/docs/advanced/sub-applications.md | 28 +- docs/zh/docs/advanced/templates.md | 48 +- docs/zh/docs/advanced/testing-dependencies.md | 9 +- docs/zh/docs/advanced/testing-events.md | 12 +- docs/zh/docs/advanced/testing-websockets.md | 12 +- .../docs/advanced/using-request-directly.md | 14 +- docs/zh/docs/advanced/websockets.md | 64 +- docs/zh/docs/advanced/wsgi.md | 32 +- docs/zh/docs/async.md | 74 +- docs/zh/docs/benchmarks.md | 22 +- docs/zh/docs/deployment/cloud.md | 25 +- docs/zh/docs/deployment/concepts.md | 115 ++- docs/zh/docs/deployment/docker.md | 652 +++++++----------- docs/zh/docs/deployment/https.md | 99 ++- docs/zh/docs/deployment/index.md | 12 +- docs/zh/docs/deployment/manually.md | 17 +- docs/zh/docs/deployment/server-workers.md | 12 +- docs/zh/docs/deployment/versions.md | 48 +- docs/zh/docs/environment-variables.md | 32 +- docs/zh/docs/fastapi-cli.md | 20 +- docs/zh/docs/features.md | 110 ++- docs/zh/docs/help-fastapi.md | 267 ++++--- docs/zh/docs/history-design-future.md | 19 +- docs/zh/docs/how-to/configure-swagger-ui.md | 24 +- docs/zh/docs/how-to/general.md | 24 +- docs/zh/docs/how-to/index.md | 4 +- docs/zh/docs/index.md | 447 +++++++----- docs/zh/docs/learn/index.md | 2 +- docs/zh/docs/project-generation.md | 44 +- docs/zh/docs/python-types.md | 462 +++++++++---- docs/zh/docs/tutorial/background-tasks.md | 64 +- docs/zh/docs/tutorial/bigger-applications.md | 134 ++-- docs/zh/docs/tutorial/body-fields.md | 19 +- docs/zh/docs/tutorial/body-multiple-params.md | 34 +- docs/zh/docs/tutorial/body-nested-models.md | 67 +- docs/zh/docs/tutorial/body-updates.md | 87 +-- docs/zh/docs/tutorial/body.md | 114 +-- docs/zh/docs/tutorial/cookie-param-models.md | 18 +- docs/zh/docs/tutorial/cookie-params.md | 23 +- docs/zh/docs/tutorial/cors.md | 39 +- docs/zh/docs/tutorial/debugging.md | 14 +- .../dependencies/classes-as-dependencies.md | 158 ++++- ...pendencies-in-path-operation-decorators.md | 30 +- .../dependencies/dependencies-with-yield.md | 200 +++--- .../dependencies/global-dependencies.md | 10 +- docs/zh/docs/tutorial/dependencies/index.md | 216 +++--- .../tutorial/dependencies/sub-dependencies.md | 45 +- docs/zh/docs/tutorial/encoder.md | 6 +- docs/zh/docs/tutorial/extra-data-types.md | 14 +- docs/zh/docs/tutorial/extra-models.md | 104 +-- docs/zh/docs/tutorial/first-steps.md | 117 +++- docs/zh/docs/tutorial/handling-errors.md | 175 ++--- docs/zh/docs/tutorial/header-param-models.md | 28 +- docs/zh/docs/tutorial/header-params.md | 14 +- docs/zh/docs/tutorial/index.md | 16 +- docs/zh/docs/tutorial/metadata.md | 74 +- docs/zh/docs/tutorial/middleware.md | 93 ++- .../tutorial/path-operation-configuration.md | 58 +- .../path-params-numeric-validations.md | 137 ++-- docs/zh/docs/tutorial/path-params.md | 147 ++-- docs/zh/docs/tutorial/query-param-models.md | 16 +- .../tutorial/query-params-str-validations.md | 473 +++++++++---- docs/zh/docs/tutorial/query-params.md | 51 +- docs/zh/docs/tutorial/request-files.md | 142 ++-- docs/zh/docs/tutorial/request-form-models.md | 38 +- .../docs/tutorial/request-forms-and-files.md | 22 +- docs/zh/docs/tutorial/request-forms.md | 58 +- docs/zh/docs/tutorial/response-model.md | 286 +++++--- docs/zh/docs/tutorial/response-status-code.md | 48 +- docs/zh/docs/tutorial/schema-extra-example.md | 201 +++++- docs/zh/docs/tutorial/security/first-steps.md | 174 ++--- .../tutorial/security/get-current-user.md | 28 +- docs/zh/docs/tutorial/security/index.md | 26 +- docs/zh/docs/tutorial/security/oauth2-jwt.md | 215 +++--- .../docs/tutorial/security/simple-oauth2.md | 66 +- docs/zh/docs/tutorial/sql-databases.md | 195 +++--- docs/zh/docs/tutorial/static-files.md | 38 +- docs/zh/docs/tutorial/testing.md | 92 +-- docs/zh/docs/virtual-environments.md | 106 +-- scripts/docs.py | 2 +- 103 files changed, 5463 insertions(+), 4129 deletions(-) diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index ccf5ffc7a..60d2f977e 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -331,6 +331,8 @@ extra: name: tr - Türkçe - link: /uk/ name: uk - українська мова + - link: /zh/ + name: zh - 简体中文 - link: /zh-hant/ name: zh-hant - 繁體中文 extra_css: diff --git a/docs/zh/docs/advanced/additional-responses.md b/docs/zh/docs/advanced/additional-responses.md index 362ef9460..bc197280a 100644 --- a/docs/zh/docs/advanced/additional-responses.md +++ b/docs/zh/docs/advanced/additional-responses.md @@ -1,44 +1,57 @@ -# OPENAPI 中的其他响应 +# OpenAPI 中的附加响应 { #additional-responses-in-openapi } -您可以声明附加响应,包括附加状态代码、媒体类型、描述等。 +/// warning | 警告 -这些额外的响应将包含在OpenAPI模式中,因此它们也将出现在API文档中。 +这是一个相对高级的话题。 -但是对于那些额外的响应,你必须确保你直接返回一个像 `JSONResponse` 一样的 `Response` ,并包含你的状态代码和内容。 - -## `model`附加响应 -您可以向路径操作装饰器传递参数 `responses` 。 - -它接收一个 `dict`,键是每个响应的状态代码(如`200`),值是包含每个响应信息的其他 `dict`。 - -每个响应字典都可以有一个关键模型,其中包含一个 `Pydantic` 模型,就像 `response_model` 一样。 - -**FastAPI**将采用该模型,生成其`JSON Schema`并将其包含在`OpenAPI`中的正确位置。 - -例如,要声明另一个具有状态码 `404` 和`Pydantic`模型 `Message` 的响应,可以写: -{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} - -/// note - -请记住,您必须直接返回 `JSONResponse` 。 +如果你刚开始使用 **FastAPI**,可能暂时用不到。 /// -/// info +你可以声明附加响应,包括额外的状态码、媒体类型、描述等。 -`model` 密钥不是OpenAPI的一部分。 -**FastAPI**将从那里获取`Pydantic`模型,生成` JSON Schema` ,并将其放在正确的位置。 -- 正确的位置是: - - 在键 `content` 中,其具有另一个`JSON`对象( `dict` )作为值,该`JSON`对象包含: - - 媒体类型的密钥,例如 `application/json` ,它包含另一个`JSON`对象作为值,该对象包含: - - 一个键` schema` ,它的值是来自模型的`JSON Schema`,正确的位置在这里。 - - **FastAPI**在这里添加了对OpenAPI中另一个地方的全局JSON模式的引用,而不是直接包含它。这样,其他应用程序和客户端可以直接使用这些JSON模式,提供更好的代码生成工具等。 +这些附加响应会被包含在 OpenAPI 模式中,因此它们也会出现在 API 文档中。 + +但是对于这些附加响应,你必须确保直接返回一个 `Response`(例如 `JSONResponse`),并携带你的状态码和内容。 + +## 带有 `model` 的附加响应 { #additional-response-with-model } + +你可以向你的*路径操作装饰器*传入参数 `responses`。 + +它接收一个 `dict`:键是每个响应的状态码(例如 `200`),值是包含该响应信息的另一个 `dict`。 + +这些响应的每个 `dict` 都可以有一个键 `model`,包含一个 Pydantic 模型,就像 `response_model` 一样。 + +**FastAPI** 会获取该模型,生成它的 JSON Schema,并将其放在 OpenAPI 中的正确位置。 + +例如,要声明另一个状态码为 `404` 且具有 Pydantic 模型 `Message` 的响应,你可以这样写: + +{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} + +/// note | 注意 + +记住你需要直接返回 `JSONResponse`。 /// -**在OpenAPI中为该路径操作生成的响应将是:** +/// info | 信息 -```json hl_lines="3-12" +`model` 键不是 OpenAPI 的一部分。 + +**FastAPI** 会从这里获取 Pydantic 模型,生成 JSON Schema,并把它放到正确的位置。 + +正确的位置是: + +* 在键 `content` 中,它的值是另一个 JSON 对象(`dict`),该对象包含: + * 一个媒体类型作为键,例如 `application/json`,它的值是另一个 JSON 对象,该对象包含: + * 一个键 `schema`,它的值是来自该模型的 JSON Schema,这里就是正确的位置。 + * **FastAPI** 会在这里添加一个引用,指向你 OpenAPI 中另一个位置的全局 JSON Schemas,而不是直接内联。这样,其他应用和客户端可以直接使用这些 JSON Schemas,提供更好的代码生成工具等。 + +/// + +为该*路径操作*在 OpenAPI 中生成的响应将是: + +```JSON hl_lines="3-12" { "responses": { "404": { @@ -73,10 +86,11 @@ } } } - ``` -**模式被引用到OpenAPI模式中的另一个位置:** -```json hl_lines="4-16" + +这些模式在 OpenAPI 模式中被引用到另一个位置: + +```JSON hl_lines="4-16" { "components": { "schemas": { @@ -153,48 +167,54 @@ } } } - ``` -## 主响应的其他媒体类型 -您可以使用相同的 `responses` 参数为相同的主响应添加不同的媒体类型。 +## 主响应的其他媒体类型 { #additional-media-types-for-the-main-response } -例如,您可以添加一个额外的媒体类型` image/png` ,声明您的路径操作可以返回JSON对象(媒体类型 `application/json` )或PNG图像: +你可以使用同一个 `responses` 参数为同一个主响应添加不同的媒体类型。 -{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} +例如,你可以添加一个额外的媒体类型 `image/png`,声明你的*路径操作*可以返回 JSON 对象(媒体类型为 `application/json`)或 PNG 图片: -/// note +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} -- 请注意,您必须直接使用 `FileResponse` 返回图像。 +/// note | 注意 + +请注意,你必须直接使用 `FileResponse` 返回图片。 /// -/// info +/// info | 信息 -- 除非在 `responses` 参数中明确指定不同的媒体类型,否则**FastAPI**将假定响应与主响应类具有相同的媒体类型(默认为` application/json` )。 -- 但是如果您指定了一个自定义响应类,并将 `None `作为其媒体类型,**FastAPI**将使用 `application/json` 作为具有关联模型的任何其他响应。 +除非你在 `responses` 参数中明确指定不同的媒体类型,否则 FastAPI 会假设响应与主响应类具有相同的媒体类型(默认是 `application/json`)。 + +但是如果你指定了一个媒体类型为 `None` 的自定义响应类,FastAPI 会对任何具有关联模型的附加响应使用 `application/json`。 /// -## 组合信息 -您还可以联合接收来自多个位置的响应信息,包括 `response_model `、 `status_code` 和 `responses `参数。 +## 组合信息 { #combining-information } -您可以使用默认的状态码 `200` (或者您需要的自定义状态码)声明一个 `response_model `,然后直接在OpenAPI模式中在 `responses` 中声明相同响应的其他信息。 +你也可以把来自多个位置的响应信息组合在一起,包括 `response_model`、`status_code` 和 `responses` 参数。 -**FastAPI**将保留来自 `responses` 的附加信息,并将其与模型中的JSON Schema结合起来。 +你可以声明一个 `response_model`,使用默认状态码 `200`(或根据需要使用自定义状态码),然后在 `responses` 中直接在 OpenAPI 模式里为同一个响应声明附加信息。 -例如,您可以使用状态码 `404` 声明响应,该响应使用`Pydantic`模型并具有自定义的` description` 。 +**FastAPI** 会保留来自 `responses` 的附加信息,并把它与你的模型生成的 JSON Schema 合并。 -以及一个状态码为 `200` 的响应,它使用您的 `response_model` ,但包含自定义的 `example` : +例如,你可以声明一个状态码为 `404` 的响应,它使用一个 Pydantic 模型并带有自定义的 `description`。 -{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} +以及一个状态码为 `200` 的响应,它使用你的 `response_model`,但包含自定义的 `example`: -所有这些都将被合并并包含在您的OpenAPI中,并在API文档中显示: +{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} -## 联合预定义响应和自定义响应 +所有这些都会被合并并包含到你的 OpenAPI 中,并显示在 API 文档里: + + + +## 组合预定义响应和自定义响应 { #combine-predefined-responses-and-custom-ones } + +你可能希望有一些适用于许多*路径操作*的预定义响应,但同时又想把它们与每个*路径操作*所需的自定义响应组合在一起。 + +在这些情况下,你可以使用 Python 的“解包”`dict` 的技巧 `**dict_to_unpack`: -您可能希望有一些应用于许多路径操作的预定义响应,但是你想将不同的路径和自定义的相应组合在一块。 -对于这些情况,你可以使用Python的技术,将 `dict` 与 `**dict_to_unpack` 解包: ```Python old_dict = { "old key": "old value", @@ -203,19 +223,25 @@ old_dict = { new_dict = {**old_dict, "new key": "new value"} ``` -这里, new_dict 将包含来自 old_dict 的所有键值对加上新的键值对: -```python +这里,`new_dict` 将包含来自 `old_dict` 的所有键值对,再加上新的键值对: + +```Python { "old key": "old value", "second old key": "second old value", "new key": "new value", } ``` -您可以使用该技术在路径操作中重用一些预定义的响应,并将它们与其他自定义响应相结合。 -**例如:** -{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} -## 有关OpenAPI响应的更多信息 -要了解您可以在响应中包含哪些内容,您可以查看OpenAPI规范中的以下部分: - + [OpenAPI响应对象](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responsesObject),它包括 Response Object 。 - + [OpenAPI响应对象](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responseObject),您可以直接在 `responses` 参数中的每个响应中包含任何内容。包括 `description` 、 `headers` 、 `content` (其中是声明不同的媒体类型和JSON Schemas)和 `links` 。 +你可以使用该技巧在*路径操作*中重用一些预定义响应,并把它们与额外的自定义响应组合在一起。 + +例如: + +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} + +## 关于 OpenAPI 响应的更多信息 { #more-information-about-openapi-responses } + +要查看响应中究竟可以包含什么,你可以查看 OpenAPI 规范中的以下部分: + +* OpenAPI Responses 对象,它包含 `Response Object`。 +* OpenAPI Response 对象,你可以把这里的任何内容直接包含到 `responses` 参数中的每个响应里。包括 `description`、`headers`、`content`(在这里声明不同的媒体类型和 JSON Schemas),以及 `links`。 diff --git a/docs/zh/docs/advanced/additional-status-codes.md b/docs/zh/docs/advanced/additional-status-codes.md index b048a2a17..23ceab4e8 100644 --- a/docs/zh/docs/advanced/additional-status-codes.md +++ b/docs/zh/docs/advanced/additional-status-codes.md @@ -1,10 +1,10 @@ -# 额外的状态码 +# 额外的状态码 { #additional-status-codes } **FastAPI** 默认使用 `JSONResponse` 返回一个响应,将你的 *路径操作* 中的返回内容放到该 `JSONResponse` 中。 **FastAPI** 会自动使用默认的状态码或者使用你在 *路径操作* 中设置的状态码。 -## 额外的状态码 +## 额外的状态码 { #additional-status-codes_1 } 如果你想要返回主要状态码之外的状态码,你可以通过直接返回一个 `Response` 来实现,比如 `JSONResponse`,然后直接设置额外的状态码。 @@ -12,15 +12,15 @@ 但是你也希望它能够接受新的条目。并且当这些条目不存在时,会自动创建并返回 201 「创建」的 HTTP 状态码。 -要实现它,导入 `JSONResponse`,然后在其中直接返回你的内容,并将 `status_code` 设置为为你要的值。 +要实现它,导入 `JSONResponse`,然后在其中直接返回你的内容,并将 `status_code` 设置为你要的值。 -{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *} +{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} -/// warning | 警告 +/// warning 当你直接返回一个像上面例子中的 `Response` 对象时,它会直接返回。 -FastAPI 不会用模型等对该响应进行序列化。 +它不会用模型等进行序列化。 确保其中有你想要的数据,且返回的值为合法的 JSON(如果你使用 `JSONResponse` 的话)。 @@ -34,7 +34,7 @@ FastAPI 不会用模型等对该响应进行序列化。 /// -## OpenAPI 和 API 文档 +## OpenAPI 和 API 文档 { #openapi-and-api-docs } 如果你直接返回额外的状态码和响应,它们不会包含在 OpenAPI 方案(API 文档)中,因为 FastAPI 没办法预先知道你要返回什么。 diff --git a/docs/zh/docs/advanced/advanced-dependencies.md b/docs/zh/docs/advanced/advanced-dependencies.md index 8375bd48e..3efca8944 100644 --- a/docs/zh/docs/advanced/advanced-dependencies.md +++ b/docs/zh/docs/advanced/advanced-dependencies.md @@ -1,65 +1,163 @@ -# 高级依赖项 +# 高级依赖项 { #advanced-dependencies } -## 参数化的依赖项 +## 参数化的依赖项 { #parameterized-dependencies } -我们之前看到的所有依赖项都是写死的函数或类。 +目前我们看到的依赖项都是固定的函数或类。 -但也可以为依赖项设置参数,避免声明多个不同的函数或类。 +但有时你可能希望为依赖项设置参数,而不必声明许多不同的函数或类。 -假设要创建校验查询参数 `q` 是否包含固定内容的依赖项。 +假设我们要有一个依赖项,用来检查查询参数 `q` 是否包含某个固定内容。 -但此处要把待检验的固定内容定义为参数。 +但我们希望能够把这个固定内容参数化。 -## **可调用**实例 +## “可调用”的实例 { #a-callable-instance } -Python 可以把类实例变为**可调用项**。 +在 Python 中,可以让某个类的实例变成“可调用对象”(callable)。 -这里说的不是类本身(类本就是可调用项),而是类实例。 +这里指的是类的实例(类本身已经是可调用的),而不是类本身。 -为此,需要声明 `__call__` 方法: +为此,声明一个 `__call__` 方法: -{* ../../docs_src/dependencies/tutorial011.py hl[10] *} +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} -本例中,**FastAPI** 使用 `__call__` 检查附加参数及子依赖项,稍后,还要调用它向*路径操作函数*传递值。 +在这种情况下,**FastAPI** 会使用这个 `__call__` 来检查附加参数和子依赖,并且稍后会调用它,把返回值传递给你的*路径操作函数*中的参数。 -## 参数化实例 +## 参数化实例 { #parameterize-the-instance } -接下来,使用 `__init__` 声明用于**参数化**依赖项的实例参数: +现在,我们可以用 `__init__` 声明实例的参数,用来“参数化”这个依赖项: -{* ../../docs_src/dependencies/tutorial011.py hl[7] *} +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} -本例中,**FastAPI** 不使用 `__init__`,我们要直接在代码中使用。 +在本例中,**FastAPI** 不会接触或关心 `__init__`,我们会在自己的代码中直接使用它。 -## 创建实例 +## 创建实例 { #create-an-instance } -使用以下代码创建类实例: +我们可以这样创建该类的实例: -{* ../../docs_src/dependencies/tutorial011.py hl[16] *} +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} -这样就可以**参数化**依赖项,它包含 `checker.fixed_content` 的属性 - `"bar"`。 +这样就把依赖项“参数化”了,现在它内部带有属性 `checker.fixed_content` 的值 `"bar"`。 -## 把实例作为依赖项 +## 把实例作为依赖项 { #use-the-instance-as-a-dependency } -然后,不要再在 `Depends(checker)` 中使用 `Depends(FixedContentQueryChecker)`, 而是要使用 `checker`,因为依赖项是类实例 - `checker`,不是类。 +然后,我们可以在 `Depends(checker)` 中使用这个 `checker`,而不是 `Depends(FixedContentQueryChecker)`,因为依赖项是实例 `checker`,不是类本身。 -处理依赖项时,**FastAPI** 以如下方式调用 `checker`: +解析依赖项时,**FastAPI** 会像这样调用 `checker`: ```Python checker(q="somequery") ``` -……并用*路径操作函数*的参数 `fixed_content_included` 返回依赖项的值: +...并将其返回值作为依赖项的值,传给我们的*路径操作函数*中的参数 `fixed_content_included`: -{* ../../docs_src/dependencies/tutorial011.py hl[20] *} +{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} /// tip | 提示 -本章示例有些刻意,也看不出有什么用处。 +这些看起来可能有些牵强,目前它的用处也许还不太明显。 -这个简例只是为了说明高级依赖项的运作机制。 +这些示例刻意保持简单,但展示了整体的工作方式。 -在有关安全的章节中,工具函数将以这种方式实现。 +在安全相关的章节里,有一些工具函数就是以相同的方式实现的。 -只要能理解本章内容,就能理解安全工具背后的运行机制。 +如果你理解了这里的内容,你就已经知道那些安全工具在底层是如何工作的。 /// + +## 带 `yield` 的依赖项、`HTTPException`、`except` 与后台任务 { #dependencies-with-yield-httpexception-except-and-background-tasks } + +/// warning | 警告 + +你很可能不需要了解这些技术细节。 + +这些细节主要在你的 FastAPI 应用版本低于 0.121.0 且你正遇到带 `yield` 的依赖项问题时才有用。 + +/// + +带 `yield` 的依赖项随着时间演进以覆盖不同用例并修复一些问题,下面是变更摘要。 + +### 带 `yield` 的依赖项与 `scope` { #dependencies-with-yield-and-scope } + +在 0.121.0 版本中,FastAPI 为带 `yield` 的依赖项新增了 `Depends(scope="function")` 的支持。 + +使用 `Depends(scope="function")` 时,`yield` 之后的退出代码会在*路径操作函数*结束后、响应发送给客户端之前立即执行。 + +而当使用默认的 `Depends(scope="request")` 时,`yield` 之后的退出代码会在响应发送之后执行。 + +你可以在文档 [带 `yield` 的依赖项 - 提前退出与 `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope) 中了解更多。 + +### 带 `yield` 的依赖项与 `StreamingResponse`(技术细节) { #dependencies-with-yield-and-streamingresponse-technical-details } + +在 FastAPI 0.118.0 之前,如果你使用带 `yield` 的依赖项,它会在*路径操作函数*返回后、发送响应之前运行 `yield` 之后的退出代码。 + +这样做的目的是避免在等待响应通过网络传输期间不必要地占用资源。 + +这也意味着,如果你返回的是 `StreamingResponse`,那么该带 `yield` 的依赖项的退出代码会在开始发送响应前就已经执行完毕。 + +例如,如果你在带 `yield` 的依赖项中持有一个数据库会话,那么 `StreamingResponse` 在流式发送数据时将无法使用该会话,因为会话已经在 `yield` 之后的退出代码里被关闭了。 + +在 0.118.0 中,这一行为被回退为:让 `yield` 之后的退出代码在响应发送之后再执行。 + +/// info | 信息 + +如你在下文所见,这与 0.106.0 之前的行为非常相似,但对若干边界情况做了改进和修复。 + +/// + +#### 需要提前执行退出代码的用例 { #use-cases-with-early-exit-code } + +在某些特定条件下,旧的行为(在发送响应之前执行带 `yield` 依赖项的退出代码)会更有利。 + +例如,设想你在带 `yield` 的依赖项中仅用数据库会话来校验用户,而在*路径操作函数*中并不会再次使用该会话;同时,响应需要很长时间才能发送完,比如一个缓慢发送数据的 `StreamingResponse`,且它出于某种原因并不使用数据库。 + +这种情况下,会一直持有数据库会话直到响应发送完毕;但如果并不再使用它,就没有必要一直占用。 + +代码可能如下: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py *} + +退出代码(自动关闭 `Session`)位于: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +...会在响应把慢速数据发送完之后才运行: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +但由于 `generate_stream()` 并不使用数据库会话,因此在发送响应期间保持会话打开并非必要。 + +如果你使用的是 SQLModel(或 SQLAlchemy)并碰到这种特定用例,你可以在不再需要时显式关闭会话: + +{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *} + +这样会话会释放数据库连接,让其他请求可以使用。 + +如果你还有其他需要在 `yield` 依赖项中提前退出的用例,请创建一个 GitHub 讨论问题,说明你的具体用例以及为何提前关闭会对你有帮助。 + +如果确有有力的用例需要提前关闭,我会考虑新增一种选择性启用提前关闭的方式。 + +### 带 `yield` 的依赖项与 `except`(技术细节) { #dependencies-with-yield-and-except-technical-details } + +在 FastAPI 0.110.0 之前,如果你在带 `yield` 的依赖项中用 `except` 捕获了一个异常,并且没有再次抛出它,那么该异常会被自动抛出/转发给任意异常处理器或内部服务器错误处理器。 + +在 0.110.0 中对此作出了变更,以修复将异常转发为未处理(内部服务器错误)时造成的内存消耗问题,并使其与常规 Python 代码的行为保持一致。 + +### 后台任务与带 `yield` 的依赖项(技术细节) { #background-tasks-and-dependencies-with-yield-technical-details } + +在 FastAPI 0.106.0 之前,`yield` 之后抛出异常是不可行的,因为带 `yield` 的依赖项中的退出代码会在响应发送之后才执行,此时[异常处理器](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}已经运行完毕。 + +之所以这样设计,主要是为了允许在后台任务中继续使用依赖项通过 `yield`“产出”的对象,因为退出代码会在后台任务完成之后才执行。 + +在 FastAPI 0.106.0 中,这一行为被修改,目的是避免在等待响应通过网络传输时一直占用资源。 + +/// tip | 提示 + +另外,后台任务通常是一段独立的逻辑,应该单独处理,并使用它自己的资源(例如它自己的数据库连接)。 + +因此,这样做你的代码通常会更清晰。 + +/// + +如果你过去依赖于旧行为,现在应在后台任务内部自行创建所需资源,并且只在内部使用不依赖于带 `yield` 依赖项资源的数据。 + +例如,不要复用相同的数据库会话,而是在后台任务内部创建一个新的会话,并用这个新会话从数据库获取对象。然后,不是把数据库对象本身作为参数传给后台任务函数,而是传递该对象的 ID,并在后台任务函数内部再次获取该对象。 diff --git a/docs/zh/docs/advanced/async-tests.md b/docs/zh/docs/advanced/async-tests.md index b5ac15b5b..6803358d2 100644 --- a/docs/zh/docs/advanced/async-tests.md +++ b/docs/zh/docs/advanced/async-tests.md @@ -1,4 +1,4 @@ -# 异步测试 +# 异步测试 { #async-tests } 您已经了解了如何使用 `TestClient` 测试 **FastAPI** 应用程序。但是到目前为止,您只了解了如何编写同步测试,而没有使用 `async` 异步函数。 @@ -6,11 +6,11 @@ 让我们看看如何才能实现这一点。 -## pytest.mark.anyio +## pytest.mark.anyio { #pytest-mark-anyio } 如果我们想在测试中调用异步函数,那么我们的测试函数必须是异步的。 AnyIO 为此提供了一个简洁的插件,它允许我们指定一些测试函数要异步调用。 -## HTTPX +## HTTPX { #httpx } 即使您的 **FastAPI** 应用程序使用普通的 `def` 函数而不是 `async def` ,它本质上仍是一个 `async` 异步应用程序。 @@ -18,7 +18,7 @@ `TestClient` 是基于 HTTPX 的。幸运的是,我们可以直接使用它来测试API。 -## 示例 +## 示例 { #example } 举个简单的例子,让我们来看一个[更大的应用](../tutorial/bigger-applications.md){.internal-link target=_blank}和[测试](../tutorial/testing.md){.internal-link target=_blank}中描述的类似文件结构: @@ -32,13 +32,13 @@ 文件 `main.py` 将包含: -{* ../../docs_src/async_tests/main.py *} +{* ../../docs_src/async_tests/app_a_py39/main.py *} 文件 `test_main.py` 将包含针对 `main.py` 的测试,现在它可能看起来如下: -{* ../../docs_src/async_tests/test_main.py *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py *} -## 运行测试 +## 运行测试 { #run-it } 您可以通过以下方式照常运行测试: @@ -52,13 +52,13 @@ $ pytest -## 详细说明 +## 详细说明 { #in-detail } 这个标记 `@pytest.mark.anyio` 会告诉 pytest 该测试函数应该被异步调用: -{* ../../docs_src/async_tests/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} -/// tip +/// tip | 提示 请注意,测试函数现在用的是 `async def`,而不是像以前使用 `TestClient` 时那样只是 `def` 。 @@ -66,7 +66,7 @@ $ pytest 我们现在可以使用应用程序创建一个 `AsyncClient` ,并使用 `await` 向其发送异步请求。 -{* ../../docs_src/async_tests/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} 这相当于: @@ -76,24 +76,24 @@ response = client.get('/') 我们曾经通过它向 `TestClient` 发出请求。 -/// tip +/// tip | 提示 请注意,我们正在将 async/await 与新的 `AsyncClient` 一起使用——请求是异步的。 /// -/// warning +/// warning | 警告 如果您的应用程序依赖于生命周期事件, `AsyncClient` 将不会触发这些事件。为了确保它们被触发,请使用 florimondmanca/asgi-lifespan 中的 `LifespanManager` 。 /// -## 其他异步函数调用 +## 其他异步函数调用 { #other-asynchronous-function-calls } 由于测试函数现在是异步的,因此除了在测试中向 FastAPI 应用程序发送请求之外,您现在还可以调用(和使用 `await` 等待)其他 `async` 异步函数,就和您在代码中的其他任何地方调用它们的方法一样。 -/// tip +/// tip | 提示 -如果您在测试程序中集成异步函数调用的时候遇到一个 `RuntimeError: Task attached to a different loop` 的报错(例如,使用 MongoDB 的 MotorClient 时),请记住,只能在异步函数中实例化需要事件循环的对象,例如通过 `'@app.on_event("startup")` 回调函数进行初始化。 +如果您在测试程序中集成异步函数调用的时候遇到一个 `RuntimeError: Task attached to a different loop` 的报错(例如,使用 MongoDB 的 MotorClient 时),请记住,只能在异步函数中实例化需要事件循环的对象,例如在 `@app.on_event("startup")` 回调中初始化。 /// diff --git a/docs/zh/docs/advanced/behind-a-proxy.md b/docs/zh/docs/advanced/behind-a-proxy.md index f8f61c8a3..b44b0b5ac 100644 --- a/docs/zh/docs/advanced/behind-a-proxy.md +++ b/docs/zh/docs/advanced/behind-a-proxy.md @@ -1,30 +1,131 @@ -# 使用代理 +# 使用代理 { #behind-a-proxy } -有些情况下,您可能要使用 Traefik 或 Nginx 等**代理**服务器,并添加应用不能识别的附加路径前缀配置。 +在很多情况下,你会在 FastAPI 应用前面使用像 Traefik 或 Nginx 这样的**代理**。 -此时,要使用 `root_path` 配置应用。 +这些代理可以处理 HTTPS 证书等事项。 -`root_path` 是 ASGI 规范提供的机制,FastAPI 就是基于此规范开发的(通过 Starlette)。 +## 代理转发的请求头 { #proxy-forwarded-headers } + +在你的应用前面的**代理**通常会在把请求转发给你的**服务器**之前,临时设置一些请求头,让服务器知道该请求是由代理**转发**的,并告知原始(公网)URL,包括域名、是否使用 HTTPS 等。 + +**服务器**程序(例如通过 **FastAPI CLI** 运行的 **Uvicorn**)能够解析这些请求头,然后把这些信息传递给你的应用。 + +但出于安全考虑,由于服务器并不知道自己处在受信任的代理之后,它默认不会解析这些请求头。 + +/// note | 技术细节 + +这些代理相关的请求头包括: + +- X-Forwarded-For +- X-Forwarded-Proto +- X-Forwarded-Host + +/// + +### 启用代理转发的请求头 { #enable-proxy-forwarded-headers } + +你可以用 *CLI 选项* `--forwarded-allow-ips` 启动 FastAPI CLI,并传入应该被信任、允许读取这些转发请求头的 IP 地址列表。 + +如果设置为 `--forwarded-allow-ips="*"`,就会信任所有来源 IP。 + +如果你的**服务器**位于受信任的**代理**之后,并且只有代理会与它通信,这将使其接受该**代理**的任何 IP。 + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### 使用 HTTPS 的重定向 { #redirects-with-https } + +例如,假设你定义了一个*路径操作* `/items/`: + +{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} + +如果客户端尝试访问 `/items`,默认会被重定向到 `/items/`。 + +但在设置 *CLI 选项* `--forwarded-allow-ips` 之前,它可能会重定向到 `http://localhost:8000/items/`。 + +而你的应用可能托管在 `https://mysuperapp.com`,重定向应当是 `https://mysuperapp.com/items/`。 + +通过设置 `--proxy-headers`,FastAPI 现在就可以重定向到正确的位置。😎 + +``` +https://mysuperapp.com/items/ +``` + +/// tip | 提示 + +如果你想了解更多关于 HTTPS 的内容,查看指南:[关于 HTTPS](../deployment/https.md){.internal-link target=_blank}。 + +/// + +### 代理转发请求头如何工作 { #how-proxy-forwarded-headers-work } + +下面是一个可视化图示,展示了**代理**如何在客户端与**应用服务器**之间添加转发请求头: + +```mermaid +sequenceDiagram + participant Client + participant Proxy as Proxy/Load Balancer + participant Server as FastAPI Server + + Client->>Proxy: HTTPS Request
Host: mysuperapp.com
Path: /items + + Note over Proxy: Proxy adds forwarded headers + + Proxy->>Server: HTTP Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items + + Note over Server: Server interprets headers
(if --forwarded-allow-ips is set) + + Server->>Proxy: HTTP Response
with correct HTTPS URLs + + Proxy->>Client: HTTPS Response +``` + +**代理**会拦截原始客户端请求,并在将请求传递给**应用服务器**之前,添加特殊的*转发*请求头(`X-Forwarded-*`)。 + +这些请求头保留了原始请求中否则会丢失的信息: + +- X-Forwarded-For:原始客户端的 IP 地址 +- X-Forwarded-Proto:原始协议(`https`) +- X-Forwarded-Host:原始主机(`mysuperapp.com`) + +当 **FastAPI CLI** 配置了 `--forwarded-allow-ips` 后,它会信任并使用这些请求头,例如用于在重定向中生成正确的 URL。 + +## 移除路径前缀的代理 { #proxy-with-a-stripped-path-prefix } + +你可能会有一个代理,为你的应用添加一个路径前缀。 + +在这些情况下,你可以使用 `root_path` 来配置你的应用。 + +`root_path` 是 ASGI 规范(FastAPI 基于该规范,通过 Starlette 构建)提供的机制。 `root_path` 用于处理这些特定情况。 -在挂载子应用时,也可以在内部使用。 +在挂载子应用时,它也会在内部使用。 -## 移除路径前缀的代理 +“移除路径前缀的代理”在这里的意思是:你可以在代码中声明一个路径 `/app`,然后在顶层添加一层(代理),把你的 **FastAPI** 应用放在类似 `/api/v1` 的路径下。 -本例中,移除路径前缀的代理是指在代码中声明路径 `/app`,然后在应用顶层添加代理,把 **FastAPI** 应用放在 `/api/v1` 路径下。 +在这种情况下,原始路径 `/app` 实际上会在 `/api/v1/app` 提供服务。 -本例的原始路径 `/app` 实际上是在 `/api/v1/app` 提供服务。 +即使你的所有代码都假设只有 `/app`。 -哪怕所有代码都假设只有 `/app`。 +{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} -代理只在把请求传送给 Uvicorn 之前才会**移除路径前缀**,让应用以为它是在 `/app` 提供服务,因此不必在代码中加入前缀 `/api/v1`。 +代理会在将请求传递给应用服务器(可能是通过 FastAPI CLI 运行的 Uvicorn)之前,实时**“移除”**这个**路径前缀**,让你的应用认为它是在 `/app` 被服务,这样你就不需要更新所有代码去包含 `/api/v1` 前缀。 -但之后,在(前端)打开 API 文档时,代理会要求在 `/openapi.json`,而不是 `/api/v1/openapi.json` 中提取 OpenAPI 概图。 +到这里,一切都会像往常一样工作。 -因此, (运行在浏览器中的)前端会尝试访问 `/openapi.json`,但没有办法获取 OpenAPI 概图。 +但是,当你打开集成的文档界面(前端)时,它会期望在 `/openapi.json` 获取 OpenAPI 模式,而不是在 `/api/v1/openapi.json`。 -这是因为应用使用了以 `/api/v1` 为路径前缀的代理,前端要从 `/api/v1/openapi.json` 中提取 OpenAPI 概图。 +因此,(在浏览器中运行的)前端会尝试访问 `/openapi.json`,但无法获取 OpenAPI 模式。 + +因为我们的应用使用了路径前缀为 `/api/v1` 的代理,前端需要从 `/api/v1/openapi.json` 获取 OpenAPI 模式。 ```mermaid graph LR @@ -39,15 +140,15 @@ proxy --> server /// tip | 提示 -IP `0.0.0.0` 常用于指程序监听本机或服务器上的所有有效 IP。 +IP `0.0.0.0` 通常表示程序监听该机器/服务器上的所有可用 IP。 /// -API 文档还需要 OpenAPI 概图声明 API `server` 位于 `/api/v1`(使用代理时的 URL)。例如: +文档界面还需要 OpenAPI 模式声明该 API 的 `server` 位于 `/api/v1`(代理后面)。例如: ```JSON hl_lines="4-8" { - "openapi": "3.0.2", + "openapi": "3.1.0", // More stuff here "servers": [ { @@ -60,53 +161,53 @@ API 文档还需要 OpenAPI 概图声明 API `server` 位于 `/api/v1`(使用 } ``` -本例中的 `Proxy` 是 **Traefik**,`server` 是运行 FastAPI 应用的 **Uvicorn**。 +在此示例中,“Proxy” 可以是 **Traefik** 之类的。服务器可以是用 **Uvicorn** 的 **FastAPI CLI** 运行你的 FastAPI 应用。 -### 提供 `root_path` +### 提供 `root_path` { #providing-the-root-path } -为此,要以如下方式使用命令行选项 `--root-path`: +为此,你可以像下面这样使用命令行选项 `--root-path`:
```console -$ uvicorn main:app --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-Hypercorn 也支持 `--root-path `选项。 +如果你使用 Hypercorn,它也有 `--root-path` 选项。 /// note | 技术细节 -ASGI 规范定义的 `root_path` 就是为了这种用例。 +ASGI 规范为这种用例定义了 `root_path`。 -并且 `--root-path` 命令行选项支持 `root_path`。 +命令行选项 `--root-path` 会提供该 `root_path`。 /// -### 查看当前的 `root_path` +### 查看当前的 `root_path` { #checking-the-current-root-path } -获取应用为每个请求使用的当前 `root_path`,这是 `scope` 字典的内容(也是 ASGI 规范的内容)。 +你可以获取应用在每个请求中使用的当前 `root_path`,它是 `scope` 字典的一部分(ASGI 规范的一部分)。 -我们在这里的信息里包含 `roo_path` 只是为了演示。 +这里我们把它包含在响应消息中仅用于演示。 -{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} -然后,用以下命令启动 Uvicorn: +然后,如果你这样启动 Uvicorn:
```console -$ uvicorn main:app --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-返回的响应如下: +响应类似于: ```JSON { @@ -115,19 +216,19 @@ $ uvicorn main:app --root-path /api/v1 } ``` -### 在 FastAPI 应用里设置 `root_path` +### 在 FastAPI 应用中设置 `root_path` { #setting-the-root-path-in-the-fastapi-app } -还有一种方案,如果不能提供 `--root-path` 或等效的命令行选项,则在创建 FastAPI 应用时要设置 `root_path` 参数。 +或者,如果你无法提供类似 `--root-path` 的命令行选项,你可以在创建 FastAPI 应用时设置参数 `root_path`: -{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} +{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} -传递 `root_path` 给 `FastAPI` 与传递 `--root-path` 命令行选项给 Uvicorn 或 Hypercorn 一样。 +把 `root_path` 传给 `FastAPI` 等同于把命令行选项 `--root-path` 传给 Uvicorn 或 Hypercorn。 -### 关于 `root_path` +### 关于 `root_path` { #about-root-path } -注意,服务器(Uvicorn)只是把 `root_path` 传递给应用。 +请注意,服务器(Uvicorn)不会用这个 `root_path` 做别的事情,只会把它传给应用。 -在浏览器中输入 http://127.0.0.1:8000/app 时能看到标准响应: +但是,如果你用浏览器打开 http://127.0.0.1:8000/app,你会看到正常的响应: ```JSON { @@ -136,25 +237,25 @@ $ uvicorn main:app --root-path /api/v1 } ``` -它不要求访问 `http://127.0.0.1:800/api/v1/app`。 +因此,它不会期望被访问于 `http://127.0.0.1:8000/api/v1/app`。 -Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn,而在顶部添加 `/api/v1` 前缀是代理要做的事情。 +Uvicorn 会期望代理以 `http://127.0.0.1:8000/app` 访问 Uvicorn,而在顶部额外添加 `/api/v1` 前缀是代理的职责。 -## 关于移除路径前缀的代理 +## 关于移除路径前缀的代理 { #about-proxies-with-a-stripped-path-prefix } -注意,移除路径前缀的代理只是配置代理的方式之一。 +请记住,移除路径前缀只是配置代理的一种方式。 -大部分情况下,代理默认都不会移除路径前缀。 +在很多情况下,默认是代理不会移除路径前缀。 -(未移除路径前缀时)代理监听 `https://myawesomeapp.com` 等对象,如果浏览器跳转到 `https://myawesomeapp.com/api/v1/app`,且服务器(例如 Uvicorn)监听 `http://127.0.0.1:8000` 代理(未移除路径前缀) 会在同样的路径:`http://127.0.0.1:8000/api/v1/app` 访问 Uvicorn。 +在这种情况下(没有移除路径前缀),代理会监听类似 `https://myawesomeapp.com`,当浏览器访问 `https://myawesomeapp.com/api/v1/app` 且你的服务器(例如 Uvicorn)监听 `http://127.0.0.1:8000` 时,代理(未移除路径前缀)会以相同路径访问 Uvicorn:`http://127.0.0.1:8000/api/v1/app`。 -## 本地测试 Traefik +## 使用 Traefik 进行本地测试 { #testing-locally-with-traefik } -您可以轻易地在本地使用 Traefik 运行移除路径前缀的试验。 +你可以很容易地使用 Traefik 在本地运行一个移除路径前缀的实验。 -下载 Traefik,这是一个二进制文件,需要解压文件,并在 Terminal 中直接运行。 +下载 Traefik,它是一个单独的二进制文件,你可以解压压缩包并直接在终端中运行。 -然后创建包含如下内容的 `traefik.toml` 文件: +然后创建一个 `traefik.toml` 文件,内容如下: ```TOML hl_lines="3" [entryPoints] @@ -166,15 +267,15 @@ Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn,而在顶 filename = "routes.toml" ``` -这个文件把 Traefik 监听端口设置为 `9999`,并设置要使用另一个文件 `routes.toml`。 +这告诉 Traefik 监听端口 9999,并使用另一个文件 `routes.toml`。 /// tip | 提示 -使用端口 9999 代替标准的 HTTP 端口 80,这样就不必使用管理员权限运行(`sudo`)。 +我们使用 9999 端口而不是标准 HTTP 端口 80,这样你就不需要用管理员(`sudo`)权限运行。 /// -接下来,创建 `routes.toml`: +现在创建另一个文件 `routes.toml`: ```TOML hl_lines="5 12 20" [http] @@ -201,9 +302,9 @@ Uvicorn 预期代理在 `http://127.0.0.1:8000/app` 访问 Uvicorn,而在顶 这个文件配置 Traefik 使用路径前缀 `/api/v1`。 -然后,它把请求重定位到运行在 `http://127.0.0.1:8000` 上的 Uvicorn。 +随后 Traefik 会把请求转发到运行在 `http://127.0.0.1:8000` 的 Uvicorn。 -现在,启动 Traefik: +现在启动 Traefik:
@@ -215,21 +316,21 @@ INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml
-接下来,使用 Uvicorn 启动应用,并使用 `--root-path` 选项: +然后使用 `--root-path` 选项启动你的应用:
```console -$ uvicorn main:app --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-### 查看响应 +### 查看响应 { #check-the-responses } -访问含 Uvicorn 端口的 URL:http://127.0.0.1:8000/app,就能看到标准响应: +现在,如果你访问 Uvicorn 端口对应的 URL:http://127.0.0.1:8000/app,你会看到正常响应: ```JSON { @@ -240,13 +341,13 @@ $ uvicorn main:app --root-path /api/v1 /// tip | 提示 -注意,就算访问 `http://127.0.0.1:8000/app`,也显示从选项 `--root-path` 中提取的 `/api/v1`,这是 `root_path` 的值。 +注意,尽管你是通过 `http://127.0.0.1:8000/app` 访问,它仍显示 `root_path` 为 `/api/v1`,该值来自 `--root-path` 选项。 /// -打开含 Traefik 端口的 URL,包含路径前缀:http://127.0.0.1:9999/api/v1/app。 +现在打开包含路径前缀、使用 Traefik 端口的 URL:http://127.0.0.1:9999/api/v1/app。 -得到同样的响应: +我们得到相同的响应: ```JSON { @@ -255,57 +356,57 @@ $ uvicorn main:app --root-path /api/v1 } ``` -但这一次 URL 包含了代理提供的路径前缀:`/api/v1`。 +但这次 URL 中带有代理提供的前缀路径:`/api/v1`。 -当然,这是通过代理访问应用的方式,因此,路径前缀 `/app/v1` 版本才是**正确**的。 +当然,这里的想法是每个人都通过代理访问应用,因此带有路径前缀 `/api/v1` 的版本才是“正确”的。 -而不带路径前缀的版本(`http://127.0.0.1:8000/app`),则由 Uvicorn 直接提供,专供*代理*(Traefik)访问。 +而不带路径前缀的版本(`http://127.0.0.1:8000/app`)由 Uvicorn 直接提供,仅供_代理_(Traefik)访问。 -这演示了代理(Traefik)如何使用路径前缀,以及服务器(Uvicorn)如何使用选项 `--root-path` 中的 `root_path`。 +这说明了代理(Traefik)如何使用路径前缀,以及服务器(Uvicorn)如何使用 `--root-path` 选项提供的 `root_path`。 -### 查看文档 +### 查看文档界面 { #check-the-docs-ui } -但这才是有趣的地方 ✨ +有趣的部分来了。✨ -访问应用的**官方**方式是通过含路径前缀的代理。因此,不出所料,如果没有在 URL 中添加路径前缀,直接访问通过 Uvicorn 运行的 API 文档,不能正常访问,因为需要通过代理才能访问。 +访问应用的“官方”方式应该是通过我们定义的带有路径前缀的代理。因此,正如预期的那样,如果你尝试不带路径前缀、直接由 Uvicorn 提供的文档界面,它将无法工作,因为它期望通过代理访问。 -输入 http://127.0.0.1:8000/docs 查看 API 文档: +你可以在 http://127.0.0.1:8000/docs 查看: -但输入**官方**链接 `/api/v1/docs`,并使用端口 `9999` 访问 API 文档,就能正常运行了!🎉 +但如果我们在“官方”URL(代理端口为 `9999`)的 `/api/v1/docs` 访问文档界面,它就能正常工作!🎉 -输入 http://127.0.0.1:9999/api/v1/docs 查看文档: +你可以在 http://127.0.0.1:9999/api/v1/docs 查看: -一切正常。 ✔️ +完全符合我们的预期。✔️ -这是因为 FastAPI 在 OpenAPI 里使用 `root_path` 提供的 URL 创建默认 `server`。 +这是因为 FastAPI 使用该 `root_path` 在 OpenAPI 中创建默认的 `server`,其 URL 来自 `root_path`。 -## 附加的服务器 +## 附加的服务器 { #additional-servers } /// warning | 警告 -此用例较难,可以跳过。 +这是一个更高级的用例,可以跳过。 /// -默认情况下,**FastAPI** 使用 `root_path` 的链接在 OpenAPI 概图中创建 `server`。 +默认情况下,**FastAPI** 会在 OpenAPI 模式中使用 `root_path` 的 URL 创建一个 `server`。 -但也可以使用其它备选 `servers`,例如,需要同一个 API 文档与 staging 和生产环境交互。 +但你也可以提供其他备选的 `servers`,例如你希望让“同一个”文档界面同时与预发布环境和生产环境交互。 -如果传递自定义 `servers` 列表,并有 `root_path`( 因为 API 使用了代理),**FastAPI** 会在列表开头使用这个 `root_path` 插入**服务器**。 +如果你传入了自定义的 `servers` 列表,并且存在 `root_path`(因为你的 API 位于代理后面),**FastAPI** 会在列表开头插入一个使用该 `root_path` 的“server”。 例如: -{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} +{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} -这段代码生产如下 OpenAPI 概图: +会生成如下的 OpenAPI 模式: ```JSON hl_lines="5-7" { - "openapi": "3.0.2", + "openapi": "3.1.0", // More stuff here "servers": [ { @@ -328,30 +429,38 @@ $ uvicorn main:app --root-path /api/v1 /// tip | 提示 -注意,自动生成服务器时,`url` 的值 `/api/v1` 提取自 `roog_path`。 +注意这个自动生成的服务器,`url` 的值为 `/api/v1`,取自 `root_path`。 /// -http://127.0.0.1:9999/api/v1/docs 的 API 文档所示如下: +在 http://127.0.0.1:9999/api/v1/docs 的文档界面中,它看起来是这样的: /// tip | 提示 -API 文档与所选的服务器进行交互。 +文档界面会与你所选择的服务器交互。 /// -### 从 `root_path` 禁用自动服务器 +/// note | 技术细节 -如果不想让 **FastAPI** 包含使用 `root_path` 的自动服务器,则要使用参数 `root_path_in_servers=False`: +OpenAPI 规范中的 `servers` 属性是可选的。 -{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} +如果你没有指定 `servers` 参数,并且 `root_path` 等于 `/`,则默认情况下,生成的 OpenAPI 模式中会完全省略 `servers` 属性,这等价于只有一个 `url` 值为 `/` 的服务器。 -这样,就不会在 OpenAPI 概图中包含服务器了。 +/// -## 挂载子应用 +### 从 `root_path` 禁用自动服务器 { #disable-automatic-server-from-root-path } -如需挂载子应用(详见 [子应用 - 挂载](sub-applications.md){.internal-link target=_blank}),也要通过 `root_path` 使用代理,这与正常应用一样,别无二致。 +如果你不希望 **FastAPI** 包含一个使用 `root_path` 的自动服务器,可以使用参数 `root_path_in_servers=False`: -FastAPI 在内部使用 `root_path`,因此子应用也可以正常运行。✨ +{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} + +这样它就不会被包含到 OpenAPI 模式中。 + +## 挂载子应用 { #mounting-a-sub-application } + +如果你需要在使用带有 `root_path` 的代理时挂载一个子应用(参见 [子应用 - 挂载](sub-applications.md){.internal-link target=_blank}),你可以像预期的那样正常操作。 + +FastAPI 会在内部智能地使用 `root_path`,因此它可以直接正常工作。✨ diff --git a/docs/zh/docs/advanced/custom-response.md b/docs/zh/docs/advanced/custom-response.md index 22a9b4b51..f5bec5fdc 100644 --- a/docs/zh/docs/advanced/custom-response.md +++ b/docs/zh/docs/advanced/custom-response.md @@ -1,32 +1,38 @@ -# 自定义响应 - HTML,流,文件和其他 +# 自定义响应 - HTML、流、文件等 { #custom-response-html-stream-file-others } **FastAPI** 默认会使用 `JSONResponse` 返回响应。 你可以通过直接返回 `Response` 来重载它,参见 [直接返回响应](response-directly.md){.internal-link target=_blank}。 -但如果你直接返回 `Response`,返回数据不会自动转换,也不会自动生成文档(例如,在 HTTP 头 `Content-Type` 中包含特定的「媒体类型」作为生成的 OpenAPI 的一部分)。 +但如果你直接返回一个 `Response`(或其任意子类,比如 `JSONResponse`),返回数据不会自动转换(即使你声明了 `response_model`),也不会自动生成文档(例如,在生成的 OpenAPI 中,HTTP 头 `Content-Type` 里的特定「媒体类型」不会被包含)。 -你还可以在 *路径操作装饰器* 中声明你想用的 `Response`。 +你还可以在 *路径操作装饰器* 中通过 `response_class` 参数声明要使用的 `Response`(例如任意 `Response` 子类)。 你从 *路径操作函数* 中返回的内容将被放在该 `Response` 中。 -并且如果该 `Response` 有一个 JSON 媒体类型(`application/json`),比如使用 `JSONResponse` 或者 `UJSONResponse` 的时候,返回的数据将使用你在路径操作装饰器中声明的任何 Pydantic 的 `response_model` 自动转换(和过滤)。 +并且如果该 `Response` 有一个 JSON 媒体类型(`application/json`),比如使用 `JSONResponse` 或 `UJSONResponse` 的时候,返回的数据将使用你在路径操作装饰器中声明的任何 Pydantic 的 `response_model` 自动转换(和过滤)。 -/// note | 说明 +/// note | 注意 -如果你使用不带有任何媒体类型的响应类,FastAPI 认为你的响应没有任何内容,所以不会在生成的OpenAPI文档中记录响应格式。 +如果你使用不带有任何媒体类型的响应类,FastAPI 会认为你的响应没有任何内容,所以不会在生成的 OpenAPI 文档中记录响应格式。 /// -## 使用 `ORJSONResponse` +## 使用 `ORJSONResponse` { #use-orjsonresponse } 例如,如果你需要压榨性能,你可以安装并使用 `orjson` 并将响应设置为 `ORJSONResponse`。 导入你想要使用的 `Response` 类(子类)然后在 *路径操作装饰器* 中声明它。 -{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} +对于较大的响应,直接返回一个 `Response` 会比返回一个字典快得多。 -/// info | 提示 +这是因为默认情况下,FastAPI 会检查其中的每一项并确保它可以被序列化为 JSON,使用教程中解释的相同 [JSON 兼容编码器](../tutorial/encoder.md){.internal-link target=_blank}。这正是它允许你返回「任意对象」的原因,例如数据库模型。 + +但如果你确定你返回的内容是「可以用 JSON 序列化」的,你可以将它直接传给响应类,从而避免在传给响应类之前先通过 `jsonable_encoder` 带来的额外开销。 + +{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} + +/// info | 信息 参数 `response_class` 也会用来定义响应的「媒体类型」。 @@ -36,22 +42,22 @@ /// -/// tip | 小贴士 +/// tip | 提示 `ORJSONResponse` 目前只在 FastAPI 中可用,而在 Starlette 中不可用。 /// -## HTML 响应 +## HTML 响应 { #html-response } 使用 `HTMLResponse` 来从 **FastAPI** 中直接返回一个 HTML 响应。 * 导入 `HTMLResponse`。 * 将 `HTMLResponse` 作为你的 *路径操作* 的 `response_class` 参数传入。 -{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} -/// info | 提示 +/// info | 信息 参数 `response_class` 也会用来定义响应的「媒体类型」。 @@ -61,13 +67,13 @@ /// -### 返回一个 `Response` +### 返回一个 `Response` { #return-a-response } 正如你在 [直接返回响应](response-directly.md){.internal-link target=_blank} 中了解到的,你也可以通过直接返回响应在 *路径操作* 中直接重载响应。 和上面一样的例子,返回一个 `HTMLResponse` 看起来可能是这样: -{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} /// warning | 警告 @@ -75,33 +81,33 @@ /// -/// info | 提示 +/// info | 信息 -当然,实际的 `Content-Type` 头,状态码等等,将来自于你返回的 `Response` 对象。 +当然,实际的 `Content-Type` 头、状态码等等,将来自于你返回的 `Response` 对象。 /// -### OpenAPI 中的文档和重载 `Response` +### 在 OpenAPI 中文档化并重载 `Response` { #document-in-openapi-and-override-response } 如果你想要在函数内重载响应,但是同时在 OpenAPI 中文档化「媒体类型」,你可以使用 `response_class` 参数并返回一个 `Response` 对象。 接着 `response_class` 参数只会被用来文档化 OpenAPI 的 *路径操作*,你的 `Response` 用来返回响应。 -### 直接返回 `HTMLResponse` +#### 直接返回 `HTMLResponse` { #return-an-htmlresponse-directly } 比如像这样: -{* ../../docs_src/custom_response/tutorial004.py hl[7,23,21] *} +{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} 在这个例子中,函数 `generate_html_response()` 已经生成并返回 `Response` 对象而不是在 `str` 中返回 HTML。 -通过返回函数 `generate_html_response()` 的调用结果,你已经返回一个重载 **FastAPI** 默认行为的 `Response` 对象, +通过返回函数 `generate_html_response()` 的调用结果,你已经返回一个重载 **FastAPI** 默认行为的 `Response` 对象。 -但如果你在 `response_class` 中也传入了 `HTMLResponse`,**FastAPI** 会知道如何在 OpenAPI 和交互式文档中使用 `text/html` 将其文档化为 HTML。 +但如果你在 `response_class` 中也传入了 `HTMLResponse`,**FastAPI** 会知道如何在 OpenAPI 和交互式文档中使用 `text/html` 将其文档化为 HTML: -## 可用响应 +## 可用响应 { #available-responses } 这里有一些可用的响应。 @@ -115,7 +121,7 @@ /// -### `Response` +### `Response` { #response } 其他全部的响应都继承自主类 `Response`。 @@ -128,77 +134,115 @@ * `headers` - 一个由字符串组成的 `dict`。 * `media_type` - 一个给出媒体类型的 `str`,比如 `"text/html"`。 -FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它还将包含一个基于 media_type 的 Content-Type 头,并为文本类型附加一个字符集。 +FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它还将包含一个基于 `media_type` 的 Content-Type 头,并为文本类型附加一个字符集。 +{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} -{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} - -### `HTMLResponse` +### `HTMLResponse` { #htmlresponse } 如上文所述,接受文本或字节并返回 HTML 响应。 -### `PlainTextResponse` +### `PlainTextResponse` { #plaintextresponse } 接受文本或字节并返回纯文本响应。 -{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} -### `JSONResponse` +### `JSONResponse` { #jsonresponse } 接受数据并返回一个 `application/json` 编码的响应。 如上文所述,这是 **FastAPI** 中使用的默认响应。 -### `ORJSONResponse` +### `ORJSONResponse` { #orjsonresponse } 如上文所述,`ORJSONResponse` 是一个使用 `orjson` 的快速的可选 JSON 响应。 +/// info | 信息 -### `UJSONResponse` +这需要先安装 `orjson`,例如使用 `pip install orjson`。 + +/// + +### `UJSONResponse` { #ujsonresponse } `UJSONResponse` 是一个使用 `ujson` 的可选 JSON 响应。 +/// info | 信息 + +这需要先安装 `ujson`,例如使用 `pip install ujson`。 + +/// + /// warning | 警告 在处理某些边缘情况时,`ujson` 不如 Python 的内置实现那么谨慎。 /// -{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} -/// tip | 小贴士 +/// tip | 提示 `ORJSONResponse` 可能是一个更快的选择。 /// -### `RedirectResponse` +### `RedirectResponse` { #redirectresponse } -返回 HTTP 重定向。默认情况下使用 307 状态代码(临时重定向)。 +返回 HTTP 重定向。默认情况下使用 307 状态码(临时重定向)。 -{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} +你可以直接返回一个 `RedirectResponse`: -### `StreamingResponse` +{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} + +--- + +或者你可以把它用于 `response_class` 参数: + +{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} + +如果你这么做,那么你可以在 *路径操作* 函数中直接返回 URL。 + +在这种情况下,将使用 `RedirectResponse` 的默认 `status_code`,即 `307`。 + +--- + +你也可以将 `status_code` 参数和 `response_class` 参数结合使用: + +{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} + +### `StreamingResponse` { #streamingresponse } 采用异步生成器或普通生成器/迭代器,然后流式传输响应主体。 -{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} -#### 对类似文件的对象使用 `StreamingResponse` +#### 对类似文件的对象使用 `StreamingResponse` { #using-streamingresponse-with-file-like-objects } -如果您有类似文件的对象(例如,由 `open()` 返回的对象),则可以在 `StreamingResponse` 中将其返回。 +如果您有一个类文件对象(例如由 `open()` 返回的对象),你可以创建一个生成器函数来迭代该类文件对象。 -包括许多与云存储,视频处理等交互的库。 +这样,你就不必先把它全部读入内存,可以将该生成器函数传给 `StreamingResponse` 并返回它。 -{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} +这也包括许多与云存储、视频处理等交互的库。 -/// tip | 小贴士 +{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} + +1. 这是生成器函数。之所以是「生成器函数」,是因为它内部包含 `yield` 语句。 +2. 通过使用 `with` 代码块,我们可以确保在生成器函数完成后关闭类文件对象。因此,在它完成发送响应之后会被关闭。 +3. 这个 `yield from` 告诉函数去迭代名为 `file_like` 的那个对象。然后,对于每个被迭代出来的部分,都把该部分作为来自这个生成器函数(`iterfile`)的值再 `yield` 出去。 + + 因此,它是一个把「生成」工作内部转交给其他东西的生成器函数。 + + 通过这种方式,我们可以把它放在 `with` 代码块中,从而确保类文件对象在结束后被关闭。 + +/// tip | 提示 注意在这里,因为我们使用的是不支持 `async` 和 `await` 的标准 `open()`,我们使用普通的 `def` 声明了路径操作。 /// -### `FileResponse` +### `FileResponse` { #fileresponse } 异步传输文件作为响应。 @@ -209,10 +253,60 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 * `media_type` - 给出媒体类型的字符串。如果未设置,则文件名或路径将用于推断媒体类型。 * `filename` - 如果给出,它将包含在响应的 `Content-Disposition` 中。 -文件响应将包含适当的 `Content-Length`,`Last-Modified` 和 `ETag` 的响应头。 +文件响应将包含适当的 `Content-Length`、`Last-Modified` 和 `ETag` 响应头。 -{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} -## 额外文档 +你也可以使用 `response_class` 参数: -您还可以使用 `response` 在 OpenAPI 中声明媒体类型和许多其他详细信息:[OpenAPI 中的额外文档](additional-responses.md){.internal-link target=_blank}。 +{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} + +在这种情况下,你可以在 *路径操作* 函数中直接返回文件路径。 + +## 自定义响应类 { #custom-response-class } + +你可以创建你自己的自定义响应类,继承自 `Response` 并使用它。 + +例如,假设你想使用 `orjson`,但要使用内置 `ORJSONResponse` 类没有启用的一些自定义设置。 + +假设你想让它返回带缩进、格式化的 JSON,因此你想使用 orjson 选项 `orjson.OPT_INDENT_2`。 + +你可以创建一个 `CustomORJSONResponse`。你需要做的主要事情是实现一个返回 `bytes` 的 `Response.render(content)` 方法: + +{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} + +现在,不再是返回: + +```json +{"message": "Hello World"} +``` + +…这个响应将返回: + +```json +{ + "message": "Hello World" +} +``` + +当然,你很可能会找到比格式化 JSON 更好的方式来利用这一点。😉 + +## 默认响应类 { #default-response-class } + +在创建 **FastAPI** 类实例或 `APIRouter` 时,你可以指定默认要使用的响应类。 + +用于定义它的参数是 `default_response_class`。 + +在下面的示例中,**FastAPI** 会在所有 *路径操作* 中默认使用 `ORJSONResponse`,而不是 `JSONResponse`。 + +{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} + +/// tip | 提示 + +你仍然可以像之前一样在 *路径操作* 中重载 `response_class`。 + +/// + +## 额外文档 { #additional-documentation } + +你还可以使用 `responses` 在 OpenAPI 中声明媒体类型和许多其他详细信息:[OpenAPI 中的额外文档](additional-responses.md){.internal-link target=_blank}。 diff --git a/docs/zh/docs/advanced/dataclasses.md b/docs/zh/docs/advanced/dataclasses.md index 4e8e77d2a..d62aef8f3 100644 --- a/docs/zh/docs/advanced/dataclasses.md +++ b/docs/zh/docs/advanced/dataclasses.md @@ -1,97 +1,87 @@ -# 使用数据类 +# 使用数据类 { #using-dataclasses } -FastAPI 基于 **Pydantic** 构建,前文已经介绍过如何使用 Pydantic 模型声明请求与响应。 +FastAPI 基于 **Pydantic** 构建,我已经向你展示过如何使用 Pydantic 模型声明请求与响应。 -但 FastAPI 还可以使用数据类(`dataclasses`): +但 FastAPI 也支持以相同方式使用 `dataclasses`: -{* ../../docs_src/dataclasses_/tutorial001.py hl[1,7:12,19:20] *} +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} -这还是借助于 **Pydantic** 及其内置的 `dataclasses`。 +这仍然得益于 **Pydantic**,因为它对 `dataclasses` 的内置支持。 -因此,即便上述代码没有显式使用 Pydantic,FastAPI 仍会使用 Pydantic 把标准数据类转换为 Pydantic 数据类(`dataclasses`)。 +因此,即便上面的代码没有显式使用 Pydantic,FastAPI 也会使用 Pydantic 将那些标准数据类转换为 Pydantic 风格的 dataclasses。 并且,它仍然支持以下功能: * 数据验证 * 数据序列化 -* 数据存档等 +* 数据文档等 -数据类的和运作方式与 Pydantic 模型相同。实际上,它的底层使用的也是 Pydantic。 +这与使用 Pydantic 模型时的工作方式相同。而且底层实际上也是借助 Pydantic 实现的。 -/// info | 说明 +/// info | 信息 -注意,数据类不支持 Pydantic 模型的所有功能。 +请注意,数据类不能完成 Pydantic 模型能做的所有事情。 -因此,开发时仍需要使用 Pydantic 模型。 +因此,你可能仍然需要使用 Pydantic 模型。 -但如果数据类很多,这一技巧能给 FastAPI 开发 Web API 增添不少助力。🤓 +但如果你已有一堆数据类,这个技巧可以让它们很好地为使用 FastAPI 的 Web API 所用。🤓 /// -## `response_model` 使用数据类 +## 在 `response_model` 中使用数据类 { #dataclasses-in-response-model } -在 `response_model` 参数中使用 `dataclasses`: +你也可以在 `response_model` 参数中使用 `dataclasses`: -{* ../../docs_src/dataclasses_/tutorial002.py hl[1,7:13,19] *} +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} -本例把数据类自动转换为 Pydantic 数据类。 +该数据类会被自动转换为 Pydantic 的数据类。 -API 文档中也会显示相关概图: +这样,它的模式会显示在 API 文档界面中: -## 在嵌套数据结构中使用数据类 +## 在嵌套数据结构中使用数据类 { #dataclasses-in-nested-data-structures } -您还可以把 `dataclasses` 与其它类型注解组合在一起,创建嵌套数据结构。 +你也可以把 `dataclasses` 与其它类型注解组合在一起,创建嵌套数据结构。 -还有一些情况也可以使用 Pydantic 的 `dataclasses`。例如,在 API 文档中显示错误。 +在某些情况下,你可能仍然需要使用 Pydantic 的 `dataclasses` 版本。例如,如果自动生成的 API 文档出现错误。 -本例把标准的 `dataclasses` 直接替换为 `pydantic.dataclasses`: +在这种情况下,你可以直接把标准的 `dataclasses` 替换为 `pydantic.dataclasses`,它是一个可直接替换的实现: -```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } -{!../../docs_src/dataclasses_/tutorial003.py!} -``` +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} -1. 本例依然要从标准的 `dataclasses` 中导入 `field`; +1. 我们仍然从标准库的 `dataclasses` 导入 `field`。 +2. `pydantic.dataclasses` 是 `dataclasses` 的可直接替换版本。 +3. `Author` 数据类包含一个由 `Item` 数据类组成的列表。 +4. `Author` 数据类被用作 `response_model` 参数。 +5. 你可以将其它标准类型注解与数据类一起用作请求体。 -2. 使用 `pydantic.dataclasses` 直接替换 `dataclasses`; + 在本例中,它是一个 `Item` 数据类列表。 +6. 这里我们返回一个字典,里面的 `items` 是一个数据类列表。 -3. `Author` 数据类包含 `Item` 数据类列表; + FastAPI 仍然能够将数据序列化为 JSON。 +7. 这里的 `response_model` 使用了 “`Author` 数据类列表” 的类型注解。 -4. `Author` 数据类用于 `response_model` 参数; + 同样,你可以将 `dataclasses` 与标准类型注解组合使用。 +8. 注意,这个 *路径操作函数* 使用的是常规的 `def` 而不是 `async def`。 -5. 其它带有数据类的标准类型注解也可以作为请求体; + 一如既往,在 FastAPI 中你可以按需组合 `def` 和 `async def`。 - 本例使用的是 `Item` 数据类列表; + 如果需要回顾何时用哪一个,请查看关于 [`async` 和 `await`](../async.md#in-a-hurry){.internal-link target=_blank} 的文档中的 _“急不可待?”_ 一节。 +9. 这个 *路径操作函数* 返回的不是数据类(当然也可以返回数据类),而是包含内部数据的字典列表。 -6. 这行代码返回的是包含 `items` 的字典,`items` 是数据类列表; + FastAPI 会使用(包含数据类的)`response_model` 参数来转换响应。 - FastAPI 仍能把数据序列化为 JSON; +你可以将 `dataclasses` 与其它类型注解以多种不同方式组合,来构建复杂的数据结构。 -7. 这行代码中,`response_model` 的类型注解是 `Author` 数据类列表; +更多细节请参考上面代码中的内联注释提示。 - 再一次,可以把 `dataclasses` 与标准类型注解一起使用; +## 深入学习 { #learn-more } -8. 注意,*路径操作函数*使用的是普通函数,不是异步函数; +你还可以把 `dataclasses` 与其它 Pydantic 模型组合、从它们继承、把它们包含到你自己的模型中等。 - 与往常一样,在 FastAPI 中,可以按需组合普通函数与异步函数; +想了解更多,请查看 Pydantic 关于 dataclasses 的文档。 - 如果不清楚何时使用异步函数或普通函数,请参阅**急不可待?**一节中对 `async` 与 `await` 的说明; +## 版本 { #version } -9. *路径操作函数*返回的不是数据类(虽然它可以返回数据类),而是返回内含数据的字典列表; - - FastAPI 使用(包含数据类的) `response_model` 参数转换响应。 - -把 `dataclasses` 与其它类型注解组合在一起,可以组成不同形式的复杂数据结构。 - -更多内容详见上述代码内的注释。 - -## 深入学习 - -您还可以把 `dataclasses` 与其它 Pydantic 模型组合在一起,继承合并的模型,把它们包含在您自己的模型里。 - -详见 Pydantic 官档 - 数据类。 - -## 版本 - -本章内容自 FastAPI `0.67.0` 版起生效。🔖 +自 FastAPI 版本 `0.67.0` 起可用。🔖 diff --git a/docs/zh/docs/advanced/events.md b/docs/zh/docs/advanced/events.md index 1ef6cdd3c..7b49931a4 100644 --- a/docs/zh/docs/advanced/events.md +++ b/docs/zh/docs/advanced/events.md @@ -1,18 +1,18 @@ -# 生命周期事件 +# 生命周期事件 { #lifespan-events } 你可以定义在应用**启动**前执行的逻辑(代码)。这意味着在应用**开始接收请求**之前,这些代码只会被执行**一次**。 同样地,你可以定义在应用**关闭**时应执行的逻辑。在这种情况下,这段代码将在**处理可能的多次请求后**执行**一次**。 -因为这段代码在应用开始接收请求**之前**执行,也会在处理可能的若干请求**之后**执行,它覆盖了整个应用程序的**生命周期**("生命周期"这个词很重要😉)。 +因为这段代码在应用开始接收请求**之前**执行,也会在处理可能的若干请求**之后**执行,它覆盖了整个应用程序的**生命周期**(“生命周期”这个词很重要😉)。 这对于设置你需要在整个应用中使用的**资源**非常有用,这些资源在请求之间**共享**,你可能需要在之后进行**释放**。例如,数据库连接池,或加载一个共享的机器学习模型。 -## 用例 +## 用例 { #use-case } -让我们从一个示例用例开始,看看如何解决它。 +让我们从一个示例**用例**开始,看看如何用它来解决问题。 -假设你有几个**机器学习的模型**,你想要用它们来处理请求。 +假设你有几个**机器学习的模型**,你想要用它们来处理请求。🤖 相同的模型在请求之间是共享的,因此并非每个请求或每个用户各自拥有一个模型。 @@ -20,19 +20,17 @@ 你可以在模块/文件的顶部加载它,但这也意味着即使你只是在运行一个简单的自动化测试,它也会**加载模型**,这样测试将**变慢**,因为它必须在能够独立运行代码的其他部分之前等待模型加载完成。 -这就是我们要解决的问题——在处理请求前加载模型,但只是在应用开始接收请求前,而不是代码执行时。 +这就是我们要解决的问题——在处理请求前加载模型,但只是在应用开始接收请求前,而不是在代码被加载时。 -## 生命周期 lifespan +## Lifespan { #lifespan } -你可以使用`FastAPI()`应用的`lifespan`参数和一个上下文管理器(稍后我将为你展示)来定义**启动**和**关闭**的逻辑。 +你可以使用 `FastAPI` 应用的 `lifespan` 参数和一个“上下文管理器”(稍后我将为你展示)来定义**启动**和**关闭**的逻辑。 让我们从一个例子开始,然后详细介绍。 -我们使用`yield`创建了一个异步函数`lifespan()`像这样: +我们使用 `yield` 创建了一个异步函数 `lifespan()` 像这样: -```Python hl_lines="16 19" -{!../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} 在这里,我们在 `yield` 之前将(虚拟的)模型函数放入机器学习模型的字典中,以此模拟加载模型的耗时**启动**操作。这段代码将在应用程序**开始处理请求之前**执行,即**启动**期间。 @@ -40,35 +38,31 @@ /// tip | 提示 -**关闭**事件只会在你停止应用时触发。 +**关闭**事件会在你**停止**应用时发生。 -可能你需要启动一个新版本,或者你只是你厌倦了运行它。 🤷 +可能你需要启动一个新版本,或者你只是厌倦了运行它。 🤷 /// -## 生命周期函数 +### 生命周期函数 { #lifespan-function } 首先要注意的是,我们定义了一个带有 `yield` 的异步函数。这与带有 `yield` 的依赖项非常相似。 -```Python hl_lines="14-19" -{!../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} -这个函数在 `yield`之前的部分,会在应用启动前执行。 +这个函数在 `yield` 之前的部分,会在应用启动前执行。 剩下的部分在 `yield` 之后,会在应用完成后执行。 -## 异步上下文管理器 +### 异步上下文管理器 { #async-context-manager } -如你所见,这个函数有一个装饰器 `@asynccontextmanager` 。 +如你所见,这个函数有一个装饰器 `@asynccontextmanager`。 它将函数转化为所谓的“**异步上下文管理器**”。 -```Python hl_lines="1 13" -{!../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} -在 Python 中, **上下文管理器**是一个你可以在 `with` 语句中使用的东西,例如,`open()` 可以作为上下文管理器使用。 +在 Python 中,**上下文管理器**是一个你可以在 `with` 语句中使用的东西,例如,`open()` 可以作为上下文管理器使用。 ```Python with open("file.txt") as file: @@ -82,21 +76,19 @@ async with lifespan(app): await do_stuff() ``` -你可以像上面一样创建了一个上下文管理器或者异步上下文管理器,它的作用是在进入 `with` 块时,执行 `yield` 之前的代码,并且在离开 `with` 块时,执行 `yield` 后面的代码。 +你可以像上面一样创建一个上下文管理器或者异步上下文管理器,它的作用是在进入 `with` 块时,执行 `yield` 之前的代码,并且在离开 `with` 块时,执行 `yield` 后面的代码。 但在我们上面的例子里,我们并不是直接使用,而是传递给 FastAPI 来供其使用。 -`FastAPI()` 的 `lifespan` 参数接受一个**异步上下文管理器**,所以我们可以把我们新定义的上下文管理器 `lifespan` 传给它。 +`FastAPI` 的 `lifespan` 参数接受一个**异步上下文管理器**,所以我们可以把我们新定义的异步上下文管理器 `lifespan` 传给它。 -```Python hl_lines="22" -{!../../docs_src/events/tutorial003.py!} -``` +{* ../../docs_src/events/tutorial003_py39.py hl[22] *} -## 替代事件(弃用) +## 替代事件(弃用) { #alternative-events-deprecated } /// warning | 警告 -配置**启动**和**关闭**事件的推荐方法是使用 `FastAPI()` 应用的 `lifespan` 参数,如前所示。如果你提供了一个 `lifespan` 参数,启动(`startup`)和关闭(`shutdown`)事件处理器将不再生效。要么使用 `lifespan`,要么配置所有事件,两者不能共用。 +配置**启动**和**关闭**的推荐方法是使用 `FastAPI` 应用的 `lifespan` 参数,如前所示。如果你提供了一个 `lifespan` 参数,启动(`startup`)和关闭(`shutdown`)事件处理器将不再生效。要么使用 `lifespan`,要么配置所有事件,两者不能共用。 你可以跳过这一部分。 @@ -104,70 +96,70 @@ async with lifespan(app): 有一种替代方法可以定义在**启动**和**关闭**期间执行的逻辑。 -**FastAPI** 支持定义在应用启动前,或应用关闭时执行的事件处理器(函数)。 +你可以定义在应用启动前或应用关闭时需要执行的事件处理器(函数)。 事件函数既可以声明为异步函数(`async def`),也可以声明为普通函数(`def`)。 -### `startup` 事件 +### `startup` 事件 { #startup-event } -使用 `startup` 事件声明 `app` 启动前运行的函数: +使用事件 `"startup"` 声明一个在应用启动前运行的函数: -{* ../../docs_src/events/tutorial001.py hl[8] *} +{* ../../docs_src/events/tutorial001_py39.py hl[8] *} -本例中,`startup` 事件处理器函数为项目数据库(只是**字典**)提供了一些初始值。 +本例中,`startup` 事件处理器函数为项目“数据库”(只是一个 `dict`)提供了一些初始值。 **FastAPI** 支持多个事件处理器函数。 只有所有 `startup` 事件处理器运行完毕,**FastAPI** 应用才开始接收请求。 -### `shutdown` 事件 +### `shutdown` 事件 { #shutdown-event } -使用 `shutdown` 事件声明 `app` 关闭时运行的函数: +使用事件 `"shutdown"` 声明一个在应用关闭时运行的函数: -{* ../../docs_src/events/tutorial002.py hl[6] *} +{* ../../docs_src/events/tutorial002_py39.py hl[6] *} -此处,`shutdown` 事件处理器函数在 `log.txt` 中写入一行文本 `Application shutdown`。 +此处,`shutdown` 事件处理器函数会向文件 `log.txt` 写入一行文本 `"Application shutdown"`。 -/// info | 说明 +/// info | 信息 -`open()` 函数中,`mode="a"` 指的是**追加**。因此这行文本会添加在文件已有内容之后,不会覆盖之前的内容。 +在 `open()` 函数中,`mode="a"` 指的是“追加”。因此这行文本会添加在文件已有内容之后,不会覆盖之前的内容。 /// /// tip | 提示 -注意,本例使用 Python `open()` 标准函数与文件交互。 +注意,本例使用 Python 标准的 `open()` 函数与文件交互。 -这个函数执行 I/O(输入/输出)操作,需要等待内容写进磁盘。 +这个函数执行 I/O(输入/输出)操作,需要“等待”内容写进磁盘。 -但 `open()` 函数不支持使用 `async` 与 `await`。 +但 `open()` 不使用 `async` 和 `await`。 -因此,声明事件处理函数要使用 `def`,不能使用 `asnyc def`。 +因此,声明事件处理函数要使用 `def`,而不是 `async def`。 /// -### `startup` 和 `shutdown` 一起使用 +### `startup` 和 `shutdown` 一起使用 { #startup-and-shutdown-together } 启动和关闭的逻辑很可能是连接在一起的,你可能希望启动某个东西然后结束它,获取一个资源然后释放它等等。 在不共享逻辑或变量的不同函数中处理这些逻辑比较困难,因为你需要在全局变量中存储值或使用类似的方式。 -因此,推荐使用 `lifespan` 。 +因此,推荐使用上面所述的 `lifespan`。 -## 技术细节 +## 技术细节 { #technical-details } 只是为好奇者提供的技术细节。🤓 -在底层,这部分是生命周期协议的一部分,参见 ASGI 技术规范,定义了称为启动(`startup`)和关闭(`shutdown`)的事件。 +在底层,这部分是 ASGI 技术规范中的 Lifespan 协议的一部分,定义了称为 `startup` 和 `shutdown` 的事件。 -/// info | 说明 +/// info | 信息 -有关事件处理器的详情,请参阅 Starlette 官档 - 事件。 +你可以在 Starlette 的 Lifespan 文档 中阅读更多关于 `lifespan` 处理器的内容。 -包括如何处理生命周期状态,这可以用于程序的其他部分。 +包括如何处理生命周期状态,以便在代码的其他部分使用。 /// -## 子应用 +## 子应用 { #sub-applications } -🚨 **FastAPI** 只会触发主应用中的生命周期事件,不包括[子应用 - 挂载](sub-applications.md){.internal-link target=_blank}中的。 +🚨 请注意,这些生命周期事件(startup 和 shutdown)只会在主应用上执行,不会在[子应用 - 挂载](sub-applications.md){.internal-link target=_blank}上执行。 diff --git a/docs/zh/docs/advanced/generate-clients.md b/docs/zh/docs/advanced/generate-clients.md index bcb9ba2bf..48a4ba07a 100644 --- a/docs/zh/docs/advanced/generate-clients.md +++ b/docs/zh/docs/advanced/generate-clients.md @@ -1,237 +1,208 @@ -# 生成客户端 +# 生成 SDK { #generating-sdks } -因为 **FastAPI** 是基于OpenAPI规范的,自然您可以使用许多相匹配的工具,包括自动生成API文档 (由 Swagger UI 提供)。 +因为 **FastAPI** 基于 **OpenAPI** 规范,它的 API 可以用许多工具都能理解的标准格式来描述。 -一个不太明显而又特别的优势是,你可以为你的API针对不同的**编程语言**来**生成客户端**(有时候被叫做 **SDKs** )。 +这让你可以轻松生成最新的**文档**、多语言的客户端库(**SDKs**),以及与代码保持同步的**测试**或**自动化工作流**。 -## OpenAPI 客户端生成 +本指南将带你为 FastAPI 后端生成一个 **TypeScript SDK**。 -有许多工具可以从**OpenAPI**生成客户端。 +## 开源 SDK 生成器 { #open-source-sdk-generators } -一个常见的工具是 OpenAPI Generator。 +一个功能多样的选择是 OpenAPI Generator,它支持**多种编程语言**,可以根据你的 OpenAPI 规范生成 SDK。 -如果您正在开发**前端**,一个非常有趣的替代方案是 openapi-ts。 +对于 **TypeScript 客户端**,Hey API 是为 TypeScript 生态打造的专用方案,提供优化的使用体验。 -## 生成一个 TypeScript 前端客户端 +你还可以在 OpenAPI.Tools 上发现更多 SDK 生成器。 -让我们从一个简单的 FastAPI 应用开始: +/// tip | 提示 -{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} - -请注意,*路径操作* 定义了他们所用于请求数据和回应数据的模型,所使用的模型是`Item` 和 `ResponseMessage`。 - -### API 文档 - -如果您访问API文档,您将看到它具有在请求中发送和在响应中接收数据的**模式(schemas)**: - - - -您可以看到这些模式,因为它们是用程序中的模型声明的。 - -那些信息可以在应用的 **OpenAPI模式** 被找到,然后显示在API文档中(通过Swagger UI)。 - -OpenAPI中所包含的模型里有相同的信息可以用于 **生成客户端代码**。 - -### 生成一个TypeScript 客户端 - -现在我们有了带有模型的应用,我们可以为前端生成客户端代码。 - -#### 安装 `openapi-ts` - -您可以使用以下工具在前端代码中安装 `openapi-ts`: - -
- -```console -$ npm install @hey-api/openapi-ts --save-dev - ----> 100% -``` - -
- -#### 生成客户端代码 - -要生成客户端代码,您可以使用现在将要安装的命令行应用程序 `openapi-ts`。 - -因为它安装在本地项目中,所以您可能无法直接使用此命令,但您可以将其放在 `package.json` 文件中。 - -它可能看起来是这样的: - -```JSON hl_lines="7" -{ - "name": "frontend-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios" - }, - "author": "", - "license": "", - "devDependencies": { - "@hey-api/openapi-ts": "^0.27.38", - "typescript": "^4.6.2" - } -} -``` - -在这里添加 NPM `generate-client` 脚本后,您可以使用以下命令运行它: - -
- -```console -$ npm run generate-client - -frontend-app@1.0.0 generate-client /home/user/code/frontend-app -> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios -``` - -
- -此命令将在 `./src/client` 中生成代码,并将在其内部使用 `axios`(前端HTTP库)。 - -### 尝试客户端代码 - -现在您可以导入并使用客户端代码,它可能看起来像这样,请注意,您可以为这些方法使用自动补全: - - - -您还将自动补全要发送的数据: - - - -/// tip - -请注意, `name` 和 `price` 的自动补全,是通过其在`Item`模型(FastAPI)中的定义实现的。 +FastAPI 会自动生成 **OpenAPI 3.1** 规范,因此你使用的任何工具都必须支持该版本。 /// -如果发送的数据字段不符,你也会看到编辑器的错误提示: +## 来自 FastAPI 赞助商的 SDK 生成器 { #sdk-generators-from-fastapi-sponsors } + +本节介绍的是由赞助 FastAPI 的公司提供的、具备**风险投资背景**或**公司支持**的方案。这些产品在高质量生成的 SDK 之上,提供了**更多特性**和**集成**。 + +通过 ✨ [**赞助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨,这些公司帮助确保框架及其**生态**保持健康并且**可持续**。 + +他们的赞助也体现了对 FastAPI **社区**(也就是你)的高度承诺,不仅关注提供**优秀的服务**,也支持一个**健壮且繁荣的框架**——FastAPI。🙇 + +例如,你可以尝试: + +* Speakeasy +* Stainless +* liblab + +其中一些方案也可能是开源的或提供免费层级,你可以不花钱就先试用。其他商业 SDK 生成器也可在网上找到。🤓 + +## 创建一个 TypeScript SDK { #create-a-typescript-sdk } + +先从一个简单的 FastAPI 应用开始: + +{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} + +请注意,这些*路径操作*使用 `Item` 和 `ResponseMessage` 模型来定义它们的请求载荷和响应载荷。 + +### API 文档 { #api-docs } + +访问 `/docs` 时,你会看到有用于请求发送和响应接收数据的**模式**: + + + +之所以能看到这些模式,是因为它们在应用中用模型声明了。 + +这些信息会包含在应用的 **OpenAPI 模式** 中,并显示在 API 文档里。 + +OpenAPI 中包含的这些模型信息就是用于**生成客户端代码**的基础。 + +### Hey API { #hey-api } + +当我们有了带模型的 FastAPI 应用后,可以使用 Hey API 来生成 TypeScript 客户端。最快的方式是通过 npx: + +```sh +npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client +``` + +这会在 `./src/client` 生成一个 TypeScript SDK。 + +你可以在其官网了解如何安装 `@hey-api/openapi-ts`,以及阅读生成结果的说明。 + +### 使用 SDK { #using-the-sdk } + +现在你可以导入并使用客户端代码了。它可能是这样,并且你会发现方法有自动补全: + + + +要发送的载荷也会有自动补全: + + + +/// tip | 提示 + +请注意 `name` 和 `price` 的自动补全,它们是在 FastAPI 应用中的 `Item` 模型里定义的。 + +/// + +你发送的数据如果不符合要求,会在编辑器中显示内联错误: -响应(response)对象也拥有自动补全: +响应对象同样有自动补全: -## 带有标签的 FastAPI 应用 +## 带有标签的 FastAPI 应用 { #fastapi-app-with-tags } -在许多情况下,你的FastAPI应用程序会更复杂,你可能会使用标签来分隔不同组的*路径操作(path operations)*。 +很多情况下,你的 FastAPI 应用会更大,你可能会用标签来划分不同组的*路径操作*。 -例如,您可以有一个用 `items` 的部分和另一个用于 `users` 的部分,它们可以用标签来分隔: +例如,你可以有一个 **items** 相关的部分和另一个 **users** 相关的部分,它们可以用标签来分隔: {* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} -### 生成带有标签的 TypeScript 客户端 +### 生成带标签的 TypeScript 客户端 { #generate-a-typescript-client-with-tags } -如果您使用标签为FastAPI应用生成客户端,它通常也会根据标签分割客户端代码。 +如果你为使用了标签的 FastAPI 应用生成客户端,通常也会根据标签来拆分客户端代码。 -通过这种方式,您将能够为客户端代码进行正确地排序和分组: +这样你就可以在客户端代码中把内容正确地组织和分组: -在这个案例中,您有: +在这个例子中,你会有: * `ItemsService` * `UsersService` -### 客户端方法名称 +### 客户端方法名 { #client-method-names } -现在生成的方法名像 `createItemItemsPost` 看起来不太简洁: +现在,像 `createItemItemsPost` 这样的生成方法名看起来不太简洁: ```TypeScript ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) ``` -...这是因为客户端生成器为每个 *路径操作* 使用OpenAPI的内部 **操作 ID(operation ID)**。 +……这是因为客户端生成器会把每个*路径操作*的 OpenAPI 内部**操作 ID(operation ID)**用作方法名的一部分。 -OpenAPI要求每个操作 ID 在所有 *路径操作* 中都是唯一的,因此 FastAPI 使用**函数名**、**路径**和**HTTP方法/操作**来生成此操作ID,因为这样可以确保这些操作 ID 是唯一的。 +OpenAPI 要求每个操作 ID 在所有*路径操作*中都是唯一的,因此 FastAPI 会使用**函数名**、**路径**和**HTTP 方法/操作**来生成操作 ID,以确保其唯一性。 -但接下来我会告诉你如何改进。 🤓 +接下来我会告诉你如何改进。🤓 -## 自定义操作ID和更好的方法名 +## 自定义操作 ID 与更好的方法名 { #custom-operation-ids-and-better-method-names } -您可以**修改**这些操作ID的**生成**方式,以使其更简洁,并在客户端中具有**更简洁的方法名称**。 +你可以**修改**这些操作 ID 的**生成**方式,使之更简单,从而在客户端中得到**更简洁的方法名**。 -在这种情况下,您必须确保每个操作ID在其他方面是**唯一**的。 +在这种情况下,你需要用其他方式确保每个操作 ID 依然是**唯一**的。 -例如,您可以确保每个*路径操作*都有一个标签,然后根据**标签**和*路径操作***名称**(函数名)来生成操作ID。 +例如,你可以确保每个*路径操作*都有一个标签,然后基于**标签**和*路径操作***名称**(函数名)来生成操作 ID。 -### 自定义生成唯一ID函数 +### 自定义唯一 ID 生成函数 { #custom-generate-unique-id-function } -FastAPI为每个*路径操作*使用一个**唯一ID**,它用于**操作ID**,也用于任何所需自定义模型的名称,用于请求或响应。 +FastAPI 为每个*路径操作*使用一个**唯一 ID**,它既用于**操作 ID**,也用于请求或响应里任何需要的自定义模型名称。 -你可以自定义该函数。它接受一个 `APIRoute` 对象作为输入,并输出一个字符串。 +你可以自定义这个函数。它接收一个 `APIRoute` 并返回一个字符串。 -例如,以下是一个示例,它使用第一个标签(你可能只有一个标签)和*路径操作*名称(函数名)。 +例如,这里使用第一个标签(你很可能只有一个标签)和*路径操作*名称(函数名)。 -然后,你可以将这个自定义函数作为 `generate_unique_id_function` 参数传递给 **FastAPI**: +然后你可以把这个自定义函数通过 `generate_unique_id_function` 参数传给 **FastAPI**: {* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} -### 使用自定义操作ID生成TypeScript客户端 +### 使用自定义操作 ID 生成 TypeScript 客户端 { #generate-a-typescript-client-with-custom-operation-ids } -现在,如果你再次生成客户端,你会发现它具有改善的方法名称: +现在再次生成客户端,你会看到方法名已经改进: -正如你所见,现在方法名称中只包含标签和函数名,不再包含URL路径和HTTP操作的信息。 +如你所见,方法名现在由标签和函数名组成,不再包含 URL 路径和 HTTP 操作的信息。 -### 预处理用于客户端生成器的OpenAPI规范 +### 为客户端生成器预处理 OpenAPI 规范 { #preprocess-the-openapi-specification-for-the-client-generator } -生成的代码仍然存在一些**重复的信息**。 +生成的代码中仍有一些**重复信息**。 -我们已经知道该方法与 **items** 相关,因为它在 `ItemsService` 中(从标签中获取),但方法名中仍然有标签名作为前缀。😕 +我们已经知道这个方法与 **items** 有关,因为它位于 `ItemsService`(来自标签),但方法名里仍然带有标签名前缀。😕 -一般情况下对于OpenAPI,我们可能仍然希望保留它,因为这将确保操作ID是**唯一的**。 +通常我们仍然希望在 OpenAPI 中保留它,以确保操作 ID 的**唯一性**。 -但对于生成的客户端,我们可以在生成客户端之前**修改** OpenAPI 操作ID,以使方法名称更加美观和**简洁**。 +但对于生成的客户端,我们可以在生成之前**修改** OpenAPI 的操作 ID,只是为了让方法名更美观、更**简洁**。 -我们可以将 OpenAPI JSON 下载到一个名为`openapi.json`的文件中,然后使用以下脚本**删除此前缀的标签**: +我们可以把 OpenAPI JSON 下载到 `openapi.json` 文件中,然后用如下脚本**移除这个标签前缀**: -{* ../../docs_src/generate_clients/tutorial004.py *} +{* ../../docs_src/generate_clients/tutorial004_py39.py *} -通过这样做,操作ID将从类似于 `items-get_items` 的名称重命名为 `get_items` ,这样客户端生成器就可以生成更简洁的方法名称。 +//// tab | Node.js -### 使用预处理的OpenAPI生成TypeScript客户端 - -现在,由于最终结果保存在文件openapi.json中,你可以修改 package.json 文件以使用此本地文件,例如: - -```JSON hl_lines="7" -{ - "name": "frontend-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios" - }, - "author": "", - "license": "", - "devDependencies": { - "@hey-api/openapi-ts": "^0.27.38", - "typescript": "^4.6.2" - } -} +```Javascript +{!> ../../docs_src/generate_clients/tutorial004.js!} ``` -生成新的客户端之后,你现在将拥有**清晰的方法名称**,具备**自动补全**、**错误提示**等功能: +//// + +这样,操作 ID 会从 `items-get_items` 之类的名字重命名为 `get_items`,从而让客户端生成器生成更简洁的方法名。 + +### 使用预处理后的 OpenAPI 生成 TypeScript 客户端 { #generate-a-typescript-client-with-the-preprocessed-openapi } + +因为最终结果现在保存在 `openapi.json` 中,你需要更新输入位置: + +```sh +npx @hey-api/openapi-ts -i ./openapi.json -o src/client +``` + +生成新客户端后,你将拥有**简洁的方法名**,并具备**自动补全**、**内联错误**等功能: -## 优点 +## 优点 { #benefits } -当使用自动生成的客户端时,你将获得以下的自动补全功能: +使用自动生成的客户端时,你会获得以下内容的**自动补全**: -* 方法。 -* 请求体中的数据、查询参数等。 -* 响应数据。 +* 方法 +* 请求体中的数据、查询参数等 +* 响应数据 -你还将获得针对所有内容的错误提示。 +你还会为所有内容获得**内联错误**。 -每当你更新后端代码并**重新生成**前端代码时,新的*路径操作*将作为方法可用,旧的方法将被删除,并且其他任何更改将反映在生成的代码中。 🤓 +每当你更新后端代码并**重新生成**前端时,新的*路径操作*会作为方法可用,旧的方法会被移除,其他任何更改都会反映到生成的代码中。🤓 -这也意味着如果有任何更改,它将自动**反映**在客户端代码中。如果你**构建**客户端,在使用的数据上存在**不匹配**时,它将报错。 +这也意味着如果有任何变更,它会自动**反映**到客户端代码中。而当你**构建**客户端时,如果所用数据存在任何**不匹配**,它会直接报错。 -因此,你将在开发周期的早期**检测到许多错误**,而不必等待错误在生产环境中向最终用户展示,然后尝试调试问题所在。 ✨ +因此,你可以在开发周期的早期就**发现许多错误**,而不必等到错误在生产环境中暴露给最终用户后再去调试问题所在。✨ diff --git a/docs/zh/docs/advanced/index.md b/docs/zh/docs/advanced/index.md index 6525802fc..610c18713 100644 --- a/docs/zh/docs/advanced/index.md +++ b/docs/zh/docs/advanced/index.md @@ -1,21 +1,21 @@ -# 高级用户指南 +# 高级用户指南 { #advanced-user-guide } -## 额外特性 +## 附加功能 { #additional-features } -主要的教程 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 应该足以让你了解 **FastAPI** 的所有主要特性。 +主要的[教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank}足以带你了解 **FastAPI** 的所有主要特性。 -你会在接下来的章节中了解到其他的选项、配置以及额外的特性。 +在接下来的章节中,你将看到其他选项、配置和附加功能。 -/// tip +/// tip | 提示 -接下来的章节**并不一定是**「高级的」。 +接下来的章节不一定是“高级”的。 -而且对于你的使用场景来说,解决方案很可能就在其中。 +对于你的用例,解决方案很可能就在其中之一。 /// -## 先阅读教程 +## 先阅读教程 { #read-the-tutorial-first } -你可能仍会用到 **FastAPI** 主教程 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 中的大多数特性。 +仅凭主要[教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank}中的知识,你已经可以使用 **FastAPI** 的大多数功能。 -接下来的章节我们认为你已经读过 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank},并且假设你已经知晓其中主要思想。 +接下来的章节默认你已经读过它,并理解其中的核心概念。 diff --git a/docs/zh/docs/advanced/middleware.md b/docs/zh/docs/advanced/middleware.md index 65e8c183f..108bbbb5c 100644 --- a/docs/zh/docs/advanced/middleware.md +++ b/docs/zh/docs/advanced/middleware.md @@ -1,4 +1,4 @@ -# 高级中间件 +# 高级中间件 { #advanced-middleware } 用户指南介绍了如何为应用添加[自定义中间件](../tutorial/middleware.md){.internal-link target=_blank} 。 @@ -6,9 +6,9 @@ 本章学习如何使用其它中间件。 -## 添加 ASGI 中间件 +## 添加 ASGI 中间件 { #adding-asgi-middlewares } -因为 **FastAPI** 基于 Starlette,且执行 ASGI 规范,所以可以使用任意 ASGI 中间件。 +因为 **FastAPI** 基于 Starlette,且执行 ASGI 规范,所以可以使用任意 ASGI 中间件。 中间件不必是专为 FastAPI 或 Starlette 定制的,只要遵循 ASGI 规范即可。 @@ -39,11 +39,11 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") `app.add_middleware()` 的第一个参数是中间件的类,其它参数则是要传递给中间件的参数。 -## 集成中间件 +## 集成中间件 { #integrated-middlewares } **FastAPI** 为常见用例提供了一些中间件,下面介绍怎么使用这些中间件。 -/// note | 技术细节 +/// note | 注意 以下几个示例中也可以使用 `from starlette.middleware.something import SomethingMiddleware`。 @@ -51,45 +51,47 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") /// -## `HTTPSRedirectMiddleware` +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } 强制所有传入请求必须是 `https` 或 `wss`。 任何传向 `http` 或 `ws` 的请求都会被重定向至安全方案。 -{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} -## `TrustedHostMiddleware` +## `TrustedHostMiddleware` { #trustedhostmiddleware } 强制所有传入请求都必须正确设置 `Host` 请求头,以防 HTTP 主机头攻击。 -{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} +{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} 支持以下参数: -* `allowed_hosts` - 允许的域名(主机名)列表。`*.example.com` 等通配符域名可以匹配子域名,或使用 `allowed_hosts=["*"]` 允许任意主机名,或省略中间件。 +* `allowed_hosts` - 允许的域名(主机名)列表。`*.example.com` 等通配符域名可以匹配子域名。若要允许任意主机名,可使用 `allowed_hosts=["*"]` 或省略此中间件。 +* `www_redirect` - 若设置为 `True`,对允许主机的非 www 版本的请求将被重定向到其 www 版本。默认为 `True`。 如果传入的请求没有通过验证,则发送 `400` 响应。 -## `GZipMiddleware` +## `GZipMiddleware` { #gzipmiddleware } -处理 `Accept-Encoding` 请求头中包含 `gzip` 请求的 GZip 响应。 +处理 `Accept-Encoding` 请求头中包含 `"gzip"` 请求的 GZip 响应。 中间件会处理标准响应与流响应。 -{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} 支持以下参数: -* `minimum_size` - 小于最小字节的响应不使用 GZip。 默认值是 `500`。 +* `minimum_size` - 小于该最小字节数的响应不使用 GZip。默认值是 `500`。 +* `compresslevel` - GZip 压缩使用的级别,为 1 到 9 的整数。默认为 `9`。值越低压缩越快但文件更大,值越高压缩越慢但文件更小。 -## 其它中间件 +## 其它中间件 { #other-middlewares } 除了上述中间件外,FastAPI 还支持其它ASGI 中间件。 例如: -* Uvicorn 的 `ProxyHeadersMiddleware` +* Uvicorn 的 `ProxyHeadersMiddleware` * MessagePack -其它可用中间件详见 Starlette 官档 -  中间件ASGI Awesome 列表。 +其它可用中间件详见 Starlette 官档 - 中间件ASGI Awesome 列表。 diff --git a/docs/zh/docs/advanced/openapi-callbacks.md b/docs/zh/docs/advanced/openapi-callbacks.md index f021eb10a..6e8df68ae 100644 --- a/docs/zh/docs/advanced/openapi-callbacks.md +++ b/docs/zh/docs/advanced/openapi-callbacks.md @@ -1,12 +1,12 @@ -# OpenAPI 回调 +# OpenAPI 回调 { #openapi-callbacks } -您可以创建触发外部 API 请求的*路径操作* API,这个外部 API 可以是别人创建的,也可以是由您自己创建的。 +您可以创建一个包含*路径操作*的 API,它会触发对别人创建的*外部 API*的请求(很可能就是那个会“使用”您 API 的同一个开发者)。 -API 应用调用外部 API 时的流程叫做**回调**。因为外部开发者编写的软件发送请求至您的 API,然后您的 API 要进行回调,并把请求发送至外部 API。 +当您的 API 应用调用*外部 API*时,这个过程被称为“回调”。因为外部开发者编写的软件会先向您的 API 发送请求,然后您的 API 再进行*回调*,向*外部 API*发送请求(很可能也是该开发者创建的)。 -此时,我们需要存档外部 API 的*信息*,比如应该有哪些*路径操作*,返回什么样的请求体,应该返回哪种响应等。 +此时,我们需要存档外部 API 的*信息*,比如应该有哪些*路径操作*,请求体应该是什么,应该返回什么响应等。 -## 使用回调的应用 +## 使用回调的应用 { #an-app-with-callbacks } 示例如下。 @@ -14,16 +14,16 @@ API 应用调用外部 API 时的流程叫做**回调**。因为外部开发者 发票包括 `id`、`title`(可选)、`customer`、`total` 等属性。 -API 的用户 (外部开发者)要在您的 API 内使用 POST 请求创建一条发票记录。 +API 的用户(外部开发者)要在您的 API 内使用 POST 请求创建一条发票记录。 (假设)您的 API 将: * 把发票发送至外部开发者的消费者 * 归集现金 * 把通知发送至 API 的用户(外部开发者) - * 通过(从您的 API)发送 POST 请求至外部 API (即**回调**)来完成 + * 通过(从您的 API)发送 POST 请求至外部 API(即**回调**)来完成 -## 常规 **FastAPI** 应用 +## 常规 **FastAPI** 应用 { #the-normal-fastapi-app } 添加回调前,首先看下常规 API 应用是什么样子。 @@ -31,17 +31,17 @@ API 的用户 (外部开发者)要在您的 API 内使用 POST 请求创建 这部分代码很常规,您对绝大多数代码应该都比较熟悉了: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[10:14,37:54] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} /// tip | 提示 -`callback_url` 查询参数使用 Pydantic 的 URL 类型。 +`callback_url` 查询参数使用 Pydantic 的 Url 类型。 /// 此处唯一比较新的内容是*路径操作装饰器*中的 `callbacks=invoices_callback_router.routes` 参数,下文介绍。 -## 存档回调 +## 存档回调 { #documenting-the-callback } 实际的回调代码高度依赖于您自己的 API 应用。 @@ -51,14 +51,14 @@ API 的用户 (外部开发者)要在您的 API 内使用 POST 请求创建 ```Python callback_url = "https://example.com/api/v1/invoices/events/" -requests.post(callback_url, json={"description": "Invoice paid", "paid": True}) +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) ``` 但回调最重要的部分可能是,根据 API 要发送给回调请求体的数据等内容,确保您的 API 用户(外部开发者)正确地实现*外部 API*。 因此,我们下一步要做的就是添加代码,为从 API 接收回调的*外部 API*存档。 -这部分文档在 `/docs` 下的 Swagger API 文档中显示,并且会告诉外部开发者如何构建*外部 API*。 +这部分文档在 `/docs` 下的 Swagger UI 中显示,并且会告诉外部开发者如何构建*外部 API*。 本例没有实现回调本身(只是一行代码),只有文档部分。 @@ -66,17 +66,17 @@ requests.post(callback_url, json={"description": "Invoice paid", "paid": True}) 实际的回调只是 HTTP 请求。 -实现回调时,要使用 HTTPXRequests。 +实现回调时,要使用 HTTPXRequests。 /// -## 编写回调文档代码 +## 编写回调文档代码 { #write-the-callback-documentation-code } 应用不执行这部分代码,只是用它来*记录 外部 API* 。 但,您已经知道用 **FastAPI** 创建自动 API 文档有多简单了。 -我们要使用与存档*外部 API* 相同的知识……通过创建外部 API 要实现的*路径操作*(您的 API 要调用的)。 +我们要使用与存档*外部 API* 相同的知识...通过创建外部 API 要实现的*路径操作*(您的 API 要调用的)。 /// tip | 提示 @@ -86,13 +86,13 @@ requests.post(callback_url, json={"description": "Invoice paid", "paid": True}) /// -### 创建回调的 `APIRouter` +### 创建回调的 `APIRouter` { #create-a-callback-apirouter } 首先,新建包含一些用于回调的 `APIRouter`。 -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[5,26] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *} -### 创建回调*路径操作* +### 创建回调*路径操作* { #create-the-callback-path-operation } 创建回调*路径操作*也使用之前创建的 `APIRouter`。 @@ -101,16 +101,16 @@ requests.post(callback_url, json={"description": "Invoice paid", "paid": True}) * 声明要接收的请求体,例如,`body: InvoiceEvent` * 还要声明要返回的响应,例如,`response_model=InvoiceEventReceived` -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[17:19,22:23,29:33] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} 回调*路径操作*与常规*路径操作*有两点主要区别: * 它不需要任何实际的代码,因为应用不会调用这段代码。它只是用于存档*外部 API*。因此,函数的内容只需要 `pass` 就可以了 -* *路径*可以包含 OpenAPI 3 表达式(详见下文),可以使用带参数的变量,以及发送至您的 API 的原始请求的部分 +* *路径*可以包含 OpenAPI 3 表达式(详见下文),可以使用带参数的变量,以及发送至您的 API 的原始请求的部分 -### 回调路径表达式 +### 回调路径表达式 { #the-callback-path-expression } -回调*路径*支持包含发送给您的 API 的原始请求的部分的 OpenAPI 3 表达式。 +回调*路径*支持包含发送给您的 API 的原始请求的部分的 OpenAPI 3 表达式。 本例中是**字符串**: @@ -159,17 +159,17 @@ JSON 请求体包含如下内容: /// tip | 提示 -注意,回调 URL包含 `callback_url` (`https://www.external.org/events`)中的查询参数,还有 JSON 请求体内部的发票 ID(`2expen51ve`)。 +注意,回调 URL 包含 `callback_url`(`https://www.external.org/events`)中的查询参数,还有 JSON 请求体内部的发票 ID(`2expen51ve`)。 /// -### 添加回调路由 +### 添加回调路由 { #add-the-callback-router } 至此,在上文创建的回调路由里就包含了*回调路径操作*(外部开发者要在外部 API 中实现)。 现在使用 API *路径操作装饰器*的参数 `callbacks`,从回调路由传递属性 `.routes`(实际上只是路由/路径操作的**列表**): -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[36] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} /// tip | 提示 @@ -177,9 +177,9 @@ JSON 请求体包含如下内容: /// -### 查看文档 +### 查看文档 { #check-the-docs } -现在,使用 Uvicorn 启动应用,打开 http://127.0.0.1:8000/docs。 +现在,使用 Uvicorn 启动应用,打开 http://127.0.0.1:8000/docs。 就能看到文档的*路径操作*已经包含了**回调**的内容以及*外部 API*: diff --git a/docs/zh/docs/advanced/openapi-webhooks.md b/docs/zh/docs/advanced/openapi-webhooks.md index 92ae8db15..9e64ed4e3 100644 --- a/docs/zh/docs/advanced/openapi-webhooks.md +++ b/docs/zh/docs/advanced/openapi-webhooks.md @@ -1,4 +1,4 @@ -# OpenAPI 网络钩子 +# OpenAPI 网络钩子 { #openapi-webhooks } 有些情况下,您可能想告诉您的 API **用户**,您的应用程序可以携带一些数据调用*他们的*应用程序(给它们发送请求),通常是为了**通知**某种**事件**。 @@ -6,7 +6,7 @@ 这通常被称为**网络钩子**(Webhook)。 -## 使用网络钩子的步骤 +## 使用网络钩子的步骤 { #webhooks-steps } 通常的过程是**您**在代码中**定义**要发送的消息,即**请求的主体**。 @@ -16,27 +16,27 @@ 所有关于注册网络钩子的 URL 的**逻辑**以及发送这些请求的实际代码都由您决定。您可以在**自己的代码**中以任何想要的方式来编写它。 -## 使用 `FastAPI` 和 OpenAPI 文档化网络钩子 +## 使用 `FastAPI` 和 OpenAPI 文档化网络钩子 { #documenting-webhooks-with-fastapi-and-openapi } 使用 **FastAPI**,您可以利用 OpenAPI 来自定义这些网络钩子的名称、您的应用可以发送的 HTTP 操作类型(例如 `POST`、`PUT` 等)以及您的应用将发送的**请求体**。 这能让您的用户更轻松地**实现他们的 API** 来接收您的**网络钩子**请求,他们甚至可能能够自动生成一些自己的 API 代码。 -/// info +/// info | 信息 网络钩子在 OpenAPI 3.1.0 及以上版本中可用,FastAPI `0.99.0` 及以上版本支持。 /// -## 带有网络钩子的应用程序 +## 带有网络钩子的应用程序 { #an-app-with-webhooks } 当您创建一个 **FastAPI** 应用程序时,有一个 `webhooks` 属性可以用来定义网络钩子,方式与您定义*路径操作*的时候相同,例如使用 `@app.webhooks.post()` 。 -{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} 您定义的网络钩子将被包含在 `OpenAPI` 的架构中,并出现在自动生成的**文档 UI** 中。 -/// info +/// info | 信息 `app.webhooks` 对象实际上只是一个 `APIRouter` ,与您在使用多个文件来构建应用程序时所使用的类型相同。 @@ -46,7 +46,7 @@ 这是因为我们预计**您的用户**会以其他方式(例如通过网页仪表板)来定义他们希望接收网络钩子的请求的实际 **URL 路径**。 -### 查看文档 +### 查看文档 { #check-the-docs } 现在您可以启动您的应用程序并访问 http://127.0.0.1:8000/docs. diff --git a/docs/zh/docs/advanced/path-operation-advanced-configuration.md b/docs/zh/docs/advanced/path-operation-advanced-configuration.md index 12600eddb..6c9928ffc 100644 --- a/docs/zh/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/zh/docs/advanced/path-operation-advanced-configuration.md @@ -1,26 +1,26 @@ -# 路径操作的高级配置 +# 路径操作的高级配置 { #path-operation-advanced-configuration } -## OpenAPI 的 operationId +## OpenAPI 的 operationId { #openapi-operationid } /// warning -如果你并非 OpenAPI 的「专家」,你可能不需要这部分内容。 +如果你并非 OpenAPI 的“专家”,你可能不需要这部分内容。 /// -你可以在路径操作中通过参数 `operation_id` 设置要使用的 OpenAPI `operationId`。 +你可以在 *路径操作* 中通过参数 `operation_id` 设置要使用的 OpenAPI `operationId`。 -务必确保每个操作路径的 `operation_id` 都是唯一的。 +务必确保每个操作的 `operation_id` 都是唯一的。 -{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} -### 使用 *路径操作函数* 的函数名作为 operationId +### 使用 *路径操作函数* 的函数名作为 operationId { #using-the-path-operation-function-name-as-the-operationid } -如果你想用你的 API 的函数名作为 `operationId` 的名字,你可以遍历一遍 API 的函数名,然后使用他们的 `APIRoute.name` 重写每个 *路径操作* 的 `operation_id`。 +如果你想用 API 的函数名作为 `operationId`,你可以遍历所有路径操作,并使用它们的 `APIRoute.name` 重写每个 *路径操作* 的 `operation_id`。 你应该在添加了所有 *路径操作* 之后执行此操作。 -{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12,13,14,15,16,17,18,19,20,21,24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} /// tip @@ -36,19 +36,137 @@ /// -## 从 OpenAPI 中排除 +## 从 OpenAPI 中排除 { #exclude-from-openapi } -使用参数 `include_in_schema` 并将其设置为 `False` ,来从生成的 OpenAPI 方案中排除一个 *路径操作*(这样一来,就从自动化文档系统中排除掉了)。 +使用参数 `include_in_schema` 并将其设置为 `False`,来从生成的 OpenAPI 方案中排除一个 *路径操作*(这样一来,就从自动化文档系统中排除掉了): -{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} -## docstring 的高级描述 +## 来自 docstring 的高级描述 { #advanced-description-from-docstring } 你可以限制 *路径操作函数* 的 `docstring` 中用于 OpenAPI 的行数。 -添加一个 `\f` (一个「换页」的转义字符)可以使 **FastAPI** 在那一位置截断用于 OpenAPI 的输出。 +添加一个 `\f`(一个“换页”的转义字符)可以使 **FastAPI** 在那一位置截断用于 OpenAPI 的输出。 剩余部分不会出现在文档中,但是其他工具(比如 Sphinx)可以使用剩余部分。 +{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} -{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19,20,21,22,23,24,25,26,27,28,29] *} +## 附加响应 { #additional-responses } + +你可能已经见过如何为一个 *路径操作* 声明 `response_model` 和 `status_code`。 + +这定义了该 *路径操作* 主响应的元数据。 + +你也可以为它声明带有各自模型、状态码等的附加响应。 + +文档中有一个完整章节,你可以阅读这里的[OpenAPI 中的附加响应](additional-responses.md){.internal-link target=_blank}。 + +## OpenAPI Extra { #openapi-extra } + +当你在应用中声明一个 *路径操作* 时,**FastAPI** 会自动生成与该 *路径操作* 相关的元数据,以包含到 OpenAPI 方案中。 + +/// note | 技术细节 + +在 OpenAPI 规范中,这被称为 Operation 对象。 + +/// + +它包含关于该 *路径操作* 的所有信息,并用于生成自动文档。 + +它包括 `tags`、`parameters`、`requestBody`、`responses` 等。 + +这个特定于 *路径操作* 的 OpenAPI 方案通常由 **FastAPI** 自动生成,但你也可以扩展它。 + +/// tip + +这是一个较低层级的扩展点。 + +如果你只需要声明附加响应,更方便的方式是使用[OpenAPI 中的附加响应](additional-responses.md){.internal-link target=_blank}。 + +/// + +你可以使用参数 `openapi_extra` 扩展某个 *路径操作* 的 OpenAPI 方案。 + +### OpenAPI 扩展 { #openapi-extensions } + +例如,这个 `openapi_extra` 可用于声明 [OpenAPI 扩展](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): + +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} + +当你打开自动 API 文档时,你的扩展会显示在该 *路径操作* 的底部。 + + + +如果你查看最终生成的 OpenAPI(在你的 API 的 `/openapi.json`),你也会看到你的扩展作为该 *路径操作* 的一部分: + +```JSON hl_lines="22" +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "x-aperture-labs-portal": "blue" + } + } + } +} +``` + +### 自定义 OpenAPI 路径操作方案 { #custom-openapi-path-operation-schema } + +`openapi_extra` 中的字典会与该 *路径操作* 自动生成的 OpenAPI 方案进行深度合并。 + +因此,你可以在自动生成的方案上添加额外数据。 + +例如,你可以决定用自己的代码读取并验证请求,而不使用 FastAPI 与 Pydantic 的自动功能,但你仍然希望在 OpenAPI 方案中定义该请求。 + +你可以用 `openapi_extra` 来做到: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} + +在这个示例中,我们没有声明任何 Pydantic 模型。事实上,请求体甚至没有被 解析 为 JSON,而是直接以 `bytes` 读取,并由函数 `magic_data_reader()` 以某种方式负责解析。 + +尽管如此,我们仍然可以声明请求体的预期方案。 + +### 自定义 OpenAPI 内容类型 { #custom-openapi-content-type } + +使用同样的技巧,你可以用一个 Pydantic 模型来定义 JSON Schema,然后把它包含到该 *路径操作* 的自定义 OpenAPI 方案部分中。 + +即使请求中的数据类型不是 JSON,你也可以这样做。 + +例如,在这个应用中我们不使用 FastAPI 集成的从 Pydantic 模型提取 JSON Schema 的功能,也不使用对 JSON 的自动校验。实际上,我们将请求的内容类型声明为 YAML,而不是 JSON: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} + +尽管我们没有使用默认的集成功能,我们仍然使用 Pydantic 模型手动生成我们想以 YAML 接收的数据的 JSON Schema。 + +然后我们直接使用请求并将请求体提取为 `bytes`。这意味着 FastAPI 甚至不会尝试将请求负载解析为 JSON。 + +接着在我们的代码中,我们直接解析该 YAML 内容,然后再次使用同一个 Pydantic 模型来验证该 YAML 内容: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} + +/// tip + +这里我们复用了同一个 Pydantic 模型。 + +但同样地,我们也可以用其他方式对其进行验证。 + +/// diff --git a/docs/zh/docs/advanced/response-change-status-code.md b/docs/zh/docs/advanced/response-change-status-code.md index cc1f2a73e..cdcd39f50 100644 --- a/docs/zh/docs/advanced/response-change-status-code.md +++ b/docs/zh/docs/advanced/response-change-status-code.md @@ -1,10 +1,10 @@ -# 响应 - 更改状态码 +# 响应 - 更改状态码 { #response-change-status-code } 你可能之前已经了解到,你可以设置默认的[响应状态码](../tutorial/response-status-code.md){.internal-link target=_blank}。 但在某些情况下,你需要返回一个不同于默认值的状态码。 -## 使用场景 +## 使用场景 { #use-case } 例如,假设你想默认返回一个HTTP状态码为“OK”`200`。 @@ -14,16 +14,16 @@ 对于这些情况,你可以使用一个`Response`参数。 -## 使用 `Response` 参数 +## 使用 `Response` 参数 { #use-a-response-parameter } 你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies和头部做的那样)。 然后你可以在这个*临时*响应对象中设置`status_code`。 -{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} 然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 -**FastAPI**将使用这个临时响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 +**FastAPI**将使用这个*临时*响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 你也可以在依赖项中声明`Response`参数,并在其中设置状态码。但请注意,最后设置的状态码将会生效。 diff --git a/docs/zh/docs/advanced/response-cookies.md b/docs/zh/docs/advanced/response-cookies.md index d5f2fe6fc..cc311a270 100644 --- a/docs/zh/docs/advanced/response-cookies.md +++ b/docs/zh/docs/advanced/response-cookies.md @@ -1,10 +1,10 @@ -# 响应Cookies +# 响应Cookies { #response-cookies } -## 使用 `Response` 参数 +## 使用 `Response` 参数 { #use-a-response-parameter } -你可以在 *路径函数* 中定义一个类型为 `Response`的参数,这样你就可以在这个临时响应对象中设置cookie了。 +你可以在 *路径操作函数* 中定义一个类型为 `Response` 的参数,这样你就可以在这个临时响应对象中设置cookie了。 -{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} 而且你还可以根据你的需要响应不同的对象,比如常用的 `dict`,数据库model等。 @@ -12,17 +12,17 @@ **FastAPI** 会使用这个 *临时* 响应对象去装在这些cookies信息 (同样还有headers和状态码等信息), 最终会将这些信息和通过`response_model`转化过的数据合并到最终的响应里。 -你也可以在depend中定义`Response`参数,并设置cookie和header。 +你也可以在依赖中定义`Response`参数,并设置cookie和header。 -## 直接响应 `Response` +## 直接响应 `Response` { #return-a-response-directly } 你还可以在直接响应`Response`时直接创建cookies。 -你可以参考[Return a Response Directly](response-directly.md){.internal-link target=_blank}来创建response +你可以参考[直接返回 Response](response-directly.md){.internal-link target=_blank}来创建response 然后设置Cookies,并返回: -{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} /// tip @@ -34,7 +34,7 @@ /// -### 更多信息 +### 更多信息 { #more-info } /// note | 技术细节 diff --git a/docs/zh/docs/advanced/response-directly.md b/docs/zh/docs/advanced/response-directly.md index 4d9cd53f2..8a9cf6ab8 100644 --- a/docs/zh/docs/advanced/response-directly.md +++ b/docs/zh/docs/advanced/response-directly.md @@ -1,4 +1,4 @@ -# 直接返回响应 +# 直接返回响应 { #return-a-response-directly } 当你创建一个 **FastAPI** *路径操作* 时,你可以正常返回以下任意一种数据:`dict`,`list`,Pydantic 模型,数据库模型等等。 @@ -10,11 +10,11 @@ 直接返回响应可能会有用处,比如返回自定义的响应头和 cookies。 -## 返回 `Response` +## 返回 `Response` { #return-a-response } 事实上,你可以返回任意 `Response` 或者任意 `Response` 的子类。 -/// tip | 小贴士 +/// tip | 提示 `JSONResponse` 本身是一个 `Response` 的子类。 @@ -26,18 +26,17 @@ 这种特性给你极大的可扩展性。你可以返回任何数据类型,重写任何数据声明或者校验,等等。 -## 在 `Response` 中使用 `jsonable_encoder` +## 在 `Response` 中使用 `jsonable_encoder` { #using-the-jsonable-encoder-in-a-response } 由于 **FastAPI** 并未对你返回的 `Response` 做任何改变,你必须确保你已经准备好响应内容。 -例如,如果不首先将 Pydantic 模型转换为 `dict`,并将所有数据类型(如 `datetime`、`UUID` 等)转换为兼容 JSON 的类型,则不能将其放入JSONResponse中。 +例如,如果不首先将 Pydantic 模型转换为 `dict`,并将所有数据类型(如 `datetime`、`UUID` 等)转换为兼容 JSON 的类型,则不能将其放入 `JSONResponse` 中。 -对于这些情况,在将数据传递给响应之前,你可以使用 `jsonable_encoder` 来转换你的数据。 +对于这些情况,在将数据传递给响应之前,你可以使用 `jsonable_encoder` 来转换你的数据: +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} -{* ../../docs_src/response_directly/tutorial001.py hl[4,6,20,21] *} - -/// note | 技术细节 +/// note | 注意 你也可以使用 `from starlette.responses import JSONResponse`。 @@ -45,22 +44,22 @@ /// -## 返回自定义 `Response` +## 返回自定义 `Response` { #returning-a-custom-response } -上面的例子展示了需要的所有部分,但还不够实用,因为你本可以只是直接返回 `item`,而**FastAPI** 默认帮你把这个 `item` 放到 `JSONResponse` 中,又默认将其转换成了 `dict`等等。 +上面的例子展示了需要的所有部分,但还不够实用,因为你本可以只是直接返回 `item`,而 **FastAPI** 默认帮你把这个 `item` 放到 `JSONResponse` 中,又默认将其转换成了 `dict` 等等。 现在,让我们看看你如何才能返回一个自定义的响应。 假设你想要返回一个 XML 响应。 -你可以把你的 XML 内容放到一个字符串中,放到一个 `Response` 中,然后返回。 +你可以把你的 XML 内容放到一个字符串中,放到一个 `Response` 中,然后返回: -{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} -## 说明 +## 说明 { #notes } 当你直接返回 `Response` 时,它的数据既没有校验,又不会进行转换(序列化),也不会自动生成文档。 -但是你仍可以参考 [OpenApI 中的额外响应](additional-responses.md){.internal-link target=_blank} 给响应编写文档。 +但是你仍可以参考 [OpenAPI 中的额外响应](additional-responses.md){.internal-link target=_blank} 给响应编写文档。 在后续的章节中你可以了解到如何使用/声明这些自定义的 `Response` 的同时还保留自动化的数据转换和文档等。 diff --git a/docs/zh/docs/advanced/response-headers.md b/docs/zh/docs/advanced/response-headers.md index 5c6a62e93..fa02f53be 100644 --- a/docs/zh/docs/advanced/response-headers.md +++ b/docs/zh/docs/advanced/response-headers.md @@ -1,39 +1,41 @@ -# 响应头 +# 响应头 { #response-headers } -## 使用 `Response` 参数 +## 使用 `Response` 参数 { #use-a-response-parameter } -你可以在你的*路径操作函数*中声明一个`Response`类型的参数(就像你可以为cookies做的那样)。 +你可以在你的*路径操作函数*中声明一个 `Response` 类型的参数(就像你可以为 cookies 做的那样)。 然后你可以在这个*临时*响应对象中设置头部。 -{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *} -然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 +{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *} -**FastAPI**将使用这个临时响应来提取头部(也包括cookies和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 +然后你可以像平常一样返回任何你需要的对象(例如一个 `dict` 或者一个数据库模型)。 -你也可以在依赖项中声明`Response`参数,并在其中设置头部(和cookies)。 +如果你声明了一个 `response_model`,它仍然会被用来过滤和转换你返回的对象。 -## 直接返回 `Response` +**FastAPI** 将使用这个临时响应来提取头部(也包括 cookies 和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何 `response_model` 过滤。 -你也可以在直接返回`Response`时添加头部。 +你也可以在依赖项中声明 `Response` 参数,并在其中设置头部(和 cookies)。 + +## 直接返回 `Response` { #return-a-response-directly } + +你也可以在直接返回 `Response` 时添加头部。 按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递: -{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} - +{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *} /// note | 技术细节 -你也可以使用`from starlette.responses import Response`或`from starlette.responses import JSONResponse`。 +你也可以使用 `from starlette.responses import Response` 或 `from starlette.responses import JSONResponse`。 -**FastAPI**提供了与`fastapi.responses`相同的`starlette.responses`,只是为了方便开发者。但是,大多数可用的响应都直接来自Starlette。 +**FastAPI** 提供了与 `fastapi.responses` 相同的 `starlette.responses`,只是为了方便你(开发者)。但是,大多数可用的响应都直接来自 Starlette。 -由于`Response`经常用于设置头部和cookies,因此**FastAPI**还在`fastapi.Response`中提供了它。 +由于 `Response` 经常用于设置头部和 cookies,**FastAPI** 还在 `fastapi.Response` 中提供了它。 /// -## 自定义头部 +## 自定义头部 { #custom-headers } -请注意,可以使用'X-'前缀添加自定义专有头部。 +请注意,可以通过使用 `X-` 前缀添加自定义专有头部。 -但是,如果你有自定义头部,你希望浏览器中的客户端能够看到它们,你需要将它们添加到你的CORS配置中(在[CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank}中阅读更多),使用在Starlette的CORS文档中记录的`expose_headers`参数。 +但是,如果你有自定义头部,并希望浏览器中的客户端能够看到它们,你需要将它们添加到你的 CORS 配置中(在 [CORS(跨源资源共享)](../tutorial/cors.md){.internal-link target=_blank} 中阅读更多),使用在 Starlette 的 CORS 文档中记录的 `expose_headers` 参数。 diff --git a/docs/zh/docs/advanced/security/http-basic-auth.md b/docs/zh/docs/advanced/security/http-basic-auth.md index 599429f9d..55479d8e3 100644 --- a/docs/zh/docs/advanced/security/http-basic-auth.md +++ b/docs/zh/docs/advanced/security/http-basic-auth.md @@ -1,4 +1,4 @@ -# HTTP 基础授权 +# HTTP 基础授权 { #http-basic-auth } 最简单的用例是使用 HTTP 基础授权(HTTP Basic Auth)。 @@ -6,16 +6,16 @@ 如果没有接收到 HTTP 基础授权,就返回 HTTP 401 `"Unauthorized"` 错误。 -并返回含 `Basic` 值的请求头 `WWW-Authenticate`以及可选的 `realm` 参数。 +并返回响应头 `WWW-Authenticate`,其值为 `Basic`,以及可选的 `realm` 参数。 HTTP 基础授权让浏览器显示内置的用户名与密码提示。 输入用户名与密码后,浏览器会把它们自动发送至请求头。 -## 简单的 HTTP 基础授权 +## 简单的 HTTP 基础授权 { #simple-http-basic-auth } * 导入 `HTTPBasic` 与 `HTTPBasicCredentials` -* 使用 `HTTPBasic` 创建**安全概图** +* 使用 `HTTPBasic` 创建**安全方案** * 在*路径操作*的依赖项中使用 `security` * 返回类型为 `HTTPBasicCredentials` 的对象: * 包含发送的 `username` 与 `password` @@ -26,7 +26,7 @@ HTTP 基础授权让浏览器显示内置的用户名与密码提示。 -## 检查用户名 +## 检查用户名 { #check-the-username } 以下是更完整的示例。 @@ -52,13 +52,13 @@ if not (credentials.username == "stanleyjobson") or not (credentials.password == 但使用 `secrets.compare_digest()`,可以防御**时差攻击**,更加安全。 -### 时差攻击 +### 时差攻击 { #timing-attacks } 什么是**时差攻击**? 假设攻击者试图猜出用户名与密码。 -他们发送用户名为 `johndoe`,密码为 `love123` 的请求。 +他们发送用户名为 `johndoe`,密码为 `love123` 的请求。 然后,Python 代码执行如下操作: @@ -80,28 +80,28 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": 此时,Python 要对比 `stanleyjobsox` 与 `stanleyjobson` 中的 `stanleyjobso`,才能知道这两个字符串不一样。因此会多花费几微秒来返回**错误的用户或密码**。 -#### 反应时间对攻击者的帮助 +#### 反应时间对攻击者的帮助 { #the-time-to-answer-helps-the-attackers } 通过服务器花费了更多微秒才发送**错误的用户或密码**响应,攻击者会知道猜对了一些内容,起码开头字母是正确的。 然后,他们就可以放弃 `johndoe`,再用类似 `stanleyjobsox` 的内容进行尝试。 -#### **专业**攻击 +#### **专业**攻击 { #a-professional-attack } 当然,攻击者不用手动操作,而是编写每秒能执行成千上万次测试的攻击程序,每次都会找到更多正确字符。 但是,在您的应用的**帮助**下,攻击者利用时间差,就能在几分钟或几小时内,以这种方式猜出正确的用户名和密码。 -#### 使用 `secrets.compare_digest()` 修补 +#### 使用 `secrets.compare_digest()` 修补 { #fix-it-with-secrets-compare-digest } 在此,代码中使用了 `secrets.compare_digest()`。 简单的说,它使用相同的时间对比 `stanleyjobsox` 和 `stanleyjobson`,还有 `johndoe` 和 `stanleyjobson`。对比密码时也一样。 -在代码中使用 `secrets.compare_digest()` ,就可以安全地防御全面攻击了。 +在代码中使用 `secrets.compare_digest()` ,就可以安全地防御这整类安全攻击。 -### 返回错误 +### 返回错误 { #return-the-error } -检测到凭证不正确后,返回 `HTTPException` 及状态码 401(与无凭证时返回的内容一样),并添加请求头 `WWW-Authenticate`,让浏览器再次显示登录提示: +检测到凭证不正确后,返回 `HTTPException` 及状态码 401(与无凭证时返回的内容一样),并添加响应头 `WWW-Authenticate`,让浏览器再次显示登录提示: {* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} diff --git a/docs/zh/docs/advanced/security/index.md b/docs/zh/docs/advanced/security/index.md index 267e7ced7..84fec7aab 100644 --- a/docs/zh/docs/advanced/security/index.md +++ b/docs/zh/docs/advanced/security/index.md @@ -1,19 +1,19 @@ -# 高级安全 +# 高级安全 { #advanced-security } -## 附加特性 +## 附加特性 { #additional-features } -除 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性. +除 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank} 中涵盖的功能之外,还有一些额外的功能来处理安全性。 -/// tip | 小贴士 +/// tip | 提示 -接下来的章节 **并不一定是 "高级的"**. +接下来的章节**并不一定是 "高级的"**。 而且对于你的使用场景来说,解决方案很可能就在其中。 /// -## 先阅读教程 +## 先阅读教程 { #read-the-tutorial-first } -接下来的部分假设你已经阅读了主要的 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank}. +接下来的部分假设你已经阅读了主要的 [教程 - 用户指南: 安全性](../../tutorial/security/index.md){.internal-link target=_blank}。 -它们都基于相同的概念,但支持一些额外的功能. +它们都基于相同的概念,但支持一些额外的功能。 diff --git a/docs/zh/docs/advanced/security/oauth2-scopes.md b/docs/zh/docs/advanced/security/oauth2-scopes.md index 784c38490..ce7facf4b 100644 --- a/docs/zh/docs/advanced/security/oauth2-scopes.md +++ b/docs/zh/docs/advanced/security/oauth2-scopes.md @@ -1,274 +1,274 @@ -# OAuth2 作用域 +# OAuth2 作用域 { #oauth2-scopes } -**FastAPI** 无缝集成 OAuth2 作用域(`Scopes`),可以直接使用。 +你可以在 **FastAPI** 中直接使用 OAuth2 作用域(Scopes),它们已无缝集成。 -作用域是更精密的权限系统,遵循 OAuth2 标准,与 OpenAPI 应用(和 API 自动文档)集成。 +这样你就可以按照 OAuth2 标准,构建更精细的权限系统,并将其集成进你的 OpenAPI 应用(以及 API 文档)中。 -OAuth2 也是脸书、谷歌、GitHub、微软、推特等第三方身份验证应用使用的机制。这些身份验证应用在用户登录应用时使用 OAuth2 提供指定权限。 +带作用域的 OAuth2 是很多大型身份验证提供商使用的机制,例如 Facebook、Google、GitHub、Microsoft、X (Twitter) 等。它们用它来为用户和应用授予特定权限。 -脸书、谷歌、GitHub、微软、推特就是 OAuth2 作用域登录。 +每次你“使用” Facebook、Google、GitHub、Microsoft、X (Twitter) “登录”时,该应用就在使用带作用域的 OAuth2。 -本章介绍如何在 **FastAPI** 应用中使用 OAuth2 作用域管理验证与授权。 +本节将介绍如何在你的 **FastAPI** 应用中,使用相同的带作用域的 OAuth2 管理认证与授权。 /// warning | 警告 -本章内容较难,刚接触 FastAPI 的新手可以跳过。 +本节内容相对进阶,如果你刚开始,可以先跳过。 -OAuth2 作用域不是必需的,没有它,您也可以处理身份验证与授权。 +你并不一定需要 OAuth2 作用域,你也可以用你自己的方式处理认证与授权。 -但 OAuth2 作用域与 API(通过 OpenAPI)及 API 文档集成地更好。 +但带作用域的 OAuth2 能很好地集成进你的 API(通过 OpenAPI)和 API 文档。 -不管怎么说,**FastAPI** 支持在代码中使用作用域或其它安全/授权需求项。 +不过,无论如何,你都可以在代码中按需强制这些作用域,或任何其它安全/授权需求。 -很多情况下,OAuth2 作用域就像一把牛刀。 +很多情况下,带作用域的 OAuth2 可能有点“大材小用”。 -但如果您确定要使用作用域,或对它有兴趣,请继续阅读。 +但如果你确实需要它,或者只是好奇,请继续阅读。 /// -## OAuth2 作用域与 OpenAPI +## OAuth2 作用域与 OpenAPI { #oauth2-scopes-and-openapi } -OAuth2 规范的**作用域**是由空格分割的字符串组成的列表。 +OAuth2 规范将“作用域”定义为由空格分隔的字符串列表。 -这些字符串支持任何格式,但不能包含空格。 +这些字符串的内容可以是任意格式,但不应包含空格。 -作用域表示的是**权限**。 +这些作用域表示“权限”。 -OpenAPI 中(例如 API 文档)可以定义**安全方案**。 +在 OpenAPI(例如 API 文档)中,你可以定义“安全方案”(security schemes)。 -这些安全方案在使用 OAuth2 时,还可以声明和使用作用域。 +当这些安全方案使用 OAuth2 时,你还可以声明并使用作用域。 -**作用域**只是(不带空格的)字符串。 +每个“作用域”只是一个(不带空格的)字符串。 -常用于声明特定安全权限,例如: +它们通常用于声明特定的安全权限,例如: -* 常见用例为,`users:read` 或 `users:write` -* 脸书和 Instagram 使用 `instagram_basic` -* 谷歌使用 `https://www.googleapis.com/auth/drive` +* 常见示例:`users:read` 或 `users:write` +* Facebook / Instagram 使用 `instagram_basic` +* Google 使用 `https://www.googleapis.com/auth/drive` -/// info | 说明 +/// info | 信息 -OAuth2 中,**作用域**只是声明特定权限的字符串。 +在 OAuth2 中,“作用域”只是一个声明所需特定权限的字符串。 -是否使用冒号 `:` 等符号,或是不是 URL 并不重要。 +是否包含像 `:` 这样的字符,或者是不是一个 URL,并不重要。 -这些细节只是特定的实现方式。 +这些细节取决于具体实现。 -对 OAuth2 来说,它们都只是字符串而已。 +对 OAuth2 而言,它们都只是字符串。 /// -## 全局纵览 +## 全局纵览 { #global-view } -首先,快速浏览一下以下代码与**用户指南**中 [OAuth2 实现密码哈希与 Bearer JWT 令牌验证](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}一章中代码的区别。以下代码使用 OAuth2 作用域: +首先,让我们快速看看与**用户指南**中 [OAuth2 实现密码(含哈希)、Bearer + JWT 令牌](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank} 示例相比有哪些变化。现在开始使用 OAuth2 作用域: -{* ../../docs_src/security/tutorial005.py hl[2,4,8,12,46,64,105,107:115,121:124,128:134,139,153] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} -下面,我们逐步说明修改的代码内容。 +下面我们逐步回顾这些更改。 -## OAuth2 安全方案 +## OAuth2 安全方案 { #oauth2-security-scheme } -第一个修改的地方是,使用两个作用域 `me` 和 `items ` 声明 OAuth2 安全方案。 +第一个变化是:我们在声明 OAuth2 安全方案时,添加了两个可用的作用域 `me` 和 `items`。 -`scopes` 参数接收**字典**,键是作用域、值是作用域的描述: +参数 `scopes` 接收一个 `dict`,以作用域为键、描述为值: -{* ../../docs_src/security/tutorial005.py hl[62:65] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} -因为声明了作用域,所以登录或授权时会在 API 文档中显示。 +因为我们现在声明了这些作用域,所以当你登录/授权时,它们会显示在 API 文档里。 -此处,选择给予访问权限的作用域: `me` 和 `items`。 +你可以选择要授予访问权限的作用域:`me` 和 `items`。 -这也是使用脸书、谷歌、GitHub 登录时的授权机制。 +这与使用 Facebook、Google、GitHub 等登录时授予权限的机制相同: -## JWT 令牌作用域 +## 带作用域的 JWT 令牌 { #jwt-token-with-scopes } -现在,修改令牌*路径操作*,返回请求的作用域。 +现在,修改令牌的*路径操作*以返回请求的作用域。 -此处仍然使用 `OAuth2PasswordRequestForm`。它包含类型为**字符串列表**的 `scopes` 属性,且`scopes` 属性中包含要在请求里接收的每个作用域。 +我们仍然使用 `OAuth2PasswordRequestForm`。它包含 `scopes` 属性,其值是 `list[str]`,包含请求中接收到的每个作用域。 -这样,返回的 JWT 令牌中就包含了作用域。 +我们把这些作用域作为 JWT 令牌的一部分返回。 /// danger | 危险 -为了简明起见,本例把接收的作用域直接添加到了令牌里。 +为简单起见,此处我们只是把接收到的作用域直接添加到了令牌中。 -但在您的应用中,为了安全,应该只把作用域添加到确实需要作用域的用户,或预定义的用户。 +但在你的应用里,为了安全起见,你应该只添加该用户实际能够拥有的作用域,或你预先定义的作用域。 /// -{* ../../docs_src/security/tutorial005.py hl[153] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} -## 在*路径操作*与依赖项中声明作用域 +## 在*路径操作*与依赖项中声明作用域 { #declare-scopes-in-path-operations-and-dependencies } -接下来,为*路径操作* `/users/me/items/` 声明作用域 `items`。 +现在我们声明,路径操作 `/users/me/items/` 需要作用域 `items`。 -为此,要从 `fastapi` 中导入并使用 `Security` 。 +为此,从 `fastapi` 导入并使用 `Security`。 -`Security` 声明依赖项的方式和 `Depends` 一样,但 `Security` 还能接收作用域(字符串)列表类型的参数 `scopes`。 +你可以用 `Security` 来声明依赖(就像 `Depends` 一样),但 `Security` 还接收一个 `scopes` 参数,其值是作用域(字符串)列表。 -此处使用与 `Depends` 相同的方式,把依赖项函数 `get_current_active_user` 传递给 `Security`。 +在这里,我们把依赖函数 `get_current_active_user` 传给 `Security`(就像用 `Depends` 一样)。 -同时,还传递了作用域**列表**,本例中只传递了一个作用域:`items`(此处支持传递更多作用域)。 +同时还传入一个作用域 `list`,此处仅包含一个作用域:`items`(也可以包含更多)。 -依赖项函数 `get_current_active_user` 还能声明子依赖项,不仅可以使用 `Depends`,也可以使用 `Security`。声明子依赖项函数(`get_current_user`)及更多作用域。 +依赖函数 `get_current_active_user` 也可以声明子依赖,不仅可以用 `Depends`,也可以用 `Security`。它声明了自己的子依赖函数(`get_current_user`),并添加了更多的作用域需求。 -本例要求使用作用域 `me`(还可以使用更多作用域)。 +在这个例子里,它需要作用域 `me`(也可以需要多个作用域)。 -/// note | 笔记 +/// note | 注意 不必在不同位置添加不同的作用域。 -本例使用的这种方式只是为了展示 **FastAPI** 如何处理在不同层级声明的作用域。 +这里这样做,是为了演示 **FastAPI** 如何处理在不同层级声明的作用域。 /// -{* ../../docs_src/security/tutorial005.py hl[4,139,166] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} /// info | 技术细节 -`Security` 实际上是 `Depends` 的子类,而且只比 `Depends` 多一个参数。 +`Security` 实际上是 `Depends` 的子类,它只多了一个我们稍后会看到的参数。 -但使用 `Security` 代替 `Depends`,**FastAPI** 可以声明安全作用域,并在内部使用这些作用域,同时,使用 OpenAPI 存档 API。 +但当你使用 `Security` 而不是 `Depends` 时,**FastAPI** 会知道它可以声明安全作用域,在内部使用它们,并用 OpenAPI 文档化 API。 -但实际上,从 `fastapi` 导入的 `Query`、`Path`、`Depends`、`Security` 等对象,只是返回特殊类的函数。 +另外,从 `fastapi` 导入的 `Query`、`Path`、`Depends`、`Security` 等,实际上都是返回特殊类的函数。 /// -## 使用 `SecurityScopes` +## 使用 `SecurityScopes` { #use-securityscopes } -修改依赖项 `get_current_user`。 +现在更新依赖项 `get_current_user`。 -这是上面的依赖项使用的依赖项。 +上面那些依赖会用到它。 -这里使用的也是之前创建的 OAuth2 方案,并把它声明为依赖项:`oauth2_scheme`。 +这里我们使用之前创建的同一个 OAuth2 方案,并把它声明为依赖:`oauth2_scheme`。 -该依赖项函数本身不需要作用域,因此,可以使用 `Depends` 和 `oauth2_scheme`。不需要指定安全作用域时,不必使用 `Security`。 +因为这个依赖函数本身没有任何作用域需求,所以我们可以用 `Depends(oauth2_scheme)`,当不需要指定安全作用域时,不必使用 `Security`。 -此处还声明了从 `fastapi.security` 导入的 `SecurityScopes` 类型的特殊参数。 +我们还声明了一个从 `fastapi.security` 导入的特殊参数 `SecurityScopes` 类型。 -`SecuriScopes` 类与 `Request` 类似(`Request` 用于直接提取请求对象)。 +这个 `SecurityScopes` 类类似于 `Request`(`Request` 用来直接获取请求对象)。 -{* ../../docs_src/security/tutorial005.py hl[8,105] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} -## 使用 `scopes` +## 使用 `scopes` { #use-the-scopes } 参数 `security_scopes` 的类型是 `SecurityScopes`。 -它的属性 `scopes` 是作用域列表,所有依赖项都把它作为子依赖项。也就是说所有**依赖**……这听起来有些绕,后文会有解释。 +它会有一个 `scopes` 属性,包含一个列表,里面是它自身以及所有把它作为子依赖的依赖项所需要的所有作用域。也就是说,所有“依赖者”……这可能有点绕,下面会再次解释。 -(类 `SecurityScopes` 的)`security_scopes` 对象还提供了单字符串类型的属性 `scope_str`,该属性是(要在本例中使用的)用空格分割的作用域。 +`security_scopes` 对象(类型为 `SecurityScopes`)还提供了一个 `scope_str` 属性,它是一个用空格分隔这些作用域的单个字符串(我们将会用到它)。 -此处还创建了后续代码中要复用(`raise`)的 `HTTPException` 。 +我们创建一个 `HTTPException`,后面可以在多个位置复用(`raise`)它。 -该异常包含了作用域所需的(如有),以空格分割的字符串(使用 `scope_str`)。该字符串要放到包含作用域的 `WWW-Authenticate` 请求头中(这也是规范的要求)。 +在这个异常中,我们包含所需的作用域(如果有的话),以空格分隔的字符串(使用 `scope_str`)。我们把这个包含作用域的字符串放在 `WWW-Authenticate` 响应头中(这是规范要求的一部分)。 -{* ../../docs_src/security/tutorial005.py hl[105,107:115] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} -## 校验 `username` 与数据形状 +## 校验 `username` 与数据形状 { #verify-the-username-and-data-shape } -我们可以校验是否获取了 `username`,并抽取作用域。 +我们校验是否获取到了 `username`,并提取作用域。 -然后,使用 Pydantic 模型校验数据(捕获 `ValidationError` 异常),如果读取 JWT 令牌或使用 Pydantic 模型验证数据时出错,就会触发之前创建的 `HTTPException` 异常。 +然后使用 Pydantic 模型验证这些数据(捕获 `ValidationError` 异常),如果读取 JWT 令牌或用 Pydantic 验证数据时出错,就抛出我们之前创建的 `HTTPException`。 -对此,要使用新的属性 `scopes` 更新 Pydantic 模型 `TokenData`。 +为此,我们给 Pydantic 模型 `TokenData` 添加了一个新属性 `scopes`。 -使用 Pydantic 验证数据可以确保数据中含有由作用域组成的**字符串列表**,以及 `username` 字符串等内容。 +通过用 Pydantic 验证数据,我们可以确保确实得到了例如一个由作用域组成的 `list[str]`,以及一个 `str` 类型的 `username`。 -反之,如果使用**字典**或其它数据结构,就有可能在后面某些位置破坏应用,形成安全隐患。 +而不是,例如得到一个 `dict` 或其它什么,这可能会在后续某个时刻破坏应用,形成安全风险。 -还可以使用用户名验证用户,如果没有用户,也会触发之前创建的异常。 +我们还验证是否存在该用户名的用户,如果没有,就抛出前面创建的同一个异常。 -{* ../../docs_src/security/tutorial005.py hl[46,116:127] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} -## 校验 `scopes` +## 校验 `scopes` { #verify-the-scopes } -接下来,校验所有依赖项和依赖要素(包括*路径操作*)所需的作用域。这些作用域包含在令牌的 `scopes` 里,如果不在其中就会触发 `HTTPException` 异常。 +现在我们要验证,这个依赖以及所有依赖者(包括*路径操作*)所需的所有作用域,是否都包含在接收到的令牌里的作用域中,否则就抛出 `HTTPException`。 -为此,要使用包含所有作用域**字符串列表**的 `security_scopes.scopes`, 。 +为此,我们使用 `security_scopes.scopes`,它包含一个由这些作用域组成的 `list[str]`。 -{* ../../docs_src/security/tutorial005.py hl[128:134] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} -## 依赖项树与作用域 +## 依赖树与作用域 { #dependency-tree-and-scopes } -再次查看这个依赖项树与作用域。 +再次回顾这个依赖树与作用域。 -`get_current_active_user` 依赖项包含子依赖项 `get_current_user`,并在 `get_current_active_user`中声明了作用域 `"me"` 包含所需作用域列表 ,在 `security_scopes.scopes` 中传递给 `get_current_user`。 +由于 `get_current_active_user` 依赖把 `get_current_user` 作为子依赖,因此在 `get_current_active_user` 中声明的作用域 `"me"` 会被包含在传给 `get_current_user` 的 `security_scopes.scopes` 所需作用域列表中。 -*路径操作*自身也声明了作用域,`"items"`,这也是 `security_scopes.scopes` 列表传递给 `get_current_user` 的。 +*路径操作*本身也声明了一个作用域 `"items"`,它也会包含在传给 `get_current_user` 的 `security_scopes.scopes` 列表中。 -依赖项与作用域的层级架构如下: +依赖与作用域的层级结构如下: * *路径操作* `read_own_items` 包含: - * 依赖项所需的作用域 `["items"]`: - * `get_current_active_user`: - * 依赖项函数 `get_current_active_user` 包含: - * 所需的作用域 `"me"` 包含依赖项: - * `get_current_user`: - * 依赖项函数 `get_current_user` 包含: - * 没有作用域需求其自身 - * 依赖项使用 `oauth2_scheme` - * `security_scopes` 参数的类型是 `SecurityScopes`: - * `security_scopes` 参数的属性 `scopes` 是包含上述声明的所有作用域的**列表**,因此: - * `security_scopes.scopes` 包含用于*路径操作*的 `["me", "items"]` - * `security_scopes.scopes` 包含*路径操作* `read_users_me` 的 `["me"]`,因为它在依赖项里被声明 - * `security_scopes.scopes` 包含用于*路径操作* `read_system_status` 的 `[]`(空列表),并且它的依赖项 `get_current_user` 也没有声明任何 `scope` + * 带有依赖的必需作用域 `["items"]`: + * `get_current_active_user`: + * 依赖函数 `get_current_active_user` 包含: + * 带有依赖的必需作用域 `["me"]`: + * `get_current_user`: + * 依赖函数 `get_current_user` 包含: + * 自身不需要任何作用域。 + * 一个使用 `oauth2_scheme` 的依赖。 + * 一个类型为 `SecurityScopes` 的 `security_scopes` 参数: + * 该 `security_scopes` 参数有一个 `scopes` 属性,它是一个包含上面所有已声明作用域的 `list`,因此: + * 对于*路径操作* `read_own_items`,`security_scopes.scopes` 将包含 `["me", "items"]`。 + * 对于*路径操作* `read_users_me`,`security_scopes.scopes` 将包含 `["me"]`,因为它在依赖 `get_current_active_user` 中被声明。 + * 对于*路径操作* `read_system_status`,`security_scopes.scopes` 将包含 `[]`(空列表),因为它既没有声明任何带 `scopes` 的 `Security`,其依赖 `get_current_user` 也没有声明任何 `scopes`。 /// tip | 提示 -此处重要且**神奇**的事情是,`get_current_user` 检查每个*路径操作*时可以使用不同的 `scopes` 列表。 +这里重要且“神奇”的地方是,`get_current_user` 在检查每个*路径操作*时会得到不同的 `scopes` 列表。 -所有这些都依赖于在每个*路径操作*和指定*路径操作*的依赖树中的每个依赖项。 +这一切都取决于为该特定*路径操作*在其自身以及依赖树中的每个依赖里声明的 `scopes`。 /// -## `SecurityScopes` 的更多细节 +## 关于 `SecurityScopes` 的更多细节 { #more-details-about-securityscopes } -您可以任何位置或多个位置使用 `SecurityScopes`,不一定非得在**根**依赖项中使用。 +你可以在任意位置、多个位置使用 `SecurityScopes`,不一定非得在“根”依赖里。 -它总是在当前 `Security` 依赖项中和所有依赖因子对于**特定** *路径操作*和**特定**依赖树中安全作用域 +它总会包含当前 `Security` 依赖中以及所有依赖者在“该特定”*路径操作*和“该特定”依赖树里声明的安全作用域。 -因为 `SecurityScopes` 包含所有由依赖项声明的作用域,可以在核心依赖函数中用它验证所需作用域的令牌,然后再在不同的*路径操作*中声明不同作用域需求。 +因为 `SecurityScopes` 会包含依赖者声明的所有作用域,你可以在一个核心依赖函数里用它验证令牌是否具有所需作用域,然后在不同的*路径操作*里声明不同的作用域需求。 -它们会为每个*路径操作*进行单独检查。 +它们会针对每个*路径操作*分别检查。 -## 查看文档 +## 查看文档 { #check-it } -打开 API 文档,进行身份验证,并指定要授权的作用域。 +打开 API 文档,你可以进行身份验证,并指定要授权的作用域。 -没有选择任何作用域,也可以进行**身份验证**,但访问 `/uses/me` 或 `/users/me/items` 时,会显示没有足够的权限。但仍可以访问 `/status/`。 +如果你不选择任何作用域,你依然会“通过认证”,但当你访问 `/users/me/` 或 `/users/me/items/` 时,会收到一个错误,提示你没有足够的权限。你仍然可以访问 `/status/`。 -如果选择了作用域 `me`,但没有选择作用域 `items`,则可以访问 `/users/me/`,但不能访问 `/users/me/items`。 +如果你选择了作用域 `me`,但没有选择作用域 `items`,你可以访问 `/users/me/`,但不能访问 `/users/me/items/`。 -这就是通过用户提供的令牌使用第三方应用访问这些*路径操作*时会发生的情况,具体怎样取决于用户授予第三方应用的权限。 +当第三方应用使用用户提供的令牌访问这些*路径操作*时,也会发生同样的情况,取决于用户授予该应用了多少权限。 -## 关于第三方集成 +## 关于第三方集成 { #about-third-party-integrations } -本例使用 OAuth2 **密码**流。 +在这个示例中我们使用的是 OAuth2 的“password”流。 -这种方式适用于登录我们自己的应用,最好使用我们自己的前端。 +当我们登录自己的应用(很可能还有我们自己的前端)时,这是合适的。 -因为我们能控制自己的前端应用,可以信任它接收 `username` 与 `password`。 +因为我们可以信任它来接收 `username` 和 `password`,毕竟我们掌控它。 -但如果构建的是连接其它应用的 OAuth2 应用,比如具有与脸书、谷歌、GitHub 相同功能的第三方身份验证应用。那您就应该使用其它安全流。 +但如果你在构建一个 OAuth2 应用,让其它应用来连接(也就是说,你在构建等同于 Facebook、Google、GitHub 等的身份验证提供商),你应该使用其它的流。 -最常用的是隐式流。 +最常见的是隐式流(implicit flow)。 -最安全的是代码流,但实现起来更复杂,而且需要更多步骤。因为它更复杂,很多第三方身份验证应用最终建议使用隐式流。 +最安全的是代码流(authorization code flow),但实现更复杂,需要更多步骤。也因为更复杂,很多提供商最终会建议使用隐式流。 -/// note | 笔记 +/// note | 注意 -每个身份验证应用都会采用不同方式会命名流,以便融合入自己的品牌。 +每个身份验证提供商常常会用不同的方式给它们的流命名,以融入自己的品牌。 -但归根结底,它们使用的都是 OAuth2 标准。 +但归根结底,它们实现的都是同一个 OAuth2 标准。 /// -**FastAPI** 的 `fastapi.security.oauth2` 里包含了所有 OAuth2 身份验证流工具。 +**FastAPI** 在 `fastapi.security.oauth2` 中为所有这些 OAuth2 身份验证流提供了工具。 -## 装饰器 `dependencies` 中的 `Security` +## 装饰器 `dependencies` 中的 `Security` { #security-in-decorator-dependencies } -同样,您可以在装饰器的 `dependencies` 参数中定义 `Depends` 列表,(详见[路径操作装饰器依赖项](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank})),也可以把 `scopes` 与 `Security` 一起使用。 +就像你可以在装饰器的 `dependencies` 参数中定义 `Depends` 的 `list`(详见[路径操作装饰器依赖项](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}),你也可以在那儿配合 `Security` 使用 `scopes`。 diff --git a/docs/zh/docs/advanced/settings.md b/docs/zh/docs/advanced/settings.md index e33da136f..adf264491 100644 --- a/docs/zh/docs/advanced/settings.md +++ b/docs/zh/docs/advanced/settings.md @@ -1,190 +1,94 @@ -# 设置和环境变量 +# 设置和环境变量 { #settings-and-environment-variables } -在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等等。 +在许多情况下,你的应用可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务的凭据等。 -这些设置中的大多数是可变的(可以更改的),比如数据库的 URL。而且许多设置可能是敏感的,比如密钥。 +这些设置中的大多数是可变的(可能会改变),例如数据库 URL。并且很多可能是敏感的,比如密钥。 因此,通常会将它们提供为由应用程序读取的环境变量。 -## 环境变量 +/// tip | 提示 -/// tip - -如果您已经知道什么是"环境变量"以及如何使用它们,请随意跳到下面的下一节。 +要理解环境变量,你可以阅读[环境变量](../environment-variables.md){.internal-link target=_blank}。 /// -环境变量(也称为"env var")是一种存在于 Python 代码之外、存在于操作系统中的变量,可以被您的 Python 代码(或其他程序)读取。 +## 类型和验证 { #types-and-validation } -您可以在 shell 中创建和使用环境变量,而无需使用 Python: +这些环境变量只能处理文本字符串,因为它们在 Python 之外,并且必须与其他程序及系统的其余部分兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。 -//// tab | Linux、macOS、Windows Bash +这意味着,在 Python 中从环境变量读取的任何值都是 `str` 类型,任何到不同类型的转换或任何验证都必须在代码中完成。 + +## Pydantic 的 `Settings` { #pydantic-settings } + +幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的这些设置:Pydantic: Settings management。 + +### 安装 `pydantic-settings` { #install-pydantic-settings } + +首先,确保你创建并激活了[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装 `pydantic-settings` 包:
```console -// 您可以创建一个名为 MY_NAME 的环境变量 -$ export MY_NAME="Wade Wilson" - -// 然后您可以与其他程序一起使用它,例如 -$ echo "Hello $MY_NAME" - -Hello Wade Wilson +$ pip install pydantic-settings +---> 100% ```
-//// - -//// tab | Windows PowerShell +当你用以下方式安装 `all` 扩展时,它也会被一并安装:
```console -// 创建一个名为 MY_NAME 的环境变量 -$ $Env:MY_NAME = "Wade Wilson" - -// 与其他程序一起使用它,例如 -$ echo "Hello $Env:MY_NAME" - -Hello Wade Wilson +$ pip install "fastapi[all]" +---> 100% ```
-//// +### 创建 `Settings` 对象 { #create-the-settings-object } -### 在 Python 中读取环境变量 +从 Pydantic 导入 `BaseSettings` 并创建一个子类,这与创建 Pydantic 模型非常相似。 -您还可以在 Python 之外的地方(例如终端中或使用任何其他方法)创建环境变量,然后在 Python 中读取它们。 +与 Pydantic 模型一样,用类型注解声明类属性,也可以指定默认值。 -例如,您可以有一个名为 `main.py` 的文件,其中包含以下内容: +你可以使用与 Pydantic 模型相同的验证功能和工具,例如不同的数据类型,以及使用 `Field()` 进行附加验证。 -```Python hl_lines="3" -import os +{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} -name = os.getenv("MY_NAME", "World") -print(f"Hello {name} from Python") -``` +/// tip | 提示 -/// tip - -`os.getenv()` 的第二个参数是要返回的默认值。 - -如果没有提供默认值,默认为 `None`,此处我们提供了 `"World"` 作为要使用的默认值。 +如果你想要一个可以快速复制粘贴的示例,请不要使用这个示例,使用下面最后一个示例。 /// -然后,您可以调用该 Python 程序: +当你创建该 `Settings` 类的实例(此处是 `settings` 对象)时,Pydantic 会以不区分大小写的方式读取环境变量,因此,大写变量 `APP_NAME` 仍会用于属性 `app_name`。 + +接着它会转换并验证数据。因此,当你使用该 `settings` 对象时,你将获得你声明的类型的数据(例如 `items_per_user` 将是 `int`)。 + +### 使用 `settings` { #use-the-settings } + +然后你可以在应用中使用新的 `settings` 对象: + +{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *} + +### 运行服务器 { #run-the-server } + +接下来,运行服务器,并把配置作为环境变量传入,例如你可以设置 `ADMIN_EMAIL` 和 `APP_NAME`:
```console -// 这里我们还没有设置环境变量 -$ python main.py - -// 因为我们没有设置环境变量,所以我们得到默认值 - -Hello World from Python - -// 但是如果我们先创建一个环境变量 -$ export MY_NAME="Wade Wilson" - -// 然后再次调用程序 -$ python main.py - -// 现在它可以读取环境变量 - -Hello Wade Wilson from Python -``` - -
- -由于环境变量可以在代码之外设置,但可以由代码读取,并且不需要与其他文件一起存储(提交到 `git`),因此通常将它们用于配置或设置。 - - - -您还可以仅为特定程序调用创建一个环境变量,该环境变量仅对该程序可用,并且仅在其运行期间有效。 - -要做到这一点,在程序本身之前的同一行创建它: - -
- -```console -// 在此程序调用行中创建一个名为 MY_NAME 的环境变量 -$ MY_NAME="Wade Wilson" python main.py - -// 现在它可以读取环境变量 - -Hello Wade Wilson from Python - -// 之后环境变量不再存在 -$ python main.py - -Hello World from Python -``` - -
- -/// tip - -您可以在 Twelve-Factor App: Config 中阅读更多相关信息。 - -/// - -### 类型和验证 - -这些环境变量只能处理文本字符串,因为它们是外部于 Python 的,并且必须与其他程序和整个系统兼容(甚至与不同的操作系统,如 Linux、Windows、macOS)。 - -这意味着从环境变量中在 Python 中读取的任何值都将是 `str` 类型,任何类型的转换或验证都必须在代码中完成。 - -## Pydantic 的 `Settings` - -幸运的是,Pydantic 提供了一个很好的工具来处理来自环境变量的设置,即Pydantic: Settings management。 - -### 创建 `Settings` 对象 - -从 Pydantic 导入 `BaseSettings` 并创建一个子类,与 Pydantic 模型非常相似。 - -与 Pydantic 模型一样,您使用类型注释声明类属性,还可以指定默认值。 - -您可以使用与 Pydantic 模型相同的验证功能和工具,比如不同的数据类型和使用 `Field()` 进行附加验证。 - -{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} - -/// tip - -如果您需要一个快速的复制粘贴示例,请不要使用此示例,而应使用下面的最后一个示例。 - -/// - -然后,当您创建该 `Settings` 类的实例(在此示例中是 `settings` 对象)时,Pydantic 将以不区分大小写的方式读取环境变量,因此,大写的变量 `APP_NAME` 仍将为属性 `app_name` 读取。 - -然后,它将转换和验证数据。因此,当您使用该 `settings` 对象时,您将获得您声明的类型的数据(例如 `items_per_user` 将为 `int` 类型)。 - -### 使用 `settings` - -然后,您可以在应用程序中使用新的 `settings` 对象: - -{* ../../docs_src/settings/tutorial001.py hl[18:20] *} - -### 运行服务器 - -接下来,您将运行服务器,并将配置作为环境变量传递。例如,您可以设置一个 `ADMIN_EMAIL` 和 `APP_NAME`,如下所示: - -
- -```console -$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-/// tip +/// tip | 提示 -要为单个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前。 +要为单个命令设置多个环境变量,只需用空格分隔它们,并把它们都放在命令前面。 /// @@ -192,118 +96,118 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp"uvicorn main:app `app_name` 将为 `"ChimichangApp"`。 -而 `items_per_user` 将保持其默认值为 `50`。 +而 `items_per_user` 会保持默认值 `50`。 -## 在另一个模块中设置 +## 在另一个模块中放置设置 { #settings-in-another-module } -您可以将这些设置放在另一个模块文件中,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中所见的那样。 +你可以把这些设置放在另一个模块文件中,就像你在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 -例如,您可以创建一个名为 `config.py` 的文件,其中包含以下内容: +例如,可以有一个 `config.py` 文件: -{* ../../docs_src/settings/app01/config.py *} +{* ../../docs_src/settings/app01_py39/config.py *} -然后在一个名为 `main.py` 的文件中使用它: +然后在 `main.py` 文件中使用它: -{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} +{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *} -/// tip +/// tip | 提示 -您还需要一个名为 `__init__.py` 的文件,就像您在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 +你还需要一个 `__init__.py` 文件,就像你在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 /// -## 在依赖项中使用设置 +## 在依赖项中提供设置 { #settings-in-a-dependency } -在某些情况下,从依赖项中提供设置可能比在所有地方都使用全局对象 `settings` 更有用。 +在某些情况下,从依赖项中提供设置可能更有用,而不是在所有地方都使用一个全局的 `settings` 对象。 -这在测试期间尤其有用,因为很容易用自定义设置覆盖依赖项。 +这在测试期间尤其有用,因为可以很容易地用你自己的自定义设置覆盖依赖项。 -### 配置文件 +### 配置文件 { #the-config-file } -根据前面的示例,您的 `config.py` 文件可能如下所示: +延续上一个示例,你的 `config.py` 文件可能如下所示: -{* ../../docs_src/settings/app02/config.py hl[10] *} +{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} -请注意,现在我们不创建默认实例 `settings = Settings()`。 +注意,现在我们不再创建默认实例 `settings = Settings()`。 -### 主应用程序文件 +### 主应用文件 { #the-main-app-file } 现在我们创建一个依赖项,返回一个新的 `config.Settings()`。 {* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} -/// tip +/// tip | 提示 我们稍后会讨论 `@lru_cache`。 -目前,您可以将 `get_settings()` 视为普通函数。 +目前你可以把 `get_settings()` 当作普通函数。 /// -然后,我们可以将其作为依赖项从“路径操作函数”中引入,并在需要时使用它。 +然后我们可以在“路径操作函数”中将其作为依赖项引入,并在需要的任何地方使用它。 {* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} -### 设置和测试 +### 设置与测试 { #settings-and-testing } -然后,在测试期间,通过创建 `get_settings` 的依赖项覆盖,很容易提供一个不同的设置对象: +接着,在测试期间,通过为 `get_settings` 创建依赖项覆盖,就可以很容易地提供一个不同的设置对象: -{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} +{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} 在依赖项覆盖中,我们在创建新的 `Settings` 对象时为 `admin_email` 设置了一个新值,然后返回该新对象。 -然后,我们可以测试它是否被使用。 +然后我们可以测试它是否被使用。 -## 从 `.env` 文件中读取设置 +## 读取 `.env` 文件 { #reading-a-env-file } -如果您有许多可能经常更改的设置,可能在不同的环境中,将它们放在一个文件中,然后从该文件中读取它们,就像它们是环境变量一样,可能非常有用。 +如果你有许多设置可能经常变化,或在不同环境中不同,那么把它们放进一个文件中,然后像环境变量一样从中读取,可能非常有用。 -这种做法相当常见,有一个名称,这些环境变量通常放在一个名为 `.env` 的文件中,该文件被称为“dotenv”。 +这种做法非常常见:这些环境变量通常放在名为 `.env` 的文件中,该文件被称为 “dotenv”。 -/// tip +/// tip | 提示 -以点 (`.`) 开头的文件是 Unix-like 系统(如 Linux 和 macOS)中的隐藏文件。 +以点(`.`)开头的文件在类 Unix 系统(如 Linux 和 macOS)中是隐藏文件。 -但是,dotenv 文件实际上不一定要具有确切的文件名。 +但 dotenv 文件并不一定必须是这个确切的文件名。 /// -Pydantic 支持使用外部库从这些类型的文件中读取。您可以在Pydantic 设置: Dotenv (.env) 支持中阅读更多相关信息。 +Pydantic 支持使用一个外部库来从这类文件中读取。你可以在 Pydantic Settings: Dotenv (.env) support 中阅读更多信息。 -/// tip +/// tip | 提示 -要使其工作,您需要执行 `pip install python-dotenv`。 +要使其工作,你需要执行 `pip install python-dotenv`。 /// -### `.env` 文件 +### `.env` 文件 { #the-env-file } -您可以使用以下内容创建一个名为 `.env` 的文件: +你可以有一个 `.env` 文件,内容如下: ```bash ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" ``` -### 从 `.env` 文件中读取设置 +### 从 `.env` 中读取设置 { #read-settings-from-env } -然后,您可以使用以下方式更新您的 `config.py`: +然后更新 `config.py`: -{* ../../docs_src/settings/app03/config.py hl[9:10] *} +{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} -在这里,我们在 Pydantic 的 `Settings` 类中创建了一个名为 `Config` 的类,并将 `env_file` 设置为我们想要使用的 dotenv 文件的文件名。 +/// tip | 提示 -/// tip - -`Config` 类仅用于 Pydantic 配置。您可以在Pydantic Model Config中阅读更多相关信息。 +`model_config` 属性仅用于 Pydantic 配置。你可以在 Pydantic: Concepts: Configuration 中阅读更多信息。 /// -### 使用 `lru_cache` 仅创建一次 `Settings` +这里我们在你的 Pydantic `Settings` 类中定义配置项 `env_file`,并将其设置为我们想要使用的 dotenv 文件名。 -从磁盘中读取文件通常是一项耗时的(慢)操作,因此您可能希望仅在首次读取后并重复使用相同的设置对象,而不是为每个请求都读取它。 +### 使用 `lru_cache` 仅创建一次 `Settings` { #creating-the-settings-only-once-with-lru-cache } -但是,每次执行以下操作: +从磁盘读取文件通常是一个代价较高(缓慢)的操作,所以你可能希望只在第一次读取,然后复用同一个设置对象,而不是为每个请求都重新读取。 + +但是,每次我们执行: ```Python Settings() @@ -311,35 +215,36 @@ Settings() 都会创建一个新的 `Settings` 对象,并且在创建时会再次读取 `.env` 文件。 -如果依赖项函数只是这样的: +如果依赖项函数是这样的: ```Python def get_settings(): return Settings() ``` -我们将为每个请求创建该对象,并且将在每个请求中读取 `.env` 文件。 ⚠️ +我们就会为每个请求创建该对象,并为每个请求读取 `.env` 文件。 ⚠️ -但是,由于我们在顶部使用了 `@lru_cache` 装饰器,因此只有在第一次调用它时,才会创建 `Settings` 对象一次。 ✔️ +但由于我们在顶部使用了 `@lru_cache` 装饰器,`Settings` 对象只会在第一次调用时创建一次。 ✔️ {* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} -然后,在下一次请求的依赖项中对 `get_settings()` 进行任何后续调用时,它不会执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是返回在第一次调用时返回的相同对象,一次又一次。 +接着,对于后续请求中依赖项里对 `get_settings()` 的任何调用,它不会再次执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是会一遍又一遍地返回第一次调用时返回的那个相同对象。 -#### `lru_cache` 技术细节 +#### `lru_cache` 技术细节 { #lru-cache-technical-details } -`@lru_cache` 修改了它所装饰的函数,以返回第一次返回的相同值,而不是再次计算它,每次都执行函数的代码。 +`@lru_cache` 会修改它所装饰的函数,使其返回第一次返回的相同值,而不是每次都重新计算并执行函数代码。 -因此,下面的函数将对每个参数组合执行一次。然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。 +因此,下面的函数会针对每个参数组合执行一次。然后,当以完全相同的参数组合调用该函数时,将重复使用该参数组合先前返回的值。 + +例如,如果你有一个函数: -例如,如果您有一个函数: ```Python @lru_cache def say_hi(name: str, salutation: str = "Ms."): return f"Hello {salutation} {name}" ``` -您的程序可以像这样执行: +你的程序可能会像这样执行: ```mermaid sequenceDiagram @@ -382,16 +287,16 @@ participant execute as Execute function end ``` -对于我们的依赖项 `get_settings()`,该函数甚至不接受任何参数,因此它始终返回相同的值。 +在我们的依赖项 `get_settings()` 的情况下,该函数甚至不接受任何参数,因此它始终返回相同的值。 -这样,它的行为几乎就像是一个全局变量。但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。 +这样,它的行为几乎就像是一个全局变量。但由于它使用了依赖项函数,我们可以在测试时很容易地覆盖它。 -`@lru_cache` 是 `functools` 的一部分,它是 Python 标准库的一部分,您可以在Python 文档中了解有关 `@lru_cache` 的更多信息。 +`@lru_cache` 是 `functools` 的一部分,它属于 Python 标准库。你可以在 Python 文档中关于 `@lru_cache` 的章节阅读更多信息。 -## 小结 +## 小结 { #recap } -您可以使用 Pydantic 设置处理应用程序的设置或配置,利用 Pydantic 模型的所有功能。 +你可以使用 Pydantic Settings 来处理应用的设置或配置,享受 Pydantic 模型的全部能力。 -* 通过使用依赖项,您可以简化测试。 -* 您可以使用 `.env` 文件。 -* 使用 `@lru_cache` 可以避免为每个请求重复读取 dotenv 文件,同时允许您在测试时进行覆盖。 +- 通过使用依赖项,你可以简化测试。 +- 你可以与它一起使用 `.env` 文件。 +- 使用 `@lru_cache` 可以避免为每个请求反复读取 dotenv 文件,同时允许你在测试时进行覆盖。 diff --git a/docs/zh/docs/advanced/sub-applications.md b/docs/zh/docs/advanced/sub-applications.md index c42be2849..fe1fcd121 100644 --- a/docs/zh/docs/advanced/sub-applications.md +++ b/docs/zh/docs/advanced/sub-applications.md @@ -1,54 +1,54 @@ -# 子应用 - 挂载 +# 子应用 - 挂载 { #sub-applications-mounts } 如果需要两个独立的 FastAPI 应用,拥有各自独立的 OpenAPI 与文档,则需设置一个主应用,并**挂载**一个(或多个)子应用。 -## 挂载 **FastAPI** 应用 +## 挂载 **FastAPI** 应用 { #mounting-a-fastapi-application } **挂载**是指在特定路径中添加完全**独立**的应用,然后在该路径下使用*路径操作*声明的子应用处理所有事务。 -### 顶层应用 +### 顶层应用 { #top-level-application } 首先,创建主(顶层)**FastAPI** 应用及其*路径操作*: -{* ../../docs_src/sub_applications/tutorial001.py hl[3,6:8] *} +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *} -### 子应用 +### 子应用 { #sub-application } 接下来,创建子应用及其*路径操作*。 子应用只是另一个标准 FastAPI 应用,但这个应用是被**挂载**的应用: -{* ../../docs_src/sub_applications/tutorial001.py hl[11,14:16] *} +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *} -### 挂载子应用 +### 挂载子应用 { #mount-the-sub-application } 在顶层应用 `app` 中,挂载子应用 `subapi`。 本例的子应用挂载在 `/subapi` 路径下: -{* ../../docs_src/sub_applications/tutorial001.py hl[11,19] *} +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *} -### 查看文档 +### 查看自动 API 文档 { #check-the-automatic-api-docs } -如果主文件是 `main.py`,则用以下 `uvicorn` 命令运行主应用: +现在,使用你的文件运行 `fastapi` 命令:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-查看文档 http://127.0.0.1:8000/docs。 +然后在 http://127.0.0.1:8000/docs 打开文档。 下图显示的是主应用 API 文档,只包括其自有的*路径操作*。 -然后查看子应用文档 http://127.0.0.1:8000/subapi/docs。 +然后查看子应用文档 http://127.0.0.1:8000/subapi/docs。 下图显示的是子应用的 API 文档,也是只包括其自有的*路径操作*,所有这些路径操作都在 `/subapi` 子路径前缀下。 @@ -56,7 +56,7 @@ $ uvicorn main:app --reload 两个用户界面都可以正常运行,因为浏览器能够与每个指定的应用或子应用会话。 -### 技术细节:`root_path` +### 技术细节:`root_path` { #technical-details-root-path } 以上述方式挂载子应用时,FastAPI 使用 ASGI 规范中的 `root_path` 机制处理挂载子应用路径之间的通信。 diff --git a/docs/zh/docs/advanced/templates.md b/docs/zh/docs/advanced/templates.md index e627eed98..f2e5c21cf 100644 --- a/docs/zh/docs/advanced/templates.md +++ b/docs/zh/docs/advanced/templates.md @@ -1,4 +1,4 @@ -# 模板 +# 模板 { #templates } **FastAPI** 支持多种模板引擎。 @@ -6,9 +6,9 @@ Flask 等工具使用的 Jinja2 是最用的模板引擎。 在 Starlette 的支持下,**FastAPI** 应用可以直接使用工具轻易地配置 Jinja2。 -## 安装依赖项 +## 安装依赖项 { #install-dependencies } -安装 `jinja2`: +确保你创建一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},激活它,并安装 `jinja2`:
@@ -20,23 +20,23 @@ $ pip install jinja2
-## 使用 `Jinja2Templates` +## 使用 `Jinja2Templates` { #using-jinja2templates } * 导入 `Jinja2Templates` * 创建可复用的 `templates` 对象 * 在返回模板的*路径操作*中声明 `Request` 参数 -* 使用 `templates` 渲染并返回 `TemplateResponse`, 传递模板的名称、request对象以及一个包含多个键值对(用于Jinja2模板)的"context"字典, +* 使用 `templates` 渲染并返回 `TemplateResponse`,传递模板的名称、request 对象以及一个包含多个键值对(用于 Jinja2 模板)的 "context" 字典。 -{* ../../docs_src/templates/tutorial001.py hl[4,11,15:16] *} +{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *} -/// note | 笔记 +/// note -在FastAPI 0.108.0,Starlette 0.29.0之前,`name`是第一个参数。 -并且,在此之前,`request`对象是作为context的一部分以键值对的形式传递的。 +在 FastAPI 0.108.0,Starlette 0.29.0 之前,`name` 是第一个参数。 +并且,在此之前,`request` 对象是作为 context 的一部分以键值对的形式传递的。 /// -/// tip | 提示 +/// tip 通过声明 `response_class=HTMLResponse`,API 文档就能识别响应的对象是 HTML。 @@ -46,11 +46,11 @@ $ pip install jinja2 您还可以使用 `from starlette.templating import Jinja2Templates`。 -**FastAPI** 的 `fastapi.templating` 只是为开发者提供的快捷方式。实际上,绝大多数可用响应都直接继承自 Starlette。 `Request` 与 `StaticFiles` 也一样。 +**FastAPI** 的 `fastapi.templating` 只是为开发者提供的快捷方式。实际上,绝大多数可用响应都直接继承自 Starlette。`Request` 与 `StaticFiles` 也一样。 /// -## 编写模板 +## 编写模板 { #writing-templates } 编写模板 `templates/item.html`,代码如下: @@ -58,7 +58,7 @@ $ pip install jinja2 {!../../docs_src/templates/templates/item.html!} ``` -### 模板上下文 +### 模板上下文值 { #template-context-values } 在包含如下语句的html中: @@ -70,21 +70,21 @@ Item ID: {{ id }} {% endraw %} -...这将显示你从"context"字典传递的 `id`: +...这将显示你从 "context" 字典传递的 `id`: ```Python {"id": id} ``` -例如。当ID为 `42`时, 会渲染成: +例如。当 ID 为 `42` 时, 会渲染成: ```html Item ID: 42 ``` -### 模板 `url_for` 参数 +### 模板 `url_for` 参数 { #template-url-for-arguments } -你还可以在模板内使用 `url_for()`,其参数与*路径操作函数*的参数相同. +你还可以在模板内使用 `url_for()`,其参数与*路径操作函数*的参数相同。 所以,该部分: @@ -96,30 +96,30 @@ Item ID: 42 {% endraw %} -...将生成一个与处理*路径操作函数* `read_item(id=id)`的URL相同的链接 +...将生成一个与处理*路径操作函数* `read_item(id=id)`的 URL 相同的链接 -例如。当ID为 `42`时, 会渲染成: +例如。当 ID 为 `42` 时, 会渲染成: ```html ``` -## 模板与静态文件 +## 模板与静态文件 { #templates-and-static-files } -你还可以在模板内部将 `url_for()`用于静态文件,例如你挂载的 `name="static"`的 `StaticFiles`。 +你还可以在模板内部将 `url_for()` 用于静态文件,例如你挂载的 `name="static"` 的 `StaticFiles`。 ```jinja hl_lines="4" {!../../docs_src/templates/templates/item.html!} ``` -本例中,它将链接到 `static/styles.css`中的CSS文件: +本例中,它将链接到 `static/styles.css` 中的 CSS 文件: ```CSS hl_lines="4" {!../../docs_src/templates/static/styles.css!} ``` -因为使用了 `StaticFiles`, **FastAPI** 应用会自动提供位于 URL `/static/styles.css`的 CSS 文件。 +因为使用了 `StaticFiles`,**FastAPI** 应用会自动提供位于 URL `/static/styles.css` 的 CSS 文件。 -## 更多说明 +## 更多说明 { #more-details } 包括测试模板等更多详情,请参阅 Starlette 官方文档 - 模板。 diff --git a/docs/zh/docs/advanced/testing-dependencies.md b/docs/zh/docs/advanced/testing-dependencies.md index 8d53a6d49..db0b39483 100644 --- a/docs/zh/docs/advanced/testing-dependencies.md +++ b/docs/zh/docs/advanced/testing-dependencies.md @@ -1,6 +1,6 @@ -# 测试依赖项 +# 使用覆盖测试依赖项 { #testing-dependencies-with-overrides } -## 测试时覆盖依赖项 +## 测试时覆盖依赖项 { #overriding-dependencies-during-testing } 有些场景下,您可能需要在测试时覆盖依赖项。 @@ -8,7 +8,7 @@ 反之,要在测试期间(或只是为某些特定测试)提供只用于测试的依赖项,并使用此依赖项的值替换原有依赖项的值。 -### 用例:外部服务 +### 用例:外部服务 { #use-cases-external-service } 常见实例是调用外部第三方身份验证应用。 @@ -20,7 +20,7 @@ 此时,最好覆盖调用外部验证应用的依赖项,使用返回模拟测试用户的自定义依赖项就可以了。 -### 使用 `app.dependency_overrides` 属性 +### 使用 `app.dependency_overrides` 属性 { #use-the-app-dependency-overrides-attribute } 对于这些用例,**FastAPI** 应用支持 `app.dependency_overrides` 属性,该属性就是**字典**。 @@ -46,6 +46,7 @@ FastAPI 可以覆盖这些位置的依赖项。 app.dependency_overrides = {} ``` + /// tip | 提示 如果只在某些测试时覆盖依赖项,您可以在测试开始时(在测试函数内)设置覆盖依赖项,并在结束时(在测试函数结尾)重置覆盖依赖项。 diff --git a/docs/zh/docs/advanced/testing-events.md b/docs/zh/docs/advanced/testing-events.md index 71b3739c3..221984e90 100644 --- a/docs/zh/docs/advanced/testing-events.md +++ b/docs/zh/docs/advanced/testing-events.md @@ -1,5 +1,11 @@ -# 测试事件:启动 - 关闭 +# 测试事件:lifespan 和 startup - shutdown { #testing-events-lifespan-and-startup-shutdown } -使用 `TestClient` 和 `with` 语句,在测试中运行事件处理器(`startup` 与 `shutdown`)。 +当你需要在测试中运行 `lifespan` 时,可以将 `TestClient` 与 `with` 语句一起使用: -{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} +{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *} + +你可以在[官方 Starlette 文档站点的“在测试中运行 lifespan”](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)阅读更多细节。 + +对于已弃用的 `startup` 和 `shutdown` 事件,可以按如下方式使用 `TestClient`: + +{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *} diff --git a/docs/zh/docs/advanced/testing-websockets.md b/docs/zh/docs/advanced/testing-websockets.md index b84647a3e..64e1d3005 100644 --- a/docs/zh/docs/advanced/testing-websockets.md +++ b/docs/zh/docs/advanced/testing-websockets.md @@ -1,13 +1,13 @@ -# 测试 WebSockets +# 测试 WebSockets { #testing-websockets } -测试 WebSockets 也使用 `TestClient`。 +你可以使用同一个 `TestClient` 来测试 WebSockets。 -为此,要在 `with` 语句中使用 `TestClient` 连接 WebSocket。 +为此,在 `with` 语句中使用 `TestClient` 连接到 WebSocket: -{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} +{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *} -/// note | 笔记 +/// note | 注意 -更多细节详见 Starlette 官档 - 测试 WebSockets。 +更多细节请查看 Starlette 的文档:测试 WebSockets。 /// diff --git a/docs/zh/docs/advanced/using-request-directly.md b/docs/zh/docs/advanced/using-request-directly.md index a9658c034..64ba8da1b 100644 --- a/docs/zh/docs/advanced/using-request-directly.md +++ b/docs/zh/docs/advanced/using-request-directly.md @@ -1,4 +1,4 @@ -# 直接使用请求 +# 直接使用 Request { #using-the-request-directly } 至此,我们已经使用多种类型声明了请求的各种组件。 @@ -13,23 +13,23 @@ 但有时,我们也需要直接访问 `Request` 对象。 -## `Request` 对象的细节 +## `Request` 对象的细节 { #details-about-the-request-object } 实际上,**FastAPI** 的底层是 **Starlette**,**FastAPI** 只不过是在 **Starlette** 顶层提供了一些工具,所以能直接使用 Starlette 的 `Request` 对象。 -但直接从 `Request` 对象提取数据时(例如,读取请求体),**FastAPI** 不会验证、转换和存档数据(为 API 文档使用 OpenAPI)。 +但直接从 `Request` 对象提取数据时(例如,读取请求体),这些数据不会被 **FastAPI** 验证、转换或文档化(使用 OpenAPI,为自动的 API 用户界面)。 不过,仍可以验证、转换与注释(使用 Pydantic 模型的请求体等)其它正常声明的参数。 但在某些特定情况下,还是需要提取 `Request` 对象。 -## 直接使用 `Request` 对象 +## 直接使用 `Request` 对象 { #use-the-request-object-directly } 假设要在*路径操作函数*中获取客户端 IP 地址和主机。 此时,需要直接访问请求。 -{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} +{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *} 把*路径操作函数*的参数类型声明为 `Request`,**FastAPI** 就能把 `Request` 传递到参数里。 @@ -43,7 +43,7 @@ /// -## `Request` 文档 +## `Request` 文档 { #request-documentation } 更多细节详见 Starlette 官档 - `Request` 对象。 @@ -51,6 +51,6 @@ 您也可以使用 `from starlette.requests import Request`。 -**FastAPI** 的 `from fastapi import Request` 只是为开发者提供的快捷方式,但其实它直接继承自 Starlette。 +**FastAPI** 直接提供它只是为了方便开发者,但它直接来自 Starlette。 /// diff --git a/docs/zh/docs/advanced/websockets.md b/docs/zh/docs/advanced/websockets.md index 005ed9242..4486a2e69 100644 --- a/docs/zh/docs/advanced/websockets.md +++ b/docs/zh/docs/advanced/websockets.md @@ -1,10 +1,12 @@ -# WebSockets +# WebSockets { #websockets } -您可以在 **FastAPI** 中使用 [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)。 +您可以在 **FastAPI** 中使用 WebSockets。 -## 安装 `WebSockets` +## 安装 `websockets` { #install-websockets } -首先,您需要安装 `WebSockets`: +请确保您创建一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank}、激活它,并安装 `websockets`(一个让使用“WebSocket”协议更容易的 Python 库): + +
```console $ pip install websockets @@ -12,11 +14,13 @@ $ pip install websockets ---> 100% ``` -## WebSockets 客户端 +
-### 在生产环境中 +## WebSockets 客户端 { #websockets-client } -在您的生产系统中,您可能使用现代框架(如React、Vue.js或Angular)创建了一个前端。 +### 在生产环境中 { #in-production } + +在您的生产系统中,您可能使用现代框架(如 React、Vue.js 或 Angular)创建了一个前端。 要使用 WebSockets 与后端进行通信,您可能会使用前端的工具。 @@ -26,7 +30,7 @@ $ pip install websockets --- -但是,在本示例中,我们将使用一个非常简单的HTML文档,其中包含一些JavaScript,全部放在一个长字符串中。 +但是,在本示例中,我们将使用一个非常简单的 HTML 文档,其中包含一些 JavaScript,全部放在一个长字符串中。 当然,这并不是最优的做法,您不应该在生产环境中使用它。 @@ -34,13 +38,13 @@ $ pip install websockets 但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式: -{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *} -## 创建 `websocket` +## 创建 `websocket` { #create-a-websocket } 在您的 **FastAPI** 应用程序中,创建一个 `websocket`: -{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} +{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *} /// note | 技术细节 @@ -50,24 +54,28 @@ $ pip install websockets /// -## 等待消息并发送消息 +## 等待消息并发送消息 { #await-for-messages-and-send-messages } 在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。 -{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} +{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *} 您可以接收和发送二进制、文本和 JSON 数据。 -## 尝试一下 +## 尝试一下 { #try-it } 如果您的文件名为 `main.py`,请使用以下命令运行应用程序: +
+ ```console -$ uvicorn main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` +
+ 在浏览器中打开 http://127.0.0.1:8000。 您将看到一个简单的页面,如下所示: @@ -86,11 +94,9 @@ $ uvicorn main:app --reload -所有这些消息都将使用同一个 WebSocket 连 +所有这些消息都将使用同一个 WebSocket 连接。 -接。 - -## 使用 `Depends` 和其他依赖项 +## 使用 `Depends` 和其他依赖项 { #using-depends-and-others } 在 WebSocket 端点中,您可以从 `fastapi` 导入并使用以下内容: @@ -101,7 +107,7 @@ $ uvicorn main:app --reload * `Path` * `Query` -它们的工作方式与其他 FastAPI 端点/ *路径操作* 相同: +它们的工作方式与其他 FastAPI 端点/*路径操作* 相同: {* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} @@ -113,16 +119,20 @@ $ uvicorn main:app --reload /// -### 尝试带有依赖项的 WebSockets +### 尝试带有依赖项的 WebSockets { #try-the-websockets-with-dependencies } 如果您的文件名为 `main.py`,请使用以下命令运行应用程序: +
+ ```console -$ uvicorn main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` +
+ 在浏览器中打开 http://127.0.0.1:8000。 在页面中,您可以设置: @@ -140,7 +150,7 @@ $ uvicorn main:app --reload -## 处理断开连接和多个客户端 +## 处理断开连接和多个客户端 { #handling-disconnections-and-multiple-clients } 当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。 @@ -164,13 +174,13 @@ Client #1596980209979 left the chat 但请记住,由于所有内容都在内存中以单个列表的形式处理,因此它只能在进程运行时工作,并且只能使用单个进程。 -如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 [encode/broadcaster](https://github.com/encode/broadcaster)。 +如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 encode/broadcaster。 /// -## 更多信息 +## 更多信息 { #more-info } 要了解更多选项,请查看 Starlette 的文档: -* [WebSocket 类](https://www.starlette.dev/websockets/) -* [基于类的 WebSocket 处理](https://www.starlette.dev/endpoints/#websocketendpoint)。 +* `WebSocket` 类。 +* 基于类的 WebSocket 处理。 diff --git a/docs/zh/docs/advanced/wsgi.md b/docs/zh/docs/advanced/wsgi.md index 363025a34..73fcb3219 100644 --- a/docs/zh/docs/advanced/wsgi.md +++ b/docs/zh/docs/advanced/wsgi.md @@ -1,32 +1,48 @@ -# 包含 WSGI - Flask,Django,其它 +# 包含 WSGI - Flask,Django,其它 { #including-wsgi-flask-django-others } -您可以挂载多个 WSGI 应用,正如您在 [Sub Applications - Mounts](sub-applications.md){.internal-link target=_blank}, [Behind a Proxy](behind-a-proxy.md){.internal-link target=_blank} 中所看到的那样。 +您可以挂载 WSGI 应用,正如您在 [子应用 - 挂载](sub-applications.md){.internal-link target=_blank}、[在代理之后](behind-a-proxy.md){.internal-link target=_blank} 中所看到的那样。 为此, 您可以使用 `WSGIMiddleware` 来包装你的 WSGI 应用,如:Flask,Django,等等。 -## 使用 `WSGIMiddleware` +## 使用 `WSGIMiddleware` { #using-wsgimiddleware } -您需要导入 `WSGIMiddleware`。 +/// info | 信息 + +需要安装 `a2wsgi`,例如使用 `pip install a2wsgi`。 + +/// + +您需要从 `a2wsgi` 导入 `WSGIMiddleware`。 然后使用该中间件包装 WSGI 应用(例如 Flask)。 之后将其挂载到某一个路径下。 -{* ../../docs_src/wsgi/tutorial001.py hl[2:3,22] *} +{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} -## 检查 +/// note | 注意 + +之前推荐使用 `fastapi.middleware.wsgi` 中的 `WSGIMiddleware`,但它现在已被弃用。 + +建议改用 `a2wsgi` 包,使用方式保持不变。 + +只要确保已安装 `a2wsgi` 包,并且从 `a2wsgi` 正确导入 `WSGIMiddleware` 即可。 + +/// + +## 检查 { #check-it } 现在,所有定义在 `/v1/` 路径下的请求将会被 Flask 应用处理。 其余的请求则会被 **FastAPI** 处理。 -如果您使用 Uvicorn 运行应用实例并且访问 http://localhost:8000/v1/,您将会看到由 Flask 返回的响应: +如果你运行它并访问 http://localhost:8000/v1/,你将会看到由 Flask 返回的响应: ```txt Hello, World from Flask! ``` -并且如果您访问 http://localhost:8000/v2,您将会看到由 FastAPI 返回的响应: +如果你访问 http://localhost:8000/v2,你将会看到由 FastAPI 返回的响应: ```JSON { diff --git a/docs/zh/docs/async.md b/docs/zh/docs/async.md index 4028ed51a..c94c90787 100644 --- a/docs/zh/docs/async.md +++ b/docs/zh/docs/async.md @@ -1,10 +1,10 @@ -# 并发 async / await +# 并发 async / await { #concurrency-and-async-await } 有关路径操作函数的 `async def` 语法以及异步代码、并发和并行的一些背景知识。 -## 赶时间吗? +## 赶时间吗? { #in-a-hurry } -TL;DR: +TL;DR: 如果你正在使用第三方库,它们会告诉你使用 `await` 关键字来调用它们,就像这样: @@ -21,7 +21,7 @@ async def read_results(): return results ``` -/// note +/// note | 注意 你只能在被 `async def` 创建的函数内使用 `await` @@ -40,7 +40,7 @@ def results(): --- -如果你的应用程序不需要与其他任何东西通信而等待其响应,请使用 `async def`。 +如果你的应用程序不需要与其他任何东西通信而等待其响应,请使用 `async def`,即使函数内部不需要使用 `await`。 --- @@ -54,7 +54,7 @@ def results(): 但是,通过遵循上述步骤,它将能够进行一些性能优化。 -## 技术细节 +## 技术细节 { #technical-details } Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 `await` 语法的东西来写**”异步代码“**。 @@ -64,7 +64,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 * **`async` 和 `await`** * **协程** -## 异步代码 +## 异步代码 { #asynchronous-code } 异步代码仅仅意味着编程语言 💬 有办法告诉计算机/程序 🤖 在代码中的某个点,它 🤖 将不得不等待在某些地方完成一些事情。让我们假设一些事情被称为 "慢文件"📝. @@ -74,7 +74,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 接下来,它 🤖 完成第一个任务(比如是我们的"慢文件"📝) 并继续与之相关的一切。 -这个"等待其他事情"通常指的是一些相对较慢(与处理器和 RAM 存储器的速度相比)的 I/O 操作,比如说: +这个"等待其他事情"通常指的是一些相对较慢(与处理器和 RAM 存储器的速度相比)的 I/O 操作,比如说: * 通过网络发送来自客户端的数据 * 客户端接收来自网络中的数据 @@ -85,7 +85,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 * 一个数据库查询,直到返回结果 * 等等. -这个执行的时间大多是在等待 I/O 操作,因此它们被叫做 "I/O 密集型" 操作。 +这个执行的时间大多是在等待 I/O 操作,因此它们被叫做 "I/O 密集型" 操作。 它被称为"异步"的原因是因为计算机/程序不必与慢任务"同步",去等待任务完成的确切时刻,而在此期间不做任何事情直到能够获取任务结果才继续工作。 @@ -93,7 +93,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 对于"同步"(与"异步"相反),他们通常也使用"顺序"一词,因为计算机程序在切换到另一个任务之前是按顺序执行所有步骤,即使这些步骤涉及到等待。 -### 并发与汉堡 +### 并发与汉堡 { #concurrency-and-burgers } 上述异步代码的思想有时也被称为“并发”,它不同于“并行”。 @@ -103,7 +103,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 要了解差异,请想象以下关于汉堡的故事: -### 并发汉堡 +### 并发汉堡 { #concurrent-burgers } 你和你的恋人一起去快餐店,你排队在后面,收银员从你前面的人接单。😍 @@ -139,7 +139,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 -/// info +/// info | 信息 漂亮的插画来自 Ketrina Thompson. 🎨 @@ -163,7 +163,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 然后你去柜台🔀, 到现在初始任务已经完成⏯, 拿起汉堡,说声谢谢,然后把它们送到桌上。这就完成了与计数器交互的步骤/任务⏹. 这反过来又产生了一项新任务,即"吃汉堡"🔀 ⏯, 上一个"拿汉堡"的任务已经结束了⏹. -### 并行汉堡 +### 并行汉堡 { #parallel-burgers } 现在让我们假设不是"并发汉堡",而是"并行汉堡"。 @@ -205,7 +205,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 没有太多的交谈或调情,因为大部分时间 🕙 都在柜台前等待😞。 -/// info +/// info | 信息 漂亮的插画来自 Ketrina Thompson. 🎨 @@ -233,7 +233,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 你可不会想带你的恋人 😍 和你一起去银行办事🏦. -### 汉堡结论 +### 汉堡结论 { #burger-conclusion } 在"你与恋人一起吃汉堡"的这个场景中,因为有很多人在等待🕙, 使用并发系统更有意义⏸🔀⏯. @@ -253,7 +253,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 你可以同时拥有并行性和异步性,你可以获得比大多数经过测试的 NodeJS 框架更高的性能,并且与 Go 不相上下, Go 是一种更接近于 C 的编译语言(全部归功于 Starlette)。 -### 并发比并行好吗? +### 并发比并行好吗? { #is-concurrency-better-than-parallelism } 不!这不是故事的本意。 @@ -277,7 +277,7 @@ Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 在这个场景中,每个清洁工(包括你)都将是一个处理器,完成这个工作的一部分。 -由于大多数执行时间是由实际工作(而不是等待)占用的,并且计算机中的工作是由 CPU 完成的,所以他们称这些问题为"CPU 密集型"。 +由于大多数执行时间是由实际工作(而不是等待)占用的,并且计算机中的工作是由 CPU 完成的,所以他们称这些问题为"CPU 密集型"。 --- @@ -290,7 +290,7 @@ CPU 密集型操作的常见示例是需要复杂的数学处理。 * **机器学习**: 它通常需要大量的"矩阵"和"向量"乘法。想象一个包含数字的巨大电子表格,并同时将所有数字相乘; * **深度学习**: 这是机器学习的一个子领域,同样适用。只是没有一个数字的电子表格可以相乘,而是一个庞大的数字集合,在很多情况下,你需要使用一个特殊的处理器来构建和使用这些模型。 -### 并发 + 并行: Web + 机器学习 +### 并发 + 并行: Web + 机器学习 { #concurrency-parallelism-web-machine-learning } 使用 **FastAPI**,你可以利用 Web 开发中常见的并发机制的优势(NodeJS 的主要吸引力)。 @@ -300,7 +300,7 @@ CPU 密集型操作的常见示例是需要复杂的数学处理。 了解如何在生产环境中实现这种并行性,可查看此文 [Deployment](deployment/index.md){.internal-link target=_blank}。 -## `async` 和 `await` +## `async` 和 `await` { #async-and-await } 现代版本的 Python 有一种非常直观的方式来定义异步代码。这使它看起来就像正常的"顺序"代码,并在适当的时候"等待"。 @@ -316,16 +316,16 @@ burgers = await get_burgers(2) ```Python hl_lines="1" async def get_burgers(number: int): - # Do some asynchronous stuff to create the burgers + # 执行一些异步操作来制作汉堡 return burgers ``` ...而不是 `def`: ```Python hl_lines="2" -# This is not asynchronous +# 这不是异步的 def get_sequential_burgers(number: int): - # Do some sequential stuff to create the burgers + # 执行一些顺序操作来制作汉堡 return burgers ``` @@ -334,7 +334,7 @@ def get_sequential_burgers(number: int): 当你想调用一个 `async def` 函数时,你必须"等待"它。因此,这不会起作用: ```Python -# This won't work, because get_burgers was defined with: async def +# 这样不行,因为 get_burgers 是用 async def 定义的 burgers = get_burgers(2) ``` @@ -349,7 +349,7 @@ async def read_burgers(): return burgers ``` -### 更多技术细节 +### 更多技术细节 { #more-technical-details } 你可能已经注意到,`await` 只能在 `async def` 定义的函数内部使用。 @@ -361,7 +361,7 @@ async def read_burgers(): 但如果你想在没有 FastAPI 的情况下使用 `async` / `await`,则可以这样做。 -### 编写自己的异步代码 +### 编写自己的异步代码 { #write-your-own-async-code } Starlette (和 **FastAPI**) 是基于 AnyIO 实现的,这使得它们可以兼容 Python 的标准库 asyncioTrio。 @@ -371,7 +371,7 @@ Starlette (和 **FastAPI**) 是基于 Asyncer。如果你有**结合使用异步代码和常规**(阻塞/同步)代码的需求,这个库会特别有用。 -### 其他形式的异步代码 +### 其他形式的异步代码 { #other-forms-of-asynchronous-code } 这种使用 `async` 和 `await` 的风格在语言中相对较新。 @@ -385,13 +385,13 @@ Starlette (和 **FastAPI**) 是基于 I/O 的代码。 +如果你使用过另一个不以上述方式工作的异步框架,并且你习惯于用普通的 `def` 定义普通的仅计算路径操作函数,以获得微小的性能增益(大约100纳秒),请注意,在 FastAPI 中,效果将完全相反。在这些情况下,最好使用 `async def`,除非路径操作函数内使用执行阻塞 I/O 的代码。 -在这两种情况下,与你之前的框架相比,**FastAPI** 可能[仍然很快](index.md#_11){.internal-link target=_blank}。 +在这两种情况下,与你之前的框架相比,**FastAPI** 可能[仍然很快](index.md#performance){.internal-link target=_blank}。 -### 依赖 +### 依赖 { #dependencies } 这同样适用于[依赖](tutorial/dependencies/index.md){.internal-link target=_blank}。如果一个依赖是标准的 `def` 函数而不是 `async def`,它将被运行在外部线程池中。 -### 子依赖 +### 子依赖 { #sub-dependencies } 你可以拥有多个相互依赖的依赖以及[子依赖](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} (作为函数的参数),它们中的一些可能是通过 `async def` 声明,也可能是通过 `def` 声明。它们仍然可以正常工作,这些通过 `def` 声明的函数将会在外部线程中调用(来自线程池),而不是"被等待"。 -### 其他函数 +### 其他函数 { #other-utility-functions } 你可直接调用通过 `def` 或 `async def` 创建的任何其他函数,FastAPI 不会影响你调用它们的方式。 @@ -441,4 +441,4 @@ Starlette (和 **FastAPI**) 是基于 赶时间吗?. +否则,你最好应该遵守的指导原则赶时间吗?. diff --git a/docs/zh/docs/benchmarks.md b/docs/zh/docs/benchmarks.md index 71e8d4838..1a4b4a3de 100644 --- a/docs/zh/docs/benchmarks.md +++ b/docs/zh/docs/benchmarks.md @@ -1,10 +1,10 @@ -# 基准测试 +# 基准测试 { #benchmarks } -第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 可用的最快的 Python 框架之一,仅次于 Starlette 和 Uvicorn 本身 (由 FastAPI 内部使用)。(*) +第三方机构 TechEmpower 的基准测试表明在 Uvicorn 下运行的 **FastAPI** 应用程序是 可用的最快的 Python 框架之一,仅次于 Starlette 和 Uvicorn 本身(由 FastAPI 内部使用)。 但是在查看基准得分和对比时,请注意以下几点。 -## 基准测试和速度 +## 基准测试和速度 { #benchmarks-and-speed } 当你查看基准测试时,几个不同类型的工具被等效地做比较是很常见的情况。 @@ -20,15 +20,15 @@ * **Uvicorn**: * 具有最佳性能,因为除了服务器本身外,它没有太多额外的代码。 - * 您不会直接在 Uvicorn 中编写应用程序。这意味着您的代码至少必须包含 Starlette(或 **FastAPI**)提供的代码。如果您这样做了(即直接在 Uvicorn 中编写应用程序),最终的应用程序会和使用了框架并且最小化了应用代码和 bug 的情况具有相同的性能损耗。 - * 如果要对比与 Uvicorn 对标的服务器,请将其与 Daphne,Hypercorn,uWSGI等应用服务器进行比较。 + * 你不会直接在 Uvicorn 中编写应用程序。这意味着你的代码至少必须包含 Starlette(或 **FastAPI**)提供的代码。如果你这样做了(即直接在 Uvicorn 中编写应用程序),最终的应用程序会和使用了框架并且最小化了应用代码和 bug 的情况具有相同的性能损耗。 + * 如果你要对比 Uvicorn,请将其与 Daphne,Hypercorn,uWSGI 等应用服务器进行比较。 * **Starlette**: - * 在 Uvicorn 后使用 Starlette,性能会略有下降。实际上,Starlette 使用 Uvicorn运行。因此,由于必须执行更多的代码,它只会比 Uvicorn 更慢。 - * 但它为您提供了构建简单的网络程序的工具,并具有基于路径的路由等功能。 + * 在 Uvicorn 后使用 Starlette,性能会略有下降。实际上,Starlette 使用 Uvicorn 运行。因此,由于必须执行更多的代码,它只会比 Uvicorn 更慢。 + * 但它为你提供了构建简单的网络程序的工具,并具有基于路径的路由等功能。 * 如果想对比与 Starlette 对标的开发框架,请将其与 Sanic,Flask,Django 等网络框架(或微框架)进行比较。 * **FastAPI**: * 与 Starlette 使用 Uvicorn 一样,由于 **FastAPI** 使用 Starlette,因此 FastAPI 不能比 Starlette 更快。 - * FastAPI 在 Starlette 基础上提供了更多功能。例如在开发 API 时,所需的数据验证和序列化功能。FastAPI 可以帮助您自动生成 API文档,(文档在应用程序启动时自动生成,所以不会增加应用程序运行时的开销)。 - * 如果您不使用 FastAPI 而直接使用 Starlette(或诸如 Sanic,Flask,Responder 等其它工具),您则要自己实现所有的数据验证和序列化。那么最终您的应用程序会和使用 FastAPI 构建的程序有相同的开销。一般这种数据验证和序列化的操作在您应用程序的代码中会占很大比重。 - * 因此,通过使用 FastAPI 意味着您可以节省开发时间,减少编码错误,用更少的编码实现其功能,并且相比不使用 FastAPI 您很大可能会获得相同或更好的性能(因为那样您必须在代码中实现所有相同的功能)。 - * 如果您想对比与 FastAPI 对标的开发框架,请与能够提供数据验证,序列化和带有自动文档生成的网络应用程序框架(或工具集)进行对比,例如具有集成自动数据验证,序列化和自动化文档的 Flask-apispec,NestJS,Molten 等。 + * FastAPI 在 Starlette 基础上提供了更多功能。例如在开发 API 时,所需的数据验证和序列化功能。FastAPI 可以帮助你自动生成 API文档,(文档在应用程序启动时自动生成,所以不会增加应用程序运行时的开销)。 + * 如果你不使用 FastAPI 而直接使用 Starlette(或诸如 Sanic,Flask,Responder 等其它工具),你则要自己实现所有的数据验证和序列化。那么最终你的应用程序会和使用 FastAPI 构建的程序有相同的开销。一般这种数据验证和序列化的操作在你应用程序的代码中会占很大比重。 + * 因此,通过使用 FastAPI 意味着你可以节省开发时间,减少编码错误,用更少的编码实现其功能,并且相比不使用 FastAPI 你很大可能会获得相同或更好的性能(因为那样你必须在代码中实现所有相同的功能)。 + * 如果你想对比 FastAPI,请与能够提供数据验证、序列化和文档的网络应用程序框架(或工具集)进行对比,例如具有集成自动数据验证、序列化和自动化文档的 Flask-apispec,NestJS,Molten 等。 diff --git a/docs/zh/docs/deployment/cloud.md b/docs/zh/docs/deployment/cloud.md index 8a892a560..96883bd6b 100644 --- a/docs/zh/docs/deployment/cloud.md +++ b/docs/zh/docs/deployment/cloud.md @@ -1,13 +1,24 @@ -# 在云上部署 FastAPI +# 在云服务商上部署 FastAPI { #deploy-fastapi-on-cloud-providers } -您几乎可以使用**任何云服务商**来部署 FastAPI 应用程序。 +你几乎可以使用**任何云服务商**来部署你的 FastAPI 应用。 -在大多数情况下,主要的云服务商都有部署 FastAPI 的指南。 +在大多数情况下,主流云服务商都有部署 FastAPI 的指南。 -## 云服务商 - 赞助商 +## FastAPI Cloud { #fastapi-cloud } -一些云服务商 ✨ [**赞助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨,这确保了FastAPI 及其**生态系统**持续健康地**发展**。 +**FastAPI Cloud** 由 **FastAPI** 背后的同一作者与团队打造。 -这表明了他们对 FastAPI 及其**社区**(您)的真正承诺,因为他们不仅想为您提供**良好的服务**,而且还想确保您拥有一个**良好且健康的框架**:FastAPI。 🙇 +它简化了**构建**、**部署**和**访问** API 的流程,几乎不费力。 -您可能想尝试他们的服务并阅读他们的指南. +它把使用 FastAPI 构建应用时相同的**开发者体验**带到了将应用**部署**到云上的过程。🎉 + +FastAPI Cloud 是 *FastAPI and friends* 开源项目的主要赞助方和资金提供者。✨ + +## 云服务商 - 赞助商 { #cloud-providers-sponsors } + +还有一些云服务商也会 ✨ [**赞助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨。🙇 + +你也可以考虑按照他们的指南尝试他们的服务: + +* Render +* Railway diff --git a/docs/zh/docs/deployment/concepts.md b/docs/zh/docs/deployment/concepts.md index f7208da7c..66d32629c 100644 --- a/docs/zh/docs/deployment/concepts.md +++ b/docs/zh/docs/deployment/concepts.md @@ -1,4 +1,4 @@ -# 部署概念 +# 部署概念 { #deployments-concepts } 在部署 **FastAPI** 应用程序或任何类型的 Web API 时,有几个概念值得了解,通过掌握这些概念您可以找到**最合适的**方法来**部署您的应用程序**。 @@ -13,7 +13,7 @@ 我们接下来了解它们将如何影响**部署**。 -我们的最终目标是能够以**安全**的方式**为您的 API 客户端**提供服务,同时要**避免中断**,并且尽可能高效地利用**计算资源**( 例如服务器CPU资源)。 🚀 +我们的最终目标是能够以**安全**的方式**为您的 API 客户端**提供服务,同时要**避免中断**,并且尽可能高效地利用**计算资源**(例如远程服务器/虚拟机)。 🚀 我将在这里告诉您更多关于这些**概念**的信息,希望能给您提供**直觉**来决定如何在非常不同的环境中部署 API,甚至在是尚不存在的**未来**的环境里。 @@ -23,7 +23,7 @@ 但现在,让我们仔细看一下这些重要的**概念**。 这些概念也适用于任何其他类型的 Web API。 💡 -## 安全性 - HTTPS +## 安全性 - HTTPS { #security-https } 在[上一章有关 HTTPS](https.md){.internal-link target=_blank} 中,我们了解了 HTTPS 如何为您的 API 提供加密。 @@ -31,21 +31,20 @@ 并且必须有某个东西负责**更新 HTTPS 证书**,它可以是相同的组件,也可以是不同的组件。 - -### HTTPS 示例工具 +### HTTPS 示例工具 { #example-tools-for-https } 您可以用作 TLS 终止代理的一些工具包括: * Traefik - * 自动处理证书更新 ✨ + * 自动处理证书更新 ✨ * Caddy - * 自动处理证书更新 ✨ + * 自动处理证书更新 ✨ * Nginx - * 使用 Certbot 等外部组件进行证书更新 + * 使用 Certbot 等外部组件进行证书更新 * HAProxy - * 使用 Certbot 等外部组件进行证书更新 -* 带有 Ingress Controller(如Nginx) 的 Kubernetes - * 使用诸如 cert-manager 之类的外部组件来进行证书更新 + * 使用 Certbot 等外部组件进行证书更新 +* 带有 Ingress Controller(如 Nginx) 的 Kubernetes + * 使用诸如 cert-manager 之类的外部组件来进行证书更新 * 由云服务商内部处理,作为其服务的一部分(请阅读下文👇) 另一种选择是您可以使用**云服务**来完成更多工作,包括设置 HTTPS。 它可能有一些限制或向您收取更多费用等。但在这种情况下,您不必自己设置 TLS 终止代理。 @@ -56,11 +55,11 @@ 接下来要考虑的概念都是关于运行实际 API 的程序(例如 Uvicorn)。 -## 程序和进程 +## 程序和进程 { #program-and-process } 我们将讨论很多关于正在运行的“**进程**”的内容,因此弄清楚它的含义以及与“**程序**”这个词有什么区别是很有用的。 -### 什么是程序 +### 什么是程序 { #what-is-a-program } **程序**这个词通常用来描述很多东西: @@ -68,12 +67,12 @@ * 操作系统可以**执行**的**文件**,例如:`python`、`python.exe`或`uvicorn`。 * 在操作系统上**运行**、使用CPU 并将内容存储在内存上的特定程序。 这也被称为**进程**。 -### 什么是进程 +### 什么是进程 { #what-is-a-process } **进程** 这个词通常以更具体的方式使用,仅指在操作系统中运行的东西(如上面的最后一点): * 在操作系统上**运行**的特定程序。 - * 这不是指文件,也不是指代码,它**具体**指的是操作系统正在**执行**和管理的东西。 + * 这不是指文件,也不是指代码,它**具体**指的是操作系统正在**执行**和管理的东西。 * 任何程序,任何代码,**只有在执行时才能做事**。 因此,是当有**进程正在运行**时。 * 该进程可以由您或操作系统**终止**(或“杀死”)。 那时,它停止运行/被执行,并且它可以**不再做事情**。 * 您计算机上运行的每个应用程序背后都有一些进程,每个正在运行的程序,每个窗口等。并且通常在计算机打开时**同时**运行许多进程。 @@ -89,13 +88,13 @@ 现在我们知道了术语“进程”和“程序”之间的区别,让我们继续讨论部署。 -## 启动时运行 +## 启动时运行 { #running-on-startup } 在大多数情况下,当您创建 Web API 时,您希望它**始终运行**、不间断,以便您的客户端始终可以访问它。 这是当然的,除非您有特定原因希望它仅在某些情况下运行,但大多数时候您希望它不断运行并且**可用**。 -### 在远程服务器中 +### 在远程服务器中 { #in-a-remote-server } -当您设置远程服务器(云服务器、虚拟机等)时,您可以做的最简单的事情就是手动运行 Uvicorn(或类似的),就像本地开发时一样。 +当您设置远程服务器(云服务器、虚拟机等)时,您可以做的最简单的事情就是使用 `fastapi run`(它使用 Uvicorn)或类似方式,手动运行,就像本地开发时一样。 它将会在**开发过程中**发挥作用并发挥作用。 @@ -103,16 +102,15 @@ 如果服务器重新启动(例如更新后或从云提供商迁移后),您可能**不会注意到它**。 因此,您甚至不知道必须手动重新启动该进程。 所以,你的 API 将一直处于挂掉的状态。 😱 - -### 启动时自动运行 +### 启动时自动运行 { #run-automatically-on-startup } 一般来说,您可能希望服务器程序(例如 Uvicorn)在服务器启动时自动启动,并且不需要任何**人为干预**,让进程始终与您的 API 一起运行(例如 Uvicorn 运行您的 FastAPI 应用程序) 。 -### 单独的程序 +### 单独的程序 { #separate-program } 为了实现这一点,您通常会有一个**单独的程序**来确保您的应用程序在启动时运行。 在许多情况下,它还可以确保其他组件或应用程序也运行,例如数据库。 -### 启动时运行的示例工具 +### 启动时运行的示例工具 { #example-tools-to-run-at-startup } 可以完成这项工作的工具的一些示例是: @@ -127,44 +125,43 @@ 我将在接下来的章节中为您提供更具体的示例。 - -## 重新启动 +## 重新启动 { #restarts } 与确保应用程序在启动时运行类似,您可能还想确保它在挂掉后**重新启动**。 -### 我们会犯错误 +### 我们会犯错误 { #we-make-mistakes } 作为人类,我们总是会犯**错误**。 软件几乎*总是*在不同的地方隐藏着**bug**。 🐛 作为开发人员,当我们发现这些bug并实现新功能(也可能添加新bug😅)时,我们会不断改进代码。 -### 自动处理小错误 +### 自动处理小错误 { #small-errors-automatically-handled } 使用 FastAPI 构建 Web API 时,如果我们的代码中存在错误,FastAPI 通常会将其包含到触发错误的单个请求中。 🛡 对于该请求,客户端将收到 **500 内部服务器错误**,但应用程序将继续处理下一个请求,而不是完全崩溃。 -### 更大的错误 - 崩溃 +### 更大的错误 - 崩溃 { #bigger-errors-crashes } 尽管如此,在某些情况下,我们编写的一些代码可能会导致整个应用程序崩溃,从而导致 Uvicorn 和 Python 崩溃。 💥 尽管如此,您可能不希望应用程序因为某个地方出现错误而保持死机状态,您可能希望它**继续运行**,至少对于未破坏的*路径操作*。 -### 崩溃后重新启动 +### 崩溃后重新启动 { #restart-after-crash } 但在那些严重错误导致正在运行的**进程**崩溃的情况下,您需要一个外部组件来负责**重新启动**进程,至少尝试几次...... -/// tip +/// tip | 提示 ...尽管如果整个应用程序只是**立即崩溃**,那么永远重新启动它可能没有意义。 但在这些情况下,您可能会在开发过程中注意到它,或者至少在部署后立即注意到它。 - 因此,让我们关注主要情况,在**未来**的某些特定情况下,它可能会完全崩溃,但重新启动它仍然有意义。 +因此,让我们关注主要情况,在**未来**的某些特定情况下,它可能会完全崩溃,但重新启动它仍然有意义。 /// 您可能希望让这个东西作为 **外部组件** 负责重新启动您的应用程序,因为到那时,使用 Uvicorn 和 Python 的同一应用程序已经崩溃了,因此同一应用程序的相同代码中没有东西可以对此做出什么。 -### 自动重新启动的示例工具 +### 自动重新启动的示例工具 { #example-tools-to-restart-automatically } 在大多数情况下,用于**启动时运行程序**的同一工具也用于处理自动**重新启动**。 @@ -173,25 +170,25 @@ * Docker * Kubernetes * Docker Compose -* Docker in Swarm mode +* Docker in Swarm Mode * Systemd * Supervisor * 作为其服务的一部分由云提供商内部处理 * 其他的... -## 复制 - 进程和内存 +## 复制 - 进程和内存 { #replication-processes-and-memory } -对于 FastAPI 应用程序,使用像 Uvicorn 这样的服务器程序,在**一个进程**中运行一次就可以同时为多个客户端提供服务。 +对于 FastAPI 应用程序,使用像 `fastapi` 命令(运行 Uvicorn)这样的服务器程序,在**一个进程**中运行一次就可以同时为多个客户端提供服务。 但在许多情况下,您会希望同时运行多个工作进程。 -### 多进程 - Workers +### 多进程 - Workers { #multiple-processes-workers } -如果您的客户端数量多于单个进程可以处理的数量(例如,如果虚拟机不是太大),并且服务器的 CPU 中有 **多个核心**,那么您可以让 **多个进程** 运行 同时处理同一个应用程序,并在它们之间分发所有请求。 +如果您的客户端数量多于单个进程可以处理的数量(例如,如果虚拟机不是太大),并且服务器的 CPU 中有 **多个核心**,那么您可以让 **多个进程** 同时运行同一个应用程序,并在它们之间分发所有请求。 当您运行同一 API 程序的**多个进程**时,它们通常称为 **workers**。 -### 工作进程和端口 +### 工作进程和端口 { #worker-processes-and-ports } 还记得文档 [About HTTPS](https.md){.internal-link target=_blank} 中只有一个进程可以侦听服务器中的端口和 IP 地址的一种组合吗? @@ -199,20 +196,19 @@ 因此,为了能够同时拥有**多个进程**,必须有一个**单个进程侦听端口**,然后以某种方式将通信传输到每个工作进程。 -### 每个进程的内存 +### 每个进程的内存 { #memory-per-process } 现在,当程序将内容加载到内存中时,例如,将机器学习模型加载到变量中,或者将大文件的内容加载到变量中,所有这些都会消耗服务器的一点内存 (RAM) 。 多个进程通常**不共享任何内存**。 这意味着每个正在运行的进程都有自己的东西、变量和内存。 如果您的代码消耗了大量内存,**每个进程**将消耗等量的内存。 -### 服务器内存 +### 服务器内存 { #server-memory } 例如,如果您的代码加载 **1 GB 大小**的机器学习模型,则当您使用 API 运行一个进程时,它将至少消耗 1 GB RAM。 如果您启动 **4 个进程**(4 个工作进程),每个进程将消耗 1 GB RAM。 因此,您的 API 总共将消耗 **4 GB RAM**。 如果您的远程服务器或虚拟机只有 3 GB RAM,尝试加载超过 4 GB RAM 将导致问题。 🚨 - -### 多进程 - 一个例子 +### 多进程 - 一个例子 { #multiple-processes-an-example } 在此示例中,有一个 **Manager Process** 启动并控制两个 **Worker Processes**。 @@ -224,11 +220,11 @@ 当然,除了您的应用程序之外,同一台机器可能还运行**其他进程**。 -一个有趣的细节是,随着时间的推移,每个进程使用的 **CPU 百分比可能会发生很大变化,但内存 (RAM) 通常会或多或少保持稳定**。 +一个有趣的细节是,随着时间的推移,每个进程使用的 **CPU 百分比**可能会发生很大变化,但**内存 (RAM)** 通常会或多或少保持**稳定**。 如果您有一个每次执行相当数量的计算的 API,并且您有很多客户端,那么 **CPU 利用率** 可能也会保持稳定(而不是不断快速上升和下降)。 -### 复制工具和策略示例 +### 复制工具和策略示例 { #examples-of-replication-tools-and-strategies } 可以通过多种方法来实现这一目标,我将在接下来的章节中向您详细介绍具体策略,例如在谈论 Docker 和容器时。 @@ -236,26 +232,22 @@ 以下是一些可能的组合和策略: -* **Gunicorn** 管理 **Uvicorn workers** - * Gunicorn 将是监听 **IP** 和 **端口** 的 **进程管理器**,复制将通过 **多个 Uvicorn 工作进程** 进行 -* **Uvicorn** 管理 **Uvicorn workers** - * 一个 Uvicorn **进程管理器** 将监听 **IP** 和 **端口**,并且它将启动 **多个 Uvicorn 工作进程** +* 带有 `--workers` 的 **Uvicorn** + * 一个 Uvicorn **进程管理器** 将监听 **IP** 和 **端口**,并且它将启动 **多个 Uvicorn 工作进程**。 * **Kubernetes** 和其他分布式 **容器系统** - * **Kubernetes** 层中的某些东西将侦听 **IP** 和 **端口**。 复制将通过拥有**多个容器**,每个容器运行**一个 Uvicorn 进程** + * **Kubernetes** 层中的某些东西将侦听 **IP** 和 **端口**。 复制将通过拥有**多个容器**,每个容器运行**一个 Uvicorn 进程**。 * **云服务** 为您处理此问题 - * 云服务可能**为您处理复制**。 它可能会让您定义 **要运行的进程**,或要使用的 **容器映像**,在任何情况下,它很可能是 **单个 Uvicorn 进程**,并且云服务将负责复制它。 + * 云服务可能**为您处理复制**。 它可能会让您定义 **要运行的进程**,或要使用的 **容器映像**,在任何情况下,它很可能是 **单个 Uvicorn 进程**,并且云服务将负责复制它。 - - -/// tip +/// tip | 提示 如果这些关于 **容器**、Docker 或 Kubernetes 的内容还没有多大意义,请不要担心。 - 我将在以后的章节中向您详细介绍容器镜像、Docker、Kubernetes 等:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。 +我将在以后的章节中向您详细介绍容器镜像、Docker、Kubernetes 等:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。 /// -## 启动之前的步骤 +## 启动之前的步骤 { #previous-steps-before-starting } 在很多情况下,您希望在**启动**应用程序之前执行一些步骤。 @@ -269,15 +261,15 @@ 当然,也有一些情况,多次运行前面的步骤也没有问题,这样的话就好办多了。 -/// tip +/// tip | 提示 另外,请记住,根据您的设置,在某些情况下,您在开始应用程序之前**可能甚至不需要任何先前的步骤**。 - 在这种情况下,您就不必担心这些。 🤷 +在这种情况下,您就不必担心这些。 🤷 /// -### 前面步骤策略的示例 +### 前面步骤策略的示例 { #examples-of-previous-steps-strategies } 这将在**很大程度上取决于您部署系统的方式**,并且可能与您启动程序、处理重启等的方式有关。 @@ -285,15 +277,15 @@ * Kubernetes 中的“Init Container”在应用程序容器之前运行 * 一个 bash 脚本,运行前面的步骤,然后启动您的应用程序 - * 您仍然需要一种方法来启动/重新启动 bash 脚本、检测错误等。 + * 您仍然需要一种方法来启动/重新启动 bash 脚本、检测错误等。 -/// tip +/// tip | 提示 我将在以后的章节中为您提供使用容器执行此操作的更具体示例:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。 /// -## 资源利用率 +## 资源利用率 { #resource-utilization } 您的服务器是一个**资源**,您可以通过您的程序消耗或**利用**CPU 上的计算时间以及可用的 RAM 内存。 @@ -313,8 +305,7 @@ 您可以使用“htop”等简单工具来查看服务器中使用的 CPU 和 RAM 或每个进程使用的数量。 或者您可以使用更复杂的监控工具,这些工具可能分布在服务器等上。 - -## 回顾 +## 回顾 { #recap } 您在这里阅读了一些在决定如何部署应用程序时可能需要牢记的主要概念: diff --git a/docs/zh/docs/deployment/docker.md b/docs/zh/docs/deployment/docker.md index f120ebfb8..3d0c19903 100644 --- a/docs/zh/docs/deployment/docker.md +++ b/docs/zh/docs/deployment/docker.md @@ -1,17 +1,17 @@ -# 容器中的 FastAPI - Docker +# 容器中的 FastAPI - Docker { #fastapi-in-containers-docker } -部署 FastAPI 应用程序时,常见的方法是构建 **Linux 容器镜像**。 通常使用 **Docker** 完成。 然后,你可以通过几种可能的方式之一部署该容器镜像。 +部署 FastAPI 应用时,常见做法是构建一个**Linux 容器镜像**。通常使用 **Docker** 实现。然后你可以用几种方式之一部署该镜像。 -使用 Linux 容器有几个优点,包括**安全性**、**可复制性**、**简单性**等。 +使用 Linux 容器有多种优势,包括**安全性**、**可复制性**、**简单性**等。 -/// tip +/// tip | 提示 -赶时间并且已经知道这些东西了? 跳转到下面的 [`Dockerfile` 👇](#fastapi-docker_1)。 +赶时间并且已经了解这些?直接跳到下面的 [`Dockerfile` 👇](#build-a-docker-image-for-fastapi)。 ///
-Dockerfile Preview 👀 +Dockerfile 预览 👀 ```Dockerfile FROM python:3.9 @@ -24,140 +24,127 @@ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt COPY ./app /code/app -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +CMD ["fastapi", "run", "app/main.py", "--port", "80"] # If running behind a proxy like Nginx or Traefik add --proxy-headers -# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] +# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"] ```
-## 什么是容器 +## 什么是容器 { #what-is-a-container } -容器(主要是 Linux 容器)是一种非常**轻量级**的打包应用程序的方式,其包括所有依赖项和必要的文件,同时它们可以和同一系统中的其他容器(或者其他应用程序/组件)相互隔离。 +容器(主要是 Linux 容器)是一种非常**轻量**的方式,用来打包应用及其所有依赖和必要文件,并让它们与同一系统中的其他容器(其他应用或组件)相互隔离。 -Linux 容器使用宿主机(如物理服务器、虚拟机、云服务器等)的Linux 内核运行。 这意味着它们非常轻量(与模拟整个操作系统的完整虚拟机相比)。 +Linux 容器复用宿主机(物理机、虚拟机、云服务器等)的同一个 Linux 内核。这意味着它们非常轻量(相较于模拟整个操作系统的完整虚拟机)。 -通过这样的方式,容器消耗**很少的资源**,与直接运行进程相当(虚拟机会消耗更多)。 +因此,容器消耗的**资源很少**,大致相当于直接运行进程(而虚拟机会多很多)。 -容器的进程(通常只有一个)、文件系统和网络都运行在隔离的环境,这简化了部署、安全、开发等。 +容器还拥有各自**隔离**的运行进程(通常只有一个)、文件系统和网络,简化了部署、安全、开发等。 -## 什么是容器镜像 +## 什么是容器镜像 { #what-is-a-container-image } **容器**是从**容器镜像**运行的。 -容器镜像是容器中文件、环境变量和默认命令/程序的**静态**版本。 **静态**这里的意思是容器**镜像**还没有运行,只是打包的文件和元数据。 +容器镜像是容器中所有文件、环境变量以及应该运行的默认命令/程序的一个**静态**版本。这里的**静态**指容器**镜像**本身并不在运行,仅仅是被打包的文件和元数据。 -与存储静态内容的“**容器镜像**”相反,“**容器**”通常指正在运行的实例,即正在**执行的**。 +与存放静态内容的“**容器镜像**”相对,“**容器**”通常指一个正在运行的实例,即正在被**执行**的东西。 -当**容器**启动并运行时(从**容器镜像**启动),它可以创建或更改文件、环境变量等。这些更改将仅存在于该容器中,而不会持久化到底层的容器镜像中(不会保存到磁盘)。 +当**容器**启动并运行(从**容器镜像**启动)后,它可以创建或修改文件、环境变量等。这些更改只存在于该容器中,不会持久化到底层的容器镜像中(不会写回磁盘)。 -容器镜像相当于**程序**和文件,例如 `python`命令 和某些文件 如`main.py`。 +容器镜像可类比为**程序**文件及其内容,例如 `python` 和某个文件 `main.py`。 -而**容器**本身(与**容器镜像**相反)是镜像的实际运行实例,相当于**进程**。 事实上,容器仅在有**进程运行**时才运行(通常它只是一个单独的进程)。 当容器中没有进程运行时,容器就会停止。 +而**容器**本身(相对**容器镜像**)就是该镜像的实际运行实例,可类比为**进程**。事实上,容器只有在有**进程在运行**时才处于运行状态(通常只有一个进程)。当容器中没有任何进程在运行时,容器就会停止。 +## 容器镜像 { #container-images } +Docker 一直是创建和管理**容器镜像**与**容器**的主要工具之一。 -## 容器镜像 +还有一个公共的 Docker Hub,其中为许多工具、环境、数据库和应用提供了预制的**官方容器镜像**。 -Docker 一直是创建和管理**容器镜像**和**容器**的主要工具之一。 - -还有一个公共 Docker Hub ,其中包含预制的 **官方容器镜像**, 适用于许多工具、环境、数据库和应用程序。 - -例如,有一个官方的 Python 镜像。 - -还有许多其他镜像用于不同的需要(例如数据库),例如: +例如,有官方的 Python 镜像。 +还有许多用于不同目的(如数据库)的镜像,例如: * PostgreSQL * MySQL * MongoDB -* Redis, etc. +* Redis 等。 +通过使用预制的容器镜像,可以很容易地**组合**并使用不同工具。例如,试用一个新的数据库。在大多数情况下,你可以直接使用**官方镜像**,只需通过环境变量配置即可。 -通过使用预制的容器镜像,可以非常轻松地**组合**并使用不同的工具。 例如,尝试一个新的数据库。 在大多数情况下,你可以使用**官方镜像**,只需为其配置环境变量即可。 +这样,在很多场景中你可以学习容器和 Docker,并将这些知识复用到许多不同的工具和组件中。 -这样,在许多情况下,你可以了解容器和 Docker,并通过许多不同的工具和组件重复使用这些知识。 +因此,你可以运行包含不同内容的**多个容器**,比如一个数据库、一个 Python 应用、一个带 React 前端的 Web 服务器,并通过它们的内部网络连接在一起。 -因此,你可以运行带有不同内容的**多个容器**,例如数据库、Python 应用程序、带有 React 前端应用程序的 Web 服务器,并通过内部网络将它们连接在一起。 +所有容器管理系统(如 Docker 或 Kubernetes)都内置了这些网络功能。 -所有容器管理系统(如 Docker 或 Kubernetes)都集成了这些网络功能。 +## 容器与进程 { #containers-and-processes } -## 容器和进程 +**容器镜像**通常在其元数据中包含在**容器**启动时应运行的默认程序或命令以及要传递给该程序的参数。这与命令行中做的事情非常相似。 -**容器镜像**通常在其元数据中包含启动**容器**时应运行的默认程序或命令以及要传递给该程序的参数。 与在命令行中的情况非常相似。 - -当 **容器** 启动时,它将运行该命令/程序(尽管你可以覆盖它并使其运行不同的命令/程序)。 +当**容器**启动时,它将运行该命令/程序(尽管你可以覆盖它,让其运行不同的命令/程序)。 只要**主进程**(命令或程序)在运行,容器就在运行。 -容器通常有一个**单个进程**,但也可以从主进程启动子进程,这样你就可以在同一个容器中拥有**多个进程**。 +容器通常只有**一个进程**,但也可以由主进程启动子进程,这样同一个容器中就会有**多个进程**。 -但是,如果没有**至少一个正在运行的进程**,就不可能有一个正在运行的容器。 如果主进程停止,容器也会停止。 +但不可能在没有**至少一个运行中的进程**的情况下让容器保持运行。如果主进程停止,容器也会停止。 +## 为 FastAPI 构建 Docker 镜像 { #build-a-docker-image-for-fastapi } -## 为 FastAPI 构建 Docker 镜像 +好啦,现在动手构建点东西!🚀 -好吧,让我们现在构建一些东西! 🚀 +我将演示如何基于**官方 Python** 镜像,**从零开始**为 FastAPI 构建一个**Docker 镜像**。 -我将向你展示如何基于 **官方 Python** 镜像 **从头开始** 为 FastAPI 构建 **Docker 镜像**。 - -这是你在**大多数情况**下想要做的,例如: +这在**大多数情况**下都适用,例如: * 使用 **Kubernetes** 或类似工具 -* 在 **Raspberry Pi** 上运行时 -* 使用可为你运行容器镜像的云服务等。 +* 运行在 **Raspberry Pi** +* 使用某个为你运行容器镜像的云服务,等等 -### 依赖项 +### 包依赖 { #package-requirements } -你通常会在某个文件中包含应用程序的**依赖项**。 +通常你会把应用的**包依赖**放在某个文件里。 -具体做法取决于你**安装**这些依赖时所使用的工具。 +这主要取决于你用来**安装**这些依赖的工具。 -最常见的方法是创建一个`requirements.txt`文件,其中每行包含一个包名称和它的版本。 +最常见的方式是使用 `requirements.txt` 文件,每行一个包名及其版本范围。 -你当然也可以使用在[关于 FastAPI 版本](versions.md){.internal-link target=_blank} 中讲到的方法来设置版本范围。 - -例如,你的`requirements.txt`可能如下所示: +当然,你也可以参考你在[关于 FastAPI 版本](versions.md){.internal-link target=_blank}中读到的思路来设置版本范围。 +例如,你的 `requirements.txt` 可能是: ``` -fastapi>=0.68.0,<0.69.0 -pydantic>=1.8.0,<2.0.0 -uvicorn>=0.15.0,<0.16.0 +fastapi[standard]>=0.113.0,<0.114.0 +pydantic>=2.7.0,<3.0.0 ``` -你通常会使用`pip`安装这些依赖项: +通常你会用 `pip` 安装这些依赖,例如:
```console $ pip install -r requirements.txt ---> 100% -Successfully installed fastapi pydantic uvicorn +Successfully installed fastapi pydantic ```
-/// info +/// info | 信息 -还有其他文件格式和工具来定义和安装依赖项。 - - 我将在下面的部分中向你展示一个使用 Poetry 的示例。 👇 +还有其他格式和工具可以定义并安装包依赖。 /// -### 创建 **FastAPI** 代码 - -* 创建`app`目录并进入。 -* 创建一个空文件`__init__.py`。 -* 创建一个 `main.py` 文件: - +### 编写 **FastAPI** 代码 { #create-the-fastapi-code } +* 创建 `app` 目录并进入 +* 创建空文件 `__init__.py` +* 创建 `main.py`,内容如下: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -169,84 +156,114 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` -### Dockerfile +### Dockerfile { #dockerfile } -现在在相同的project目录创建一个名为`Dockerfile`的文件: +现在在同一个项目目录下创建 `Dockerfile` 文件: ```{ .dockerfile .annotate } -# (1) +# (1)! FROM python:3.9 -# (2) +# (2)! WORKDIR /code -# (3) +# (3)! COPY ./requirements.txt /code/requirements.txt -# (4) +# (4)! RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -# (5) +# (5)! COPY ./app /code/app -# (6) -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +# (6)! +CMD ["fastapi", "run", "app/main.py", "--port", "80"] ``` -1. 从官方Python基础镜像开始。 +1. 从官方 Python 基础镜像开始。 -2. 将当前工作目录设置为`/code`。 +2. 将当前工作目录设置为 `/code`。 - 这是我们放置`requirements.txt`文件和`app`目录的位置。 + 我们会把 `requirements.txt` 文件和 `app` 目录放在这里。 -3. 将符合要求的文件复制到`/code`目录中。 +3. 将依赖文件复制到 `/code` 目录。 - 首先仅复制requirements.txt文件,而不复制其余代码。 + 首先**只**复制依赖文件,不要复制其他代码。 - 由于此文件**不经常更改**,Docker 将检测到它并在这一步中使用**缓存**,从而为下一步启用缓存。 + 因为这个文件**不常变化**,Docker 会检测并在此步骤使用**缓存**,从而也为下一步启用缓存。 -4. 安装需求文件中的包依赖项。 +4. 安装依赖文件中的包依赖。 - `--no-cache-dir` 选项告诉 `pip` 不要在本地保存下载的包,因为只有当 `pip` 再次运行以安装相同的包时才会这样,但在与容器一起工作时情况并非如此。 + `--no-cache-dir` 选项告诉 `pip` 不要在本地保存下载的包,只有当以后还要再次用 `pip` 安装相同包时才需要,但在容器场景下不是这样。 - /// note | 笔记 + /// note | 注意 - `--no-cache-dir` 仅与 `pip` 相关,与 Docker 或容器无关。 + `--no-cache-dir` 只和 `pip` 有关,与 Docker 或容器无关。 - /// + /// - `--upgrade` 选项告诉 `pip` 升级软件包(如果已经安装)。 + `--upgrade` 选项告诉 `pip` 如果包已安装则进行升级。 - 因为上一步复制文件可以被 **Docker 缓存** 检测到,所以此步骤也将 **使用 Docker 缓存**(如果可用)。 + 由于上一步复制文件可能被 **Docker 缓存**检测到,因此这一步在可用时也会**使用 Docker 缓存**。 - 在开发过程中一次又一次构建镜像时,在此步骤中使用缓存将为你节省大量**时间**,而不是**每次**都**下载和安装**所有依赖项。 + 在开发过程中反复构建镜像时,此步骤使用缓存可以为你**节省大量时间**,而不必**每次**都**下载并安装**所有依赖。 +5. 将 `./app` 目录复制到 `/code` 目录。 -5. 将“./app”目录复制到“/code”目录中。 + 这里包含了所有**最常变化**的代码,因此 Docker **缓存**很难用于这一步或**其后的步骤**。 - 由于其中包含**更改最频繁**的所有代码,因此 Docker **缓存**不会轻易用于此操作或任何**后续步骤**。 + 所以,把它放在 `Dockerfile` 的**靠后位置**,有助于优化容器镜像的构建时间。 - 因此,将其放在`Dockerfile`**接近最后**的位置非常重要,以优化容器镜像的构建时间。 +6. 设置使用 `fastapi run` 的**命令**(底层使用 Uvicorn)。 -6. 设置**命令**来运行 `uvicorn` 服务器。 + `CMD` 接受一个字符串列表,每个字符串相当于你在命令行中用空格分隔输入的内容。 - `CMD` 接受一个字符串列表,每个字符串都是你在命令行中输入的内容,并用空格分隔。 + 该命令会从**当前工作目录**运行,也就是你用 `WORKDIR /code` 设置的 `/code` 目录。 - 该命令将从 **当前工作目录** 运行,即你上面使用`WORKDIR /code`设置的同一`/code`目录。 +/// tip | 提示 - 因为程序将从`/code`启动,并且其中包含你的代码的目录`./app`,所以**Uvicorn**将能够从`app.main`中查看并**import**`app`。 - -/// tip - -通过单击代码中的每个数字气泡来查看每行的作用。 👆 +点击代码中的每个编号气泡查看每行的作用。👆 /// -你现在应该具有如下目录结构: +/// warning | 警告 + +务必**始终**使用 `CMD` 指令的**exec 形式**,如下所述。 + +/// + +#### 使用 `CMD` - Exec 形式 { #use-cmd-exec-form } + +`CMD` 指令有两种写法: + +✅ **Exec** 形式: + +```Dockerfile +# ✅ 推荐 +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +⛔️ **Shell** 形式: + +```Dockerfile +# ⛔️ 不要这样 +CMD fastapi run app/main.py --port 80 +``` + +务必使用**exec** 形式,以确保 FastAPI 可以优雅停机并触发[生命周期事件](../advanced/events.md){.internal-link target=_blank}。 + +你可以在 Docker 文档(Shell 与 Exec 形式)中了解更多。 + +在使用 `docker compose` 时这一点尤为明显。更多技术细节参见该 FAQ:为什么我的服务需要 10 秒才能重新创建或停止? + +#### 目录结构 { #directory-structure } + +此时你的目录结构应类似: + ``` . ├── app @@ -256,56 +273,53 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] └── requirements.txt ``` +#### 在 TLS 终止代理后面 { #behind-a-tls-termination-proxy } -#### 在 TLS 终止代理后面 - -如果你在 Nginx 或 Traefik 等 TLS 终止代理(负载均衡器)后面运行容器,请添加选项 `--proxy-headers`,这将告诉 Uvicorn 信任该代理发送的标头,告诉它应用程序正在 HTTPS 后面运行等信息 +如果你在 Nginx 或 Traefik 等 TLS 终止代理(负载均衡器)后面运行容器,请添加 `--proxy-headers` 选项,这会通过 FastAPI CLI 告诉 Uvicorn 信任该代理发送的标头,表明应用运行在 HTTPS 后等。 ```Dockerfile -CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] +CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] ``` -#### Docker 缓存 +#### Docker 缓存 { #docker-cache } -这个`Dockerfile`中有一个重要的技巧,我们首先只单独复制**包含依赖项的文件**,而不是其余代码。 让我来告诉你这是为什么。 +这个 `Dockerfile` 里有个重要技巧:我们先**只复制依赖文件**,而不是其他代码。原因如下: ```Dockerfile COPY ./requirements.txt /code/requirements.txt ``` -Docker之类的构建工具是通过**增量**的方式来构建这些容器镜像的。具体做法是从`Dockerfile`顶部开始,每一条指令生成的文件都是镜像的“一层”,同过把这些“层”一层一层地叠加到基础镜像上,最后我们就得到了最终的镜像。 +Docker 等工具是**增量**地**构建**容器镜像的,从 `Dockerfile` 顶部开始,按顺序为每条指令创建**一层叠加层**,并把每步生成的文件加入。 -Docker 和类似工具在构建镜像时也会使用**内部缓存**,如果自上次构建容器镜像以来文件没有更改,那么它将**重新使用上次创建的同一层**,而不是再次复制文件并从头开始创建新层。 +构建镜像时,Docker 等工具也会使用**内部缓存**。如果自上次构建以来某个文件没有变更,它会**重用**上次创建的那一层,而不是再次复制文件并从头创建新层。 -仅仅避免文件的复制不一定会有太多速度提升,但是如果在这一步使用了缓存,那么才可以**在下一步中使用缓存**。 例如,可以使用安装依赖项那条指令的缓存: +仅仅避免复制文件并不会带来太多改进,但因为该步骤使用了缓存,它就可以**在下一步中继续使用缓存**。例如,安装依赖的这条指令也能使用缓存: ```Dockerfile RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt ``` +包含包依赖的文件**不会频繁变更**。仅复制该文件,Docker 就能在这一步**使用缓存**。 -包含包依赖项的文件**不会频繁更改**。 只复制该文件(不复制其他的应用代码),Docker 才能在这一步**使用缓存**。 +随后,Docker 还能**对下一步**(下载并安装依赖)**使用缓存**。这正是我们**节省大量时间**的地方。✨ ...并避免无聊的等待。😪😆 -Docker 进而能**使用缓存进行下一步**,即下载并安装这些依赖项。 这才是我们**节省大量时间**的地方。 ✨ ...可以避免无聊的等待。 😪😆 +下载并安装依赖**可能需要几分钟**,而使用**缓存**则**最多只需几秒**。 -下载和安装依赖项**可能需要几分钟**,但使用**缓存**最多**只需要几秒钟**。 +而且在开发中你会反复构建镜像来验证代码变更是否生效,累计节省的时间会很多。 -由于你在开发过程中会一次又一次地构建容器镜像以检查代码更改是否有效,因此可以累计节省大量时间。 - -在`Dockerfile`末尾附近,我们再添加复制代码的指令。 由于代码是**更改最频繁的**,所以将其放在最后,因为这一步之后的内容基本上都是无法使用缓存的。 +接着,在 `Dockerfile` 的末尾附近我们再复制所有代码。因为这是**变化最频繁**的部分,把它放在后面,这样几乎所有在它之后的步骤都不会使用到缓存。 ```Dockerfile COPY ./app /code/app ``` -### 构建 Docker 镜像 +### 构建 Docker 镜像 { #build-the-docker-image } -现在所有文件都已就位,让我们构建容器镜像。 +现在所有文件都就位了,开始构建容器镜像。 -* 转到项目目录(在`Dockerfile`所在的位置,包含`app`目录)。 +* 进入项目目录(`Dockerfile` 所在位置,包含 `app` 目录) * 构建你的 FastAPI 镜像: -
```console @@ -316,18 +330,17 @@ $ docker build -t myimage .
+/// tip | 提示 -/// tip +注意末尾的 `.`,它等价于 `./`,用于告诉 Docker 使用哪个目录来构建容器镜像。 -注意最后的 `.`,它相当于`./`,它告诉 Docker 用于构建容器镜像的目录。 - -在本例中,它是相同的当前目录(`.`)。 +此处就是当前目录(`.`)。 /// -### 启动 Docker 容器 +### 启动 Docker 容器 { #start-the-docker-container } -* 根据你的镜像运行容器: +* 基于你的镜像运行一个容器:
@@ -337,10 +350,9 @@ $ docker run -d --name mycontainer -p 80:80 myimage
-## 检查一下 +## 检查一下 { #check-it } - -你应该能在Docker容器的URL中检查它,例如: http://192.168.99.100/items/5?q=somequeryhttp://127.0.0.1/items/5?q=somequery (或其他等价的,使用 Docker 主机). +你应该能在容器暴露的 URL 访问它,例如:http://192.168.99.100/items/5?q=somequeryhttp://127.0.0.1/items/5?q=somequery(或其他等价地址,取决于你的 Docker 主机)。 你会看到类似内容: @@ -348,25 +360,25 @@ $ docker run -d --name mycontainer -p 80:80 myimage {"item_id": 5, "q": "somequery"} ``` -## 交互式 API 文档 +## 交互式 API 文档 { #interactive-api-docs } -现在你可以转到 http://192.168.99.100/docshttp://127.0.0.1/docs (或其他等价的,使用 Docker 主机)。 +现在你可以访问 http://192.168.99.100/docshttp://127.0.0.1/docs(或其他等价地址,取决于你的 Docker 主机)。 -你将看到自动交互式 API 文档(由 Swagger UI): +你将看到自动生成的交互式 API 文档(由 Swagger UI 提供): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -## 备选的 API 文档 +## 备选 API 文档 { #alternative-api-docs } -你还可以访问 http://192.168.99.100/redochttp://127.0.0.1/redoc (或其他等价的,使用 Docker 主机)。 +你还可以访问 http://192.168.99.100/redochttp://127.0.0.1/redoc(或其他等价地址,取决于你的 Docker 主机)。 你将看到备选的自动文档(由 ReDoc 提供): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## 使用单文件 FastAPI 构建 Docker 镜像 +## 使用单文件 FastAPI 构建 Docker 镜像 { #build-a-docker-image-with-a-single-file-fastapi } -如果你的 FastAPI 是单个文件,例如没有`./app`目录的`main.py`,则你的文件结构可能如下所示: +如果你的 FastAPI 是单个文件,例如没有 `./app` 目录、只有 `main.py`,你的文件结构可能如下: ``` . @@ -375,7 +387,7 @@ $ docker run -d --name mycontainer -p 80:80 myimage └── requirements.txt ``` -然后你只需更改相应的路径即可将文件复制到`Dockerfile`中: +然后你只需要在 `Dockerfile` 中修改相应路径来复制该文件: ```{ .dockerfile .annotate hl_lines="10 13" } FROM python:3.9 @@ -386,375 +398,221 @@ COPY ./requirements.txt /code/requirements.txt RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -# (1) +# (1)! COPY ./main.py /code/ -# (2) -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] +# (2)! +CMD ["fastapi", "run", "main.py", "--port", "80"] ``` -1. 直接将`main.py`文件复制到`/code`目录中(不包含任何`./app`目录)。 +1. 直接将 `main.py` 复制到 `/code`(没有 `./app` 目录)。 -2. 运行 Uvicorn 并告诉它从 `main` 导入 `app` 对象(而不是从 `app.main` 导入)。 +2. 使用 `fastapi run` 来运行单文件 `main.py` 中的应用。 -然后调整Uvicorn命令使用新模块`main`而不是`app.main`来导入FastAPI 实例`app`。 +当你把文件传给 `fastapi run` 时,它会自动检测这是一个单文件而不是包,并知道如何导入并服务你的 FastAPI 应用。😎 -## 部署概念 +## 部署概念 { #deployment-concepts } -我们再谈谈容器方面的一些相同的[部署概念](concepts.md){.internal-link target=_blank}。 +我们再从容器的角度讨论一些相同的[部署概念](concepts.md){.internal-link target=_blank}。 -容器主要是一种简化**构建和部署**应用程序的过程的工具,但它们并不强制执行特定的方法来处理这些**部署概念**,并且有几种可能的策略。 +容器主要是简化应用**构建与部署**流程的工具,但它们并不强制采用某种特定方式来处理这些**部署概念**,可选策略有多种。 -**好消息**是,对于每种不同的策略,都有一种方法可以涵盖所有部署概念。 🎉 +**好消息**是,不同策略下都有方式覆盖所有部署概念。🎉 -让我们从容器的角度回顾一下这些**部署概念**: +让我们从容器角度回顾这些**部署概念**: * HTTPS * 启动时运行 -* 重新启动 +* 失败重启 * 复制(运行的进程数) * 内存 -* 开始前的先前步骤 +* 启动前的前置步骤 +## HTTPS { #https } -## HTTPS +如果我们只关注 FastAPI 应用的**容器镜像**(以及后续运行的**容器**),HTTPS 通常由**外部**的其他工具处理。 -如果我们只关注 FastAPI 应用程序的 **容器镜像**(以及稍后运行的 **容器**),HTTPS 通常会由另一个工具在 **外部** 处理。 +它可以是另一个容器,例如使用 Traefik,处理 **HTTPS** 并**自动**获取**证书**。 -它可以是另一个容器,例如使用 Traefik,处理 **HTTPS** 和 **自动**获取**证书**。 +/// tip | 提示 -/// tip - -Traefik可以与 Docker、Kubernetes 等集成,因此使用它为容器设置和配置 HTTPS 非常容易。 +Traefik 与 Docker、Kubernetes 等都有集成,因此为容器设置和配置 HTTPS 非常容易。 /// -或者,HTTPS 可以由云服务商作为其服务之一进行处理(同时仍在容器中运行应用程序)。 +或者,HTTPS 也可能由云服务商作为其服务之一提供(应用仍运行在容器中)。 -## 在启动和重新启动时运行 +## 启动时运行与重启 { #running-on-startup-and-restarts } -通常还有另一个工具负责**启动和运行**你的容器。 +通常会有另一个工具负责**启动并运行**你的容器。 -它可以直接是**Docker**, 或者**Docker Compose**、**Kubernetes**、**云服务**等。 +它可以是直接的 **Docker**、**Docker Compose**、**Kubernetes**、某个**云服务**等。 -在大多数(或所有)情况下,有一个简单的选项可以在启动时运行容器并在失败时重新启动。 例如,在 Docker 中,它是命令行选项 `--restart`。 +在大多数(或全部)情况下,都有简单选项可以在开机时运行容器并在失败时启用重启。例如,在 Docker 中是命令行选项 `--restart`。 -如果不使用容器,让应用程序在启动时运行并重新启动可能会很麻烦且困难。 但在大多数情况下,当**使用容器**时,默认情况下会包含该功能。 ✨ +如果不使用容器,要让应用开机自启并带重启可能繁琐且困难。但在**容器**场景下,这种功能通常默认就包含了。✨ -## 复制 - 进程数 +## 复制 - 进程数 { #replication-number-of-processes } -如果你有一个 集群, 比如 **Kubernetes**、Docker Swarm、Nomad 或其他类似的复杂系统来管理多台机器上的分布式容器,那么你可能希望在**集群级别**处理复制**,而不是在每个容器中使用**进程管理器**(如带有Worker的 Gunicorn) 。 +如果你有一个由 **Kubernetes**、Docker Swarm Mode、Nomad 或其他类似的复杂系统管理的、在多台机器上运行的分布式容器集群,那么你很可能会希望在**集群层面**来**处理复制**,而不是在每个容器中使用**进程管理**(比如让 Uvicorn 运行多个 workers)。 -像 Kubernetes 这样的分布式容器管理系统通常有一些集成的方法来处理**容器的复制**,同时仍然支持传入请求的**负载均衡**。 全部都在**集群级别**。 +像 Kubernetes 这样的分布式容器管理系统通常都有某种内置方式来处理**容器复制**,同时对传入请求进行**负载均衡**。这一切都在**集群层面**完成。 -在这些情况下,你可能希望从头开始构建一个 **Docker 镜像**,如[上面所解释](#dockerfile)的那样,安装依赖项并运行 **单个 Uvicorn 进程**,而不是运行 Gunicorn 和 Uvicorn workers这种。 +在这些情况下,你可能希望如[上文所述](#dockerfile)那样**从头构建 Docker 镜像**,安装依赖,并仅运行**单个 Uvicorn 进程**,而不是使用多个 Uvicorn workers。 +### 负载均衡器 { #load-balancer } -### 负载均衡器 +使用容器时,通常会有某个组件**监听主端口**。它可能是另一个同时充当 **TLS 终止代理**以处理 **HTTPS** 的容器,或类似工具。 -使用容器时,通常会有一些组件**监听主端口**。 它可能是处理 **HTTPS** 的 **TLS 终止代理** 或一些类似的工具的另一个容器。 +由于该组件会承接请求的**负载**并以(期望)**均衡**的方式在 workers 间分发,它也常被称为**负载均衡器**。 -由于该组件将接受请求的**负载**并(希望)以**平衡**的方式在worker之间分配该请求,因此它通常也称为**负载均衡器**。 +/// tip | 提示 -/// tip - -用于 HTTPS **TLS 终止代理** 的相同组件也可能是 **负载均衡器**。 +用于 HTTPS 的**TLS 终止代理**组件通常也会是**负载均衡器**。 /// -当使用容器时,你用来启动和管理容器的同一系统已经具有内部工具来传输来自该**负载均衡器**(也可以是**TLS 终止代理**) 的**网络通信**(例如HTTP请求)到你的应用程序容器。 +使用容器时,你用来启动和管理容器的系统本身就已有内部工具,将来自该**负载均衡器**(也可能是**TLS 终止代理**)的**网络通信**(例如 HTTP 请求)传递到你的应用容器中。 -### 一个负载均衡器 - 多个worker容器 +### 一个负载均衡器 - 多个 worker 容器 { #one-load-balancer-multiple-worker-containers } -当使用 **Kubernetes** 或类似的分布式容器管理系统时,使用其内部网络机制将允许单个在主 **端口** 上侦听的 **负载均衡器** 将通信(请求)传输到可能的 **多个** 运行你应用程序的容器。 +在 **Kubernetes** 等分布式容器管理系统中,使用其内部网络机制,允许在主**端口**上监听的单个**负载均衡器**将通信(请求)转发给可能**多个**运行你应用的容器。 -运行你的应用程序的每个容器通常**只有一个进程**(例如,运行 FastAPI 应用程序的 Uvicorn 进程)。 它们都是**相同的容器**,运行相同的东西,但每个容器都有自己的进程、内存等。这样你就可以在 CPU 的**不同核心**, 甚至在**不同的机器**充分利用**并行化(parallelization)**。 +这些运行你应用的容器通常每个只有**一个进程**(例如,一个运行 FastAPI 应用的 Uvicorn 进程)。它们都是**相同的容器**,运行相同的东西,但每个都有自己的进程、内存等。这样你就能在 CPU 的**不同核心**,甚至在**不同机器**上利用**并行化**。 -具有**负载均衡器**的分布式容器系统将**将请求轮流分配**给你的应用程序的每个容器。 因此,每个请求都可以由运行你的应用程序的多个**复制容器**之一来处理。 +分布式容器系统配合**负载均衡器**会把请求**轮流分配**到每个应用容器。因此,每个请求都可能由多个**副本容器**之一来处理。 -通常,这个**负载均衡器**能够处理发送到集群中的*其他*应用程序的请求(例如发送到不同的域,或在不同的 URL 路径前缀下),并正确地将该通信传输到在集群中运行的*其他*应用程序的对应容器。 +通常,这个**负载均衡器**还能处理发往集群中*其他*应用的请求(例如不同域名,或不同的 URL 路径前缀),并将通信转发到运行*那个其他*应用的正确容器。 +### 每个容器一个进程 { #one-process-per-container } +在这种场景下,你大概率希望**每个容器只有一个(Uvicorn)进程**,因为你已经在集群层面处理了复制。 +因此,这种情况下你**不希望**在容器内再启多个 workers(例如通过 `--workers` 命令行选项)。你会希望每个容器仅有一个**单独的 Uvicorn 进程**(但可能会有多个容器)。 +在容器内再放一个进程管理器(就像启多个 workers 一样)只会引入**不必要的复杂性**,而这些你很可能已经在集群系统中处理了。 +### 具有多个进程和特殊情况的容器 { #containers-with-multiple-processes-and-special-cases } -### 每个容器一个进程 +当然,也有一些**特殊情况**,你可能希望让**一个容器**里运行多个 **Uvicorn worker 进程**。 -在这种类型的场景中,你可能希望**每个容器有一个(Uvicorn)进程**,因为你已经在集群级别处理复制。 +在这些情况下,你可以使用 `--workers` 命令行选项来设置要运行的 worker 数量: -因此,在这种情况下,你**不会**希望拥有像 Gunicorn 和 Uvicorn worker一样的进程管理器,或者 Uvicorn 使用自己的 Uvicorn worker。 你可能希望每个容器(但可能有多个容器)只有一个**单独的 Uvicorn 进程**。 +```{ .dockerfile .annotate } +FROM python:3.9 -在容器内拥有另一个进程管理器(就像使用 Gunicorn 或 Uvicorn 管理 Uvicorn 工作线程一样)只会增加**不必要的复杂性**,而你很可能已经在集群系统中处理这些复杂性了。 +WORKDIR /code -### 具有多个进程的容器 +COPY ./requirements.txt /code/requirements.txt -当然,在某些**特殊情况**,你可能希望拥有 **一个容器**,其中包含 **Gunicorn 进程管理器**,并在其中启动多个 **Uvicorn worker进程**。 +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -在这些情况下,你可以使用 **官方 Docker 镜像**,其中包含 **Gunicorn** 作为运行多个 **Uvicorn 工作进程** 的进程管理器,以及一些默认设置来根据当前情况调整工作进程数量 自动CPU核心。 我将在下面的 [Gunicorn - Uvicorn 官方 Docker 镜像](#official-docker-image-with-gunicorn-uvicorn) 中告诉你更多相关信息。 +COPY ./app /code/app -下面一些什么时候这种做法有意义的示例: +# (1)! +CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"] +``` +1. 这里我们使用 `--workers` 命令行选项将 worker 数量设置为 4。 -#### 一个简单的应用程序 +以下是这种做法可能合理的一些示例: -如果你的应用程序**足够简单**,你不需要(至少现在不需要)过多地微调进程数量,并且你可以使用自动默认值,那么你可能需要容器中的进程管理器 (使用官方 Docker 镜像),并且你在**单个服务器**而不是集群上运行它。 +#### 一个简单的应用 { #a-simple-app } -#### Docker Compose +如果你的应用**足够简单**,可以在**单台服务器**(不是集群)上运行,你可能会希望在容器内有一个进程管理器。 -你可以使用 **Docker Compose** 部署到**单个服务器**(而不是集群),因此你没有一种简单的方法来管理容器的复制(使用 Docker Compose),同时保留共享网络和 **负载均衡**。 +#### Docker Compose { #docker-compose } -然后,你可能希望拥有一个**单个容器**,其中有一个**进程管理器**,在其中启动**多个worker进程**。 +如果你使用 **Docker Compose** 部署到**单台服务器**(不是集群),那么你不会有一个简单的方法在保留共享网络与**负载均衡**的同时管理容器复制(通过 Docker Compose)。 -#### Prometheus和其他原因 - -你还可能有**其他原因**,这将使你更容易拥有一个带有**多个进程**的**单个容器**,而不是拥有每个容器中都有**单个进程**的**多个容器**。 - -例如(取决于你的设置)你可以在同一个容器中拥有一些工具,例如 Prometheus exporter,该工具应该有权访问**每个请求**。 - -在这种情况下,如果你有**多个容器**,默认情况下,当 Prometheus 来**读取metrics**时,它每次都会获取**单个容器**的metrics(对于处理该特定请求的容器),而不是获取所有复制容器的**累积metrics**。 - -在这种情况, 这种做法会更加简单:让**一个容器**具有**多个进程**,并在同一个容器上使用本地工具(例如 Prometheus exporter)收集所有内部进程的 Prometheus 指标并公开单个容器上的这些指标。 +这种情况下,你可能希望用**单个容器**,由**进程管理器**在容器内启动**多个 worker 进程**。 --- -要点是,这些都**不是**你必须盲目遵循的**一成不变的规则**。 你可以根据这些思路**评估你自己的场景**并决定什么方法是最适合你的的系统,考虑如何管理以下概念: +要点是,这些都**不是**你必须盲目遵循的**铁律**。你可以用这些思路来**评估你自己的场景**,并决定最适合你的系统的方法,看看如何管理以下概念: -* 安全性 - HTTPS +* 安全 - HTTPS * 启动时运行 -* 重新启动 +* 重启 * 复制(运行的进程数) * 内存 -* 开始前的先前步骤 +* 启动前的前置步骤 -## 内存 +## 内存 { #memory } -如果你**每个容器运行一个进程**,那么每个容器所消耗的内存或多或少是定义明确的、稳定的且有限的(如果它们是复制的,则不止一个)。 +如果你**每个容器只运行一个进程**,那么每个容器消耗的内存将更容易定义、较为稳定且有限(如果有复制则为多个容器)。 -然后,你可以在容器管理系统的配置中设置相同的内存限制和要求(例如在 **Kubernetes** 中)。 这样,它将能够在**可用机器**中**复制容器**,同时考虑容器所需的内存量以及集群中机器中的可用内存量。 +接着,你可以在容器管理系统(例如 **Kubernetes**)的配置中设置同样的内存限制与需求。这样它就能在**可用的机器**上**复制容器**,同时考虑容器所需的内存量以及集群中机器可用的内存量。 -如果你的应用程序很**简单**,这可能**不是问题**,并且你可能不需要指定内存限制。 但是,如果你**使用大量内存**(例如使用**机器学习**模型),则应该检查你消耗了多少内存并调整**每台机器**中运行的**容器数量**(也许可以向集群添加更多机器)。 +如果你的应用很**简单**,这可能**不成问题**,你也许不需要设置严格的内存上限。但如果你**使用大量内存**(例如使用**机器学习**模型),你应该检查自己的内存消耗,并调整**每台机器**上运行的**容器数量**(也许还需要为集群增加机器)。 -如果你**每个容器运行多个进程**(例如使用官方 Docker 镜像),你必须确保启动的进程数量不会消耗比可用内存**更多的内存**。 +如果你**每个容器运行多个进程**,你需要确保启动的进程数量不会**消耗超过可用的内存**。 -## 启动之前的步骤和容器 +## 启动前的前置步骤与容器 { #previous-steps-before-starting-and-containers } -如果你使用容器(例如 Docker、Kubernetes),那么你可以使用两种主要方法。 +如果你在使用容器(如 Docker、Kubernetes),你可以采用两种主要方式。 +### 多个容器 { #multiple-containers } -### 多个容器 +如果你有**多个容器**,可能每个容器运行一个**单独进程**(例如在 **Kubernetes** 集群中),那么你可能希望使用一个**单独的容器**来执行**前置步骤**,在一个容器中运行一个进程,**在**启动那些复制的 worker 容器**之前**完成。 -如果你有 **多个容器**,可能每个容器都运行一个 **单个进程**(例如,在 **Kubernetes** 集群中),那么你可能希望有一个 **单独的容器** 执行以下操作: 在单个容器中运行单个进程执行**先前步骤**,即运行复制的worker容器之前。 +/// info | 信息 -/// info - -如果你使用 Kubernetes,这可能是 Init Container。 +如果你使用 Kubernetes,这通常会是一个 Init Container。 /// -如果在你的用例中,运行前面的步骤**并行多次**没有问题(例如,如果你没有运行数据库迁移,而只是检查数据库是否已准备好),那么你也可以将它们放在开始主进程之前在每个容器中。 +如果在你的用例中,**并行多次**运行这些前置步骤没有问题(例如你不是在跑数据库迁移,而只是检查数据库是否就绪),那么你也可以把这些步骤放在每个容器中,在启动主进程之前执行。 -### 单容器 +### 单个容器 { #single-container } -如果你有一个简单的设置,使用一个**单个容器**,然后启动多个**工作进程**(或者也只是一个进程),那么你可以在启动进程之前在应用程序同一个容器中运行先前的步骤。 官方 Docker 镜像内部支持这一点。 +如果你的架构较为简单,使用一个**单个容器**,其后再启动多个**worker 进程**(或者也只有一个进程),那么你可以在同一个容器中,在启动应用进程之前执行这些前置步骤。 -## 带有 Gunicorn 的官方 Docker 镜像 - Uvicorn +### 基础 Docker 镜像 { #base-docker-image } -有一个官方 Docker 镜像,其中包含与 Uvicorn worker一起运行的 Gunicorn,如上一章所述:[服务器工作线程 - Gunicorn 与 Uvicorn](server-workers.md){.internal-link target=_blank}。 +曾经有一个官方的 FastAPI Docker 镜像:tiangolo/uvicorn-gunicorn-fastapi。但它现在已被弃用。⛔️ -该镜像主要在上述情况下有用:[具有多个进程和特殊情况的容器](#containers-with-multiple-processes-and-special-cases)。 +你大概率**不应该**使用这个基础镜像(或任何其它类似的镜像)。 +如果你使用 **Kubernetes**(或其他)并且已经在集群层面设置**复制**、使用多个**容器**,那么在这些情况下,最好如上所述**从头构建镜像**:[为 FastAPI 构建 Docker 镜像](#build-a-docker-image-for-fastapi)。 +如果你需要多个 workers,可以直接使用 `--workers` 命令行选项。 -* tiangolo/uvicorn-gunicorn-fastapi. +/// note | 技术细节 +这个 Docker 镜像创建于 Uvicorn 还不支持管理与重启失效 workers 的时期,那时需要用 Gunicorn 搭配 Uvicorn,这引入了不少复杂度,只是为了让 Gunicorn 管理并重启 Uvicorn 的 worker 进程。 -/// warning - -你很有可能不需要此基础镜像或任何其他类似的镜像,最好从头开始构建镜像,如[上面所述:为 FastAPI 构建 Docker 镜像](#build-a-docker-image-for-fastapi)。 +但现在 Uvicorn(以及 `fastapi` 命令)已经支持使用 `--workers`,因此没有理由不自己构建基础镜像(代码量几乎一样 😅)。 /// -该镜像包含一个**自动调整**机制,用于根据可用的 CPU 核心设置**worker进程数**。 +## 部署容器镜像 { #deploy-the-container-image } -它具有**合理的默认值**,但你仍然可以使用**环境变量**或配置文件更改和更新所有配置。 - -它还支持通过一个脚本运行**开始前的先前步骤** 。 - -/// tip - -要查看所有配置和选项,请转到 Docker 镜像页面: tiangolo/uvicorn-gunicorn-fastapi。 - -/// - -### 官方 Docker 镜像上的进程数 - -此镜像上的**进程数**是根据可用的 CPU **核心**自动计算的。 - -这意味着它将尝试尽可能多地**榨取**CPU 的**性能**。 - -你还可以使用 **环境变量** 等配置来调整它。 - -但这也意味着,由于进程数量取决于容器运行的 CPU,因此**消耗的内存量**也将取决于该数量。 - -因此,如果你的应用程序消耗大量内存(例如机器学习模型),并且你的服务器有很多 CPU 核心**但内存很少**,那么你的容器最终可能会尝试使用比实际情况更多的内存 可用,并且性能会下降很多(甚至崩溃)。 🚨 - -### 创建一个`Dockerfile` - -以下是如何根据此镜像创建`Dockerfile`: - - -```Dockerfile -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 - -COPY ./requirements.txt /app/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt - -COPY ./app /app -``` - -### 更大的应用程序 - -如果你按照有关创建[具有多个文件的更大应用程序](../tutorial/bigger-applications.md){.internal-link target=_blank}的部分进行操作,你的`Dockerfile`可能看起来这样: - -```Dockerfile hl_lines="7" -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 - -COPY ./requirements.txt /app/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt - -COPY ./app /app/app -``` - -### 何时使用 - -如果你使用 **Kubernetes** (或其他)并且你已经在集群级别设置 **复制**,并且具有多个 **容器**。 在这些情况下,你最好按照上面的描述 **从头开始构建镜像**:[为 FastAPI 构建 Docker 镜像](#build-a-docker-image-for-fastapi)。 - -该镜像主要在[具有多个进程的容器和特殊情况](#containers-with-multiple-processes-and-special-cases)中描述的特殊情况下有用。 例如,如果你的应用程序**足够简单**,基于 CPU 设置默认进程数效果很好,你不想在集群级别手动配置复制,并且不会运行更多进程, 或者你使用 **Docker Compose** 进行部署,在单个服务器上运行等。 - -## 部署容器镜像 - -拥有容器(Docker)镜像后,有多种方法可以部署它。 +得到容器(Docker)镜像后,有多种方式可以部署。 例如: -* 在单个服务器中使用 **Docker Compose** +* 在单台服务器上使用 **Docker Compose** * 使用 **Kubernetes** 集群 -* 使用 Docker Swarm 模式集群 -* 使用Nomad等其他工具 -* 使用云服务获取容器镜像并部署它 +* 使用 Docker Swarm Mode 集群 +* 使用 Nomad 等其他工具 +* 使用云服务,接收你的容器镜像并部署 -## Docker 镜像与Poetry +## 使用 `uv` 的 Docker 镜像 { #docker-image-with-uv } -如果你使用 Poetry 来管理项目的依赖项,你可以使用 Docker 多阶段构建: +如果你使用 uv 来安装和管理项目,可以参考他们的 uv Docker 指南。 +## 回顾 { #recap } - -```{ .dockerfile .annotate } -# (1) -FROM python:3.9 as requirements-stage - -# (2) -WORKDIR /tmp - -# (3) -RUN pip install poetry - -# (4) -COPY ./pyproject.toml ./poetry.lock* /tmp/ - -# (5) -RUN poetry export -f requirements.txt --output requirements.txt --without-hashes - -# (6) -FROM python:3.9 - -# (7) -WORKDIR /code - -# (8) -COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt - -# (9) -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -# (10) -COPY ./app /code/app - -# (11) -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] -``` - -1. 这是第一阶段,称为`requirements-stage`。 - -2. 将 `/tmp` 设置为当前工作目录。 - - 这是我们生成文件`requirements.txt`的地方 - -3. 在此阶段安装Poetry。 - -4. 将`pyproject.toml`和`poetry.lock`文件复制到`/tmp`目录。 - - 因为它使用 `./poetry.lock*` (以 `*` 结尾),所以如果该文件尚不可用,它不会崩溃。 - -5. 生成`requirements.txt`文件。 - -6. 这是最后阶段,这里的任何内容都将保留在最终的容器镜像中。 - -7. 将当前工作目录设置为`/code`。 - -8. 将 `requirements.txt` 文件复制到 `/code` 目录。 - - 该文件仅存在于前一个阶段,这就是为什么我们使用 `--from-requirements-stage` 来复制它。 - -9. 安装生成的`requirements.txt`文件中的依赖项。 - -10. 将`app`目录复制到`/code`目录。 - -11. 运行`uvicorn`命令,告诉它使用从`app.main`导入的`app`对象。 - -/// tip - -单击气泡数字可查看每行的作用。 - -/// - -**Docker stage** 是 `Dockerfile` 的一部分,用作 **临时容器镜像**,仅用于生成一些稍后使用的文件。 - -第一阶段仅用于 **安装 Poetry** 并使用 Poetry 的 `pyproject.toml` 文件中的项目依赖项 **生成 `requirements.txt`**。 - -此`requirements.txt`文件将在**下一阶段**与`pip`一起使用。 - -在最终的容器镜像中**仅保留最后阶段**。 之前的阶段将被丢弃。 - -使用 Poetry 时,使用 **Docker 多阶段构建** 是有意义的,因为你实际上并不需要在最终的容器镜像中安装 Poetry 及其依赖项,你 **只需要** 生成用于安装项目依赖项的`requirements.txt`文件。 - -然后,在下一个(也是最后一个)阶段,你将或多或少地以与前面描述的相同的方式构建镜像。 - -### 在TLS 终止代理后面 - Poetry - -同样,如果你在 Nginx 或 Traefik 等 TLS 终止代理(负载均衡器)后面运行容器,请将选项`--proxy-headers`添加到命令中: - - -```Dockerfile -CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] -``` - -## 回顾 - -使用容器系统(例如使用**Docker**和**Kubernetes**),处理所有**部署概念**变得相当简单: +使用容器系统(例如 **Docker** 与 **Kubernetes**)后,处理所有**部署概念**会变得相当直接: * HTTPS * 启动时运行 -* 重新启动 +* 失败重启 * 复制(运行的进程数) * 内存 -* 开始前的先前步骤 +* 启动前的前置步骤 -在大多数情况下,你可能不想使用任何基础镜像,而是基于官方 Python Docker 镜像 **从头开始构建容器镜像** 。 +在大多数情况下,你可能不想使用任何基础镜像,而是基于官方 Python Docker 镜像**从头构建容器镜像**。 -处理好`Dockerfile`和 **Docker 缓存**中指令的**顺序**,你可以**最小化构建时间**,从而最大限度地提高生产力(并避免无聊)。 😎 - -在某些特殊情况下,你可能需要使用 FastAPI 的官方 Docker 镜像。 🤓 +注意 `Dockerfile` 中指令的**顺序**并利用好**Docker 缓存**,可以**最小化构建时间**,以最大化生产力(并避免无聊)。😎 diff --git a/docs/zh/docs/deployment/https.md b/docs/zh/docs/deployment/https.md index d994c4add..4f60b7bca 100644 --- a/docs/zh/docs/deployment/https.md +++ b/docs/zh/docs/deployment/https.md @@ -1,12 +1,12 @@ -# 关于 HTTPS +# 关于 HTTPS { #about-https } 人们很容易认为 HTTPS 仅仅是“启用”或“未启用”的东西。 但实际情况比这复杂得多。 -/// note | 提示 +/// tip | 提示 -如果你很赶时间或不在乎,请继续阅读下一部分,下一部分会提供一个step-by-step的教程,告诉你怎么使用不同技术来把一切都配置好。 +如果你很赶时间或不在乎,请继续阅读后续章节,它们会提供逐步的教程,告诉你怎么使用不同技术把一切都配置好。 /// @@ -15,24 +15,24 @@ 现在,从**开发人员的视角**,在了解 HTTPS 时需要记住以下几点: * 要使用 HTTPS,**服务器**需要拥有由**第三方**生成的**"证书(certificate)"**。 - * 这些证书实际上是从第三方**获取**的,而不是“生成”的。 + * 这些证书实际上是从第三方**获取**的,而不是“生成”的。 * 证书有**生命周期**。 - * 它们会**过期**。 - * 然后它们需要**更新**,**再次从第三方获取**。 + * 它们会**过期**。 + * 然后它们需要**更新**,**再次从第三方获取**。 * 连接的加密发生在 **TCP 层**。 - * 这是 HTTP 协议**下面的一层**。 - * 因此,**证书和加密**处理是在 **HTTP之前**完成的。 + * 这是 HTTP 协议**下面的一层**。 + * 因此,**证书和加密**处理是在 **HTTP之前**完成的。 * **TCP 不知道域名**。 仅仅知道 IP 地址。 - * 有关所请求的 **特定域名** 的信息位于 **HTTP 数据**中。 + * 有关所请求的 **特定域名** 的信息位于 **HTTP 数据**中。 * **HTTPS 证书**“证明”**某个域名**,但协议和加密发生在 TCP 层,在知道正在处理哪个域名**之前**。 * **默认情况下**,这意味着你**每个 IP 地址只能拥有一个 HTTPS 证书**。 - * 无论你的服务器有多大,或者服务器上的每个应用程序有多小。 - * 不过,对此有一个**解决方案**。 -* **TLS** 协议(在 HTTP 之下的TCP 层处理加密的协议)有一个**扩展**,称为 **SNI**。 - * SNI 扩展允许一台服务器(具有 **单个 IP 地址**)拥有 **多个 HTTPS 证书** 并提供 **多个 HTTPS 域名/应用程序**。 - * 为此,服务器上会有**单独**的一个组件(程序)侦听**公共 IP 地址**,这个组件必须拥有服务器中的**所有 HTTPS 证书**。 + * 无论你的服务器有多大,或者服务器上的每个应用程序有多小。 + * 不过,对此有一个**解决方案**。 +* **TLS** 协议(在 HTTP 之下的 TCP 层处理加密的协议)有一个**扩展**,称为 **SNI**。 + * SNI 扩展允许一台服务器(具有 **单个 IP 地址**)拥有 **多个 HTTPS 证书** 并提供 **多个 HTTPS 域名/应用程序**。 + * 为此,服务器上会有**单独**的一个组件(程序)侦听**公共 IP 地址**,这个组件必须拥有服务器中的**所有 HTTPS 证书**。 * **获得安全连接后**,通信协议**仍然是HTTP**。 - * 内容是 **加密过的**,即使它们是通过 **HTTP 协议** 发送的。 + * 内容是 **加密过的**,即使它们是通过 **HTTP 协议** 发送的。 通常的做法是在服务器上运行**一个程序/HTTP 服务器**并**管理所有 HTTPS 部分**:接收**加密的 HTTPS 请求**, 将 **解密的 HTTP 请求** 发送到在同一服务器中运行的实际 HTTP 应用程序(在本例中为 **FastAPI** 应用程序),从应用程序中获取 **HTTP 响应**, 使用适当的 **HTTPS 证书**对其进行加密并使用 **HTTPS** 将其发送回客户端。 此服务器通常被称为 **TLS 终止代理(TLS Termination Proxy)**。 @@ -43,7 +43,7 @@ * Nginx * HAProxy -## Let's Encrypt +## Let's Encrypt { #lets-encrypt } 在 Let's Encrypt 之前,这些 **HTTPS 证书** 由受信任的第三方出售。 @@ -57,16 +57,15 @@ 我们的想法是自动获取和更新这些证书,以便你可以永远免费拥有**安全的 HTTPS**。 -## 面向开发人员的 HTTPS +## 面向开发人员的 HTTPS { #https-for-developers } 这里有一个 HTTPS API 看起来是什么样的示例,我们会分步说明,并且主要关注对开发人员重要的部分。 - -### 域名 +### 域名 { #domain-name } 第一步我们要先**获取**一些**域名(Domain Name)**。 然后可以在 DNS 服务器(可能是你的同一家云服务商提供的)中配置它。 -你可能拥有一个云服务器(虚拟机)或类似的东西,并且它会有一个固定 **公共IP地址**。 +你可能拥有一个云服务器(虚拟机)或类似的东西,并且它会有一个固定 **公共IP地址**。 在 DNS 服务器中,你可以配置一条记录(“A 记录”)以将 **你的域名** 指向你服务器的公共 **IP 地址**。 @@ -78,7 +77,7 @@ /// -### DNS +### DNS { #dns } 现在让我们关注真正的 HTTPS 部分。 @@ -88,7 +87,7 @@ DNS 服务器会告诉浏览器使用某个特定的 **IP 地址**。 这将是 -### TLS 握手开始 +### TLS 握手开始 { #tls-handshake-start } 然后,浏览器将在**端口 443**(HTTPS 端口)上与该 IP 地址进行通信。 @@ -98,7 +97,7 @@ DNS 服务器会告诉浏览器使用某个特定的 **IP 地址**。 这将是 客户端和服务器之间建立 TLS 连接的过程称为 **TLS 握手**。 -### 带有 SNI 扩展的 TLS +### 带有 SNI 扩展的 TLS { #tls-with-sni-extension } **服务器中只有一个进程**可以侦听特定 **IP 地址**的特定 **端口**。 可能有其他进程在同一 IP 地址的其他端口上侦听,但每个 IP 地址和端口组合只有一个进程。 @@ -128,7 +127,7 @@ TLS 终止代理可以访问一个或多个 **TLS 证书**(HTTPS 证书)。 /// -### HTTPS 请求 +### HTTPS 请求 { #https-request } 现在客户端和服务器(特别是浏览器和 TLS 终止代理)具有 **加密的 TCP 连接**,它们可以开始 **HTTP 通信**。 @@ -136,19 +135,19 @@ TLS 终止代理可以访问一个或多个 **TLS 证书**(HTTPS 证书)。 -### 解密请求 +### 解密请求 { #decrypt-the-request } TLS 终止代理将使用协商好的加密算法**解密请求**,并将**(解密的)HTTP 请求**传输到运行应用程序的进程(例如运行 FastAPI 应用的 Uvicorn 进程)。 -### HTTP 响应 +### HTTP 响应 { #http-response } 应用程序将处理请求并向 TLS 终止代理发送**(未加密)HTTP 响应**。 -### HTTPS 响应 +### HTTPS 响应 { #https-response } 然后,TLS 终止代理将使用之前协商的加密算法(以`someapp.example.com`的证书开头)对响应进行加密,并将其发送回浏览器。 @@ -158,7 +157,7 @@ TLS 终止代理将使用协商好的加密算法**解密请求**,并将**( 客户端(浏览器)将知道响应来自正确的服务器,因为它使用了他们之前使用 **HTTPS 证书** 协商出的加密算法。 -### 多个应用程序 +### 多个应用程序 { #multiple-applications } 在同一台(或多台)服务器中,可能存在**多个应用程序**,例如其他 API 程序或数据库。 @@ -168,7 +167,7 @@ TLS 终止代理将使用协商好的加密算法**解密请求**,并将**( 这样,TLS 终止代理就可以为多个应用程序处理**多个域名**的 HTTPS 和证书,然后在每种情况下将请求传输到正确的应用程序。 -### 证书更新 +### 证书更新 { #certificate-renewal } 在未来的某个时候,每个证书都会**过期**(大约在获得证书后 3 个月)。 @@ -183,16 +182,48 @@ TLS 终止代理将使用协商好的加密算法**解密请求**,并将**( 有多种方法可以做到这一点。 一些流行的方式是: * **修改一些DNS记录**。 - * 为此,续订程序需要支持 DNS 提供商的 API,因此,要看你使用的 DNS 提供商是否提供这一功能。 + * 为此,续订程序需要支持 DNS 提供商的 API,因此,要看你使用的 DNS 提供商是否提供这一功能。 * **在与域名关联的公共 IP 地址上作为服务器运行**(至少在证书获取过程中)。 - * 正如我们上面所说,只有一个进程可以监听特定的 IP 和端口。 - * 这就是当同一个 TLS 终止代理还负责证书续订过程时它非常有用的原因之一。 - * 否则,你可能需要暂时停止 TLS 终止代理,启动续订程序以获取证书,然后使用 TLS 终止代理配置它们,然后重新启动 TLS 终止代理。 这并不理想,因为你的应用程序在 TLS 终止代理关闭期间将不可用。 + * 正如我们上面所说,只有一个进程可以监听特定的 IP 和端口。 + * 这就是当同一个 TLS 终止代理还负责证书续订过程时它非常有用的原因之一。 + * 否则,你可能需要暂时停止 TLS 终止代理,启动续订程序以获取证书,然后使用 TLS 终止代理配置它们,然后重新启动 TLS 终止代理。 这并不理想,因为你的应用程序在 TLS 终止代理关闭期间将不可用。 通过拥有一个**单独的系统来使用 TLS 终止代理来处理 HTTPS**, 而不是直接将 TLS 证书与应用程序服务器一起使用 (例如 Uvicorn),你可以在 更新证书的过程中同时保持提供服务。 -## 回顾 +## 代理转发请求头 { #proxy-forwarded-headers } + +当使用代理来处理 HTTPS 时,你的**应用服务器**(例如通过 FastAPI CLI 运行的 Uvicorn)对 HTTPS 过程并不了解,它只通过纯 HTTP 与 **TLS 终止代理**通信。 + +这个**代理**通常会在将请求转发给**应用服务器**之前,临时设置一些 HTTP 请求头,以便让应用服务器知道该请求是由代理**转发**过来的。 + +/// note | 技术细节 + +这些代理请求头包括: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +不过,由于**应用服务器**并不知道自己位于受信任的**代理**之后,默认情况下,它不会信任这些请求头。 + +但你可以配置**应用服务器**去信任由**代理**发送的这些“转发”请求头。如果你在使用 FastAPI CLI,可以使用命令行选项 `--forwarded-allow-ips` 指定它应该信任哪些 IP 发来的这些“转发”请求头。 + +例如,如果**应用服务器**只接收来自受信任**代理**的通信,你可以设置 `--forwarded-allow-ips="*"`,让它信任所有传入的 IP,因为它只会接收来自**代理**所使用 IP 的请求。 + +这样,应用就能知道自己的公共 URL、是否使用 HTTPS、域名等信息。 + +这在需要正确处理重定向等场景时很有用。 + +/// tip + +你可以在文档中了解更多:[在代理之后 - 启用代理转发请求头](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} + +/// + +## 回顾 { #recap } 拥有**HTTPS** 非常重要,并且在大多数情况下相当**关键**。 作为开发人员,你围绕 HTTPS 所做的大部分努力就是**理解这些概念**以及它们的工作原理。 diff --git a/docs/zh/docs/deployment/index.md b/docs/zh/docs/deployment/index.md index 1ec0c5c5b..47dcede65 100644 --- a/docs/zh/docs/deployment/index.md +++ b/docs/zh/docs/deployment/index.md @@ -1,21 +1,23 @@ -# 部署 +# 部署 { #deployment } 部署 **FastAPI** 应用程序相对容易。 -## 部署是什么意思 +## 部署是什么意思 { #what-does-deployment-mean } **部署**应用程序意味着执行必要的步骤以使其**可供用户使用**。 -对于**Web API**来说,通常涉及将上传到**云服务器**中,搭配一个性能和稳定性都不错的**服务器程序**,以便你的**用户**可以高效地**访问**你的应用程序,而不会出现中断或其他问题。 +对于**Web API**来说,通常涉及将其放到一台**远程机器**中,搭配一个性能和稳定性都不错的**服务器程序**,以便你的**用户**可以高效地**访问**你的应用程序,而不会出现中断或其他问题。 -这与**开发**阶段形成鲜明对比,在**开发**阶段,你不断更改代码、破坏代码、修复代码, 来回停止和重启服务器等。 +这与**开发**阶段形成鲜明对比,在**开发**阶段,你不断更改代码、破坏代码、修复代码,来回停止和重启开发服务器等。 -## 部署策略 +## 部署策略 { #deployment-strategies } 根据你的使用场景和使用的工具,有多种方法可以实现此目的。 你可以使用一些工具自行**部署服务器**,你也可以使用能为你完成部分工作的**云服务**,或其他可能的选项。 +例如,我们(FastAPI 团队)构建了 **FastAPI Cloud**,让将 FastAPI 应用部署到云端尽可能流畅,并且保持与使用 FastAPI 开发时相同的开发者体验。 + 我将向你展示在部署 **FastAPI** 应用程序时你可能应该记住的一些主要概念(尽管其中大部分适用于任何其他类型的 Web 应用程序)。 在接下来的部分中,你将看到更多需要记住的细节以及一些技巧。 ✨ diff --git a/docs/zh/docs/deployment/manually.md b/docs/zh/docs/deployment/manually.md index 2c2784a64..6f2ad27b2 100644 --- a/docs/zh/docs/deployment/manually.md +++ b/docs/zh/docs/deployment/manually.md @@ -1,6 +1,6 @@ -# 手动运行服务器 +# 手动运行服务器 { #run-a-server-manually } -## 使用 `fastapi run` 命令 +## 使用 `fastapi run` 命令 { #use-the-fastapi-run-command } 简而言之,使用 `fastapi run` 来运行您的 FastAPI 应用程序: @@ -42,11 +42,11 @@ $ fastapi run ASGI。FastAPI 本质上是一个 ASGI Web 框架。 +FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称为 ASGI。FastAPI 本质上是一个 ASGI Web 框架。 要在远程服务器上运行 **FastAPI** 应用(或任何其他 ASGI 应用),您需要一个 ASGI 服务器程序,例如 **Uvicorn**。它是 `fastapi` 命令默认使用的 ASGI 服务器。 @@ -58,7 +58,7 @@ FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称 * Granian:基于 Rust 的 HTTP 服务器,专为 Python 应用设计。 * NGINX Unit:NGINX Unit 是一个轻量级且灵活的 Web 应用运行时环境。 -## 服务器主机和服务器程序 +## 服务器主机和服务器程序 { #server-machine-and-server-program } 关于名称,有一个小细节需要记住。 💡 @@ -68,8 +68,7 @@ FastAPI 使用了一种用于构建 Python Web 框架和服务器的标准,称 当提到远程主机时,通常将其称为**服务器**,但也称为**机器**(machine)、**VM**(虚拟机)、**节点**。 这些都是指某种类型的远程计算机,通常运行 Linux,您可以在其中运行程序。 - -## 安装服务器程序 +## 安装服务器程序 { #install-the-server-program } 当您安装 FastAPI 时,它自带一个生产环境服务器——Uvicorn,并且您可以使用 `fastapi run` 命令来启动它。 @@ -101,7 +100,7 @@ $ pip install "uvicorn[standard]" /// -## 运行服务器程序 +## 运行服务器程序 { #run-the-server-program } 如果您手动安装了 ASGI 服务器,通常需要以特定格式传递一个导入字符串,以便服务器能够正确导入您的 FastAPI 应用: @@ -142,7 +141,7 @@ Uvicorn 和其他服务器支持 `--reload` 选项,该选项在开发过程中 /// -## 部署概念 +## 部署概念 { #deployment-concepts } 这些示例运行服务器程序(例如 Uvicorn),启动**单个进程**,在所有 IP(`0.0.0.0`)上监听预定义端口(例如`80`)。 diff --git a/docs/zh/docs/deployment/server-workers.md b/docs/zh/docs/deployment/server-workers.md index e46ba7a09..2bbd5d9b6 100644 --- a/docs/zh/docs/deployment/server-workers.md +++ b/docs/zh/docs/deployment/server-workers.md @@ -1,4 +1,4 @@ -# 服务器工作进程(Workers) - 使用 Uvicorn 的多工作进程模式 +# 服务器工作进程(Workers) - 使用 Uvicorn 的多工作进程模式 { #server-workers-uvicorn-with-workers } 让我们回顾一下之前的部署概念: @@ -17,7 +17,7 @@ 在本章节中,我将向您展示如何使用 `fastapi` 命令或直接使用 `uvicorn` 命令以**多工作进程模式**运行 **Uvicorn**。 -/// info +/// info | 信息 如果您正在使用容器,例如 Docker 或 Kubernetes,我将在下一章中告诉您更多相关信息:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。 @@ -25,7 +25,7 @@ /// -## 多个工作进程 +## 多个工作进程 { #multiple-workers } 您可以使用 `--workers` 命令行选项来启动多个工作进程: @@ -111,7 +111,7 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 您还可以看到它显示了每个进程的 **PID**,父进程(这是**进程管理器**)的 PID 为`27365`,每个工作进程的 PID 为:`27368`、`27369`, `27370`和`27367`。 -## 部署概念 +## 部署概念 { #deployment-concepts } 在这里,您学习了如何使用多个**工作进程(workers)**来让应用程序的执行**并行化**,充分利用 CPU 的**多核性能**,并能够处理**更多的请求**。 @@ -124,13 +124,13 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 * **内存** * **启动之前的先前步骤** -## 容器和 Docker +## 容器和 Docker { #containers-and-docker } 在关于 [容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank} 的下一章中,我将介绍一些可用于处理其他**部署概念**的策略。 我将向您展示如何**从零开始构建自己的镜像**,以运行一个单独的 Uvicorn 进程。这个过程相对简单,并且在使用 **Kubernetes** 等分布式容器管理系统时,这通常是您需要采取的方法。 -## 回顾 +## 回顾 { #recap } 您可以在使用 `fastapi` 或 `uvicorn` 命令时,通过 `--workers` CLI 选项启用多个工作进程(workers),以充分利用**多核 CPU**,以**并行运行多个进程**。 diff --git a/docs/zh/docs/deployment/versions.md b/docs/zh/docs/deployment/versions.md index 228bb0765..23c37f3b5 100644 --- a/docs/zh/docs/deployment/versions.md +++ b/docs/zh/docs/deployment/versions.md @@ -1,4 +1,4 @@ -# 关于 FastAPI 版本 +# 关于 FastAPI 版本 { #about-fastapi-versions } **FastAPI** 已在许多应用程序和系统的生产环境中使用。 并且测试覆盖率保持在100%。 但其开发进度仍在快速推进。 @@ -8,41 +8,41 @@ 你现在就可以使用 **FastAPI** 创建生产环境应用程序(你可能已经这样做了一段时间),你只需确保使用的版本可以与其余代码正确配合即可。 -## 固定你的 `fastapi` 版本 +## 固定你的 `fastapi` 版本 { #pin-your-fastapi-version } 你应该做的第一件事是将你正在使用的 **FastAPI** 版本“固定”到你知道适用于你的应用程序的特定最新版本。 -例如,假设你在应用程序中使用版本`0.45.0`。 +例如,假设你在应用程序中使用版本`0.112.0`。 如果你使用`requirements.txt`文件,你可以使用以下命令指定版本: -````txt -fastapi==0.45.0 -```` +```txt +fastapi[standard]==0.112.0 +``` -这意味着你将使用版本`0.45.0`。 +这意味着你将使用版本`0.112.0`。 或者你也可以将其固定为: -````txt -fastapi>=0.45.0,<0.46.0 -```` +```txt +fastapi[standard]>=0.112.0,<0.113.0 +``` -这意味着你将使用`0.45.0`或更高版本,但低于`0.46.0`,例如,版本`0.45.2`仍会被接受。 +这意味着你将使用`0.112.0`或更高版本,但低于`0.113.0`,例如,版本`0.112.2`仍会被接受。 -如果你使用任何其他工具来管理你的安装,例如 Poetry、Pipenv 或其他工具,它们都有一种定义包的特定版本的方法。 +如果你使用任何其他工具来管理你的安装,例如 `uv`、Poetry、Pipenv 或其他工具,它们都有一种定义包的特定版本的方法。 -## 可用版本 +## 可用版本 { #available-versions } 你可以在[发行说明](../release-notes.md){.internal-link target=_blank}中查看可用版本(例如查看当前最新版本)。 -## 关于版本 +## 关于版本 { #about-versions } 遵循语义版本控制约定,任何低于`1.0.0`的版本都可能会添加 breaking changes。 -FastAPI 还遵循这样的约定:任何`PATCH`版本更改都是为了bug修复和non-breaking changes。 +FastAPI 还遵循这样的约定:任何"PATCH"版本更改都是为了bug修复和non-breaking changes。 -/// tip +/// tip | 提示 "PATCH"是最后一个数字,例如,在`0.2.3`中,PATCH版本是`3`。 @@ -56,13 +56,13 @@ fastapi>=0.45.0,<0.46.0 "MINOR"版本中会添加breaking changes和新功能。 -/// tip +/// tip | 提示 "MINOR"是中间的数字,例如,在`0.2.3`中,MINOR版本是`2`。 /// -## 升级FastAPI版本 +## 升级FastAPI版本 { #upgrading-the-fastapi-versions } 你应该为你的应用程序添加测试。 @@ -72,7 +72,7 @@ fastapi>=0.45.0,<0.46.0 如果一切正常,或者在进行必要的更改之后,并且所有测试都通过了,那么你可以将`fastapi`固定到新的版本。 -## 关于Starlette +## 关于Starlette { #about-starlette } 你不应该固定`starlette`的版本。 @@ -80,14 +80,14 @@ fastapi>=0.45.0,<0.46.0 因此,**FastAPI** 自己可以使用正确的 Starlette 版本。 -## 关于 Pydantic +## 关于 Pydantic { #about-pydantic } Pydantic 包含针对 **FastAPI** 的测试及其自己的测试,因此 Pydantic 的新版本(`1.0.0`以上)始终与 FastAPI 兼容。 -你可以将 Pydantic 固定到适合你的`1.0.0`以上和`2.0.0`以下的任何版本。 +你可以将 Pydantic 固定到任何高于 `1.0.0` 且适合你的版本。 例如: -````txt -pydantic>=1.2.0,<2.0.0 -```` +```txt +pydantic>=2.7.0,<3.0.0 +``` diff --git a/docs/zh/docs/environment-variables.md b/docs/zh/docs/environment-variables.md index 812278051..8729a6306 100644 --- a/docs/zh/docs/environment-variables.md +++ b/docs/zh/docs/environment-variables.md @@ -1,6 +1,6 @@ -# 环境变量 +# 环境变量 { #environment-variables } -/// tip +/// tip | 提示 如果你已经知道什么是“环境变量”并且知道如何使用它们,你可以放心跳过这一部分。 @@ -10,7 +10,7 @@ 环境变量对于处理应用程序**设置**、作为 Python **安装**的一部分等方面非常有用。 -## 创建和使用环境变量 +## 创建和使用环境变量 { #create-and-use-env-vars } 你在 **shell(终端)**中就可以**创建**和使用环境变量,并不需要用到 Python: @@ -50,7 +50,7 @@ Hello Wade Wilson //// -## 在 Python 中读取环境变量 +## 在 Python 中读取环境变量 { #read-env-vars-in-python } 你也可以在 Python **之外**的终端中创建环境变量(或使用任何其他方法),然后在 Python 中**读取**它们。 @@ -63,9 +63,9 @@ name = os.getenv("MY_NAME", "World") print(f"Hello {name} from Python") ``` -/// tip +/// tip | 提示 -第二个参数是 `os.getenv()` 的默认返回值。 +第二个参数是 `os.getenv()` 的默认返回值。 如果没有提供,默认值为 `None`,这里我们提供 `"World"` 作为默认值。 @@ -151,21 +151,21 @@ Hello World from Python -/// tip +/// tip | 提示 -你可以在 The Twelve-Factor App: 配置中了解更多信息。 +你可以在 The Twelve-Factor App: 配置中了解更多信息。 /// -## 类型和验证 +## 类型和验证 { #types-and-validation } 这些环境变量只能处理**文本字符串**,因为它们是处于 Python 范畴之外的,必须与其他程序和操作系统的其余部分兼容(甚至与不同的操作系统兼容,如 Linux、Windows、macOS)。 这意味着从环境变量中读取的**任何值**在 Python 中都将是一个 `str`,任何类型转换或验证都必须在代码中完成。 -你将在[高级用户指南 - 设置和环境变量](./advanced/settings.md)中了解更多关于使用环境变量处理**应用程序设置**的信息。 +你将在[高级用户指南 - 设置和环境变量](./advanced/settings.md){.internal-link target=_blank}中了解更多关于使用环境变量处理**应用程序设置**的信息。 -## `PATH` 环境变量 +## `PATH` 环境变量 { #path-environment-variable } 有一个**特殊的**环境变量称为 **`PATH`**,操作系统(Linux、macOS、Windows)用它来查找要运行的程序。 @@ -209,7 +209,7 @@ C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System3 如果找到了,那么操作系统将**使用它**;否则,操作系统会继续在**其他目录**中查找。 -### 安装 Python 和更新 `PATH` +### 安装 Python 和更新 `PATH` { #installing-python-and-updating-the-path } 安装 Python 时,可能会询问你是否要更新 `PATH` 环境变量。 @@ -233,7 +233,7 @@ C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System3 假设你安装 Python 并最终将其安装在了目录 `C:\opt\custompython\bin` 中。 -如果你同意更新 `PATH` 环境变量 (在 Python 安装程序中,这个操作是名为 `Add Python x.xx to PATH` 的复选框 —— 译者注),那么安装程序将会将 `C:\opt\custompython\bin` 添加到 `PATH` 环境变量中。 +如果你同意更新 `PATH` 环境变量,那么安装程序将会将 `C:\opt\custompython\bin` 添加到 `PATH` 环境变量中。 ```plaintext C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin @@ -285,13 +285,13 @@ $ C:\opt\custompython\bin\python //// -当学习[虚拟环境](virtual-environments.md)时,这些信息将会很有用。 +当学习[虚拟环境](virtual-environments.md){.internal-link target=_blank}时,这些信息将会很有用。 -## 结论 +## 结论 { #conclusion } 通过这个教程,你应该对**环境变量**是什么以及如何在 Python 中使用它们有了基本的了解。 -你也可以在环境变量 - 维基百科 (Wikipedia for Environment Variable) 中了解更多关于它们的信息。 +你也可以在环境变量 - 维基百科中了解更多关于它们的信息。 在许多情况下,环境变量的用途和适用性并不是很明显。但是在开发过程中,它们会在许多不同的场景中出现,因此了解它们是很有必要的。 diff --git a/docs/zh/docs/fastapi-cli.md b/docs/zh/docs/fastapi-cli.md index 3b67eb664..4d3b51a57 100644 --- a/docs/zh/docs/fastapi-cli.md +++ b/docs/zh/docs/fastapi-cli.md @@ -1,8 +1,8 @@ -# FastAPI CLI +# FastAPI CLI { #fastapi-cli } **FastAPI CLI** 是一个命令行程序,你可以用它来部署和运行你的 FastAPI 应用程序,管理你的 FastAPI 项目,等等。 -当你安装 FastAPI 时(例如使用 `pip install FastAPI` 命令),会包含一个名为 `fastapi-cli` 的软件包,该软件包在终端中提供 `fastapi` 命令。 +当你安装 FastAPI 时(例如使用 `pip install "fastapi[standard]"`),会包含一个名为 `fastapi-cli` 的软件包,该软件包在终端中提供 `fastapi` 命令。 要在开发环境中运行你的 FastAPI 应用,你可以使用 `fastapi dev` 命令: @@ -48,32 +48,28 @@ $ fastapi dev Uvicorn,这是一个高性能、适用于生产环境的 ASGI 服务器。😎 -## `fastapi dev` +## `fastapi dev` { #fastapi-dev } 当你运行 `fastapi dev` 时,它将以开发模式运行。 -默认情况下,它会启用**自动重载**,因此当你更改代码时,它会自动重新加载服务器。该功能是资源密集型的,且相较不启用时更不稳定,因此你应该仅在开发环境下使用它。 +默认情况下,它会启用**自动重载**,因此当你更改代码时,它会自动重新加载服务器。该功能是资源密集型的,且相较不启用时更不稳定,因此你应该仅在开发环境下使用它。它还会监听 IP 地址 `127.0.0.1`,这是你的机器仅与自身通信的 IP(`localhost`)。 -默认情况下,它将监听 IP 地址 `127.0.0.1`,这是你的机器与自身通信的 IP 地址(`localhost`)。 - -## `fastapi run` +## `fastapi run` { #fastapi-run } 当你运行 `fastapi run` 时,它默认以生产环境模式运行。 -默认情况下,**自动重载是禁用的**。 - -它将监听 IP 地址 `0.0.0.0`,即所有可用的 IP 地址,这样任何能够与该机器通信的人都可以公开访问它。这通常是你在生产环境中运行它的方式,例如在容器中运行。 +默认情况下,**自动重载是禁用的**。它将监听 IP 地址 `0.0.0.0`,即所有可用的 IP 地址,这样任何能够与该机器通信的人都可以公开访问它。这通常是你在生产环境中运行它的方式,例如在容器中运行。 在大多数情况下,你会(且应该)有一个“终止代理”在上层为你处理 HTTPS,这取决于你如何部署应用程序,你的服务提供商可能会为你处理此事,或者你可能需要自己设置。 /// tip | 提示 -你可以在 [deployment documentation](deployment/index.md){.internal-link target=_blank} 获得更多信息。 +你可以在[部署文档](deployment/index.md){.internal-link target=_blank}中了解更多。 /// diff --git a/docs/zh/docs/features.md b/docs/zh/docs/features.md index eaf8daff7..7d7aa19c0 100644 --- a/docs/zh/docs/features.md +++ b/docs/zh/docs/features.md @@ -1,22 +1,21 @@ -# 特性 +# 特性 { #features } -## FastAPI 特性 +## FastAPI 特性 { #fastapi-features } **FastAPI** 提供了以下内容: -### 基于开放标准 +### 基于开放标准 { #based-on-open-standards } - -* 用于创建 API 的 OpenAPI 包含了路径操作,请求参数,请求体,安全性等的声明。 -* 使用 JSON Schema (因为 OpenAPI 本身就是基于 JSON Schema 的)自动生成数据模型文档。 +* 用于创建 API 的 OpenAPI,包含对路径 操作、参数、请求体、安全等的声明。 +* 使用 JSON Schema 自动生成数据模型文档(因为 OpenAPI 本身就是基于 JSON Schema 的)。 * 经过了缜密的研究后围绕这些标准而设计。并非狗尾续貂。 * 这也允许了在很多语言中自动**生成客户端代码**。 -### 自动生成文档 +### 自动生成文档 { #automatic-docs } 交互式 API 文档以及具探索性 web 界面。因为该框架是基于 OpenAPI,所以有很多可选项,FastAPI 默认自带两个交互式 API 文档。 -* Swagger UI,可交互式操作,能在浏览器中直接调用和测试你的 API 。 +* Swagger UI,可交互式操作,能在浏览器中直接调用和测试你的 API。 ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) @@ -24,11 +23,11 @@ ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### 更主流的 Python +### 更主流的 Python { #just-modern-python } -全部都基于标准的 **Python 3.6 类型**声明(感谢 Pydantic )。没有新的语法需要学习。只需要标准的 Python 。 +全部都基于标准的 **Python 类型** 声明(感谢 Pydantic)。没有新的语法需要学习。只需要标准的现代 Python。 -如果你需要2分钟来学习如何使用 Python 类型(即使你不使用 FastAPI ),看看这个简短的教程:[Python Types](python-types.md){.internal-link target=_blank}。 +如果你需要2分钟来学习如何使用 Python 类型(即使你不使用 FastAPI),看看这个简短的教程:[Python Types](python-types.md){.internal-link target=_blank}。 编写带有类型标注的标准 Python: @@ -37,13 +36,13 @@ from datetime import date from pydantic import BaseModel -# Declare a variable as a str -# and get editor support inside the function +# 将变量声明为 str +# 并在函数内获得编辑器支持 def main(user_id: str): return user_id -# A Pydantic model +# 一个 Pydantic 模型 class User(BaseModel): id: int name: str @@ -65,19 +64,19 @@ my_second_user: User = User(**second_user_data) ``` -/// info +/// info | 信息 -`**second_user_data` 意思是: +`**second_user_data` 意思是: -直接将`second_user_data`字典的键和值直接作为key-value参数传递,等同于:`User(id=4, name="Mary", joined="2018-11-30")` +直接将 `second_user_data` 字典的键和值作为 key-value 参数传入,等同于:`User(id=4, name="Mary", joined="2018-11-30")` /// -### 编辑器支持 +### 编辑器支持 { #editor-support } 整个框架都被设计得易于使用且直观,所有的决定都在开发之前就在多个编辑器上进行了测试,来确保最佳的开发体验。 -在最近的 Python 开发者调查中,我们能看到 被使用最多的功能是"自动补全"。 +在最近的 Python 开发者调查中,我们能看到 被使用最多的功能是“自动补全”。 整个 **FastAPI** 框架就是基于这一点的。任何地方都可以进行自动补全。 @@ -85,62 +84,58 @@ my_second_user: User = User(**second_user_data) 在这里,你的编辑器可能会这样帮助你: -* Visual Studio Code 中: +* 在 Visual Studio Code 中: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -* PyCharm 中: +* 在 PyCharm 中: ![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) 你将能进行代码补全,这是在之前你可能曾认为不可能的事。例如,在来自请求 JSON 体(可能是嵌套的)中的键 `price`。 -不会再输错键名,来回翻看文档,或者来回滚动寻找你最后使用的 `username` 或者 `user_name` 。 +不会再输错键名,来回翻看文档,或者来回滚动寻找你最后使用的 `username` 或者 `user_name`。 - - -### 简洁 +### 简洁 { #short } 任何类型都有合理的**默认值**,任何和地方都有可选配置。所有的参数被微调,来满足你的需求,定义成你需要的 API。 但是默认情况下,一切都能**“顺利工作”**。 -### 验证 +### 验证 { #validation } * 校验大部分(甚至所有?)的 Python **数据类型**,包括: - * JSON 对象 (`dict`). + * JSON 对象 (`dict`)。 * JSON 数组 (`list`) 定义成员类型。 - * 字符串 (`str`) 字段, 定义最小或最大长度。 - * 数字 (`int`, `float`) 有最大值和最小值, 等等。 + * 字符串 (`str`) 字段,定义最小或最大长度。 + * 数字 (`int`, `float`) 有最大值和最小值,等等。 -* 校验外来类型, 比如: - * URL. - * Email. - * UUID. - * ...及其他. +* 校验外来类型,比如: + * URL。 + * Email。 + * UUID。 + * ...及其他。 所有的校验都由完善且强大的 **Pydantic** 处理。 -### 安全性及身份验证 +### 安全性及身份验证 { #security-and-authentication } 集成了安全性和身份认证。杜绝数据库或者数据模型的渗透风险。 OpenAPI 中定义的安全模式,包括: * HTTP 基本认证。 -* **OAuth2** (也使用 **JWT tokens**)。在 [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}查看教程。 +* **OAuth2**(也使用 **JWT tokens**)。在 [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}查看教程。 * API 密钥,在: * 请求头。 * 查询参数。 - * Cookies, 等等。 + * Cookies,等等。 加上来自 Starlette(包括 **session cookie**)的所有安全特性。 所有的这些都是可复用的工具和组件,可以轻松与你的系统,数据仓库,关系型以及 NoSQL 数据库等等集成。 - - -### 依赖注入 +### 依赖注入 { #dependency-injection } FastAPI 有一个使用非常简单,但是非常强大的依赖注入系统。 @@ -149,48 +144,47 @@ FastAPI 有一个使用非常简单,但是非常强大的IDE/linter/brain** 适配: * 因为 pydantic 数据结构仅仅是你定义的类的实例;自动补全,linting,mypy 以及你的直觉应该可以和你验证的数据一起正常工作。 * 验证**复杂结构**: - * 使用分层的 Pydantic 模型, Python `typing`的 `List` 和 `Dict` 等等。 - * 验证器使我们能够简单清楚的将复杂的数据模式定义、检查并记录为 JSON Schema。 + * 使用分层的 Pydantic 模型,Python `typing` 的 `List` 和 `Dict` 等等。 + * 验证器使我们能够简单清楚地将复杂的数据模式定义、检查并记录为 JSON Schema。 * 你可以拥有深度**嵌套的 JSON** 对象并对它们进行验证和注释。 * **可扩展**: * Pydantic 允许定义自定义数据类型或者你可以用验证器装饰器对被装饰的模型上的方法扩展验证。 diff --git a/docs/zh/docs/help-fastapi.md b/docs/zh/docs/help-fastapi.md index f01eb9eb5..5a5157b5d 100644 --- a/docs/zh/docs/help-fastapi.md +++ b/docs/zh/docs/help-fastapi.md @@ -1,147 +1,254 @@ -# 帮助 FastAPI 与求助 +# 帮助 FastAPI - 获取帮助 { #help-fastapi-get-help } -您喜欢 **FastAPI** 吗? +你喜欢 **FastAPI** 吗? -想帮助 FastAPI?其它用户?还有项目作者? +想帮助 FastAPI、其他用户和作者吗? -或要求助怎么使用 **FastAPI**? +或者你想获取 **FastAPI** 的帮助? -以下几种帮助的方式都非常简单(有些只需要点击一两下鼠标)。 +有很多非常简单的方式可以帮忙(有些只需点一两下)。 -求助的渠道也很多。 +同样,也有多种途径可以获得帮助。 -## 订阅新闻邮件 +## 订阅新闻邮件 { #subscribe-to-the-newsletter } -您可以订阅 [**FastAPI 和它的小伙伴** 新闻邮件](newsletter.md){.internal-link target=_blank}(不会经常收到) +你可以订阅(不频繁的)[**FastAPI and friends** 新闻邮件](newsletter.md){.internal-link target=_blank},获取如下更新: * FastAPI 及其小伙伴的新闻 🚀 * 指南 📝 * 功能 ✨ * 破坏性更改 🚨 -* 开发技巧 ✅ +* 使用技巧 ✅ -## 在推特上关注 FastAPI +## 在 X (Twitter) 上关注 FastAPI { #follow-fastapi-on-x-twitter } -在 **X (Twitter)** 上关注 @fastapi 获取 **FastAPI** 的最新消息。🐦 +在 **X (Twitter)** 上关注 @fastapi 获取 **FastAPI** 的最新动态。🐦 -## 在 GitHub 上为 **FastAPI** 加星 +## 在 GitHub 上为 **FastAPI** 加星 { #star-fastapi-in-github } -您可以在 GitHub 上 **Star** FastAPI(只要点击右上角的星星就可以了): https://github.com/fastapi/fastapi。⭐️ +你可以在 GitHub 上为 FastAPI 点亮「星标」(点击右上角的星形按钮):https://github.com/fastapi/fastapi。⭐️ -**Star** 以后,其它用户就能更容易找到 FastAPI,并了解到已经有其他用户在使用它了。 +点亮星标后,其他用户更容易发现它,并看到它已经对许多人有帮助。 -## 关注 GitHub 资源库的版本发布 +## 关注 GitHub 资源库的版本发布 { #watch-the-github-repository-for-releases } -您还可以在 GitHub 上 **Watch** FastAPI,(点击右上角的 **Watch** 按钮)https://github.com/fastapi/fastapi。👀 +你可以在 GitHub 上「关注」FastAPI(点击右上角的「watch」按钮):https://github.com/fastapi/fastapi。👀 -您可以选择只关注发布(**Releases only**)。 +在那里你可以选择「Releases only」。 -这样,您就可以(在电子邮件里)接收到 **FastAPI** 新版发布的通知,及时了解 bug 修复与新功能。 +这样做之后,每当 **FastAPI** 发布新版本(包含修复和新功能),你都会收到通知(邮件)。 -## 联系作者 +## 联系作者 { #connect-with-the-author } -您可以联系项目作者,就是我(Sebastián Ramírez / `tiangolo`)。 +你可以联系我(Sebastián Ramírez / `tiangolo`),作者本人。 -您可以: +你可以: -* 在 **GitHub** 上关注我 - * 了解其它我创建的开源项目,或许对您会有帮助 - * 关注我什么时候创建新的开源项目 -* 在 **X (Twitter)** 上关注我 - * 告诉我您使用 FastAPI(我非常乐意听到这种消息) - * 接收我发布公告或新工具的消息 - * 您还可以关注@fastapi on X (Twitter),这是个独立的账号 -* 在**领英**上联系我 - * 接收我发布公告或新工具的消息(虽然我用 X (Twitter) 比较多) -* 阅读我在 **Dev.to****Medium** 上的文章,或关注我 - * 阅读我的其它想法、文章,了解我创建的工具 - * 关注我,这样就可以随时看到我发布的新文章 +* 在 **GitHub** 上关注我。 + * 了解我创建的其他开源项目,也许对你有帮助。 + * 关注我何时创建新的开源项目。 +* 关注我在 **X (Twitter)**Mastodon。 + * 告诉我你如何使用 FastAPI(我很喜欢听这些)。 + * 获取我发布公告或新工具的消息。 + * 你也可以关注 @fastapi on X (Twitter)(独立账号)。 +* 在 **LinkedIn** 上关注我。 + * 获取我发布公告或新工具的消息(不过我更常用 X (Twitter) 🤷‍♂)。 +* 阅读我在 **Dev.to****Medium** 上的文章(或关注我)。 + * 阅读我的其他想法、文章,以及我创建的工具。 + * 关注我,这样当我发布新文章时你会第一时间看到。 -## Tweet about **FastAPI** +## 发推谈谈 **FastAPI** { #tweet-about-fastapi } -Tweet about **FastAPI** 让我和大家知道您为什么喜欢 FastAPI。🎉 +Tweet about **FastAPI**,告诉我和大家你为什么喜欢它。🎉 -知道有人使用 **FastAPI**,我会很开心,我也想知道您为什么喜欢 FastAPI,以及您在什么项目/哪些公司使用 FastAPI,等等。 +我很高兴听到 **FastAPI** 的使用情况、你喜欢它的哪些点、你在哪个项目/公司使用它,等等。 -## 为 FastAPI 投票 +## 为 FastAPI 投票 { #vote-for-fastapi } -* 在 Slant 上为 **FastAPI** 投票 -* 在 AlternativeTo 上为 **FastAPI** 投票 +* 在 Slant 上为 **FastAPI** 投票。 +* 在 AlternativeTo 上为 **FastAPI** 投票。 +* 在 StackShare 上标注你在用 **FastAPI**。 -## 在 GitHub 上帮助其他人解决问题 +## 在 GitHub 上帮别人解答问题 { #help-others-with-questions-in-github } -您可以查看现有 issues,并尝试帮助其他人解决问题,说不定您能解决这些问题呢。🤓 +你可以尝试在以下地方帮助他人解答问题: -如果帮助很多人解决了问题,您就有可能成为 [FastAPI 的官方专家](fastapi-people.md#_3){.internal-link target=_blank}。🎉 +* GitHub Discussions +* GitHub Issues -## 监听 GitHub 资源库 +很多情况下,你也许已经知道这些问题的答案了。🤓 -您可以在 GitHub 上「监听」FastAPI(点击右上角的 "watch" 按钮): https://github.com/fastapi/fastapi. 👀 +如果你帮助了很多人解答问题,你会成为官方的 [FastAPI 专家](fastapi-people.md#fastapi-experts){.internal-link target=_blank}。🎉 -如果您选择 "Watching" 而不是 "Releases only",有人创建新 Issue 时,您会接收到通知。 +只要记住,最重要的一点是:尽量友善。人们带着挫败感而来,很多时候他们的提问方式并不理想,但请尽你所能地友好对待。🤗 -然后您就可以尝试并帮助他们解决问题。 +我们的目标是让 **FastAPI** 社区友好且包容。同时,也不要接受对他人的霸凌或不尊重。我们需要彼此照顾。 -## 创建 Issue +--- -您可以在 GitHub 资源库中创建 Issue,例如: +以下是如何帮助他人解答问题(在 Discussions 或 Issues 中): -* 提出**问题**或**意见** -* 提出新**特性**建议 +### 理解问题 { #understand-the-question } -**注意**:如果您创建 Issue,我会要求您也要帮助别的用户。😉 +* 看看你是否能理解提问者的**目的**和使用场景。 -## 创建 PR +* 然后检查问题(绝大多数是提问)是否**清晰**。 -您可以创建 PR 为源代码做[贡献](contributing.md){.internal-link target=_blank},例如: +* 很多时候,问题是围绕提问者想象中的解决方案,但可能有**更好的**方案。如果你更好地理解了问题和使用场景,你就可能提出更**合适的替代方案**。 -* 修改文档错别字 -* 编辑这个文件,分享 FastAPI 的文章、视频、博客,不论是您自己的,还是您看到的都成 - * 注意,添加的链接要放在对应区块的开头 -* [翻译文档](contributing.md#_8){.internal-link target=_blank} - * 审阅别人翻译的文档 -* 添加新的文档内容 -* 修复现有问题/Bug -* 添加新功能 +* 如果你没能理解问题,请请求更多**细节**。 -## 加入聊天 +### 复现问题 { #reproduce-the-problem } -快加入 👥 Discord 聊天服务器 👥 和 FastAPI 社区里的小伙伴一起哈皮吧。 +在大多数情况下与问题相关的都是提问者的**原始代码**。 -/// tip | 提示 +很多时候他们只会粘贴一小段代码,但这不足以**复现问题**。 -如有问题,请在 GitHub Issues 里提问,在这里更容易得到 [FastAPI 专家](fastapi-people.md#_3){.internal-link target=_blank}的帮助。 +* 你可以请他们提供一个可最小复现的示例,你可以**复制粘贴**并在本地运行,看到与他们相同的错误或行为,或者更好地理解他们的用例。 -聊天室仅供闲聊。 +* 如果你非常热心,你也可以尝试仅根据问题描述自己**构造一个示例**。不过要记住,这可能会花很多时间,通常先请他们澄清问题会更好。 + +### 提出解决方案 { #suggest-solutions } + +* 在能够理解问题之后,你可以给出一个可能的**答案**。 + +* 很多情况下,更好的是去理解他们**底层的问题或场景**,因为可能存在比他们尝试的方法更好的解决方式。 + +### 请求关闭问题 { #ask-to-close } + +如果他们回复了,很有可能你已经解决了他们的问题,恭喜,**你是英雄**!🦸 + +* 现在,如果问题已解决,你可以请他们: + * 在 GitHub Discussions 中:将你的评论标记为**答案**。 + * 在 GitHub Issues 中:**关闭**该 issue。 + +## 关注 GitHub 资源库 { #watch-the-github-repository } + +你可以在 GitHub 上「关注」FastAPI(点击右上角的「watch」按钮):https://github.com/fastapi/fastapi。👀 + +如果你选择「Watching」而非「Releases only」,当有人创建新的 issue 或问题时你会收到通知。你也可以指定只通知新 issues、discussions、PR 等。 + +然后你就可以尝试帮助他们解决这些问题。 + +## 提问 { #ask-questions } + +你可以在 GitHub 资源库中创建一个新问题(Question),例如: + +* 提出一个**问题**或关于某个**问题**的求助。 +* 建议一个新的**功能**。 + +**注意**:如果你这么做了,我也会请你去帮助其他人。😉 + +## 审阅 Pull Request { #review-pull-requests } + +你可以帮我审阅他人的 Pull Request。 + +再次提醒,请尽力保持友善。🤗 + +--- + +下面是需要注意的点,以及如何审阅一个 Pull Request: + +### 理解问题 { #understand-the-problem } + +* 首先,确保你**理解这个 PR 要解决的问题**。它可能在 GitHub Discussion 或 issue 中有更长的讨论。 + +* 也有很大可能这个 PR 实际上并不需要,因为问题可以用**不同方式**解决。这种情况下你可以提出或询问该方案。 + +### 不用过分担心风格 { #dont-worry-about-style } + +* 不用太在意提交信息风格等,我会在合并时 squash 并手动调整提交信息。 + +* 也不用过分担心代码风格规则,已经有自动化工具在检查。 + +如果还有其他风格或一致性需求,我会直接提出,或者我会在其上追加提交做必要修改。 + +### 检查代码 { #check-the-code } + +* 检查并阅读代码,看看是否说得通,**在本地运行**并确认它确实解决了问题。 + +* 然后**评论**说明你已经这样做了,这样我就知道你确实检查过。 + +/// info | 信息 + +不幸的是,我不能仅仅信任那些有很多人批准的 PR。 + +多次发生过这样的情况:PR 有 3、5 个甚至更多的批准,可能是因为描述很吸引人,但当我检查时,它们实际上是坏的、有 bug,或者并没有解决它声称要解决的问题。😅 + +所以,真正重要的是你确实读过并运行过代码,并在评论里告诉我你做过这些。🤓 /// -### 别在聊天室里提问 +* 如果 PR 可以在某些方面简化,你可以提出建议,但没必要过分挑剔,很多东西比较主观(我也会有我自己的看法 🙈),因此尽量关注关键点更好。 -注意,聊天室更倾向于“闲聊”,经常有人会提出一些笼统得让人难以回答的问题,所以在这里提问一般没人回答。 +### 测试 { #tests } -GitHub Issues 里提供了模板,指引您提出正确的问题,有利于获得优质的回答,甚至可能解决您还没有想到的问题。而且就算答疑解惑要耗费不少时间,我还是会尽量在 GitHub 里回答问题。但在聊天室里,我就没功夫这么做了。😅 +* 帮我检查 PR 是否包含**测试**。 -聊天室里的聊天内容也不如 GitHub 里好搜索,聊天里的问答很容易就找不到了。只有在 GitHub Issues 里的问答才能帮助您成为 [FastAPI 专家](fastapi-people.md#_3){.internal-link target=_blank},在 GitHub Issues 中为您带来更多关注。 +* 确认在合并 PR 之前,测试**会失败**。🚨 -另一方面,聊天室里有成千上万的用户,在这里,您有很大可能遇到聊得来的人。😄 +* 然后确认合并 PR 之后,测试**能通过**。✅ -## 赞助作者 +* 很多 PR 没有测试,你可以**提醒**他们添加测试,或者你甚至可以自己**建议**一些测试。这是最耗时的部分之一,你能在这方面帮上大忙。 -您还可以通过 GitHub 赞助商资助本项目的作者(就是我)。 +* 然后也请评论你做了哪些验证,这样我就知道你检查过。🤓 -给我买杯咖啡 ☕️ 以示感谢 😄 +## 创建 Pull Request { #create-a-pull-request } -当然您也可以成为 FastAPI 的金牌或银牌赞助商。🏅🎉 +你可以通过 Pull Request 为源代码[做贡献](contributing.md){.internal-link target=_blank},例如: -## 赞助 FastAPI 使用的工具 +* 修正文档中的一个错别字。 +* 通过编辑这个文件分享你创建或发现的关于 FastAPI 的文章、视频或播客。 + * 请确保把你的链接添加到相应区块的开头。 +* 帮助把[文档翻译](contributing.md#translations){.internal-link target=_blank}成你的语言。 + * 你也可以审阅他人创建的翻译。 +* 提议新增文档章节。 +* 修复现有 issue/bug。 + * 记得添加测试。 +* 添加新功能。 + * 记得添加测试。 + * 如果相关,记得补充文档。 -如您在本文档中所见,FastAPI 站在巨人的肩膀上,它们分别是 Starlette 和 Pydantic。 +## 帮忙维护 FastAPI { #help-maintain-fastapi } -您还可以赞助: +帮我一起维护 **FastAPI** 吧!🤓 -* Samuel Colvin (Pydantic) -* Encode (Starlette, Uvicorn) +有很多工作要做,其中大部分其实**你**都能做。 + +你现在就能做的主要事情有: + +* [在 GitHub 上帮别人解答问题](#help-others-with-questions-in-github){.internal-link target=_blank}(见上面的章节)。 +* [审阅 Pull Request](#review-pull-requests){.internal-link target=_blank}(见上面的章节)。 + +这两项工作是**最耗时**的。这也是维护 FastAPI 的主要工作。 + +如果你能在这方面帮我,**你就是在帮我维护 FastAPI**,并确保它**更快更好地前进**。🚀 + +## 加入聊天 { #join-the-chat } + +加入 👥 Discord 聊天服务器 👥,和 FastAPI 社区的小伙伴们一起交流。 + +/// tip | 提示 + +关于提问,请在 GitHub Discussions 中发布,这样更有机会得到 [FastAPI 专家](fastapi-people.md#fastapi-experts){.internal-link target=_blank} 的帮助。 + +聊天仅用于其他日常交流。 + +/// + +### 别在聊天里提问 { #dont-use-the-chat-for-questions } + +请记住,聊天更偏向“自由交流”,很容易提出过于笼统、难以回答的问题,因此你可能收不到解答。 + +在 GitHub 中,模板会引导你写出恰当的问题,从而更容易获得好的回答,甚至在提问之前就能自己解决。而且在 GitHub 里,我能尽量确保最终回复每个问题,即使这需要一些时间。对聊天系统来说,我个人做不到这一点。😅 + +聊天系统中的对话也不像 GitHub 那样容易搜索,因此问答可能在聊天中淹没。而且只有在 GitHub 中的问答才会计入成为 [FastAPI 专家](fastapi-people.md#fastapi-experts){.internal-link target=_blank} 的贡献,所以你在 GitHub 上更可能获得关注。 + +另一方面,聊天系统里有成千上万的用户,你几乎随时都能在那里找到聊得来的人。😄 + +## 赞助作者 { #sponsor-the-author } + +如果你的**产品/公司**依赖或与 **FastAPI** 相关,并且你想触达它的用户,你可以通过 GitHub sponsors 赞助作者(我)。根据赞助层级,你还可能获得一些额外福利,比如在文档中展示徽章。🎁 --- diff --git a/docs/zh/docs/history-design-future.md b/docs/zh/docs/history-design-future.md index 4db5c8472..00945eab5 100644 --- a/docs/zh/docs/history-design-future.md +++ b/docs/zh/docs/history-design-future.md @@ -1,12 +1,12 @@ -# 历史、设计、未来 +# 历史、设计、未来 { #history-design-and-future } 不久前,曾有 **FastAPI** 用户问过: -> 这个项目有怎样的历史?好像它只用了几周就从默默无闻变得众所周知…… +> 这个项目有怎样的历史?好像它只用了几周就从默默无闻变得众所周知... 在此,我们简单回顾一下 **FastAPI** 的历史。 -## 备选方案 +## 备选方案 { #alternatives } 有那么几年,我曾领导数个开发团队为诸多复杂需求创建各种 API,这些需求包括机器学习、分布系统、异步任务、NoSQL 数据库等领域。 @@ -24,10 +24,9 @@ 在那几年中,我一直回避创建新的框架。首先,我尝试使用各种框架、插件、工具解决 **FastAPI** 现在的功能。 但到了一定程度之后,我别无选择,只能从之前的工具中汲取最优思路,并以尽量好的方式把这些思路整合在一起,使用之前甚至是不支持的语言特性(Python 3.6+ 的类型提示),从而创建一个能满足我所有需求的框架。 - -## 调研 +## 调研 { #investigation } 通过使用之前所有的备选方案,我有机会从它们之中学到了很多东西,获取了很多想法,并以我和我的开发团队能想到的最好方式把这些思路整合成一体。 @@ -37,7 +36,7 @@ 因此,甚至在开发 **FastAPI** 前,我就花了几个月的时间研究 OpenAPI、JSON Schema、OAuth2 等规范。深入理解它们之间的关系、重叠及区别之处。 -## 设计 +## 设计 { #design } 然后,我又花了一些时间从用户角度(使用 FastAPI 的开发者)设计了开发者 **API**。 @@ -51,7 +50,7 @@ 所有这些都是为了给开发者提供最佳的开发体验。 -## 需求项 +## 需求项 { #requirements } 经过测试多种备选方案,我最终决定使用 **Pydantic**,并充分利用它的优势。 @@ -59,11 +58,11 @@ 在开发期间,我还为 **Starlette** 做了不少贡献,这是另一个关键需求项。 -## 开发 +## 开发 { #development } 当我启动 **FastAPI** 开发的时候,绝大多数部件都已经就位,设计已经定义,需求项和工具也已经准备就绪,相关标准与规范的知识储备也非常清晰而新鲜。 -## 未来 +## 未来 { #future } 至此,**FastAPI** 及其理念已经为很多人所用。 @@ -73,6 +72,6 @@ 但,**FastAPI** 仍有很多改进的余地,也还需要添加更多的功能。 -总之,**FastAPI** 前景光明。 +**FastAPI** 前景光明。 在此,我们衷心感谢[您的帮助](help-fastapi.md){.internal-link target=_blank}。 diff --git a/docs/zh/docs/how-to/configure-swagger-ui.md b/docs/zh/docs/how-to/configure-swagger-ui.md index 108e0cb95..104baff4b 100644 --- a/docs/zh/docs/how-to/configure-swagger-ui.md +++ b/docs/zh/docs/how-to/configure-swagger-ui.md @@ -1,4 +1,4 @@ -# 配置 Swagger UI +# 配置 Swagger UI { #configure-swagger-ui } 你可以配置一些额外的 Swagger UI 参数. @@ -8,7 +8,7 @@ FastAPI会将这些配置转换为 **JSON**,使其与 JavaScript 兼容,因为这是 Swagger UI 需要的。 -## 不使用语法高亮 +## 禁用语法高亮 { #disable-syntax-highlighting } 比如,你可以禁用 Swagger UI 中的语法高亮。 @@ -18,41 +18,41 @@ FastAPI会将这些配置转换为 **JSON**,使其与 JavaScript 兼容,因 但是你可以通过设置 `syntaxHighlight` 为 `False` 来禁用 Swagger UI 中的语法高亮: -{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} ...在此之后,Swagger UI 将不会高亮代码: -## 改变主题 +## 改变主题 { #change-the-theme } 同样地,你也可以通过设置键 `"syntaxHighlight.theme"` 来设置语法高亮主题(注意中间有一个点): -{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} 这个配置会改变语法高亮主题: -## 改变默认 Swagger UI 参数 +## 改变默认 Swagger UI 参数 { #change-default-swagger-ui-parameters } FastAPI 包含了一些默认配置参数,适用于大多数用例。 其包括这些默认配置参数: -{* ../../fastapi/openapi/docs.py ln[7:23] *} +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} 你可以通过在 `swagger_ui_parameters` 中设置不同的值来覆盖它们。 比如,如果要禁用 `deepLinking`,你可以像这样传递设置到 `swagger_ui_parameters` 中: -{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} -## 其他 Swagger UI 参数 +## 其他 Swagger UI 参数 { #other-swagger-ui-parameters } -查看其他 Swagger UI 参数,请阅读 docs for Swagger UI parameters。 +查看所有其他可用的配置,请阅读 Swagger UI 参数文档。 -## JavaScript-only 配置 +## JavaScript-only 配置 { #javascript-only-settings } Swagger UI 同样允许使用 **JavaScript-only** 配置对象(例如,JavaScript 函数)。 @@ -67,4 +67,4 @@ presets: [ 这些是 **JavaScript** 对象,而不是字符串,所以你不能直接从 Python 代码中传递它们。 -如果你需要像这样使用 JavaScript-only 配置,你可以使用上述方法之一。覆盖所有 Swagger UI *path operation* 并手动编写任何你需要的 JavaScript。 +如果你需要像这样使用 JavaScript-only 配置,你可以使用上述方法之一。覆盖所有 Swagger UI *路径操作* 并手动编写任何你需要的 JavaScript。 diff --git a/docs/zh/docs/how-to/general.md b/docs/zh/docs/how-to/general.md index e8b6dd3b2..e75ad6c79 100644 --- a/docs/zh/docs/how-to/general.md +++ b/docs/zh/docs/how-to/general.md @@ -1,39 +1,39 @@ -# 通用 - 如何操作 - 诀窍 +# 通用 - 如何操作 - 诀窍 { #general-how-to-recipes } 这里是一些指向文档中其他部分的链接,用于解答一般性或常见问题。 -## 数据过滤 - 安全性 +## 数据过滤 - 安全性 { #filter-data-security } 为确保不返回超过需要的数据,请阅读 [教程 - 响应模型 - 返回类型](../tutorial/response-model.md){.internal-link target=_blank} 文档。 -## 文档的标签 - OpenAPI +## 文档的标签 - OpenAPI { #documentation-tags-openapi } 在文档界面中添加**路径操作**的标签和进行分组,请阅读 [教程 - 路径操作配置 - Tags 参数](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank} 文档。 -## 文档的概要和描述 - OpenAPI +## 文档的概要和描述 - OpenAPI { #documentation-summary-and-description-openapi } -在文档界面中添加**路径操作**的概要和描述,请阅读 [教程 - 路径操作配置 - Summary 和 Description 参数](../tutorial/path-operation-configuration.md#summary-description){.internal-link target=_blank} 文档。 +在文档界面中添加**路径操作**的概要和描述,请阅读 [教程 - 路径操作配置 - Summary 和 Description 参数](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank} 文档。 -## 文档的响应描述 - OpenAPI +## 文档的响应描述 - OpenAPI { #documentation-response-description-openapi } 在文档界面中定义并显示响应描述,请阅读 [教程 - 路径操作配置 - 响应描述](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank} 文档。 -## 文档弃用**路径操作** - OpenAPI +## 文档弃用**路径操作** - OpenAPI { #documentation-deprecate-a-path-operation-openapi } 在文档界面中显示弃用的**路径操作**,请阅读 [教程 - 路径操作配置 - 弃用](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank} 文档。 -## 将任何数据转换为 JSON 兼容格式 +## 将任何数据转换为 JSON 兼容格式 { #convert-any-data-to-json-compatible } 要将任何数据转换为 JSON 兼容格式,请阅读 [教程 - JSON 兼容编码器](../tutorial/encoder.md){.internal-link target=_blank} 文档。 -## OpenAPI 元数据 - 文档 +## OpenAPI 元数据 - 文档 { #openapi-metadata-docs } 要添加 OpenAPI 的元数据,包括许可证、版本、联系方式等,请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md){.internal-link target=_blank} 文档。 -## OpenAPI 自定义 URL +## OpenAPI 自定义 URL { #openapi-custom-url } 要自定义 OpenAPI 的 URL(或删除它),请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md#openapi-url){.internal-link target=_blank} 文档。 -## OpenAPI 文档 URL +## OpenAPI 文档 URL { #openapi-docs-urls } -要更改用于自动生成文档的 URL,请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. +要更改用于自动生成文档的 URL,请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}。 diff --git a/docs/zh/docs/how-to/index.md b/docs/zh/docs/how-to/index.md index ac097618b..980dcd1a6 100644 --- a/docs/zh/docs/how-to/index.md +++ b/docs/zh/docs/how-to/index.md @@ -1,4 +1,4 @@ -# 如何操作 - 诀窍 +# 如何操作 - 诀窍 { #how-to-recipes } 在这里,你将看到关于**多个主题**的不同诀窍或“如何操作”指南。 @@ -6,7 +6,7 @@ 如果某些内容看起来对你的项目有用,请继续查阅,否则请直接跳过它们。 -/// tip | 小技巧 +/// tip | 提示 如果你想以系统的方式**学习 FastAPI**(推荐),请阅读 [教程 - 用户指南](../tutorial/index.md){.internal-link target=_blank} 的每一章节。 diff --git a/docs/zh/docs/index.md b/docs/zh/docs/index.md index 9f4b6d3d5..1c2aea328 100644 --- a/docs/zh/docs/index.md +++ b/docs/zh/docs/index.md @@ -1,11 +1,11 @@ -# FastAPI +# FastAPI { #fastapi }

- FastAPI + FastAPI

FastAPI 框架,高性能,易于学习,高效编码,生产可用 @@ -27,135 +27,140 @@ --- -**文档**: https://fastapi.tiangolo.com +**文档**: https://fastapi.tiangolo.com **源码**: https://github.com/fastapi/fastapi --- -FastAPI 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 并基于标准的 Python 类型提示。 +FastAPI 是一个用于构建 API 的现代、快速(高性能)的 Web 框架,使用 Python 并基于标准的 Python 类型提示。 -关键特性: +关键特性: -* **快速**:可与 **NodeJS** 和 **Go** 并肩的极高性能(归功于 Starlette 和 Pydantic)。[最快的 Python web 框架之一](#_11)。 +* **快速**:极高性能,可与 **NodeJS** 和 **Go** 并肩(归功于 Starlette 和 Pydantic)。[最快的 Python 框架之一](#performance)。 +* **高效编码**:功能开发速度提升约 200% ~ 300%。* +* **更少 bug**:人为(开发者)错误减少约 40%。* +* **直观**:极佳的编辑器支持。处处皆可自动补全。更少的调试时间。 +* **易用**:为易用和易学而设计。更少的文档阅读时间。 +* **简短**:最小化代码重复。一次参数声明即可获得多种功能。更少的 bug。 +* **健壮**:生产可用级代码。并带有自动生成的交互式文档。 +* **标准化**:基于(并完全兼容)API 的开放标准:OpenAPI(以前称为 Swagger)和 JSON Schema。 -* **高效编码**:提高功能开发速度约 200% 至 300%。* -* **更少 bug**:减少约 40% 的人为(开发者)导致错误。* -* **智能**:极佳的编辑器支持。处处皆可自动补全,减少调试时间。 -* **简单**:设计的易于使用和学习,阅读文档的时间更短。 -* **简短**:使代码重复最小化。通过不同的参数声明实现丰富功能。bug 更少。 -* **健壮**:生产可用级别的代码。还有自动生成的交互式文档。 -* **标准化**:基于(并完全兼容)API 的相关开放标准:OpenAPI (以前被称为 Swagger) 和 JSON Schema。 +* 基于某内部开发团队在构建生产应用时的测试估算。 -* 根据对某个构建线上应用的内部开发团队所进行的测试估算得出。 - -## Sponsors +## 赞助商 { #sponsors } -{% if sponsors %} +### Keystone 赞助商 { #keystone-sponsor } + +{% for sponsor in sponsors.keystone -%} + +{% endfor -%} + +### 金牌和银牌赞助商 { #gold-and-silver-sponsors } + {% for sponsor in sponsors.gold -%} {% endfor -%} {%- for sponsor in sponsors.silver -%} {% endfor %} -{% endif %} -Other sponsors +其他赞助商 -## 评价 +## 评价 { #opinions } -「_[...] 最近我一直在使用 **FastAPI**。[...] 实际上我正在计划将其用于我所在的**微软**团队的所有**机器学习服务**。其中一些服务正被集成进核心 **Windows** 产品和一些 **Office** 产品。_」 +「_[...] 最近我大量使用 **FastAPI**。[...] 我实际上计划把它用于我团队在 **微软** 的所有 **机器学习服务**。其中一些正在集成进核心 **Windows** 产品以及一些 **Office** 产品。_」 -

Kabir Khan - 微软 (ref)
+
Kabir Khan - Microsoft (ref)
--- -「_我们选择了 **FastAPI** 来创建用于获取**预测结果**的 **REST** 服务。[用于 Ludwig]_」 +「_我们采用 **FastAPI** 来构建可查询以获取**预测结果**的 **REST** 服务器。[用于 Ludwig]_」 -
Piero Molino,Yaroslav Dudin 和 Sai Sumanth Miryala - Uber (ref)
+
Piero Molino,Yaroslav Dudin,Sai Sumanth Miryala - Uber (ref)
--- -「_**Netflix** 非常高兴地宣布,正式开源我们的**危机管理**编排框架:**Dispatch**![使用 **FastAPI** 构建]_」 +「_**Netflix** 很高兴宣布开源我们的**危机管理**编排框架:**Dispatch**![使用 **FastAPI** 构建]_」
Kevin Glisson,Marc Vilanova,Forest Monsen - Netflix (ref)
--- -「_**FastAPI** 让我兴奋的欣喜若狂。它太棒了!_」 +「_我对 **FastAPI** 兴奋到飞起。它太有趣了!_」
Brian Okken - Python Bytes 播客主持人 (ref)
--- -「_老实说,你的作品看起来非常可靠和优美。在很多方面,这就是我想让 **Hug** 成为的样子 - 看到有人实现了它真的很鼓舞人心。_」 +「_老实说,你构建的东西非常稳健而且打磨得很好。从很多方面看,这就是我想让 **Hug** 成为的样子 —— 看到有人把它做出来真的很鼓舞人心。_」
Timothy Crosley - Hug 作者 (ref)
--- -「_如果你正打算学习一个**现代框架**用来构建 REST API,来看下 **FastAPI** [...] 它快速、易用且易于学习 [...]_」 +「_如果你想学一个用于构建 REST API 的**现代框架**,看看 **FastAPI** [...] 它快速、易用且易学 [...]_」 -「_我们已经将 **API** 服务切换到了 **FastAPI** [...] 我认为你会喜欢它的 [...]_」 +「_我们已经把我们的 **API** 切换到 **FastAPI** [...] 我想你会喜欢它 [...]_」
Ines Montani - Matthew Honnibal - Explosion AI 创始人 - spaCy 作者 (ref) - (ref)
--- -## **Typer**,命令行中的 FastAPI +「_如果有人正在构建生产级的 Python API,我强烈推荐 **FastAPI**。它**设计优雅**、**使用简单**且**高度可扩展**,已经成为我们 API 优先开发战略中的**关键组件**,并驱动了许多自动化和服务,比如我们的 Virtual TAC Engineer。_」 + +
Deon Pillsbury - Cisco (ref)
+ +--- + +## FastAPI 迷你纪录片 { #fastapi-mini-documentary } + +在 2025 年末发布了一部FastAPI 迷你纪录片,你可以在线观看: + +FastAPI Mini Documentary + +## **Typer**,命令行中的 FastAPI { #typer-the-fastapi-of-clis } -如果你正在开发一个在终端中运行的命令行应用而不是 web API,不妨试下 **Typer**。 +如果你要开发一个用于终端的 命令行应用而不是 Web API,看看 **Typer**。 -**Typer** 是 FastAPI 的小同胞。它想要成为**命令行中的 FastAPI**。 ⌨️ 🚀 +**Typer** 是 FastAPI 的小同胞。它的目标是成为**命令行中的 FastAPI**。⌨️ 🚀 -## 依赖 +## 依赖 { #requirements } -Python 及更高版本 +FastAPI 站在巨人的肩膀之上: -FastAPI 站在以下巨人的肩膀之上: - -* Starlette 负责 web 部分。 +* Starlette 负责 Web 部分。 * Pydantic 负责数据部分。 -## 安装 +## 安装 { #installation } + +创建并激活一个虚拟环境,然后安装 FastAPI:
```console -$ pip install fastapi +$ pip install "fastapi[standard]" ---> 100% ```
-你还会需要一个 ASGI 服务器,生产环境可以使用 Uvicorn 或者 Hypercorn。 +**Note**: 请确保把 `"fastapi[standard]"` 用引号包起来,以保证在所有终端中都能正常工作。 -
+## 示例 { #example } -```console -$ pip install "uvicorn[standard]" +### 创建 { #create-it } ----> 100% -``` - -
- -## 示例 - -### 创建 - -* 创建一个 `main.py` 文件并写入以下内容: +创建文件 `main.py`,内容如下: ```Python -from typing import Union - from fastapi import FastAPI app = FastAPI() @@ -167,18 +172,16 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ```
或者使用 async def... -如果你的代码里会出现 `async` / `await`,请使用 `async def`: - -```Python hl_lines="9 14" -from typing import Union +如果你的代码里会用到 `async` / `await`,请使用 `async def`: +```Python hl_lines="7 12" from fastapi import FastAPI app = FastAPI() @@ -190,28 +193,41 @@ async def read_root(): @app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): +async def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} ``` **Note**: -如果你不知道是否会用到,可以查看文档的 _"In a hurry?"_ 章节中 关于 `async` 和 `await` 的部分。 +如果你不确定,请查看文档中 _"In a hurry?"_ 章节的`async` 和 `await`部分。
-### 运行 +### 运行 { #run-it } -通过以下命令运行服务器: +用下面的命令运行服务器:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py + ╭────────── 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 [28720] -INFO: Started server process [28722] +INFO: Started reloader process [2248755] using WatchFiles +INFO: Started server process [2248757] INFO: Waiting for application startup. INFO: Application startup complete. ``` @@ -219,58 +235,56 @@ INFO: Application startup complete.
-关于 uvicorn main:app --reload 命令...... +关于命令 fastapi dev main.py... - `uvicorn main:app` 命令含义如下: +`fastapi dev` 命令会读取你的 `main.py` 文件,检测其中的 **FastAPI** 应用,并使用 Uvicorn 启动服务器。 -* `main`:`main.py` 文件(一个 Python "模块")。 -* `app`:在 `main.py` 文件中通过 `app = FastAPI()` 创建的对象。 -* `--reload`:让服务器在更新代码后重新启动。仅在开发时使用该选项。 +默认情况下,`fastapi dev` 会在本地开发时启用自动重载。 + +你可以在 FastAPI CLI 文档中了解更多。
-### 检查 +### 检查 { #check-it } -使用浏览器访问 http://127.0.0.1:8000/items/5?q=somequery。 +用浏览器打开 http://127.0.0.1:8000/items/5?q=somequery。 -你将会看到如下 JSON 响应: +你会看到如下 JSON 响应: ```JSON {"item_id": 5, "q": "somequery"} ``` -你已经创建了一个具有以下功能的 API: +你已经创建了一个 API,它可以: -* 通过 _路径_ `/` 和 `/items/{item_id}` 接受 HTTP 请求。 -* 以上 _路径_ 都接受 `GET` 操作(也被称为 HTTP _方法_)。 -* `/items/{item_id}` _路径_ 有一个 _路径参数_ `item_id` 并且应该为 `int` 类型。 -* `/items/{item_id}` _路径_ 有一个可选的 `str` 类型的 _查询参数_ `q`。 +* 在路径 `/` 和 `/items/{item_id}` 接收 HTTP 请求。 +* 以上两个路径都接受 `GET` 操作(也称为 HTTP 方法)。 +* 路径 `/items/{item_id}` 有一个应为 `int` 的路径参数 `item_id`。 +* 路径 `/items/{item_id}` 有一个可选的 `str` 类型查询参数 `q`。 -### 交互式 API 文档 +### 交互式 API 文档 { #interactive-api-docs } 现在访问 http://127.0.0.1:8000/docs。 -你会看到自动生成的交互式 API 文档(由 Swagger UI生成): +你会看到自动生成的交互式 API 文档(由 Swagger UI 提供): ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### 可选的 API 文档 +### 可选的 API 文档 { #alternative-api-docs } -访问 http://127.0.0.1:8000/redoc。 +然后访问 http://127.0.0.1:8000/redoc。 -你会看到另一个自动生成的文档(由 ReDoc 生成): +你会看到另一个自动生成的文档(由 ReDoc 提供): ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -## 示例升级 +## 示例升级 { #example-upgrade } -现在修改 `main.py` 文件来从 `PUT` 请求中接收请求体。 +现在修改 `main.py` 文件来接收来自 `PUT` 请求的请求体。 -我们借助 Pydantic 来使用标准的 Python 类型声明请求体。 - -```Python hl_lines="4 9-12 25-27" -from typing import Union +借助 Pydantic,使用标准的 Python 类型来声明请求体。 +```Python hl_lines="2 7-10 23-25" from fastapi import FastAPI from pydantic import BaseModel @@ -280,7 +294,7 @@ app = FastAPI() class Item(BaseModel): name: str price: float - is_offer: Union[bool, None] = None + is_offer: bool | None = None @app.get("/") @@ -289,7 +303,7 @@ def read_root(): @app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): +def read_item(item_id: int, q: str | None = None): return {"item_id": item_id, "q": q} @@ -298,173 +312,248 @@ def update_item(item_id: int, item: Item): return {"item_name": item.name, "item_id": item_id} ``` -服务器将会自动重载(因为在上面的步骤中你向 `uvicorn` 命令添加了 `--reload` 选项)。 +`fastapi dev` 服务器会自动重载。 -### 交互式 API 文档升级 +### 交互式 API 文档升级 { #interactive-api-docs-upgrade } -访问 http://127.0.0.1:8000/docs。 +现在访问 http://127.0.0.1:8000/docs。 -* 交互式 API 文档将会自动更新,并加入新的请求体: +* 交互式 API 文档会自动更新,并包含新的请求体: ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* 点击「Try it out」按钮,之后你可以填写参数并直接调用 API: +* 点击「Try it out」按钮,它允许你填写参数并直接与 API 交互: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) -* 然后点击「Execute」按钮,用户界面将会和 API 进行通信,发送参数,获取结果并在屏幕上展示: +* 然后点击「Execute」按钮,界面会与你的 API 通信、发送参数、获取结果并在屏幕上展示: ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) -### 可选文档升级 +### 可选文档升级 { #alternative-api-docs-upgrade } -访问 http://127.0.0.1:8000/redoc。 +再访问 http://127.0.0.1:8000/redoc。 -* 可选文档同样会体现新加入的请求参数和请求体: +* 可选文档同样会体现新的查询参数和请求体: ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### 总结 +### 总结 { #recap } -总的来说,你就像声明函数的参数类型一样只声明了**一次**请求参数、请求体等的类型。 +总之,你只需要把参数、请求体等的类型作为函数参数**声明一次**。 -你使用了标准的现代 Python 类型来完成声明。 +这些都使用标准的现代 Python 类型即可。 -你不需要去学习新的语法、了解特定库的方法或类,等等。 +你不需要学习新的语法、某个特定库的方法或类等。 -只需要使用标准的 **Python 及更高版本**。 +只需要标准的 **Python**。 -举个例子,比如声明 `int` 类型: +例如,一个 `int`: ```Python item_id: int ``` -或者一个更复杂的 `Item` 模型: +或者更复杂的 `Item` 模型: ```Python item: Item ``` -......在进行一次声明之后,你将获得: +……通过一次声明,你将获得: * 编辑器支持,包括: - * 自动补全 - * 类型检查 + * 自动补全。 + * 类型检查。 * 数据校验: - * 在校验失败时自动生成清晰的错误信息 - * 对多层嵌套的 JSON 对象依然执行校验 -* 转换 来自网络请求的输入数据为 Python 数据类型。包括以下数据: - * JSON - * 路径参数 - * 查询参数 - * Cookies - * 请求头 - * 表单 - * 文件 -* 转换 输出的数据:转换 Python 数据类型为供网络传输的 JSON 数据: - * 转换 Python 基础类型 (`str`、 `int`、 `float`、 `bool`、 `list` 等) - * `datetime` 对象 - * `UUID` 对象 - * 数据库模型 - * ......以及更多其他类型 + * 当数据无效时自动生成清晰的错误信息。 + * 即便是多层嵌套的 JSON 对象也会进行校验。 +* 输入数据的转换:从网络读取到 Python 数据和类型。读取来源: + * JSON。 + * 路径参数。 + * 查询参数。 + * Cookies。 + * Headers。 + * Forms。 + * Files。 +* 输出数据的转换:从 Python 数据和类型转换为网络数据(JSON): + * 转换 Python 类型(`str`、`int`、`float`、`bool`、`list` 等)。 + * `datetime` 对象。 + * `UUID` 对象。 + * 数据库模型。 + * ……以及更多。 * 自动生成的交互式 API 文档,包括两种可选的用户界面: - * Swagger UI - * ReDoc + * Swagger UI。 + * ReDoc。 --- -回到前面的代码示例,**FastAPI** 将会: +回到之前的代码示例,**FastAPI** 将会: -* 校验 `GET` 和 `PUT` 请求的路径中是否含有 `item_id`。 +* 校验 `GET` 和 `PUT` 请求的路径中是否包含 `item_id`。 * 校验 `GET` 和 `PUT` 请求中的 `item_id` 是否为 `int` 类型。 - * 如果不是,客户端将会收到清晰有用的错误信息。 -* 检查 `GET` 请求中是否有命名为 `q` 的可选查询参数(比如 `http://127.0.0.1:8000/items/foo?q=somequery`)。 - * 因为 `q` 被声明为 `= None`,所以它是可选的。 - * 如果没有 `None` 它将会是必需的 (如 `PUT` 例子中的请求体)。 -* 对于访问 `/items/{item_id}` 的 `PUT` 请求,将请求体读取为 JSON 并: - * 检查是否有必需属性 `name` 并且值为 `str` 类型 。 - * 检查是否有必需属性 `price` 并且值为 `float` 类型。 - * 检查是否有可选属性 `is_offer`, 如果有的话值应该为 `bool` 类型。 - * 以上过程对于多层嵌套的 JSON 对象同样也会执行 -* 自动对 JSON 进行转换或转换成 JSON。 -* 通过 OpenAPI 文档来记录所有内容,可被用于: - * 交互式文档系统 - * 许多编程语言的客户端代码自动生成系统 -* 直接提供 2 种交互式文档 web 界面。 + * 如果不是,客户端会看到清晰有用的错误信息。 +* 对于 `GET` 请求,检查是否存在名为 `q` 的可选查询参数(如 `http://127.0.0.1:8000/items/foo?q=somequery`)。 + * 因为参数 `q` 被声明为 `= None`,所以它是可选的。 + * 如果没有 `None`,它就是必需的(就像 `PUT` 情况下的请求体)。 +* 对于发送到 `/items/{item_id}` 的 `PUT` 请求,把请求体作为 JSON 读取: + * 检查是否存在必需属性 `name`,且为 `str`。 + * 检查是否存在必需属性 `price`,且为 `float`。 + * 检查是否存在可选属性 `is_offer`,如果存在则应为 `bool`。 + * 对于多层嵌套的 JSON 对象,同样适用。 +* 自动完成 JSON 的读取与输出转换。 +* 使用 OpenAPI 记录所有内容,可用于: + * 交互式文档系统。 + * 多语言的客户端代码自动生成系统。 +* 直接提供 2 种交互式文档 Web 界面。 --- -虽然我们才刚刚开始,但其实你已经了解了这一切是如何工作的。 +我们只是浅尝辄止,但你已经大致了解其工作方式了。 -尝试更改下面这行代码: +尝试把这一行: ```Python return {"item_name": item.name, "item_id": item_id} ``` -......从: +……从: ```Python ... "item_name": item.name ... ``` -......改为: +……改为: ```Python ... "item_price": item.price ... ``` -......注意观察编辑器是如何自动补全属性并且还知道它们的类型: +……看看你的编辑器如何自动补全属性并知道它们的类型: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -教程 - 用户指南 中有包含更多特性的更完整示例。 +更多包含更多特性的完整示例,请参阅 教程 - 用户指南。 -**剧透警告**: 教程 - 用户指南中的内容有: +**剧透警告**:教程 - 用户指南包括: -* 对来自不同地方的参数进行声明,如:**请求头**、**cookies**、**form 表单**以及**上传的文件**。 -* 如何设置**校验约束**如 `maximum_length` 或者 `regex`。 -* 一个强大并易于使用的 **依赖注入** 系统。 -* 安全性和身份验证,包括通过 **JWT 令牌**和 **HTTP 基本身份认证**来支持 **OAuth2**。 -* 更进阶(但同样简单)的技巧来声明 **多层嵌套 JSON 模型** (借助 Pydantic)。 -* 许多额外功能(归功于 Starlette)比如: +* 来自不同位置的**参数**声明:**headers**、**cookies**、**form 字段**和**文件**。 +* 如何设置**校验约束**,如 `maximum_length` 或 `regex`。 +* 功能强大且易用的 **依赖注入** 系统。 +* 安全与认证,包括对 **OAuth2**、**JWT tokens** 和 **HTTP Basic** 认证的支持。 +* 更高级(但同样简单)的 **多层嵌套 JSON 模型** 声明技巧(得益于 Pydantic)。 +* 通过 Strawberry 等库进行 **GraphQL** 集成。 +* 许多额外特性(归功于 Starlette),例如: * **WebSockets** - * **GraphQL** * 基于 HTTPX 和 `pytest` 的极其简单的测试 * **CORS** * **Cookie Sessions** - * ......以及更多 + * ……以及更多。 -## 性能 +### 部署你的应用(可选) { #deploy-your-app-optional } -独立机构 TechEmpower 所作的基准测试结果显示,基于 Uvicorn 运行的 **FastAPI** 程序是 最快的 Python web 框架之一,仅次于 Starlette 和 Uvicorn 本身(FastAPI 内部使用了它们)。(*) +你可以选择把 FastAPI 应用部署到 FastAPI Cloud,如果还没有的话去加入候补名单吧。🚀 -想了解更多,请查阅 基准测试 章节。 +如果你已经有 **FastAPI Cloud** 账号(我们从候补名单邀请了你 😉),你可以用一个命令部署你的应用。 -## 可选依赖 +部署前,先确认已登录: -用于 Pydantic: +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +然后部署你的应用: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +就这样!现在你可以通过该 URL 访问你的应用了。✨ + +#### 关于 FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** 由 **FastAPI** 的同一位作者和团队打造。 + +它让你以最小的工作量就能**构建**、**部署**并**访问**一个 API。 + +它把用 FastAPI 构建应用时的**开发者体验**带到了部署到云上的过程。🎉 + +FastAPI Cloud 是「FastAPI and friends」开源项目的主要赞助方和资金提供者。✨ + +#### 部署到其他云厂商 { #deploy-to-other-cloud-providers } + +FastAPI 是开源且基于标准的。你可以部署 FastAPI 应用到你选择的任意云厂商。 + +按照你的云厂商的指南部署 FastAPI 应用即可。🤓 + +## 性能 { #performance } + +独立机构 TechEmpower 的基准测试显示,运行在 Uvicorn 下的 **FastAPI** 应用是最快的 Python 框架之一,仅次于 Starlette 和 Uvicorn 本身(FastAPI 内部使用它们)。(*) + +想了解更多,请参阅基准测试章节。 + +## 依赖项 { #dependencies } + +FastAPI 依赖 Pydantic 和 Starlette。 + +### `standard` 依赖 { #standard-dependencies } + +当你通过 `pip install "fastapi[standard]"` 安装 FastAPI 时,会包含 `standard` 组的一些可选依赖: + +Pydantic 使用: * email-validator - 用于 email 校验。 -用于 Starlette: +Starlette 使用: -* httpx - 使用 `TestClient` 时安装。 -* jinja2 - 使用默认模板配置时安装。 -* python-multipart - 需要通过 `request.form()` 对表单进行「解析」时安装。 -* itsdangerous - 需要 `SessionMiddleware` 支持时安装。 -* pyyaml - 使用 Starlette 提供的 `SchemaGenerator` 时安装(有 FastAPI 你可能并不需要它)。 -* graphene - 需要 `GraphQLApp` 支持时安装。 +* httpx - 使用 `TestClient` 时需要。 +* jinja2 - 使用默认模板配置时需要。 +* python-multipart - 使用 `request.form()` 支持表单「解析」时需要。 -用于 FastAPI / Starlette: +FastAPI 使用: -* uvicorn - 用于加载和运行你的应用程序的服务器。 -* orjson - 使用 `ORJSONResponse` 时安装。 -* ujson - 使用 `UJSONResponse` 时安装。 +* uvicorn - 加载并提供你的应用的服务器。包含 `uvicorn[standard]`,其中包含高性能服务所需的一些依赖(例如 `uvloop`)。 +* `fastapi-cli[standard]` - 提供 `fastapi` 命令。 + * 其中包含 `fastapi-cloud-cli`,它允许你将 FastAPI 应用部署到 FastAPI Cloud。 -你可以通过 `pip install "fastapi[all]"` 命令来安装以上所有依赖。 +### 不包含 `standard` 依赖 { #without-standard-dependencies } -## 许可协议 +如果你不想包含这些 `standard` 可选依赖,可以使用 `pip install fastapi`,而不是 `pip install "fastapi[standard]"`。 + +### 不包含 `fastapi-cloud-cli` { #without-fastapi-cloud-cli } + +如果你想安装带有 standard 依赖但不包含 `fastapi-cloud-cli` 的 FastAPI,可以使用 `pip install "fastapi[standard-no-fastapi-cloud-cli]"`。 + +### 其他可选依赖 { #additional-optional-dependencies } + +还有一些你可能想安装的可选依赖。 + +额外的 Pydantic 可选依赖: + +* pydantic-settings - 用于配置管理。 +* pydantic-extra-types - 用于在 Pydantic 中使用的额外类型。 + +额外的 FastAPI 可选依赖: + +* orjson - 使用 `ORJSONResponse` 时需要。 +* ujson - 使用 `UJSONResponse` 时需要。 + +## 许可协议 { #license } 该项目遵循 MIT 许可协议。 diff --git a/docs/zh/docs/learn/index.md b/docs/zh/docs/learn/index.md index 38696f6fe..144d5b2a9 100644 --- a/docs/zh/docs/learn/index.md +++ b/docs/zh/docs/learn/index.md @@ -1,4 +1,4 @@ -# 学习 +# 学习 { #learn } 以下是学习 **FastAPI** 的介绍部分和教程。 diff --git a/docs/zh/docs/project-generation.md b/docs/zh/docs/project-generation.md index 48eb990df..a6ad9f94a 100644 --- a/docs/zh/docs/project-generation.md +++ b/docs/zh/docs/project-generation.md @@ -1,28 +1,28 @@ -# FastAPI全栈模板 +# FastAPI全栈模板 { #full-stack-fastapi-template } -模板通常带有特定的设置,而且被设计为灵活和可定制的。这允许您根据项目的需求修改和调整它们,使它们成为一个很好的起点。🏁 +模板通常带有特定的设置,但它们被设计为灵活且可定制。这样你可以根据项目需求进行修改和调整,使其成为很好的起点。🏁 -您可以使用此模板开始,因为它包含了许多已经为您完成的初始设置、安全性、数据库和一些API端点。 +你可以使用此模板开始,它已经为你完成了大量的初始设置、安全性、数据库以及一些 API 端点。 -代码仓: Full Stack FastAPI Template +GitHub 仓库: Full Stack FastAPI Template -## FastAPI全栈模板 - 技术栈和特性 +## FastAPI全栈模板 - 技术栈和特性 { #full-stack-fastapi-template-technology-stack-and-features } -- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) 用于Python后端API. - - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) 用于Python和SQL数据库的集成(ORM)。 - - 🔍 [Pydantic](https://docs.pydantic.dev) FastAPI的依赖项之一,用于数据验证和配置管理。 - - 💾 [PostgreSQL](https://www.postgresql.org) 作为SQL数据库。 +- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/zh) 用于 Python 后端 API。 + - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) 用于 Python 与 SQL 数据库的交互(ORM)。 + - 🔍 [Pydantic](https://docs.pydantic.dev),FastAPI 使用,用于数据验证与配置管理。 + - 💾 [PostgreSQL](https://www.postgresql.org) 作为 SQL 数据库。 - 🚀 [React](https://react.dev) 用于前端。 - - 💃 使用了TypeScript、hooks、[Vite](https://vitejs.dev)和其他一些现代化的前端技术栈。 - - 🎨 [Chakra UI](https://chakra-ui.com) 用于前端组件。 - - 🤖 一个自动化生成的前端客户端。 - - 🧪 [Playwright](https://playwright.dev)用于端到端测试。 - - 🦇 支持暗黑主题(Dark mode)。 -- 🐋 [Docker Compose](https://www.docker.com) 用于开发环境和生产环境。 -- 🔒 默认使用密码哈希来保证安全。 -- 🔑 JWT令牌用于权限验证。 -- 📫 使用邮箱来进行密码恢复。 -- ✅ 单元测试用了[Pytest](https://pytest.org). -- 📞 [Traefik](https://traefik.io) 用于反向代理和负载均衡。 -- 🚢 部署指南(Docker Compose)包含了如何起一个Traefik前端代理来自动化HTTPS认证。 -- 🏭 CI(持续集成)和 CD(持续部署)基于GitHub Actions。 + - 💃 使用 TypeScript、hooks、Vite 以及现代前端技术栈的其他部分。 + - 🎨 [Tailwind CSS](https://tailwindcss.com) 与 [shadcn/ui](https://ui.shadcn.com) 用于前端组件。 + - 🤖 自动生成的前端客户端。 + - 🧪 [Playwright](https://playwright.dev) 用于端到端测试。 + - 🦇 支持暗黑模式。 +- 🐋 [Docker Compose](https://www.docker.com) 用于开发与生产。 +- 🔒 默认启用安全的密码哈希。 +- 🔑 JWT(JSON Web Token)认证。 +- 📫 基于邮箱的密码找回。 +- ✅ 使用 [Pytest](https://pytest.org) 进行测试。 +- 📞 [Traefik](https://traefik.io) 用作反向代理/负载均衡。 +- 🚢 使用 Docker Compose 的部署指南,包括如何设置前端 Traefik 代理以自动处理 HTTPS 证书。 +- 🏭 基于 GitHub Actions 的 CI(持续集成)与 CD(持续部署)。 diff --git a/docs/zh/docs/python-types.md b/docs/zh/docs/python-types.md index a7f76d97f..3e1c593c1 100644 --- a/docs/zh/docs/python-types.md +++ b/docs/zh/docs/python-types.md @@ -1,31 +1,30 @@ -# Python 类型提示简介 +# Python 类型提示简介 { #python-types-intro } -**Python 3.6+ 版本**加入了对"类型提示"的支持。 +Python 支持可选的“类型提示”(也叫“类型注解”)。 -这些**"类型提示"**是一种新的语法(在 Python 3.6 版本加入)用来声明一个变量的类型。 +这些“类型提示”或注解是一种特殊语法,用来声明变量的类型。 -通过声明变量的类型,编辑器和一些工具能给你提供更好的支持。 +通过为变量声明类型,编辑器和工具可以为你提供更好的支持。 -这只是一个关于 Python 类型提示的**快速入门 / 复习**。它仅涵盖与 **FastAPI** 一起使用所需的最少部分...实际上只有很少一点。 +这只是一个关于 Python 类型提示的快速入门/复习。它只涵盖与 **FastAPI** 一起使用所需的最少部分……实际上非常少。 -整个 **FastAPI** 都基于这些类型提示构建,它们带来了许多优点和好处。 +**FastAPI** 完全基于这些类型提示构建,它们带来了许多优势和好处。 -但即使你不会用到 **FastAPI**,了解一下类型提示也会让你从中受益。 +但即使你从不使用 **FastAPI**,了解一些类型提示也会让你受益。 -/// note +/// note | 注意 -如果你已经精通 Python,并且了解关于类型提示的一切知识,直接跳到下一章节吧。 +如果你已经是 Python 专家,并且对类型提示了如指掌,可以跳到下一章。 /// -## 动机 +## 动机 { #motivation } 让我们从一个简单的例子开始: -{* ../../docs_src/python_types/tutorial001.py *} +{* ../../docs_src/python_types/tutorial001_py39.py *} - -运行这段程序将输出: +运行这个程序会输出: ``` John Doe @@ -33,38 +32,37 @@ John Doe 这个函数做了下面这些事情: -* 接收 `first_name` 和 `last_name` 参数。 -* 通过 `title()` 将每个参数的第一个字母转换为大写形式。 -* 中间用一个空格来拼接它们。 +* 接收 `first_name` 和 `last_name`。 +* 通过 `title()` 将每个参数的第一个字母转换为大写。 +* 用一个空格将它们拼接起来。 -{* ../../docs_src/python_types/tutorial001.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} - -### 修改示例 +### 修改它 { #edit-it } 这是一个非常简单的程序。 -现在假设你将从头开始编写这段程序。 +但现在想象你要从零开始写它。 -在某一时刻,你开始定义函数,并且准备好了参数...。 +在某个时刻你开始定义函数,并且准备好了参数…… -现在你需要调用一个"将第一个字母转换为大写形式的方法"。 +接下来你需要调用“那个把首字母变大写的方法”。 -等等,那个方法是什么来着?`upper`?还是 `uppercase`?`first_uppercase`?`capitalize`? +是 `upper`?是 `uppercase`?`first_uppercase`?还是 `capitalize`? -然后你尝试向程序员老手的朋友——编辑器自动补全寻求帮助。 +然后,你试试程序员的老朋友——编辑器的自动补全。 -输入函数的第一个参数 `first_name`,输入点号(`.`)然后敲下 `Ctrl+Space` 来触发代码补全。 +你输入函数的第一个参数 `first_name`,再输入一个点(`.`),然后按下 `Ctrl+Space` 触发补全。 -但遗憾的是并没有起什么作用: +但很遗憾,没有什么有用的提示: - + -### 添加类型 +### 添加类型 { #add-types } -让我们来修改上面例子的一行代码。 +我们来改前一个版本的一行代码。 -我们将把下面这段代码中的函数参数从: +把函数参数从: ```Python first_name, last_name @@ -78,227 +76,389 @@ John Doe 就是这样。 -这些就是"类型提示": +这些就是“类型提示”: -{* ../../docs_src/python_types/tutorial002.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} - -这和声明默认值是不同的,例如: +这和声明默认值不同,比如: ```Python first_name="john", last_name="doe" ``` -这两者不一样。 +这是两码事。 我们用的是冒号(`:`),不是等号(`=`)。 -而且添加类型提示一般不会改变原来的运行结果。 +而且添加类型提示通常不会改变代码本来的行为。 -现在假设我们又一次正在创建这个函数,这次添加了类型提示。 +现在,再想象你又在编写这个函数了,不过这次加上了类型提示。 -在同样的地方,通过 `Ctrl+Space` 触发自动补全,你会发现: +在同样的位置,你用 `Ctrl+Space` 触发自动补全,就能看到: - + -这样,你可以滚动查看选项,直到你找到看起来眼熟的那个: +这样,你可以滚动查看选项,直到找到那个“看着眼熟”的: - + -## 更多动机 +## 更多动机 { #more-motivation } -下面是一个已经有类型提示的函数: +看这个已经带有类型提示的函数: -{* ../../docs_src/python_types/tutorial003.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +因为编辑器知道变量的类型,你不仅能得到补全,还能获得错误检查: -因为编辑器已经知道了这些变量的类型,所以不仅能对代码进行补全,还能检查其中的错误: + - +现在你知道需要修复它,用 `str(age)` 把 `age` 转成字符串: -现在你知道了必须先修复这个问题,通过 `str(age)` 把 `age` 转换成字符串: +{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} -{* ../../docs_src/python_types/tutorial004.py hl[2] *} +## 声明类型 { #declaring-types } +你刚刚看到的是声明类型提示的主要位置:函数参数。 -## 声明类型 +这也是你在 **FastAPI** 中使用它们的主要场景。 -你刚刚看到的就是声明类型提示的主要场景。用于函数的参数。 +### 简单类型 { #simple-types } -这也是你将在 **FastAPI** 中使用它们的主要场景。 +你不仅可以声明 `str`,还可以声明所有标准的 Python 类型。 -### 简单类型 - -不只是 `str`,你能够声明所有的标准 Python 类型。 - -比如以下类型: +例如: * `int` * `float` * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +### 带类型参数的泛型类型 { #generic-types-with-type-parameters } -### 嵌套类型 +有些数据结构可以包含其他值,比如 `dict`、`list`、`set` 和 `tuple`。而内部的值也会有自己的类型。 -有些容器数据结构可以包含其他的值,比如 `dict`、`list`、`set` 和 `tuple`。它们内部的值也会拥有自己的类型。 +这些带有内部类型的类型称为“泛型”(generic)类型。可以把它们连同内部类型一起声明出来。 -你可以使用 Python 的 `typing` 标准库来声明这些类型以及子类型。 +要声明这些类型以及内部类型,你可以使用 Python 标准库模块 `typing`。它就是为支持这些类型提示而存在的。 -它专门用来支持这些类型提示。 +#### 更新的 Python 版本 { #newer-versions-of-python } -#### 列表 +使用 `typing` 的语法与所有版本兼容,从 Python 3.6 到最新版本(包括 Python 3.9、Python 3.10 等)。 -例如,让我们来定义一个由 `str` 组成的 `list` 变量。 +随着 Python 的发展,更新的版本对这些类型注解的支持更好,在很多情况下你甚至不需要导入和使用 `typing` 模块来声明类型注解。 -从 `typing` 模块导入 `List`(注意是大写的 `L`): +如果你可以为项目选择更高版本的 Python,你将能享受到这种额外的简化。 -{* ../../docs_src/python_types/tutorial006.py hl[1] *} +在整个文档中,会根据不同 Python 版本提供相应的示例(当存在差异时)。 +比如“Python 3.6+”表示兼容 Python 3.6 及以上(包括 3.7、3.8、3.9、3.10 等)。而“Python 3.9+”表示兼容 Python 3.9 及以上(包括 3.10 等)。 -同样以冒号(`:`)来声明这个变量。 +如果你可以使用最新的 Python 版本,请使用最新版本的示例,它们将拥有最简洁的语法,例如“Python 3.10+”。 -输入 `List` 作为类型。 +#### 列表 { #list } -由于列表是带有"子类型"的类型,所以我们把子类型放在方括号中: +例如,我们来定义一个由 `str` 组成的 `list` 变量。 -{* ../../docs_src/python_types/tutorial006.py hl[4] *} +用同样的冒号(`:`)语法声明变量。 +类型写 `list`。 -这表示:"变量 `items` 是一个 `list`,并且这个列表里的每一个元素都是 `str`"。 +因为 list 是一种包含内部类型的类型,把内部类型写在方括号里: -这样,即使在处理列表中的元素时,你的编辑器也可以提供支持。 +{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} -没有类型,几乎是不可能实现下面这样: +/// info | 信息 - +方括号中的这些内部类型称为“类型参数”(type parameters)。 -注意,变量 `item` 是列表 `items` 中的元素之一。 +在这个例子中,`str` 是传给 `list` 的类型参数。 -而且,编辑器仍然知道它是一个 `str`,并为此提供了支持。 +/// -#### 元组和集合 +这表示:“变量 `items` 是一个 `list`,并且列表中的每一个元素都是 `str`”。 -声明 `tuple` 和 `set` 的方法也是一样的: +这样,即使是在处理列表中的元素时,编辑器也能给你提供支持: -{* ../../docs_src/python_types/tutorial007.py hl[1,4] *} + +没有类型的话,这几乎是不可能做到的。 + +注意,变量 `item` 是列表 `items` 中的一个元素。 + +即便如此,编辑器仍然知道它是 `str`,并为此提供支持。 + +#### 元组和集合 { #tuple-and-set } + +声明 `tuple` 和 `set` 的方式类似: + +{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} 这表示: -* 变量 `items_t` 是一个 `tuple`,其中的前两个元素都是 `int` 类型, 最后一个元素是 `str` 类型。 -* 变量 `items_s` 是一个 `set`,其中的每个元素都是 `bytes` 类型。 +* 变量 `items_t` 是一个含有 3 个元素的 `tuple`,分别是一个 `int`、另一个 `int`,以及一个 `str`。 +* 变量 `items_s` 是一个 `set`,其中每个元素的类型是 `bytes`。 -#### 字典 +#### 字典 { #dict } -定义 `dict` 时,需要传入两个子类型,用逗号进行分隔。 +定义 `dict` 时,需要传入 2 个类型参数,用逗号分隔。 -第一个子类型声明 `dict` 的所有键。 +第一个类型参数用于字典的键。 -第二个子类型声明 `dict` 的所有值: - -{* ../../docs_src/python_types/tutorial008.py hl[1,4] *} +第二个类型参数用于字典的值: +{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} 这表示: * 变量 `prices` 是一个 `dict`: - * 这个 `dict` 的所有键为 `str` 类型(可以看作是字典内每个元素的名称)。 - * 这个 `dict` 的所有值为 `float` 类型(可以看作是字典内每个元素的价格)。 + * 这个 `dict` 的键是 `str` 类型(比如,每个条目的名称)。 + * 这个 `dict` 的值是 `float` 类型(比如,每个条目的价格)。 -### 类作为类型 +#### Union { #union } -你也可以将类声明为变量的类型。 +你可以声明一个变量可以是若干种类型中的任意一种,比如既可以是 `int` 也可以是 `str`。 -假设你有一个名为 `Person` 的类,拥有 name 属性: +在 Python 3.6 及以上(包括 Python 3.10),你可以使用 `typing` 中的 `Union`,把可能的类型放到方括号里。 -{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} - - -接下来,你可以将一个变量声明为 `Person` 类型: - -{* ../../docs_src/python_types/tutorial010.py hl[6] *} - - -然后,你将再次获得所有的编辑器支持: - - - -## Pydantic 模型 - -Pydantic 是一个用来执行数据校验的 Python 库。 - -你可以将数据的"结构"声明为具有属性的类。 - -每个属性都拥有类型。 - -接着你用一些值来创建这个类的实例,这些值会被校验,并被转换为适当的类型(在需要的情况下),返回一个包含所有数据的对象。 - -然后,你将获得这个对象的所有编辑器支持。 - -下面的例子来自 Pydantic 官方文档: +在 Python 3.10 中还有一种新的语法,可以用竖线(`|`)把可能的类型分隔开。 //// tab | Python 3.10+ -```Python -{!> ../../docs_src/python_types/tutorial011_py310.py!} +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` //// //// tab | Python 3.9+ -```Python -{!> ../../docs_src/python_types/tutorial011_py39.py!} +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial008b_py39.py!} ``` //// -//// tab | Python 3.8+ +两种方式的含义一致:`item` 可以是 `int` 或 `str`。 -```Python -{!> ../../docs_src/python_types/tutorial011.py!} +#### 可能为 `None` { #possibly-none } + +你可以声明一个值的类型是某种类型(比如 `str`),但它也可能是 `None`。 + +在 Python 3.6 及以上(包括 Python 3.10),你可以通过从 `typing` 模块导入并使用 `Optional` 来声明: + +```Python hl_lines="1 4" +{!../../docs_src/python_types/tutorial009_py39.py!} +``` + +使用 `Optional[str]` 而不是仅仅 `str`,可以让编辑器帮助你发现把值当成总是 `str` 的错误(实际上它也可能是 `None`)。 + +`Optional[Something]` 实际上是 `Union[Something, None]` 的简写,它们等价。 + +这也意味着在 Python 3.10 中,你可以使用 `Something | None`: + +//// tab | Python 3.10+ + +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial009_py310.py!} ``` //// +//// tab | Python 3.9+ -/// info +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial009_py39.py!} +``` -想进一步了解 Pydantic,请阅读其文档. +//// + +//// tab | Python 3.9+ 另一种写法 + +```Python hl_lines="1 4" +{!> ../../docs_src/python_types/tutorial009b_py39.py!} +``` + +//// + +#### 使用 `Union` 或 `Optional` { #using-union-or-optional } + +如果你使用的是 3.10 以下的 Python 版本,这里有个来自我非常主观的建议: + +* 🚨 避免使用 `Optional[SomeType]` +* 改用 ✨**`Union[SomeType, None]`**✨ + +两者等价,底层相同,但我更推荐 `Union` 而不是 `Optional`,因为“optional(可选)”这个词看起来像是在说“参数可选”,而它实际上表示“它可以是 `None`”,即使它并不是可选的,仍然是必填的。 + +我认为 `Union[SomeType, None]` 更明确地表达了它的意思。 + +这只是关于词语和名字。但这些词会影响你和你的队友如何理解代码。 + +例如,看下面这个函数: + +{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} + +参数 `name` 被定义为 `Optional[str]`,但它并不是“可选”的,你不能不传这个参数就调用函数: + +```Python +say_hi() # 哦不,这会抛错!😱 +``` + +参数 `name` 仍然是必填的(不是“可选”),因为它没有默认值。不过,`name` 接受 `None` 作为值: + +```Python +say_hi(name=None) # 这样可以,None 是有效值 🎉 +``` + +好消息是,一旦你使用 Python 3.10,就无需再为此操心,因为你可以直接用 `|` 来定义类型联合: + +{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} + +这样你就不必再考虑 `Optional` 和 `Union` 这些名字了。😎 + +#### 泛型类型 { #generic-types } + +这些在方括号中接收类型参数的类型称为“泛型类型”(Generic types)或“泛型”(Generics),例如: + +//// tab | Python 3.10+ + +你可以把同样的内建类型作为泛型使用(带方括号和内部类型): + +* `list` +* `tuple` +* `set` +* `dict` + +以及与之前的 Python 版本一样,来自 `typing` 模块的: + +* `Union` +* `Optional` +* ……以及其他。 + +在 Python 3.10 中,作为使用泛型 `Union` 和 `Optional` 的替代,你可以使用竖线(`|`)来声明类型联合,这更好也更简单。 + +//// + +//// tab | Python 3.9+ + +你可以把同样的内建类型作为泛型使用(带方括号和内部类型): + +* `list` +* `tuple` +* `set` +* `dict` + +以及来自 `typing` 模块的泛型: + +* `Union` +* `Optional` +* ……以及其他。 + +//// + +### 类作为类型 { #classes-as-types } + +你也可以把类声明为变量的类型。 + +假设你有一个名为 `Person` 的类,带有 name: + +{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} + +然后你可以声明一个变量是 `Person` 类型: + +{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} + +接着,你会再次获得所有的编辑器支持: + + + +注意,这表示“`one_person` 是类 `Person` 的一个实例(instance)”。 + +它并不表示“`one_person` 是名为 `Person` 的类本身(class)”。 + +## Pydantic 模型 { #pydantic-models } + +Pydantic 是一个用于执行数据校验的 Python 库。 + +你将数据的“结构”声明为带有属性的类。 + +每个属性都有一个类型。 + +然后你用一些值创建这个类的实例,它会校验这些值,并在需要时把它们转换为合适的类型,返回一个包含所有数据的对象。 + +你还能对这个结果对象获得完整的编辑器支持。 + +下面是来自 Pydantic 官方文档的一个示例: + +{* ../../docs_src/python_types/tutorial011_py310.py *} + +/// info | 信息 + +想了解更多关于 Pydantic 的信息,请查看其文档。 /// -整个 **FastAPI** 建立在 Pydantic 的基础之上。 +**FastAPI** 完全建立在 Pydantic 之上。 -实际上你将在 [教程 - 用户指南](tutorial/index.md){.internal-link target=_blank} 看到很多这种情况。 +你会在[教程 - 用户指南](tutorial/index.md){.internal-link target=_blank}中看到更多的实战示例。 -## **FastAPI** 中的类型提示 +/// tip | 提示 -**FastAPI** 利用这些类型提示来做下面几件事。 - -使用 **FastAPI** 时用类型提示声明参数可以获得: - -* **编辑器支持**。 -* **类型检查**。 - -...并且 **FastAPI** 还会用这些类型声明来: - -* **定义参数要求**:声明对请求路径参数、查询参数、请求头、请求体、依赖等的要求。 -* **转换数据**:将来自请求的数据转换为需要的类型。 -* **校验数据**: 对于每一个请求: - * 当数据校验失败时自动生成**错误信息**返回给客户端。 -* 使用 OpenAPI **记录** API: - * 然后用于自动生成交互式文档的用户界面。 - -听上去有点抽象。不过不用担心。你将在 [教程 - 用户指南](tutorial/index.md){.internal-link target=_blank} 中看到所有的实战。 - -最重要的是,通过使用标准的 Python 类型,只需要在一个地方声明(而不是添加更多的类、装饰器等),**FastAPI** 会为你完成很多的工作。 - -/// info - -如果你已经阅读了所有教程,回过头来想了解有关类型的更多信息,来自 `mypy` 的"速查表"是不错的资源。 +当你在没有默认值的情况下使用 `Optional` 或 `Union[Something, None]` 时,Pydantic 有一个特殊行为,你可以在 Pydantic 文档的 必填的 Optional 字段 中了解更多。 + +/// + +## 带元数据注解的类型提示 { #type-hints-with-metadata-annotations } + +Python 还提供了一个特性,可以使用 `Annotated` 在这些类型提示中放入额外的元数据。 + +从 Python 3.9 起,`Annotated` 是标准库的一部分,因此可以从 `typing` 导入。 + +{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} + +Python 本身不会对这个 `Annotated` 做任何处理。对于编辑器和其他工具,类型仍然是 `str`。 + +但你可以在 `Annotated` 中为 **FastAPI** 提供额外的元数据,来描述你希望应用如何行为。 + +重要的是要记住:传给 `Annotated` 的第一个类型参数才是实际类型。其余的只是给其他工具用的元数据。 + +现在你只需要知道 `Annotated` 的存在,并且它是标准 Python。😎 + +稍后你会看到它有多么强大。 + +/// tip | 提示 + +这是标准 Python,这意味着你仍然可以在编辑器里获得尽可能好的开发体验,并能和你用来分析、重构代码的工具良好协作等。✨ + +同时你的代码也能与许多其他 Python 工具和库高度兼容。🚀 + +/// + +## **FastAPI** 中的类型提示 { #type-hints-in-fastapi } + +**FastAPI** 利用这些类型提示来完成多件事情。 + +在 **FastAPI** 中,用类型提示来声明参数,你将获得: + +* 编辑器支持。 +* 类型检查。 + +……并且 **FastAPI** 会使用相同的声明来: + +* 定义要求:从请求路径参数、查询参数、请求头、请求体、依赖等。 +* 转换数据:把请求中的数据转换为所需类型。 +* 校验数据:对于每个请求: + * 当数据无效时,自动生成错误信息返回给客户端。 +* 使用 OpenAPI 记录 API: + * 然后用于自动生成交互式文档界面。 + +这些听起来可能有点抽象。别担心。你会在[教程 - 用户指南](tutorial/index.md){.internal-link target=_blank}中看到所有这些的实际效果。 + +重要的是,通过使用标准的 Python 类型,而且只在一个地方声明(而不是添加更多类、装饰器等),**FastAPI** 会为你完成大量工作。 + +/// info | 信息 + +如果你已经读完所有教程,又回来想进一步了解类型,一个不错的资源是 `mypy` 的“速查表”。 /// diff --git a/docs/zh/docs/tutorial/background-tasks.md b/docs/zh/docs/tutorial/background-tasks.md index b9becd8bf..8398472c3 100644 --- a/docs/zh/docs/tutorial/background-tasks.md +++ b/docs/zh/docs/tutorial/background-tasks.md @@ -1,4 +1,4 @@ -# 后台任务 +# 后台任务 { #background-tasks } 你可以定义在返回响应后运行的后台任务。 @@ -11,15 +11,15 @@ * 处理数据: * 例如,假设您收到的文件必须经过一个缓慢的过程,您可以返回一个"Accepted"(HTTP 202)响应并在后台处理它。 -## 使用 `BackgroundTasks` +## 使用 `BackgroundTasks` { #using-backgroundtasks } 首先导入 `BackgroundTasks` 并在 *路径操作函数* 中使用类型声明 `BackgroundTasks` 定义一个参数: -{* ../../docs_src/background_tasks/tutorial001.py hl[1, 13] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} **FastAPI** 会创建一个 `BackgroundTasks` 类型的对象并作为该参数传入。 -## 创建一个任务函数 +## 创建一个任务函数 { #create-a-task-function } 创建要作为后台任务运行的函数。 @@ -31,13 +31,13 @@ 由于写操作不使用 `async` 和 `await`,我们用普通的 `def` 定义函数: -{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} -## 添加后台任务 +## 添加后台任务 { #add-the-background-task } 在你的 *路径操作函数* 里,用 `.add_task()` 方法将任务函数传到 *后台任务* 对象中: -{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} `.add_task()` 接收以下参数: @@ -45,53 +45,13 @@ * 应按顺序传递给任务函数的任意参数序列(`email`)。 * 应传递给任务函数的任意关键字参数(`message="some notification"`)。 -## 依赖注入 +## 依赖注入 { #dependency-injection } 使用 `BackgroundTasks` 也适用于依赖注入系统,你可以在多个级别声明 `BackgroundTasks` 类型的参数:在 *路径操作函数* 里,在依赖中(可依赖),在子依赖中,等等。 **FastAPI** 知道在每种情况下该做什么以及如何复用同一对象,因此所有后台任务被合并在一起并且随后在后台运行: -//// tab | Python 3.10+ - -{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13, 15, 22, 25] *} - -//// - -//// tab | Python 3.9+ - -{* ../../docs_src/background_tasks/tutorial002_an_py39.py hl[13, 15, 22, 25] *} - -//// - -//// tab | Python 3.8+ - -{* ../../docs_src/background_tasks/tutorial002_an.py hl[14, 16, 23, 26] *} - -//// - -//// tab | Python 3.10+ 没Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -{* ../../docs_src/background_tasks/tutorial002_py310.py hl[11, 13, 20, 23] *} - -//// - -//// tab | Python 3.8+ 没Annotated - -/// tip - -尽可能选择使用 `Annotated` 的版本。 - -/// - -{* ../../docs_src/background_tasks/tutorial002.py hl[13, 15, 22, 25] *} - -//// +{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *} 该示例中,信息会在响应发出 *之后* 被写到 `log.txt` 文件。 @@ -99,7 +59,7 @@ 然后另一个在 *路径操作函数* 生成的后台任务会使用路径参数 `email` 写入一条信息。 -## 技术细节 +## 技术细节 { #technical-details } `BackgroundTasks` 类直接来自 `starlette.background`。 @@ -111,7 +71,7 @@ 更多细节查看 Starlette's official docs for Background Tasks. -## 告诫 +## 告诫 { #caveat } 如果您需要执行繁重的后台计算,并且不一定需要由同一进程运行(例如,您不需要共享内存、变量等),那么使用其他更大的工具(如 Celery)可能更好。 @@ -119,6 +79,6 @@ 但是,如果您需要从同一个**FastAPI**应用程序访问变量和对象,或者您需要执行小型后台任务(如发送电子邮件通知),您只需使用 `BackgroundTasks` 即可。 -## 回顾 +## 回顾 { #recap } 导入并使用 `BackgroundTasks` 通过 *路径操作函数* 中的参数和依赖项来添加后台任务。 diff --git a/docs/zh/docs/tutorial/bigger-applications.md b/docs/zh/docs/tutorial/bigger-applications.md index 554bc654f..1ced002dc 100644 --- a/docs/zh/docs/tutorial/bigger-applications.md +++ b/docs/zh/docs/tutorial/bigger-applications.md @@ -1,16 +1,16 @@ -# 更大的应用 - 多个文件 +# 更大的应用 - 多个文件 { #bigger-applications-multiple-files } 如果你正在开发一个应用程序或 Web API,很少会将所有的内容都放在一个文件中。 **FastAPI** 提供了一个方便的工具,可以在保持所有灵活性的同时构建你的应用程序。 -/// info +/// info | 信息 如果你来自 Flask,那这将相当于 Flask 的 Blueprints。 /// -## 一个文件结构示例 +## 一个文件结构示例 { #an-example-file-structure } 假设你的文件结构如下: @@ -29,7 +29,7 @@ │   └── admin.py ``` -/// tip +/// tip | 提示 上面有几个 `__init__.py` 文件:每个目录或子目录中都有一个。 @@ -52,11 +52,11 @@ from app.routers import items * 还有一个子目录 `app/internal/` 包含另一个 `__init__.py` 文件,因此它是又一个「Python 子包」:`app.internal`。 * `app/internal/admin.py` 是另一个子模块:`app.internal.admin`。 - + 带有注释的同一文件结构: -``` +```bash . ├── app # 「app」是一个 Python 包 │   ├── __init__.py # 这个文件使「app」成为一个 Python 包 @@ -71,7 +71,7 @@ from app.routers import items │   └── admin.py # 「admin」子模块,例如 import app.internal.admin ``` -## `APIRouter` +## `APIRouter` { #apirouter } 假设专门用于处理用户逻辑的文件是位于 `/app/routers/users.py` 的子模块。 @@ -81,23 +81,19 @@ from app.routers import items 你可以使用 `APIRouter` 为该模块创建*路径操作*。 -### 导入 `APIRouter` +### 导入 `APIRouter` { #import-apirouter } 你可以导入它并通过与 `FastAPI` 类相同的方式创建一个「实例」: -```Python hl_lines="1 3" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} -### 使用 `APIRouter` 的*路径操作* +### 使用 `APIRouter` 的*路径操作* { #path-operations-with-apirouter } 然后你可以使用它来声明*路径操作*。 使用方式与 `FastAPI` 类相同: -```Python hl_lines="6 11 16" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} 你可以将 `APIRouter` 视为一个「迷你 `FastAPI`」类。 @@ -105,7 +101,7 @@ from app.routers import items 所有相同的 `parameters`、`responses`、`dependencies`、`tags` 等等。 -/// tip +/// tip | 提示 在此示例中,该变量被命名为 `router`,但你可以根据你的想法自由命名。 @@ -113,7 +109,7 @@ from app.routers import items 我们将在主 `FastAPI` 应用中包含该 `APIRouter`,但首先,让我们来看看依赖项和另一个 `APIRouter`。 -## 依赖项 +## 依赖项 { #dependencies } 我们了解到我们将需要一些在应用程序的好几个地方所使用的依赖项。 @@ -121,11 +117,9 @@ from app.routers import items 现在我们将使用一个简单的依赖项来读取一个自定义的 `X-Token` 请求首部: -```Python hl_lines="1 4-6" title="app/dependencies.py" -{!../../docs_src/bigger_applications/app/dependencies.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} -/// tip +/// tip | 提示 我们正在使用虚构的请求首部来简化此示例。 @@ -133,7 +127,7 @@ from app.routers import items /// -## 其他使用 `APIRouter` 的模块 +## 其他使用 `APIRouter` 的模块 { #another-module-with-apirouter } 假设你在位于 `app/routers/items.py` 的模块中还有专门用于处理应用程序中「项目」的端点。 @@ -155,9 +149,7 @@ from app.routers import items 因此,我们可以将其添加到 `APIRouter` 中,而不是将其添加到每个路径操作中。 -```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} 由于每个*路径操作*的路径都必须以 `/` 开头,例如: @@ -175,7 +167,7 @@ async def read_item(item_id: str): 我们可以添加一个 `dependencies` 列表,这些依赖项将被添加到路由器中的所有*路径操作*中,并将针对向它们发起的每个请求执行/解决。 -/// tip +/// tip | 提示 请注意,和[*路径操作装饰器*中的依赖项](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}很类似,没有值会被传递给你的*路径操作函数*。 @@ -196,19 +188,19 @@ async def read_item(item_id: str): * 路由器的依赖项最先执行,然后是[装饰器中的 `dependencies`](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank},再然后是普通的参数依赖项。 * 你还可以添加[具有 `scopes` 的 `Security` 依赖项](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}。 -/// tip +/// tip | 提示 在 `APIRouter`中具有 `dependencies` 可以用来,例如,对一整组的*路径操作*要求身份认证。即使这些依赖项并没有分别添加到每个路径操作中。 /// -/// check +/// check | 检查 `prefix`、`tags`、`responses` 以及 `dependencies` 参数只是(和其他很多情况一样)**FastAPI** 的一个用于帮助你避免代码重复的功能。 /// -### 导入依赖项 +### 导入依赖项 { #import-the-dependencies } 这些代码位于 `app.routers.items` 模块,`app/routers/items.py` 文件中。 @@ -216,13 +208,11 @@ async def read_item(item_id: str): 因此,我们通过 `..` 对依赖项使用了相对导入: -```Python hl_lines="3" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} -#### 相对导入如何工作 +#### 相对导入如何工作 { #how-relative-imports-work } -/// tip +/// tip | 提示 如果你完全了解导入的工作原理,请从下面的下一部分继续。 @@ -244,7 +234,7 @@ from .dependencies import get_token_header 请记住我们的程序/文件结构是怎样的: - + --- @@ -276,24 +266,22 @@ from ...dependencies import get_token_header * 从该模块(`app/routers/items.py` 文件)所在的同一个包(`app/routers/` 目录)开始... * 跳转到其父包(`app/` 目录)... * 然后跳转到该包的父包(该父包并不存在,`app` 已经是最顶层的包 😱)... -* 在该父包中,找到 `dependencies` 模块(位于 `app/` 更上一级目录中的 `dependencies.py` 文件)... +* 在该父包中,找到 `dependencies` 模块(位于 `app/dependencies.py` 的文件)... * 然后从中导入函数 `get_token_header`。 这将引用 `app/` 的往上一级,带有其自己的 `__init __.py` 等文件的某个包。但是我们并没有这个包。因此,这将在我们的示例中引发错误。🚨 但是现在你知道了它的工作原理,因此无论它们多么复杂,你都可以在自己的应用程序中使用相对导入。🤓 -### 添加一些自定义的 `tags`、`responses` 和 `dependencies` +### 添加一些自定义的 `tags`、`responses` 和 `dependencies` { #add-some-custom-tags-responses-and-dependencies } 我们不打算在每个*路径操作*中添加前缀 `/items` 或 `tags =["items"]`,因为我们将它们添加到了 `APIRouter` 中。 但是我们仍然可以添加*更多*将会应用于特定的*路径操作*的 `tags`,以及一些特定于该*路径操作*的额外 `responses`: -```Python hl_lines="30-31" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} -/// tip +/// tip | 提示 最后的这个路径操作将包含标签的组合:`["items","custom"]`。 @@ -301,7 +289,7 @@ from ...dependencies import get_token_header /// -## `FastAPI` 主体 +## `FastAPI` 主体 { #the-main-fastapi } 现在,让我们来看看位于 `app/main.py` 的模块。 @@ -311,27 +299,23 @@ from ...dependencies import get_token_header 并且由于你的大部分逻辑现在都存在于其自己的特定模块中,因此主文件的内容将非常简单。 -### 导入 `FastAPI` +### 导入 `FastAPI` { #import-fastapi } 你可以像平常一样导入并创建一个 `FastAPI` 类。 我们甚至可以声明[全局依赖项](dependencies/global-dependencies.md){.internal-link target=_blank},它会和每个 `APIRouter` 的依赖项组合在一起: -```Python hl_lines="1 3 7" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} -### 导入 `APIRouter` +### 导入 `APIRouter` { #import-the-apirouter } 现在,我们导入具有 `APIRouter` 的其他子模块: -```Python hl_lines="5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} 由于文件 `app/routers/users.py` 和 `app/routers/items.py` 是同一 Python 包 `app` 一个部分的子模块,因此我们可以使用单个点 ` .` 通过「相对导入」来导入它们。 -### 导入是如何工作的 +### 导入是如何工作的 { #how-the-importing-works } 这段代码: @@ -355,7 +339,7 @@ from .routers import items, users from app.routers import items, users ``` -/// info +/// info | 信息 第一个版本是「相对导入」: @@ -373,7 +357,7 @@ from app.routers import items, users /// -### 避免名称冲突 +### 避免名称冲突 { #avoid-name-collisions } 我们将直接导入 `items` 子模块,而不是仅导入其 `router` 变量。 @@ -390,19 +374,15 @@ from .routers.users import router 因此,为了能够在同一个文件中使用它们,我们直接导入子模块: -```Python hl_lines="5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} -### 包含 `users` 和 `items` 的 `APIRouter` +### 包含 `users` 和 `items` 的 `APIRouter` { #include-the-apirouters-for-users-and-items } 现在,让我们来包含来自 `users` 和 `items` 子模块的 `router`。 -```Python hl_lines="10-11" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} -/// info +/// info | 信息 `users.router` 包含了 `app/routers/users.py` 文件中的 `APIRouter`。 @@ -422,7 +402,7 @@ from .routers.users import router /// -/// check +/// check | 检查 包含路由器时,你不必担心性能问题。 @@ -432,7 +412,7 @@ from .routers.users import router /// -### 包含一个有自定义 `prefix`、`tags`、`responses` 和 `dependencies` 的 `APIRouter` +### 包含一个有自定义 `prefix`、`tags`、`responses` 和 `dependencies` 的 `APIRouter` { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies } 现在,假设你的组织为你提供了 `app/internal/admin.py` 文件。 @@ -440,17 +420,13 @@ from .routers.users import router 对于此示例,它将非常简单。但是假设由于它是与组织中的其他项目所共享的,因此我们无法对其进行修改,以及直接在 `APIRouter` 中添加 `prefix`、`dependencies`、`tags` 等: -```Python hl_lines="3" title="app/internal/admin.py" -{!../../docs_src/bigger_applications/app/internal/admin.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} 但是我们仍然希望在包含 `APIRouter` 时设置一个自定义的 `prefix`,以便其所有*路径操作*以 `/admin` 开头,我们希望使用本项目已经有的 `dependencies` 保护它,并且我们希望它包含自定义的 `tags` 和 `responses`。 我们可以通过将这些参数传递给 `app.include_router()` 来完成所有的声明,而不必修改原始的 `APIRouter`: -```Python hl_lines="14-17" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} 这样,原始的 `APIRouter` 将保持不变,因此我们仍然可以与组织中的其他项目共享相同的 `app/internal/admin.py` 文件。 @@ -465,15 +441,13 @@ from .routers.users import router 因此,举例来说,其他项目能够以不同的身份认证方法使用相同的 `APIRouter`。 -### 包含一个*路径操作* +### 包含一个*路径操作* { #include-a-path-operation } 我们还可以直接将*路径操作*添加到 `FastAPI` 应用中。 这里我们这样做了...只是为了表明我们可以做到🤷: -```Python hl_lines="21-23" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} 它将与通过 `app.include_router()` 添加的所有其他*路径操作*一起正常运行。 @@ -491,14 +465,14 @@ from .routers.users import router /// -## 查看自动化的 API 文档 +## 查看自动化的 API 文档 { #check-the-automatic-api-docs } -现在,使用 `app.main` 模块和 `app` 变量运行 `uvicorn`: +现在,运行你的应用:
```console -$ uvicorn app.main:app --reload +$ fastapi dev app/main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -509,9 +483,9 @@ $ uvicorn app.main:app --reload 你将看到使用了正确路径(和前缀)和正确标签的自动化 API 文档,包括了来自所有子模块的路径: - + -## 多次使用不同的 `prefix` 包含同一个路由器 +## 多次使用不同的 `prefix` 包含同一个路由器 { #include-the-same-router-multiple-times-with-different-prefix } 你也可以在*同一*路由器上使用不同的前缀来多次使用 `.include_router()`。 @@ -519,7 +493,7 @@ $ uvicorn app.main:app --reload 这是一个你可能并不真正需要的高级用法,但万一你有需要了就能够用上。 -## 在另一个 `APIRouter` 中包含一个 `APIRouter` +## 在另一个 `APIRouter` 中包含一个 `APIRouter` { #include-an-apirouter-in-another } 与在 `FastAPI` 应用程序中包含 `APIRouter` 的方式相同,你也可以在另一个 `APIRouter` 中包含 `APIRouter`,通过: @@ -527,4 +501,4 @@ $ uvicorn app.main:app --reload router.include_router(other_router) ``` -请确保在你将 `router` 包含到 `FastAPI` 应用程序之前进行此操作,以便 `other_router` 中的`路径操作`也能被包含进来。 +请确保在你将 `router` 包含到 `FastAPI` 应用程序之前进行此操作,以便 `other_router` 中的*路径操作*也能被包含进来。 diff --git a/docs/zh/docs/tutorial/body-fields.md b/docs/zh/docs/tutorial/body-fields.md index 4cff58bfc..36be7c419 100644 --- a/docs/zh/docs/tutorial/body-fields.md +++ b/docs/zh/docs/tutorial/body-fields.md @@ -1,8 +1,8 @@ -# 请求体 - 字段 +# 请求体 - 字段 { #body-fields } 与在*路径操作函数*中使用 `Query`、`Path` 、`Body` 声明校验与元数据的方式一样,可以使用 Pydantic 的 `Field` 在 Pydantic 模型内部声明校验和元数据。 -## 导入 `Field` +## 导入 `Field` { #import-field } 首先,从 Pydantic 中导入 `Field`: @@ -14,7 +14,7 @@ /// -## 声明模型属性 +## 声明模型属性 { #declare-model-attributes } 然后,使用 `Field` 定义模型的属性: @@ -24,7 +24,7 @@ /// note | 技术细节 -实际上,`Query`、`Path` 都是 `Params` 的子类,而 `Params` 类又是 Pydantic 中 `FieldInfo` 的子类。 +实际上,`Query`、`Path` 以及你接下来会看到的其它对象,会创建公共 `Param` 类的子类的对象,而 `Param` 本身是 Pydantic 中 `FieldInfo` 的子类。 Pydantic 的 `Field` 返回也是 `FieldInfo` 的类实例。 @@ -40,13 +40,20 @@ Pydantic 的 `Field` 返回也是 `FieldInfo` 的类实例。 /// -## 添加更多信息 +## 添加更多信息 { #add-extra-information } `Field`、`Query`、`Body` 等对象里可以声明更多信息,并且 JSON Schema 中也会集成这些信息。 *声明示例*一章中将详细介绍添加更多信息的知识。 -## 小结 +/// warning | 警告 + +传递给 `Field` 的额外键也会出现在你的应用生成的 OpenAPI 架构中。 +由于这些键不一定属于 OpenAPI 规范的一部分,某些 OpenAPI 工具(例如 [OpenAPI 验证器](https://validator.swagger.io/))可能无法处理你生成的架构。 + +/// + +## 小结 { #recap } Pydantic 的 `Field` 可以为模型属性声明更多校验和元数据。 diff --git a/docs/zh/docs/tutorial/body-multiple-params.md b/docs/zh/docs/tutorial/body-multiple-params.md index b4356fdcb..7d0ddfc1e 100644 --- a/docs/zh/docs/tutorial/body-multiple-params.md +++ b/docs/zh/docs/tutorial/body-multiple-params.md @@ -1,8 +1,8 @@ -# 请求体 - 多个参数 +# 请求体 - 多个参数 { #body-multiple-parameters } 既然我们已经知道了如何使用 `Path` 和 `Query`,下面让我们来了解一下请求体声明的更高级用法。 -## 混合使用 `Path`、`Query` 和请求体参数 +## 混合使用 `Path`、`Query` 和请求体参数 { #mix-path-query-and-body-parameters } 首先,毫无疑问地,你可以随意地混合使用 `Path`、`Query` 和请求体参数声明,**FastAPI** 会知道该如何处理。 @@ -10,13 +10,13 @@ {* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} -/// note +/// note | 注意 请注意,在这种情况下,将从请求体获取的 `item` 是可选的。因为它的默认值为 `None`。 /// -## 多个请求体参数 +## 多个请求体参数 { #multiple-body-parameters } 在上面的示例中,*路径操作*将期望一个具有 `Item` 的属性的 JSON 请求体,就像: @@ -52,7 +52,7 @@ } ``` -/// note +/// note | 注意 请注意,即使 `item` 的声明方式与之前相同,但现在它被期望通过 `item` 键内嵌在请求体中。 @@ -62,7 +62,7 @@ 它将执行对复合数据的校验,并且像现在这样为 OpenAPI 模式和自动化文档对其进行记录。 -## 请求体中的单一值 +## 请求体中的单一值 { #singular-values-in-body } 与使用 `Query` 和 `Path` 为查询参数和路径参数定义额外数据的方式相同,**FastAPI** 提供了一个同等的 `Body`。 @@ -72,12 +72,10 @@ 但是你可以使用 `Body` 指示 **FastAPI** 将其作为请求体的另一个键进行处理。 - {* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *} 在这种情况下,**FastAPI** 将期望像这样的请求体: - ```JSON { "item": { @@ -96,27 +94,33 @@ 同样的,它将转换数据类型,校验,生成文档等。 -## 多个请求体参数和查询参数 +## 多个请求体参数和查询参数 { #multiple-body-params-and-query } 当然,除了请求体参数外,你还可以在任何需要的时候声明额外的查询参数。 -由于默认情况下单一值被解释为查询参数,因此你不必显式地添加 `Query`,你可以仅执行以下操作: +由于默认情况下单一值会被解释为查询参数,因此你不必显式地添加 `Query`,你可以这样写: ```Python -q: str = None +q: str | None = None +``` + +或者在 Python 3.9 中: + +```Python +q: Union[str, None] = None ``` 比如: -{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[27] *} +{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} -/// info +/// info | 信息 `Body` 同样具有与 `Query`、`Path` 以及其他后面将看到的类完全相同的额外校验和元数据参数。 /// -## 嵌入单个请求体参数 +## 嵌入单个请求体参数 { #embed-a-single-body-parameter } 假设你只有一个来自 Pydantic 模型 `Item` 的请求体参数 `item`。 @@ -156,7 +160,7 @@ item: Item = Body(embed=True) } ``` -## 总结 +## 总结 { #recap } 你可以添加多个请求体参数到*路径操作函数*中,即使一个请求只能有一个请求体。 diff --git a/docs/zh/docs/tutorial/body-nested-models.md b/docs/zh/docs/tutorial/body-nested-models.md index df96d96b4..242aa5822 100644 --- a/docs/zh/docs/tutorial/body-nested-models.md +++ b/docs/zh/docs/tutorial/body-nested-models.md @@ -1,53 +1,42 @@ -# 请求体 - 嵌套模型 +# 请求体 - 嵌套模型 { #body-nested-models } 使用 **FastAPI**,你可以定义、校验、记录文档并使用任意深度嵌套的模型(归功于Pydantic)。 -## List 字段 +## List 字段 { #list-fields } -你可以将一个属性定义为拥有子元素的类型。例如 Python `list`: +你可以将一个属性定义为一个子类型。例如,Python `list`: {* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *} 这将使 `tags` 成为一个由元素组成的列表。不过它没有声明每个元素的类型。 -## 具有子类型的 List 字段 +## 带类型参数的 List 字段 { #list-fields-with-type-parameter } -但是 Python 有一种特定的方法来声明具有子类型的列表: +不过,Python 有一种用于声明具有内部类型(类型参数)的列表的特定方式: -### 从 typing 导入 `List` +### 声明带类型参数的 `list` { #declare-a-list-with-a-type-parameter } -首先,从 Python 的标准库 `typing` 模块中导入 `List`: - -{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} - -### 声明具有子类型的 List - -要声明具有子类型的类型,例如 `list`、`dict`、`tuple`: - -* 从 `typing` 模块导入它们 -* 使用方括号 `[` 和 `]` 将子类型作为「类型参数」传入 +要声明具有类型参数(内部类型)的类型,例如 `list`、`dict`、`tuple`,使用方括号 `[` 和 `]` 传入内部类型作为「类型参数」: ```Python -from typing import List - -my_list: List[str] +my_list: list[str] ``` 这完全是用于类型声明的标准 Python 语法。 -对具有子类型的模型属性也使用相同的标准语法。 +对具有内部类型的模型属性也使用相同的标准语法。 因此,在我们的示例中,我们可以将 `tags` 明确地指定为一个「字符串列表」: {* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *} -## Set 类型 +## Set 类型 { #set-types } 但是随后我们考虑了一下,意识到标签不应该重复,它们很大可能会是唯一的字符串。 -Python 具有一种特殊的数据类型来保存一组唯一的元素,即 `set`。 +而 Python 有一种用于保存唯一元素集合的特殊数据类型 `set`。 -然后我们可以导入 `Set` 并将 `tag` 声明为一个由 `str` 组成的 `set`: +然后我们可以将 `tags` 声明为一个由字符串组成的 set: {* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *} @@ -57,7 +46,7 @@ Python 具有一种特殊的数据类型来保存一组唯一的元素,即 `se 并且还会被相应地标注 / 记录文档。 -## 嵌套模型 +## 嵌套模型 { #nested-models } Pydantic 模型的每个属性都具有类型。 @@ -67,13 +56,13 @@ Pydantic 模型的每个属性都具有类型。 上述这些都可以任意的嵌套。 -### 定义子模型 +### 定义子模型 { #define-a-submodel } 例如,我们可以定义一个 `Image` 模型: {* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *} -### 将子模型用作类型 +### 将子模型用作类型 { #use-the-submodel-as-a-type } 然后我们可以将其用作一个属性的类型: @@ -102,11 +91,11 @@ Pydantic 模型的每个属性都具有类型。 * 数据校验 * 自动生成文档 -## 特殊的类型和校验 +## 特殊的类型和校验 { #special-types-and-validation } 除了普通的单一值类型(如 `str`、`int`、`float` 等)外,你还可以使用从 `str` 继承的更复杂的单一值类型。 -要了解所有的可用选项,请查看关于 来自 Pydantic 的外部类型 的文档。你将在下一章节中看到一些示例。 +要了解所有的可用选项,请查看 Pydantic 的类型概览。你将在下一章节中看到一些示例。 例如,在 `Image` 模型中我们有一个 `url` 字段,我们可以把它声明为 Pydantic 的 `HttpUrl`,而不是 `str`: @@ -114,7 +103,7 @@ Pydantic 模型的每个属性都具有类型。 该字符串将被检查是否为有效的 URL,并在 JSON Schema / OpenAPI 文档中进行记录。 -## 带有一组子模型的属性 +## 带有一组子模型的属性 { #attributes-with-lists-of-submodels } 你还可以将 Pydantic 模型用作 `list`、`set` 等的子类型: @@ -146,49 +135,49 @@ Pydantic 模型的每个属性都具有类型。 } ``` -/// info +/// info | 信息 请注意 `images` 键现在具有一组 image 对象是如何发生的。 /// -## 深度嵌套模型 +## 深度嵌套模型 { #deeply-nested-models } 你可以定义任意深度的嵌套模型: {* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *} -/// info +/// info | 信息 请注意 `Offer` 拥有一组 `Item` 而反过来 `Item` 又是一个可选的 `Image` 列表是如何发生的。 /// -## 纯列表请求体 +## 纯列表请求体 { #bodies-of-pure-lists } 如果你期望的 JSON 请求体的最外层是一个 JSON `array`(即 Python `list`),则可以在路径操作函数的参数中声明此类型,就像声明 Pydantic 模型一样: ```Python -images: List[Image] +images: list[Image] ``` 例如: {* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} -## 无处不在的编辑器支持 +## 无处不在的编辑器支持 { #editor-support-everywhere } 你可以随处获得编辑器支持。 即使是列表中的元素: - + 如果你直接使用 `dict` 而不是 Pydantic 模型,那你将无法获得这种编辑器支持。 但是你根本不必担心这两者,传入的字典会自动被转换,你的输出也会自动被转换为 JSON。 -## 任意 `dict` 构成的请求体 +## 任意 `dict` 构成的请求体 { #bodies-of-arbitrary-dicts } 你也可以将请求体声明为使用某类型的键和其他类型值的 `dict`。 @@ -206,7 +195,7 @@ images: List[Image] {* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} -/// tip +/// tip | 提示 请记住 JSON 仅支持将 `str` 作为键。 @@ -218,7 +207,7 @@ images: List[Image] /// -## 总结 +## 总结 { #recap } 使用 **FastAPI** 你可以拥有 Pydantic 模型提供的极高灵活性,同时保持代码的简单、简短和优雅。 diff --git a/docs/zh/docs/tutorial/body-updates.md b/docs/zh/docs/tutorial/body-updates.md index 87f88f255..000201de9 100644 --- a/docs/zh/docs/tutorial/body-updates.md +++ b/docs/zh/docs/tutorial/body-updates.md @@ -1,18 +1,18 @@ -# 请求体 - 更新数据 +# 请求体 - 更新数据 { #body-updates } -## 用 `PUT` 更新数据 +## 用 `PUT` 替换式更新 { #update-replacing-with-put } -更新数据请用 HTTP `PUT` 操作。 +更新数据可以使用 HTTP `PUT` 操作。 把输入数据转换为以 JSON 格式存储的数据(比如,使用 NoSQL 数据库时),可以使用 `jsonable_encoder`。例如,把 `datetime` 转换为 `str`。 -{* ../../docs_src/body_updates/tutorial001.py hl[30:35] *} +{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *} `PUT` 用于接收替换现有数据的数据。 -### 关于更新数据的警告 +### 关于替换的警告 { #warning-about-replacing } -用 `PUT` 把数据项 `bar` 更新为以下内容时: +用 `PUT` 把数据项 `bar` 更新为以下请求体时: ```Python { @@ -22,78 +22,79 @@ } ``` -因为上述数据未包含已存储的属性 `"tax": 20.2`,新的输入模型会把 `"tax": 10.5` 作为默认值。 +因为其中未包含已存储的属性 `"tax": 20.2`,输入模型会取 `"tax": 10.5` 的默认值。 -因此,本次操作把 `tax` 的值「更新」为 `10.5`。 +因此,保存的数据会带有这个“新的” `tax` 值 `10.5`。 -## 用 `PATCH` 进行部分更新 +## 用 `PATCH` 进行部分更新 { #partial-updates-with-patch } -HTTP `PATCH` 操作用于更新 *部分* 数据。 +也可以使用 HTTP `PATCH` 操作对数据进行*部分*更新。 -即,只发送要更新的数据,其余数据保持不变。 +也就是说,你只需发送想要更新的数据,其余数据保持不变。 -/// note | 笔记 +/// note | 注意 -`PATCH` 没有 `PUT` 知名,也怎么不常用。 +`PATCH` 没有 `PUT` 知名,也没那么常用。 -很多人甚至只用 `PUT` 实现部分更新。 +很多团队甚至只用 `PUT` 实现部分更新。 -**FastAPI** 对此没有任何限制,可以**随意**互换使用这两种操作。 +你可以**随意**选择如何使用它们,**FastAPI** 不做任何限制。 -但本指南也会分别介绍这两种操作各自的用途。 +但本指南会大致展示它们的预期用法。 /// -### 使用 Pydantic 的 `exclude_unset` 参数 +### 使用 Pydantic 的 `exclude_unset` 参数 { #using-pydantics-exclude-unset-parameter } -更新部分数据时,可以在 Pydantic 模型的 `.dict()` 中使用 `exclude_unset` 参数。 +如果要接收部分更新,建议在 Pydantic 模型的 `.model_dump()` 中使用 `exclude_unset` 参数。 -比如,`item.dict(exclude_unset=True)`。 +比如,`item.model_dump(exclude_unset=True)`。 -这段代码生成的 `dict` 只包含创建 `item` 模型时显式设置的数据,而不包括默认值。 +这会生成一个 `dict`,只包含创建 `item` 模型时显式设置的数据,不包含默认值。 -然后再用它生成一个只含已设置(在请求中所发送)数据,且省略了默认值的 `dict`: +然后再用它生成一个只含已设置(在请求中发送)数据、且省略默认值的 `dict`: -{* ../../docs_src/body_updates/tutorial002.py hl[34] *} +{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *} -### 使用 Pydantic 的 `update` 参数 +### 使用 Pydantic 的 `update` 参数 { #using-pydantics-update-parameter } -接下来,用 `.copy()` 为已有模型创建调用 `update` 参数的副本,该参数为包含更新数据的 `dict`。 +接下来,用 `.model_copy()` 为已有模型创建副本,并传入 `update` 参数,值为包含更新数据的 `dict`。 -例如,`stored_item_model.copy(update=update_data)`: +例如,`stored_item_model.model_copy(update=update_data)`: -{* ../../docs_src/body_updates/tutorial002.py hl[35] *} +{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} -### 更新部分数据小结 +### 部分更新小结 { #partial-updates-recap } -简而言之,更新部分数据应: +简而言之,应用部分更新应当: -* 使用 `PATCH` 而不是 `PUT` (可选,也可以用 `PUT`); -* 提取存储的数据; -* 把数据放入 Pydantic 模型; -* 生成不含输入模型默认值的 `dict` (使用 `exclude_unset` 参数); - * 只更新用户设置过的值,不用模型中的默认值覆盖已存储过的值。 -* 为已存储的模型创建副本,用接收的数据更新其属性 (使用 `update` 参数)。 +* (可选)使用 `PATCH` 而不是 `PUT`。 +* 提取已存储的数据。 +* 把该数据放入 Pydantic 模型。 +* 生成不含输入模型默认值的 `dict`(使用 `exclude_unset`)。 + * 这样只会更新用户实际设置的值,而不会用模型中的默认值覆盖已存储的值。 +* 为已存储的模型创建副本,用接收到的部分更新数据更新其属性(使用 `update` 参数)。 * 把模型副本转换为可存入数据库的形式(比如,使用 `jsonable_encoder`)。 - * 这种方式与 Pydantic 模型的 `.dict()` 方法类似,但能确保把值转换为适配 JSON 的数据类型,例如, 把 `datetime` 转换为 `str` 。 -* 把数据保存至数据库; + * 这类似于再次调用模型的 `.model_dump()` 方法,但会确保(并转换)值为可转换为 JSON 的数据类型,例如把 `datetime` 转换为 `str`。 +* 把数据保存至数据库。 * 返回更新后的模型。 -{* ../../docs_src/body_updates/tutorial002.py hl[30:37] *} +{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *} /// tip | 提示 -实际上,HTTP `PUT` 也可以完成相同的操作。 -但本节以 `PATCH` 为例的原因是,该操作就是为了这种用例创建的。 +实际上,HTTP `PUT` 也可以使用同样的技巧。 + +但这里用 `PATCH` 举例,因为它就是为这种用例设计的。 /// -/// note | 笔记 +/// note | 注意 -注意,输入模型仍需验证。 +注意,输入模型仍会被验证。 -因此,如果希望接收的部分更新数据可以省略其他所有属性,则要把模型中所有的属性标记为可选(使用默认值或 `None`)。 +因此,如果希望接收的部分更新可以省略所有属性,则需要一个所有属性都标记为可选(带默认值或 `None`)的模型。 -为了区分用于**更新**所有可选值的模型与用于**创建**包含必选值的模型,请参照[更多模型](extra-models.md){.internal-link target=_blank} 一节中的思路。 +为了区分用于**更新**(全部可选)和用于**创建**(必填)的模型,可以参考[更多模型](extra-models.md){.internal-link target=_blank} 中介绍的思路。 /// diff --git a/docs/zh/docs/tutorial/body.md b/docs/zh/docs/tutorial/body.md index 3820fc747..60088a048 100644 --- a/docs/zh/docs/tutorial/body.md +++ b/docs/zh/docs/tutorial/body.md @@ -1,30 +1,30 @@ -# 请求体 +# 请求体 { #request-body } -FastAPI 使用**请求体**从客户端(例如浏览器)向 API 发送数据。 +当你需要从客户端(比如浏览器)向你的 API 发送数据时,会把它作为**请求体**发送。 -**请求体**是客户端发送给 API 的数据。**响应体**是 API 发送给客户端的数据。 +**请求体**是客户端发送给你的 API 的数据。**响应体**是你的 API 发送给客户端的数据。 -API 基本上肯定要发送**响应体**,但是客户端不一定发送**请求体**。 +你的 API 几乎总是需要发送**响应体**。但客户端不一定总是要发送**请求体**,有时它们只请求某个路径,可能带一些查询参数,但不会发送请求体。 -使用 Pydantic 模型声明**请求体**,能充分利用它的功能和优点。 +使用 Pydantic 模型来声明**请求体**,能充分利用它的功能和优点。 -/// info | 说明 +/// info | 信息 -发送数据使用 `POST`(最常用)、`PUT`、`DELETE`、`PATCH` 等操作。 +发送数据应使用以下之一:`POST`(最常见)、`PUT`、`DELETE` 或 `PATCH`。 -规范中没有定义使用 `GET` 发送请求体的操作,但不管怎样,FastAPI 也支持这种方式,只不过仅用于非常复杂或极端的用例。 +规范中没有定义用 `GET` 请求发送请求体的行为,但 FastAPI 仍支持这种方式,只用于非常复杂/极端的用例。 -我们不建议使用 `GET`,因此,在 Swagger UI 交互文档中不会显示有关 `GET` 的内容,而且代理协议也不一定支持 `GET`。 +由于不推荐,在使用 `GET` 时,Swagger UI 的交互式文档不会显示请求体的文档,而且中间的代理可能也不支持它。 /// -## 导入 Pydantic 的 `BaseModel` +## 导入 Pydantic 的 `BaseModel` { #import-pydantics-basemodel } 从 `pydantic` 中导入 `BaseModel`: {* ../../docs_src/body/tutorial001_py310.py hl[2] *} -## 创建数据模型 +## 创建数据模型 { #create-your-data-model } 把数据模型声明为继承 `BaseModel` 的类。 @@ -32,9 +32,9 @@ API 基本上肯定要发送**响应体**,但是客户端不一定发送**请 {* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} -与声明查询参数一样,包含默认值的模型属性是可选的,否则就是必选的。默认值为 `None` 的模型属性也是可选的。 +与声明查询参数一样,包含默认值的模型属性是可选的,否则就是必选的。把默认值设为 `None` 可使其变为可选。 -例如,上述模型声明如下 JSON **对象**(即 Python **字典**): +例如,上述模型声明如下 JSON "object"(即 Python `dict`): ```JSON { @@ -45,7 +45,7 @@ API 基本上肯定要发送**响应体**,但是客户端不一定发送**请 } ``` -……由于 `description` 和 `tax` 是可选的(默认值为 `None`),下面的 JSON **对象**也有效: +...由于 `description` 和 `tax` 是可选的(默认值为 `None`),下面的 JSON "object" 也有效: ```JSON { @@ -54,40 +54,40 @@ API 基本上肯定要发送**响应体**,但是客户端不一定发送**请 } ``` -## 声明请求体参数 +## 声明为参数 { #declare-it-as-a-parameter } -使用与声明路径和查询参数相同的方式声明请求体,把请求体添加至*路径操作*: +使用与声明路径和查询参数相同的方式,把它添加至*路径操作*: {* ../../docs_src/body/tutorial001_py310.py hl[16] *} -……此处,请求体参数的类型为 `Item` 模型。 +...并把其类型声明为你创建的模型 `Item`。 -## 结论 +## 结果 { #results } -仅使用 Python 类型声明,**FastAPI** 就可以: +仅使用这些 Python 类型声明,**FastAPI** 就可以: -* 以 JSON 形式读取请求体 -* (在必要时)把请求体转换为对应的类型 -* 校验数据: - * 数据无效时返回错误信息,并指出错误数据的确切位置和内容 -* 把接收的数据赋值给参数 `item` - * 把函数中请求体参数的类型声明为 `Item`,还能获得代码补全等编辑器支持 -* 为模型生成 JSON Schema,在项目中所需的位置使用 -* 这些概图是 OpenAPI 概图的部件,用于 API 文档 UI +* 以 JSON 形式读取请求体。 +* (在必要时)把请求体转换为对应的类型。 +* 校验数据。 + * 数据无效时返回清晰的错误信息,并指出错误数据的确切位置和内容。 +* 把接收的数据赋值给参数 `item`。 + * 因为你把函数中的参数类型声明为 `Item`,所以还能获得所有属性及其类型的编辑器支持(补全等)。 +* 为你的模型生成 JSON Schema 定义,如果对你的项目有意义,还可以在其他地方使用它们。 +* 这些 schema 会成为生成的 OpenAPI Schema 的一部分,并被自动文档的 UIs 使用。 -## API 文档 +## 自动文档 { #automatic-docs } -Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文档中显示: +你的模型的 JSON Schema 会成为生成的 OpenAPI Schema 的一部分,并显示在交互式 API 文档中: -而且,还会用于 API 文档中使用了概图的*路径操作*: +并且,还会用于需要它们的每个*路径操作*的 API 文档中: -## 编辑器支持 +## 编辑器支持 { #editor-support } -在编辑器中,函数内部均可使用类型提示、代码补全(如果接收的不是 Pydantic 模型,而是**字典**,就没有这样的支持): +在编辑器中,函数内部你会在各处得到类型提示与补全(如果接收的不是 Pydantic 模型,而是 `dict`,就不会有这样的支持): @@ -95,23 +95,23 @@ Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文 -这并非偶然,整个 **FastAPI** 框架都是围绕这种思路精心设计的。 +这并非偶然,整个框架都是围绕这种设计构建的。 -并且,在 FastAPI 的设计阶段,我们就已经进行了全面测试,以确保 FastAPI 可以获得所有编辑器的支持。 +并且在设计阶段、实现之前就进行了全面测试,以确保它能在所有编辑器中正常工作。 -我们还改进了 Pydantic,让它也支持这些功能。 +我们甚至对 Pydantic 本身做了一些改动以支持这些功能。 -虽然上面的截图取自 Visual Studio Code。 +上面的截图来自 Visual Studio Code。 -但 PyCharm 和大多数 Python 编辑器也支持同样的功能: +但使用 PyCharm 和大多数其他 Python 编辑器,你也会获得相同的编辑器支持: /// tip | 提示 -使用 PyCharm 编辑器时,推荐安装 Pydantic PyCharm 插件。 +如果你使用 PyCharm 作为编辑器,可以使用 Pydantic PyCharm 插件。 -该插件用于完善 PyCharm 对 Pydantic 模型的支持,优化的功能如下: +它能改进对 Pydantic 模型的编辑器支持,包括: * 自动补全 * 类型检查 @@ -121,42 +121,44 @@ Pydantic 模型的 JSON 概图是 OpenAPI 生成的概图部件,可在 API 文 /// -## 使用模型 +## 使用模型 { #use-the-model } -在*路径操作*函数内部直接访问模型对象的属性: +在*路径操作*函数内部直接访问模型对象的所有属性: -{* ../../docs_src/body/tutorial002_py310.py hl[19] *} +{* ../../docs_src/body/tutorial002_py310.py *} -## 请求体 + 路径参数 +## 请求体 + 路径参数 { #request-body-path-parameters } -**FastAPI** 支持同时声明路径参数和请求体。 +可以同时声明路径参数和请求体。 -**FastAPI** 能识别与**路径参数**匹配的函数参数,还能识别从**请求体**中获取的类型为 Pydantic 模型的函数参数。 +**FastAPI** 能识别与**路径参数**匹配的函数参数应该**从路径中获取**,而声明为 Pydantic 模型的函数参数应该**从请求体中获取**。 {* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} -## 请求体 + 路径参数 + 查询参数 +## 请求体 + 路径 + 查询参数 { #request-body-path-query-parameters } -**FastAPI** 支持同时声明**请求体**、**路径参数**和**查询参数**。 +也可以同时声明**请求体**、**路径**和**查询**参数。 -**FastAPI** 能够正确识别这三种参数,并从正确的位置获取数据。 +**FastAPI** 会分别识别它们,并从正确的位置获取数据。 {* ../../docs_src/body/tutorial004_py310.py hl[16] *} 函数参数按如下规则进行识别: -- **路径**中声明了相同参数的参数,是路径参数 -- 类型是(`int`、`float`、`str`、`bool` 等)**单类型**的参数,是**查询**参数 -- 类型是 **Pydantic 模型**的参数,是**请求体** +* 如果该参数也在**路径**中声明了,它就是路径参数。 +* 如果该参数是(`int`、`float`、`str`、`bool` 等)**单一类型**,它会被当作**查询**参数。 +* 如果该参数的类型声明为 **Pydantic 模型**,它会被当作请求**体**。 -/// note | 笔记 +/// note | 注意 -因为默认值是 `None`, FastAPI 会把 `q` 当作可选参数。 +FastAPI 会根据默认值 `= None` 知道 `q` 的值不是必填的。 -FastAPI 不使用 `Optional[str]` 中的 `Optional`, 但 `Optional` 可以让编辑器提供更好的支持,并检测错误。 +`str | None`(Python 3.10+)或 `Union[str, None]`(Python 3.9+ 中的 `Union`)并不是 FastAPI 用来判断是否必填的依据;是否必填由是否有默认值 `= None` 决定。 + +但添加这些类型注解可以让你的编辑器提供更好的支持并检测错误。 /// -## 不使用 Pydantic +## 不使用 Pydantic { #without-pydantic } -即便不使用 Pydantic 模型也能使用 **Body** 参数。详见[请求体 - 多参数:请求体中的单值](body-multiple-params.md#_2){.internal-link target=\_blank}。 +即便不使用 Pydantic 模型也能使用 **Body** 参数。详见[请求体 - 多参数:请求体中的单值](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}。 diff --git a/docs/zh/docs/tutorial/cookie-param-models.md b/docs/zh/docs/tutorial/cookie-param-models.md index 6a7b09e25..707a6a9c7 100644 --- a/docs/zh/docs/tutorial/cookie-param-models.md +++ b/docs/zh/docs/tutorial/cookie-param-models.md @@ -1,22 +1,22 @@ -# Cookie 参数模型 +# Cookie 参数模型 { #cookie-parameter-models } 如果您有一组相关的 **cookie**,您可以创建一个 **Pydantic 模型**来声明它们。🍪 这将允许您在**多个地方**能够**重用模型**,并且可以一次性声明所有参数的验证方式和元数据。😎 -/// note +/// note | 注意 自 FastAPI 版本 `0.115.0` 起支持此功能。🤓 /// -/// tip +/// tip | 提示 此技术同样适用于 `Query` 、 `Cookie` 和 `Header` 。😎 /// -## 带有 Pydantic 模型的 Cookie +## 带有 Pydantic 模型的 Cookie { #cookies-with-a-pydantic-model } 在 **Pydantic** 模型中声明所需的 **cookie** 参数,然后将参数声明为 `Cookie` : @@ -24,7 +24,7 @@ **FastAPI** 将从请求中接收到的 **cookie** 中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。 -## 查看文档 +## 查看文档 { #check-the-docs } 您可以在文档 UI 的 `/docs` 中查看定义的 cookie: @@ -32,7 +32,7 @@
-/// info +/// info | 信息 请记住,由于**浏览器**以特殊方式**处理 cookie**,并在后台进行操作,因此它们**不会**轻易允许 **JavaScript** 访问这些 cookie。 @@ -42,7 +42,7 @@ /// -## 禁止额外的 Cookie +## 禁止额外的 Cookie { #forbid-extra-cookies } 在某些特殊使用情况下(可能并不常见),您可能希望**限制**您想要接收的 cookie。 @@ -50,7 +50,7 @@ 您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段: -{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} +{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *} 如果客户尝试发送一些**额外的 cookie**,他们将收到**错误**响应。 @@ -71,6 +71,6 @@ } ``` -## 总结 +## 总结 { #summary } 您可以使用 **Pydantic 模型**在 **FastAPI** 中声明 **cookie**。😎 diff --git a/docs/zh/docs/tutorial/cookie-params.md b/docs/zh/docs/tutorial/cookie-params.md index 495600814..ab05cd7d2 100644 --- a/docs/zh/docs/tutorial/cookie-params.md +++ b/docs/zh/docs/tutorial/cookie-params.md @@ -1,20 +1,19 @@ -# Cookie 参数 +# Cookie 参数 { #cookie-parameters } - 定义 `Cookie` 参数与定义 `Query` 和 `Path` 参数一样。 +定义 `Cookie` 参数与定义 `Query` 和 `Path` 参数一样。 -## 导入 `Cookie` +## 导入 `Cookie` { #import-cookie } 首先,导入 `Cookie`: {* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *} -## 声明 `Cookie` 参数 +## 声明 `Cookie` 参数 { #declare-cookie-parameters } 声明 `Cookie` 参数的方式与声明 `Query` 和 `Path` 参数相同。 第一个值是默认值,还可以传递所有验证参数或注释参数: - {* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *} /// note | 技术细节 @@ -25,12 +24,22 @@ /// -/// info | 说明 +/// info | 信息 必须使用 `Cookie` 声明 cookie 参数,否则该参数会被解释为查询参数。 /// -## 小结 +/// info | 信息 + +请注意,由于**浏览器会以特殊方式并在幕后处理 cookies**,它们**不会**轻易允许**JavaScript**访问它们。 + +如果你前往位于 `/docs` 的**API 文档界面**,你可以看到你的*路径操作*中有关 cookies 的**文档**。 + +但即使你**填写了数据**并点击 "Execute",由于文档界面依赖于**JavaScript**工作,cookies 也不会被发送,你会看到一个**错误**消息,好像你没有填写任何值一样。 + +/// + +## 小结 { #recap } 使用 `Cookie` 声明 cookie 参数的方式与 `Query` 和 `Path` 相同。 diff --git a/docs/zh/docs/tutorial/cors.md b/docs/zh/docs/tutorial/cors.md index a4f15f647..3a296ca72 100644 --- a/docs/zh/docs/tutorial/cors.md +++ b/docs/zh/docs/tutorial/cors.md @@ -1,8 +1,8 @@ -# CORS(跨域资源共享) +# CORS(跨域资源共享) { #cors-cross-origin-resource-sharing } CORS 或者「跨域资源共享」 指浏览器中运行的前端拥有与后端通信的 JavaScript 代码,而后端处于与前端不同的「源」的情况。 -## 源 +## 源 { #origin } 源是协议(`http`,`https`)、域(`myapp.com`,`localhost`,`localhost.tiangolo.com`)以及端口(`80`、`443`、`8080`)的组合。 @@ -14,25 +14,25 @@ 即使它们都在 `localhost` 中,但是它们使用不同的协议或者端口,所以它们都是不同的「源」。 -## 步骤 +## 步骤 { #steps } 假设你的浏览器中有一个前端运行在 `http://localhost:8080`,并且它的 JavaScript 正在尝试与运行在 `http://localhost` 的后端通信(因为我们没有指定端口,浏览器会采用默认的端口 `80`)。 -然后,浏览器会向后端发送一个 HTTP `OPTIONS` 请求,如果后端发送适当的 headers 来授权来自这个不同源(`http://localhost:8080`)的通信,浏览器将允许前端的 JavaScript 向后端发送请求。 +然后,浏览器会向 `:80` 的后端发送一个 HTTP `OPTIONS` 请求,如果后端发送适当的 headers 来授权来自这个不同源(`http://localhost:8080`)的通信,那么运行在 `:8080` 的浏览器就会允许前端中的 JavaScript 向 `:80` 的后端发送请求。 -为此,后端必须有一个「允许的源」列表。 +为此,`:80` 的后端必须有一个「允许的源」列表。 -在这种情况下,它必须包含 `http://localhost:8080`,前端才能正常工作。 +在这种情况下,它必须包含 `http://localhost:8080`,这样 `:8080` 的前端才能正常工作。 -## 通配符 +## 通配符 { #wildcards } 也可以使用 `"*"`(一个「通配符」)声明这个列表,表示全部都是允许的。 -但这仅允许某些类型的通信,不包括所有涉及凭据的内容:像 Cookies 以及那些使用 Bearer 令牌的授权 headers 等。 +但这仅允许某些类型的通信,不包括所有涉及凭据的内容:比如 Cookies,以及那些使用 Bearer 令牌的 Authorization 请求头等。 因此,为了一切都能正常工作,最好显式地指定允许的源。 -## 使用 `CORSMiddleware` +## 使用 `CORSMiddleware` { #use-corsmiddleware } 你可以在 **FastAPI** 应用中使用 `CORSMiddleware` 来配置它。 @@ -42,11 +42,11 @@ 你也可以指定后端是否允许: -* 凭证(授权 headers,Cookies 等)。 +* 凭证(Authorization 请求头、Cookies 等)。 * 特定的 HTTP 方法(`POST`,`PUT`)或者使用通配符 `"*"` 允许所有方法。 -* 特定的 HTTP headers 或者使用通配符 `"*"` 允许所有 headers。 +* 特定的 HTTP 请求头或者使用通配符 `"*"` 允许所有请求头。 -{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} 默认情况下,这个 `CORSMiddleware` 实现所使用的默认参数较为保守,所以你需要显式地启用特定的源、方法或者 headers,以便浏览器能够在跨域上下文中使用它们。 @@ -55,26 +55,29 @@ * `allow_origins` - 一个允许跨域请求的源列表。例如 `['https://example.org', 'https://www.example.org']`。你可以使用 `['*']` 允许任何源。 * `allow_origin_regex` - 一个正则表达式字符串,匹配的源允许跨域请求。例如 `'https://.*\.example\.org'`。 * `allow_methods` - 一个允许跨域请求的 HTTP 方法列表。默认为 `['GET']`。你可以使用 `['*']` 来允许所有标准方法。 -* `allow_headers` - 一个允许跨域请求的 HTTP 请求头列表。默认为 `[]`。你可以使用 `['*']` 允许所有的请求头。`Accept`、`Accept-Language`、`Content-Language` 以及 `Content-Type` 请求头总是允许 CORS 请求。 -* `allow_credentials` - 指示跨域请求支持 cookies。默认是 `False`。另外,允许凭证时 `allow_origins` 不能设定为 `['*']`,必须指定源。 +* `allow_headers` - 一个允许跨域请求的 HTTP 请求头列表。默认为 `[]`。你可以使用 `['*']` 允许所有的请求头。`Accept`、`Accept-Language`、`Content-Language` 以及 `Content-Type` 这几个请求头在简单 CORS 请求中总是被允许。 +* `allow_credentials` - 指示跨域请求支持 cookies。默认是 `False`。 + + 当 `allow_credentials` 设为 `True` 时,`allow_origins`、`allow_methods` 和 `allow_headers` 都不能设为 `['*']`。它们必须显式指定。 + * `expose_headers` - 指示可以被浏览器访问的响应头。默认为 `[]`。 * `max_age` - 设定浏览器缓存 CORS 响应的最长时间,单位是秒。默认为 `600`。 中间件响应两种特定类型的 HTTP 请求…… -### CORS 预检请求 +### CORS 预检请求 { #cors-preflight-requests } 这是些带有 `Origin` 和 `Access-Control-Request-Method` 请求头的 `OPTIONS` 请求。 在这种情况下,中间件将拦截传入的请求并进行响应,出于提供信息的目的返回一个使用了适当的 CORS headers 的 `200` 或 `400` 响应。 -### 简单请求 +### 简单请求 { #simple-requests } 任何带有 `Origin` 请求头的请求。在这种情况下,中间件将像平常一样传递请求,但是在响应中包含适当的 CORS headers。 -## 更多信息 +## 更多信息 { #more-info } -更多关于 CORS 的信息,请查看 Mozilla CORS 文档。 +更多关于 CORS 的信息,请查看 Mozilla CORS 文档。 /// note | 技术细节 diff --git a/docs/zh/docs/tutorial/debugging.md b/docs/zh/docs/tutorial/debugging.md index 734b85565..6e0cefe5b 100644 --- a/docs/zh/docs/tutorial/debugging.md +++ b/docs/zh/docs/tutorial/debugging.md @@ -1,14 +1,14 @@ -# 调试 +# 调试 { #debugging } 你可以在编辑器中连接调试器,例如使用 Visual Studio Code 或 PyCharm。 -## 调用 `uvicorn` +## 调用 `uvicorn` { #call-uvicorn } 在你的 FastAPI 应用中直接导入 `uvicorn` 并运行: -{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} -### 关于 `__name__ == "__main__"` +### 关于 `__name__ == "__main__"` { #about-name-main } `__name__ == "__main__"` 的主要目的是使用以下代码调用文件时执行一些代码: @@ -26,7 +26,7 @@ $ python myapp.py from myapp import app ``` -#### 更多细节 +#### 更多细节 { #more-details } 假设你的文件命名为 `myapp.py`。 @@ -57,7 +57,7 @@ $ python myapp.py ```Python from myapp import app -# Some more code +# 其他一些代码 ``` 在这种情况下,`myapp.py` 内部的自动变量不会有值为 `"__main__"` 的变量 `__name__`。 @@ -74,7 +74,7 @@ from myapp import app /// -## 使用你的调试器运行代码 +## 使用你的调试器运行代码 { #run-your-code-with-your-debugger } 由于是从代码直接运行的 Uvicorn 服务器,所以你可以从调试器直接调用 Python 程序(你的 FastAPI 应用)。 diff --git a/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md index f07280790..d83321ddd 100644 --- a/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md @@ -1,12 +1,12 @@ -# 类作为依赖项 +# 类作为依赖项 { #classes-as-dependencies } 在深入探究 **依赖注入** 系统之前,让我们升级之前的例子。 -## 来自前一个例子的`dict` +## 来自前一个例子的`dict` { #a-dict-from-the-previous-example } 在前面的例子中, 我们从依赖项 ("可依赖对象") 中返回了一个 `dict`: -{* ../../docs_src/dependencies/tutorial001_py310.py hl[7] *} +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *} 但是后面我们在路径操作函数的参数 `commons` 中得到了一个 `dict`。 @@ -14,9 +14,9 @@ 对此,我们可以做的更好... -## 什么构成了依赖项? +## 什么构成了依赖项? { #what-makes-a-dependency } -到目前为止,您看到的依赖项都被声明为函数。 +到目前为止,你看到的依赖项都被声明为函数。 但这并不是声明依赖项的唯一方法(尽管它可能是更常见的方法)。 @@ -38,9 +38,9 @@ something(some_argument, some_keyword_argument="foo") 这就是 "可调用对象"。 -## 类作为依赖项 +## 类作为依赖项 { #classes-as-dependencies_1 } -您可能会注意到,要创建一个 Python 类的实例,您可以使用相同的语法。 +你可能会注意到,要创建一个 Python 类的实例,你可以使用相同的语法。 举个例子: @@ -63,21 +63,21 @@ fluffy = Cat(name="Mr Fluffy") 实际上 FastAPI 检查的是它是一个 "可调用对象"(函数,类或其他任何类型)以及定义的参数。 -如果您在 **FastAPI** 中传递一个 "可调用对象" 作为依赖项,它将分析该 "可调用对象" 的参数,并以处理路径操作函数的参数的方式来处理它们。包括子依赖项。 +如果你在 **FastAPI** 中传递一个 "可调用对象" 作为依赖项,它将分析该 "可调用对象" 的参数,并以处理路径操作函数的参数的方式来处理它们。包括子依赖项。 这也适用于完全没有参数的可调用对象。这与不带参数的路径操作函数一样。 所以,我们可以将上面的依赖项 "可依赖对象" `common_parameters` 更改为类 `CommonQueryParams`: -{* ../../docs_src/dependencies/tutorial002_py310.py hl[9:13] *} +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *} 注意用于创建类实例的 `__init__` 方法: -{* ../../docs_src/dependencies/tutorial002_py310.py hl[10] *} +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *} ...它与我们以前的 `common_parameters` 具有相同的参数: -{* ../../docs_src/dependencies/tutorial001_py310.py hl[6] *} +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *} 这些参数就是 **FastAPI** 用来 "处理" 依赖项的。 @@ -89,26 +89,44 @@ fluffy = Cat(name="Mr Fluffy") 在两个例子下,数据都将被转换、验证、在 OpenAPI schema 上文档化,等等。 -## 使用它 +## 使用它 { #use-it } -现在,您可以使用这个类来声明你的依赖项了。 +现在,你可以使用这个类来声明你的依赖项了。 -{* ../../docs_src/dependencies/tutorial002_py310.py hl[17] *} +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *} **FastAPI** 调用 `CommonQueryParams` 类。这将创建该类的一个 "实例",该实例将作为参数 `commons` 被传递给你的函数。 -## 类型注解 vs `Depends` +## 类型注解 vs `Depends` { #type-annotation-vs-depends } 注意,我们在上面的代码中编写了两次`CommonQueryParams`: +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ 未使用 Annotated + +/// tip | 提示 + +尽可能使用 `Annotated` 版本。 + +/// + ```Python commons: CommonQueryParams = Depends(CommonQueryParams) ``` +//// + 最后的 `CommonQueryParams`: ```Python -... = Depends(CommonQueryParams) +... Depends(CommonQueryParams) ``` ...实际上是 **Fastapi** 用来知道依赖项是什么的。 @@ -119,62 +137,152 @@ FastAPI 将从依赖项中提取声明的参数,这才是 FastAPI 实际调用 在本例中,第一个 `CommonQueryParams` : +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, ... +``` + +//// + +//// tab | Python 3.9+ 未使用 Annotated + +/// tip | 提示 + +尽可能使用 `Annotated` 版本。 + +/// + ```Python commons: CommonQueryParams ... ``` -...对于 **FastAPI** 没有任何特殊的意义。FastAPI 不会使用它进行数据转换、验证等 (因为对于这,它使用 `= Depends(CommonQueryParams)`)。 +//// + +...对于 **FastAPI** 没有任何特殊的意义。FastAPI 不会使用它进行数据转换、验证等 (因为对于这,它使用 `Depends(CommonQueryParams)`)。 你实际上可以只这样编写: +//// tab | Python 3.9+ + +```Python +commons: Annotated[Any, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ 未使用 Annotated + +/// tip | 提示 + +尽可能使用 `Annotated` 版本。 + +/// + ```Python commons = Depends(CommonQueryParams) ``` +//// + ..就像: -{* ../../docs_src/dependencies/tutorial003_py310.py hl[17] *} +{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *} 但是声明类型是被鼓励的,因为那样你的编辑器就会知道将传递什么作为参数 `commons` ,然后它可以帮助你完成代码,类型检查,等等: -## 快捷方式 +## 快捷方式 { #shortcut } -但是您可以看到,我们在这里有一些代码重复了,编写了`CommonQueryParams`两次: +但是你可以看到,我们在这里有一些代码重复了,编写了`CommonQueryParams`两次: + +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ 未使用 Annotated + +/// tip | 提示 + +尽可能使用 `Annotated` 版本。 + +/// ```Python commons: CommonQueryParams = Depends(CommonQueryParams) ``` +//// + **FastAPI** 为这些情况提供了一个快捷方式,在这些情况下,依赖项 *明确地* 是一个类,**FastAPI** 将 "调用" 它来创建类本身的一个实例。 -对于这些特定的情况,您可以跟随以下操作: +对于这些特定的情况,你可以按如下操作: 不是写成这样: +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.9+ 未使用 Annotated + +/// tip | 提示 + +尽可能使用 `Annotated` 版本。 + +/// + ```Python commons: CommonQueryParams = Depends(CommonQueryParams) ``` +//// + ...而是这样写: +//// tab | Python 3.9+ + +```Python +commons: Annotated[CommonQueryParams, Depends()] +``` + +//// + +//// tab | Python 3.9+ 未使用 Annotated + +/// tip | 提示 + +尽可能使用 `Annotated` 版本。 + +/// + ```Python commons: CommonQueryParams = Depends() ``` -您声明依赖项作为参数的类型,并使用 `Depends()` 作为该函数的参数的 "默认" 值(在 `=` 之后),而在 `Depends()` 中没有任何参数,而不是在 `Depends(CommonQueryParams)` 编写完整的类。 +//// + +你声明依赖项作为参数的类型,并使用 `Depends()` 作为该函数的参数的 "默认" 值(在 `=` 之后),而在 `Depends()` 中没有任何参数,而不是在 `Depends(CommonQueryParams)` 中*再次*编写完整的类。 同样的例子看起来像这样: -{* ../../docs_src/dependencies/tutorial004_py310.py hl[17] *} +{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *} ... **FastAPI** 会知道怎么处理。 -/// tip +/// tip | 提示 如果这看起来更加混乱而不是更加有帮助,那么请忽略它,你不*需要*它。 -这只是一个快捷方式。因为 **FastAPI** 关心的是帮助您减少代码重复。 +这只是一个快捷方式。因为 **FastAPI** 关心的是帮助你减少代码重复。 /// diff --git a/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 51b3e9fc3..02fcf62a0 100644 --- a/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -1,4 +1,4 @@ -# 路径操作装饰器依赖项 +# 路径操作装饰器依赖项 { #dependencies-in-path-operation-decorators } 有时,我们并不需要在*路径操作函数*中使用依赖项的返回值。 @@ -8,15 +8,15 @@ 对于这种情况,不必在声明*路径操作函数*的参数时使用 `Depends`,而是可以在*路径操作装饰器*中添加一个由 `dependencies` 组成的 `list`。 -## 在*路径操作装饰器*中添加 `dependencies` 参数 +## 在*路径操作装饰器*中添加 `dependencies` 参数 { #add-dependencies-to-the-path-operation-decorator } -*路径操作装饰器*支持可选参数 ~ `dependencies`。 +*路径操作装饰器*支持可选参数 `dependencies`。 该参数的值是由 `Depends()` 组成的 `list`: -{* ../../docs_src/dependencies/tutorial006.py hl[17] *} +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} -路径操作装饰器依赖项(以下简称为**“路径装饰器依赖项”**)的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给*路径操作函数*。 +路径操作装饰器依赖项的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给*路径操作函数*。 /// tip | 提示 @@ -36,34 +36,34 @@ /// -## 依赖项错误和返回值 +## 依赖项错误和返回值 { #dependencies-errors-and-return-values } 路径装饰器依赖项也可以使用普通的依赖项*函数*。 -### 依赖项的需求项 +### 依赖项的需求项 { #dependency-requirements } 路径装饰器依赖项可以声明请求的需求项(比如响应头)或其他子依赖项: -{* ../../docs_src/dependencies/tutorial006.py hl[6,11] *} +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} -### 触发异常 +### 触发异常 { #raise-exceptions } 路径装饰器依赖项与正常的依赖项一样,可以 `raise` 异常: -{* ../../docs_src/dependencies/tutorial006.py hl[8,13] *} +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} -### 返回值 +### 返回值 { #return-values } 无论路径装饰器依赖项是否返回值,路径操作都不会使用这些值。 因此,可以复用在其他位置使用过的、(能返回值的)普通依赖项,即使没有使用这个值,也会执行该依赖项: -{* ../../docs_src/dependencies/tutorial006.py hl[9,14] *} +{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} -## 为一组路径操作定义依赖项 +## 为一组路径操作定义依赖项 { #dependencies-for-a-group-of-path-operations } -稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=\_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。 +稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。 -## 全局依赖项 +## 全局依赖项 { #global-dependencies } 接下来,我们将学习如何为 `FastAPI` 应用程序添加全局依赖项,创建应用于每个*路径操作*的依赖项。 diff --git a/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md index a863bb861..bf495c9f3 100644 --- a/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,136 +1,139 @@ -# 使用yield的依赖项 +# 使用 yield 的依赖项 { #dependencies-with-yield } -FastAPI支持在完成后执行一些额外步骤的依赖项. +FastAPI 支持那些在完成后执行一些额外步骤的依赖项。 -为此,你需要使用 `yield` 而不是 `return`,然后再编写这些额外的步骤(代码)。 +为此,使用 `yield` 而不是 `return`,并把这些额外步骤(代码)写在后面。 /// tip | 提示 -确保在每个依赖中只使用一次 `yield`。 +确保在每个依赖里只使用一次 `yield`。 /// /// note | 技术细节 -任何一个可以与以下内容一起使用的函数: +任何可以与以下装饰器一起使用的函数: -* `@contextlib.contextmanager` 或者 +* `@contextlib.contextmanager` 或 * `@contextlib.asynccontextmanager` 都可以作为 **FastAPI** 的依赖项。 -实际上,FastAPI内部就使用了这两个装饰器。 +实际上,FastAPI 在内部就是用的这两个装饰器。 /// -## 使用 `yield` 的数据库依赖项 +## 使用 `yield` 的数据库依赖项 { #a-database-dependency-with-yield } -例如,你可以使用这种方式创建一个数据库会话,并在完成后关闭它。 +例如,你可以用这种方式创建一个数据库会话,并在完成后将其关闭。 -在发送响应之前,只会执行 `yield` 语句及之前的代码: +在创建响应之前,只会执行 `yield` 语句及其之前的代码: -{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} -生成的值会注入到 *路由函数* 和其他依赖项中: +`yield` 产生的值会注入到 *路径操作* 和其他依赖项中: -{* ../../docs_src/dependencies/tutorial007.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} -`yield` 语句后面的代码会在创建响应后,发送响应前执行: +`yield` 语句后面的代码会在响应之后执行: -{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} /// tip | 提示 你可以使用 `async` 或普通函数。 -**FastAPI** 会像处理普通依赖一样,对每个依赖做正确的处理。 +**FastAPI** 会像处理普通依赖一样对它们进行正确处理。 /// -## 包含 `yield` 和 `try` 的依赖项 +## 同时使用 `yield` 和 `try` 的依赖项 { #a-dependency-with-yield-and-try } -如果在包含 `yield` 的依赖中使用 `try` 代码块,你会捕获到使用依赖时抛出的任何异常。 +如果你在带有 `yield` 的依赖中使用了 `try` 代码块,那么当使用该依赖时抛出的任何异常你都会收到。 -例如,如果某段代码在另一个依赖中或在 *路由函数* 中使数据库事务"回滚"或产生任何其他错误,你将会在依赖中捕获到异常。 +例如,如果在中间的某处代码中(在另一个依赖或在某个 *路径操作* 中)发生了数据库事务“回滚”或产生了其他异常,你会在你的依赖中收到这个异常。 -因此,你可以使用 `except SomeException` 在依赖中捕获特定的异常。 +因此,你可以在该依赖中用 `except SomeException` 来捕获这个特定异常。 -同样,你也可以使用 `finally` 来确保退出步骤得到执行,无论是否存在异常。 +同样地,你可以使用 `finally` 来确保退出步骤一定会被执行,无论是否发生异常。 -{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} -## 使用 `yield` 的子依赖项 +{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} -你可以声明任意数量和层级的树状依赖,而且它们中的任何一个或所有的都可以使用 `yield`。 +## 使用 `yield` 的子依赖项 { #sub-dependencies-with-yield } -**FastAPI** 会确保每个带有 `yield` 的依赖中的"退出代码"按正确顺序运行。 +你可以声明任意大小和形状的子依赖及其“树”,其中任意一个或全部都可以使用 `yield`。 -例如,`dependency_c` 可以依赖于 `dependency_b`,而 `dependency_b` 则依赖于 `dependency_a`。 +**FastAPI** 会确保每个带有 `yield` 的依赖中的“退出代码”按正确的顺序运行。 + +例如,`dependency_c` 可以依赖 `dependency_b`,而 `dependency_b` 则依赖 `dependency_a`: {* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} -所有这些依赖都可以使用 `yield`。 +并且它们都可以使用 `yield`。 -在这种情况下,`dependency_c` 在执行其退出代码时需要 `dependency_b`(此处称为 `dep_b`)的值仍然可用。 +在这种情况下,`dependency_c` 在执行其退出代码时需要 `dependency_b`(此处命名为 `dep_b`)的值仍然可用。 -而 `dependency_b` 反过来则需要 `dependency_a`(此处称为 `dep_a` )的值在其退出代码中可用。 +而 `dependency_b` 又需要 `dependency_a`(此处命名为 `dep_a`)的值在其退出代码中可用。 {* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *} -同样,你可以混合使用带有 `yield` 或 `return` 的依赖。 +同样地,你可以将一些依赖用 `yield`,另一些用 `return`,并让其中一些依赖依赖于另一些。 -你也可以声明一个依赖于多个带有 `yield` 的依赖,等等。 +你也可以有一个依赖需要多个带有 `yield` 的依赖,等等。 你可以拥有任何你想要的依赖组合。 -**FastAPI** 将确保按正确的顺序运行所有内容。 +**FastAPI** 将确保一切都按正确的顺序运行。 /// note | 技术细节 -这是由 Python 的上下文管理器完成的。 +这要归功于 Python 的上下文管理器。 **FastAPI** 在内部使用它们来实现这一点。 /// -## 包含 `yield` 和 `HTTPException` 的依赖项 +## 同时使用 `yield` 和 `HTTPException` 的依赖项 { #dependencies-with-yield-and-httpexception } -你可以使用带有 `yield` 的依赖项,并且可以包含 `try` 代码块用于捕获异常。 +你已经看到可以在带有 `yield` 的依赖中使用 `try` 块尝试执行一些代码,然后在 `finally` 之后运行一些退出代码。 -同样,你可以在 `yield` 之后的退出代码中抛出一个 `HTTPException` 或类似的异常。 +你也可以使用 `except` 来捕获引发的异常并对其进行处理。 + +例如,你可以抛出一个不同的异常,如 `HTTPException`。 /// tip | 提示 -这是一种相对高级的技巧,在大多数情况下你并不需要使用它,因为你可以在其他代码中抛出异常(包括 `HTTPException` ),例如在 *路由函数* 中。 +这是一种相对高级的技巧,在大多数情况下你并不需要使用它,因为你可以在应用的其他代码中(例如在 *路径操作函数* 里)抛出异常(包括 `HTTPException`)。 -但是如果你需要,你也可以在依赖项中做到这一点。🤓 +但是如果你需要,它就在这里。🤓 /// {* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *} -你还可以创建一个 [自定义异常处理器](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} 用于捕获异常(同时也可以抛出另一个 `HTTPException`)。 +如果你想捕获异常并基于它创建一个自定义响应,请创建一个[自定义异常处理器](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}。 -## 包含 `yield` 和 `except` 的依赖项 +## 同时使用 `yield` 和 `except` 的依赖项 { #dependencies-with-yield-and-except } -如果你在包含 `yield` 的依赖项中使用 `except` 捕获了一个异常,然后你没有重新抛出该异常(或抛出一个新异常),与在普通的Python代码中相同,FastAPI不会注意到发生了异常。 +如果你在带有 `yield` 的依赖中使用 `except` 捕获了一个异常,并且你没有再次抛出它(或抛出一个新异常),FastAPI 将无法察觉发生过异常,就像普通的 Python 代码那样: {* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} -在示例代码的情况下,客户端将会收到 *HTTP 500 Internal Server Error* 的响应,因为我们没有抛出 `HTTPException` 或者类似的异常,并且服务器也 **不会有任何日志** 或者其他提示来告诉我们错误是什么。😱 +在这种情况下,客户端会像预期那样看到一个 *HTTP 500 Internal Server Error* 响应,因为我们没有抛出 `HTTPException` 或类似异常,但服务器将**没有任何日志**或其他关于错误是什么的提示。😱 -### 在包含 `yield` 和 `except` 的依赖项中一定要 `raise` +### 在带有 `yield` 和 `except` 的依赖中务必 `raise` { #always-raise-in-dependencies-with-yield-and-except } -如果你在使用 `yield` 的依赖项中捕获到了一个异常,你应该再次抛出捕获到的异常,除非你抛出 `HTTPException` 或类似的其他异常, +如果你在带有 `yield` 的依赖中捕获到了一个异常,除非你抛出另一个 `HTTPException` 或类似异常,**否则你应该重新抛出原始异常**。 -你可以使用 `raise` 再次抛出捕获到的异常。 +你可以使用 `raise` 重新抛出同一个异常: {* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *} -现在客户端同样会得到 *HTTP 500 Internal Server Error* 响应,但是服务器日志会记录下我们自定义的 `InternalError`。 +现在客户端仍会得到同样的 *HTTP 500 Internal Server Error* 响应,但服务器日志中会有我们自定义的 `InternalError`。😎 -## 使用 `yield` 的依赖项的执行 +## 使用 `yield` 的依赖项的执行 { #execution-of-dependencies-with-yield } -执行顺序大致如下时序图所示。时间轴从上到下,每一列都代表交互或者代码执行的一部分。 +执行顺序大致如下图所示。时间轴从上到下,每一列都代表交互或执行代码的一部分。 ```mermaid sequenceDiagram @@ -167,63 +170,78 @@ participant tasks as Background tasks end ``` -/// info | 说明 +/// info | 信息 -只会向客户端发送 **一次响应** ,可能是一个错误响应,也可能是来自 *路由函数* 的响应。 +只会向客户端发送**一次响应**。它可能是某个错误响应,或者是来自 *路径操作* 的响应。 -在发送了其中一个响应之后,就无法再发送其他响应了。 +在其中一个响应发送之后,就不能再发送其他响应了。 /// /// tip | 提示 -这个时序图展示了 `HTTPException`,除此之外你也可以抛出任何你在使用 `yield` 的依赖项中或者[自定义异常处理器](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}中捕获的异常。 - -如果你引发任何异常,它将传递给使用 `yield` 的依赖项,包括 `HTTPException`。在大多数情况下你应当从使用 `yield` 的依赖项中重新抛出捕获的异常或者一个新的异常来确保它会被正确的处理。 +如果你在 *路径操作函数* 的代码中引发任何异常,它都会被传递给带有 `yield` 的依赖项,包括 `HTTPException`。在大多数情况下,你会希望在带有 `yield` 的依赖中重新抛出相同的异常或一个新的异常,以确保它被正确处理。 /// -## 包含 `yield`, `HTTPException`, `except` 的依赖项和后台任务 +## 提前退出与 `scope` { #early-exit-and-scope } -/// warning | 注意 +通常,带有 `yield` 的依赖的退出代码会在响应发送给客户端**之后**执行。 -你大概率不需要了解这些技术细节,可以跳过这一章节继续阅读后续的内容。 +但如果你知道在从 *路径操作函数* 返回之后不再需要使用该依赖,你可以使用 `Depends(scope="function")` 告诉 FastAPI:应当在 *路径操作函数* 返回后、但在**响应发送之前**关闭该依赖。 -如果你使用的FastAPI的版本早于0.106.0,并且在使用后台任务中使用了包含 `yield` 的依赖项中的资源,那么这些细节会对你有一些用处。 +{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *} -/// +`Depends()` 接收一个 `scope` 参数,可为: -### 包含 `yield` 和 `except` 的依赖项的技术细节 +* `"function"`:在处理请求的 *路径操作函数* 之前启动依赖,在 *路径操作函数* 结束后结束依赖,但在响应发送给客户端**之前**。因此,依赖函数将围绕这个*路径操作函数*执行。 +* `"request"`:在处理请求的 *路径操作函数* 之前启动依赖(与使用 `"function"` 时类似),但在响应发送给客户端**之后**结束。因此,依赖函数将围绕这个**请求**与响应周期执行。 -在FastAPI 0.110.0版本之前,如果使用了一个包含 `yield` 的依赖项,你在依赖项中使用 `except` 捕获了一个异常,但是你没有再次抛出该异常,这个异常会被自动抛出/转发到异常处理器或者内部服务错误处理器。 +如果未指定且依赖包含 `yield`,则默认 `scope` 为 `"request"`。 -### 后台任务和使用 `yield` 的依赖项的技术细节 +### 子依赖的 `scope` { #scope-for-sub-dependencies } -在FastAPI 0.106.0版本之前,在 `yield` 后面抛出异常是不可行的,因为 `yield` 之后的退出代码是在响应被发送之后再执行,这个时候异常处理器已经执行过了。 +当你声明一个 `scope="request"`(默认)的依赖时,任何子依赖也需要有 `"request"` 的 `scope`。 -这样设计的目的主要是为了允许在后台任务中使用被依赖项`yield`的对象,因为退出代码会在后台任务结束后再执行。 +但一个 `scope` 为 `"function"` 的依赖可以有 `scope` 为 `"function"` 和 `"request"` 的子依赖。 -然而这也意味着在等待响应通过网络传输的同时,非必要的持有一个 `yield` 依赖项中的资源(例如数据库连接),这一行为在FastAPI 0.106.0被改变了。 +这是因为任何依赖都需要能够在子依赖之前运行其退出代码,因为它的退出代码中可能还需要使用这些子依赖。 -/// tip | 提示 +```mermaid +sequenceDiagram -除此之外,后台任务通常是一组独立的逻辑,应该被单独处理,并且使用它自己的资源(例如它自己的数据库连接)。 +participant client as Client +participant dep_req as Dep scope="request" +participant dep_func as Dep scope="function" +participant operation as Path Operation -这样也会让你的代码更加简洁。 + client ->> dep_req: Start request + Note over dep_req: Run code up to yield + dep_req ->> dep_func: Pass dependency + Note over dep_func: Run code up to yield + dep_func ->> operation: Run path operation with dependency + operation ->> dep_func: Return from path operation + Note over dep_func: Run code after yield + Note over dep_func: ✅ Dependency closed + dep_func ->> client: Send response to client + Note over client: Response sent + Note over dep_req: Run code after yield + Note over dep_req: ✅ Dependency closed +``` -/// +## 包含 `yield`、`HTTPException`、`except` 和后台任务的依赖项 { #dependencies-with-yield-httpexception-except-and-background-tasks } -如果你之前依赖于这一行为,那么现在你应该在后台任务中创建并使用它自己的资源,不要在内部使用属于 `yield` 依赖项的资源。 +带有 `yield` 的依赖项随着时间演进以涵盖不同的用例并修复了一些问题。 -例如,你应该在后台任务中创建一个新的数据库会话用于查询数据,而不是使用相同的会话。你应该将对象的ID作为参数传递给后台任务函数,然后在该函数中重新获取该对象,而不是直接将数据库对象作为参数。 +如果你想了解在不同 FastAPI 版本中发生了哪些变化,可以在进阶指南中阅读更多:[高级依赖项 —— 包含 `yield`、`HTTPException`、`except` 和后台任务的依赖项](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}。 -## 上下文管理器 +## 上下文管理器 { #context-managers } -### 什么是"上下文管理器" +### 什么是“上下文管理器” { #what-are-context-managers } -"上下文管理器"是你可以在 `with` 语句中使用的任何Python对象。 +“上下文管理器”是你可以在 `with` 语句中使用的任意 Python 对象。 -例如,你可以使用`with`读取文件: +例如,你可以用 `with` 来读取文件: ```Python with open("./somefile.txt") as f: @@ -231,37 +249,39 @@ with open("./somefile.txt") as f: print(contents) ``` -在底层,`open("./somefile.txt")`创建了一个被称为"上下文管理器"的对象。 +在底层,`open("./somefile.txt")` 会创建一个“上下文管理器”对象。 -当 `with` 代码块结束时,它会确保关闭文件,即使发生了异常也是如此。 +当 `with` 代码块结束时,它会确保文件被关闭,即使期间发生了异常。 -当你使用 `yield` 创建一个依赖项时,**FastAPI** 会在内部将其转换为上下文管理器,并与其他相关工具结合使用。 +当你用 `yield` 创建一个依赖时,**FastAPI** 会在内部为它创建一个上下文管理器,并与其他相关工具结合使用。 -### 在使用 `yield` 的依赖项中使用上下文管理器 +### 在带有 `yield` 的依赖中使用上下文管理器 { #using-context-managers-in-dependencies-with-yield } -/// warning | 注意 +/// warning | 警告 -这是一个更为"高级"的想法。 +这算是一个“高级”概念。 -如果你刚开始使用 **FastAPI** ,你可以暂时可以跳过它。 +如果你刚开始使用 **FastAPI**,现在可以先跳过。 /// -在Python中,你可以通过创建一个带有`__enter__()`和`__exit__()`方法的类来创建上下文管理器。 +在 Python 中,你可以通过创建一个带有 `__enter__()` 和 `__exit__()` 方法的类来创建上下文管理器。 -你也可以在 **FastAPI** 的 `yield` 依赖项中通过 `with` 或者 `async with` 语句来使用它们: +你也可以在 **FastAPI** 的带有 `yield` 的依赖中,使用依赖函数内部的 `with` 或 `async with` 语句来使用它们: -{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *} +{* ../../docs_src/dependencies/tutorial010_py39.py hl[1:9,13] *} /// tip | 提示 -另一种创建上下文管理器的方法是: +另一种创建上下文管理器的方式是: -* `@contextlib.contextmanager`或者 -* `@contextlib.asynccontextmanager` +* `@contextlib.contextmanager` 或 +* `@contextlib.asynccontextmanager` -使用它们装饰一个只有单个 `yield` 的函数。这就是 **FastAPI** 内部对于 `yield` 依赖项的处理方式。 +用它们去装饰一个只包含单个 `yield` 的函数。 -但是你不需要为FastAPI的依赖项使用这些装饰器(而且也不应该)。FastAPI会在内部为你处理这些。 +这正是 **FastAPI** 在内部处理带有 `yield` 的依赖时所使用的方式。 + +但你不需要(也不应该)为 FastAPI 的依赖去使用这些装饰器。FastAPI 会在内部为你处理好。 /// diff --git a/docs/zh/docs/tutorial/dependencies/global-dependencies.md b/docs/zh/docs/tutorial/dependencies/global-dependencies.md index 797fd76b7..36cf9cf44 100644 --- a/docs/zh/docs/tutorial/dependencies/global-dependencies.md +++ b/docs/zh/docs/tutorial/dependencies/global-dependencies.md @@ -1,15 +1,15 @@ -# 全局依赖项 +# 全局依赖项 { #global-dependencies } 有时,我们要为整个应用添加依赖项。 -通过与定义[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 类似的方式,可以把依赖项添加至整个 `FastAPI` 应用。 +通过与[将 `dependencies` 添加到*路径操作装饰器*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 类似的方式,可以把依赖项添加至整个 `FastAPI` 应用。 这样一来,就可以为所有*路径操作*应用该依赖项: -{* ../../docs_src/dependencies/tutorial012.py hl[15] *} +{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *} -[*路径装饰器依赖项*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 一章的思路均适用于全局依赖项, 在本例中,这些依赖项可以用于应用中的所有*路径操作*。 +[将 `dependencies` 添加到*路径操作装饰器*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 一章的思路均适用于全局依赖项, 在本例中,这些依赖项可以用于应用中的所有*路径操作*。 -## 为一组路径操作定义依赖项 +## 为一组路径操作定义依赖项 { #dependencies-for-groups-of-path-operations } 稍后,[大型应用 - 多文件](../../tutorial/bigger-applications.md){.internal-link target=_blank}一章中会介绍如何使用多个文件创建大型应用程序,在这一章中,您将了解到如何为一组*路径操作*声明单个 `dependencies` 参数。 diff --git a/docs/zh/docs/tutorial/dependencies/index.md b/docs/zh/docs/tutorial/dependencies/index.md index 9eec69ed5..20d2c0b64 100644 --- a/docs/zh/docs/tutorial/dependencies/index.md +++ b/docs/zh/docs/tutorial/dependencies/index.md @@ -1,85 +1,97 @@ -# 依赖项 +# 依赖项 { #dependencies } -FastAPI 提供了简单易用,但功能强大的**依赖注入**系统。 +**FastAPI** 提供了简单直观但功能强大的**依赖注入**系统。 -这个依赖系统设计的简单易用,可以让开发人员轻松地把组件集成至 **FastAPI**。 +它被设计得非常易用,能让任何开发者都能轻松把其他组件与 **FastAPI** 集成。 -## 什么是「依赖注入」 +## 什么是「依赖注入」 { #what-is-dependency-injection } -编程中的**「依赖注入」**是声明代码(本文中为*路径操作函数* )运行所需的,或要使用的「依赖」的一种方式。 +在编程中,**「依赖注入」**指的是,你的代码(本文中为*路径操作函数*)声明其运行所需并要使用的东西:“依赖”。 -然后,由系统(本文中为 **FastAPI**)负责执行任意需要的逻辑,为代码提供这些依赖(「注入」依赖项)。 +然后,由该系统(本文中为 **FastAPI**)负责执行所有必要的逻辑,为你的代码提供这些所需的依赖(“注入”依赖)。 -依赖注入常用于以下场景: +当你需要以下内容时,这非常有用: -* 共享业务逻辑(复用相同的代码逻辑) +* 共享业务逻辑(同一段代码逻辑反复复用) * 共享数据库连接 -* 实现安全、验证、角色权限 -* 等…… +* 实施安全、认证、角色权限等要求 +* 以及更多其他内容... -上述场景均可以使用**依赖注入**,将代码重复最小化。 +同时尽量减少代码重复。 -## 第一步 +## 第一步 { #first-steps } -接下来,我们学习一个非常简单的例子,尽管它过于简单,不是很实用。 +先来看一个非常简单的例子。它现在简单到几乎没什么用。 -但通过这个例子,您可以初步了解「依赖注入」的工作机制。 +但这样我们就可以专注于**依赖注入**系统是如何工作的。 -### 创建依赖项 +### 创建依赖项,或“dependable” { #create-a-dependency-or-dependable } -首先,要关注的是依赖项。 +首先关注依赖项。 -依赖项就是一个函数,且可以使用与*路径操作函数*相同的参数: +它只是一个函数,且可以接收与*路径操作函数*相同的所有参数: -{* ../../docs_src/dependencies/tutorial001.py hl[8:11] *} +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *} 大功告成。 只用了**2 行**代码。 -依赖项函数的形式和结构与*路径操作函数*一样。 +它的形式和结构与所有*路径操作函数*相同。 -因此,可以把依赖项当作没有「装饰器」(即,没有 `@app.get("/some-path")` )的路径操作函数。 +你可以把它当作没有“装饰器”(没有 `@app.get("/some-path")`)的*路径操作函数*。 -依赖项可以返回各种内容。 +而且它可以返回任何你想要的内容。 -本例中的依赖项预期接收如下参数: +本例中的依赖项预期接收: * 类型为 `str` 的可选查询参数 `q` -* 类型为 `int` 的可选查询参数 `skip`,默认值是 `0` -* 类型为 `int` 的可选查询参数 `limit`,默认值是 `100` +* 类型为 `int` 的可选查询参数 `skip`,默认值 `0` +* 类型为 `int` 的可选查询参数 `limit`,默认值 `100` -然后,依赖项函数返回包含这些值的 `dict`。 +然后它只需返回一个包含这些值的 `dict`。 -### 导入 `Depends` +/// info | 信息 -{* ../../docs_src/dependencies/tutorial001.py hl[3] *} +FastAPI 在 0.95.0 版本中新增了对 `Annotated` 的支持(并开始推荐使用)。 -### 声明依赖项 +如果你的版本较旧,尝试使用 `Annotated` 会报错。 -与在*路径操作函数*参数中使用 `Body`、`Query` 的方式相同,声明依赖项需要使用 `Depends` 和一个新的参数: - -{* ../../docs_src/dependencies/tutorial001.py hl[15,20] *} - -虽然,在路径操作函数的参数中使用 `Depends` 的方式与 `Body`、`Query` 相同,但 `Depends` 的工作方式略有不同。 - -这里只能传给 Depends 一个参数。 - -且该参数必须是可调用对象,比如函数。 - -该函数接收的参数和*路径操作函数*的参数一样。 - -/// tip | 提示 - -下一章介绍,除了函数还有哪些「对象」可以用作依赖项。 +在使用 `Annotated` 之前,请确保[升级 FastAPI 版本](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}到至少 0.95.1。 /// -接收到新的请求时,**FastAPI** 执行如下操作: +### 导入 `Depends` { #import-depends } -* 用正确的参数调用依赖项函数(「可依赖项」) +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *} + +### 在“dependant”中声明依赖项 { #declare-the-dependency-in-the-dependant } + +与在*路径操作函数*的参数中使用 `Body`、`Query` 等相同,给参数使用 `Depends` 来声明一个新的依赖项: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *} + +虽然你在函数参数中使用 `Depends` 的方式与 `Body`、`Query` 等相同,但 `Depends` 的工作方式略有不同。 + +这里只能给 `Depends` 传入一个参数。 + +这个参数必须是类似函数的可调用对象。 + +你不需要直接调用它(不要在末尾加括号),只需将其作为参数传给 `Depends()`。 + +该函数接收的参数与*路径操作函数*的参数相同。 + +/// tip | 提示 + +下一章会介绍除了函数之外,还有哪些“东西”可以用作依赖项。 + +/// + +接收到新的请求时,**FastAPI** 会负责: + +* 用正确的参数调用你的依赖项(“dependable”)函数 * 获取函数返回的结果 -* 把函数返回的结果赋值给*路径操作函数*的参数 +* 将该结果赋值给你的*路径操作函数*中的参数 ```mermaid graph TB @@ -92,95 +104,121 @@ common_parameters --> read_items common_parameters --> read_users ``` -这样,只编写一次代码,**FastAPI** 就可以为多个*路径操作*共享这段代码 。 +这样,你只需编写一次共享代码,**FastAPI** 会在你的*路径操作*中为你调用它。 /// check | 检查 -注意,无需创建专门的类,并将之传递给 **FastAPI** 以进行「注册」或执行类似的操作。 +注意,无需创建专门的类并传给 **FastAPI** 去“注册”之类的操作。 -只要把它传递给 `Depends`,**FastAPI** 就知道该如何执行后续操作。 +只要把它传给 `Depends`,**FastAPI** 就知道该怎么做了。 /// -## 要不要使用 `async`? +## 共享 `Annotated` 依赖项 { #share-annotated-dependencies } -**FastAPI** 调用依赖项的方式与*路径操作函数*一样,因此,定义依赖项函数,也要应用与路径操作函数相同的规则。 +在上面的示例中,你会发现这里有一点点**代码重复**。 -即,既可以使用异步的 `async def`,也可以使用普通的 `def` 定义依赖项。 +当你需要使用 `common_parameters()` 这个依赖时,你必须写出完整的带类型注解和 `Depends()` 的参数: -在普通的 `def` *路径操作函数*中,可以声明异步的 `async def` 依赖项;也可以在异步的 `async def` *路径操作函数*中声明普通的 `def` 依赖项。 +```Python +commons: Annotated[dict, Depends(common_parameters)] +``` -上述这些操作都是可行的,**FastAPI** 知道该怎么处理。 +但因为我们使用了 `Annotated`,可以把这个 `Annotated` 的值存到一个变量里,在多个地方复用: -/// note | 笔记 +{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *} -如里不了解异步,请参阅[异步:*“着急了?”*](../../async.md){.internal-link target=_blank} 一章中 `async` 和 `await` 的内容。 +/// tip | 提示 + +这只是标准的 Python,叫做“类型别名”,并不是 **FastAPI** 特有的。 + +但因为 **FastAPI** 基于 Python 标准(包括 `Annotated`),你就可以在代码里使用这个技巧。😎 /// -## 与 OpenAPI 集成 +这些依赖会照常工作,而**最棒的是**,**类型信息会被保留**,这意味着你的编辑器依然能提供**自动补全**、**行内报错**等。同样适用于 `mypy` 等其他工具。 -依赖项及子依赖项的所有请求声明、验证和需求都可以集成至同一个 OpenAPI 概图。 +当你在**大型代码库**中,在**很多*路径操作***里反复使用**相同的依赖**时,这会特别有用。 -所以,交互文档里也会显示依赖项的所有信息: +## 要不要使用 `async`? { #to-async-or-not-to-async } + +由于依赖项也会由 **FastAPI** 调用(与*路径操作函数*相同),因此定义函数时同样的规则也适用。 + +你可以使用 `async def` 或普通的 `def`。 + +你可以在普通的 `def` *路径操作函数*中声明 `async def` 的依赖项;也可以在异步的 `async def` *路径操作函数*中声明普通的 `def` 依赖项,等等。 + +都没关系,**FastAPI** 知道该怎么处理。 + +/// note | 注意 + +如果不了解异步,请参阅文档中关于 `async` 和 `await` 的章节:[异步:*“着急了?”*](../../async.md#in-a-hurry){.internal-link target=_blank}。 + +/// + +## 与 OpenAPI 集成 { #integrated-with-openapi } + +依赖项及子依赖项中声明的所有请求、验证和需求都会集成到同一个 OpenAPI 模式中。 + +因此,交互式文档中也会包含这些依赖项的所有信息: -## 简单用法 +## 简单用法 { #simple-usage } -观察一下就会发现,只要*路径* 和*操作*匹配,就可以使用声明的路径操作函数。然后,**FastAPI** 会用正确的参数调用函数,并提取请求中的数据。 +观察一下就会发现,只要*路径*和*操作*匹配,就会使用声明的*路径操作函数*。随后,**FastAPI** 会用正确的参数调用该函数,并从请求中提取数据。 -实际上,所有(或大多数)网络框架的工作方式都是这样的。 +事实上,所有(或大多数)Web 框架的工作方式都是这样的。 -开发人员永远都不需要直接调用这些函数,这些函数是由框架(在此为 **FastAPI** )调用的。 +你从不会直接调用这些函数。它们由你的框架(此处为 **FastAPI**)调用。 -通过依赖注入系统,只要告诉 **FastAPI** *路径操作函数* 还要「依赖」其他在*路径操作函数*之前执行的内容,**FastAPI** 就会执行函数代码,并「注入」函数返回的结果。 +通过依赖注入系统,你还可以告诉 **FastAPI**,你的*路径操作函数*还“依赖”某些应在*路径操作函数*之前执行的内容,**FastAPI** 会负责执行它并“注入”结果。 -其他与「依赖注入」概念相同的术语为: +“依赖注入”的其他常见术语包括: -* 资源(Resource) -* 提供方(Provider) -* 服务(Service) -* 可注入(Injectable) -* 组件(Component) +* 资源(resources) +* 提供方(providers) +* 服务(services) +* 可注入(injectables) +* 组件(components) -## **FastAPI** 插件 +## **FastAPI** 插件 { #fastapi-plug-ins } -**依赖注入**系统支持构建集成和「插件」。但实际上,FastAPI 根本**不需要创建「插件」**,因为使用依赖项可以声明不限数量的、可用于*路径操作函数*的集成与交互。 +可以使用**依赖注入**系统构建集成和“插件”。但实际上,根本**不需要创建“插件”**,因为通过依赖项可以声明无限多的集成与交互,使其可用于*路径操作函数*。 -创建依赖项非常简单、直观,并且还支持导入 Python 包。毫不夸张地说,只要几行代码就可以把需要的 Python 包与 API 函数集成在一起。 +依赖项可以用非常简单直观的方式创建,你只需导入所需的 Python 包,用*字面意义上的*几行代码就能把它们与你的 API 函数集成起来。 -下一章将详细介绍在关系型数据库、NoSQL 数据库、安全等方面使用依赖项的例子。 +在接下来的章节中,你会看到关于关系型数据库、NoSQL 数据库、安全等方面的示例。 -## **FastAPI** 兼容性 +## **FastAPI** 兼容性 { #fastapi-compatibility } -依赖注入系统如此简洁的特性,让 **FastAPI** 可以与下列系统兼容: +依赖注入系统的简洁让 **FastAPI** 能与以下内容兼容: -* 关系型数据库 +* 各类关系型数据库 * NoSQL 数据库 -* 外部支持库 +* 外部包 * 外部 API -* 认证和鉴权系统 +* 认证与授权系统 * API 使用监控系统 * 响应数据注入系统 -* 等等…… +* 等等... -## 简单而强大 +## 簡单而强大 { #simple-and-powerful } -虽然,**层级式依赖注入系统**的定义与使用十分简单,但它却非常强大。 +虽然**层级式依赖注入系统**的定义与使用非常简单,但它依然非常强大。 -比如,可以定义依赖其他依赖项的依赖项。 +你可以定义依赖其他依赖项的依赖项。 -最后,依赖项层级树构建后,**依赖注入系统**会处理所有依赖项及其子依赖项,并为每一步操作提供(注入)结果。 +最终会构建出一个依赖项的层级树,**依赖注入**系统会处理所有这些依赖(及其子依赖),并在每一步提供(注入)相应的结果。 -比如,下面有 4 个 API 路径操作(*端点*): +例如,假设你有 4 个 API 路径操作(*端点*): * `/items/public/` * `/items/private/` * `/users/{user_id}/activate` * `/items/pro/` -开发人员可以使用依赖项及其子依赖项为这些路径操作添加不同的权限: +你可以仅通过依赖项及其子依赖项为它们添加不同的权限要求: ```mermaid graph TB @@ -205,8 +243,8 @@ admin_user --> activate_user paying_user --> pro_items ``` -## 与 **OpenAPI** 集成 +## 与 **OpenAPI** 集成 { #integrated-with-openapi_1 } -在声明需求时,所有这些依赖项还会把参数、验证等功能添加至路径操作。 +在声明需求的同时,所有这些依赖项也会为你的*路径操作*添加参数、验证等内容。 -**FastAPI** 负责把上述内容全部添加到 OpenAPI 概图,并显示在交互文档中。 +**FastAPI** 会负责把这些全部添加到 OpenAPI 模式中,以便它们显示在交互式文档系统里。 diff --git a/docs/zh/docs/tutorial/dependencies/sub-dependencies.md b/docs/zh/docs/tutorial/dependencies/sub-dependencies.md index 2e7746433..0b73c392d 100644 --- a/docs/zh/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/zh/docs/tutorial/dependencies/sub-dependencies.md @@ -1,4 +1,4 @@ -# 子依赖项 +# 子依赖项 { #sub-dependencies } FastAPI 支持创建含**子依赖项**的依赖项。 @@ -6,34 +6,34 @@ FastAPI 支持创建含**子依赖项**的依赖项。 **FastAPI** 负责处理解析不同深度的子依赖项。 -### 第一层依赖项 +## 第一层依赖项 “dependable” { #first-dependency-dependable } -下列代码创建了第一层依赖项: +你可以创建一个第一层依赖项(“dependable”),如下: -{* ../../docs_src/dependencies/tutorial005.py hl[8:9] *} +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *} 这段代码声明了类型为 `str` 的可选查询参数 `q`,然后返回这个查询参数。 这个函数很简单(不过也没什么用),但却有助于让我们专注于了解子依赖项的工作方式。 -### 第二层依赖项 +## 第二层依赖项,“dependable”和“dependant” { #second-dependency-dependable-and-dependant } -接下来,创建另一个依赖项函数,并同时用该依赖项自身再声明一个依赖项(所以这也是一个「依赖项」): +接下来,创建另一个依赖项函数(一个“dependable”),并同时为它自身再声明一个依赖项(因此它同时也是一个“dependant”): -{* ../../docs_src/dependencies/tutorial005.py hl[13] *} +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *} 这里重点说明一下声明的参数: -* 尽管该函数自身是依赖项,但还声明了另一个依赖项(它「依赖」于其他对象) +* 尽管该函数自身是依赖项(“dependable”),但还声明了另一个依赖项(它“依赖”于其他对象) * 该函数依赖 `query_extractor`, 并把 `query_extractor` 的返回值赋给参数 `q` * 同时,该函数还声明了类型是 `str` 的可选 cookie(`last_query`) * 用户未提供查询参数 `q` 时,则使用上次使用后保存在 cookie 中的查询 -### 使用依赖项 +## 使用依赖项 { #use-the-dependency } 接下来,就可以使用依赖项: -{* ../../docs_src/dependencies/tutorial005.py hl[22] *} +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[23] *} /// info | 信息 @@ -54,20 +54,39 @@ read_query["/items/"] query_extractor --> query_or_cookie_extractor --> read_query ``` -## 多次使用同一个依赖项 +## 多次使用同一个依赖项 { #using-the-same-dependency-multiple-times } 如果在同一个*路径操作* 多次声明了同一个依赖项,例如,多个依赖项共用一个子依赖项,**FastAPI** 在处理同一请求时,只调用一次该子依赖项。 FastAPI 不会为同一个请求多次调用同一个依赖项,而是把依赖项的返回值进行「缓存」,并把它传递给同一请求中所有需要使用该返回值的「依赖项」。 -在高级使用场景中,如果不想使用「缓存」值,而是为需要在同一请求的每一步操作(多次)中都实际调用依赖项,可以把 `Depends` 的参数 `use_cache` 的值设置为 `False` : +在高级使用场景中,如果不想使用「缓存」值,而是为需要在同一请求的每一步操作(多次)中都实际调用依赖项,可以把 `Depends` 的参数 `use_cache` 的值设置为 `False`: + +//// tab | Python 3.9+ + +```Python hl_lines="1" +async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): + return {"fresh_value": fresh_value} +``` + +//// + +//// tab | Python 3.9+ 非 Annotated + +/// tip | 提示 + +尽可能优先使用 `Annotated` 版本。 + +/// ```Python hl_lines="1" async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)): return {"fresh_value": fresh_value} ``` -## 小结 +//// + +## 小结 { #recap } 千万别被本章里这些花里胡哨的词藻吓倒了,其实**依赖注入**系统非常简单。 diff --git a/docs/zh/docs/tutorial/encoder.md b/docs/zh/docs/tutorial/encoder.md index e52aaa2ed..f47a09201 100644 --- a/docs/zh/docs/tutorial/encoder.md +++ b/docs/zh/docs/tutorial/encoder.md @@ -1,4 +1,4 @@ -# JSON 兼容编码器 +# JSON 兼容编码器 { #json-compatible-encoder } 在某些情况下,您可能需要将数据类型(如Pydantic模型)转换为与JSON兼容的数据类型(如`dict`、`list`等)。 @@ -6,7 +6,7 @@ 对于这种要求, **FastAPI**提供了`jsonable_encoder()`函数。 -## 使用`jsonable_encoder` +## 使用`jsonable_encoder` { #using-the-jsonable-encoder } 让我们假设你有一个数据库名为`fake_db`,它只能接收与JSON兼容的数据。 @@ -28,7 +28,7 @@ 这个操作不会返回一个包含JSON格式(作为字符串)数据的庞大的`str`。它将返回一个Python标准数据结构(例如`dict`),其值和子值都与JSON兼容。 -/// note +/// note | 注意 `jsonable_encoder`实际上是FastAPI内部用来转换数据的。但是它在许多其他场景中也很有用。 diff --git a/docs/zh/docs/tutorial/extra-data-types.md b/docs/zh/docs/tutorial/extra-data-types.md index b064ee551..2cefd163d 100644 --- a/docs/zh/docs/tutorial/extra-data-types.md +++ b/docs/zh/docs/tutorial/extra-data-types.md @@ -1,4 +1,4 @@ -# 额外数据类型 +# 额外数据类型 { #extra-data-types } 到目前为止,您一直在使用常见的数据类型,如: @@ -15,9 +15,9 @@ * 传入请求的数据转换。 * 响应数据转换。 * 数据验证。 -* 自动补全和文档。 +* 自动注解和文档。 -## 其他数据类型 +## 其他数据类型 { #other-data-types } 下面是一些你可以使用的其他数据类型: @@ -36,12 +36,12 @@ * `datetime.timedelta`: * 一个 Python `datetime.timedelta`. * 在请求和响应中将表示为 `float` 代表总秒数。 - * Pydantic 也允许将其表示为 "ISO 8601 时间差异编码", 查看文档了解更多信息。 + * Pydantic 也允许将其表示为 "ISO 8601 时间差异编码", 查看文档了解更多信息。 * `frozenset`: * 在请求和响应中,作为 `set` 对待: * 在请求中,列表将被读取,消除重复,并将其转换为一个 `set`。 * 在响应中 `set` 将被转换为 `list` 。 - * 产生的模式将指定那些 `set` 的值是唯一的 (使用 JSON 模式的 `uniqueItems`)。 + * 产生的模式将指定那些 `set` 的值是唯一的 (使用 JSON Schema 的 `uniqueItems`)。 * `bytes`: * 标准的 Python `bytes`。 * 在请求和响应中被当作 `str` 处理。 @@ -49,9 +49,9 @@ * `Decimal`: * 标准的 Python `Decimal`。 * 在请求和响应中被当做 `float` 一样处理。 -* 您可以在这里检查所有有效的pydantic数据类型: Pydantic data types. +* 您可以在这里检查所有有效的 Pydantic 数据类型: Pydantic data types. -## 例子 +## 例子 { #example } 下面是一个*路径操作*的示例,其中的参数使用了上面的一些类型。 diff --git a/docs/zh/docs/tutorial/extra-models.md b/docs/zh/docs/tutorial/extra-models.md index ccfb3aa5a..4d18c76ec 100644 --- a/docs/zh/docs/tutorial/extra-models.md +++ b/docs/zh/docs/tutorial/extra-models.md @@ -1,4 +1,4 @@ -# 更多模型 +# 更多模型 { #extra-models } 书接上文,多个关联模型这种情况很常见。 @@ -6,29 +6,29 @@ * **输入模型**应该含密码 * **输出模型**不应含密码 -* **数据库模型**需要加密的密码 +* **数据库模型**可能需要包含哈希后的密码 -/// danger | 危险 +/// danger -千万不要存储用户的明文密码。始终存储可以进行验证的**安全哈希值**。 +不要存储用户的明文密码。始终只存储之后可用于校验的“安全哈希”。 -如果不了解这方面的知识,请参阅[安全性中的章节](security/simple-oauth2.md#password-hashing){.internal-link target=_blank},了解什么是**密码哈希**。 +如果你还不了解,可以在[安全性章节](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}中学习什么是“密码哈希”。 /// -## 多个模型 +## 多个模型 { #multiple-models } 下面的代码展示了不同模型处理密码字段的方式,及使用位置的大致思路: {* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} -### `**user_in.dict()` 简介 +### 关于 `**user_in.model_dump()` { #about-user-in-model-dump } -#### Pydantic 的 `.dict()` +#### Pydantic 的 `.model_dump()` { #pydantics-model-dump } `user_in` 是类 `UserIn` 的 Pydantic 模型。 -Pydantic 模型支持 `.dict()` 方法,能返回包含模型数据的**字典**。 +Pydantic 模型有 `.model_dump()` 方法,会返回包含模型数据的 `dict`。 因此,如果使用如下方式创建 Pydantic 对象 `user_in`: @@ -39,10 +39,10 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com 就能以如下方式调用: ```Python -user_dict = user_in.dict() +user_dict = user_in.model_dump() ``` -现在,变量 `user_dict`中的就是包含数据的**字典**(变量 `user_dict` 是字典,不是 Pydantic 模型对象)。 +现在,变量 `user_dict` 中的是包含数据的 `dict`(它是 `dict`,不是 Pydantic 模型对象)。 以如下方式调用: @@ -50,7 +50,7 @@ user_dict = user_in.dict() print(user_dict) ``` -输出的就是 Python **字典**: +输出的就是 Python `dict`: ```Python { @@ -61,9 +61,9 @@ print(user_dict) } ``` -#### 解包 `dict` +#### 解包 `dict` { #unpacking-a-dict } -把**字典** `user_dict` 以 `**user_dict` 形式传递给函数(或类),Python 会执行**解包**操作。它会把 `user_dict` 的键和值作为关键字参数直接传递。 +把 `dict`(如 `user_dict`)以 `**user_dict` 形式传递给函数(或类),Python 会执行“解包”。它会把 `user_dict` 的键和值作为关键字参数直接传递。 因此,接着上面的 `user_dict` 继续编写如下代码: @@ -82,7 +82,7 @@ UserInDB( ) ``` -或更精准,直接把可能会用到的内容与 `user_dict` 一起使用: +或更精准,直接使用 `user_dict`(无论它将来包含什么字段): ```Python UserInDB( @@ -93,31 +93,31 @@ UserInDB( ) ``` -#### 用其它模型中的内容生成 Pydantic 模型 +#### 用另一个模型的内容生成 Pydantic 模型 { #a-pydantic-model-from-the-contents-of-another } -上例中 ,从 `user_in.dict()` 中得到了 `user_dict`,下面的代码: +上例中 ,从 `user_in.model_dump()` 中得到了 `user_dict`,下面的代码: ```Python -user_dict = user_in.dict() +user_dict = user_in.model_dump() UserInDB(**user_dict) ``` 等效于: ```Python -UserInDB(**user_in.dict()) +UserInDB(**user_in.model_dump()) ``` -……因为 `user_in.dict()` 是字典,在传递给 `UserInDB` 时,把 `**` 加在 `user_in.dict()` 前,可以让 Python 进行**解包**。 +……因为 `user_in.model_dump()` 是 `dict`,在传递给 `UserInDB` 时,把 `**` 加在 `user_in.model_dump()` 前,可以让 Python 进行解包。 这样,就可以用其它 Pydantic 模型中的数据生成 Pydantic 模型。 -#### 解包 `dict` 和更多关键字 +#### 解包 `dict` 并添加额外关键字参数 { #unpacking-a-dict-and-extra-keywords } 接下来,继续添加关键字参数 `hashed_password=hashed_password`,例如: ```Python -UserInDB(**user_in.dict(), hashed_password=hashed_password) +UserInDB(**user_in.model_dump(), hashed_password=hashed_password) ``` ……输出结果如下: @@ -132,68 +132,80 @@ UserInDB( ) ``` -/// warning | 警告 +/// warning -辅助的附加函数只是为了演示可能的数据流,但它们显然不能提供任何真正的安全机制。 +配套的辅助函数 `fake_password_hasher` 和 `fake_save_user` 仅用于演示可能的数据流,当然并不提供真实的安全性。 /// -## 减少重复 +## 减少重复 { #reduce-duplication } -**FastAPI** 的核心思想就是减少代码重复。 +减少代码重复是 **FastAPI** 的核心思想之一。 代码重复会导致 bug、安全问题、代码失步等问题(更新了某个位置的代码,但没有同步更新其它位置的代码)。 上面的这些模型共享了大量数据,拥有重复的属性名和类型。 -FastAPI 可以做得更好。 +我们可以做得更好。 -声明 `UserBase` 模型作为其它模型的基类。然后,用该类衍生出继承其属性(类型声明、验证等)的子类。 +声明 `UserBase` 模型作为其它模型的基类。然后,用该类衍生出继承其属性(类型声明、校验等)的子类。 所有数据转换、校验、文档等功能仍将正常运行。 -这样,就可以仅声明模型之间的差异部分(具有明文的 `password`、具有 `hashed_password` 以及不包括密码)。 - -通过这种方式,可以只声明模型之间的区别(分别包含明文密码、哈希密码,以及无密码的模型)。 +这样,就可以仅声明模型之间的差异部分(具有明文的 `password`、具有 `hashed_password` 以及不包括密码): {* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *} -## `Union` 或者 `anyOf` +## `Union` 或 `anyOf` { #union-or-anyof } -响应可以声明为两种类型的 `Union` 类型,即该响应可以是两种类型中的任意类型。 +响应可以声明为两个或多个类型的 `Union`,即该响应可以是这些类型中的任意一种。 -在 OpenAPI 中可以使用 `anyOf` 定义。 +在 OpenAPI 中会用 `anyOf` 表示。 为此,请使用 Python 标准类型提示 `typing.Union`: -/// note | 笔记 +/// note -定义 `Union` 类型时,要把详细的类型写在前面,然后是不太详细的类型。下例中,更详细的 `PlaneItem` 位于 `Union[PlaneItem,CarItem]` 中的 `CarItem` 之前。 +定义 `Union` 类型时,要把更具体的类型写在前面,然后是不太具体的类型。下例中,更具体的 `PlaneItem` 位于 `Union[PlaneItem, CarItem]` 中的 `CarItem` 之前。 /// {* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *} -## 模型列表 +### Python 3.10 中的 `Union` { #union-in-python-3-10 } -使用同样的方式也可以声明由对象列表构成的响应。 +在这个示例中,我们把 `Union[PlaneItem, CarItem]` 作为参数 `response_model` 的值传入。 -为此,请使用标准的 Python `typing.List`: +因为这是作为“参数的值”而不是放在“类型注解”中,所以即使在 Python 3.10 也必须使用 `Union`。 + +如果是在类型注解中,我们就可以使用竖线: + +```Python +some_variable: PlaneItem | CarItem +``` + +但如果把它写成赋值 `response_model=PlaneItem | CarItem`,就会报错,因为 Python 会尝试在 `PlaneItem` 和 `CarItem` 之间执行一个“无效的运算”,而不是把它当作类型注解来解析。 + +## 模型列表 { #list-of-models } + +同样地,你可以声明由对象列表构成的响应。 + +为此,请使用标准的 Python `typing.List`(在 Python 3.9+ 中也可以直接用 `list`): {* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} -## 任意 `dict` 构成的响应 +## 任意 `dict` 的响应 { #response-with-arbitrary-dict } -任意的 `dict` 都能用于声明响应,只要声明键和值的类型,无需使用 Pydantic 模型。 +你也可以使用普通的任意 `dict` 来声明响应,只需声明键和值的类型,无需使用 Pydantic 模型。 -事先不知道可用的字段 / 属性名时(Pydantic 模型必须知道字段是什么),这种方式特别有用。 +如果你事先不知道有效的字段/属性名(Pydantic 模型需要预先知道字段)时,这很有用。 -此时,可以使用 `typing.Dict`: +此时,可以使用 `typing.Dict`(在 Python 3.9+ 中也可以直接用 `dict`): {* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} -## 小结 +## 小结 { #recap } -针对不同场景,可以随意使用不同的 Pydantic 模型继承定义的基类。 +针对不同场景,可以随意使用不同的 Pydantic 模型并通过继承复用。 -实体必须具有不同的**状态**时,不必为不同状态的实体单独定义数据模型。例如,用户**实体**就有包含 `password`、包含 `password_hash` 以及不含密码等多种状态。 +当一个实体需要具备不同的“状态”时,无需只为该实体定义一个数据模型。例如,用户“实体”就可能有包含 `password`、包含 `password_hash` 以及不含密码等多种状态。 diff --git a/docs/zh/docs/tutorial/first-steps.md b/docs/zh/docs/tutorial/first-steps.md index 2d7c35c8c..5d01884b8 100644 --- a/docs/zh/docs/tutorial/first-steps.md +++ b/docs/zh/docs/tutorial/first-steps.md @@ -1,8 +1,8 @@ -# 第一步 +# 第一步 { #first-steps } 最简单的 FastAPI 文件可能像下面这样: -{* ../../docs_src/first_steps/tutorial001.py *} +{* ../../docs_src/first_steps/tutorial001_py39.py *} 将其复制到 `main.py` 文件中。 @@ -56,7 +56,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) 该行显示了你的应用在本机所提供服务的 URL 地址。 -### 查看 +### 查看 { #check-it } 打开浏览器访问 http://127.0.0.1:8000。 @@ -66,7 +66,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) {"message": "Hello World"} ``` -### 交互式 API 文档 +### 交互式 API 文档 { #interactive-api-docs } 跳转到 http://127.0.0.1:8000/docs。 @@ -74,7 +74,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) -### 可选的 API 文档 +### 可选的 API 文档 { #alternative-api-docs } 前往 http://127.0.0.1:8000/redoc。 @@ -82,35 +82,35 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) -### OpenAPI +### OpenAPI { #openapi } **FastAPI** 使用定义 API 的 **OpenAPI** 标准将你的所有 API 转换成「模式」。 -#### 「模式」 +#### 「模式」 { #schema } 「模式」是对事物的一种定义或描述。它并非具体的实现代码,而只是抽象的描述。 -#### API「模式」 +#### API「模式」 { #api-schema } 在这种场景下,OpenAPI 是一种规定如何定义 API 模式的规范。 「模式」的定义包括你的 API 路径,以及它们可能使用的参数等等。 -#### 数据「模式」 +#### 数据「模式」 { #data-schema } 「模式」这个术语也可能指的是某些数据比如 JSON 的结构。 在这种情况下,它可以表示 JSON 的属性及其具有的数据类型,等等。 -#### OpenAPI 和 JSON Schema +#### OpenAPI 和 JSON Schema { #openapi-and-json-schema } OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送和接收的数据的定义(或称为「模式」),这些定义通过 JSON 数据模式标准 **JSON Schema** 所生成。 -#### 查看 `openapi.json` +#### 查看 `openapi.json` { #check-the-openapi-json } 如果你对原始的 OpenAPI 模式长什么样子感到好奇,FastAPI 自动生成了包含所有 API 描述的 JSON(模式)。 -你可以直接在:http://127.0.0.1:8000/openapi.json 看到它。 +你可以直接在:http://127.0.0.1:800api.json 看到它。 它将显示以如下内容开头的 JSON: @@ -135,7 +135,7 @@ OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送 ... ``` -#### OpenAPI 的用途 +#### OpenAPI 的用途 { #what-is-openapi-for } 驱动 FastAPI 内置的 2 个交互式文档系统的正是 OpenAPI 模式。 @@ -143,11 +143,47 @@ OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送 你还可以使用它自动生成与你的 API 进行通信的客户端代码。例如 web 前端,移动端或物联网嵌入程序。 -## 分步概括 +### 部署你的应用(可选) { #deploy-your-app-optional } -### 步骤 1:导入 `FastAPI` +你可以选择将 FastAPI 应用部署到 FastAPI Cloud,如果还没有,先去加入候补名单。🚀 -{* ../../docs_src/first_steps/tutorial001.py hl[1] *} +如果你已经拥有 **FastAPI Cloud** 账户(我们从候补名单邀请了你 😉),你可以用一条命令部署应用。 + +部署前,先确保已登录: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +然后部署你的应用: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +就这些!现在你可以通过该 URL 访问你的应用了。✨ + +## 分步概括 { #recap-step-by-step } + +### 步骤 1:导入 `FastAPI` { #step-1-import-fastapi } + +{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} `FastAPI` 是一个为你的 API 提供了所有功能的 Python 类。 @@ -159,17 +195,17 @@ OpenAPI 为你的 API 定义 API 模式。该模式中包含了你的 API 发送 /// -### 步骤 2:创建一个 `FastAPI`「实例」 +### 步骤 2:创建一个 `FastAPI`「实例」 { #step-2-create-a-fastapi-instance } -{* ../../docs_src/first_steps/tutorial001.py hl[3] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *} 这里的变量 `app` 会是 `FastAPI` 类的一个「实例」。 这个实例将是创建你所有 API 的主要交互对象。 -### 步骤 3:创建一个*路径操作* +### 步骤 3:创建一个*路径操作* { #step-3-create-a-path-operation } -#### 路径 +#### 路径 { #path } 这里的「路径」指的是 URL 中从第一个 `/` 起的后半部分。 @@ -193,7 +229,7 @@ https://example.com/items/foo 开发 API 时,「路径」是用来分离「关注点」和「资源」的主要手段。 -#### 操作 +#### 操作 { #operation } 这里的「操作」指的是一种 HTTP「方法」。 @@ -228,9 +264,9 @@ https://example.com/items/foo 我们也打算称呼它们为「操作」。 -#### 定义一个*路径操作装饰器* +#### 定义一个*路径操作装饰器* { #define-a-path-operation-decorator } -{* ../../docs_src/first_steps/tutorial001.py hl[6] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *} `@app.get("/")` 告诉 **FastAPI** 在它下方的函数负责处理如下访问请求: @@ -276,7 +312,7 @@ https://example.com/items/foo /// -### 步骤 4:定义**路径操作函数** +### 步骤 4:定义**路径操作函数** { #step-4-define-the-path-operation-function } 这是我们的「**路径操作函数**」: @@ -284,7 +320,7 @@ https://example.com/items/foo * **操作**:是 `get`。 * **函数**:是位于「装饰器」下方的函数(位于 `@app.get("/")` 下方)。 -{* ../../docs_src/first_steps/tutorial001.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} 这是一个 Python 函数。 @@ -296,17 +332,17 @@ https://example.com/items/foo 你也可以将其定义为常规函数而不使用 `async def`: -{* ../../docs_src/first_steps/tutorial003.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} /// note -如果你不知道两者的区别,请查阅 [并发: *赶时间吗?*](../async.md#_1){.internal-link target=_blank}。 +如果你不知道两者的区别,请查阅 [并发: *赶时间吗?*](../async.md#in-a-hurry){.internal-link target=_blank}。 /// -### 步骤 5:返回内容 +### 步骤 5:返回内容 { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} 你可以返回一个 `dict`、`list`,像 `str`、`int` 一样的单个值,等等。 @@ -314,10 +350,31 @@ https://example.com/items/foo 还有许多其他将会自动转换为 JSON 的对象和模型(包括 ORM 对象等)。尝试下使用你最喜欢的一种,它很有可能已经被支持。 -## 总结 +### 步骤 6:部署 { #step-6-deploy-it } + +用一条命令将你的应用部署到 **FastAPI Cloud**:`fastapi deploy`。🎉 + +#### 关于 FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** 由 **FastAPI** 的作者和团队打造。 + +它以最小的投入简化了 **构建**、**部署** 和 **访问** API 的流程。 + +它把使用 FastAPI 构建应用的相同**开发者体验**带到了将应用**部署**到云端的过程。🎉 + +FastAPI Cloud 是 *FastAPI 及其朋友们* 开源项目的主要赞助和资金提供方。✨ + +#### 部署到其他云服务商 { #deploy-to-other-cloud-providers } + +FastAPI 是开源并基于标准的。你可以将 FastAPI 应用部署到你选择的任何云服务商。 + +按照你的云服务商的指南部署 FastAPI 应用即可。🤓 + +## 总结 { #recap } * 导入 `FastAPI`。 * 创建一个 `app` 实例。 * 编写一个**路径操作装饰器**,如 `@app.get("/")`。 * 定义一个**路径操作函数**,如 `def root(): ...`。 * 使用命令 `fastapi dev` 运行开发服务器。 +* 可选:使用 `fastapi deploy` 部署你的应用。 diff --git a/docs/zh/docs/tutorial/handling-errors.md b/docs/zh/docs/tutorial/handling-errors.md index ae667b74a..986a84772 100644 --- a/docs/zh/docs/tutorial/handling-errors.md +++ b/docs/zh/docs/tutorial/handling-errors.md @@ -1,64 +1,62 @@ -# 处理错误 +# 处理错误 { #handling-errors } -某些情况下,需要向客户端返回错误提示。 +某些情况下,需要向使用你的 API 的客户端返回错误提示。 -这里所谓的客户端包括前端浏览器、其他应用程序、物联网设备等。 +这里所谓的客户端包括前端浏览器、他人的代码、物联网设备等。 -需要向客户端返回错误提示的场景主要如下: +你可能需要告诉客户端: -- 客户端没有执行操作的权限 -- 客户端没有访问资源的权限 +- 客户端没有执行该操作的权限 +- 客户端没有访问该资源的权限 - 客户端要访问的项目不存在 -- 等等 ... +- 等等 遇到这些情况时,通常要返回 **4XX**(400 至 499)**HTTP 状态码**。 -**4XX** 状态码与表示请求成功的 **2XX**(200 至 299) HTTP 状态码类似。 +这与表示请求成功的 **2XX**(200 至 299)HTTP 状态码类似。那些“200”状态码表示某种程度上的“成功”。 -只不过,**4XX** 状态码表示客户端发生的错误。 +而 **4XX** 状态码表示客户端发生了错误。 大家都知道**「404 Not Found」**错误,还有调侃这个错误的笑话吧? -## 使用 `HTTPException` +## 使用 `HTTPException` { #use-httpexception } 向客户端返回 HTTP 错误响应,可以使用 `HTTPException`。 -### 导入 `HTTPException` +### 导入 `HTTPException` { #import-httpexception } -{* ../../docs_src/handling_errors/tutorial001.py hl[1] *} +{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *} -### 触发 `HTTPException` +### 在代码中触发 `HTTPException` { #raise-an-httpexception-in-your-code } `HTTPException` 是额外包含了和 API 有关数据的常规 Python 异常。 因为是 Python 异常,所以不能 `return`,只能 `raise`。 -如在调用*路径操作函数*里的工具函数时,触发了 `HTTPException`,FastAPI 就不再继续执行*路径操作函数*中的后续代码,而是立即终止请求,并把 `HTTPException` 的 HTTP 错误发送至客户端。 +这也意味着,如果你在*路径操作函数*里调用的某个工具函数内部触发了 `HTTPException`,那么*路径操作函数*中后续的代码将不会继续执行,请求会立刻终止,并把 `HTTPException` 的 HTTP 错误发送给客户端。 -在介绍依赖项与安全的章节中,您可以了解更多用 `raise` 异常代替 `return` 值的优势。 +在介绍依赖项与安全的章节中,你可以更直观地看到用 `raise` 异常代替 `return` 值的优势。 -本例中,客户端用 `ID` 请求的 `item` 不存在时,触发状态码为 `404` 的异常: +本例中,客户端用不存在的 `ID` 请求 `item` 时,触发状态码为 `404` 的异常: -{* ../../docs_src/handling_errors/tutorial001.py hl[11] *} +{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *} -### 响应结果 +### 响应结果 { #the-resulting-response } -请求为 `http://example.com/items/foo`(`item_id` 为 `「foo」`)时,客户端会接收到 HTTP 状态码 - 200 及如下 JSON 响应结果: +请求为 `http://example.com/items/foo`(`item_id` 为 `"foo"`)时,客户端会接收到 HTTP 状态码 200 及如下 JSON 响应结果: ```JSON { "item": "The Foo Wrestlers" } - ``` -但如果客户端请求 `http://example.com/items/bar`(`item_id` `「bar」` 不存在时),则会接收到 HTTP 状态码 - 404(「未找到」错误)及如下 JSON 响应结果: +但如果客户端请求 `http://example.com/items/bar`(不存在的 `item_id` `"bar"`),则会接收到 HTTP 状态码 404(“未找到”错误)及如下 JSON 响应结果: ```JSON { "detail": "Item not found" } - ``` /// tip | 提示 @@ -71,68 +69,67 @@ /// -## 添加自定义响应头 +## 添加自定义响应头 { #add-custom-headers } -有些场景下要为 HTTP 错误添加自定义响应头。例如,出于某些方面的安全需要。 +有些场景下要为 HTTP 错误添加自定义响应头。例如,出于某些类型的安全需要。 -一般情况下可能不会需要在代码中直接使用响应头。 +一般情况下你可能不会在代码中直接使用它。 -但对于某些高级应用场景,还是需要添加自定义响应头: +但在某些高级场景中需要时,你可以添加自定义响应头: -{* ../../docs_src/handling_errors/tutorial002.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *} -## 安装自定义异常处理器 +## 安装自定义异常处理器 { #install-custom-exception-handlers } -添加自定义处理器,要使用 [Starlette 的异常工具](https://www.starlette.dev/exceptions/)。 +可以使用与 Starlette 相同的异常处理工具添加自定义异常处理器。 -假设要触发的自定义异常叫作 `UnicornException`。 +假设有一个自定义异常 `UnicornException`(你自己或你使用的库可能会 `raise` 它)。 -且需要 FastAPI 实现全局处理该异常。 +并且你希望用 FastAPI 在全局处理该异常。 -此时,可以用 `@app.exception_handler()` 添加自定义异常控制器: +此时,可以用 `@app.exception_handler()` 添加自定义异常处理器: -{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *} +{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *} -请求 `/unicorns/yolo` 时,路径操作会触发 `UnicornException`。 +这里,请求 `/unicorns/yolo` 时,路径操作会触发 `UnicornException`。 但该异常将会被 `unicorn_exception_handler` 处理。 -接收到的错误信息清晰明了,HTTP 状态码为 `418`,JSON 内容如下: +你会收到清晰的错误信息,HTTP 状态码为 `418`,JSON 内容如下: ```JSON {"message": "Oops! yolo did something. There goes a rainbow..."} - ``` /// note | 技术细节 -`from starlette.requests import Request` 和 `from starlette.responses import JSONResponse` 也可以用于导入 `Request` 和 `JSONResponse`。 +也可以使用 `from starlette.requests import Request` 和 `from starlette.responses import JSONResponse`。 -**FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为快捷方式,但大部分响应操作都可以直接从 Starlette 导入。同理,`Request` 也是如此。 +**FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为便捷方式,但大多数可用的响应都直接来自 Starlette。`Request` 也是如此。 /// -## 覆盖默认异常处理器 +## 覆盖默认异常处理器 { #override-the-default-exception-handlers } **FastAPI** 自带了一些默认异常处理器。 -触发 `HTTPException` 或请求无效数据时,这些处理器返回默认的 JSON 响应结果。 +当你触发 `HTTPException`,或者请求中包含无效数据时,这些处理器负责返回默认的 JSON 响应。 -不过,也可以使用自定义处理器覆盖默认异常处理器。 +你也可以用自己的处理器覆盖它们。 -### 覆盖请求验证异常 +### 覆盖请求验证异常 { #override-request-validation-exceptions } 请求中包含无效数据时,**FastAPI** 内部会触发 `RequestValidationError`。 -该异常也内置了默认异常处理器。 +它也内置了该异常的默认处理器。 -覆盖默认异常处理器时需要导入 `RequestValidationError`,并用 `@app.excption_handler(RequestValidationError)` 装饰异常处理器。 +要覆盖它,导入 `RequestValidationError`,并用 `@app.exception_handler(RequestValidationError)` 装饰你的异常处理器。 -这样,异常处理器就可以接收 `Request` 与异常。 +异常处理器会接收 `Request` 和该异常。 -{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} +{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *} -访问 `/items/foo`,可以看到默认的 JSON 错误信息: +现在,访问 `/items/foo` 时,默认的 JSON 错误为: ```JSON { @@ -147,59 +144,46 @@ } ] } - ``` -被替换为了以下文本格式的错误信息: +将得到如下文本内容: ``` -1 validation error -path -> item_id - value is not a valid integer (type=type_error.integer) - +Validation errors: +Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer ``` -### `RequestValidationError` vs `ValidationError` +### 覆盖 `HTTPException` 错误处理器 { #override-the-httpexception-error-handler } -/// warning | 警告 +同理,也可以覆盖 `HTTPException` 的处理器。 -如果您觉得现在还用不到以下技术细节,可以先跳过下面的内容。 +例如,只为这些错误返回纯文本响应,而不是 JSON: -/// - -`RequestValidationError` 是 Pydantic 的 `ValidationError` 的子类。 - -**FastAPI** 调用的就是 `RequestValidationError` 类,因此,如果在 `response_model` 中使用 Pydantic 模型,且数据有错误时,在日志中就会看到这个错误。 - -但客户端或用户看不到这个错误。反之,客户端接收到的是 HTTP 状态码为 `500` 的「内部服务器错误」。 - -这是因为在*响应*或代码(不是在客户端的请求里)中出现的 Pydantic `ValidationError` 是代码的 bug。 - -修复错误时,客户端或用户不能访问错误的内部信息,否则会造成安全隐患。 - -### 覆盖 `HTTPException` 错误处理器 - -同理,也可以覆盖 `HTTPException` 处理器。 - -例如,只为错误返回纯文本响应,而不是返回 JSON 格式的内容: - -{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *} +{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *} /// note | 技术细节 还可以使用 `from starlette.responses import PlainTextResponse`。 -**FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为快捷方式,但大部分响应都可以直接从 Starlette 导入。 +**FastAPI** 提供了与 `starlette.responses` 相同的 `fastapi.responses` 作为便捷方式,但大多数可用的响应都直接来自 Starlette。 /// -### 使用 `RequestValidationError` 的请求体 +/// warning | 警告 -`RequestValidationError` 包含其接收到的无效数据请求的 `body` 。 +请注意,`RequestValidationError` 包含发生验证错误的文件名和行号信息,你可以在需要时将其记录到日志中以提供相关信息。 -开发时,可以用这个请求体生成日志、调试错误,并返回给用户。 +但这也意味着,如果你只是将其直接转换为字符串并返回,可能会泄露一些关于系统的细节信息。因此,这里的代码会提取并分别显示每个错误。 -{* ../../docs_src/handling_errors/tutorial005.py hl[14] *} +/// + +### 使用 `RequestValidationError` 的请求体 { #use-the-requestvalidationerror-body } + +`RequestValidationError` 包含其接收到的带有无效数据的请求体 `body`。 + +开发时,你可以用它来记录请求体、调试错误,或返回给用户等。 + +{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *} 现在试着发送一个无效的 `item`,例如: @@ -208,10 +192,9 @@ path -> item_id "title": "towel", "size": "XL" } - ``` -收到的响应包含 `body` 信息,并说明数据是无效的: +收到的响应会告诉你数据无效,并包含收到的请求体: ```JSON hl_lines="12-15" { @@ -230,40 +213,32 @@ path -> item_id "size": "XL" } } - ``` -### FastAPI `HTTPException` vs Starlette `HTTPException` +#### FastAPI 的 `HTTPException` vs Starlette 的 `HTTPException` { #fastapis-httpexception-vs-starlettes-httpexception } **FastAPI** 也提供了自有的 `HTTPException`。 -**FastAPI** 的 `HTTPException` 继承自 Starlette 的 `HTTPException` 错误类。 +**FastAPI** 的 `HTTPException` 错误类继承自 Starlette 的 `HTTPException` 错误类。 -它们之间的唯一区别是,**FastAPI** 的 `HTTPException` 可以在响应中添加响应头。 +它们之间的唯一区别是,**FastAPI** 的 `HTTPException` 在 `detail` 字段中接受任意可转换为 JSON 的数据,而 Starlette 的 `HTTPException` 只接受字符串。 -OAuth 2.0 等安全工具需要在内部调用这些响应头。 - -因此你可以继续像平常一样在代码中触发 **FastAPI** 的 `HTTPException` 。 +因此,你可以继续像平常一样在代码中触发 **FastAPI** 的 `HTTPException`。 但注册异常处理器时,应该注册到来自 Starlette 的 `HTTPException`。 -这样做是为了,当 Starlette 的内部代码、扩展或插件触发 Starlette `HTTPException` 时,处理程序能够捕获、并处理此异常。 +这样做是为了,当 Starlette 的内部代码、扩展或插件触发 Starlette `HTTPException` 时,你的处理器能够捕获并处理它。 -注意,本例代码中同时使用了这两个 `HTTPException`,此时,要把 Starlette 的 `HTTPException` 命名为 `StarletteHTTPException`: +本例中,为了在同一份代码中同时使用两个 `HTTPException`,将 Starlette 的异常重命名为 `StarletteHTTPException`: ```Python from starlette.exceptions import HTTPException as StarletteHTTPException - ``` -### 复用 **FastAPI** 异常处理器 +### 复用 **FastAPI** 的异常处理器 { #reuse-fastapis-exception-handlers } -FastAPI 支持先对异常进行某些处理,然后再使用 **FastAPI** 中处理该异常的默认异常处理器。 +如果你想在自定义处理后仍复用 **FastAPI** 的默认异常处理器,可以从 `fastapi.exception_handlers` 导入并复用这些默认处理器: -从 `fastapi.exception_handlers` 中导入要复用的默认异常处理器: +{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *} -{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *} - -虽然,本例只是输出了夸大其词的错误信息。 - -但也足以说明,可以在处理异常之后再复用默认的异常处理器。 +虽然本例只是用非常夸张的信息打印了错误,但足以说明:你可以先处理异常,然后再复用默认的异常处理器。 diff --git a/docs/zh/docs/tutorial/header-param-models.md b/docs/zh/docs/tutorial/header-param-models.md index 13366aebc..e7d548317 100644 --- a/docs/zh/docs/tutorial/header-param-models.md +++ b/docs/zh/docs/tutorial/header-param-models.md @@ -1,16 +1,16 @@ -# Header 参数模型 +# Header 参数模型 { #header-parameter-models } 如果您有一组相关的 **header 参数**,您可以创建一个 **Pydantic 模型**来声明它们。 这将允许您在**多个地方**能够**重用模型**,并且可以一次性声明所有参数的验证和元数据。😎 -/// note +/// note | 注意 自 FastAPI 版本 `0.115.0` 起支持此功能。🤓 /// -## 使用 Pydantic 模型的 Header 参数 +## 使用 Pydantic 模型的 Header 参数 { #header-parameters-with-a-pydantic-model } 在 **Pydantic 模型**中声明所需的 **header 参数**,然后将参数声明为 `Header` : @@ -18,7 +18,7 @@ **FastAPI** 将从请求中接收到的 **headers** 中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。 -## 查看文档 +## 查看文档 { #check-the-docs } 您可以在文档 UI 的 `/docs` 中查看所需的 headers: @@ -26,7 +26,7 @@ -## 禁止额外的 Headers +## 禁止额外的 Headers { #forbid-extra-headers } 在某些特殊使用情况下(可能并不常见),您可能希望**限制**您想要接收的 headers。 @@ -51,6 +51,22 @@ } ``` -## 总结 +## 禁用下划线转换 { #disable-convert-underscores } + +与常规的 header 参数相同,当参数名中包含下划线时,会**自动转换为连字符**。 + +例如,如果你的代码中有一个名为 `save_data` 的 header 参数,那么预期的 HTTP 头将是 `save-data`,并且在文档中也会以这种形式显示。 + +如果由于某些原因你需要禁用这种自动转换,你也可以在用于 header 参数的 Pydantic 模型中进行设置。 + +{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *} + +/// warning | 警告 + +在将 `convert_underscores` 设为 `False` 之前,请注意某些 HTTP 代理和服务器不允许使用带下划线的 headers。 + +/// + +## 总结 { #summary } 您可以使用 **Pydantic 模型**在 **FastAPI** 中声明 **headers**。😎 diff --git a/docs/zh/docs/tutorial/header-params.md b/docs/zh/docs/tutorial/header-params.md index 19bb455cf..ccb88ae7f 100644 --- a/docs/zh/docs/tutorial/header-params.md +++ b/docs/zh/docs/tutorial/header-params.md @@ -1,14 +1,14 @@ -# Header 参数 +# Header 参数 { #header-parameters } 定义 `Header` 参数的方式与定义 `Query`、`Path`、`Cookie` 参数相同。 -## 导入 `Header` +## 导入 `Header` { #import-header } 首先,导入 `Header`: {* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *} -## 声明 `Header` 参数 +## 声明 `Header` 参数 { #declare-header-parameters } 然后,使用和 `Path`、`Query`、`Cookie` 一样的结构定义 header 参数。 @@ -24,13 +24,13 @@ /// -/// info | 说明 +/// info | 信息 必须使用 `Header` 声明 header 参数,否则该参数会被解释为查询参数。 /// -## 自动转换 +## 自动转换 { #automatic-conversion } `Header` 比 `Path`、`Query` 和 `Cookie` 提供了更多功能。 @@ -54,7 +54,7 @@ /// -## 重复的请求头 +## 重复的请求头 { #duplicate-headers } 有时,可能需要接收重复的请求头。即同一个请求头有多个值。 @@ -84,7 +84,7 @@ X-Token: bar } ``` -## 小结 +## 小结 { #recap } 使用 `Header` 声明请求头的方式与 `Query`、`Path` 、`Cookie` 相同。 diff --git a/docs/zh/docs/tutorial/index.md b/docs/zh/docs/tutorial/index.md index 3ca927337..793458302 100644 --- a/docs/zh/docs/tutorial/index.md +++ b/docs/zh/docs/tutorial/index.md @@ -1,12 +1,12 @@ -# 教程 - 用户指南 +# 教程 - 用户指南 { #tutorial-user-guide } 本教程将一步步向您展示如何使用 **FastAPI** 的绝大部分特性。 -各个章节的内容循序渐进,但是又围绕着单独的主题,所以您可以直接跳转到某个章节以解决您的特定需求。 +各个章节的内容循序渐进,但是又围绕着单独的主题,所以您可以直接跳转到某个章节以解决您的特定 API 需求。 本教程同样可以作为将来的参考手册,所以您可以随时回到本教程并查阅您需要的内容。 -## 运行代码 +## 运行代码 { #run-the-code } 所有代码片段都可以复制后直接使用(它们实际上是经过测试的 Python 文件)。 @@ -58,7 +58,7 @@ $ fastapi dev -/// note +/// note | 注意 -当您使用 `pip install "fastapi[standard]"` 进行安装时,它会附带一些默认的可选标准依赖项。 +当您使用 `pip install "fastapi[standard]"` 安装时,它会附带一些默认的可选标准依赖项,其中包括 `fastapi-cloud-cli`,它可以让您部署到 FastAPI Cloud。 如果您不想安装这些可选依赖,可以选择安装 `pip install fastapi`。 +如果您想安装标准依赖但不包含 `fastapi-cloud-cli`,可以使用 `pip install "fastapi[standard-no-fastapi-cloud-cli]"` 安装。 + /// -## 进阶用户指南 +## 进阶用户指南 { #advanced-user-guide } 在本**教程-用户指南**之后,您可以阅读**进阶用户指南**。 diff --git a/docs/zh/docs/tutorial/metadata.md b/docs/zh/docs/tutorial/metadata.md index d29a1e6d0..6ec7e9add 100644 --- a/docs/zh/docs/tutorial/metadata.md +++ b/docs/zh/docs/tutorial/metadata.md @@ -1,28 +1,28 @@ -# 元数据和文档 URL +# 元数据和文档 URL { #metadata-and-docs-urls } 你可以在 FastAPI 应用程序中自定义多个元数据配置。 -## API 元数据 +## API 元数据 { #metadata-for-api } 你可以在设置 OpenAPI 规范和自动 API 文档 UI 中使用的以下字段: | 参数 | 类型 | 描述 | |------------|------|-------------| | `title` | `str` | API 的标题。 | -| `summary` | `str` | API 的简短摘要。 自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。. | -| `description` | `str` | API 的简短描述。可以使用Markdown。 | -| `version` | `string` | API 的版本。这是您自己的应用程序的版本,而不是 OpenAPI 的版本。例如 `2.5.0` 。 | +| `summary` | `str` | API 的简短摘要。 自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。 | +| `description` | `str` | API 的简短描述。可以使用 Markdown。 | +| `version` | `string` | API 的版本。这是您自己的应用程序的版本,而不是 OpenAPI 的版本。例如 `2.5.0`。 | | `terms_of_service` | `str` | API 服务条款的 URL。如果提供,则必须是 URL。 | -| `contact` | `dict` | 公开的 API 的联系信息。它可以包含多个字段。
contact 字段
参数Type描述
namestr联系人/组织的识别名称。
urlstr指向联系信息的 URL。必须采用 URL 格式。
emailstr联系人/组织的电子邮件地址。必须采用电子邮件地址的格式。
| -| `license_info` | `dict` | 公开的 API 的许可证信息。它可以包含多个字段。
license_info 字段
参数类型描述
namestr必须的 (如果设置了license_info). 用于 API 的许可证名称。
identifierstr一个API的SPDX许可证表达。 The identifier field is mutually exclusive of the url field. 自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。
urlstr用于 API 的许可证的 URL。必须采用 URL 格式。
| +| `contact` | `dict` | 公开的 API 的联系信息。它可以包含多个字段。
contact 字段
参数类型描述
namestr联系人/组织的识别名称。
urlstr指向联系信息的 URL。必须采用 URL 格式。
emailstr联系人/组织的电子邮件地址。必须采用电子邮件地址的格式。
| +| `license_info` | `dict` | 公开的 API 的许可证信息。它可以包含多个字段。
license_info 字段
参数类型描述
namestr必须(如果设置了 license_info)。用于 API 的许可证名称。
identifierstrAPI 的 SPDX 许可证表达式。字段 identifier 与字段 url 互斥。自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。
urlstr用于 API 的许可证的 URL。必须采用 URL 格式。
| 你可以按如下方式设置它们: -{* ../../docs_src/metadata/tutorial001.py hl[4:6] *} +{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} -/// tip +/// tip | 提示 -您可以在 `description` 字段中编写 Markdown,它将在输出中呈现。 +你可以在 `description` 字段中编写 Markdown,它会在输出中渲染。 /// @@ -30,15 +30,35 @@ -## 标签元数据 +## 许可证标识符 { #license-identifier } -### 创建标签元数据 +自 OpenAPI 3.1.0 和 FastAPI 0.99.0 起,你还可以在 `license_info` 中使用 `identifier` 而不是 `url`。 + +例如: + +{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} + +## 标签元数据 { #metadata-for-tags } + +你也可以通过参数 `openapi_tags` 为用于分组路径操作的不同标签添加额外的元数据。 + +它接收一个列表,列表中每个标签对应一个字典。 + +每个字典可以包含: + +- `name`(必填):一个 `str`,与在你的*路径操作*和 `APIRouter` 的 `tags` 参数中使用的标签名相同。 +- `description`:一个 `str`,该标签的简短描述。可以使用 Markdown,并会显示在文档 UI 中。 +- `externalDocs`:一个 `dict`,描述外部文档,包含: + - `description`:一个 `str`,该外部文档的简短描述。 + - `url`(必填):一个 `str`,该外部文档的 URL。 + +### 创建标签元数据 { #create-metadata-for-tags } 让我们在带有标签的示例中为 `users` 和 `items` 试一下。 创建标签元数据并把它传递给 `openapi_tags` 参数: -{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *} +{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} 注意你可以在描述内使用 Markdown,例如「login」会显示为粗体(**login**)以及「fancy」会显示为斜体(_fancy_)。 @@ -48,11 +68,11 @@ /// -### 使用你的标签 +### 使用你的标签 { #use-your-tags } 将 `tags` 参数和*路径操作*(以及 `APIRouter`)一起使用,将其分配给不同的标签: -{* ../../docs_src/metadata/tutorial004.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} /// info | 信息 @@ -60,19 +80,19 @@ /// -### 查看文档 +### 查看文档 { #check-the-docs } 如果你现在查看文档,它们会显示所有附加的元数据: -### 标签顺序 +### 标签顺序 { #order-of-tags } 每个标签元数据字典的顺序也定义了在文档用户界面显示的顺序。 例如按照字母顺序,即使 `users` 排在 `items` 之后,它也会显示在前面,因为我们将它的元数据添加为列表内的第一个字典。 -## OpenAPI URL +## OpenAPI URL { #openapi-url } 默认情况下,OpenAPI 模式服务于 `/openapi.json`。 @@ -80,21 +100,21 @@ 例如,将其设置为服务于 `/api/v1/openapi.json`: -{* ../../docs_src/metadata/tutorial002.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} 如果你想完全禁用 OpenAPI 模式,可以将其设置为 `openapi_url=None`,这样也会禁用使用它的文档用户界面。 -## 文档 URLs +## 文档 URLs { #docs-urls } 你可以配置两个文档用户界面,包括: -* **Swagger UI**:服务于 `/docs`。 - * 可以使用参数 `docs_url` 设置它的 URL。 - * 可以通过设置 `docs_url=None` 禁用它。 -* ReDoc:服务于 `/redoc`。 - * 可以使用参数 `redoc_url` 设置它的 URL。 - * 可以通过设置 `redoc_url=None` 禁用它。 +- **Swagger UI**:服务于 `/docs`。 + - 可以使用参数 `docs_url` 设置它的 URL。 + - 可以通过设置 `docs_url=None` 禁用它。 +- **ReDoc**:服务于 `/redoc`。 + - 可以使用参数 `redoc_url` 设置它的 URL。 + - 可以通过设置 `redoc_url=None` 禁用它。 例如,设置 Swagger UI 服务于 `/documentation` 并禁用 ReDoc: -{* ../../docs_src/metadata/tutorial003.py hl[3] *} +{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} diff --git a/docs/zh/docs/tutorial/middleware.md b/docs/zh/docs/tutorial/middleware.md index 5608c4ee1..a3f833d78 100644 --- a/docs/zh/docs/tutorial/middleware.md +++ b/docs/zh/docs/tutorial/middleware.md @@ -1,66 +1,95 @@ -# 中间件 +# 中间件 { #middleware } -你可以向 **FastAPI** 应用添加中间件. +你可以向 **FastAPI** 应用添加中间件。 -"中间件"是一个函数,它在每个**请求**被特定的*路径操作*处理之前,以及在每个**响应**返回之前工作. +“中间件”是一个函数,它会在每个特定的*路径操作*处理每个**请求**之前运行,也会在返回每个**响应**之前运行。 -* 它接收你的应用程序的每一个**请求**. -* 然后它可以对这个**请求**做一些事情或者执行任何需要的代码. -* 然后它将**请求**传递给应用程序的其他部分 (通过某种*路径操作*). -* 然后它获取应用程序生产的**响应** (通过某种*路径操作*). -* 它可以对该**响应**做些什么或者执行任何需要的代码. -* 然后它返回这个 **响应**. +* 它接收你的应用的每一个**请求**。 +* 然后它可以对这个**请求**做一些事情或者执行任何需要的代码。 +* 然后它将这个**请求**传递给应用程序的其他部分(某个*路径操作*)处理。 +* 之后它获取应用程序生成的**响应**(由某个*路径操作*产生)。 +* 它可以对该**响应**做一些事情或者执行任何需要的代码。 +* 然后它返回这个**响应**。 /// note | 技术细节 -如果你使用了 `yield` 关键字依赖, 依赖中的退出代码将在执行中间件*后*执行. +如果你有使用 `yield` 的依赖,依赖中的退出代码会在中间件之后运行。 -如果有任何后台任务(稍后记录), 它们将在执行中间件*后*运行. +如果有任何后台任务(会在[后台任务](background-tasks.md){.internal-link target=_blank}一节中介绍,你稍后会看到),它们会在所有中间件之后运行。 /// -## 创建中间件 +## 创建中间件 { #create-a-middleware } -要创建中间件你可以在函数的顶部使用装饰器 `@app.middleware("http")`. +要创建中间件,你可以在函数的顶部使用装饰器 `@app.middleware("http")`。 -中间件参数接收如下参数: +中间件函数会接收: -* `request`. -* 一个函数 `call_next` 它将接收 `request` 作为参数. - * 这个函数将 `request` 传递给相应的 *路径操作*. - * 然后它将返回由相应的*路径操作*生成的 `response`. -* 然后你可以在返回 `response` 前进一步修改它. +* `request`。 +* 一个函数 `call_next`,它会把 `request` 作为参数接收。 + * 这个函数会把 `request` 传递给相应的*路径操作*。 + * 然后它返回由相应*路径操作*生成的 `response`。 +* 在返回之前,你可以进一步修改 `response`。 -{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *} +{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *} /// tip -请记住可以 用'X-' 前缀添加专有自定义请求头. +请记住可以使用 `X-` 前缀添加专有自定义请求头。 -但是如果你想让浏览器中的客户端看到你的自定义请求头, 你需要把它们加到 CORS 配置 ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) 的 `expose_headers` 参数中,在 Starlette's CORS docs文档中. +但是如果你有希望让浏览器中的客户端可见的自定义请求头,你需要把它们加到你的 CORS 配置([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank})的 `expose_headers` 参数中,参见 Starlette 的 CORS 文档。 /// /// note | 技术细节 -你也可以使用 `from starlette.requests import Request`. +你也可以使用 `from starlette.requests import Request`。 -**FastAPI** 为了开发者方便提供了该对象. 但其实它直接来自于 Starlette. +**FastAPI** 为了开发者方便提供了该对象,但它直接来自 Starlette。 /// -### 在 `response` 的前和后 +### 在 `response` 之前与之后 { #before-and-after-the-response } -在任何*路径操作*收到`request`前,可以添加要和请求一起运行的代码. +你可以在任何*路径操作*接收 `request` 之前,添加要与该 `request` 一起运行的代码。 -也可以在*响应*生成但是返回之前添加代码. +也可以在生成 `response` 之后、返回之前添加代码。 -例如你可以添加自定义请求头 `X-Process-Time` 包含以秒为单位的接收请求和生成响应的时间: +例如,你可以添加一个自定义请求头 `X-Process-Time`,其值为处理请求并生成响应所花费的秒数: -{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} +{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *} -## 其他中间件 +/// tip -你可以稍后在 [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}阅读更多关于中间件的教程. +这里我们使用 `time.perf_counter()` 而不是 `time.time()`,因为在这类场景中它可能更精确。🤓 -你将在下一节中学习如何使用中间件处理 CORS . +/// + +## 多个中间件的执行顺序 { #multiple-middleware-execution-order } + +当你使用 `@app.middleware()` 装饰器或 `app.add_middleware()` 方法添加多个中间件时,每个新中间件都会包裹应用,形成一个栈。最后添加的中间件是“最外层”的,最先添加的是“最内层”的。 + +在请求路径上,最外层的中间件先运行。 + +在响应路径上,它最后运行。 + +例如: + +```Python +app.add_middleware(MiddlewareA) +app.add_middleware(MiddlewareB) +``` + +这会产生如下执行顺序: + +* 请求:MiddlewareB → MiddlewareA → 路由 + +* 响应:路由 → MiddlewareA → MiddlewareB + +这种栈式行为确保中间件按可预测且可控的顺序执行。 + +## 其他中间件 { #other-middlewares } + +你可以稍后在[高级用户指南:高级中间件](../advanced/middleware.md){.internal-link target=_blank}中阅读更多关于其他中间件的内容。 + +你将在下一节中了解如何使用中间件处理 CORS。 diff --git a/docs/zh/docs/tutorial/path-operation-configuration.md b/docs/zh/docs/tutorial/path-operation-configuration.md index adeca2d3f..49b7baabc 100644 --- a/docs/zh/docs/tutorial/path-operation-configuration.md +++ b/docs/zh/docs/tutorial/path-operation-configuration.md @@ -1,66 +1,76 @@ -# 路径操作配置 +# 路径操作配置 { #path-operation-configuration } *路径操作装饰器*支持多种配置参数。 /// warning | 警告 -注意:以下参数应直接传递给**路径操作装饰器**,不能传递给*路径操作函数*。 +注意:以下参数应直接传递给*路径操作装饰器*,不能传递给*路径操作函数*。 /// -## `status_code` 状态码 +## 响应状态码 { #response-status-code } -`status_code` 用于定义*路径操作*响应中的 HTTP 状态码。 +可以在*路径操作*的响应中定义(HTTP)`status_code`。 -可以直接传递 `int` 代码, 比如 `404`。 +可以直接传递 `int` 代码,比如 `404`。 -如果记不住数字码的涵义,也可以用 `status` 的快捷常量: +如果记不住数字码的含义,也可以用 `status` 的快捷常量: -{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *} +{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *} -状态码在响应中使用,并会被添加到 OpenAPI 概图。 +该状态码会用于响应中,并会被添加到 OpenAPI 概图。 /// note | 技术细节 也可以使用 `from starlette import status` 导入状态码。 -**FastAPI** 的`fastapi.status` 和 `starlette.status` 一样,只是快捷方式。实际上,`fastapi.status` 直接继承自 Starlette。 +**FastAPI** 提供的 `fastapi.status` 与 `starlette.status` 相同,方便你作为开发者使用。实际上它直接来自 Starlette。 /// -## `tags` 参数 +## 标签 { #tags } -`tags` 参数的值是由 `str` 组成的 `list` (一般只有一个 `str` ),`tags` 用于为*路径操作*添加标签: +可以通过传入由 `str` 组成的 `list`(通常只有一个 `str`)的参数 `tags`,为*路径操作*添加标签: -{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *} +{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *} OpenAPI 概图会自动添加标签,供 API 文档接口使用: -## `summary` 和 `description` 参数 +### 使用 Enum 的标签 { #tags-with-enums } -路径装饰器还支持 `summary` 和 `description` 这两个参数: +如果你的应用很大,可能会积累出很多标签,你会希望确保相关的*路径操作*始终使用相同的标签。 -{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *} +这种情况下,把标签存放在 `Enum` 中会更合适。 -## 文档字符串(`docstring`) +**FastAPI** 对此的支持与使用普通字符串相同: -描述内容比较长且占用多行时,可以在函数的 docstring 中声明*路径操作*的描述,**FastAPI** 支持从文档字符串中读取描述内容。 +{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *} + +## 摘要和描述 { #summary-and-description } + +可以添加 `summary` 和 `description`: + +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} + +## 从 docstring 获取描述 { #description-from-docstring } + +描述内容比较长且占用多行时,可以在函数的 docstring 中声明*路径操作*的描述,**FastAPI** 会从中读取。 文档字符串支持 Markdown,能正确解析和显示 Markdown 的内容,但要注意文档字符串的缩进。 -{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *} +{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *} 下图为 Markdown 文本在 API 文档中的显示效果: -## 响应描述 +## 响应描述 { #response-description } `response_description` 参数用于定义响应的描述说明: -{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *} +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} /// info | 说明 @@ -78,11 +88,11 @@ OpenAPI 规定每个*路径操作*都要有响应描述。 -## 弃用*路径操作* +## 弃用*路径操作* { #deprecate-a-path-operation } `deprecated` 参数可以把*路径操作*标记为弃用,无需直接删除: -{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} API 文档会把该路径操作标记为弃用: @@ -92,6 +102,6 @@ API 文档会把该路径操作标记为弃用: -## 小结 +## 小结 { #recap } -通过传递参数给*路径操作装饰器* ,即可轻松地配置*路径操作*、添加元数据。 +通过传递参数给*路径操作装饰器*,即可轻松地配置*路径操作*、添加元数据。 diff --git a/docs/zh/docs/tutorial/path-params-numeric-validations.md b/docs/zh/docs/tutorial/path-params-numeric-validations.md index ff6242835..ff8b1762c 100644 --- a/docs/zh/docs/tutorial/path-params-numeric-validations.md +++ b/docs/zh/docs/tutorial/path-params-numeric-validations.md @@ -1,91 +1,128 @@ -# 路径参数和数值校验 +# 路径参数和数值校验 { #path-parameters-and-numeric-validations } 与使用 `Query` 为查询参数声明更多的校验和元数据的方式相同,你也可以使用 `Path` 为路径参数声明相同类型的校验和元数据。 -## 导入 Path +## 导入 `Path` { #import-path } -首先,从 `fastapi` 导入 `Path`: +首先,从 `fastapi` 导入 `Path`,并导入 `Annotated`: {* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *} -## 声明元数据 +/// info | 信息 -你可以声明与 `Query` 相同的所有参数。 +FastAPI 在 0.95.0 版本添加了对 `Annotated` 的支持(并开始推荐使用它)。 -例如,要声明路径参数 `item_id`的 `title` 元数据值,你可以输入: +如果你使用的是更旧的版本,尝试使用 `Annotated` 会报错。 -{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *} - -/// note - -路径参数总是必需的,因为它必须是路径的一部分。 - -所以,你应该在声明时使用 `...` 将其标记为必需参数。 - -然而,即使你使用 `None` 声明路径参数或设置一个其他默认值也不会有任何影响,它依然会是必需参数。 +请确保在使用 `Annotated` 之前,将 FastAPI 版本[升级](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}到至少 0.95.1。 /// -## 按需对参数排序 +## 声明元数据 { #declare-metadata } -假设你想要声明一个必需的 `str` 类型查询参数 `q`。 +你可以声明与 `Query` 相同的所有参数。 -而且你不需要为该参数声明任何其他内容,所以实际上你并不需要使用 `Query`。 +例如,要为路径参数 `item_id` 声明 `title` 元数据值,你可以这样写: -但是你仍然需要使用 `Path` 来声明路径参数 `item_id`。 +{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *} -如果你将带有「默认值」的参数放在没有「默认值」的参数之前,Python 将会报错。 +/// note | 注意 -但是你可以对其重新排序,并将不带默认值的值(查询参数 `q`)放到最前面。 +路径参数总是必需的,因为它必须是路径的一部分。即使你将其声明为 `None` 或设置了默认值,也不会产生任何影响,它依然始终是必需参数。 -对 **FastAPI** 来说这无关紧要。它将通过参数的名称、类型和默认值声明(`Query`、`Path` 等)来检测参数,而不在乎参数的顺序。 +/// + +## 按需对参数排序 { #order-the-parameters-as-you-need } + +/// tip | 提示 + +如果你使用 `Annotated`,这点可能不那么重要或必要。 + +/// + +假设你想要将查询参数 `q` 声明为必需的 `str`。 + +并且你不需要为该参数声明其他内容,所以实际上不需要用到 `Query`。 + +但是你仍然需要为路径参数 `item_id` 使用 `Path`。并且出于某些原因你不想使用 `Annotated`。 + +如果你将带有“默认值”的参数放在没有“默认值”的参数之前,Python 会报错。 + +不过你可以重新排序,让没有默认值的参数(查询参数 `q`)放在最前面。 + +对 **FastAPI** 来说这无关紧要。它会通过参数的名称、类型和默认值声明(`Query`、`Path` 等)来检测参数,而不关心顺序。 因此,你可以将函数声明为: -{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} -## 按需对参数排序的技巧 +但请记住,如果你使用 `Annotated`,你就不会遇到这个问题,因为你没有使用 `Query()` 或 `Path()` 作为函数参数的默认值。 -如果你想不使用 `Query` 声明没有默认值的查询参数 `q`,同时使用 `Path` 声明路径参数 `item_id`,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。 +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} -传递 `*` 作为函数的第一个参数。 +## 按需对参数排序的技巧 { #order-the-parameters-as-you-need-tricks } -Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 kwargs,来调用。即使它们没有默认值。 +/// tip | 提示 -{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} +如果你使用 `Annotated`,这点可能不那么重要或必要。 -## 数值校验:大于等于 +/// -使用 `Query` 和 `Path`(以及你将在后面看到的其他类)可以声明字符串约束,但也可以声明数值约束。 +这里有一个小技巧,可能会很方便,但你并不会经常需要它。 -像下面这样,添加 `ge=1` 后,`item_id` 将必须是一个大于(`g`reater than)或等于(`e`qual)`1` 的整数。 +如果你想要: -{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *} +* 在没有 `Query` 且没有任何默认值的情况下声明查询参数 `q` +* 使用 `Path` 声明路径参数 `item_id` +* 让它们的顺序与上面不同 +* 不使用 `Annotated` -## 数值校验:大于和小于等于 +...Python 为此有一个小的特殊语法。 -同样的规则适用于: +在函数的第一个参数位置传入 `*`。 + +Python 不会对这个 `*` 做任何事,但它会知道之后的所有参数都应该作为关键字参数(键值对)来调用,也被称为 kwargs。即使它们没有默认值。 + +{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} + +### 使用 `Annotated` 更好 { #better-with-annotated } + +请记住,如果你使用 `Annotated`,因为你没有使用函数参数的默认值,所以你不会有这个问题,你大概率也不需要使用 `*`。 + +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} + +## 数值校验:大于等于 { #number-validations-greater-than-or-equal } + +使用 `Query` 和 `Path`(以及你稍后会看到的其他类)你可以声明数值约束。 + +在这里,使用 `ge=1` 后,`item_id` 必须是一个整数,值要「`g`reater than or `e`qual」1。 + +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} + +## 数值校验:大于和小于等于 { #number-validations-greater-than-and-less-than-or-equal } + +同样适用于: * `gt`:大于(`g`reater `t`han) * `le`:小于等于(`l`ess than or `e`qual) -{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} -## 数值校验:浮点数、大于和小于 +## 数值校验:浮点数、大于和小于 { #number-validations-floats-greater-than-and-less-than } 数值校验同样适用于 `float` 值。 -能够声明 gt 而不仅仅是 ge 在这个前提下变得重要起来。例如,你可以要求一个值必须大于 `0`,即使它小于 `1`。 +能够声明 gt 而不仅仅是 ge 在这里变得很重要。例如,你可以要求一个值必须大于 `0`,即使它小于 `1`。 -因此,`0.5` 将是有效值。但是 `0.0`或 `0` 不是。 +因此,`0.5` 将是有效值。但是 `0.0` 或 `0` 不是。 -对于 lt 也是一样的。 +对于 lt 也是一样的。 -{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} -## 总结 +## 总结 { #recap } -你能够以与 [查询参数和字符串校验](query-params-str-validations.md){.internal-link target=_blank} 相同的方式使用 `Query`、`Path`(以及其他你还没见过的类)声明元数据和字符串校验。 +你能够以与[查询参数和字符串校验](query-params-str-validations.md){.internal-link target=_blank}相同的方式使用 `Query`、`Path`(以及其他你还没见过的类)声明元数据和字符串校验。 而且你还可以声明数值校验: @@ -94,24 +131,24 @@ Python 不会对该 `*` 做任何事情,但是它将知道之后的所有参 * `lt`:小于(`l`ess `t`han) * `le`:小于等于(`l`ess than or `e`qual) -/// info +/// info | 信息 -`Query`、`Path` 以及你后面会看到的其他类继承自一个共同的 `Param` 类(不需要直接使用它)。 +`Query`、`Path` 以及你后面会看到的其他类,都是一个通用 `Param` 类的子类。 -而且它们都共享相同的所有你已看到并用于添加额外校验和元数据的参数。 +它们都共享相同的参数,用于你已看到的额外校验和元数据。 /// /// note | 技术细节 -当你从 `fastapi` 导入 `Query`、`Path` 和其他同类对象时,它们实际上是函数。 +当你从 `fastapi` 导入 `Query`、`Path` 和其他对象时,它们实际上是函数。 -当被调用时,它们返回同名类的实例。 +当被调用时,它们会返回同名类的实例。 -如此,你导入 `Query` 这个函数。当你调用它时,它将返回一个同样命名为 `Query` 的类的实例。 +也就是说,你导入的是函数 `Query`。当你调用它时,它会返回一个同名的 `Query` 类的实例。 -因为使用了这些函数(而不是直接使用类),所以你的编辑器不会标记有关其类型的错误。 +之所以使用这些函数(而不是直接使用类),是为了让你的编辑器不要因为它们的类型而标记错误。 -这样,你可以使用常规的编辑器和编码工具,而不必添加自定义配置来忽略这些错误。 +这样你就可以使用常规的编辑器和编码工具,而不必添加自定义配置来忽略这些错误。 /// diff --git a/docs/zh/docs/tutorial/path-params.md b/docs/zh/docs/tutorial/path-params.md index ac9df0831..fa4c9514a 100644 --- a/docs/zh/docs/tutorial/path-params.md +++ b/docs/zh/docs/tutorial/path-params.md @@ -1,10 +1,10 @@ -# 路径参数 +# 路径参数 { #path-parameters } -FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(**变量**): +你可以使用与 Python 字符串格式化相同的语法声明路径“参数”或“变量”: -{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} -这段代码把路径参数 `item_id` 的值传递给路径函数的参数 `item_id`。 +路径参数 `item_id` 的值会作为参数 `item_id` 传递给你的函数。 运行示例并访问 http://127.0.0.1:8000/items/foo,可获得如下响应: @@ -12,11 +12,11 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(** {"item_id":"foo"} ``` -## 声明路径参数的类型 +## 声明路径参数的类型 { #path-parameters-with-types } -使用 Python 标准类型注解,声明路径操作函数中路径参数的类型。 +使用 Python 标准类型注解,声明路径操作函数中路径参数的类型: -{* ../../docs_src/path_params/tutorial002.py hl[7] *} +{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *} 本例把 `item_id` 的类型声明为 `int`。 @@ -26,7 +26,7 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(** /// -## 数据转换 +## 数据转换 { #data-conversion } 运行示例并访问 http://127.0.0.1:8000/items/3,返回的响应如下: @@ -42,56 +42,57 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(** /// -## 数据校验 +## 数据校验 { #data-validation } 通过浏览器访问 http://127.0.0.1:8000/items/foo,接收如下 HTTP 错误信息: ```JSON { - "detail": [ - { - "loc": [ - "path", - "item_id" - ], - "msg": "value is not a valid integer", - "type": "type_error.integer" - } - ] + "detail": [ + { + "type": "int_parsing", + "loc": [ + "path", + "item_id" + ], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "foo" + } + ] } ``` -这是因为路径参数 `item_id` 的值 (`"foo"`)的类型不是 `int`。 +这是因为路径参数 `item_id` 的值(`"foo"`)的类型不是 `int`。 -值的类型不是 `int ` 而是浮点数(`float`)时也会显示同样的错误,比如: http://127.0.0.1:8000/items/4.2。 +值的类型不是 `int` 而是浮点数(`float`)时也会显示同样的错误,比如: http://127.0.0.1:8000/items/4.2 /// check | 检查 -**FastAPI** 使用 Python 类型声明实现了数据校验。 +**FastAPI** 使用同样的 Python 类型声明实现了数据校验。 -注意,上面的错误清晰地指出了未通过校验的具体原因。 +注意,上面的错误清晰地指出了未通过校验的具体位置。 这在开发调试与 API 交互的代码时非常有用。 /// -## 查看文档 +## 文档 { #documentation } -访问 http://127.0.0.1:8000/docs,查看自动生成的 API 文档: +访问 http://127.0.0.1:8000/docs,查看自动生成的交互式 API 文档: /// check | 检查 -还是使用 Python 类型声明,**FastAPI** 提供了(集成 Swagger UI 的)API 文档。 +还是使用 Python 类型声明,**FastAPI** 提供了(集成 Swagger UI 的)自动交互式文档。 注意,路径参数的类型是整数。 /// -## 基于标准的好处,备选文档 +## 基于标准的好处,备选文档 { #standards-based-benefits-alternative-documentation } -**FastAPI** 使用 OpenAPI 生成概图,所以能兼容很多工具。 +**FastAPI** 使用 OpenAPI 生成概图,所以能兼容很多工具。 因此,**FastAPI** 还内置了 ReDoc 生成的备选 API 文档,可在此查看 http://127.0.0.1:8000/redoc: @@ -99,15 +100,15 @@ FastAPI 支持使用 Python 字符串格式化语法声明**路径参数**(** 同样,还有很多兼容工具,包括多种语言的代码生成工具。 -## Pydantic +## Pydantic { #pydantic } FastAPI 充分地利用了 Pydantic 的优势,用它在后台校验数据。众所周知,Pydantic 擅长的就是数据校验。 同样,`str`、`float`、`bool` 以及很多复合数据类型都可以使用类型声明。 -下一章介绍详细内容。 +接下来的章节会介绍其中的好几种。 -## 顺序很重要 +## 顺序很重要 { #order-matters } 有时,*路径操作*中的路径是写死的。 @@ -117,15 +118,21 @@ FastAPI 充分地利用了 `Enum` 类型接收预设的*路径参数*。 +{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} -### 创建 `Enum` 类 +由于路径首先匹配,始终会使用第一个定义的。 + +## 预设值 { #predefined-values } + +路径操作使用 Python 的 `Enum` 类型接收预设的路径参数。 + +### 创建 `Enum` 类 { #create-an-enum-class } 导入 `Enum` 并创建继承自 `str` 和 `Enum` 的子类。 @@ -133,47 +140,41 @@ FastAPI 充分地利用了 枚举(即 enums)。 - -/// +{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} /// tip | 提示 -**AlexNet**、**ResNet**、**LeNet** 是机器学习模型。 +**AlexNet**、**ResNet**、**LeNet** 是机器学习模型的名字。 /// -### 声明*路径参数* +### 声明路径参数 { #declare-a-path-parameter } -使用 Enum 类(`ModelName`)创建使用类型注解的*路径参数*: +使用 Enum 类(`ModelName`)创建使用类型注解的路径参数: -{* ../../docs_src/path_params/tutorial005.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} -### 查看文档 +### 查看文档 { #check-the-docs } - API 文档会显示预定义*路径参数*的可用值: +API 文档会显示预定义路径参数的可用值: -### 使用 Python _枚举类型_ +### 使用 Python 枚举 { #working-with-python-enumerations } -*路径参数*的值是枚举的元素。 +路径参数的值是一个枚举成员。 -#### 比较*枚举元素* +#### 比较枚举成员 { #compare-enumeration-members } -枚举类 `ModelName` 中的*枚举元素*支持比较操作: +可以将其与枚举类 `ModelName` 中的枚举成员进行比较: -{* ../../docs_src/path_params/tutorial005.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} -#### 获取*枚举值* +#### 获取枚举值 { #get-the-enumeration-value } -使用 `model_name.value` 或 `your_enum_member.value` 获取实际的值(本例中为**字符串**): +使用 `model_name.value` 或通用的 `your_enum_member.value` 获取实际的值(本例中为 `str`): -{* ../../docs_src/path_params/tutorial005.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} /// tip | 提示 @@ -181,13 +182,13 @@ Python 3.4 及之后版本支持解析** +- 数据 "解析" - 数据校验 -- API 注解和 API 文档 +- API 注解和自动文档 只需要声明一次即可。 diff --git a/docs/zh/docs/tutorial/query-param-models.md b/docs/zh/docs/tutorial/query-param-models.md index c6a79a71a..fc691839d 100644 --- a/docs/zh/docs/tutorial/query-param-models.md +++ b/docs/zh/docs/tutorial/query-param-models.md @@ -1,16 +1,16 @@ -# 查询参数模型 +# 查询参数模型 { #query-parameter-models } 如果你有一组具有相关性的**查询参数**,你可以创建一个 **Pydantic 模型**来声明它们。 这将允许你在**多个地方**去**复用模型**,并且一次性为所有参数声明验证和元数据。😎 -/// note +/// note | 注意 FastAPI 从 `0.115.0` 版本开始支持这个特性。🤓 /// -## 使用 Pydantic 模型的查询参数 +## 使用 Pydantic 模型的查询参数 { #query-parameters-with-a-pydantic-model } 在一个 **Pydantic 模型**中声明你需要的**查询参数**,然后将参数声明为 `Query`: @@ -18,7 +18,7 @@ FastAPI 从 `0.115.0` 版本开始支持这个特性。🤓 **FastAPI** 将会从请求的**查询参数**中**提取**出**每个字段**的数据,并将其提供给你定义的 Pydantic 模型。 -## 查看文档 +## 查看文档 { #check-the-docs } 你可以在 `/docs` 页面的 UI 中查看查询参数: @@ -26,11 +26,11 @@ FastAPI 从 `0.115.0` 版本开始支持这个特性。🤓 -## 禁止额外的查询参数 +## 禁止额外的查询参数 { #forbid-extra-query-parameters } 在一些特殊的使用场景中(可能不是很常见),你可能希望**限制**你要接收的查询参数。 -你可以使用 Pydantic 的模型配置来 `forbid`(意为禁止 —— 译者注)任何 `extra`(意为额外的 —— 译者注)字段: +你可以使用 Pydantic 的模型配置来 `forbid` 任何 `extra` 字段: {* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} @@ -57,11 +57,11 @@ https://example.com/items/?limit=10&tool=plumbus } ``` -## 总结 +## 总结 { #summary } 你可以使用 **Pydantic 模型**在 **FastAPI** 中声明**查询参数**。😎 -/// tip +/// tip | 提示 剧透警告:你也可以使用 Pydantic 模型来声明 cookie 和 headers,但你将在本教程的后面部分阅读到这部分内容。🤫 diff --git a/docs/zh/docs/tutorial/query-params-str-validations.md b/docs/zh/docs/tutorial/query-params-str-validations.md index c2f9a7e9f..62552e6d0 100644 --- a/docs/zh/docs/tutorial/query-params-str-validations.md +++ b/docs/zh/docs/tutorial/query-params-str-validations.md @@ -1,142 +1,271 @@ -# 查询参数和字符串校验 +# 查询参数和字符串校验 { #query-parameters-and-string-validations } **FastAPI** 允许你为参数声明额外的信息和校验。 -让我们以下面的应用程序为例: +让我们以下面的应用为例: {* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} -查询参数 `q` 的类型为 `str`,默认值为 `None`,因此它是可选的。 +查询参数 `q` 的类型为 `str | None`,这意味着它是 `str` 类型,但也可以是 `None`。其默认值确实为 `None`,所以 FastAPI 会知道它不是必填的。 -## 额外的校验 +/// note | 注意 -我们打算添加约束条件:即使 `q` 是可选的,但只要提供了该参数,则该参数值**不能超过50个字符的长度**。 +FastAPI 会因为默认值 `= None` 而知道 `q` 的值不是必填的。 -### 导入 `Query` - -为此,首先从 `fastapi` 导入 `Query`: - -{* ../../docs_src/query_params_str_validations/tutorial002.py hl[1] *} - -## 使用 `Query` 作为默认值 - -现在,将 `Query` 用作查询参数的默认值,并将它的 `max_length` 参数设置为 50: - -{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *} - -由于我们必须用 `Query(default=None)` 替换默认值 `None`,`Query` 的第一个参数同样也是用于定义默认值。 - -所以: - -```Python -q: Union[str, None] = Query(default=None) -``` - -...使得参数可选,等同于: - -```Python -q: str = None -``` - -但是 `Query` 显式地将其声明为查询参数。 - -然后,我们可以将更多的参数传递给 `Query`。在本例中,适用于字符串的 `max_length` 参数: - -```Python -q: Union[str, None] = Query(default=None, max_length=50) -``` - -将会校验数据,在数据无效时展示清晰的错误信息,并在 OpenAPI 模式的*路径操作*中记录该参​​数。 - -## 添加更多校验 - -你还可以添加 `min_length` 参数: - -{* ../../docs_src/query_params_str_validations/tutorial003.py hl[10] *} - -## 添加正则表达式 - -你可以定义一个参数值必须匹配的正则表达式: - -{* ../../docs_src/query_params_str_validations/tutorial004.py hl[11] *} - -这个指定的正则表达式通过以下规则检查接收到的参数值: - -* `^`:以该符号之后的字符开头,符号之前没有字符。 -* `fixedquery`: 值精确地等于 `fixedquery`。 -* `$`: 到此结束,在 `fixedquery` 之后没有更多字符。 - -如果你对所有的这些**「正则表达式」**概念感到迷茫,请不要担心。对于许多人来说这都是一个困难的主题。你仍然可以在无需正则表达式的情况下做很多事情。 - -但是,一旦你需要用到并去学习它们时,请了解你已经可以在 **FastAPI** 中直接使用它们。 - -## 默认值 - -你可以向 `Query` 的第一个参数传入 `None` 用作查询参数的默认值,以同样的方式你也可以传递其他默认值。 - -假设你想要声明查询参数 `q`,使其 `min_length` 为 `3`,并且默认值为 `fixedquery`: - -{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *} - -/// note - -具有默认值还会使该参数成为可选参数。 +将类型标注为 `str | None` 能让你的编辑器提供更好的辅助和错误检测。 /// -## 声明为必需参数 +## 额外校验 { #additional-validation } -当我们不需要声明额外的校验或元数据时,只需不声明默认值就可以使 `q` 参数成为必需参数,例如: +我们打算添加约束:即使 `q` 是可选的,但只要提供了该参数,**其长度不能超过 50 个字符**。 + +### 导入 `Query` 和 `Annotated` { #import-query-and-annotated } + +为此,先导入: + +- 从 `fastapi` 导入 `Query` +- 从 `typing` 导入 `Annotated` + +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *} + +/// info | 信息 + +FastAPI 在 0.95.0 版本中添加了对 `Annotated` 的支持(并开始推荐使用)。 + +如果你的版本更旧,使用 `Annotated` 会报错。 + +在使用 `Annotated` 之前,请确保先[升级 FastAPI 版本](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}到至少 0.95.1。 + +/// + +## 在 `q` 参数的类型中使用 `Annotated` { #use-annotated-in-the-type-for-the-q-parameter } + +还记得我之前在[Python 类型简介](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}中说过可以用 `Annotated` 给参数添加元数据吗? + +现在正是与 FastAPI 搭配使用它的时候。🚀 + +我们之前的类型标注是: + +//// tab | Python 3.10+ ```Python -q: str +q: str | None = None ``` -代替: +//// + +//// tab | Python 3.9+ ```Python q: Union[str, None] = None ``` -但是现在我们正在用 `Query` 声明它,例如: +//// + +我们要做的是用 `Annotated` 把它包起来,变成: + +//// tab | Python 3.10+ ```Python -q: Union[str, None] = Query(default=None, min_length=3) +q: Annotated[str | None] = None ``` -因此,当你在使用 `Query` 且需要声明一个值是必需的时,只需不声明默认参数: +//// -{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} +//// tab | Python 3.9+ -### 使用`None`声明必需参数 +```Python +q: Annotated[Union[str, None]] = None +``` -你可以声明一个参数可以接收`None`值,但它仍然是必需的。这将强制客户端发送一个值,即使该值是`None`。 +//// -为此,你可以声明`None`是一个有效的类型,并仍然使用`default=...`: +这两种写法含义相同,`q` 是一个可以是 `str` 或 `None` 的参数,默认是 `None`。 -{* ../../docs_src/query_params_str_validations/tutorial006c.py hl[9] *} +现在进入更有趣的部分。🎉 -/// tip +## 在 `q` 的 `Annotated` 中添加 `Query` { #add-query-to-annotated-in-the-q-parameter } -Pydantic 是 FastAPI 中所有数据验证和序列化的核心,当你在没有设默认值的情况下使用 `Optional` 或 `Union[Something, None]` 时,它具有特殊行为,你可以在 Pydantic 文档中阅读有关必需可选字段的更多信息。 +有了 `Annotated` 之后,我们就可以放入更多信息(本例中是额外的校验)。在 `Annotated` 中添加 `Query`,并把参数 `max_length` 设为 `50`: + +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *} + +注意默认值依然是 `None`,所以该参数仍是可选的。 + +但现在把 `Query(max_length=50)` 放到 `Annotated` 里,我们就在告诉 FastAPI,这个值需要**额外校验**,最大长度为 50 个字符。😎 + +/// tip | 提示 + +这里用的是 `Query()`,因为这是一个**查询参数**。稍后我们还会看到 `Path()`、`Body()`、`Header()` 和 `Cookie()`,它们也接受与 `Query()` 相同的参数。 /// -## 查询参数列表 / 多个值 +FastAPI 现在会: -当你使用 `Query` 显式地定义查询参数时,你还可以声明它去接收一组值,或换句话来说,接收多个值。 +- 对数据进行**校验**,确保最大长度为 50 个字符 +- 当数据无效时向客户端展示**清晰的错误** +- 在 OpenAPI 模式的*路径操作*中**记录**该参数(因此会出现在**自动文档 UI** 中) + +## 另一种(旧的)方式:把 `Query` 作为默认值 { #alternative-old-query-as-the-default-value } + +早期版本的 FastAPI(0.95.0 之前)要求你把 `Query` 作为参数的默认值,而不是放在 `Annotated` 里。你很可能会在别处看到这种写法,所以我也给你解释一下。 + +/// tip | 提示 + +对于新代码以及在可能的情况下,请按上文所述使用 `Annotated`。它有多项优势(如下所述),没有劣势。🍰 + +/// + +像这样把 `Query()` 作为函数参数的默认值,并把参数 `max_length` 设为 50: + +{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *} + +由于这种情况下(不使用 `Annotated`)我们必须把函数中的默认值 `None` 替换为 `Query()`,因此需要通过参数 `Query(default=None)` 来设置默认值,它起到同样的作用(至少对 FastAPI 来说)。 + +所以: + +```Python +q: str | None = Query(default=None) +``` + +……会让参数变成可选,默认值为 `None`,等同于: + +```Python +q: str | None = None +``` + +但使用 `Query` 的版本会显式把它声明为一个查询参数。 + +然后,我们可以向 `Query` 传入更多参数。本例中是适用于字符串的 `max_length` 参数: + +```Python +q: str | None = Query(default=None, max_length=50) +``` + +这会校验数据、在数据无效时展示清晰的错误,并在 OpenAPI 模式的*路径操作*中记录该参数。 + +### 在默认值中使用 `Query` 或在 `Annotated` 中使用 `Query` { #query-as-the-default-value-or-in-annotated } + +注意,当你在 `Annotated` 中使用 `Query` 时,不能再给 `Query` 传 `default` 参数。 + +相反,应使用函数参数本身的实际默认值。否则会不一致。 + +例如,下面这样是不允许的: + +```Python +q: Annotated[str, Query(default="rick")] = "morty" +``` + +……因为不清楚默认值应该是 `"rick"` 还是 `"morty"`。 + +因此,你应该这样用(推荐): + +```Python +q: Annotated[str, Query()] = "rick" +``` + +……或者在旧代码库中你会见到: + +```Python +q: str = Query(default="rick") +``` + +### `Annotated` 的优势 { #advantages-of-annotated } + +**推荐使用 `Annotated`**,而不是把 `Query` 放在函数参数的默认值里,这样做在多方面都**更好**。🤓 + +函数参数的**默认值**就是**真正的默认值**,这与 Python 的直觉更一致。😌 + +你可以在**其他地方**不通过 FastAPI **直接调用**这个函数,而且它会**按预期工作**。如果有**必填**参数(没有默认值),你的**编辑器**会报错提示;如果在运行时没有传入必填参数,**Python** 也会报错。 + +当你不使用 `Annotated` 而是使用**(旧的)默认值风格**时,如果你在**其他地方**不通过 FastAPI 调用该函数,你必须**记得**给函数传参,否则得到的值会和预期不同(例如得到 `QueryInfo` 之类的对象而不是 `str`)。而你的编辑器不会报错,Python 也不会在调用时报错,只有在函数内部的操作出错时才会暴露问题。 + +由于 `Annotated` 可以包含多个元数据标注,你甚至可以用同一个函数与其他工具配合,例如 Typer。🚀 + +## 添加更多校验 { #add-more-validations } + +你还可以添加 `min_length` 参数: + +{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *} + +## 添加正则表达式 { #add-regular-expressions } + +你可以定义一个参数必须匹配的 正则表达式 `pattern`: + +{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} + +这个特定的正则表达式通过以下规则检查接收到的参数值: + +- `^`:必须以接下来的字符开头,前面没有其他字符。 +- `fixedquery`:值必须精确等于 `fixedquery`。 +- `$`:到此结束,在 `fixedquery` 之后没有更多字符。 + +如果你对这些**「正则表达式」**概念感到迷茫,不必担心。对很多人来说这都是个难点。你仍然可以在不使用正则表达式的情况下做很多事情。 + +现在你知道了,一旦需要时,你可以在 **FastAPI** 中直接使用它们。 + +## 默认值 { #default-values } + +当然,你也可以使用 `None` 以外的默认值。 + +假设你想要声明查询参数 `q` 的 `min_length` 为 `3`,并且默认值为 `"fixedquery"`: + +{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} + +/// note | 注意 + +任何类型的默认值(包括 `None`)都会让该参数变为可选(非必填)。 + +/// + +## 必填参数 { #required-parameters } + +当我们不需要声明更多校验或元数据时,只需不声明默认值就可以让查询参数 `q` 成为必填参数,例如: + +```Python +q: str +``` + +而不是: + +```Python +q: str | None = None +``` + +但现在我们用 `Query` 来声明它,例如: + +```Python +q: Annotated[str | None, Query(min_length=3)] = None +``` + +因此,在使用 `Query` 的同时需要把某个值声明为必填时,只需不声明默认值: + +{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} + +### 必填,但可以为 `None` { #required-can-be-none } + +你可以声明一个参数可以接收 `None`,但它仍然是必填的。这将强制客户端必须发送一个值,即使该值是 `None`。 + +为此,你可以声明 `None` 是有效类型,但不声明默认值: + +{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} + +## 查询参数列表 / 多个值 { #query-parameter-list-multiple-values } + +当你用 `Query` 显式地定义查询参数时,你还可以声明它接收一个值列表,换句话说,接收多个值。 例如,要声明一个可在 URL 中出现多次的查询参数 `q`,你可以这样写: -{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *} -然后,输入如下网址: +然后,访问如下 URL: ``` http://localhost:8000/items/?q=foo&q=bar ``` -你会在*路径操作函数*的*函数参数* `q` 中以一个 Python `list` 的形式接收到*查询参数* `q` 的多个值(`foo` 和 `bar`)。 +你会在*路径操作函数*的*函数参数* `q` 中以一个 Python `list` 的形式接收到多个 `q` *查询参数* 的值(`foo` 和 `bar`)。 因此,该 URL 的响应将会是: @@ -149,21 +278,21 @@ http://localhost:8000/items/?q=foo&q=bar } ``` -/// tip +/// tip | 提示 -要声明类型为 `list` 的查询参数,如上例所示,你需要显式地使用 `Query`,否则该参数将被解释为请求体。 +要声明类型为 `list` 的查询参数(如上例),你需要显式地使用 `Query`,否则它会被解释为请求体。 /// -交互式 API 文档将会相应地进行更新,以允许使用多个值: +交互式 API 文档会相应更新,以支持多个值: - + -### 具有默认值的查询参数列表 / 多个值 +### 具有默认值的查询参数列表 / 多个值 { #query-parameter-list-multiple-values-with-defaults } -你还可以定义在没有任何给定值时的默认 `list` 值: +你还可以定义在没有给定值时的默认 `list`: -{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} 如果你访问: @@ -182,93 +311,163 @@ http://localhost:8000/items/ } ``` -#### 使用 `list` +#### 只使用 `list` { #using-just-list } -你也可以直接使用 `list` 代替 `List [str]`: +你也可以直接使用 `list`,而不是 `list[str]`: -{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} -/// note +/// note | 注意 -请记住,在这种情况下 FastAPI 将不会检查列表的内容。 +请记住,在这种情况下 FastAPI 不会检查列表的内容。 -例如,`List[int]` 将检查(并记录到文档)列表的内容必须是整数。但是单独的 `list` 不会。 +例如,`list[int]` 会检查(并记录到文档)列表的内容必须是整数。但仅用 `list` 不会。 /// -## 声明更多元数据 +## 声明更多元数据 { #declare-more-metadata } 你可以添加更多有关该参数的信息。 -这些信息将包含在生成的 OpenAPI 模式中,并由文档用户界面和外部工具所使用。 +这些信息会包含在生成的 OpenAPI 中,并被文档用户界面和外部工具使用。 -/// note +/// note | 注意 请记住,不同的工具对 OpenAPI 的支持程度可能不同。 -其中一些可能不会展示所有已声明的额外信息,尽管在大多数情况下,缺少的这部分功能已经计划进行开发。 +其中一些可能还不会展示所有已声明的额外信息,尽管在大多数情况下,缺失的功能已经在计划开发中。 /// 你可以添加 `title`: -{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *} +{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *} 以及 `description`: -{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *} +{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *} -## 别名参数 +## 别名参数 { #alias-parameters } -假设你想要查询参数为 `item-query`。 +假设你想要参数名为 `item-query`。 -像下面这样: +像这样: ``` http://127.0.0.1:8000/items/?item-query=foobaritems ``` -但是 `item-query` 不是一个有效的 Python 变量名称。 +但 `item-query` 不是有效的 Python 变量名。 最接近的有效名称是 `item_query`。 -但是你仍然要求它在 URL 中必须是 `item-query`... +但你仍然需要它在 URL 中就是 `item-query`…… -这时你可以用 `alias` 参数声明一个别名,该别名将用于在 URL 中查找查询参数值: +这时可以用 `alias` 参数声明一个别名,FastAPI 会用该别名在 URL 中查找参数值: -{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *} -## 弃用参数 +## 弃用参数 { #deprecating-parameters } -现在假设你不再喜欢此参数。 +现在假设你不再喜欢这个参数了。 -你不得不将其保留一段时间,因为有些客户端正在使用它,但你希望文档清楚地将其展示为已弃用。 +由于还有客户端在使用它,你不得不保留一段时间,但你希望文档清楚地将其展示为已弃用。 -那么将参数 `deprecated=True` 传入 `Query`: +那么将参数 `deprecated=True` 传给 `Query`: -{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *} +{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *} 文档将会像下面这样展示它: - + -## 总结 +## 从 OpenAPI 中排除参数 { #exclude-parameters-from-openapi } -你可以为查询参数声明额外的校验和元数据。 +要把某个查询参数从生成的 OpenAPI 模式中排除(从而也不会出现在自动文档系统中),将 `Query` 的参数 `include_in_schema` 设为 `False`: + +{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *} + +## 自定义校验 { #custom-validation } + +有些情况下你需要做一些无法通过上述参数完成的**自定义校验**。 + +在这些情况下,你可以使用**自定义校验函数**,该函数会在正常校验之后应用(例如,在先校验值是 `str` 之后)。 + +你可以在 `Annotated` 中使用 Pydantic 的 `AfterValidator` 来实现。 + +/// tip | 提示 + +Pydantic 还有 `BeforeValidator` 等。🤓 + +/// + +例如,这个自定义校验器会检查条目 ID 是否以 `isbn-`(用于 ISBN 书号)或 `imdb-`(用于 IMDB 电影 URL 的 ID)开头: + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} + +/// info | 信息 + +这在 Pydantic 2 或更高版本中可用。😎 + +/// + +/// tip | 提示 + +如果你需要进行任何需要与**外部组件**通信的校验,例如数据库或其他 API,你应该改用 **FastAPI 依赖项**,稍后你会学到它们。 + +这些自定义校验器用于只需检查请求中**同一份数据**即可完成的事情。 + +/// + +### 理解这段代码 { #understand-that-code } + +关键点仅仅是:在 `Annotated` 中使用带函数的 **`AfterValidator`**。不感兴趣可以跳过这一节。🤸 + +--- + +但如果你对这个具体示例好奇,并且还愿意继续看,这里有一些额外细节。 + +#### 字符串与 `value.startswith()` { #string-with-value-startswith } + +注意到了吗?字符串的 `value.startswith()` 可以接收一个元组,它会检查元组中的每个值: + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *} + +#### 一个随机条目 { #a-random-item } + +使用 `data.items()` 我们会得到一个包含每个字典项键和值的元组的 可迭代对象。 + +我们用 `list(data.items())` 把这个可迭代对象转换成一个真正的 `list`。 + +然后用 `random.choice()` 可以从该列表中获取一个**随机值**,也就是一个 `(id, name)` 的元组。它可能像 `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")` 这样。 + +接着我们把这个元组的**两个值**分别赋给变量 `id` 和 `name`。 + +所以,即使用户没有提供条目 ID,他们仍然会收到一个随机推荐。 + +……而我们把这些都放在**一行简单的代码**里完成。🤯 你不爱 Python 吗?🐍 + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} + +## 总结 { #recap } + +你可以为参数声明额外的校验和元数据。 通用的校验和元数据: -* `alias` -* `title` -* `description` -* `deprecated` +- `alias` +- `title` +- `description` +- `deprecated` -特定于字符串的校验: +字符串特有的校验: -* `min_length` -* `max_length` -* `regex` +- `min_length` +- `max_length` +- `pattern` -在这些示例中,你了解了如何声明对 `str` 值的校验。 +也可以使用 `AfterValidator` 进行自定义校验。 -请参阅下一章节,以了解如何声明对其他类型例如数值的校验。 +在这些示例中,你看到了如何为 `str` 值声明校验。 + +参阅下一章节,了解如何为其他类型(例如数值)声明校验。 diff --git a/docs/zh/docs/tutorial/query-params.md b/docs/zh/docs/tutorial/query-params.md index cc2e74b9e..9ef998731 100644 --- a/docs/zh/docs/tutorial/query-params.md +++ b/docs/zh/docs/tutorial/query-params.md @@ -1,8 +1,8 @@ -# 查询参数 +# 查询参数 { #query-parameters } 声明的参数不是路径参数时,路径操作函数会把该参数自动解释为**查询**参数。 -{* ../../docs_src/query_params/tutorial001.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} 查询字符串是键值对的集合,这些键值对位于 URL 的 `?` 之后,以 `&` 分隔。 @@ -26,9 +26,9 @@ http://127.0.0.1:8000/items/?skip=0&limit=10 * (显而易见的)编辑器支持 * 数据**解析** * 数据校验 -* API 文档 +* 自动文档 -## 默认值 +## 默认值 { #defaults } 查询参数不是路径的固定内容,它是可选的,还支持默认值。 @@ -57,7 +57,7 @@ http://127.0.0.1:8000/items/?skip=20 * `skip=20`:在 URL 中设定的值 * `limit=10`:使用默认值 -## 可选参数 +## 可选参数 { #optional-parameters } 同理,把默认值设为 `None` 即可声明**可选的**查询参数: @@ -71,15 +71,7 @@ http://127.0.0.1:8000/items/?skip=20 /// -/// note | 笔记 - -因为默认值为 `= None`,FastAPI 把 `q` 识别为可选参数。 - -FastAPI 不使用 `Optional[str]` 中的 `Optional`(只使用 `str`),但 `Optional[str]` 可以帮助编辑器发现代码中的错误。 - -/// - -## 查询参数类型转换 +## 查询参数类型转换 { #query-parameter-type-conversion } 参数还可以声明为 `bool` 类型,FastAPI 会自动转换参数类型: @@ -116,10 +108,10 @@ http://127.0.0.1:8000/items/foo?short=on http://127.0.0.1:8000/items/foo?short=yes ``` -或其它任意大小写形式(大写、首字母大写等),函数接收的 `short` 参数都是布尔值 `True`。值为 `False` 时也一样。 +或其它任意大小写形式(大写、首字母大写等),函数接收的 `short` 参数都是布尔值 `True`。否则为 `False`。 -## 多个路径和查询参数 +## 多个路径和查询参数 { #multiple-path-and-query-parameters } **FastAPI** 可以识别同时声明的多个路径参数和查询参数。 @@ -129,7 +121,7 @@ FastAPI 通过参数名进行检测: {* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *} -## 必选查询参数 +## 必选查询参数 { #required-query-parameters } 为不是路径参数的参数声明默认值(至此,仅有查询参数),该参数就**不是必选**的了。 @@ -137,7 +129,7 @@ FastAPI 通过参数名进行检测: 如果要把查询参数设置为**必选**,就不要声明默认值: -{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} 这里的查询参数 `needy` 是类型为 `str` 的必选查询参数。 @@ -151,16 +143,17 @@ http://127.0.0.1:8000/items/foo-item ```JSON { - "detail": [ - { - "loc": [ - "query", - "needy" - ], - "msg": "field required", - "type": "value_error.missing" - } - ] + "detail": [ + { + "type": "missing", + "loc": [ + "query", + "needy" + ], + "msg": "Field required", + "input": null + } + ] } ``` @@ -191,6 +184,6 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy /// tip | 提示 -还可以像在[路径参数](path-params.md#_8){.internal-link target=_blank} 中那样使用 `Enum`。 +还可以像在[路径参数](path-params.md#predefined-values){.internal-link target=_blank} 中那样使用 `Enum`。 /// diff --git a/docs/zh/docs/tutorial/request-files.md b/docs/zh/docs/tutorial/request-files.md index 81ddc7238..927bd093b 100644 --- a/docs/zh/docs/tutorial/request-files.md +++ b/docs/zh/docs/tutorial/request-files.md @@ -1,159 +1,163 @@ -# 请求文件 +# 请求文件 { #request-files } -`File` 用于定义客户端的上传文件。 +你可以使用 `File` 定义由客户端上传的文件。 -/// info | 说明 +/// info | 信息 -因为上传文件以「表单数据」形式发送。 +要接收上传的文件,请先安装 `python-multipart`。 -所以接收上传文件,要预先安装 `python-multipart`。 +请确保你创建一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank}、激活它,然后安装,例如: -例如: `pip install python-multipart`。 +```console +$ pip install python-multipart +``` + +这是因为上传文件是以「表单数据」发送的。 /// -## 导入 `File` +## 导入 `File` { #import-file } 从 `fastapi` 导入 `File` 和 `UploadFile`: -{* ../../docs_src/request_files/tutorial001.py hl[1] *} +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} -## 定义 `File` 参数 +## 定义 `File` 参数 { #define-file-parameters } -创建文件(`File`)参数的方式与 `Body` 和 `Form` 一样: +像为 `Body` 或 `Form` 一样创建文件参数: -{* ../../docs_src/request_files/tutorial001.py hl[7] *} +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} -/// info | 说明 +/// info | 信息 `File` 是直接继承自 `Form` 的类。 -注意,从 `fastapi` 导入的 `Query`、`Path`、`File` 等项,实际上是返回特定类的函数。 +但要注意,从 `fastapi` 导入的 `Query`、`Path`、`File` 等项,实际上是返回特定类的函数。 /// /// tip | 提示 -声明文件体必须使用 `File`,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。 +声明文件体必须使用 `File`,否则,这些参数会被当作查询参数或请求体(JSON)参数。 /// -文件作为「表单数据」上传。 +文件将作为「表单数据」上传。 -如果把*路径操作函数*参数的类型声明为 `bytes`,**FastAPI** 将以 `bytes` 形式读取和接收文件内容。 +如果把*路径操作函数*参数的类型声明为 `bytes`,**FastAPI** 会为你读取文件,并以 `bytes` 的形式接收其内容。 -这种方式把文件的所有内容都存储在内存里,适用于小型文件。 +请注意,这意味着整个内容会存储在内存中,适用于小型文件。 -不过,很多情况下,`UploadFile` 更好用。 +不过,在很多情况下,使用 `UploadFile` 会更有优势。 -## 含 `UploadFile` 的文件参数 +## 含 `UploadFile` 的文件参数 { #file-parameters-with-uploadfile } -定义文件参数时使用 `UploadFile`: +将文件参数的类型声明为 `UploadFile`: -{* ../../docs_src/request_files/tutorial001.py hl[12] *} +{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} -`UploadFile` 与 `bytes` 相比有更多优势: +与 `bytes` 相比,使用 `UploadFile` 有多项优势: -* 使用 `spooled` 文件: - * 存储在内存的文件超出最大上限时,FastAPI 会把文件存入磁盘; -* 这种方式更适于处理图像、视频、二进制文件等大型文件,好处是不会占用所有内存; -* 可获取上传文件的元数据; -* 自带 file-like `async` 接口; -* 暴露的 Python `SpooledTemporaryFile` 对象,可直接传递给其他预期「file-like」对象的库。 +* 无需在参数的默认值中使用 `File()`。 +* 它使用“spooled”文件: + * 文件会先存储在内存中,直到达到最大上限,超过该上限后会写入磁盘。 +* 因此,非常适合处理图像、视频、大型二进制等大文件,而不会占用所有内存。 +* 你可以获取上传文件的元数据。 +* 它提供 file-like 的 `async` 接口。 +* 它暴露了一个实际的 Python `SpooledTemporaryFile` 对象,你可以直接传给期望「file-like」对象的其他库。 -### `UploadFile` +### `UploadFile` { #uploadfile } `UploadFile` 的属性如下: -* `filename`:上传文件名字符串(`str`),例如, `myimage.jpg`; -* `content_type`:内容类型(MIME 类型 / 媒体类型)字符串(`str`),例如,`image/jpeg`; -* `file`: `SpooledTemporaryFile`file-like 对象)。其实就是 Python文件,可直接传递给其他预期 `file-like` 对象的函数或支持库。 +* `filename`:上传的原始文件名字符串(`str`),例如 `myimage.jpg`。 +* `content_type`:内容类型(MIME 类型 / 媒体类型)的字符串(`str`),例如 `image/jpeg`。 +* `file`:`SpooledTemporaryFile`(一个 file-like 对象)。这是实际的 Python 文件对象,你可以直接传递给其他期望「file-like」对象的函数或库。 -`UploadFile` 支持以下 `async` 方法,(使用内部 `SpooledTemporaryFile`)可调用相应的文件方法。 +`UploadFile` 具有以下 `async` 方法。它们都会在底层调用对应的文件方法(使用内部的 `SpooledTemporaryFile`)。 -* `write(data)`:把 `data` (`str` 或 `bytes`)写入文件; -* `read(size)`:按指定数量的字节或字符(`size` (`int`))读取文件内容; -* `seek(offset)`:移动至文件 `offset` (`int`)字节处的位置; - * 例如,`await myfile.seek(0) ` 移动到文件开头; - * 执行 `await myfile.read()` 后,需再次读取已读取内容时,这种方法特别好用; +* `write(data)`:将 `data`(`str` 或 `bytes`)写入文件。 +* `read(size)`:读取文件中 `size`(`int`)个字节/字符。 +* `seek(offset)`:移动到文件中字节位置 `offset`(`int`)。 + * 例如,`await myfile.seek(0)` 会移动到文件开头。 + * 如果你先运行过 `await myfile.read()`,然后需要再次读取内容时,这尤其有用。 * `close()`:关闭文件。 -因为上述方法都是 `async` 方法,要搭配「await」使用。 +由于这些方法都是 `async` 方法,你需要对它们使用 await。 -例如,在 `async` *路径操作函数* 内,要用以下方式读取文件内容: +例如,在 `async` *路径操作函数* 内,你可以这样获取内容: ```Python contents = await myfile.read() ``` -在普通 `def` *路径操作函数* 内,则可以直接访问 `UploadFile.file`,例如: +如果是在普通 `def` *路径操作函数* 内,你可以直接访问 `UploadFile.file`,例如: ```Python contents = myfile.file.read() ``` -/// note | `async` 技术细节 +/// note | 注意 -使用 `async` 方法时,**FastAPI** 在线程池中执行文件方法,并 `await` 操作完成。 +当你使用这些 `async` 方法时,**FastAPI** 会在线程池中运行相应的文件方法并等待其完成。 /// -/// note | Starlette 技术细节 +/// note | 注意 -**FastAPI** 的 `UploadFile` 直接继承自 **Starlette** 的 `UploadFile`,但添加了一些必要功能,使之与 **Pydantic** 及 FastAPI 的其它部件兼容。 +**FastAPI** 的 `UploadFile` 直接继承自 **Starlette** 的 `UploadFile`,但添加了一些必要的部分,使其与 **Pydantic** 以及 FastAPI 的其他部分兼容。 /// -## 什么是 「表单数据」 +## 什么是「表单数据」 { #what-is-form-data } -与 JSON 不同,HTML 表单(`
`)向服务器发送数据通常使用「特殊」的编码。 +HTML 表单(`
`)向服务器发送数据的方式通常会对数据使用一种「特殊」的编码,这与 JSON 不同。 -**FastAPI** 要确保从正确的位置读取数据,而不是读取 JSON。 +**FastAPI** 会确保从正确的位置读取这些数据,而不是从 JSON 中读取。 -/// note | 技术细节 +/// note | 注意 -不包含文件时,表单数据一般用 `application/x-www-form-urlencoded`「媒体类型」编码。 +当不包含文件时,来自表单的数据通常使用「媒体类型」`application/x-www-form-urlencoded` 编码。 -但表单包含文件时,编码为 `multipart/form-data`。使用了 `File`,**FastAPI** 就知道要从请求体的正确位置获取文件。 +但当表单包含文件时,会编码为 `multipart/form-data`。如果你使用 `File`,**FastAPI** 会知道需要从请求体的正确位置获取文件。 -编码和表单字段详见 MDN Web 文档的 POST 小节。 +如果你想进一步了解这些编码和表单字段,请参阅 MDN web docs for POST。 /// /// warning | 警告 -可在一个*路径操作*中声明多个 `File` 和 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码是 `multipart/form-data`,不是 `application/json`。 +你可以在一个*路径操作*中声明多个 `File` 和 `Form` 参数,但不能同时声明希望以 JSON 接收的 `Body` 字段,因为此时请求体会使用 `multipart/form-data` 编码,而不是 `application/json`。 -这不是 **FastAPI** 的问题,而是 HTTP 协议的规定。 +这不是 **FastAPI** 的限制,而是 HTTP 协议的一部分。 /// -## 可选文件上传 +## 可选文件上传 { #optional-file-upload } 您可以通过使用标准类型注解并将 None 作为默认值的方式将一个文件参数设为可选: -{* ../../docs_src/request_files/tutorial001_02_py310.py hl[7,14] *} +{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} -## 带有额外元数据的 `UploadFile` +## 带有额外元数据的 `UploadFile` { #uploadfile-with-additional-metadata } 您也可以将 `File()` 与 `UploadFile` 一起使用,例如,设置额外的元数据: -{* ../../docs_src/request_files/tutorial001_03.py hl[13] *} +{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} -## 多文件上传 +## 多文件上传 { #multiple-file-uploads } FastAPI 支持同时上传多个文件。 -可用同一个「表单字段」发送含多个文件的「表单数据」。 +它们会被关联到同一个通过「表单数据」发送的「表单字段」。 -上传多个文件时,要声明含 `bytes` 或 `UploadFile` 的列表(`List`): +要实现这一点,声明一个由 `bytes` 或 `UploadFile` 组成的列表(`List`): -{* ../../docs_src/request_files/tutorial002_py39.py hl[8,13] *} +{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} 接收的也是含 `bytes` 或 `UploadFile` 的列表(`list`)。 - -/// note | 技术细节 +/// note | 注意 也可以使用 `from starlette.responses import HTMLResponse`。 @@ -161,12 +165,12 @@ FastAPI 支持同时上传多个文件。 /// -### 带有额外元数据的多文件上传 +### 带有额外元数据的多文件上传 { #multiple-file-uploads-with-additional-metadata } -和之前的方式一样, 您可以为 `File()` 设置额外参数, 即使是 `UploadFile`: +和之前的方式一样,你可以为 `File()` 设置额外参数,即使是 `UploadFile`: -{* ../../docs_src/request_files/tutorial003_py39.py hl[16] *} +{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} -## 小结 +## 小结 { #recap } -本节介绍了如何用 `File` 把上传文件声明为(表单数据的)输入参数。 +使用 `File`、`bytes` 和 `UploadFile` 来声明在请求中上传的文件,它们以表单数据发送。 diff --git a/docs/zh/docs/tutorial/request-form-models.md b/docs/zh/docs/tutorial/request-form-models.md index e639e4b9f..4eb98ea22 100644 --- a/docs/zh/docs/tutorial/request-form-models.md +++ b/docs/zh/docs/tutorial/request-form-models.md @@ -1,12 +1,12 @@ -# 表单模型 +# 表单模型 { #form-models } -您可以使用 **Pydantic 模型**在 FastAPI 中声明**表单字段**。 +你可以在 FastAPI 中使用 **Pydantic 模型**声明**表单字段**。 -/// info +/// info | 信息 -要使用表单,需预先安装 `python-multipart` 。 +要使用表单,首先安装 `python-multipart`。 -确保您创建、激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank}后再安装。 +确保你创建一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},激活它,然后再安装,例如: ```console $ pip install python-multipart @@ -14,51 +14,51 @@ $ pip install python-multipart /// -/// note +/// note | 注意 自 FastAPI 版本 `0.113.0` 起支持此功能。🤓 /// -## 表单的 Pydantic 模型 +## 表单的 Pydantic 模型 { #pydantic-models-for-forms } -您只需声明一个 **Pydantic 模型**,其中包含您希望接收的**表单字段**,然后将参数声明为 `Form` : +你只需声明一个 **Pydantic 模型**,其中包含你希望接收的**表单字段**,然后将参数声明为 `Form`: {* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} -**FastAPI** 将从请求中的**表单数据**中**提取**出**每个字段**的数据,并提供您定义的 Pydantic 模型。 +**FastAPI** 将从请求中的**表单数据**中**提取**出**每个字段**的数据,并提供你定义的 Pydantic 模型。 -## 检查文档 +## 检查文档 { #check-the-docs } -您可以在文档 UI 中验证它,地址为 `/docs` : +你可以在文档 UI 中验证它,地址为 `/docs`:
-## 禁止额外的表单字段 +## 禁止额外的表单字段 { #forbid-extra-form-fields } -在某些特殊使用情况下(可能并不常见),您可能希望将表单字段**限制**为仅在 Pydantic 模型中声明过的字段,并**禁止**任何**额外**的字段。 +在某些特殊使用情况下(可能并不常见),你可能希望将表单字段**限制**为仅在 Pydantic 模型中声明过的字段,并**禁止**任何**额外**的字段。 -/// note +/// note | 注意 自 FastAPI 版本 `0.114.0` 起支持此功能。🤓 /// -您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段: +你可以使用 Pydantic 的模型配置来 `forbid` 任何 `extra` 字段: {* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} 如果客户端尝试发送一些额外的数据,他们将收到**错误**响应。 -例如,如果客户端尝试发送这样的表单字段: +例如,客户端尝试发送如下表单字段: * `username`: `Rick` * `password`: `Portal Gun` * `extra`: `Mr. Poopybutthole` -他们将收到一条错误响应,表明字段 `extra` 是不被允许的: +他们将收到一条错误响应,表明字段 `extra` 不被允许: ```json { @@ -73,6 +73,6 @@ $ pip install python-multipart } ``` -## 总结 +## 总结 { #summary } -您可以使用 Pydantic 模型在 FastAPI 中声明表单字段。😎 +你可以使用 Pydantic 模型在 FastAPI 中声明表单字段。😎 diff --git a/docs/zh/docs/tutorial/request-forms-and-files.md b/docs/zh/docs/tutorial/request-forms-and-files.md index f72e5047a..3c809868b 100644 --- a/docs/zh/docs/tutorial/request-forms-and-files.md +++ b/docs/zh/docs/tutorial/request-forms-and-files.md @@ -1,24 +1,28 @@ -# 请求表单与文件 +# 请求表单与文件 { #request-forms-and-files } FastAPI 支持同时使用 `File` 和 `Form` 定义文件和表单字段。 -/// info | 说明 +/// info | 信息 -接收上传文件或表单数据,要预先安装 `python-multipart`。 +接收上传的文件和/或表单数据,首先安装 `python-multipart`。 -例如,`pip install python-multipart`。 +请先创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后再安装,例如: + +```console +$ pip install python-multipart +``` /// -## 导入 `File` 与 `Form` +## 导入 `File` 与 `Form` { #import-file-and-form } -{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} -## 定义 `File` 与 `Form` 参数 +## 定义 `File` 与 `Form` 参数 { #define-file-and-form-parameters } 创建文件和表单参数的方式与 `Body` 和 `Query` 一样: -{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} 文件和表单字段作为表单数据上传与接收。 @@ -32,6 +36,6 @@ FastAPI 支持同时使用 `File` 和 `Form` 定义文件和表单字段。 /// -## 小结 +## 小结 { #recap } 在同一个请求中接收数据和文件时,应同时使用 `File` 和 `Form`。 diff --git a/docs/zh/docs/tutorial/request-forms.md b/docs/zh/docs/tutorial/request-forms.md index ee03e82a7..70eb93a26 100644 --- a/docs/zh/docs/tutorial/request-forms.md +++ b/docs/zh/docs/tutorial/request-forms.md @@ -1,69 +1,73 @@ -# 表单数据 +# 表单数据 { #form-data } -接收的不是 JSON,而是表单字段时,要使用 `Form`。 +当你需要接收表单字段而不是 JSON 时,可以使用 `Form`。 -/// info | 说明 +/// info -要使用表单,需预先安装 `python-multipart`。 +要使用表单,首先安装 `python-multipart`。 -例如,`pip install python-multipart`。 +请先创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后再进行安装,例如: + +```console +$ pip install python-multipart +``` /// -## 导入 `Form` +## 导入 `Form` { #import-form } 从 `fastapi` 导入 `Form`: -{* ../../docs_src/request_forms/tutorial001.py hl[1] *} +{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} -## 定义 `Form` 参数 +## 定义 `Form` 参数 { #define-form-parameters } -创建表单(`Form`)参数的方式与 `Body` 和 `Query` 一样: +创建表单参数的方式与 `Body` 或 `Query` 相同: -{* ../../docs_src/request_forms/tutorial001.py hl[7] *} +{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} -例如,OAuth2 规范的 "密码流" 模式规定要通过表单字段发送 `username` 和 `password`。 +例如,在 OAuth2 规范的一种使用方式(称为“密码流”)中,要求将 `username` 和 `password` 作为表单字段发送。 -该规范要求字段必须命名为 `username` 和 `password`,并通过表单字段发送,不能用 JSON。 +spec 要求这些字段必须精确命名为 `username` 和 `password`,并且作为表单字段发送,而不是 JSON。 -使用 `Form` 可以声明与 `Body` (及 `Query`、`Path`、`Cookie`)相同的元数据和验证。 +使用 `Form` 可以像使用 `Body`(以及 `Query`、`Path`、`Cookie`)一样声明相同的配置,包括校验、示例、别名(例如将 `username` 写成 `user-name`)等。 -/// info | 说明 +/// info `Form` 是直接继承自 `Body` 的类。 /// -/// tip | 提示 +/// tip -声明表单体要显式使用 `Form` ,否则,FastAPI 会把该参数当作查询参数或请求体(JSON)参数。 +要声明表单请求体,必须显式使用 `Form`,否则这些参数会被当作查询参数或请求体(JSON)参数。 /// -## 关于 "表单字段" +## 关于 "表单字段" { #about-form-fields } -与 JSON 不同,HTML 表单(`
`)向服务器发送数据通常使用「特殊」的编码。 +HTML 表单(`
`)向服务器发送数据时通常会对数据使用一种“特殊”的编码方式,这与 JSON 不同。 -**FastAPI** 要确保从正确的位置读取数据,而不是读取 JSON。 +**FastAPI** 会确保从正确的位置读取这些数据,而不是从 JSON 中读取。 /// note | 技术细节 -表单数据的「媒体类型」编码一般为 `application/x-www-form-urlencoded`。 +表单数据通常使用“媒体类型” `application/x-www-form-urlencoded` 进行编码。 -但包含文件的表单编码为 `multipart/form-data`。文件处理详见下节。 +但当表单包含文件时,会编码为 `multipart/form-data`。你将在下一章阅读如何处理文件。 -编码和表单字段详见 MDN Web 文档的 POST小节。 +如果你想了解更多关于这些编码和表单字段的信息,请参阅 MDN Web 文档的 POST。 /// -/// warning | 警告 +/// warning -可在一个*路径操作*中声明多个 `Form` 参数,但不能同时声明要接收 JSON 的 `Body` 字段。因为此时请求体的编码是 `application/x-www-form-urlencoded`,不是 `application/json`。 +你可以在一个路径操作中声明多个 `Form` 参数,但不能同时再声明要接收为 JSON 的 `Body` 字段,因为此时请求体会使用 `application/x-www-form-urlencoded` 而不是 `application/json` 进行编码。 -这不是 **FastAPI** 的问题,而是 HTTP 协议的规定。 +这不是 **FastAPI** 的限制,而是 HTTP 协议的一部分。 /// -## 小结 +## 小结 { #recap } -本节介绍了如何使用 `Form` 声明表单数据输入参数。 +使用 `Form` 来声明表单数据输入参数。 diff --git a/docs/zh/docs/tutorial/response-model.md b/docs/zh/docs/tutorial/response-model.md index 049cd1223..791eb66fb 100644 --- a/docs/zh/docs/tutorial/response-model.md +++ b/docs/zh/docs/tutorial/response-model.md @@ -1,6 +1,35 @@ -# 响应模型 +# 响应模型 - 返回类型 { #response-model-return-type } -你可以在任意的*路径操作*中使用 `response_model` 参数来声明用于响应的模型: +你可以通过为*路径操作函数*的**返回类型**添加注解来声明用于响应的类型。 + +和为输入数据在函数**参数**里做类型注解的方式相同,你可以使用 Pydantic 模型、`list`、`dict`、以及整数、布尔值等标量类型。 + +{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} + +FastAPI 会使用这个返回类型来: + +* 对返回数据进行**校验**。 + * 如果数据无效(例如缺少某个字段),这意味着你的应用代码有问题,没有返回应有的数据,FastAPI 将返回服务器错误而不是返回错误的数据。这样你和你的客户端都可以确定会收到期望的数据及其结构。 +* 在 OpenAPI 的*路径操作*中为响应添加**JSON Schema**。 + * 它会被**自动文档**使用。 + * 它也会被自动客户端代码生成工具使用。 + +但更重要的是: + +* 它会将输出数据**限制并过滤**为返回类型中定义的内容。 + * 这对**安全性**尤为重要,下面会进一步介绍。 + +## `response_model` 参数 { #response-model-parameter } + +在一些情况下,你需要或希望返回的数据与声明的类型不完全一致。 + +例如,你可能希望**返回一个字典**或数据库对象,但**将其声明为一个 Pydantic 模型**。这样 Pydantic 模型就会为你返回的对象(例如字典或数据库对象)完成文档、校验等工作。 + +如果你添加了返回类型注解,工具和编辑器会(正确地)报错,提示你的函数返回的类型(例如 `dict`)与声明的类型(例如一个 Pydantic 模型)不同。 + +在这些情况下,你可以使用*路径操作装饰器*参数 `response_model`,而不是返回类型。 + +你可以在任意*路径操作*中使用 `response_model` 参数: * `@app.get()` * `@app.post()` @@ -10,102 +39,209 @@ {* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *} -/// note +/// note | 注意 -注意,`response_model`是「装饰器」方法(`get`,`post` 等)的一个参数。不像之前的所有参数和请求体,它不属于*路径操作函数*。 +注意,`response_model` 是「装饰器」方法(`get`、`post` 等)的一个参数。不是你的*路径操作函数*的参数,不像所有查询参数和请求体那样。 /// -它接收的类型与你将为 Pydantic 模型属性所声明的类型相同,因此它可以是一个 Pydantic 模型,但也可以是一个由 Pydantic 模型组成的 `list`,例如 `List[Item]`。 +`response_model` 接收的类型与为 Pydantic 模型字段声明的类型相同,因此它可以是一个 Pydantic 模型,也可以是一个由 Pydantic 模型组成的 `list`,例如 `List[Item]`。 -FastAPI 将使用此 `response_model` 来: +FastAPI 会使用这个 `response_model` 来完成数据文档、校验等,并且还会将输出数据**转换并过滤**为其类型声明。 -* 将输出数据转换为其声明的类型。 -* 校验数据。 -* 在 OpenAPI 的*路径操作*中为响应添加一个 JSON Schema。 -* 并在自动生成文档系统中使用。 +/// tip | 提示 -但最重要的是: +如果你的编辑器、mypy 等进行严格类型检查,你可以将函数返回类型声明为 `Any`。 -* 会将输出数据限制在该模型定义内。下面我们会看到这一点有多重要。 - -/// note | 技术细节 - -响应模型在参数中被声明,而不是作为函数返回类型的注解,这是因为路径函数可能不会真正返回该响应模型,而是返回一个 `dict`、数据库对象或其他模型,然后再使用 `response_model` 来执行字段约束和序列化。 +这样你告诉编辑器你是有意返回任意类型。但 FastAPI 仍会使用 `response_model` 做数据文档、校验、过滤等工作。 /// -## 返回与输入相同的数据 +### `response_model` 的优先级 { #response-model-priority } -现在我们声明一个 `UserIn` 模型,它将包含一个明文密码属性。 +如果你同时声明了返回类型和 `response_model`,`response_model` 会具有优先级并由 FastAPI 使用。 -{* ../../docs_src/response_model/tutorial002.py hl[9,11] *} +这样,即使你返回的类型与响应模型不同,你也可以为函数添加正确的类型注解,供编辑器和 mypy 等工具使用。同时你仍然可以让 FastAPI 使用 `response_model` 进行数据校验、文档等。 -我们正在使用此模型声明输入数据,并使用同一模型声明输出数据: +你也可以使用 `response_model=None` 来禁用该*路径操作*的响应模型生成;当你为一些不是有效 Pydantic 字段的东西添加类型注解时,可能需要这样做,下面的章节会有示例。 -{* ../../docs_src/response_model/tutorial002.py hl[17:18] *} +## 返回与输入相同的数据 { #return-the-same-input-data } -现在,每当浏览器使用一个密码创建用户时,API 都会在响应中返回相同的密码。 +这里我们声明一个 `UserIn` 模型,它包含一个明文密码: -在这个案例中,这可能不算是问题,因为用户自己正在发送密码。 +{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *} -但是,如果我们在其他的*路径操作*中使用相同的模型,则可能会将用户的密码发送给每个客户端。 +/// info | 信息 -/// danger +要使用 `EmailStr`,首先安装 `email-validator`。 -永远不要存储用户的明文密码,也不要在响应中发送密码。 +请先创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装,例如: + +```console +$ pip install email-validator +``` + +或者: + +```console +$ pip install "pydantic[email]" +``` /// -## 添加输出模型 +我们使用这个模型来声明输入,同时也用相同的模型来声明输出: -相反,我们可以创建一个有明文密码的输入模型和一个没有明文密码的输出模型: +{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *} + +现在,每当浏览器使用密码创建用户时,API 会在响应中返回相同的密码。 + +在这个场景下,这可能不算问题,因为发送密码的是同一个用户。 + +但如果我们在其他*路径操作*中使用相同的模型,就可能会把用户的密码发送给每个客户端。 + +/// danger | 危险 + +除非你非常清楚所有注意事项并确实知道自己在做什么,否则永远不要存储用户的明文密码,也不要像这样在响应中发送它。 + +/// + +## 添加输出模型 { #add-an-output-model } + +相反,我们可以创建一个包含明文密码的输入模型和一个不包含它的输出模型: {* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *} -这样,即便我们的*路径操作函数*将会返回包含密码的相同输入用户: +这里,即使我们的*路径操作函数*返回的是包含密码的同一个输入用户: {* ../../docs_src/response_model/tutorial003_py310.py hl[24] *} -...我们已经将 `response_model` 声明为了不包含密码的 `UserOut` 模型: +……我们仍将 `response_model` 声明为不包含密码的 `UserOut` 模型: {* ../../docs_src/response_model/tutorial003_py310.py hl[22] *} -因此,**FastAPI** 将会负责过滤掉未在输出模型中声明的所有数据(使用 Pydantic)。 +因此,**FastAPI** 会负责过滤掉输出模型中未声明的所有数据(使用 Pydantic)。 -## 在文档中查看 +### `response_model` 还是返回类型 { #response-model-or-return-type } -当你查看自动化文档时,你可以检查输入模型和输出模型是否都具有自己的 JSON Schema: +在这个例子中,因为两个模型不同,如果我们将函数返回类型注解为 `UserOut`,编辑器和工具会抱怨我们返回了无效类型,因为它们是不同的类。 - +这就是为什么在这个例子里我们必须在 `response_model` 参数中声明它。 -并且两种模型都将在交互式 API 文档中使用: +……但继续往下读,看看如何更好地处理这种情况。 - +## 返回类型与数据过滤 { #return-type-and-data-filtering } -## 响应模型编码参数 +延续上一个例子。我们希望**用一种类型来注解函数**,但希望从函数返回的内容实际上可以**包含更多数据**。 + +我们希望 FastAPI 继续使用响应模型来**过滤**数据。这样即使函数返回了更多数据,响应也只会包含响应模型中声明的字段。 + +在上一个例子中,因为类不同,我们不得不使用 `response_model` 参数。但这也意味着我们无法从编辑器和工具处获得对函数返回类型的检查支持。 + +不过在大多数需要这样做的场景里,我们只是希望模型像这个例子中那样**过滤/移除**一部分数据。 + +在这些场景里,我们可以使用类和继承,既利用函数的**类型注解**获取更好的编辑器和工具支持,又能获得 FastAPI 的**数据过滤**。 + +{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *} + +这样一来,我们既能从编辑器和 mypy 获得工具支持(这段代码在类型上是正确的),也能从 FastAPI 获得数据过滤。 + +这是如何做到的?我们来看看。🤓 + +### 类型注解与工具链 { #type-annotations-and-tooling } + +先看看编辑器、mypy 和其他工具会如何看待它。 + +`BaseUser` 有基础字段。然后 `UserIn` 继承自 `BaseUser` 并新增了 `password` 字段,因此它包含了两个模型的全部字段。 + +我们把函数返回类型注解为 `BaseUser`,但实际上返回的是一个 `UserIn` 实例。 + +编辑器、mypy 和其他工具不会对此抱怨,因为在类型系统里,`UserIn` 是 `BaseUser` 的子类,这意味着当期望 `BaseUser` 时,返回 `UserIn` 是*合法*的。 + +### FastAPI 的数据过滤 { #fastapi-data-filtering } + +对于 FastAPI,它会查看返回类型并确保你返回的内容**只**包含该类型中声明的字段。 + +FastAPI 在内部配合 Pydantic 做了多项处理,确保不会把类继承的这些规则用于返回数据的过滤,否则你可能会返回比预期多得多的数据。 + +这样,你就能兼得两方面的优势:带有**工具支持**的类型注解和**数据过滤**。 + +## 在文档中查看 { #see-it-in-the-docs } + +当你查看自动文档时,你会看到输入模型和输出模型都会有各自的 JSON Schema: + + + +并且两个模型都会用于交互式 API 文档: + + + +## 其他返回类型注解 { #other-return-type-annotations } + +有些情况下你会返回一些不是有效 Pydantic 字段的内容,并在函数上做了相应注解,只是为了获得工具链(编辑器、mypy 等)的支持。 + +### 直接返回 Response { #return-a-response-directly } + +最常见的情况是[直接返回 Response,详见进阶文档](../advanced/response-directly.md){.internal-link target=_blank}。 + +{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} + +这个简单场景 FastAPI 会自动处理,因为返回类型注解是 `Response`(或其子类)。 + +工具也会满意,因为 `RedirectResponse` 和 `JSONResponse` 都是 `Response` 的子类,所以类型注解是正确的。 + +### 注解 Response 的子类 { #annotate-a-response-subclass } + +你也可以在类型注解中使用 `Response` 的子类: + +{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} + +这同样可行,因为 `RedirectResponse` 是 `Response` 的子类,FastAPI 会自动处理这个简单场景。 + +### 无效的返回类型注解 { #invalid-return-type-annotations } + +但当你返回其他任意对象(如数据库对象)而它不是有效的 Pydantic 类型,并在函数中按此进行了注解时,FastAPI 会尝试基于该类型注解创建一个 Pydantic 响应模型,但会失败。 + +如果你有一个在多个类型之间的联合类型,其中一个或多个不是有效的 Pydantic 类型,也会发生同样的情况,例如这个会失败 💥: + +{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} + +……它失败是因为该类型注解不是 Pydantic 类型,也不只是单个 `Response` 类或其子类,而是 `Response` 与 `dict` 的联合类型(任意其一)。 + +### 禁用响应模型 { #disable-response-model } + +延续上面的例子,你可能不想要 FastAPI 执行默认的数据校验、文档、过滤等。 + +但你可能仍然想在函数上保留返回类型注解,以获得编辑器和类型检查器(如 mypy)的支持。 + +在这种情况下,你可以通过设置 `response_model=None` 来禁用响应模型生成: + +{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *} + +这会让 FastAPI 跳过响应模型的生成,这样你就可以按需使用任意返回类型注解,而不会影响你的 FastAPI 应用。🤓 + +## 响应模型的编码参数 { #response-model-encoding-parameters } 你的响应模型可以具有默认值,例如: -{* ../../docs_src/response_model/tutorial004.py hl[11,13:14] *} +{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *} -* `description: Union[str, None] = None` 具有默认值 `None`。 -* `tax: float = 10.5` 具有默认值 `10.5`. -* `tags: List[str] = []` 具有一个空列表作为默认值: `[]`. +* `description: Union[str, None] = None`(或在 Python 3.10 中的 `str | None = None`)默认值为 `None`。 +* `tax: float = 10.5` 默认值为 `10.5`。 +* `tags: List[str] = []` 默认值为一个空列表:`[]`。 -但如果它们并没有存储实际的值,你可能想从结果中忽略它们的默认值。 +但如果它们并没有被实际存储,你可能希望在结果中省略这些默认值。 -举个例子,当你在 NoSQL 数据库中保存了具有许多可选属性的模型,但你又不想发送充满默认值的很长的 JSON 响应。 +例如,当你在 NoSQL 数据库中保存了具有许多可选属性的模型,但又不想发送充满默认值的冗长 JSON 响应。 -### 使用 `response_model_exclude_unset` 参数 +### 使用 `response_model_exclude_unset` 参数 { #use-the-response-model-exclude-unset-parameter } -你可以设置*路径操作装饰器*的 `response_model_exclude_unset=True` 参数: +你可以设置*路径操作装饰器*参数 `response_model_exclude_unset=True`: -{* ../../docs_src/response_model/tutorial004.py hl[24] *} +{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *} -然后响应中将不会包含那些默认值,而是仅有实际设置的值。 +这样响应中将不会包含那些默认值,而只包含实际设置的值。 -因此,如果你向*路径操作*发送 ID 为 `foo` 的商品的请求,则响应(不包括默认值)将为: +因此,如果你向该*路径操作*请求 ID 为 `foo` 的商品,响应(不包括默认值)将为: ```JSON { @@ -114,24 +250,18 @@ FastAPI 将使用此 `response_model` 来: } ``` -/// info - -FastAPI 通过 Pydantic 模型的 `.dict()` 配合 该方法的 `exclude_unset` 参数 来实现此功能。 - -/// - -/// info +/// info | 信息 你还可以使用: * `response_model_exclude_defaults=True` * `response_model_exclude_none=True` -参考 Pydantic 文档 中对 `exclude_defaults` 和 `exclude_none` 的描述。 +详见 Pydantic 文档中对 `exclude_defaults` 和 `exclude_none` 的说明。 /// -#### 默认值字段有实际值的数据 +#### 默认字段有实际值的数据 { #data-with-values-for-fields-with-defaults } 但是,如果你的数据在具有默认值的模型字段中有实际的值,例如 ID 为 `bar` 的项: @@ -146,7 +276,7 @@ FastAPI 通过 Pydantic 模型的 `.dict()` 配合 `http.HTTPStatus`。 @@ -31,7 +31,7 @@ -/// note | 笔记 +/// note | 注意 某些响应状态码表示响应没有响应体(参阅下一章)。 @@ -39,9 +39,9 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。 /// -## 关于 HTTP 状态码 +## 关于 HTTP 状态码 { #about-http-status-codes } -/// note | 笔记 +/// note | 注意 如果已经了解 HTTP 状态码,请跳到下一章。 @@ -53,40 +53,40 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。 简言之: -* `100` 及以上的状态码用于返回**信息**。这类状态码很少直接使用。具有这些状态码的响应不能包含响应体 -* **`200`** 及以上的状态码用于表示**成功**。这些状态码是最常用的 - * `200` 是默认状态代码,表示一切**正常** - * `201` 表示**已创建**,通常在数据库中创建新记录后使用 - * `204` 是一种特殊的例子,表示**无内容**。该响应在没有为客户端返回内容时使用,因此,该响应不能包含响应体 -* **`300`** 及以上的状态码用于**重定向**。具有这些状态码的响应不一定包含响应体,但 `304`**未修改**是个例外,该响应不得包含响应体 -* **`400`** 及以上的状态码用于表示**客户端错误**。这些可能是第二常用的类型 - * `404`,用于**未找到**响应 +* `100 - 199` 用于返回“信息”。这类状态码很少直接使用。具有这些状态码的响应不能包含响应体 +* **`200 - 299`** 用于表示“成功”。这些状态码是最常用的 + * `200` 是默认状态码,表示一切“OK” + * `201` 表示“已创建”,通常在数据库中创建新记录后使用 + * `204` 是一种特殊的例子,表示“无内容”。该响应在没有为客户端返回内容时使用,因此,该响应不能包含响应体 +* **`300 - 399`** 用于“重定向”。具有这些状态码的响应不一定包含响应体,但 `304`“未修改”是个例外,该响应不得包含响应体 +* **`400 - 499`** 用于表示“客户端错误”。这些可能是第二常用的类型 + * `404`,用于“未找到”响应 * 对于来自客户端的一般错误,可以只使用 `400` -* `500` 及以上的状态码用于表示服务器端错误。几乎永远不会直接使用这些状态码。应用代码或服务器出现问题时,会自动返回这些状态代码 +* `500 - 599` 用于表示服务器端错误。几乎永远不会直接使用这些状态码。应用代码或服务器出现问题时,会自动返回这些状态码 /// tip | 提示 -状态码及适用场景的详情,请参阅 MDN 的 HTTP 状态码文档。 +状态码及适用场景的详情,请参阅 MDN 的 HTTP 状态码文档。 /// -## 状态码名称快捷方式 +## 状态码名称快捷方式 { #shortcut-to-remember-the-names } 再看下之前的例子: -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} -`201` 表示**已创建**的状态码。 +`201` 表示“已创建”的状态码。 但我们没有必要记住所有代码的含义。 可以使用 `fastapi.status` 中的快捷变量。 -{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} 这只是一种快捷方式,具有相同的数字代码,但它可以使用编辑器的自动补全功能: - + /// note | 技术细节 @@ -96,6 +96,6 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。 /// -## 更改默认状态码 +## 更改默认状态码 { #changing-the-default } [高级用户指南](../advanced/response-change-status-code.md){.internal-link target=_blank}中,将介绍如何返回与在此声明的默认状态码不同的状态码。 diff --git a/docs/zh/docs/tutorial/schema-extra-example.md b/docs/zh/docs/tutorial/schema-extra-example.md index 6c132eed3..818ff5087 100644 --- a/docs/zh/docs/tutorial/schema-extra-example.md +++ b/docs/zh/docs/tutorial/schema-extra-example.md @@ -1,55 +1,202 @@ -# 模式的额外信息 - 例子 +# 声明请求示例数据 { #declare-request-example-data } -您可以在JSON模式中定义额外的信息。 +你可以为你的应用将接收的数据声明示例。 -一个常见的用例是添加一个将在文档中显示的`example`。 +这里有几种实现方式。 -有几种方法可以声明额外的 JSON 模式信息。 +## Pydantic 模型中的额外 JSON Schema 数据 { #extra-json-schema-data-in-pydantic-models } -## Pydantic `schema_extra` +你可以为一个 Pydantic 模型声明 `examples`,它们会被添加到生成的 JSON Schema 中。 -您可以使用 `Config` 和 `schema_extra` 为Pydantic模型声明一个示例,如Pydantic 文档:定制 Schema 中所述: +{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} -{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:21] *} +这些额外信息会原样添加到该模型输出的 JSON Schema 中,并会在 API 文档中使用。 -这些额外的信息将按原样添加到输出的JSON模式中。 +你可以使用属性 `model_config`,它接收一个 `dict`,详见 Pydantic 文档:配置。 -## `Field` 的附加参数 +你可以设置 `"json_schema_extra"`,其值为一个 `dict`,包含你希望出现在生成 JSON Schema 中的任意附加数据,包括 `examples`。 -在 `Field`, `Path`, `Query`, `Body` 和其他你之后将会看到的工厂函数,你可以为JSON 模式声明额外信息,你也可以通过给工厂函数传递其他的任意参数来给JSON 模式声明额外信息,比如增加 `example`: +/// tip | 提示 -{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} +你也可以用同样的技巧扩展 JSON Schema,添加你自己的自定义额外信息。 -/// warning - -请记住,传递的那些额外参数不会添加任何验证,只会添加注释,用于文档的目的。 +例如,你可以用它为前端用户界面添加元数据等。 /// -## `Body` 额外参数 +/// info | 信息 -你可以通过传递额外信息给 `Field` 同样的方式操作`Path`, `Query`, `Body`等。 +OpenAPI 3.1.0(自 FastAPI 0.99.0 起使用)增加了对 `examples` 的支持,它是 JSON Schema 标准的一部分。 -比如,你可以将请求体的一个 `example` 传递给 `Body`: +在此之前,只支持使用单个示例的关键字 `example`。OpenAPI 3.1.0 仍然支持它,但它已被弃用,并不属于 JSON Schema 标准。因此,建议你把 `example` 迁移到 `examples`。🤓 -{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:27] *} +你可以在本页末尾阅读更多内容。 -## 文档 UI 中的例子 +/// -使用上面的任何方法,它在 `/docs` 中看起来都是这样的: +## `Field` 的附加参数 { #field-additional-arguments } + +在 Pydantic 模型中使用 `Field()` 时,你也可以声明额外的 `examples`: + +{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} + +## JSON Schema 中的 `examples` - OpenAPI { #examples-in-json-schema-openapi } + +在以下任意场景中使用: + +- `Path()` +- `Query()` +- `Header()` +- `Cookie()` +- `Body()` +- `Form()` +- `File()` + +你也可以声明一组 `examples`,这些带有附加信息的示例将被添加到它们在 OpenAPI 中的 JSON Schema 里。 + +### 带有 `examples` 的 `Body` { #body-with-examples } + +这里我们向 `Body()` 传入 `examples`,其中包含一个期望的数据示例: + +{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *} + +### 文档 UI 中的示例 { #example-in-the-docs-ui } + +使用上述任一方法,在 `/docs` 中看起来会是这样: -## 技术细节 +### 带有多个 `examples` 的 `Body` { #body-with-multiple-examples } -关于 `example` 和 `examples`... +当然,你也可以传入多个 `examples`: -JSON Schema在最新的一个版本中定义了一个字段 `examples` ,但是 OpenAPI 基于之前的一个旧版JSON Schema,并没有 `examples`. +{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} -所以 OpenAPI为了相似的目的定义了自己的 `example` (使用 `example`, 而不是 `examples`), 这也是文档 UI 所使用的 (使用 Swagger UI). +这样做时,这些示例会成为该请求体数据内部 JSON Schema 的一部分。 -所以,虽然 `example` 不是JSON Schema的一部分,但它是OpenAPI的一部分,这将被文档UI使用。 +不过,在撰写本文时,用于展示文档 UI 的 Swagger UI 并不支持显示 JSON Schema 中数据的多个示例。但请继续阅读,下面有一种变通方法。 -## 其他信息 +### OpenAPI 特定的 `examples` { #openapi-specific-examples } -同样的方法,你可以添加你自己的额外信息,这些信息将被添加到每个模型的JSON模式中,例如定制前端用户界面,等等。 +在 JSON Schema 支持 `examples` 之前,OpenAPI 就已支持一个同名但不同的字段 `examples`。 + +这个面向 OpenAPI 的 `examples` 位于 OpenAPI 规范的另一处。它放在每个路径操作的详细信息中,而不是每个 JSON Schema 里。 + +而 Swagger UI 早就支持这个特定的 `examples` 字段。因此,你可以用它在文档 UI 中展示不同的示例。 + +这个 OpenAPI 特定字段 `examples` 的结构是一个包含多个示例的 `dict`(而不是一个 `list`),每个示例都包含会被添加到 OpenAPI 的额外信息。 + +这不放在 OpenAPI 内部包含的各个 JSON Schema 里,而是直接放在路径操作上。 + +### 使用 `openapi_examples` 参数 { #using-the-openapi-examples-parameter } + +你可以在 FastAPI 中通过参数 `openapi_examples` 来声明这个 OpenAPI 特定的 `examples`,适用于: + +- `Path()` +- `Query()` +- `Header()` +- `Cookie()` +- `Body()` +- `Form()` +- `File()` + +这个 `dict` 的键用于标识每个示例,每个值是另一个 `dict`。 + +`examples` 中每个具体示例的 `dict` 可以包含: + +- `summary`:该示例的简短描述。 +- `description`:较长描述,可以包含 Markdown 文本。 +- `value`:实际展示的示例,例如一个 `dict`。 +- `externalValue`:`value` 的替代项,指向该示例的 URL。不过它的工具支持度可能不如 `value`。 + +你可以这样使用: + +{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *} + +### 文档 UI 中的 OpenAPI 示例 { #openapi-examples-in-the-docs-ui } + +当把 `openapi_examples` 添加到 `Body()` 后,`/docs` 会如下所示: + + + +## 技术细节 { #technical-details } + +/// tip | 提示 + +如果你已经在使用 FastAPI 版本 0.99.0 或更高版本,你大概率可以跳过这些细节。 + +它们对更早版本(OpenAPI 3.1.0 尚不可用之前)更相关。 + +你可以把这当作一堂简短的 OpenAPI 和 JSON Schema 历史课。🤓 + +/// + +/// warning | 警告 + +以下是关于 JSON Schema 和 OpenAPI 标准的非常技术性的细节。 + +如果上面的思路对你已经足够可用,你可能不需要这些细节,可以直接跳过。 + +/// + +在 OpenAPI 3.1.0 之前,OpenAPI 使用的是一个更旧且经过修改的 JSON Schema 版本。 + +当时 JSON Schema 没有 `examples`,所以 OpenAPI 在它修改过的版本中添加了自己的 `example` 字段。 + +OpenAPI 还在规范的其他部分添加了 `example` 和 `examples` 字段: + +- `Parameter Object`(规范中),被 FastAPI 的以下内容使用: + - `Path()` + - `Query()` + - `Header()` + - `Cookie()` +- `Request Body Object` 中的 `content` 字段里的 `Media Type Object`(规范中),被 FastAPI 的以下内容使用: + - `Body()` + - `File()` + - `Form()` + +/// info | 信息 + +这个旧的、OpenAPI 特定的 `examples` 参数,自 FastAPI `0.103.0` 起改名为 `openapi_examples`。 + +/// + +### JSON Schema 的 `examples` 字段 { #json-schemas-examples-field } + +后来,JSON Schema 在新版本的规范中添加了 `examples` 字段。 + +随后新的 OpenAPI 3.1.0 基于最新版本(JSON Schema 2020-12),其中包含了这个新的 `examples` 字段。 + +现在,这个新的 `examples` 字段优先于旧的单个(且自定义的)`example` 字段,后者已被弃用。 + +JSON Schema 中这个新的 `examples` 字段只是一个由示例组成的 `list`,而不是像上面提到的 OpenAPI 其他位置那样带有额外元数据的 `dict`。 + +/// info | 信息 + +即使在 OpenAPI 3.1.0 发布、并与 JSON Schema 有了这种更简单的集成之后,有一段时间里,提供自动文档的 Swagger UI 并不支持 OpenAPI 3.1.0(它自 5.0.0 版本起已支持 🎉)。 + +因此,FastAPI 0.99.0 之前的版本仍然使用低于 3.1.0 的 OpenAPI 版本。 + +/// + +### Pydantic 与 FastAPI 的 `examples` { #pydantic-and-fastapi-examples } + +当你在 Pydantic 模型中添加 `examples`,通过 `schema_extra` 或 `Field(examples=["something"])`,这些示例会被添加到该 Pydantic 模型的 JSON Schema 中。 + +这个 Pydantic 模型的 JSON Schema 会被包含到你的 API 的 OpenAPI 中,然后在文档 UI 中使用。 + +在 FastAPI 0.99.0 之前的版本(0.99.0 及以上使用更新的 OpenAPI 3.1.0),当你在其他工具(`Query()`、`Body()` 等)中使用 `example` 或 `examples` 时,这些示例不会被添加到描述该数据的 JSON Schema 中(甚至不会添加到 OpenAPI 自己的 JSON Schema 版本中),而是会直接添加到 OpenAPI 的路径操作声明中(在 OpenAPI 使用 JSON Schema 的部分之外)。 + +但现在 FastAPI 0.99.0 及以上使用 OpenAPI 3.1.0(其使用 JSON Schema 2020-12)以及 Swagger UI 5.0.0 及以上后,一切更加一致,示例会包含在 JSON Schema 中。 + +### Swagger UI 与 OpenAPI 特定的 `examples` { #swagger-ui-and-openapi-specific-examples } + +此前,由于 Swagger UI 不支持多个 JSON Schema 示例(截至 2023-08-26),用户无法在文档中展示多个示例。 + +为了解决这个问题,FastAPI `0.103.0` 通过新增参数 `openapi_examples`,为声明同样的旧式 OpenAPI 特定 `examples` 字段提供了支持。🤓 + +### 总结 { #summary } + +我曾经说我不太喜欢历史……结果现在在这儿上“技术史”课。😅 + +简而言之,升级到 FastAPI 0.99.0 或更高版本,一切会更简单、一致、直观,你也不必了解这些历史细节。😎 diff --git a/docs/zh/docs/tutorial/security/first-steps.md b/docs/zh/docs/tutorial/security/first-steps.md index 225eb2695..43b7c6657 100644 --- a/docs/zh/docs/tutorial/security/first-steps.md +++ b/docs/zh/docs/tutorial/security/first-steps.md @@ -1,197 +1,203 @@ -# 安全 - 第一步 +# 安全 - 第一步 { #security-first-steps } -假设**后端** API 在某个域。 +假设你的**后端** API 位于某个域名下。 -**前端**在另一个域,或(移动应用中)在同一个域的不同路径下。 +而**前端**在另一个域名,或同一域名的不同路径(或在移动应用中)。 -并且,前端要使用后端的 **username** 与 **password** 验证用户身份。 +你希望前端能通过**username** 和 **password** 与后端进行身份验证。 -固然,**FastAPI** 支持 **OAuth2** 身份验证。 +我们可以用 **OAuth2** 在 **FastAPI** 中实现它。 -但为了节省开发者的时间,不要只为了查找很少的内容,不得不阅读冗长的规范文档。 +但为了节省你的时间,不必为获取少量信息而通读冗长的规范。 -我们建议使用 **FastAPI** 的安全工具。 +我们直接使用 **FastAPI** 提供的安全工具。 -## 概览 +## 效果预览 { #how-it-looks } -首先,看看下面的代码是怎么运行的,然后再回过头来了解其背后的原理。 +先直接运行代码看看效果,之后再回过头理解其背后的原理。 -## 创建 `main.py` +## 创建 `main.py` { #create-main-py } 把下面的示例代码复制到 `main.py`: {* ../../docs_src/security/tutorial001_an_py39.py *} -## 运行 +## 运行 { #run-it } -/// info | 说明 +/// info | 信息 -先安装 `python-multipart`。 +当你使用命令 `pip install "fastapi[standard]"` 安装 **FastAPI** 时,`python-multipart` 包会自动安装。 -安装命令: `pip install python-multipart`。 +但是,如果你使用 `pip install fastapi`,默认不会包含 `python-multipart` 包。 -这是因为 **OAuth2** 使用**表单数据**发送 `username` 与 `password`。 +如需手动安装,请先创建并激活[虚拟环境](../../virtual-environments.md){.internal-link target=_blank},然后执行: + +```console +$ pip install python-multipart +``` + +这是因为 **OAuth2** 使用“表单数据”来发送 `username` 和 `password`。 /// -用下面的命令运行该示例: +用下面的命令运行示例:
```console -$ uvicorn main:app --reload +$ fastapi dev main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-## 查看文档 +## 查看 { #check-it } -打开 API 文档: http://127.0.0.1:8000/docs。 +打开交互式文档:http://127.0.0.1:8000/docs。 -界面如下图所示: +你会看到类似这样的界面: /// check | Authorize 按钮! -页面右上角出现了一个「**Authorize**」按钮。 +页面右上角已经有一个崭新的“Authorize”按钮。 -*路径操作*的右上角也出现了一个可以点击的小锁图标。 +你的*路径操作*右上角还有一个可点击的小锁图标。 /// -点击 **Authorize** 按钮,弹出授权表单,输入 `username` 与 `password` 及其它可选字段: +点击它,会弹出一个授权表单,可输入 `username` 和 `password`(以及其它可选字段): -/// note | 笔记 +/// note | 注意 -目前,在表单中输入内容不会有任何反应,后文会介绍相关内容。 +目前无论在表单中输入什么都不会生效,我们稍后就会实现它。 /// -虽然此文档不是给前端最终用户使用的,但这个自动工具非常实用,可在文档中与所有 API 交互。 +这当然不是面向最终用户的前端,但它是一个很棒的自动化工具,可交互式地为整个 API 提供文档。 -前端团队(可能就是开发者本人)可以使用本工具。 +前端团队(也可能就是你自己)可以使用它。 -第三方应用与系统也可以调用本工具。 +第三方应用和系统也可以使用它。 -开发者也可以用它来调试、检查、测试应用。 +你也可以用它来调试、检查和测试同一个应用。 -## 密码流 +## `password` 流 { #the-password-flow } -现在,我们回过头来介绍这段代码的原理。 +现在回过头来理解这些内容。 -`Password` **流**是 OAuth2 定义的,用于处理安全与身份验证的方式(**流**)。 +`password` “流”(flow)是 OAuth2 定义的处理安全与身份验证的一种方式。 -OAuth2 的设计目标是为了让后端或 API 独立于服务器验证用户身份。 +OAuth2 的设计目标是让后端或 API 与负责用户认证的服务器解耦。 -但在本例中,**FastAPI** 应用会处理 API 与身份验证。 +但在这个例子中,**FastAPI** 应用同时处理 API 和认证。 -下面,我们来看一下简化的运行流程: +从这个简化的角度来看看流程: -- 用户在前端输入 `username` 与`password`,并点击**回车** -- (用户浏览器中运行的)前端把 `username` 与`password` 发送至 API 中指定的 URL(使用 `tokenUrl="token"` 声明) -- API 检查 `username` 与`password`,并用令牌(`Token`) 响应(暂未实现此功能): - - 令牌只是用于验证用户的字符串 - - 一般来说,令牌会在一段时间后过期 - - 过时后,用户要再次登录 - - 这样一来,就算令牌被人窃取,风险也较低。因为它与永久密钥不同,**在绝大多数情况下**不会长期有效 -- 前端临时将令牌存储在某个位置 -- 用户点击前端,前往前端应用的其它部件 -- 前端需要从 API 中提取更多数据: - - 为指定的端点(Endpoint)进行身份验证 - - 因此,用 API 验证身份时,要发送值为 `Bearer` + 令牌的请求头 `Authorization` - - 假如令牌为 `foobar`,`Authorization` 请求头就是: `Bearer foobar` +* 用户在前端输入 `username` 和 `password`,然后按下 `Enter`。 +* 前端(运行在用户浏览器中)把 `username` 和 `password` 发送到我们 API 中的特定 URL(使用 `tokenUrl="token"` 声明)。 +* API 校验 `username` 和 `password`,并返回一个“令牌”(这些我们尚未实现)。 + * “令牌”只是一个字符串,包含一些内容,之后可用来验证该用户。 + * 通常,令牌会在一段时间后过期。 + * 因此,用户过一段时间需要重新登录。 + * 如果令牌被窃取,风险也更小。它不像一把永久有效的钥匙(在大多数情况下)。 +* 前端会把令牌临时存储在某处。 +* 用户在前端中点击跳转到前端应用的其他部分。 +* 前端需要从 API 获取更多数据。 + * 但该端点需要身份验证。 + * 因此,为了与我们的 API 进行身份验证,它会发送一个 `Authorization` 请求头,值为 `Bearer ` 加上令牌。 + * 如果令牌内容是 `foobar`,`Authorization` 请求头的内容就是:`Bearer foobar`。 -## **FastAPI** 的 `OAuth2PasswordBearer` +## **FastAPI** 的 `OAuth2PasswordBearer` { #fastapis-oauth2passwordbearer } -**FastAPI** 提供了不同抽象级别的安全工具。 +**FastAPI** 在不同抽象层级提供了多种安全工具。 -本例使用 **OAuth2** 的 **Password** 流以及 **Bearer** 令牌(`Token`)。为此要使用 `OAuth2PasswordBearer` 类。 +本示例将使用 **OAuth2** 的 **Password** 流程并配合 **Bearer** 令牌,通过 `OAuth2PasswordBearer` 类来实现。 -/// info | 说明 +/// info | 信息 -`Bearer` 令牌不是唯一的选择。 +“Bearer” 令牌并非唯一选项。 -但它是最适合这个用例的方案。 +但它非常适合我们的用例。 -甚至可以说,它是适用于绝大多数用例的最佳方案,除非您是 OAuth2 的专家,知道为什么其它方案更合适。 +对于大多数用例,它也可能是最佳选择,除非你是 OAuth2 专家,并明确知道为何其他方案更适合你的需求。 -本例中,**FastAPI** 还提供了构建工具。 +在那种情况下,**FastAPI** 同样提供了相应的构建工具。 /// -创建 `OAuth2PasswordBearer` 的类实例时,要传递 `tokenUrl` 参数。该参数包含客户端(用户浏览器中运行的前端) 的 URL,用于发送 `username` 与 `password`,并获取令牌。 +创建 `OAuth2PasswordBearer` 类实例时,需要传入 `tokenUrl` 参数。该参数包含客户端(运行在用户浏览器中的前端)用来发送 `username` 和 `password` 以获取令牌的 URL。 -{* ../../docs_src/security/tutorial001.py hl[6] *} +{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} /// tip | 提示 -在此,`tokenUrl="token"` 指向的是暂未创建的相对 URL `token`。这个相对 URL 相当于 `./token`。 +这里的 `tokenUrl="token"` 指向的是尚未创建的相对 URL `token`,等价于 `./token`。 -因为使用的是相对 URL,如果 API 位于 `https://example.com/`,则指向 `https://example.com/token`。但如果 API 位于 `https://example.com/api/v1/`,它指向的就是`https://example.com/api/v1/token`。 +因为使用的是相对 URL,若你的 API 位于 `https://example.com/`,它将指向 `https://example.com/token`;若你的 API 位于 `https://example.com/api/v1/`,它将指向 `https://example.com/api/v1/token`。 -使用相对 URL 非常重要,可以确保应用在遇到[使用代理](../../advanced/behind-a-proxy.md){.internal-link target=_blank}这样的高级用例时,也能正常运行。 +使用相对 URL 很重要,这能确保你的应用在诸如[使用代理](../../advanced/behind-a-proxy.md){.internal-link target=_blank}等高级用例中依然正常工作。 /// -该参数不会创建端点或*路径操作*,但会声明客户端用来获取令牌的 URL `/token` 。此信息用于 OpenAPI 及 API 文档。 +这个参数不会创建该端点/*路径操作*,而是声明客户端应使用 `/token` 这个 URL 来获取令牌。这些信息会用于 OpenAPI,进而用于交互式 API 文档系统。 -接下来,学习如何创建实际的路径操作。 +我们很快也会创建对应的实际路径操作。 -/// info | 说明 +/// info | 信息 -严苛的 **Pythonista** 可能不喜欢用 `tokenUrl` 这种命名风格代替 `token_url`。 +如果你是非常严格的 “Pythonista”,可能不喜欢使用参数名 `tokenUrl` 而不是 `token_url`。 -这种命名方式是因为要使用与 OpenAPI 规范中相同的名字。以便在深入校验安全方案时,能通过复制粘贴查找更多相关信息。 +这是因为它使用了与 OpenAPI 规范中相同的名称。这样当你需要深入了解这些安全方案时,可以直接复制粘贴去查找更多信息。 /// -`oauth2_scheme` 变量是 `OAuth2PasswordBearer` 的实例,也是**可调用项**。 +`oauth2_scheme` 变量是 `OAuth2PasswordBearer` 的一个实例,同时它也是“可调用”的。 -以如下方式调用: +可以像这样调用: ```Python oauth2_scheme(some, parameters) ``` -因此,`Depends` 可以调用 `oauth2_scheme` 变量。 +因此,它可以与 `Depends` 一起使用。 -### 使用 +### 使用 { #use-it } -接下来,使用 `Depends` 把 `oauth2_scheme` 传入依赖项。 +现在你可以通过 `Depends` 将 `oauth2_scheme` 作为依赖传入。 -{* ../../docs_src/security/tutorial001.py hl[10] *} +{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} -该依赖项使用字符串(`str`)接收*路径操作函数*的参数 `token` 。 +该依赖会提供一个 `str`,赋值给*路径操作函数*的参数 `token`。 -**FastAPI** 使用依赖项在 OpenAPI 概图(及 API 文档)中定义**安全方案**。 +**FastAPI** 会据此在 OpenAPI 架构(以及自动生成的 API 文档)中定义一个“安全方案”。 /// info | 技术细节 -**FastAPI** 使用(在依赖项中声明的)类 `OAuth2PasswordBearer` 在 OpenAPI 中定义安全方案,这是因为它继承自 `fastapi.security.oauth2.OAuth2`,而该类又是继承自`fastapi.security.base.SecurityBase`。 +**FastAPI** 之所以知道可以使用(在依赖中声明的)`OAuth2PasswordBearer` 在 OpenAPI 中定义安全方案,是因为它继承自 `fastapi.security.oauth2.OAuth2`,而后者又继承自 `fastapi.security.base.SecurityBase`。 -所有与 OpenAPI(及 API 文档)集成的安全工具都继承自 `SecurityBase`, 这就是为什么 **FastAPI** 能把它们集成至 OpenAPI 的原因。 +所有与 OpenAPI(以及自动 API 文档)集成的安全工具都继承自 `SecurityBase`,这就是 **FastAPI** 能将它们集成到 OpenAPI 的方式。 /// -## 实现的操作 +## 它做了什么 { #what-it-does } -FastAPI 校验请求中的 `Authorization` 请求头,核对请求头的值是不是由 `Bearer ` + 令牌组成, 并返回令牌字符串(`str`)。 +它会在请求中查找 `Authorization` 请求头,检查其值是否为 `Bearer ` 加上一些令牌,并将该令牌作为 `str` 返回。 -如果没有找到 `Authorization` 请求头,或请求头的值不是 `Bearer ` + 令牌。FastAPI 直接返回 401 错误状态码(`UNAUTHORIZED`)。 +如果没有 `Authorization` 请求头,或者其值不包含 `Bearer ` 令牌,它会直接返回 401 状态码错误(`UNAUTHORIZED`)。 -开发者不需要检查错误信息,查看令牌是否存在,只要该函数能够执行,函数中就会包含令牌字符串。 +你甚至无需检查令牌是否存在即可返回错误;只要你的函数被执行,就可以确定会拿到一个 `str` 类型的令牌。 -正如下图所示,API 文档已经包含了这项功能: +你已经可以在交互式文档中试试了: -目前,暂时还没有实现验证令牌是否有效的功能,不过后文很快就会介绍的。 +我们还没有验证令牌是否有效,但这已经是一个良好的开端。 -## 小结 +## 小结 { #recap } -看到了吧,只要多写三四行代码,就可以添加基础的安全表单。 +只需增加三四行代码,你就已经拥有了一种初步的安全机制。 diff --git a/docs/zh/docs/tutorial/security/get-current-user.md b/docs/zh/docs/tutorial/security/get-current-user.md index 1f254a103..c14bba28a 100644 --- a/docs/zh/docs/tutorial/security/get-current-user.md +++ b/docs/zh/docs/tutorial/security/get-current-user.md @@ -1,23 +1,23 @@ -# 获取当前用户 +# 获取当前用户 { #get-current-user } 上一章中,(基于依赖注入系统的)安全系统向*路径操作函数*传递了 `str` 类型的 `token`: -{* ../../docs_src/security/tutorial001.py hl[10] *} +{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} 但这并不实用。 接下来,我们学习如何返回当前用户。 -## 创建用户模型 +## 创建用户模型 { #create-a-user-model } 首先,创建 Pydantic 用户模型。 与使用 Pydantic 声明请求体相同,并且可在任何位置使用: -{* ../../docs_src/security/tutorial002.py hl[5,12:16] *} +{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *} -## 创建 `get_current_user` 依赖项 +## 创建 `get_current_user` 依赖项 { #create-a-get-current-user-dependency } 创建 `get_current_user` 依赖项。 @@ -27,19 +27,19 @@ 与之前直接在路径操作中的做法相同,新的 `get_current_user` 依赖项从子依赖项 `oauth2_scheme` 中接收 `str` 类型的 `token`: -{* ../../docs_src/security/tutorial002.py hl[25] *} +{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *} -## 获取用户 +## 获取用户 { #get-the-user } `get_current_user` 使用创建的(伪)工具函数,该函数接收 `str` 类型的令牌,并返回 Pydantic 的 `User` 模型: -{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *} +{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *} -## 注入当前用户 +## 注入当前用户 { #inject-the-current-user } 在*路径操作* 的 `Depends` 中使用 `get_current_user`: -{* ../../docs_src/security/tutorial002.py hl[31] *} +{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *} 注意,此处把 `current_user` 的类型声明为 Pydantic 的 `User` 模型。 @@ -61,7 +61,7 @@ /// -## 其它模型 +## 其它模型 { #other-models } 接下来,直接在*路径操作函数*中获取当前用户,并用 `Depends` 在**依赖注入**系统中处理安全机制。 @@ -78,7 +78,7 @@ 尽管使用应用所需的任何模型、类、数据库。**FastAPI** 通过依赖注入系统都能帮您搞定。 -## 代码大小 +## 代码大小 { #code-size } 这个示例看起来有些冗长。毕竟这个文件同时包含了安全、数据模型的工具函数,以及路径操作等代码。 @@ -94,9 +94,9 @@ 所有*路径操作*只需 3 行代码就可以了: -{* ../../docs_src/security/tutorial002.py hl[30:32] *} +{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *} -## 小结 +## 小结 { #recap } 现在,我们可以直接在*路径操作函数*中获取当前用户。 diff --git a/docs/zh/docs/tutorial/security/index.md b/docs/zh/docs/tutorial/security/index.md index 1484b99fd..589f93c3e 100644 --- a/docs/zh/docs/tutorial/security/index.md +++ b/docs/zh/docs/tutorial/security/index.md @@ -1,4 +1,4 @@ -# 安全性 +# 安全性 { #security } 有许多方法可以处理安全性、身份认证和授权等问题。 @@ -10,11 +10,11 @@ 但首先,让我们来看一些小的概念。 -## 没有时间? +## 没有时间? { #in-a-hurry } -如果你不关心这些术语,而只需要*立即*通过基于用户名和密码的身份认证来增加安全性,请跳转到下一章。 +如果你不关心这些术语,而只需要*立即*通过基于用户名和密码的身份认证来增加安全性,请跳转到接下来的章节。 -## OAuth2 +## OAuth2 { #oauth2 } OAuth2是一个规范,它定义了几种处理身份认证和授权的方法。 @@ -24,7 +24,7 @@ OAuth2是一个规范,它定义了几种处理身份认证和授权的方法 这就是所有带有「使用 Facebook,Google,X (Twitter),GitHub 登录」的系统背后所使用的机制。 -### OAuth 1 +### OAuth 1 { #oauth-1 } 有一个 OAuth 1,它与 OAuth2 完全不同,并且更为复杂,因为它直接包含了有关如何加密通信的规范。 @@ -32,13 +32,13 @@ OAuth2是一个规范,它定义了几种处理身份认证和授权的方法 OAuth2 没有指定如何加密通信,它期望你为应用程序使用 HTTPS 进行通信。 -/// tip +/// tip | 提示 在有关**部署**的章节中,你将了解如何使用 Traefik 和 Let's Encrypt 免费设置 HTTPS。 /// -## OpenID Connect +## OpenID Connect { #openid-connect } OpenID Connect 是另一个基于 **OAuth2** 的规范。 @@ -48,7 +48,7 @@ OpenID Connect 是另一个基于 **OAuth2** 的规范。 但是 Facebook 登录不支持 OpenID Connect。它具有自己的 OAuth2 风格。 -### OpenID(非「OpenID Connect」) +### OpenID(非「OpenID Connect」) { #openid-not-openid-connect } 还有一个「OpenID」规范。它试图解决与 **OpenID Connect** 相同的问题,但它不是基于 OAuth2。 @@ -56,7 +56,7 @@ OpenID Connect 是另一个基于 **OAuth2** 的规范。 如今它已经不是很流行,没有被广泛使用了。 -## OpenAPI +## OpenAPI { #openapi } OpenAPI(以前称为 Swagger)是用于构建 API 的开放规范(现已成为 Linux Foundation 的一部分)。 @@ -75,7 +75,7 @@ OpenAPI 定义了以下安全方案: * 请求头。 * cookie。 * `http`:标准的 HTTP 身份认证系统,包括: - * `bearer`: 一个值为 `Bearer` 加令牌字符串的 `Authorization` 请求头。这是从 OAuth2 继承的。 + * `bearer`: 一个值为 `Bearer ` 加令牌字符串的 `Authorization` 请求头。这是从 OAuth2 继承的。 * HTTP Basic 认证方式。 * HTTP Digest,等等。 * `oauth2`:所有的 OAuth2 处理安全性的方式(称为「流程」)。 @@ -89,7 +89,7 @@ OpenAPI 定义了以下安全方案: * 此自动发现机制是 OpenID Connect 规范中定义的内容。 -/// tip +/// tip | 提示 集成其他身份认证/授权提供者(例如Google,Facebook,X (Twitter),GitHub等)也是可能的,而且较为容易。 @@ -97,10 +97,10 @@ OpenAPI 定义了以下安全方案: /// -## **FastAPI** 实用工具 +## **FastAPI** 实用工具 { #fastapi-utilities } FastAPI 在 `fastapi.security` 模块中为每个安全方案提供了几种工具,这些工具简化了这些安全机制的使用方法。 -在下一章中,你将看到如何使用 **FastAPI** 所提供的这些工具为你的 API 增加安全性。 +在接下来的章节中,你将看到如何使用 **FastAPI** 所提供的这些工具为你的 API 增加安全性。 而且你还将看到它如何自动地被集成到交互式文档系统中。 diff --git a/docs/zh/docs/tutorial/security/oauth2-jwt.md b/docs/zh/docs/tutorial/security/oauth2-jwt.md index 7d338419b..c7eb9bd90 100644 --- a/docs/zh/docs/tutorial/security/oauth2-jwt.md +++ b/docs/zh/docs/tutorial/security/oauth2-jwt.md @@ -1,34 +1,36 @@ -# OAuth2 实现密码哈希与 Bearer JWT 令牌验证 +# 使用密码(及哈希)的 OAuth2,基于 JWT 的 Bearer 令牌 { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens } -至此,我们已经编写了所有安全流,本章学习如何使用 JWT 令牌(Token)和安全密码哈希(Hash)实现真正的安全机制。 +现在我们已经有了完整的安全流程,接下来用 JWT 令牌和安全的密码哈希,让应用真正安全起来。 -本章的示例代码真正实现了在应用的数据库中保存哈希密码等功能。 +这些代码可以直接用于你的应用,你可以把密码哈希保存到数据库中,等等。 -接下来,我们紧接上一章,继续完善安全机制。 +我们将从上一章结束的地方继续,逐步完善。 -## JWT 简介 +## 关于 JWT { #about-jwt } -JWT 即**JSON 网络令牌**(JSON Web Tokens)。 +JWT 意为 “JSON Web Tokens”。 -JWT 是一种将 JSON 对象编码为没有空格,且难以理解的长字符串的标准。JWT 的内容如下所示: +它是一种标准,把一个 JSON 对象编码成没有空格、很密集的一长串字符串。看起来像这样: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ``` -JWT 字符串没有加密,任何人都能用它恢复原始信息。 +它不是加密的,所以任何人都可以从内容中恢复信息。 -但 JWT 使用了签名机制。接受令牌时,可以用签名校验令牌。 +但它是“签名”的。因此,当你收到一个自己签发的令牌时,你可以验证它确实是你签发的。 -使用 JWT 创建有效期为一周的令牌。第二天,用户持令牌再次访问时,仍为登录状态。 +这样你就可以创建一个例如有效期为 1 周的令牌。然后当用户第二天带着这个令牌回来时,你能知道该用户仍然处于登录状态。 -令牌于一周后过期,届时,用户身份验证就会失败。只有再次登录,才能获得新的令牌。如果用户(或第三方)篡改令牌的过期时间,因为签名不匹配会导致身份验证失败。 +一周后令牌过期,用户将不再被授权,需要重新登录以获取新令牌。而如果用户(或第三方)尝试修改令牌来更改过期时间,你也能发现,因为签名将不匹配。 -如需深入了解 JWT 令牌,了解它的工作方式,请参阅 https://jwt.io。 +如果你想动手体验 JWT 令牌并了解它的工作方式,请访问 https://jwt.io。 -## 安装 `PyJWT` +## 安装 `PyJWT` { #install-pyjwt } -安装 `PyJWT`,在 Python 中生成和校验 JWT 令牌: +我们需要安装 `PyJWT`,以便在 Python 中生成和校验 JWT 令牌。 + +请确保创建并激活一个[虚拟环境](../../virtual-environments.md){.internal-link target=_blank},然后安装 `pyjwt`:
@@ -40,42 +42,42 @@ $ pip install pyjwt
-/// info | 说明 +/// info | 信息 -如果您打算使用类似 RSA 或 ECDSA 的数字签名算法,您应该安装加密库依赖项 `pyjwt[crypto]`。 +如果你计划使用类似 RSA 或 ECDSA 的数字签名算法,你应该安装加密库依赖项 `pyjwt[crypto]`。 -您可以在 PyJWT Installation docs 获得更多信息。 +可以在 PyJWT 安装文档中了解更多。 /// -## 密码哈希 +## 密码哈希 { #password-hashing } -**哈希**是指把特定内容(本例中为密码)转换为乱码形式的字节序列(其实就是字符串)。 +“哈希”是指把一些内容(这里是密码)转换成看起来像乱码的一串字节(其实就是字符串)。 -每次传入完全相同的内容时(比如,完全相同的密码),返回的都是完全相同的乱码。 +当你每次传入完全相同的内容(完全相同的密码)时,都会得到完全相同的“乱码”。 -但这个乱码无法转换回传入的密码。 +但你无法从这个“乱码”反向还原出密码。 -### 为什么使用密码哈希 +### 为什么使用密码哈希 { #why-use-password-hashing } -原因很简单,假如数据库被盗,窃贼无法获取用户的明文密码,得到的只是哈希值。 +如果你的数据库被盗,窃贼拿到的不会是用户的明文密码,而只是哈希值。 -这样一来,窃贼就无法在其它应用中使用窃取的密码(要知道,很多用户在所有系统中都使用相同的密码,风险超大)。 +因此,窃贼无法把该密码拿去尝试登录另一个系统(很多用户在各处都用相同的密码,这将非常危险)。 -## 安装 `passlib` +## 安装 `pwdlib` { #install-pwdlib } -Passlib 是处理密码哈希的 Python 包。 +pwdlib 是一个用于处理密码哈希的优秀 Python 包。 -它支持很多安全哈希算法及配套工具。 +它支持多种安全的哈希算法以及相关工具。 -本教程推荐的算法是 **Bcrypt**。 +推荐的算法是 “Argon2”。 -因此,请先安装附带 Bcrypt 的 PassLib: +请确保创建并激活一个[虚拟环境](../../virtual-environments.md){.internal-link target=_blank},然后安装带 Argon2 的 pwdlib:
```console -$ pip install passlib[bcrypt] +$ pip install "pwdlib[argon2]" ---> 100% ``` @@ -84,51 +86,51 @@ $ pip install passlib[bcrypt] /// tip | 提示 -`passlib` 甚至可以读取 Django、Flask 的安全插件等工具创建的密码。 +使用 `pwdlib`,你甚至可以把它配置为能够读取由 **Django**、**Flask** 安全插件或其他许多工具创建的密码。 -例如,把 Django 应用的数据共享给 FastAPI 应用的数据库。或利用同一个数据库,可以逐步把应用从 Django 迁移到 FastAPI。 +例如,你可以在数据库中让一个 Django 应用和一个 FastAPI 应用共享同一份数据。或者在使用同一个数据库的前提下,逐步迁移一个 Django 应用到 FastAPI。 -并且,用户可以同时从 Django 应用或 FastAPI 应用登录。 +同时,你的用户既可以从 Django 应用登录,也可以从 **FastAPI** 应用登录。 /// -## 密码哈希与校验 +## 哈希并校验密码 { #hash-and-verify-the-passwords } -从 `passlib` 导入所需工具。 +从 `pwdlib` 导入所需工具。 -创建用于密码哈希和身份校验的 PassLib **上下文**。 +用推荐设置创建一个 PasswordHash 实例——它将用于哈希与校验密码。 /// tip | 提示 -PassLib 上下文还支持使用不同哈希算法的功能,包括只能校验的已弃用旧算法等。 +pwdlib 也支持 bcrypt 哈希算法,但不包含遗留算法——如果需要处理过时的哈希,建议使用 passlib 库。 -例如,用它读取和校验其它系统(如 Django)生成的密码,但要使用其它算法,如 Bcrypt,生成新的哈希密码。 +例如,你可以用它读取并校验其他系统(如 Django)生成的密码,但对任何新密码使用不同的算法(如 Argon2 或 Bcrypt)进行哈希。 -同时,这些功能都是兼容的。 +并且能够同时与它们全部兼容。 /// -接下来,创建三个工具函数,其中一个函数用于哈希用户的密码。 +创建一个工具函数来哈希用户传入的密码。 -第一个函数用于校验接收的密码是否匹配存储的哈希值。 +再创建一个工具函数来校验接收的密码是否匹配已存储的哈希。 -第三个函数用于身份验证,并返回用户。 +再创建一个工具函数来进行身份验证并返回用户。 {* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} -/// note | 笔记 +/// note | 注意 -查看新的(伪)数据库 `fake_users_db`,就能看到哈希后的密码:`"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`。 +如果你查看新的(伪)数据库 `fake_users_db`,现在你会看到哈希后的密码类似这样:`"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`。 /// -## 处理 JWT 令牌 +## 处理 JWT 令牌 { #handle-jwt-tokens } 导入已安装的模块。 -创建用于 JWT 令牌签名的随机密钥。 +创建一个用于对 JWT 令牌进行签名的随机密钥。 -使用以下命令,生成安全的随机密钥: +使用下列命令生成一个安全的随机密钥:
@@ -140,81 +142,82 @@ $ openssl rand -hex 32
-然后,把生成的密钥复制到变量**SECRET_KEY**,注意,不要使用本例所示的密钥。 +把输出复制到变量 `SECRET_KEY`(不要使用示例中的那个)。 -创建指定 JWT 令牌签名算法的变量 **ALGORITHM**,本例中的值为 `"HS256"`。 +创建变量 `ALGORITHM`,设置用于签名 JWT 令牌的算法,这里设为 `"HS256"`。 -创建设置令牌过期时间的变量。 +创建一个变量用于设置令牌的过期时间。 -定义令牌端点响应的 Pydantic 模型。 +定义一个用于令牌端点响应的 Pydantic 模型。 -创建生成新的访问令牌的工具函数。 - -{* ../../docs_src/security/tutorial004.py hl[6,12:14,28:30,78:86] *} - -## 更新依赖项 - -更新 `get_current_user` 以接收与之前相同的令牌,但这里用的是 JWT 令牌。 - -解码并校验接收到的令牌,然后,返回当前用户。 - -如果令牌无效,则直接返回 HTTP 错误。 +创建一个生成新访问令牌的工具函数。 {* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} -## 更新 `/token` *路径操作* +## 更新依赖项 { #update-the-dependencies } -用令牌过期时间创建 `timedelta` 对象。 +更新 `get_current_user` 以接收与之前相同的令牌,但这次使用的是 JWT 令牌。 -创建并返回真正的 JWT 访问令牌。 +解码接收到的令牌,进行校验,并返回当前用户。 + +如果令牌无效,立即返回一个 HTTP 错误。 + +{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} + +## 更新 `/token` 路径操作 { #update-the-token-path-operation } + +用令牌的过期时间创建一个 `timedelta`。 + +创建一个真正的 JWT 访问令牌并返回它。 {* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} -### JWT `sub` 的技术细节 +### 关于 JWT “主题” `sub` 的技术细节 { #technical-details-about-the-jwt-subject-sub } -JWT 规范还包括 `sub` 键,值是令牌的主题。 +JWT 规范中有一个 `sub` 键,表示令牌的“主题”(subject)。 -该键是可选的,但要把用户标识放在这个键里,所以本例使用了该键。 +使用它是可选的,但通常会把用户的标识放在这里,所以本例中我们使用它。 -除了识别用户与许可用户在 API 上直接执行操作之外,JWT 还可能用于其它事情。 +JWT 除了用于识别用户并允许其直接在你的 API 上执行操作之外,还可能用于其他场景。 -例如,识别**汽车**或**博客**。 +例如,你可以用它来标识一辆“车”或一篇“博客文章”。 -接着,为实体添加权限,比如**驾驶**(汽车)或**编辑**(博客)。 +然后你可以为该实体添加权限,比如“drive”(用于车)或“edit”(用于博客)。 -然后,把 JWT 令牌交给用户(或机器人),他们就可以执行驾驶汽车,或编辑博客等操作。无需注册账户,只要有 API 生成的 JWT 令牌就可以。 +接着,你可以把这个 JWT 令牌交给一个用户(或机器人),他们就可以在没有账户的前提下,仅凭你的 API 生成的 JWT 令牌来执行这些操作(开车、编辑文章)。 -同理,JWT 可以用于更复杂的场景。 +基于这些想法,JWT 可以用于更复杂的场景。 -在这些情况下,多个实体的 ID 可能是相同的,以 ID `foo` 为例,用户的 ID 是 `foo`,车的 ID 是 `foo`,博客的 ID 也是 `foo`。 +在这些情况下,多个实体可能会有相同的 ID,比如都叫 `foo`(用户 `foo`、车 `foo`、博客文章 `foo`)。 -为了避免 ID 冲突,在给用户创建 JWT 令牌时,可以为 `sub` 键的值加上前缀,例如 `username:`。因此,在本例中,`sub` 的值可以是:`username:johndoe`。 +因此,为了避免 ID 冲突,在为用户创建 JWT 令牌时,你可以给 `sub` 键的值加一个前缀,例如 `username:`。所以在这个例子中,`sub` 的值可以是:`username:johndoe`。 -注意,划重点,`sub` 键在整个应用中应该只有一个唯一的标识符,而且应该是字符串。 +需要牢记的一点是,`sub` 键在整个应用中应该是一个唯一标识符,并且它应该是字符串。 -## 检查 +## 检查 { #check-it } -运行服务器并访问文档: http://127.0.0.1:8000/docs。 +运行服务器并打开文档:http://127.0.0.1:8000/docs。 -可以看到如下用户界面: +你会看到这样的用户界面: - + -用与上一章同样的方式实现应用授权。 +像之前一样进行授权。 -使用如下凭证: +使用以下凭证: -用户名: `johndoe` 密码: `secret` +用户名: `johndoe` +密码: `secret` /// check | 检查 -注意,代码中没有明文密码**`secret`**,只保存了它的哈希值。 +注意,代码中的任何地方都没有明文密码 “`secret`”,我们只有它的哈希版本。 /// - + -调用 `/users/me/` 端点,收到下面的响应: +调用 `/users/me/` 端点,你将得到如下响应: ```JSON { @@ -225,46 +228,46 @@ JWT 规范还包括 `sub` 键,值是令牌的主题。 } ``` - + -打开浏览器的开发者工具,查看数据是怎么发送的,而且数据里只包含了令牌,只有验证用户的第一个请求才发送密码,并获取访问令牌,但之后不会再发送密码: +如果你打开开发者工具,你会看到发送的数据只包含令牌。密码只会在第一个请求中用于认证用户并获取访问令牌,之后就不会再发送密码了: - + -/// note | 笔记 +/// note | 注意 -注意,请求中 `Authorization` 响应头的值以 `Bearer` 开头。 +注意 `Authorization` 请求头,其值以 `Bearer ` 开头。 /// -## `scopes` 高级用法 +## 使用 `scopes` 的高级用法 { #advanced-usage-with-scopes } -OAuth2 支持**`scopes`**(作用域)。 +OAuth2 支持 “scopes”(作用域)。 -**`scopes`**为 JWT 令牌添加指定权限。 +你可以用它们为 JWT 令牌添加一组特定的权限。 -让持有令牌的用户或第三方在指定限制条件下与 API 交互。 +然后你可以把这个令牌直接交给用户或第三方,在一组限制条件下与 API 交互。 -**高级用户指南**中将介绍如何使用 `scopes`,及如何把 `scopes` 集成至 **FastAPI**。 +在**高级用户指南**中你将学习如何使用它们,以及它们如何集成进 **FastAPI**。 -## 小结 +## 小结 { #recap } -至此,您可以使用 OAuth2 和 JWT 等标准配置安全的 **FastAPI** 应用。 +通过目前所学内容,你可以使用 OAuth2 和 JWT 等标准来搭建一个安全的 **FastAPI** 应用。 -几乎在所有框架中,处理安全问题很快都会变得非常复杂。 +在几乎任何框架中,处理安全问题都会很快变得相当复杂。 -有些包为了简化安全流,不得不在数据模型、数据库和功能上做出妥协。而有些过于简化的软件包其实存在了安全隐患。 +许多把安全流程大幅简化的包,往往要在数据模型、数据库和可用特性上做出大量妥协。而有些过度简化的包实际上在底层存在安全隐患。 --- -**FastAPI** 不向任何数据库、数据模型或工具做妥协。 +**FastAPI** 不会在任何数据库、数据模型或工具上做妥协。 -开发者可以灵活选择最适合项目的安全机制。 +它给予你完全的灵活性,选择最适合你项目的方案。 -还可以直接使用 `passlib` 和 `PyJWT` 等维护良好、使用广泛的包,这是因为 **FastAPI** 不需要任何复杂机制,就能集成外部的包。 +而且你可以直接使用许多维护良好、广泛使用的包,比如 `pwdlib` 和 `PyJWT`,因为 **FastAPI** 不需要复杂机制来集成外部包。 -而且,**FastAPI** 还提供了一些工具,在不影响灵活、稳定和安全的前提下,尽可能地简化安全机制。 +同时它也为你提供尽可能简化流程的工具,而不牺牲灵活性、健壮性或安全性。 -**FastAPI** 还支持以相对简单的方式,使用 OAuth2 等安全、标准的协议。 +你可以以相对简单的方式使用和实现像 OAuth2 这样的安全、标准协议。 -**高级用户指南**中详细介绍了 OAuth2**`scopes`**的内容,遵循同样的标准,实现更精密的权限系统。OAuth2 的作用域是脸书、谷歌、GitHub、微软、推特等第三方身份验证应用使用的机制,让用户授权第三方应用与 API 交互。 +在**高级用户指南**中,你可以进一步了解如何使用 OAuth2 的 “scopes”,以遵循相同标准实现更细粒度的权限系统。带作用域的 OAuth2 是许多大型身份认证提供商(如 Facebook、Google、GitHub、Microsoft、X(Twitter)等)用来授权第三方应用代表其用户与其 API 交互的机制。 diff --git a/docs/zh/docs/tutorial/security/simple-oauth2.md b/docs/zh/docs/tutorial/security/simple-oauth2.md index f70678df8..3037a983b 100644 --- a/docs/zh/docs/tutorial/security/simple-oauth2.md +++ b/docs/zh/docs/tutorial/security/simple-oauth2.md @@ -1,8 +1,8 @@ -# OAuth2 实现简单的 Password 和 Bearer 验证 +# OAuth2 实现简单的 Password 和 Bearer 验证 { #simple-oauth2-with-password-and-bearer } 本章添加上一章示例中欠缺的部分,实现完整的安全流。 -## 获取 `username` 和 `password` +## 获取 `username` 和 `password` { #get-the-username-and-password } 首先,使用 **FastAPI** 安全工具获取 `username` 和 `password`。 @@ -18,7 +18,7 @@ OAuth2 规范要求使用**密码流**时,客户端或用户必须以表单数 该规范要求必须以表单数据形式发送 `username` 和 `password`,因此,不能使用 JSON 对象。 -### `Scope`(作用域) +### `scope` { #scope } OAuth2 还支持客户端发送**`scope`**表单字段。 @@ -32,7 +32,7 @@ OAuth2 还支持客户端发送**`scope`**表单字段。 * 脸书和 Instagram 使用 `instagram_basic` * 谷歌使用 `https://www.googleapis.com/auth/drive` -/// info | 说明 +/// info | 信息 OAuth2 中,**作用域**只是声明指定权限的字符串。 @@ -44,15 +44,15 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 /// -## 获取 `username` 和 `password` 的代码 +## 获取 `username` 和 `password` 的代码 { #code-to-get-the-username-and-password } 接下来,使用 **FastAPI** 工具获取用户名与密码。 -### `OAuth2PasswordRequestForm` +### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform } 首先,导入 `OAuth2PasswordRequestForm`,然后,在 `/token` *路径操作* 中,用 `Depends` 把该类作为依赖项。 -{* ../../docs_src/security/tutorial003.py hl[4,76] *} +{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *} `OAuth2PasswordRequestForm` 是用以下几项内容声明表单请求体的类依赖项: @@ -72,7 +72,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 * 可选的 `client_id`(本例未使用) * 可选的 `client_secret`(本例未使用) -/// info | 说明 +/// info | 信息 `OAuth2PasswordRequestForm` 与 `OAuth2PasswordBearer` 一样,都不是 FastAPI 的特殊类。 @@ -84,7 +84,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 /// -### 使用表单数据 +### 使用表单数据 { #use-the-form-data } /// tip | 提示 @@ -100,9 +100,9 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 本例使用 `HTTPException` 异常显示此错误: -{* ../../docs_src/security/tutorial003.py hl[3,77:79] *} +{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *} -### 校验密码 +### 校验密码 { #check-the-password } 至此,我们已经从数据库中获取了用户数据,但尚未校验密码。 @@ -112,7 +112,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 如果密码不匹配,则返回与上面相同的错误。 -#### 密码哈希 +#### 密码哈希 { #password-hashing } **哈希**是指,将指定内容(本例中为密码)转换为形似乱码的字节序列(其实就是字符串)。 @@ -120,15 +120,15 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 但这个乱码无法转换回传入的密码。 -##### 为什么使用密码哈希 +##### 为什么使用密码哈希 { #why-use-password-hashing } 原因很简单,假如数据库被盗,窃贼无法获取用户的明文密码,得到的只是哈希值。 这样一来,窃贼就无法在其它应用中使用窃取的密码,要知道,很多用户在所有系统中都使用相同的密码,风险超大。 -{* ../../docs_src/security/tutorial003.py hl[80:83] *} +{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *} -#### 关于 `**user_dict` +#### 关于 `**user_dict` { #about-user-dict } `UserInDB(**user_dict)` 是指: @@ -144,13 +144,13 @@ UserInDB( ) ``` -/// info | 说明 +/// info | 信息 -`user_dict` 的说明,详见[**更多模型**一章](../extra-models.md#user_indict){.internal-link target=_blank}。 +`user_dict` 的说明,详见[**更多模型**一章](../extra-models.md#about-user-in-dict){.internal-link target=_blank}。 /// -## 返回 Token +## 返回 Token { #return-the-token } `token` 端点的响应必须是 JSON 对象。 @@ -162,13 +162,13 @@ UserInDB( /// tip | 提示 -下一章介绍使用哈希密码和 JWT Token 的真正安全机制。 +下一章介绍使用哈希密码和 JWT Token 的真正安全机制。 但现在,仅关注所需的特定细节。 /// -{* ../../docs_src/security/tutorial003.py hl[85] *} +{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *} /// tip | 提示 @@ -182,7 +182,7 @@ UserInDB( /// -## 更新依赖项 +## 更新依赖项 { #update-the-dependencies } 接下来,更新依赖项。 @@ -194,9 +194,9 @@ UserInDB( 因此,在端点中,只有当用户存在、通过身份验证、且状态为激活时,才能获得该用户: -{* ../../docs_src/security/tutorial003.py hl[58:67,69:72,90] *} +{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *} -/// info | 说明 +/// info | 信息 此处返回值为 `Bearer` 的响应头 `WWW-Authenticate` 也是规范的一部分。 @@ -210,15 +210,15 @@ UserInDB( 说不定什么时候,就有工具用得上它,而且,开发者或用户也可能用得上。 -这就是遵循标准的好处…… +这就是遵循标准的好处... /// -## 实际效果 +## 实际效果 { #see-it-in-action } 打开 API 文档:http://127.0.0.1:8000/docs。 -### 身份验证 +### 身份验证 { #authenticate } 点击**Authorize**按钮。 @@ -228,13 +228,13 @@ UserInDB( 密码:`secret` - + 通过身份验证后,显示下图所示的内容: - + -### 获取当前用户数据 +### 获取当前用户数据 { #get-your-own-user-data } 使用 `/users/me` 路径的 `GET` 操作。 @@ -250,7 +250,7 @@ UserInDB( } ``` - + 点击小锁图标,注销后,再执行同样的操作,则会得到 HTTP 401 错误: @@ -260,7 +260,7 @@ UserInDB( } ``` -### 未激活用户 +### 未激活用户 { #inactive-user } 测试未激活用户,输入以下信息,进行身份验证: @@ -278,7 +278,7 @@ UserInDB( } ``` -## 小结 +## 小结 { #recap } 使用本章的工具实现基于 `username` 和 `password` 的完整 API 安全系统。 @@ -286,4 +286,4 @@ UserInDB( 唯一欠缺的是,它仍然不是真的**安全**。 -下一章,介绍使用密码哈希支持库与 JWT 令牌实现真正的安全机制。 +下一章,介绍使用密码哈希支持库与 JWT 令牌实现真正的安全机制。 diff --git a/docs/zh/docs/tutorial/sql-databases.md b/docs/zh/docs/tutorial/sql-databases.md index fbdf3be6c..944e960a7 100644 --- a/docs/zh/docs/tutorial/sql-databases.md +++ b/docs/zh/docs/tutorial/sql-databases.md @@ -1,40 +1,40 @@ -# SQL(关系型)数据库 +# SQL(关系型)数据库 { #sql-relational-databases } -**FastAPI** 并不要求您使用 SQL(关系型)数据库。您可以使用**任何**想用的数据库。 +**FastAPI** 并不要求你使用 SQL(关系型)数据库。你可以使用你想用的**任何数据库**。 这里,我们来看一个使用 SQLModel 的示例。 -**SQLModel** 是基于 SQLAlchemy 和 Pydantic 构建的。它由 **FastAPI** 的同一作者制作,旨在完美匹配需要使用 **SQL 数据库**的 FastAPI 应用程序。 +**SQLModel** 基于 SQLAlchemy 和 Pydantic 构建。它由 **FastAPI** 的同一作者制作,旨在完美匹配需要使用**SQL 数据库**的 FastAPI 应用程序。 -/// tip +/// tip | 提示 -您可以使用任何其他您想要的 SQL 或 NoSQL 数据库(在某些情况下称为 “ORM”),FastAPI 不会强迫您使用任何东西。😎 +你可以使用任意其他你想要的 SQL 或 NoSQL 数据库库(在某些情况下称为 "ORMs"),FastAPI 不会强迫你使用任何东西。😎 /// -由于 SQLModel 基于 SQLAlchemy,因此您可以轻松使用任何由 SQLAlchemy **支持的数据库**(这也让它们被 SQLModel 支持),例如: +由于 SQLModel 基于 SQLAlchemy,因此你可以轻松使用任何由 SQLAlchemy **支持的数据库**(这也让它们被 SQLModel 支持),例如: * PostgreSQL * MySQL * SQLite * Oracle -* Microsoft SQL Server 等. +* Microsoft SQL Server 等 -在这个例子中,我们将使用 **SQLite**,因为它使用单个文件,并且 Python 对其有集成支持。因此,您可以直接复制这个例子并运行。 +在这个示例中,我们将使用 **SQLite**,因为它使用单个文件,并且 Python 对其有集成支持。因此,你可以直接复制这个示例并运行。 -之后,对于您的生产应用程序,您可能会想要使用像 PostgreSQL 这样的数据库服务器。 +之后,对于你的生产应用程序,你可能会想要使用像 **PostgreSQL** 这样的数据库服务器。 -/// tip +/// tip | 提示 -有一个使用 **FastAPI** 和 **PostgreSQL** 的官方的项目生成器,其中包括了前端和更多工具: https://github.com/fastapi/full-stack-fastapi-template +有一个使用 **FastAPI** 和 **PostgreSQL** 的官方项目生成器,其中包括了前端和更多工具: https://github.com/fastapi/full-stack-fastapi-template /// -这是一个非常简单和简短的教程。如果您想了解一般的数据库、SQL 或更高级的功能,请查看 SQLModel 文档。 +这是一个非常简单和简短的教程。如果你想了解一般的数据库、SQL 或更高级的功能,请查看 SQLModel 文档。 -## 安装 `SQLModel` +## 安装 `SQLModel` { #install-sqlmodel } -首先,确保您创建并激活了[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装了 `sqlmodel` : +首先,确保你创建并激活了[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后安装 `sqlmodel`:
@@ -45,61 +45,61 @@ $ pip install sqlmodel
-## 创建含有单一模型的应用程序 +## 创建含有单一模型的应用 { #create-the-app-with-a-single-model } -我们首先创建应用程序的最简单的第一个版本,只有一个 **SQLModel** 模型。 +我们先创建应用的最简单的第一个版本,只有一个 **SQLModel** 模型。 稍后我们将通过下面的**多个模型**提高其安全性和多功能性。🤓 -### 创建模型 +### 创建模型 { #create-models } 导入 `SQLModel` 并创建一个数据库模型: {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} -`Hero` 类与 Pydantic 模型非常相似(实际上,从底层来看,它确实*就是一个 Pydantic 模型*)。 +`Hero` 类与 Pydantic 模型非常相似(实际上,从底层来看,它确实就是一个 Pydantic 模型)。 有一些区别: -* `table=True` 会告诉 SQLModel 这是一个*表模型*,它应该表示 SQL 数据库中的一个*表*,而不仅仅是一个*数据模型*(就像其他常规的 Pydantic 类一样)。 +* `table=True` 会告诉 SQLModel 这是一个*表模型*,它应该表示 SQL 数据库中的一个**表**,而不仅仅是一个*数据模型*(就像其他常规的 Pydantic 类一样)。 -* `Field(primary_key=True)` 会告诉 SQLModel `id` 是 SQL 数据库中的**主键**(您可以在 SQLModel 文档中了解更多关于 SQL 主键的信息)。 +* `Field(primary_key=True)` 会告诉 SQLModel `id` 是 SQL 数据库中的**主键**(你可以在 SQLModel 文档中了解更多关于 SQL 主键的信息)。 - 把类型设置为 `int | None` ,SQLModel 就能知道该列在 SQL 数据库中应该是 `INTEGER` 类型,并且应该是 `NULLABLE` 。 + **注意:** 我们为主键字段使用 `int | None`,这样在 Python 代码中我们可以在没有 `id`(`id=None`)的情况下创建对象,并假定数据库在保存时会生成它。SQLModel 会理解数据库会提供 `id`,并在数据库模式中将该列定义为非空的 `INTEGER`。详见 SQLModel 关于主键的文档。 * `Field(index=True)` 会告诉 SQLModel 应该为此列创建一个 **SQL 索引**,这样在读取按此列过滤的数据时,程序能在数据库中进行更快的查找。 - SQLModel 会知道声明为 `str` 的内容将是类型为 `TEXT` (或 `VARCHAR` ,具体取决于数据库)的 SQL 列。 + SQLModel 会知道声明为 `str` 的内容将是类型为 `TEXT`(或 `VARCHAR`,具体取决于数据库)的 SQL 列。 -### 创建引擎(Engine) +### 创建引擎(Engine) { #create-an-engine } -SQLModel 的引擎 `engine`(实际上它是一个 SQLAlchemy `engine` )是用来与数据库**保持连接**的。 +SQLModel 的 `engine`(实际上它是一个 SQLAlchemy 的 `engine`)是用来与数据库**保持连接**的。 -您只需构建**一个 `engine`**,来让您的所有代码连接到同一个数据库。 +你只需构建**一个 `engine` 对象**,让你的所有代码连接到同一个数据库。 {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} 使用 `check_same_thread=False` 可以让 FastAPI 在不同线程中使用同一个 SQLite 数据库。这很有必要,因为**单个请求**可能会使用**多个线程**(例如在依赖项中)。 -不用担心,我们会按照代码结构确保**每个请求使用一个单独的 SQLModel *会话***,这实际上就是 `check_same_thread` 想要实现的。 +不用担心,我们会按照代码结构确保**每个请求使用一个单独的 SQLModel 会话(session)**,这实际上就是 `check_same_thread` 想要实现的。 -### 创建表 +### 创建表 { #create-the-tables } 然后,我们来添加一个函数,使用 `SQLModel.metadata.create_all(engine)` 为所有*表模型***创建表**。 {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} -### 创建会话(Session)依赖项 +### 创建会话(Session)依赖项 { #create-a-session-dependency } **`Session`** 会存储**内存中的对象**并跟踪数据中所需更改的内容,然后它**使用 `engine`** 与数据库进行通信。 -我们会使用 `yield` 创建一个 FastAPI **依赖项**,为每个请求提供一个新的 `Session` 。这确保我们每个请求使用一个单独的会话。🤓 +我们会使用 `yield` 创建一个 FastAPI **依赖项**,为每个请求提供一个新的 `Session`。这确保我们每个请求使用一个单独的会话。🤓 然后我们创建一个 `Annotated` 的依赖项 `SessionDep` 来简化其他也会用到此依赖的代码。 {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} -### 在启动时创建数据库表 +### 在启动时创建数据库表 { #create-database-tables-on-startup } 我们会在应用程序启动时创建数据库表。 @@ -107,49 +107,47 @@ SQLModel 的引擎 `engine`(实际上它是一个 SQLAlchemy `engine` )是 此处,在应用程序启动事件中,我们创建了表。 -而对于生产环境,您可能会用一个能够在启动应用程序之前运行的迁移脚本。🤓 +在生产环境中,你可能会使用一个在启动应用程序之前运行的迁移脚本。🤓 -/// tip +/// tip | 提示 -SQLModel 将会拥有封装 Alembic 的迁移工具,但目前您可以直接使用 Alembic。 +SQLModel 将会拥有封装 Alembic 的迁移工具,但目前你可以直接使用 Alembic。 /// -### 创建 Hero 类 +### 创建 Hero { #create-a-hero } -因为每个 SQLModel 模型同时也是一个 Pydantic 模型,所以您可以在与 Pydantic 模型相同的**类型注释**中使用它。 +因为每个 SQLModel 模型同时也是一个 Pydantic 模型,所以你可以在与 Pydantic 模型相同的**类型注解**中使用它。 -例如,如果您声明一个类型为 `Hero` 的参数,它将从 **JSON 主体**中读取数据。 +例如,如果你声明一个类型为 `Hero` 的参数,它将从 **JSON 主体**中读取数据。 -同样,您可以将其声明为函数的**返回类型**,然后数据的结构就会显示在自动生成的 API 文档界面中。 +同样,你可以将其声明为函数的**返回类型**,然后数据的结构就会显示在自动生成的 API 文档界面中。 {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} - +这里,我们使用 `SessionDep` 依赖项(一个 `Session`)将新的 `Hero` 添加到 `Session` 实例中,提交更改到数据库,刷新 `hero` 中的数据,并返回它。 -这里,我们使用 `SessionDep` 依赖项(一个 `Session` )将新的 `Hero` 添加到 `Session` 实例中,提交更改到数据库,刷新 hero 中的数据,并返回它。 +### 读取 Hero { #read-heroes } -### 读取 Hero 类 - -我们可以使用 `select()` 从数据库中**读取** `Hero` 类,并利用 `limit` 和 `offset` 来对结果进行分页。 +我们可以使用 `select()` 从数据库中**读取** `Hero`,并利用 `limit` 和 `offset` 来对结果进行分页。 {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} -### 读取单个 Hero +### 读取单个 Hero { #read-one-hero } -我们可以**读取**单个 `Hero` 。 +我们可以**读取**单个 `Hero`。 {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} -### 删除单个 Hero +### 删除单个 Hero { #delete-a-hero } -我们也可以**删除**单个 `Hero` 。 +我们也可以**删除**一个 `Hero`。 {* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} -### 运行应用程序 +### 运行应用 { #run-the-app } -您可以运行这个应用程序: +你可以运行这个应用:
@@ -161,25 +159,25 @@ $ fastapi dev main.py
-然后在 `/docs` UI 中,您能够看到 **FastAPI** 会用这些**模型**来**记录** API,并且还会用它们来**序列化**和**验证**数据。 +然后在 `/docs` UI 中,你能够看到 **FastAPI** 会用这些**模型**来**记录** API,并且还会用它们来**序列化**和**验证**数据。
-## 更新应用程序以支持多个模型 +## 使用多个模型更新应用 { #update-the-app-with-multiple-models } 现在让我们稍微**重构**一下这个应用,以提高**安全性**和**多功能性**。 -如果您查看之前的应用程序,您可以在 UI 界面中看到,到目前为止,由客户端决定要创建的 `Hero` 的 `id` 值。😱 +如果你查看之前的应用程序,你可以在 UI 界面中看到,到目前为止,它允许客户端决定要创建的 `Hero` 的 `id`。😱 -我们不应该允许这样做,因为他们可能会覆盖我们在数据库中已经分配的 `id` 。决定 `id` 的行为应该由**后端**或**数据库**来完成,**而非客户端**。 +我们不应该允许这样做,因为他们可能会覆盖我们在数据库中已经分配的 `id`。决定 `id` 的行为应该由**后端**或**数据库**来完成,**而非客户端**。 -此外,我们为 hero 创建了一个 `secret_name` ,但到目前为止,我们在各处都返回了它,这就不太**秘密**了……😅 +此外,我们为 hero 创建了一个 `secret_name`,但到目前为止,我们在各处都返回了它,这就不太**秘密**了……😅 我们将通过添加一些**额外的模型**来解决这些问题,而 SQLModel 将在这里大放异彩。✨ -### 创建多个模型 +### 创建多个模型 { #create-multiple-models } 在 **SQLModel** 中,任何含有 `table=True` 属性的模型类都是一个**表模型**。 @@ -187,7 +185,7 @@ $ fastapi dev main.py 有了 SQLModel,我们就可以利用**继承**来在所有情况下**避免重复**所有字段。 -#### `HeroBase` - 基类 +#### `HeroBase` - 基类 { #herobase-the-base-class } 我们从一个 `HeroBase` 模型开始,该模型具有所有模型**共享的字段**: @@ -196,14 +194,14 @@ $ fastapi dev main.py {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} -#### `Hero` - *表模型* +#### `Hero` - *表模型* { #hero-the-table-model } -接下来,我们创建 `Hero` ,实际的*表模型*,并添加那些不总是在其他模型中的**额外字段**: +接下来,我们创建 `Hero`,实际的*表模型*,并添加那些不总是在其他模型中的**额外字段**: * `id` * `secret_name` -因为 `Hero` 继承自 HeroBase ,所以它**也**包含了在 `HeroBase` 中声明过的**字段**。因此 `Hero` 的所有字段为: +因为 `Hero` 继承自 `HeroBase`,所以它**也**包含了在 `HeroBase` 中声明过的**字段**。因此 `Hero` 的所有字段为: * `id` * `name` @@ -212,46 +210,45 @@ $ fastapi dev main.py {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} -#### `HeroPublic` - 公共*数据模型* +#### `HeroPublic` - 公共*数据模型* { #heropublic-the-public-data-model } 接下来,我们创建一个 `HeroPublic` 模型,这是将**返回**给 API 客户端的模型。 -它包含与 `HeroBase` 相同的字段,因此不会包括 `secret_name` 。 +它包含与 `HeroBase` 相同的字段,因此不会包括 `secret_name`。 -终于,我们英雄(hero)的身份得到了保护! 🥷 +终于,我们英雄(hero)的身份得到了保护!🥷 -它还重新声明了 `id: int` 。这样我们便与 API 客户端建立了一种**约定**,使他们始终可以期待 `id` 存在并且是一个整数 `int`(永远不会是 `None` )。 +它还重新声明了 `id: int`。这样我们便与 API 客户端建立了一种**约定**,使他们始终可以期待 `id` 存在并且是一个整数 `int`(永远不会是 `None`)。 -/// tip +/// tip | 提示 -确保返回模型始终提供一个值并且始终是 `int` (而不是 `None` )对 API 客户端非常有用,他们可以在这种确定性下编写更简单的代码。 +确保返回模型始终提供一个值并且始终是 `int`(而不是 `None`)对 API 客户端非常有用,他们可以在这种确定性下编写更简单的代码。 -此外,**自动生成的客户端**将拥有更简洁的接口,这样与您的 API 交互的开发者就能更轻松地使用您的 API。😎 +此外,**自动生成的客户端**将拥有更简洁的接口,这样与你的 API 交互的开发者就能更轻松地使用你的 API。😎 /// -`HeroPublic` 中的所有字段都与 `HeroBase` 中的相同,其中 `id` 声明为 `int` (不是 `None` ): +`HeroPublic` 中的所有字段都与 `HeroBase` 中的相同,其中 `id` 声明为 `int`(不是 `None`): * `id` * `name` * `age` -* `secret_name` {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} -#### `HeroCreate` - 用于创建 hero 的*数据模型* +#### `HeroCreate` - 用于创建 hero 的*数据模型* { #herocreate-the-data-model-to-create-a-hero } -现在我们创建一个 `HeroCreate` 模型,这是用于**验证**客户数据的模型。 +现在我们创建一个 `HeroCreate` 模型,这是用于**验证**客户端数据的模型。 -它不仅拥有与 `HeroBase` 相同的字段,还有 `secret_name` 。 +它不仅拥有与 `HeroBase` 相同的字段,还有 `secret_name`。 -现在,当客户端**创建一个新的 hero** 时,他们会发送 `secret_name` ,它会被存储到数据库中,但这些 `secret_name` 不会通过 API 返回给客户端。 +现在,当客户端**创建一个新的 hero** 时,他们会发送 `secret_name`,它会被存储到数据库中,但这些 `secret_name` 不会通过 API 返回给客户端。 -/// tip +/// tip | 提示 这应当是**密码**被处理的方式:接收密码,但不要通过 API 返回它们。 -在存储密码之前,您还应该对密码的值进行**哈希**处理,**绝不要以明文形式存储它们**。 +在存储密码之前,你还应该对密码的值进行**哈希**处理,**绝不要以明文形式存储它们**。 /// @@ -263,17 +260,17 @@ $ fastapi dev main.py {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} -#### `HeroUpdate` - 用于更新 hero 的*数据模型* +#### `HeroUpdate` - 用于更新 hero 的*数据模型* { #heroupdate-the-data-model-to-update-a-hero } 在之前的应用程序中,我们没有办法**更新 hero**,但现在有了**多个模型**,我们便能做到这一点了。🎉 -`HeroUpdate` *数据模型*有些特殊,它包含创建新 hero 所需的**所有相同字段**,但所有字段都是**可选的**(它们都有默认值)。这样,当您更新一个 hero 时,您可以只发送您想要更新的字段。 +`HeroUpdate` *数据模型*有些特殊,它包含创建新 hero 所需的**所有相同字段**,但所有字段都是**可选的**(它们都有默认值)。这样,当你更新一个 hero 时,你可以只发送你想要更新的字段。 -因为所有**字段实际上**都发生了**变化**(类型现在包括 `None` ,并且它们现在有一个默认值 `None` ),我们需要**重新声明**它们。 +因为所有**字段实际上**都发生了**变化**(类型现在包括 `None`,并且它们现在有一个默认值 `None`),我们需要**重新声明**它们。 -我们会重新声明所有字段,因此我们并不是真的需要从 `HeroBase` 继承。我会让它继承只是为了保持一致,但这并不必要。这更多是个人喜好的问题。🤷 +我们并不真的需要从 `HeroBase` 继承,因为我们会重新声明所有字段。我会让它继承只是为了保持一致,但这并不必要。这更多是个人喜好的问题。🤷 -`HeroUpdate` 的字段包括: +`HeroUpdate` 的字段包括: * `name` * `age` @@ -281,51 +278,51 @@ $ fastapi dev main.py {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} -### 使用 `HeroCreate` 创建并返回 `HeroPublic` +### 使用 `HeroCreate` 创建并返回 `HeroPublic` { #create-with-herocreate-and-return-a-heropublic } 既然我们有了**多个模型**,我们就可以对使用它们的应用程序部分进行更新。 我们在请求中接收到一个 `HeroCreate` *数据模型*,然后从中创建一个 `Hero` *表模型*。 -这个新的*表模型* `Hero` 会包含客户端发送的字段,以及一个由数据库生成的 `id` 。 +这个新的*表模型* `Hero` 会包含客户端发送的字段,以及一个由数据库生成的 `id`。 -然后我们将与函数中相同的*表模型* `Hero` 原样返回。但是由于我们使用 `HeroPublic` *数据模型*声明了 `response_model` ,**FastAPI** 会使用 `HeroPublic` 来验证和序列化数据。 +然后我们将与函数中相同的*表模型* `Hero` 原样返回。但是由于我们使用 `HeroPublic` *数据模型*声明了 `response_model`,**FastAPI** 会使用 `HeroPublic` 来验证和序列化数据。 {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} -/// tip +/// tip | 提示 -现在我们使用 `response_model=HeroPublic` 来代替**返回类型注释** `-> HeroPublic` ,因为我们返回的值实际上**并不是** `HeroPublic` 类型。 +现在我们使用 `response_model=HeroPublic` 来代替**返回类型注解** `-> HeroPublic`,因为我们返回的值实际上并不是 `HeroPublic`。 -如果我们声明了 `-> HeroPublic` ,您的编辑器和代码检查工具会抱怨(但也确实理所应当)您返回了一个 `Hero` 而不是一个 `HeroPublic` 。 +如果我们声明了 `-> HeroPublic`,你的编辑器和代码检查工具会(理所应当地)抱怨你返回了一个 `Hero` 而不是一个 `HeroPublic`。 通过 `response_model` 的声明,我们让 **FastAPI** 按照它自己的方式处理,而不会干扰类型注解以及编辑器和其他工具提供的帮助。 /// -### 用 `HeroPublic` 读取 Hero +### 使用 `HeroPublic` 读取 Hero { #read-heroes-with-heropublic } -我们可以像之前一样**读取** `Hero` 。同样,使用 `response_model=list[HeroPublic]` 确保正确地验证和序列化数据。 +我们可以像之前一样**读取** `Hero`,同样,使用 `response_model=list[HeroPublic]` 确保正确地验证和序列化数据。 {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} -### 用 `HeroPublic` 读取单个 Hero +### 使用 `HeroPublic` 读取单个 Hero { #read-one-hero-with-heropublic } -我们可以**读取**单个 `hero` 。 +我们可以**读取**单个 hero: {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} -### 用 `HeroUpdate` 更新单个 Hero +### 使用 `HeroUpdate` 更新单个 Hero { #update-a-hero-with-heroupdate } -我们可以**更新**单个 `hero` 。为此,我们会使用 HTTP 的 `PATCH` 操作。 +我们可以**更新**单个 hero。为此,我们会使用 HTTP 的 `PATCH` 操作。 -在代码中,我们会得到一个 `dict` ,其中包含客户端发送的所有数据,**只有客户端发送的数据**,并排除了任何一个仅仅作为默认值存在的值。为此,我们使用 `exclude_unset=True` 。这是最主要的技巧。🪄 +在代码中,我们会得到一个 `dict`,其中包含客户端发送的所有数据,**只有客户端发送的数据**,并排除了任何一个仅仅作为默认值存在的值。为此,我们使用 `exclude_unset=True`。这是最主要的技巧。🪄 -然后我们会使用 `hero_db.sqlmodel_update(hero_data)` ,来利用 `hero_data` 的数据更新 `hero_db` 。 +然后我们会使用 `hero_db.sqlmodel_update(hero_data)`,来利用 `hero_data` 的数据更新 `hero_db`。 {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} -### (又一次)删除单个 Hero +### (再次)删除单个 Hero { #delete-a-hero-again } **删除**一个 hero 基本保持不变。 @@ -333,9 +330,9 @@ $ fastapi dev main.py {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} -### (又一次)运行应用程序 +### (再次)运行应用 { #run-the-app-again } -您可以再运行一次应用程序: +你可以再运行一次应用程序:
@@ -347,14 +344,14 @@ $ fastapi dev main.py
-您会在 `/docs` API UI 看到它现在已经更新,并且在进行创建 hero 等操作时,它不会再期望从客户端接收 `id` 数据。 +如果你进入 `/docs` API UI,你会看到它现在已经更新,并且在创建 hero 时,它不会再期望从客户端接收 `id` 数据等。
-## 总结 +## 总结 { #recap } -您可以使用 **SQLModel** 与 SQL 数据库进行交互,并通过*数据模型*和*表模型*简化代码。 +你可以使用 **SQLModel** 与 SQL 数据库进行交互,并通过*数据模型*和*表模型*简化代码。 -您可以在 SQLModel 的文档中学习到更多内容,其中有一个更详细的关于如何将 SQLModel 与 FastAPI 一起使用的教程。🚀 +你可以在 **SQLModel** 文档中学习到更多内容,其中有一个更详细的将 SQLModel 与 **FastAPI** 一起使用的迷你教程。🚀 diff --git a/docs/zh/docs/tutorial/static-files.md b/docs/zh/docs/tutorial/static-files.md index 1a0d4504c..5b2f920b2 100644 --- a/docs/zh/docs/tutorial/static-files.md +++ b/docs/zh/docs/tutorial/static-files.md @@ -1,40 +1,40 @@ -# 静态文件 +# 静态文件 { #static-files } -您可以使用 `StaticFiles`从目录中自动提供静态文件。 +你可以使用 `StaticFiles` 从目录中自动提供静态文件。 -## 使用`StaticFiles` +## 使用 `StaticFiles` { #use-staticfiles } -* 导入`StaticFiles`。 -* "挂载"(Mount) 一个 `StaticFiles()` 实例到一个指定路径。 +* 导入 `StaticFiles`。 +* 将一个 `StaticFiles()` 实例“挂载”(Mount)到指定路径。 -{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} -/// note | 技术细节 +/// note | 注意 你也可以用 `from starlette.staticfiles import StaticFiles`。 -**FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles` ,只是为了方便你,开发者。但它确实来自Starlette。 +**FastAPI** 提供了和 `starlette.staticfiles` 相同的 `fastapi.staticfiles`,只是为了方便你这个开发者。但它确实直接来自 Starlette。 /// -### 什么是"挂载"(Mounting) +### 什么是“挂载”(Mounting) { #what-is-mounting } -"挂载" 表示在特定路径添加一个完全"独立的"应用,然后负责处理所有子路径。 +“挂载”表示在特定路径添加一个完全“独立”的应用,然后负责处理所有子路径。 -这与使用`APIRouter`不同,因为安装的应用程序是完全独立的。OpenAPI和来自你主应用的文档不会包含已挂载应用的任何东西等等。 +这与使用 `APIRouter` 不同,因为挂载的应用是完全独立的。主应用的 OpenAPI 和文档不会包含已挂载应用的任何内容,等等。 -你可以在**高级用户指南**中了解更多。 +你可以在[高级用户指南](../advanced/index.md){.internal-link target=_blank}中了解更多。 -## 细节 +## 细节 { #details } -这个 "子应用" 会被 "挂载" 到第一个 `"/static"` 指向的子路径。因此,任何以`"/static"`开头的路径都会被它处理。 +第一个 `"/static"` 指的是这个“子应用”将被“挂载”到的子路径。因此,任何以 `"/static"` 开头的路径都会由它处理。 - `directory="static"` 指向包含你的静态文件的目录名字。 +`directory="static"` 指的是包含你的静态文件的目录名称。 -`name="static"` 提供了一个能被**FastAPI**内部使用的名字。 +`name="static"` 为它提供了一个可被 **FastAPI** 内部使用的名称。 -所有这些参数可以不同于"`static`",根据你应用的需要和具体细节调整它们。 +这些参数都可以不是“`static`”,请根据你的应用需求和具体细节进行调整。 -## 更多信息 +## 更多信息 { #more-info } -更多细节和选择查阅 Starlette's docs about Static Files. +更多细节和选项请查阅 Starlette 的静态文件文档。 diff --git a/docs/zh/docs/tutorial/testing.md b/docs/zh/docs/tutorial/testing.md index 3877adbac..1aff9f799 100644 --- a/docs/zh/docs/tutorial/testing.md +++ b/docs/zh/docs/tutorial/testing.md @@ -1,4 +1,4 @@ -# 测试 +# 测试 { #testing } 感谢 Starlette,测试**FastAPI** 应用轻松又愉快。 @@ -6,17 +6,21 @@ 有了它,你可以直接与**FastAPI**一起使用 pytest。 -## 使用 `TestClient` +## 使用 `TestClient` { #using-testclient } /// info | 信息 -要使用 `TestClient`,先要安装 `httpx`. +要使用 `TestClient`,先要安装 `httpx`。 -例:`pip install httpx`. +确保你创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后再安装,例如: + +```console +$ pip install httpx +``` /// -导入 `TestClient`. +导入 `TestClient`。 通过传入你的**FastAPI**应用创建一个 `TestClient` 。 @@ -26,7 +30,7 @@ 为你需要检查的地方用标准的Python表达式写个简单的 `assert` 语句(重申,标准的`pytest`)。 -{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} /// tip | 提示 @@ -52,13 +56,13 @@ /// -## 分离测试 +## 分离测试 { #separating-tests } 在实际应用中,你可能会把你的测试放在另一个文件里。 您的**FastAPI**应用程序也可能由一些文件/模块组成等等。 -### **FastAPI** app 文件 +### **FastAPI** app 文件 { #fastapi-app-file } 假设你有一个像 [更大的应用](bigger-applications.md){.internal-link target=_blank} 中所描述的文件结构: @@ -72,9 +76,9 @@ 在 `main.py` 文件中你有一个 **FastAPI** app: -{* ../../docs_src/app_testing/main.py *} +{* ../../docs_src/app_testing/app_a_py39/main.py *} -### 测试文件 +### 测试文件 { #testing-file } 然后你会有一个包含测试的文件 `test_main.py` 。app可以像Python包那样存在(一样是目录,但有个 `__init__.py` 文件): @@ -88,15 +92,15 @@ 因为这文件在同一个包中,所以你可以通过相对导入从 `main` 模块(`main.py`)导入`app`对象: -{* ../../docs_src/app_testing/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} ...然后测试代码和之前一样的。 -## 测试:扩展示例 +## 测试:扩展示例 { #testing-extended-example } 现在让我们扩展这个例子,并添加更多细节,看下如何测试不同部分。 -### 扩展后的 **FastAPI** app 文件 +### 扩展后的 **FastAPI** app 文件 { #extended-fastapi-app-file } 让我们继续之前的文件结构: @@ -116,63 +120,13 @@ 所有*路径操作* 都需要一个`X-Token` 头。 -//// tab | Python 3.10+ +{* ../../docs_src/app_testing/app_b_an_py310/main.py *} -```Python -{!> ../../docs_src/app_testing/app_b_an_py310/main.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/app_testing/app_b_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/app_testing/app_b_an/main.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | 提示 - -Prefer to use the `Annotated` version if possible. - -/// - -```Python -{!> ../../docs_src/app_testing/app_b_py310/main.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | 提示 - -Prefer to use the `Annotated` version if possible. - -/// - -```Python -{!> ../../docs_src/app_testing/app_b/main.py!} -``` - -//// - -### 扩展后的测试文件 +### 扩展后的测试文件 { #extended-testing-file } 然后您可以使用扩展后的测试更新`test_main.py`: -{* ../../docs_src/app_testing/app_b/test_main.py *} +{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *} 每当你需要客户端在请求中传递信息,但你不知道如何传递时,你可以通过搜索(谷歌)如何用 `httpx`做,或者是用 `requests` 做,毕竟HTTPX的设计是基于Requests的设计的。 @@ -196,9 +150,11 @@ Prefer to use the `Annotated` version if possible. /// -## 运行起来 +## 运行起来 { #run-it } -之后,你只需要安装 `pytest`: +之后,你只需要安装 `pytest`。 + +确保你创建并激活一个[虚拟环境](../virtual-environments.md){.internal-link target=_blank},然后再安装,例如:
diff --git a/docs/zh/docs/virtual-environments.md b/docs/zh/docs/virtual-environments.md index 9b3c0340a..29e272b59 100644 --- a/docs/zh/docs/virtual-environments.md +++ b/docs/zh/docs/virtual-environments.md @@ -1,14 +1,14 @@ -# 虚拟环境 +# 虚拟环境 { #virtual-environments } 当你在 Python 工程中工作时,你可能会有必要用到一个**虚拟环境**(或类似的机制)来隔离你为每个工程安装的包。 -/// info +/// info | 信息 如果你已经了解虚拟环境,知道如何创建和使用它们,你可以考虑跳过这一部分。🤓 /// -/// tip +/// tip | 提示 **虚拟环境**和**环境变量**是不同的。 @@ -18,7 +18,7 @@ /// -/// info +/// info | 信息 这个页面将教你如何使用**虚拟环境**以及了解它们的工作原理。 @@ -26,7 +26,7 @@ /// -## 创建一个工程 +## 创建一个工程 { #create-a-project } 首先,为你的工程创建一个目录。 @@ -51,11 +51,11 @@ $ cd awesome-project
-## 创建一个虚拟环境 +## 创建一个虚拟环境 { #create-a-virtual-environment } 在开始一个 Python 工程的**第一时间**,**在你的工程内部**创建一个虚拟环境。 -/// tip +/// tip | 提示 你只需要 **在每个工程中操作一次**,而不是每次工作时都操作。 @@ -96,7 +96,7 @@ $ uv venv
-/// tip +/// tip | 提示 默认情况下,`uv` 会在一个名为 `.venv` 的目录中创建一个虚拟环境。 @@ -114,11 +114,11 @@ $ uv venv /// -## 激活虚拟环境 +## 激活虚拟环境 { #activate-the-virtual-environment } 激活新的虚拟环境来确保你运行的任何 Python 命令或安装的包都能使用到它。 -/// tip +/// tip | 提示 **每次**开始一个 **新的终端会话** 来工作在这个工程时,你都需要执行这个操作。 @@ -162,19 +162,19 @@ $ source .venv/Scripts/activate //// -/// tip +/// tip | 提示 每次你在这个环境中安装一个 **新的包** 时,都需要 **重新激活** 这个环境。 -这么做确保了当你使用一个由这个包安装的 **终端(CLI)程序** 时,你使用的是你的虚拟环境中的程序,而不是全局安装、可能版本不同的程序。 +这么做确保了当你使用一个由这个包安装的 **终端(CLI)程序** 时,你使用的是你的虚拟环境中的程序,而不是全局安装、可能版本不同的程序。 /// -## 检查虚拟环境是否激活 +## 检查虚拟环境是否激活 { #check-the-virtual-environment-is-active } 检查虚拟环境是否激活 (前面的命令是否生效)。 -/// tip +/// tip | 提示 这是 **可选的**,但这是一个很好的方法,可以 **检查** 一切是否按预期工作,以及你是否使用了你打算使用的虚拟环境。 @@ -212,9 +212,9 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python //// -## 升级 `pip` +## 升级 `pip` { #upgrade-pip } -/// tip +/// tip | 提示 如果你使用 `uv` 来安装内容,而不是 `pip`,那么你就不需要升级 `pip`。😎 @@ -224,7 +224,7 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python 在安装包时出现的许多奇怪的错误都可以通过先升级 `pip` 来解决。 -/// tip +/// tip | 提示 通常你只需要在创建虚拟环境后 **执行一次** 这个操作。 @@ -242,17 +242,37 @@ $ python -m pip install --upgrade pip -## 添加 `.gitignore` +/// tip | 提示 + +有时在尝试升级 pip 时,你可能会遇到 **`No module named pip`** 错误。 + +如果发生这种情况,使用下面的命令来安装并升级 pip: + +
+ +```console +$ python -m ensurepip --upgrade + +---> 100% +``` + +
+ +该命令会在尚未安装 pip 时进行安装,并确保安装的 pip 版本不早于 `ensurepip` 提供的版本。 + +/// + +## 添加 `.gitignore` { #add-gitignore } 如果你使用 **Git** (这是你应该使用的),添加一个 `.gitignore` 文件来排除你的 `.venv` 中的所有内容。 -/// tip +/// tip | 提示 如果你使用 `uv` 来创建虚拟环境,它会自动为你完成这个操作,你可以跳过这一步。😎 /// -/// tip +/// tip | 提示 通常你只需要在创建虚拟环境后 **执行一次** 这个操作。 @@ -282,11 +302,11 @@ $ echo "*" > .venv/.gitignore /// -## 安装软件包 +## 安装软件包 { #install-packages } 在激活虚拟环境后,你可以在其中安装软件包。 -/// tip +/// tip | 提示 当你需要安装或升级软件包时,执行本操作**一次**; @@ -294,11 +314,11 @@ $ echo "*" > .venv/.gitignore /// -### 直接安装包 +### 直接安装包 { #install-packages-directly } 如果你急于安装,不想使用文件来声明工程的软件包依赖,您可以直接安装它们。 -/// tip +/// tip | 提示 将程序所需的软件包及其版本放在文件中(例如 `requirements.txt` 或 `pyproject.toml`)是个好(并且非常好)的主意。 @@ -333,7 +353,7 @@ $ uv pip install "fastapi[standard]" //// -### 从 `requirements.txt` 安装 +### 从 `requirements.txt` 安装 { #install-from-requirements-txt } 如果你有一个 `requirements.txt` 文件,你可以使用它来安装其中的软件包。 @@ -376,7 +396,7 @@ pydantic==2.8.0 /// -## 运行程序 +## 运行程序 { #run-your-program } 在你激活虚拟环境后,你可以运行你的程序,它将使用虚拟环境中的 Python 和你在其中安装的软件包。 @@ -390,7 +410,7 @@ Hello World -## 配置编辑器 +## 配置编辑器 { #configure-your-editor } 你可能会用到编辑器(即 IDE —— 译者注),请确保配置它使用与你创建的相同的虚拟环境(它可能会自动检测到),以便你可以获得自动补全和内联错误提示。 @@ -399,13 +419,13 @@ Hello World * VS Code * PyCharm -/// tip +/// tip | 提示 通常你只需要在创建虚拟环境时执行此操作**一次**。 /// -## 退出虚拟环境 +## 退出虚拟环境 { #deactivate-the-virtual-environment } 当你完成工作后,你可以**退出**虚拟环境。 @@ -419,13 +439,13 @@ $ deactivate 这样,当你运行 `python` 时,它不会尝试从安装了软件包的虚拟环境中运行。(即,它将不再会尝试从虚拟环境中运行,也不会使用其中安装的软件包。—— 译者注) -## 开始工作 +## 开始工作 { #ready-to-work } 现在你已经准备好开始你的工作了。 -/// tip +/// tip | 提示 你想要理解上面的所有内容吗? @@ -433,7 +453,7 @@ $ deactivate /// -## 为什么要使用虚拟环境 +## 为什么要使用虚拟环境 { #why-virtual-environments } 你需要安装 Python 才能使用 FastAPI。 @@ -443,7 +463,7 @@ $ deactivate 然而,如果你直接使用 `pip`,软件包将被安装在你的**全局 Python 环境**中(即 Python 的全局安装)。 -### 存在的问题 +### 存在的问题 { #the-problem } 那么,在全局 Python 环境中安装软件包有什么问题呢? @@ -516,7 +536,7 @@ flowchart LR end ``` -/// tip +/// tip | 提示 Python 包在推出**新版本**时通常会尽量**避免破坏性更改**,但最好还是要小心,要想清楚再安装新版本,而且在运行测试以确保一切能正常工作时再安装。 @@ -526,7 +546,7 @@ Python 包在推出**新版本**时通常会尽量**避免破坏性更改**, 此外,取决于你的操作系统(例如 Linux、Windows、macOS),它可能已经预先安装了 Python。在这种情况下,它可能已经预先安装了一些软件包,这些软件包的特定版本是**系统所需的**。如果你在全局 Python 环境中安装软件包,你可能会**破坏**一些随操作系统一起安装的程序。 -## 软件包安装在哪里 +## 软件包安装在哪里 { #where-are-packages-installed } 当你安装 Python 时,它会在你的计算机上创建一些目录,并在这些目录中放一些文件。 @@ -552,7 +572,7 @@ $ pip install "fastapi[standard]" 默认情况下,它会将下载并解压的这些文件放在随 Python 安装的目录中,这就是**全局环境**。 -## 什么是虚拟环境 +## 什么是虚拟环境 { #what-are-virtual-environments } 解决软件包都安装在全局环境中的问题的方法是为你所做的每个工程使用一个**虚拟环境**。 @@ -577,7 +597,7 @@ flowchart TB stone-project ~~~ azkaban-project ``` -## 激活虚拟环境意味着什么 +## 激活虚拟环境意味着什么 { #what-does-activating-a-virtual-environment-mean } 当你激活了一个虚拟环境,例如: @@ -623,7 +643,7 @@ $ source .venv/Scripts/activate 其中之一是 `PATH` 变量。 -/// tip +/// tip | 提示 你可以在 [环境变量](environment-variables.md#path-environment-variable){.internal-link target=_blank} 部分了解更多关于 `PATH` 环境变量的内容。 @@ -714,7 +734,7 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python 激活虚拟环境还会改变其他一些东西,但这是它所做的最重要的事情之一。 -## 检查虚拟环境 +## 检查虚拟环境 { #checking-a-virtual-environment } 当你检查虚拟环境是否激活时,例如: @@ -756,7 +776,7 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python 因此,你可以确认你是否在正确的虚拟环境中。 -/// tip +/// tip | 提示 激活一个虚拟环境,获取一个 Python,然后**转到另一个工程**是一件很容易的事情; @@ -766,7 +786,7 @@ C:\Users\user\code\awesome-project\.venv\Scripts\python /// -## 为什么要停用虚拟环境 +## 为什么要停用虚拟环境 { #why-deactivate-a-virtual-environment } 例如,你可能正在一个工程 `philosophers-stone` 上工作,**激活了该虚拟环境**,安装了包并使用了该环境, @@ -820,7 +840,7 @@ I solemnly swear 🐺 -## 替代方案 +## 替代方案 { #alternatives } 这是一个简单的指南,可以帮助你入门并教会你如何理解一切**底层**的东西。 @@ -837,7 +857,7 @@ I solemnly swear 🐺 * 确保你有一个**确切**的软件包和版本集合来安装,包括它们的依赖项,这样你就可以确保在生产中运行你的工程与在开发时在你的计算机上运行的工程完全相同,这被称为**锁定** * 还有很多其他功能 -## 结论 +## 结论 { #conclusion } 如果你读过并理解了所有这些,现在**你对虚拟环境的了解比很多开发者都要多**。🤓 diff --git a/scripts/docs.py b/scripts/docs.py index 2c7d6d5e4..3cf62f084 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -30,7 +30,7 @@ SUPPORTED_LANGS = { "ru", "tr", "uk", - # "zh", + "zh", "zh-hant", } From cd31576d5704b6aef1bdadc43770238cd88aad2b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 8 Feb 2026 10:40:08 +0000 Subject: [PATCH 068/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5a714d1a0..238a4ff2a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Translations + +* 🌐 Update translations for zh (update-outdated). PR [#14843](https://github.com/fastapi/fastapi/pull/14843) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.5 ### Refactors From 227cb85a03ec6522f641467fbf6af9bc5b1c2e75 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:35:43 +0100 Subject: [PATCH 069/195] =?UTF-8?q?=E2=9C=85=20Fix=20parameterized=20tests?= =?UTF-8?q?=20with=20snapshots=20(#14875)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_path/test_required_str.py | 6 +-- ...est_tutorial001_tutorial002_tutorial003.py | 4 +- ...est_tutorial002_tutorial003_tutorial004.py | 4 +- .../test_tutorial003_tutorial004.py | 4 +- .../test_tutorial010.py | 39 +++++++++---------- 5 files changed, 28 insertions(+), 29 deletions(-) diff --git a/tests/test_request_params/test_path/test_required_str.py b/tests/test_request_params/test_path/test_required_str.py index 5add058c2..aecc2eb6c 100644 --- a/tests/test_request_params/test_path/test_required_str.py +++ b/tests/test_request_params/test_path/test_required_str.py @@ -3,7 +3,7 @@ from typing import Annotated import pytest from fastapi import FastAPI, Path from fastapi.testclient import TestClient -from inline_snapshot import snapshot +from inline_snapshot import Is, snapshot app = FastAPI() @@ -58,8 +58,8 @@ def test_schema(path: str, expected_name: str, expected_title: str): [ { "required": True, - "schema": {"title": expected_title, "type": "string"}, - "name": expected_name, + "schema": {"title": Is(expected_title), "type": "string"}, + "name": Is(expected_name), "in": "path", } ] diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py index 78e5e53a2..18bce279b 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py @@ -3,7 +3,7 @@ import importlib import pytest from dirty_equals import IsList from fastapi.testclient import TestClient -from inline_snapshot import snapshot +from inline_snapshot import Is, snapshot from ...utils import needs_py310 @@ -212,7 +212,7 @@ def test_openapi_schema(client: TestClient, mod_name: str): "title": "Tax", "anyOf": [{"type": "number"}, {"type": "null"}], }, - "tags": tags_schema, + "tags": Is(tags_schema), }, "required": [ "name", diff --git a/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py index f79d38b83..68e046ac5 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py @@ -2,7 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient -from inline_snapshot import snapshot +from inline_snapshot import Is, snapshot @pytest.fixture( @@ -59,7 +59,7 @@ def test_openapi_schema(client: TestClient, mod_name: str): "responses": { "200": { "description": "Successful Response", - "content": response_content, + "content": Is(response_content), } }, "summary": "Read Items", diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py index 8e86cd8a5..c13a3c38c 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py @@ -4,7 +4,7 @@ from textwrap import dedent import pytest from dirty_equals import IsList from fastapi.testclient import TestClient -from inline_snapshot import snapshot +from inline_snapshot import Is, snapshot from ...utils import needs_py310 @@ -75,7 +75,7 @@ def test_openapi_schema(client: TestClient, mod_name: str): "/items/": { "post": { "summary": "Create an item", - "description": DESCRIPTIONS[mod_name], + "description": Is(DESCRIPTIONS[mod_name]), "operationId": "create_item_items__post", "requestBody": { "content": { diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py index 395f79518..efe9f1fa6 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py @@ -3,7 +3,7 @@ import importlib import pytest from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE from fastapi.testclient import TestClient -from inline_snapshot import snapshot +from inline_snapshot import Is, snapshot from ...utils import needs_py310 @@ -66,6 +66,23 @@ def test_query_params_str_validations_item_query_nonregexquery(client: TestClien def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text + + parameters_schema = { + "anyOf": [ + { + "type": "string", + "minLength": 3, + "maxLength": 50, + "pattern": "^fixedquery$", + }, + {"type": "null"}, + ], + "title": "Query string", + "description": "Query string for the items to search in the database that have a good match", + # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. + **({"deprecated": True} if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) else {}), + } + assert response.json() == snapshot( { "openapi": "3.1.0", @@ -96,25 +113,7 @@ def test_openapi_schema(client: TestClient): "description": "Query string for the items to search in the database that have a good match", "required": False, "deprecated": True, - "schema": { - "anyOf": [ - { - "type": "string", - "minLength": 3, - "maxLength": 50, - "pattern": "^fixedquery$", - }, - {"type": "null"}, - ], - "title": "Query string", - "description": "Query string for the items to search in the database that have a good match", - # See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34. - **( - {"deprecated": True} - if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) - else {} - ), - }, + "schema": Is(parameters_schema), "name": "item-query", "in": "query", } From 0c0f6332e29f62001c94bf5c2d77ff1d79b73bf9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Feb 2026 15:36:09 +0000 Subject: [PATCH 070/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 238a4ff2a..fb404fcbd 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -11,6 +11,10 @@ hide: * 🌐 Update translations for zh (update-outdated). PR [#14843](https://github.com/fastapi/fastapi/pull/14843) by [@tiangolo](https://github.com/tiangolo). +### Internal + +* ✅ Fix parameterized tests with snapshots. PR [#14875](https://github.com/fastapi/fastapi/pull/14875) by [@YuriiMotov](https://github.com/YuriiMotov). + ## 0.128.5 ### Refactors From ed2512a5ec33d9d70f7a9ef2e5f9fe52d3b77ab2 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Mon, 9 Feb 2026 17:31:57 +0100 Subject: [PATCH 071/195] =?UTF-8?q?=F0=9F=90=9B=20Fix=20`on=5Fstartup`=20a?= =?UTF-8?q?nd=20`on=5Fshutdown`=20parameters=20of=20`APIRouter`=20(#14873)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- fastapi/routing.py | 21 ++++++------- tests/test_router_events.py | 60 +++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/fastapi/routing.py b/fastapi/routing.py index 0b4d28873..16a89ef3e 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -952,16 +952,6 @@ class APIRouter(routing.Router): ), ] = Default(generate_unique_id), ) -> None: - # Handle on_startup/on_shutdown locally since Starlette removed support - # Ref: https://github.com/Kludex/starlette/pull/3117 - # TODO: deprecate this once the lifespan (or alternative) interface is improved - self.on_startup: list[Callable[[], Any]] = ( - [] if on_startup is None else list(on_startup) - ) - self.on_shutdown: list[Callable[[], Any]] = ( - [] if on_shutdown is None else list(on_shutdown) - ) - # Determine the lifespan context to use if lifespan is None: # Use the default lifespan that runs on_startup/on_shutdown handlers @@ -985,6 +975,17 @@ class APIRouter(routing.Router): assert not prefix.endswith("/"), ( "A path prefix must not end with '/', as the routes will start with '/'" ) + + # Handle on_startup/on_shutdown locally since Starlette removed support + # Ref: https://github.com/Kludex/starlette/pull/3117 + # TODO: deprecate this once the lifespan (or alternative) interface is improved + self.on_startup: list[Callable[[], Any]] = ( + [] if on_startup is None else list(on_startup) + ) + self.on_shutdown: list[Callable[[], Any]] = ( + [] if on_shutdown is None else list(on_shutdown) + ) + self.prefix = prefix self.tags: list[Union[str, Enum]] = tags or [] self.dependencies = list(dependencies or []) diff --git a/tests/test_router_events.py b/tests/test_router_events.py index 65f2f521c..a47d11913 100644 --- a/tests/test_router_events.py +++ b/tests/test_router_events.py @@ -317,3 +317,63 @@ def test_router_async_generator_lifespan(state: State) -> None: assert response.json() == {"message": "Hello World"} assert state.app_startup is True assert state.app_shutdown is True + + +def test_startup_shutdown_handlers_as_parameters(state: State) -> None: + """Test that startup/shutdown handlers passed as parameters to FastAPI are called correctly.""" + + def app_startup() -> None: + state.app_startup = True + + def app_shutdown() -> None: + state.app_shutdown = True + + app = FastAPI(on_startup=[app_startup], on_shutdown=[app_shutdown]) + + @app.get("/") + def main() -> dict[str, str]: + return {"message": "Hello World"} + + def router_startup() -> None: + state.router_startup = True + + def router_shutdown() -> None: + state.router_shutdown = True + + router = APIRouter(on_startup=[router_startup], on_shutdown=[router_shutdown]) + + def sub_router_startup() -> None: + state.sub_router_startup = True + + def sub_router_shutdown() -> None: + state.sub_router_shutdown = True + + sub_router = APIRouter( + on_startup=[sub_router_startup], on_shutdown=[sub_router_shutdown] + ) + + router.include_router(sub_router) + app.include_router(router) + + assert state.app_startup is False + assert state.router_startup is False + assert state.sub_router_startup is False + assert state.app_shutdown is False + assert state.router_shutdown is False + assert state.sub_router_shutdown is False + with TestClient(app) as client: + assert state.app_startup is True + assert state.router_startup is True + assert state.sub_router_startup is True + assert state.app_shutdown is False + assert state.router_shutdown is False + assert state.sub_router_shutdown is False + response = client.get("/") + assert response.status_code == 200, response.text + assert response.json() == {"message": "Hello World"} + assert state.app_startup is True + assert state.router_startup is True + assert state.sub_router_startup is True + assert state.app_shutdown is True + assert state.router_shutdown is True + assert state.sub_router_shutdown is True From 0a4033aeeedcf8243502794648b25059e950e499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Feb 2026 18:19:22 +0100 Subject: [PATCH 072/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?8.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index fb404fcbd..9a1751d3a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.128.6 + ### Translations * 🌐 Update translations for zh (update-outdated). PR [#14843](https://github.com/fastapi/fastapi/pull/14843) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 117685689..95e57e2eb 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.128.5" +__version__ = "0.128.6" from starlette import status as status From 4e879799dd134e8e634d3e54c0973c0515a32d2e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Feb 2026 17:21:32 +0000 Subject: [PATCH 073/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 9a1751d3a..0abdfe6f2 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix `on_startup` and `on_shutdown` parameters of `APIRouter`. PR [#14873](https://github.com/fastapi/fastapi/pull/14873) by [@YuriiMotov](https://github.com/YuriiMotov). + ## 0.128.6 ### Translations From fbca586c1df1940f07ff8051716ec5a9ba97b149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 9 Feb 2026 18:25:04 +0100 Subject: [PATCH 074/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 0abdfe6f2..b151ff852 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,12 +7,12 @@ hide: ## Latest Changes +## 0.128.6 + ### Fixes * 🐛 Fix `on_startup` and `on_shutdown` parameters of `APIRouter`. PR [#14873](https://github.com/fastapi/fastapi/pull/14873) by [@YuriiMotov](https://github.com/YuriiMotov). -## 0.128.6 - ### Translations * 🌐 Update translations for zh (update-outdated). PR [#14843](https://github.com/fastapi/fastapi/pull/14843) by [@tiangolo](https://github.com/tiangolo). From 8fd291465b1211f88958c86779b9759b1268fddd Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Mon, 9 Feb 2026 18:38:48 +0100 Subject: [PATCH 075/195] =?UTF-8?q?=F0=9F=94=A7=20Configure=20`test`=20wor?= =?UTF-8?q?kflow=20to=20run=20tests=20with=20`inline-snapshot=3Dreview`=20?= =?UTF-8?q?(#14876)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e314b6a04..3fb837c04 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,7 @@ on: env: UV_NO_SYNC: true + INLINE_SNAPSHOT_DEFAULT_FLAGS: review jobs: changes: From e94028ab60d18080810910352bc9129e8046c92d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Feb 2026 17:39:11 +0000 Subject: [PATCH 076/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b151ff852..71dc7bb74 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* 🔧 Configure `test` workflow to run tests with `inline-snapshot=review`. PR [#14876](https://github.com/fastapi/fastapi/pull/14876) by [@YuriiMotov](https://github.com/YuriiMotov). + ## 0.128.6 ### Fixes From 66dc695071d8a040db72d5546eb848a58b41152e Mon Sep 17 00:00:00 2001 From: rijenkii Date: Tue, 10 Feb 2026 17:52:24 +0700 Subject: [PATCH 077/195] =?UTF-8?q?=E2=9C=A8=20Replace=20`dict`=20by=20`Ma?= =?UTF-8?q?pping`=20on=20`HTTPException.headers`=20(#12997)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alejandra <90076947+alejsdev@users.noreply.github.com> Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- fastapi/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index 62b4674de..9924d0e2b 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -1,4 +1,4 @@ -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from typing import Annotated, Any, Optional, TypedDict, Union from annotated_doc import Doc @@ -68,7 +68,7 @@ class HTTPException(StarletteHTTPException): ), ] = None, headers: Annotated[ - Optional[dict[str, str]], + Optional[Mapping[str, str]], Doc( """ Any headers to send to the client in the response. From 363aced75a26a5247855ea41b2bee53481be4d52 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Feb 2026 10:52:51 +0000 Subject: [PATCH 078/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 71dc7bb74..fff7ccc33 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Features + +* ✨ Replace `dict` by `Mapping` on `HTTPException.headers`. PR [#12997](https://github.com/fastapi/fastapi/pull/12997) by [@rijenkii](https://github.com/rijenkii). + ### Internal * 🔧 Configure `test` workflow to run tests with `inline-snapshot=review`. PR [#14876](https://github.com/fastapi/fastapi/pull/14876) by [@YuriiMotov](https://github.com/YuriiMotov). From df950111fe97c8040e49f4600d6f563456bbdba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20S=C3=A1nchez=20Castro?= <72013291+JavierSanchezCastro@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:58:40 +0100 Subject: [PATCH 079/195] =?UTF-8?q?=E2=9C=A8=20Show=20a=20clear=20error=20?= =?UTF-8?q?on=20attempt=20to=20include=20router=20into=20itself=20(#14258)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Javier Sánchez Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- fastapi/routing.py | 4 ++++ tests/test_router_circular_import.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 tests/test_router_circular_import.py diff --git a/fastapi/routing.py b/fastapi/routing.py index 16a89ef3e..ed873fda8 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1393,6 +1393,10 @@ class APIRouter(routing.Router): app.include_router(internal_router) ``` """ + assert self is not router, ( + "Cannot include the same APIRouter instance into itself. " + "Did you mean to include a different router?" + ) if prefix: assert prefix.startswith("/"), "A path prefix must start with '/'" assert not prefix.endswith("/"), ( diff --git a/tests/test_router_circular_import.py b/tests/test_router_circular_import.py new file mode 100644 index 000000000..492a26d00 --- /dev/null +++ b/tests/test_router_circular_import.py @@ -0,0 +1,12 @@ +import pytest +from fastapi import APIRouter + + +def test_router_circular_import(): + router = APIRouter() + + with pytest.raises( + AssertionError, + match="Cannot include the same APIRouter instance into itself. Did you mean to include a different router?", + ): + router.include_router(router) From 8bdb0d22423fdfbc4e12eee1baa339ffa960471d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Feb 2026 10:59:10 +0000 Subject: [PATCH 080/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index fff7ccc33..495bf83b4 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Features +* ✨ Show a clear error on attempt to include router into itself. PR [#14258](https://github.com/fastapi/fastapi/pull/14258) by [@JavierSanchezCastro](https://github.com/JavierSanchezCastro). * ✨ Replace `dict` by `Mapping` on `HTTPException.headers`. PR [#12997](https://github.com/fastapi/fastapi/pull/12997) by [@rijenkii](https://github.com/rijenkii). ### Internal From 25270fcee0c71c3bc661e40501fa3838beb6d99b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 10 Feb 2026 03:36:53 -0800 Subject: [PATCH 081/195] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Simplify=20reading?= =?UTF-8?q?=20files=20in=20memory,=20do=20it=20sequentially=20instead=20of?= =?UTF-8?q?=20(fake)=20parallel=20(#14884)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/dependencies/utils.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 80f9c76e9..23d8cd9fb 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,7 +1,7 @@ import dataclasses import inspect import sys -from collections.abc import Coroutine, Mapping, Sequence +from collections.abc import Mapping, Sequence from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy from dataclasses import dataclass @@ -15,7 +15,6 @@ from typing import ( cast, ) -import anyio from fastapi import params from fastapi._compat import ( ModelField, @@ -903,16 +902,8 @@ async def _extract_form_body( # For types assert isinstance(value, sequence_types) results: list[Union[bytes, str]] = [] - - async def process_fn( - fn: Callable[[], Coroutine[Any, Any, Any]], - ) -> None: - result = await fn() - results.append(result) # noqa: B023 - - async with anyio.create_task_group() as tg: - for sub_value in value: - tg.start_soon(process_fn, sub_value.read) + for sub_value in value: + results.append(await sub_value.read()) value = serialize_sequence_value(field=field, value=results) if value is not None: values[get_validation_alias(field)] = value From 3f1cc8f8f5fabaf85a0c3d9428004001af2486a4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Feb 2026 11:37:19 +0000 Subject: [PATCH 082/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 495bf83b4..72be2c6bd 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -12,6 +12,10 @@ hide: * ✨ Show a clear error on attempt to include router into itself. PR [#14258](https://github.com/fastapi/fastapi/pull/14258) by [@JavierSanchezCastro](https://github.com/JavierSanchezCastro). * ✨ Replace `dict` by `Mapping` on `HTTPException.headers`. PR [#12997](https://github.com/fastapi/fastapi/pull/12997) by [@rijenkii](https://github.com/rijenkii). +### Refactors + +* ♻️ Simplify reading files in memory, do it sequentially instead of (fake) parallel. PR [#14884](https://github.com/fastapi/fastapi/pull/14884) by [@tiangolo](https://github.com/tiangolo). + ### Internal * 🔧 Configure `test` workflow to run tests with `inline-snapshot=review`. PR [#14876](https://github.com/fastapi/fastapi/pull/14876) by [@YuriiMotov](https://github.com/YuriiMotov). From 38f965985e3111162070136acf92b0843c1d48b8 Mon Sep 17 00:00:00 2001 From: Valentyn Date: Tue, 10 Feb 2026 06:46:48 -0500 Subject: [PATCH 083/195] =?UTF-8?q?=E2=9C=85=20Test=20order=20for=20the=20?= =?UTF-8?q?submitted=20byte=20Files=20(#14828)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Valentyn Druzhynin Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Co-authored-by: Sebastián Ramírez --- ..._bytes_file_order_preserved_issue_14811.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/test_list_bytes_file_order_preserved_issue_14811.py diff --git a/tests/test_list_bytes_file_order_preserved_issue_14811.py b/tests/test_list_bytes_file_order_preserved_issue_14811.py new file mode 100644 index 000000000..399235bdb --- /dev/null +++ b/tests/test_list_bytes_file_order_preserved_issue_14811.py @@ -0,0 +1,46 @@ +""" +Regression test: preserve order when using list[bytes] + File() +See https://github.com/fastapi/fastapi/discussions/14811 +Related: PR #3372 +""" + +from typing import Annotated + +import anyio +import pytest +from fastapi import FastAPI, File +from fastapi.testclient import TestClient +from starlette.datastructures import UploadFile as StarletteUploadFile + + +def test_list_bytes_file_preserves_order( + monkeypatch: pytest.MonkeyPatch, +) -> None: + app = FastAPI() + + @app.post("/upload") + async def upload(files: Annotated[list[bytes], File()]): + # return something that makes order obvious + return [b[0] for b in files] + + original_read = StarletteUploadFile.read + + async def patched_read(self: StarletteUploadFile, size: int = -1) -> bytes: + # Make the FIRST file slower *deterministically* + if self.filename == "slow.txt": + await anyio.sleep(0.05) + return await original_read(self, size) + + monkeypatch.setattr(StarletteUploadFile, "read", patched_read) + + client = TestClient(app) + + files = [ + ("files", ("slow.txt", b"A" * 10, "text/plain")), + ("files", ("fast.txt", b"B" * 10, "text/plain")), + ] + r = client.post("/upload", files=files) + assert r.status_code == 200, r.text + + # Must preserve request order: slow first, fast second + assert r.json() == [ord("A"), ord("B")] From 1133a4594d96cd4c26d734f74444110ec9260405 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Feb 2026 11:47:26 +0000 Subject: [PATCH 084/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 72be2c6bd..ea027ae6f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -18,6 +18,7 @@ hide: ### Internal +* ✅ Test order for the submitted byte Files. PR [#14828](https://github.com/fastapi/fastapi/pull/14828) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). * 🔧 Configure `test` workflow to run tests with `inline-snapshot=review`. PR [#14876](https://github.com/fastapi/fastapi/pull/14876) by [@YuriiMotov](https://github.com/YuriiMotov). ## 0.128.6 From ca76a4eba9bb9ddd082b6d16f14d8b0d7977235b Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:48:27 +0100 Subject: [PATCH 085/195] =?UTF-8?q?=F0=9F=93=9D=20Use=20`dfn`=20tag=20for?= =?UTF-8?q?=20definitions=20instead=20of=20`abbr`=20in=20docs=20(#14744)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] Co-authored-by: Sebastián Ramírez --- README.md | 10 +++++----- docs/en/docs/advanced/dataclasses.md | 2 +- .../advanced/path-operation-advanced-configuration.md | 2 +- docs/en/docs/alternatives.md | 8 ++++---- docs/en/docs/css/custom.css | 5 +++++ docs/en/docs/deployment/docker.md | 2 +- docs/en/docs/deployment/https.md | 2 +- docs/en/docs/features.md | 10 +++++----- docs/en/docs/index.md | 10 +++++----- docs/en/docs/python-types.md | 10 +++++----- docs/en/docs/tutorial/cookie-param-models.md | 8 ++++---- .../tutorial/dependencies/dependencies-with-yield.md | 2 +- docs/en/docs/tutorial/dependencies/index.md | 2 +- docs/en/docs/tutorial/dependencies/sub-dependencies.md | 2 +- docs/en/docs/tutorial/first-steps.md | 2 +- docs/en/docs/tutorial/path-operation-configuration.md | 4 ++-- docs/en/docs/tutorial/path-params.md | 8 ++++---- docs/en/docs/tutorial/query-params-str-validations.md | 10 +++++----- docs/en/docs/tutorial/query-params.md | 2 +- docs/en/docs/tutorial/request-forms.md | 2 +- docs/en/docs/tutorial/response-model.md | 2 +- docs/en/docs/tutorial/schema-extra-example.md | 2 +- docs/en/docs/virtual-environments.md | 2 +- 23 files changed, 57 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 963de51ed..16d149f8f 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The key features are: * **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). * **Fast to code**: Increase the speed to develop features by about 200% to 300%. * * **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. * **Easy**: Designed to be easy to use and learn. Less time reading docs. * **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. * **Robust**: Get production-ready code. With automatic interactive documentation. @@ -371,7 +371,7 @@ item: Item * Validation of data: * Automatic and clear errors when the data is invalid. * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: +* Conversion of input data: coming from the network to Python data and types. Reading from: * JSON. * Path parameters. * Query parameters. @@ -379,7 +379,7 @@ item: Item * Headers. * Forms. * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): +* Conversion of output data: converting from Python data and types to network data (as JSON): * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). * `datetime` objects. * `UUID` objects. @@ -442,7 +442,7 @@ For a more complete example including more features, see the Dependency Injection** system. +* A very powerful and easy to use **Dependency Injection** system. * Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. * More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). * **GraphQL** integration with Strawberry and other libraries. @@ -527,7 +527,7 @@ Used by Starlette: * httpx - Required if you want to use the `TestClient`. * jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. +* python-multipart - Required if you want to support form "parsing", with `request.form()`. Used by FastAPI: diff --git a/docs/en/docs/advanced/dataclasses.md b/docs/en/docs/advanced/dataclasses.md index dbc91409a..be85303bf 100644 --- a/docs/en/docs/advanced/dataclasses.md +++ b/docs/en/docs/advanced/dataclasses.md @@ -64,7 +64,7 @@ In that case, you can simply swap the standard `dataclasses` with `pydantic.data 6. Here we are returning a dictionary that contains `items` which is a list of dataclasses. - FastAPI is still capable of serializing the data to JSON. + FastAPI is still capable of serializing the data to JSON. 7. Here the `response_model` is using a type annotation of a list of `Author` dataclasses. diff --git a/docs/en/docs/advanced/path-operation-advanced-configuration.md b/docs/en/docs/advanced/path-operation-advanced-configuration.md index e0e3c96a0..3d7bfdee5 100644 --- a/docs/en/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/en/docs/advanced/path-operation-advanced-configuration.md @@ -141,7 +141,7 @@ You could do that with `openapi_extra`: {* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} -In this example, we didn't declare any Pydantic model. In fact, the request body is not even parsed as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way. +In this example, we didn't declare any Pydantic model. In fact, the request body is not even parsed as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way. Nevertheless, we can declare the expected schema for the request body. diff --git a/docs/en/docs/alternatives.md b/docs/en/docs/alternatives.md index e65681543..73a6c1cb5 100644 --- a/docs/en/docs/alternatives.md +++ b/docs/en/docs/alternatives.md @@ -137,7 +137,7 @@ There are several Flask REST frameworks, but after investing the time and work i ### Marshmallow { #marshmallow } -One of the main features needed by API systems is data "serialization" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc. +One of the main features needed by API systems is data "serialization" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc. Another big feature needed by APIs is data validation, making sure that the data is valid, given certain parameters. For example, that some field is an `int`, and not some random string. This is especially useful for incoming data. @@ -145,7 +145,7 @@ Without a data validation system, you would have to do all the checks by hand, i These features are what Marshmallow was built to provide. It is a great library, and I have used it a lot before. -But it was created before there existed Python type hints. So, to define every schema you need to use specific utils and classes provided by Marshmallow. +But it was created before there existed Python type hints. So, to define every schema you need to use specific utils and classes provided by Marshmallow. /// check | Inspired **FastAPI** to @@ -155,7 +155,7 @@ Use code to define "schemas" that provide data types and validation, automatical ### Webargs { #webargs } -Another big feature required by APIs is parsing data from incoming requests. +Another big feature required by APIs is parsing data from incoming requests. Webargs is a tool that was made to provide that on top of several frameworks, including Flask. @@ -419,7 +419,7 @@ Handle all the data validation, data serialization and automatic model documenta ### Starlette { #starlette } -Starlette is a lightweight ASGI framework/toolkit, which is ideal for building high-performance asyncio services. +Starlette is a lightweight ASGI framework/toolkit, which is ideal for building high-performance asyncio services. It is very simple and intuitive. It's designed to be easily extensible, and have modular components. diff --git a/docs/en/docs/css/custom.css b/docs/en/docs/css/custom.css index 87111ff64..7c50dbd9b 100644 --- a/docs/en/docs/css/custom.css +++ b/docs/en/docs/css/custom.css @@ -203,3 +203,8 @@ Inspired by Termynal's CSS tricks with modifications -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; } + +.md-typeset dfn { + border-bottom: .05rem dotted var(--md-default-fg-color--light); + cursor: help; +} diff --git a/docs/en/docs/deployment/docker.md b/docs/en/docs/deployment/docker.md index 7219f3afc..0f15a60a4 100644 --- a/docs/en/docs/deployment/docker.md +++ b/docs/en/docs/deployment/docker.md @@ -454,7 +454,7 @@ Without using containers, making applications run on startup and with restarts c ## Replication - Number of Processes { #replication-number-of-processes } -If you have a cluster of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or another similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Uvicorn with workers) in each container. +If you have a cluster of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or another similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Uvicorn with workers) in each container. One of those distributed container management systems like Kubernetes normally has some integrated way of handling **replication of containers** while still supporting **load balancing** for the incoming requests. All at the **cluster level**. diff --git a/docs/en/docs/deployment/https.md b/docs/en/docs/deployment/https.md index a249a3672..6ae1228b9 100644 --- a/docs/en/docs/deployment/https.md +++ b/docs/en/docs/deployment/https.md @@ -65,7 +65,7 @@ Here's an example of how an HTTPS API could look like, step by step, paying atte It would probably all start by you **acquiring** some **domain name**. Then, you would configure it in a DNS server (possibly your same cloud provider). -You would probably get a cloud server (a virtual machine) or something similar, and it would have a fixed **public IP address**. +You would probably get a cloud server (a virtual machine) or something similar, and it would have a fixed **public IP address**. In the DNS server(s) you would configure a record (an "`A record`") to point **your domain** to the public **IP address of your server**. diff --git a/docs/en/docs/features.md b/docs/en/docs/features.md index a345e4a0e..307607ad7 100644 --- a/docs/en/docs/features.md +++ b/docs/en/docs/features.md @@ -6,7 +6,7 @@ ### Based on open standards { #based-on-open-standards } -* OpenAPI for API creation, including declarations of path operations, parameters, request bodies, security, etc. +* OpenAPI for API creation, including declarations of path operations, parameters, request bodies, security, etc. * Automatic data model documentation with JSON Schema (as OpenAPI itself is based on JSON Schema). * Designed around these standards, after a meticulous study. Instead of an afterthought layer on top. * This also allows using automatic **client code generation** in many languages. @@ -136,7 +136,7 @@ All built as reusable tools and components that are easy to integrate with your ### Dependency Injection { #dependency-injection } -FastAPI includes an extremely easy to use, but extremely powerful Dependency Injection system. +FastAPI includes an extremely easy to use, but extremely powerful Dependency Injection system. * Even dependencies can have dependencies, creating a hierarchy or **"graph" of dependencies**. * All **automatically handled** by the framework. @@ -153,8 +153,8 @@ Any integration is designed to be so simple to use (with dependencies) that you ### Tested { #tested } -* 100% test coverage. -* 100% type annotated code base. +* 100% test coverage. +* 100% type annotated code base. * Used in production applications. ## Starlette features { #starlette-features } @@ -190,7 +190,7 @@ With **FastAPI** you get all of **Pydantic**'s features (as FastAPI is based on * **No brainfuck**: * No new schema definition micro-language to learn. * If you know Python types you know how to use Pydantic. -* Plays nicely with your **IDE/linter/brain**: +* Plays nicely with your **IDE/linter/brain**: * Because pydantic data structures are just instances of classes you define; auto-completion, linting, mypy and your intuition should all work properly with your validated data. * Validate **complex structures**: * Use of hierarchical Pydantic models, Python `typing`’s `List` and `Dict`, etc. diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index ff0f4e9d9..7f9d5efbf 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -40,7 +40,7 @@ The key features are: * **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). * **Fast to code**: Increase the speed to develop features by about 200% to 300%. * * **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. +* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. * **Easy**: Designed to be easy to use and learn. Less time reading docs. * **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. * **Robust**: Get production-ready code. With automatic interactive documentation. @@ -368,7 +368,7 @@ item: Item * Validation of data: * Automatic and clear errors when the data is invalid. * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: +* Conversion of input data: coming from the network to Python data and types. Reading from: * JSON. * Path parameters. * Query parameters. @@ -376,7 +376,7 @@ item: Item * Headers. * Forms. * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): +* Conversion of output data: converting from Python data and types to network data (as JSON): * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). * `datetime` objects. * `UUID` objects. @@ -439,7 +439,7 @@ For a more complete example including more features, see the Dependency Injection** system. +* A very powerful and easy to use **Dependency Injection** system. * Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. * More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). * **GraphQL** integration with Strawberry and other libraries. @@ -524,7 +524,7 @@ Used by Starlette: * httpx - Required if you want to use the `TestClient`. * jinja2 - Required if you want to use the default template configuration. -* python-multipart - Required if you want to support form "parsing", with `request.form()`. +* python-multipart - Required if you want to support form "parsing", with `request.form()`. Used by FastAPI: diff --git a/docs/en/docs/python-types.md b/docs/en/docs/python-types.md index b685deef2..6de170ada 100644 --- a/docs/en/docs/python-types.md +++ b/docs/en/docs/python-types.md @@ -2,7 +2,7 @@ Python has support for optional "type hints" (also called "type annotations"). -These **"type hints"** or annotations are a special syntax that allow declaring the type of a variable. +These **"type hints"** or annotations are a special syntax that allow declaring the type of a variable. By declaring types for your variables, editors and tools can give you better support. @@ -34,7 +34,7 @@ The function does the following: * Takes a `first_name` and `last_name`. * Converts the first letter of each one to upper case with `title()`. -* Concatenates them with a space in the middle. +* Concatenates them with a space in the middle. {* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} @@ -222,7 +222,7 @@ You can declare that a variable can be any of **several types**, for example, an In Python 3.6 and above (including Python 3.10) you can use the `Union` type from `typing` and put inside the square brackets the possible types to accept. -In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a vertical bar (`|`). +In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a vertical bar (`|`). //// tab | Python 3.10+ @@ -336,7 +336,7 @@ And the same as with previous Python versions, from the `typing` module: * `Optional` * ...and others. -In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the vertical bar (`|`) to declare unions of types, that's a lot better and simpler. +In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the vertical bar (`|`) to declare unions of types, that's a lot better and simpler. //// @@ -411,7 +411,7 @@ Pydantic has a special behavior when you use `Optional` or `Union[Something, Non ## Type Hints with Metadata Annotations { #type-hints-with-metadata-annotations } -Python also has a feature that allows putting **additional metadata** in these type hints using `Annotated`. +Python also has a feature that allows putting **additional metadata** in these type hints using `Annotated`. Since Python 3.9, `Annotated` is a part of the standard library, so you can import it from `typing`. diff --git a/docs/en/docs/tutorial/cookie-param-models.md b/docs/en/docs/tutorial/cookie-param-models.md index 016a65d7f..609838f76 100644 --- a/docs/en/docs/tutorial/cookie-param-models.md +++ b/docs/en/docs/tutorial/cookie-param-models.md @@ -46,7 +46,7 @@ But even if you **fill the data** and click "Execute", because the docs UI works In some special use cases (probably not very common), you might want to **restrict** the cookies that you want to receive. -Your API now has the power to control its own cookie consent. 🤪🍪 +Your API now has the power to control its own cookie consent. 🤪🍪 You can use Pydantic's model configuration to `forbid` any `extra` fields: @@ -54,9 +54,9 @@ You can use Pydantic's model configuration to `forbid` any `extra` fields: If a client tries to send some **extra cookies**, they will receive an **error** response. -Poor cookie banners with all their effort to get your consent for the API to reject it. 🍪 +Poor cookie banners with all their effort to get your consent for the API to reject it. 🍪 -For example, if the client tries to send a `santa_tracker` cookie with a value of `good-list-please`, the client will receive an **error** response telling them that the `santa_tracker` cookie is not allowed: +For example, if the client tries to send a `santa_tracker` cookie with a value of `good-list-please`, the client will receive an **error** response telling them that the `santa_tracker` cookie is not allowed: ```json { @@ -73,4 +73,4 @@ For example, if the client tries to send a `santa_tracker` cookie with a value o ## Summary { #summary } -You can use **Pydantic models** to declare **cookies** in **FastAPI**. 😎 +You can use **Pydantic models** to declare **cookies** in **FastAPI**. 😎 diff --git a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md index d9f334561..cfcd961ba 100644 --- a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,6 +1,6 @@ # Dependencies with yield { #dependencies-with-yield } -FastAPI supports dependencies that do some extra steps after finishing. +FastAPI supports dependencies that do some extra steps after finishing. To do this, use `yield` instead of `return`, and write the extra steps (code) after. diff --git a/docs/en/docs/tutorial/dependencies/index.md b/docs/en/docs/tutorial/dependencies/index.md index d1116b2be..4a1bb774d 100644 --- a/docs/en/docs/tutorial/dependencies/index.md +++ b/docs/en/docs/tutorial/dependencies/index.md @@ -1,6 +1,6 @@ # Dependencies { #dependencies } -**FastAPI** has a very powerful but intuitive **Dependency Injection** system. +**FastAPI** has a very powerful but intuitive **Dependency Injection** system. It is designed to be very simple to use, and to make it very easy for any developer to integrate other components with **FastAPI**. diff --git a/docs/en/docs/tutorial/dependencies/sub-dependencies.md b/docs/en/docs/tutorial/dependencies/sub-dependencies.md index 967bffda7..b9a4e3ce7 100644 --- a/docs/en/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/en/docs/tutorial/dependencies/sub-dependencies.md @@ -58,7 +58,7 @@ query_extractor --> query_or_cookie_extractor --> read_query If one of your dependencies is declared multiple times for the same *path operation*, for example, multiple dependencies have a common sub-dependency, **FastAPI** will know to call that sub-dependency only once per request. -And it will save the returned value in a "cache" and pass it to all the "dependants" that need it in that specific request, instead of calling the dependency multiple times for the same request. +And it will save the returned value in a "cache" and pass it to all the "dependants" that need it in that specific request, instead of calling the dependency multiple times for the same request. In an advanced scenario where you know you need the dependency to be called at every step (possibly multiple times) in the same request instead of using the "cached" value, you can set the parameter `use_cache=False` when using `Depends`: diff --git a/docs/en/docs/tutorial/first-steps.md b/docs/en/docs/tutorial/first-steps.md index df8e91ecd..a3263593d 100644 --- a/docs/en/docs/tutorial/first-steps.md +++ b/docs/en/docs/tutorial/first-steps.md @@ -271,7 +271,7 @@ We are going to call them "**operations**" too. The `@app.get("/")` tells **FastAPI** that the function right below is in charge of handling requests that go to: * the path `/` -* using a get operation +* using a get operation /// info | `@decorator` Info diff --git a/docs/en/docs/tutorial/path-operation-configuration.md b/docs/en/docs/tutorial/path-operation-configuration.md index 1bb7ee554..f5c603880 100644 --- a/docs/en/docs/tutorial/path-operation-configuration.md +++ b/docs/en/docs/tutorial/path-operation-configuration.md @@ -56,7 +56,7 @@ You can add a `summary` and `description`: ## Description from docstring { #description-from-docstring } -As descriptions tend to be long and cover multiple lines, you can declare the *path operation* description in the function docstring and **FastAPI** will read it from there. +As descriptions tend to be long and cover multiple lines, you can declare the *path operation* description in the function docstring and **FastAPI** will read it from there. You can write Markdown in the docstring, it will be interpreted and displayed correctly (taking into account docstring indentation). @@ -90,7 +90,7 @@ So, if you don't provide one, **FastAPI** will automatically generate one of "Su ## Deprecate a *path operation* { #deprecate-a-path-operation } -If you need to mark a *path operation* as deprecated, but without removing it, pass the parameter `deprecated`: +If you need to mark a *path operation* as deprecated, but without removing it, pass the parameter `deprecated`: {* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} diff --git a/docs/en/docs/tutorial/path-params.md b/docs/en/docs/tutorial/path-params.md index ea4307900..cf312f1fe 100644 --- a/docs/en/docs/tutorial/path-params.md +++ b/docs/en/docs/tutorial/path-params.md @@ -26,7 +26,7 @@ This will give you editor support inside of your function, with error checks, co /// -## Data conversion { #data-conversion } +## Data conversion { #data-conversion } If you run this example and open your browser at http://127.0.0.1:8000/items/3, you will see a response of: @@ -38,7 +38,7 @@ If you run this example and open your browser at "parsing". +So, with that type declaration, **FastAPI** gives you automatic request "parsing". /// @@ -144,7 +144,7 @@ Then create class attributes with fixed values, which will be the available vali /// tip -If you are wondering, "AlexNet", "ResNet", and "LeNet" are just names of Machine Learning models. +If you are wondering, "AlexNet", "ResNet", and "LeNet" are just names of Machine Learning models. /// @@ -242,7 +242,7 @@ In that case, the URL would be: `/files//home/johndoe/myfile.txt`, with a double With **FastAPI**, by using short, intuitive and standard Python type declarations, you get: * Editor support: error checks, autocompletion, etc. -* Data "parsing" +* Data "parsing" * Data validation * API annotation and automatic documentation diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md index 4b8cc9d29..1846ad56a 100644 --- a/docs/en/docs/tutorial/query-params-str-validations.md +++ b/docs/en/docs/tutorial/query-params-str-validations.md @@ -109,7 +109,7 @@ FastAPI will now: ## Alternative (old): `Query` as the default value { #alternative-old-query-as-the-default-value } -Previous versions of FastAPI (before 0.95.0) required you to use `Query` as the default value of your parameter, instead of putting it in `Annotated`, there's a high chance that you will see code using it around, so I'll explain it to you. +Previous versions of FastAPI (before 0.95.0) required you to use `Query` as the default value of your parameter, instead of putting it in `Annotated`, there's a high chance that you will see code using it around, so I'll explain it to you. /// tip @@ -192,7 +192,7 @@ You can also add a parameter `min_length`: ## Add regular expressions { #add-regular-expressions } -You can define a regular expression `pattern` that the parameter should match: +You can define a regular expression `pattern` that the parameter should match: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -372,7 +372,7 @@ Then you can declare an `alias`, and that alias is what will be used to find the Now let's say you don't like this parameter anymore. -You have to leave it there a while because there are clients using it, but you want the docs to clearly show it as deprecated. +You have to leave it there a while because there are clients using it, but you want the docs to clearly show it as deprecated. Then pass the parameter `deprecated=True` to `Query`: @@ -402,7 +402,7 @@ Pydantic also has ISBN book number or with `imdb-` for an IMDB movie URL ID: +For example, this custom validator checks that the item ID starts with `isbn-` for an ISBN book number or with `imdb-` for an IMDB movie URL ID: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -436,7 +436,7 @@ Did you notice? a string using `value.startswith()` can take a tuple, and it wil #### A Random Item { #a-random-item } -With `data.items()` we get an iterable object with tuples containing the key and value for each dictionary item. +With `data.items()` we get an iterable object with tuples containing the key and value for each dictionary item. We convert this iterable object into a proper `list` with `list(data.items())`. diff --git a/docs/en/docs/tutorial/query-params.md b/docs/en/docs/tutorial/query-params.md index 3c9c225fb..eeb59f925 100644 --- a/docs/en/docs/tutorial/query-params.md +++ b/docs/en/docs/tutorial/query-params.md @@ -24,7 +24,7 @@ But when you declare them with Python types (in the example above, as `int`), th All the same process that applied for path parameters also applies for query parameters: * Editor support (obviously) -* Data "parsing" +* Data "parsing" * Data validation * Automatic documentation diff --git a/docs/en/docs/tutorial/request-forms.md b/docs/en/docs/tutorial/request-forms.md index d743c2ec4..d63ffd565 100644 --- a/docs/en/docs/tutorial/request-forms.md +++ b/docs/en/docs/tutorial/request-forms.md @@ -28,7 +28,7 @@ Create form parameters the same way you would for `Body` or `Query`: For example, in one of the ways the OAuth2 specification can be used (called "password flow") it is required to send a `username` and `password` as form fields. -The spec requires the fields to be exactly named `username` and `password`, and to be sent as form fields, not JSON. +The spec requires the fields to be exactly named `username` and `password`, and to be sent as form fields, not JSON. With `Form` you can declare the same configurations as with `Body` (and `Query`, `Path`, `Cookie`), including validation, examples, an alias (e.g. `user-name` instead of `username`), etc. diff --git a/docs/en/docs/tutorial/response-model.md b/docs/en/docs/tutorial/response-model.md index b287cdf50..ac35390fa 100644 --- a/docs/en/docs/tutorial/response-model.md +++ b/docs/en/docs/tutorial/response-model.md @@ -201,7 +201,7 @@ This will also work because `RedirectResponse` is a subclass of `Response`, and But when you return some other arbitrary object that is not a valid Pydantic type (e.g. a database object) and you annotate it like that in the function, FastAPI will try to create a Pydantic response model from that type annotation, and will fail. -The same would happen if you had something like a union between different types where one or more of them are not valid Pydantic types, for example this would fail 💥: +The same would happen if you had something like a union between different types where one or more of them are not valid Pydantic types, for example this would fail 💥: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} diff --git a/docs/en/docs/tutorial/schema-extra-example.md b/docs/en/docs/tutorial/schema-extra-example.md index daaea73c8..432f58b1e 100644 --- a/docs/en/docs/tutorial/schema-extra-example.md +++ b/docs/en/docs/tutorial/schema-extra-example.md @@ -74,7 +74,7 @@ You can of course also pass multiple `examples`: When you do this, the examples will be part of the internal **JSON Schema** for that body data. -Nevertheless, at the time of writing this, Swagger UI, the tool in charge of showing the docs UI, doesn't support showing multiple examples for the data in **JSON Schema**. But read below for a workaround. +Nevertheless, at the time of writing this, Swagger UI, the tool in charge of showing the docs UI, doesn't support showing multiple examples for the data in **JSON Schema**. But read below for a workaround. ### OpenAPI-specific `examples` { #openapi-specific-examples } diff --git a/docs/en/docs/virtual-environments.md b/docs/en/docs/virtual-environments.md index c02e43ab9..615d7949f 100644 --- a/docs/en/docs/virtual-environments.md +++ b/docs/en/docs/virtual-environments.md @@ -53,7 +53,7 @@ $ cd awesome-project ## Create a Virtual Environment { #create-a-virtual-environment } -When you start working on a Python project **for the first time**, create a virtual environment **inside your project**. +When you start working on a Python project **for the first time**, create a virtual environment **inside your project**. /// tip From ff721017df331de1357270f03f1f03382a50477f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Feb 2026 11:49:54 +0000 Subject: [PATCH 086/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ea027ae6f..bd4a6726e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -16,6 +16,10 @@ hide: * ♻️ Simplify reading files in memory, do it sequentially instead of (fake) parallel. PR [#14884](https://github.com/fastapi/fastapi/pull/14884) by [@tiangolo](https://github.com/tiangolo). +### Docs + +* 📝 Use `dfn` tag for definitions instead of `abbr` in docs. PR [#14744](https://github.com/fastapi/fastapi/pull/14744) by [@YuriiMotov](https://github.com/YuriiMotov). + ### Internal * ✅ Test order for the submitted byte Files. PR [#14828](https://github.com/fastapi/fastapi/pull/14828) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). From 81f82fd9558b077017e80adc448fe5f7807d3732 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:56:20 +0100 Subject: [PATCH 087/195] =?UTF-8?q?=F0=9F=94=A7=20Update=20LLM-prompt=20fo?= =?UTF-8?q?r=20`abbr`=20and=20`dfn`=20tags=20(#14747)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/de/docs/_llm-test.md | 14 ++++---- docs/en/docs/_llm-test.md | 10 +++--- docs/es/docs/_llm-test.md | 14 ++++---- docs/ko/docs/_llm-test.md | 12 +++---- docs/pt/docs/_llm-test.md | 18 +++++----- docs/ru/docs/_llm-test.md | 10 +++--- scripts/general-llm-prompt.md | 64 +++++++++++++---------------------- 7 files changed, 63 insertions(+), 79 deletions(-) diff --git a/docs/de/docs/_llm-test.md b/docs/de/docs/_llm-test.md index 0b95fe3a8..498a2e5b6 100644 --- a/docs/de/docs/_llm-test.md +++ b/docs/de/docs/_llm-test.md @@ -35,7 +35,7 @@ Siehe Abschnitt `### Content of code snippets` im allgemeinen Prompt in `scripts //// tab | Test -Gestern schrieb mein Freund: „Wenn man unkorrekt korrekt schreibt, hat man es unkorrekt geschrieben“. Worauf ich antwortete: „Korrekt, aber ‚unkorrekt‘ ist unkorrekterweise nicht ‚„unkorrekt“‘“. +Gestern schrieb mein Freund: „Wenn man ‚incorrectly‘ korrekt schreibt, hat man es falsch geschrieben“. Worauf ich antwortete: „Korrekt, aber ‚incorrectly‘ ist inkorrekterweise nicht ‚„incorrectly“‘“. /// note | Hinweis @@ -202,11 +202,6 @@ Hier einige Dinge, die in HTML-„abbr“-Elemente gepackt sind (einige sind erf * XWT * PSGI -### Das abbr gibt eine Erklärung { #the-abbr-gives-an-explanation } - -* Cluster -* Deep Learning - ### Das abbr gibt eine vollständige Phrase und eine Erklärung { #the-abbr-gives-a-full-phrase-and-an-explanation } * MDN @@ -224,6 +219,11 @@ Siehe Abschnitt `### HTML abbr elements` im allgemeinen Prompt in `scripts/trans //// +## HTML „dfn“-Elemente { #html-dfn-elements } + +* Cluster +* Deep Learning + ## Überschriften { #headings } //// tab | Test @@ -248,7 +248,7 @@ Die einzige strenge Regel für Überschriften ist, dass das LLM den Hash-Teil in Siehe Abschnitt `### Headings` im allgemeinen Prompt in `scripts/translate.py`. -Für einige sprachspezifische Anweisungen, siehe z. B. den Abschnitt `### Headings` in `docs/de/llm-prompt.md`. +Für einige sprachsspezifische Anweisungen, siehe z. B. den Abschnitt `### Headings` in `docs/de/llm-prompt.md`. //// diff --git a/docs/en/docs/_llm-test.md b/docs/en/docs/_llm-test.md index d218f7c76..ff0e24f26 100644 --- a/docs/en/docs/_llm-test.md +++ b/docs/en/docs/_llm-test.md @@ -202,11 +202,6 @@ Here some things wrapped in HTML "abbr" elements (Some are invented): * XWT * PSGI -### The abbr gives an explanation { #the-abbr-gives-an-explanation } - -* cluster -* Deep Learning - ### The abbr gives a full phrase and an explanation { #the-abbr-gives-a-full-phrase-and-an-explanation } * MDN @@ -224,6 +219,11 @@ See section `### HTML abbr elements` in the general prompt in `scripts/translate //// +## HTML "dfn" elements { #html-dfn-elements } + +* cluster +* Deep Learning + ## Headings { #headings } //// tab | Test diff --git a/docs/es/docs/_llm-test.md b/docs/es/docs/_llm-test.md index 591b1008b..dda425acb 100644 --- a/docs/es/docs/_llm-test.md +++ b/docs/es/docs/_llm-test.md @@ -202,15 +202,10 @@ Aquí algunas cosas envueltas en elementos HTML "abbr" (algunas son inventadas): * XWT * PSGI -### El abbr da una explicación { #the-abbr-gives-an-explanation } - -* clúster -* Deep Learning - ### El abbr da una frase completa y una explicación { #the-abbr-gives-a-full-phrase-and-an-explanation } * MDN -* I/O. +* I/O. //// @@ -224,6 +219,11 @@ Consulta la sección `### HTML abbr elements` en el prompt general en `scripts/t //// +## Elementos HTML "dfn" { #html-dfn-elements } + +* clúster +* Deep Learning + ## Encabezados { #headings } //// tab | Prueba @@ -433,7 +433,7 @@ Para instrucciones específicas del idioma, mira p. ej. la sección `### Heading * el motor de plantillas * la anotación de tipos -* las anotaciones de tipos +* la anotación de tipos * el worker del servidor * el worker de Uvicorn diff --git a/docs/ko/docs/_llm-test.md b/docs/ko/docs/_llm-test.md index 1b828c663..272af763a 100644 --- a/docs/ko/docs/_llm-test.md +++ b/docs/ko/docs/_llm-test.md @@ -132,7 +132,7 @@ works(foo="bar") # 이건 동작합니다 🎉 일부 텍스트 /// -/// note Technical details | 기술 세부사항 +/// note | 기술 세부사항 일부 텍스트 /// @@ -202,11 +202,6 @@ works(foo="bar") # 이건 동작합니다 🎉 * XWT * PSGI -### abbr가 설명을 제공 { #the-abbr-gives-an-explanation } - -* cluster -* Deep Learning - ### abbr가 전체 문구와 설명을 제공 { #the-abbr-gives-a-full-phrase-and-an-explanation } * MDN @@ -224,6 +219,11 @@ works(foo="bar") # 이건 동작합니다 🎉 //// +## HTML "dfn" 요소 { #html-dfn-elements } + +* 클러스터 +* 딥 러닝 + ## 제목 { #headings } //// tab | 테스트 diff --git a/docs/pt/docs/_llm-test.md b/docs/pt/docs/_llm-test.md index b59292f47..3a2801fdd 100644 --- a/docs/pt/docs/_llm-test.md +++ b/docs/pt/docs/_llm-test.md @@ -197,15 +197,10 @@ Aqui estão algumas coisas envolvidas em elementos HTML "abbr" (algumas são inv ### O abbr fornece uma frase completa { #the-abbr-gives-a-full-phrase } -* GTD -* lt -* XWT -* PSGI - -### O abbr fornece uma explicação { #the-abbr-gives-an-explanation } - -* cluster -* Deep Learning +* GTD +* lt +* XWT +* PSGI ### O abbr fornece uma frase completa e uma explicação { #the-abbr-gives-a-full-phrase-and-an-explanation } @@ -224,6 +219,11 @@ Veja a seção `### HTML abbr elements` no prompt geral em `scripts/translate.py //// +## Elementos HTML "dfn" { #html-dfn-elements } + +* cluster +* Deep Learning + ## Títulos { #headings } //// tab | Teste diff --git a/docs/ru/docs/_llm-test.md b/docs/ru/docs/_llm-test.md index 6a0272f3a..247d3a964 100644 --- a/docs/ru/docs/_llm-test.md +++ b/docs/ru/docs/_llm-test.md @@ -202,11 +202,6 @@ works(foo="bar") # Это работает 🎉 * XWT * PSGI -### abbr даёт объяснение { #the-abbr-gives-an-explanation } - -* кластер -* Глубокое обучение - ### abbr даёт полную расшифровку и объяснение { #the-abbr-gives-a-full-phrase-and-an-explanation } * MDN @@ -224,6 +219,11 @@ works(foo="bar") # Это работает 🎉 //// +## HTML-элементы "dfn" { #html-dfn-elements } + +* кластер +* Глубокое обучение + ## Заголовки { #headings } //// tab | Тест diff --git a/scripts/general-llm-prompt.md b/scripts/general-llm-prompt.md index d45ab8eb0..23926bd8e 100644 --- a/scripts/general-llm-prompt.md +++ b/scripts/general-llm-prompt.md @@ -353,7 +353,9 @@ Erstelle eine [Virtuelle Umgebung](../virtual-environments.md){.internal-link ta Translate HTML abbr elements (`text`) as follows: -- If the text surrounded by the abbr element is an abbreviation (the text may be surrounded by further HTML or Markdown markup or quotes, for example text or `text` or "text", ignore that further markup when deciding if the text is an abbreviation), and if the description (the text inside the title attribute) contains the full phrase for this abbreviation, then append a dash (-) to the full phrase, followed by the translation of the full phrase. +- The text inside abbr tag may be surrounded by further HTML or Markdown markup or quotes, for example text or `text` or "text". Preserve markup and only translate visible text inside the abbr element. + +- If the description (the text inside the title attribute) contains the full phrase for this abbreviation, then append a dash (-) to the full phrase, followed by the translation of the full phrase. Conversion scheme: @@ -421,45 +423,7 @@ Result (German): ASGI ``` -- If the description is not a full phrase for an abbreviation which the abbr element surrounds, but some other information, then just translate the description. - -Conversion scheme: - -Source (English): - -``` -{text} -``` - -Result: - -``` -{translation of text} -``` - -Examples: - - Source (English): - -``` -path -linter -parsing -0.95.0 -at the time of writing this -``` - -Result (German): - -``` -Pfad -Linter -Parsen -0.95.0 -zum Zeitpunkt als das hier geschrieben wurde -``` - -- If the text surrounded by the abbr element is an abbreviation and the description contains both the full phrase for that abbreviation, and other information, separated by a colon (`:`), then append a dash (`-`) and the translation of the full phrase to the original full phrase and translate the other information. +- If the title of abbr element contains a full phrase for that abbreviation, and other information, separated by a colon (`:`), then append a dash (`-`) and the translation of the full phrase to the original full phrase and translate the other information. Conversion scheme: @@ -526,3 +490,23 @@ Result (German): - If there is an existing translation, and it has ADDITIONAL abbr elements in a sentence, and these additional abbr elements do not exist in the related sentence in the English text, then KEEP those additional abbr elements in the translation. Do not remove them. Except when you remove the whole sentence from the translation, because the whole sentence was removed from the English text, then also remove the abbr element. The reasoning for this rule is, that such additional abbr elements are manually added by the human editor of the translation, in order to translate or explain an English word to the human readers of the translation. These additional abbr elements would not make sense in the English text, but they do make sense in the translation. So keep them in the translation, even though they are not part of the English text. This rule only applies to abbr elements. - Apply above rules also when there is an existing translation! Make sure that all title attributes in abbr elements get properly translated or updated, using the schemes given above. However, leave the ADDITIONAL abbr's described above alone. Do not change their formatting or content. + +### HTML dfn elements + +For HTML dfn elements (`text`), translate the text inside the dfn element and the title attribute. Do not include the original English text in the title attribute. + +Examples: + +Source (English): + +``` +path +linter +``` + +Result (German): + +``` +Pfad +Linter +``` From 65da3dde120c7efee7868890ff3e6e788a860ed6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Feb 2026 11:56:45 +0000 Subject: [PATCH 088/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bd4a6726e..e46f58509 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -22,6 +22,7 @@ hide: ### Internal +* 🔧 Update LLM-prompt for `abbr` and `dfn` tags. PR [#14747](https://github.com/fastapi/fastapi/pull/14747) by [@YuriiMotov](https://github.com/YuriiMotov). * ✅ Test order for the submitted byte Files. PR [#14828](https://github.com/fastapi/fastapi/pull/14828) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). * 🔧 Configure `test` workflow to run tests with `inline-snapshot=review`. PR [#14876](https://github.com/fastapi/fastapi/pull/14876) by [@YuriiMotov](https://github.com/YuriiMotov). From 6ce5e3e961876c39d8a60a3a2cb4147a6748d116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 10 Feb 2026 04:14:38 -0800 Subject: [PATCH 089/195] =?UTF-8?q?=E2=9C=85=20Tweak=20comment=20in=20test?= =?UTF-8?q?=20to=20reference=20PR=20(#14885)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_list_bytes_file_order_preserved_issue_14811.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_list_bytes_file_order_preserved_issue_14811.py b/tests/test_list_bytes_file_order_preserved_issue_14811.py index 399235bdb..dbf0a7818 100644 --- a/tests/test_list_bytes_file_order_preserved_issue_14811.py +++ b/tests/test_list_bytes_file_order_preserved_issue_14811.py @@ -1,7 +1,7 @@ """ Regression test: preserve order when using list[bytes] + File() See https://github.com/fastapi/fastapi/discussions/14811 -Related: PR #3372 +Fixed in PR: https://github.com/fastapi/fastapi/pull/14884 """ from typing import Annotated From 5bb3423205daf98f88f14d696d104fea2221028a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Feb 2026 12:15:02 +0000 Subject: [PATCH 090/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e46f58509..bdf15719b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -22,6 +22,7 @@ hide: ### Internal +* ✅ Tweak comment in test to reference PR. PR [#14885](https://github.com/fastapi/fastapi/pull/14885) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update LLM-prompt for `abbr` and `dfn` tags. PR [#14747](https://github.com/fastapi/fastapi/pull/14747) by [@YuriiMotov](https://github.com/YuriiMotov). * ✅ Test order for the submitted byte Files. PR [#14828](https://github.com/fastapi/fastapi/pull/14828) by [@valentinDruzhinin](https://github.com/valentinDruzhinin). * 🔧 Configure `test` workflow to run tests with `inline-snapshot=review`. PR [#14876](https://github.com/fastapi/fastapi/pull/14876) by [@YuriiMotov](https://github.com/YuriiMotov). From 8f82c94de0ff641a239db59e58dfdc6010a4b26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 10 Feb 2026 13:24:38 +0100 Subject: [PATCH 091/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?8.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bdf15719b..87aa6d261 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.128.7 + ### Features * ✨ Show a clear error on attempt to include router into itself. PR [#14258](https://github.com/fastapi/fastapi/pull/14258) by [@JavierSanchezCastro](https://github.com/JavierSanchezCastro). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 95e57e2eb..a9fe3e971 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.128.6" +__version__ = "0.128.7" from starlette import status as status From f0f3e7a113b7771388aef6d4a4cb863ce3890d9c Mon Sep 17 00:00:00 2001 From: Sanjana S <158697809+SanjanaS10@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:56:10 +0530 Subject: [PATCH 092/195] =?UTF-8?q?=F0=9F=93=9D=20Fix=20grammar=20in=20`do?= =?UTF-8?q?cs/en/docs/tutorial/first-steps.md`=20(#14708)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/tutorial/first-steps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/tutorial/first-steps.md b/docs/en/docs/tutorial/first-steps.md index a3263593d..ceae6c475 100644 --- a/docs/en/docs/tutorial/first-steps.md +++ b/docs/en/docs/tutorial/first-steps.md @@ -54,7 +54,7 @@ In the output, there's a line with something like: INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` -That line shows the URL where your app is being served, in your local machine. +That line shows the URL where your app is being served on your local machine. ### Check it { #check-it } From 93fa935fb8ac1c40488da7bf9b134466588ffaee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 10 Feb 2026 12:26:38 +0000 Subject: [PATCH 093/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 87aa6d261..bf8850828 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Docs + +* 📝 Fix grammar in `docs/en/docs/tutorial/first-steps.md`. PR [#14708](https://github.com/fastapi/fastapi/pull/14708) by [@SanjanaS10](https://github.com/SanjanaS10). + ## 0.128.7 ### Features From ffb8965260c71657f26644673f218b99b1315cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 11 Feb 2026 04:33:49 -0800 Subject: [PATCH 094/195] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Update=20build=20s?= =?UTF-8?q?etup=20for=20`fastapi-slim`,=20deprecate=20it,=20and=20make=20i?= =?UTF-8?q?t=20only=20depend=20on=20`fastapi`=20(#14894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-redistribute.yml | 9 ----- fastapi-slim/README.md | 54 +++++++++++++++++++++++++ pdm_build.py | 28 +++++++++++-- pyproject.toml | 23 +++++++++++ 4 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 fastapi-slim/README.md diff --git a/.github/workflows/test-redistribute.yml b/.github/workflows/test-redistribute.yml index 0491d33ba..ad9df4bf9 100644 --- a/.github/workflows/test-redistribute.yml +++ b/.github/workflows/test-redistribute.yml @@ -12,11 +12,6 @@ on: jobs: test-redistribute: runs-on: ubuntu-latest - strategy: - matrix: - package: - - fastapi - - fastapi-slim steps: - name: Dump GitHub context env: @@ -30,8 +25,6 @@ jobs: - name: Install build dependencies run: pip install build - name: Build source distribution - env: - TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} run: python -m build --sdist - name: Decompress source distribution run: | @@ -41,8 +34,6 @@ jobs: run: | cd dist/fastapi*/ pip install --group tests --editable .[all] - env: - TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} - name: Run source distribution tests run: | cd dist/fastapi*/ diff --git a/fastapi-slim/README.md b/fastapi-slim/README.md new file mode 100644 index 000000000..e378a9c8c --- /dev/null +++ b/fastapi-slim/README.md @@ -0,0 +1,54 @@ +

+ FastAPI +

+

+ FastAPI framework, high performance, easy to learn, fast to code, ready for production +

+

+ + Test + + + Coverage + + + Package version + + + Supported Python versions + +

+ +--- + +**Documentation**: https://fastapi.tiangolo.com + +**Source Code**: https://github.com/fastapi/fastapi + +--- + +FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints. + +## `fastapi-slim` + +⚠️ Do not install this package. ⚠️ + +This package, `fastapi-slim`, does nothing other than depend on `fastapi`. + +All the functionality has been integrated into `fastapi`. + +The only reason this package exists is as a migration path for old projects that used to depend on `fastapi-slim`, so that they can get the latest version of `fastapi`. + +You **should not** install this package. + +Install instead: + +```bash +pip install fastapi +``` + +This package is deprecated and will stop receiving any updates and published versions. + +## License + +This project is licensed under the terms of the MIT license. diff --git a/pdm_build.py b/pdm_build.py index a0eb88eeb..67e71cbb0 100644 --- a/pdm_build.py +++ b/pdm_build.py @@ -3,18 +3,38 @@ from typing import Any from pdm.backend.hooks import Context -TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE", "fastapi") +TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE", "typer") def pdm_build_initialize(context: Context) -> None: metadata = context.config.metadata + # Get main version + version = metadata["version"] # Get custom config for the current package, from the env var - config: dict[str, Any] = context.config.data["tool"]["tiangolo"][ + all_configs_config: dict[str, Any] = context.config.data["tool"]["tiangolo"][ "_internal-slim-build" - ]["packages"].get(TIANGOLO_BUILD_PACKAGE) - if not config: + ]["packages"] + + if TIANGOLO_BUILD_PACKAGE not in all_configs_config: return + + config = all_configs_config[TIANGOLO_BUILD_PACKAGE] project_config: dict[str, Any] = config["project"] # Override main [project] configs with custom configs for this package for key, value in project_config.items(): metadata[key] = value + # Get custom build config for the current package + build_config: dict[str, Any] = ( + config.get("tool", {}).get("pdm", {}).get("build", {}) + ) + # Override PDM build config with custom build config for this package + for key, value in build_config.items(): + context.config.build_config[key] = value + # Get main dependencies + dependencies: list[str] = metadata.get("dependencies", []) + # Sync versions in dependencies + new_dependencies = [] + for dep in dependencies: + new_dep = f"{dep}>={version}" + new_dependencies.append(new_dep) + metadata["dependencies"] = new_dependencies diff --git a/pyproject.toml b/pyproject.toml index 4895c2d34..d8c43f7a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -202,6 +202,29 @@ source-includes = [ [tool.tiangolo._internal-slim-build.packages.fastapi-slim.project] name = "fastapi-slim" +readme = "fastapi-slim/README.md" +dependencies = [ + "fastapi", +] +optional-dependencies = {} +scripts = {} + +[tool.tiangolo._internal-slim-build.packages.fastapi-slim.tool.pdm.build] +# excludes needs to explicitly exclude the top level python packages, +# otherwise PDM includes them by default +# A "*" glob pattern can't be used here because in PDM internals, the patterns are put +# in a set (unordered, order varies) and each excluded file is assigned one of the +# glob patterns that matches, as the set is unordered, the matched pattern could be "*" +# independent of the order here. And then the internal code would give it a lower score +# than the one for a default included file. +# By not using "*" and explicitly excluding the top level packages, they get a higher +# score than the default inclusion +excludes = ["fastapi", "tests", "pdm_build.py"] +# source-includes needs to explicitly define some value because PDM will check the +# truthy value of the list, and if empty, will include some defaults, including "tests", +# an empty string doesn't match anything, but makes the list truthy, so that PDM +# doesn't override it during the build. +source-includes = [""] [tool.mypy] plugins = ["pydantic.mypy"] From 417f1ee07825c6f4e983629bee6039857197112f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Feb 2026 12:34:12 +0000 Subject: [PATCH 095/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bf8850828..014bae1ab 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -11,6 +11,10 @@ hide: * 📝 Fix grammar in `docs/en/docs/tutorial/first-steps.md`. PR [#14708](https://github.com/fastapi/fastapi/pull/14708) by [@SanjanaS10](https://github.com/SanjanaS10). +### Internal + +* ♻️ Update build setup for `fastapi-slim`, deprecate it, and make it only depend on `fastapi`. PR [#14894](https://github.com/fastapi/fastapi/pull/14894) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.7 ### Features From aac30fd7071d79798130137728aeb363c8c5f695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 11 Feb 2026 05:37:09 -0800 Subject: [PATCH 096/195] =?UTF-8?q?=F0=9F=94=A8=20Tweak=20PDM=20hook=20scr?= =?UTF-8?q?ipt=20(#14895)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pdm_build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdm_build.py b/pdm_build.py index 67e71cbb0..b1b662bd3 100644 --- a/pdm_build.py +++ b/pdm_build.py @@ -3,7 +3,7 @@ from typing import Any from pdm.backend.hooks import Context -TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE", "typer") +TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE") def pdm_build_initialize(context: Context) -> None: From 1ed9bd49230b192bb531f4195d3d6a309261a777 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Feb 2026 13:37:37 +0000 Subject: [PATCH 097/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 014bae1ab..65668c8ab 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Internal +* 🔨 Tweak PDM hook script. PR [#14895](https://github.com/fastapi/fastapi/pull/14895) by [@tiangolo](https://github.com/tiangolo). * ♻️ Update build setup for `fastapi-slim`, deprecate it, and make it only depend on `fastapi`. PR [#14894](https://github.com/fastapi/fastapi/pull/14894) by [@tiangolo](https://github.com/tiangolo). ## 0.128.7 From bdd20051c42d08762b2cdcc6571e4356014de572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 11 Feb 2026 16:16:34 +0100 Subject: [PATCH 098/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?8.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 65668c8ab..be11b7827 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.128.8 + ### Docs * 📝 Fix grammar in `docs/en/docs/tutorial/first-steps.md`. PR [#14708](https://github.com/fastapi/fastapi/pull/14708) by [@SanjanaS10](https://github.com/SanjanaS10). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index a9fe3e971..9931216ee 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.128.7" +__version__ = "0.128.8" from starlette import status as status From ad4e8e006016e088dbccdb73305a58a1338b1ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 11 Feb 2026 10:32:12 -0800 Subject: [PATCH 099/195] =?UTF-8?q?=E2=9E=96=20Drop=20support=20for=20Pyth?= =?UTF-8?q?on=203.9=20(#14897)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- .github/workflows/test.yml | 6 +- .../en/docs/advanced/advanced-python-types.md | 61 + docs/en/docs/deployment/docker.md | 8 +- docs/en/docs/python-types.md | 156 +- docs/en/docs/tutorial/body-multiple-params.md | 7 - docs/en/docs/tutorial/body.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +- .../tutorial/dependencies/sub-dependencies.md | 4 +- docs/en/docs/tutorial/extra-models.md | 4 +- .../tutorial/query-params-str-validations.md | 24 - docs/en/mkdocs.yml | 1 + fastapi/_compat/shared.py | 30 +- fastapi/dependencies/utils.py | 77 +- pyproject.toml | 3 +- tests/test_dependencies_utils.py | 8 + uv.lock | 2300 +++-------------- 16 files changed, 485 insertions(+), 2230 deletions(-) create mode 100644 docs/en/docs/advanced/advanced-python-types.md create mode 100644 tests/test_dependencies_utils.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3fb837c04..338f6c390 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,14 +56,10 @@ jobs: - starlette-pypi - starlette-git include: - - os: ubuntu-latest - python-version: "3.9" - coverage: coverage - uv-resolution: lowest-direct - os: macos-latest python-version: "3.10" coverage: coverage - uv-resolution: highest + uv-resolution: lowest-direct - os: windows-latest python-version: "3.12" coverage: coverage diff --git a/docs/en/docs/advanced/advanced-python-types.md b/docs/en/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..6495cbe44 --- /dev/null +++ b/docs/en/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# Advanced Python Types { #advanced-python-types } + +Here are some additional ideas that might be useful when working with Python types. + +## Using `Union` or `Optional` { #using-union-or-optional } + +If your code for some reason can't use `|`, for example if it's not in a type annotation but in something like `response_model=`, instead of using the vertical bar (`|`) you can use `Union` from `typing`. + +For example, you could declare that something could be a `str` or `None`: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +`typing` also has a shortcut to declare that something could be `None`, with `Optional`. + +Here's a tip from my very **subjective** point of view: + +* 🚨 Avoid using `Optional[SomeType]` +* Instead ✨ **use `Union[SomeType, None]`** ✨. + +Both are equivalent and underneath they are the same, but I would recommend `Union` instead of `Optional` because the word "**optional**" would seem to imply that the value is optional, and it actually means "it can be `None`", even if it's not optional and is still required. + +I think `Union[SomeType, None]` is more explicit about what it means. + +It's just about the words and names. But those words can affect how you and your teammates think about the code. + +As an example, let's take this function: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter: + +```Python +say_hi() # Oh, no, this throws an error! 😱 +``` + +The `name` parameter is **still required** (not *optional*) because it doesn't have a default value. Still, `name` accepts `None` as the value: + +```Python +say_hi(name=None) # This works, None is valid 🎉 +``` + +The good news is, in most cases, you will be able to simply use `|` to define unions of types: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +So, normally you don't have to worry about names like `Optional` and `Union`. 😎 diff --git a/docs/en/docs/deployment/docker.md b/docs/en/docs/deployment/docker.md index 0f15a60a4..4806d779d 100644 --- a/docs/en/docs/deployment/docker.md +++ b/docs/en/docs/deployment/docker.md @@ -14,7 +14,7 @@ In a hurry and already know this stuff? Jump to the [`Dockerfile` below 👇](#b Dockerfile Preview 👀 ```Dockerfile -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -166,7 +166,7 @@ Now in the same project directory create a file `Dockerfile` with: ```{ .dockerfile .annotate } # (1)! -FROM python:3.9 +FROM python:3.14 # (2)! WORKDIR /code @@ -390,7 +390,7 @@ If your FastAPI is a single file, for example, `main.py` without an `./app` dire Then you would just have to change the corresponding paths to copy the file inside the `Dockerfile`: ```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -499,7 +499,7 @@ Of course, there are **special cases** where you could want to have **a containe In those cases, you can use the `--workers` command line option to set the number of workers that you want to run: ```{ .dockerfile .annotate } -FROM python:3.9 +FROM python:3.14 WORKDIR /code diff --git a/docs/en/docs/python-types.md b/docs/en/docs/python-types.md index 6de170ada..f2ad35d71 100644 --- a/docs/en/docs/python-types.md +++ b/docs/en/docs/python-types.md @@ -135,27 +135,30 @@ You can use, for example: {* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} -### Generic types with type parameters { #generic-types-with-type-parameters } +### `typing` module { #typing-module } -There are some data structures that can contain other values, like `dict`, `list`, `set` and `tuple`. And the internal values can have their own type too. +For some additional use cases, you might need to import some things from the standard library `typing` module, for example when you want to declare that something has "any type", you can use `Any` from `typing`: -These types that have internal types are called "**generic**" types. And it's possible to declare them, even with their internal types. +```python +from typing import Any -To declare those types and the internal types, you can use the standard Python module `typing`. It exists specifically to support these type hints. -#### Newer versions of Python { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -The syntax using `typing` is **compatible** with all versions, from Python 3.6 to the latest ones, including Python 3.9, Python 3.10, etc. +### Generic types { #generic-types } -As Python advances, **newer versions** come with improved support for these type annotations and in many cases you won't even need to import and use the `typing` module to declare the type annotations. +Some types can take "type parameters" in square brackets, to define their internal types, for example a "list of strings" would be declared `list[str]`. -If you can choose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity. +These types that can take type parameters are called **Generic types** or **Generics**. -In all the docs there are examples compatible with each version of Python (when there's a difference). +You can use the same builtin types as generics (with square brackets and types inside): -For example "**Python 3.6+**" means it's compatible with Python 3.6 or above (including 3.7, 3.8, 3.9, 3.10, etc). And "**Python 3.9+**" means it's compatible with Python 3.9 or above (including 3.10, etc). - -If you can use the **latest versions of Python**, use the examples for the latest version, those will have the **best and simplest syntax**, for example, "**Python 3.10+**". +* `list` +* `tuple` +* `set` +* `dict` #### List { #list } @@ -220,44 +223,20 @@ This means: You can declare that a variable can be any of **several types**, for example, an `int` or a `str`. -In Python 3.6 and above (including Python 3.10) you can use the `Union` type from `typing` and put inside the square brackets the possible types to accept. +To define it you use the vertical bar (`|`) to separate both types. -In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a vertical bar (`|`). - -//// tab | Python 3.10+ +This is called a "union", because the variable can be anything in the union of those two sets of types. ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -In both cases this means that `item` could be an `int` or a `str`. +This means that `item` could be an `int` or a `str`. #### Possibly `None` { #possibly-none } You can declare that a value could have a type, like `str`, but that it could also be `None`. -In Python 3.6 and above (including Python 3.10) you can declare it by importing and using `Optional` from the `typing` module. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -Using `Optional[str]` instead of just `str` will let the editor help you detect errors where you could be assuming that a value is always a `str`, when it could actually be `None` too. - -`Optional[Something]` is actually a shortcut for `Union[Something, None]`, they are equivalent. - -This also means that in Python 3.10, you can use `Something | None`: - //// tab | Python 3.10+ ```Python hl_lines="1" @@ -266,96 +245,7 @@ This also means that in Python 3.10, you can use `Something | None`: //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ alternative - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### Using `Union` or `Optional` { #using-union-or-optional } - -If you are using a Python version below 3.10, here's a tip from my very **subjective** point of view: - -* 🚨 Avoid using `Optional[SomeType]` -* Instead ✨ **use `Union[SomeType, None]`** ✨. - -Both are equivalent and underneath they are the same, but I would recommend `Union` instead of `Optional` because the word "**optional**" would seem to imply that the value is optional, and it actually means "it can be `None`", even if it's not optional and is still required. - -I think `Union[SomeType, None]` is more explicit about what it means. - -It's just about the words and names. But those words can affect how you and your teammates think about the code. - -As an example, let's take this function: - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter: - -```Python -say_hi() # Oh, no, this throws an error! 😱 -``` - -The `name` parameter is **still required** (not *optional*) because it doesn't have a default value. Still, `name` accepts `None` as the value: - -```Python -say_hi(name=None) # This works, None is valid 🎉 -``` - -The good news is, once you are on Python 3.10 you won't have to worry about that, as you will be able to simply use `|` to define unions of types: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -And then you won't have to worry about names like `Optional` and `Union`. 😎 - -#### Generic types { #generic-types } - -These types that take type parameters in square brackets are called **Generic types** or **Generics**, for example: - -//// tab | Python 3.10+ - -You can use the same builtin types as generics (with square brackets and types inside): - -* `list` -* `tuple` -* `set` -* `dict` - -And the same as with previous Python versions, from the `typing` module: - -* `Union` -* `Optional` -* ...and others. - -In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the vertical bar (`|`) to declare unions of types, that's a lot better and simpler. - -//// - -//// tab | Python 3.9+ - -You can use the same builtin types as generics (with square brackets and types inside): - -* `list` -* `tuple` -* `set` -* `dict` - -And generics from the `typing` module: - -* `Union` -* `Optional` -* ...and others. - -//// +Using `str | None` instead of just `str` will let the editor help you detect errors where you could be assuming that a value is always a `str`, when it could actually be `None` too. ### Classes as types { #classes-as-types } @@ -403,17 +293,11 @@ To learn more about Required Optional fields. - -/// - ## Type Hints with Metadata Annotations { #type-hints-with-metadata-annotations } Python also has a feature that allows putting **additional metadata** in these type hints using `Annotated`. -Since Python 3.9, `Annotated` is a part of the standard library, so you can import it from `typing`. +You can import `Annotated` from `typing`. {* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} diff --git a/docs/en/docs/tutorial/body-multiple-params.md b/docs/en/docs/tutorial/body-multiple-params.md index bb0c58368..d904fb839 100644 --- a/docs/en/docs/tutorial/body-multiple-params.md +++ b/docs/en/docs/tutorial/body-multiple-params.md @@ -106,13 +106,6 @@ As, by default, singular values are interpreted as query parameters, you don't h q: str | None = None ``` -Or in Python 3.9: - -```Python -q: Union[str, None] = None -``` - - For example: {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/en/docs/tutorial/body.md b/docs/en/docs/tutorial/body.md index 2d0dfcbb5..fb4471836 100644 --- a/docs/en/docs/tutorial/body.md +++ b/docs/en/docs/tutorial/body.md @@ -155,7 +155,7 @@ The function parameters will be recognized as follows: FastAPI will know that the value of `q` is not required because of the default value `= None`. -The `str | None` (Python 3.10+) or `Union` in `Union[str, None]` (Python 3.9+) is not used by FastAPI to determine that the value is not required, it will know it's not required because it has a default value of `= None`. +The `str | None` is not used by FastAPI to determine that the value is not required, it will know it's not required because it has a default value of `= None`. But adding the type annotations will allow your editor to give you better support and detect errors. diff --git a/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md index 0a6a786b5..d9a02d5b8 100644 --- a/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ Now you can declare your dependency using this class. Notice how we write `CommonQueryParams` twice in the above code: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip @@ -137,7 +137,7 @@ It is from this one that FastAPI will extract the declared parameters and that i In this case, the first `CommonQueryParams`, in: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip @@ -163,7 +163,7 @@ commons: CommonQueryParams ... You could actually write just: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip @@ -197,7 +197,7 @@ But declaring the type is encouraged as that way your editor will know what will But you see that we are having some code repetition here, writing `CommonQueryParams` twice: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip @@ -225,7 +225,7 @@ For those specific cases, you can do the following: Instead of writing: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...you write: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip diff --git a/docs/en/docs/tutorial/dependencies/sub-dependencies.md b/docs/en/docs/tutorial/dependencies/sub-dependencies.md index b9a4e3ce7..99588dd3c 100644 --- a/docs/en/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/en/docs/tutorial/dependencies/sub-dependencies.md @@ -62,7 +62,7 @@ And it will save the returned value in a TypeGuard[type[_T]]: try: return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type] @@ -57,13 +55,13 @@ def lenient_issubclass( raise # pragma: no cover -def _annotation_is_sequence(annotation: Union[type[Any], None]) -> bool: +def _annotation_is_sequence(annotation: type[Any] | None) -> bool: if lenient_issubclass(annotation, (str, bytes)): return False return lenient_issubclass(annotation, sequence_types) -def field_annotation_is_sequence(annotation: Union[type[Any], None]) -> bool: +def field_annotation_is_sequence(annotation: type[Any] | None) -> bool: origin = get_origin(annotation) if origin is Union or origin is UnionType: for arg in get_args(annotation): @@ -79,7 +77,7 @@ def value_is_sequence(value: Any) -> bool: return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) -def _annotation_is_complex(annotation: Union[type[Any], None]) -> bool: +def _annotation_is_complex(annotation: type[Any] | None) -> bool: return ( lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile)) or _annotation_is_sequence(annotation) @@ -87,7 +85,7 @@ def _annotation_is_complex(annotation: Union[type[Any], None]) -> bool: ) -def field_annotation_is_complex(annotation: Union[type[Any], None]) -> bool: +def field_annotation_is_complex(annotation: type[Any] | None) -> bool: origin = get_origin(annotation) if origin is Union or origin is UnionType: return any(field_annotation_is_complex(arg) for arg in get_args(annotation)) @@ -108,7 +106,7 @@ def field_annotation_is_scalar(annotation: Any) -> bool: return annotation is Ellipsis or not field_annotation_is_complex(annotation) -def field_annotation_is_scalar_sequence(annotation: Union[type[Any], None]) -> bool: +def field_annotation_is_scalar_sequence(annotation: type[Any] | None) -> bool: origin = get_origin(annotation) if origin is Union or origin is UnionType: at_least_one_scalar_sequence = False diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 23d8cd9fb..ab18ec2db 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,18 +1,19 @@ import dataclasses import inspect import sys -from collections.abc import Mapping, Sequence +from collections.abc import Callable, Mapping, Sequence from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy from dataclasses import dataclass from typing import ( Annotated, Any, - Callable, ForwardRef, - Optional, + Literal, Union, cast, + get_args, + get_origin, ) from fastapi import params @@ -63,7 +64,6 @@ from starlette.datastructures import ( from starlette.requests import HTTPConnection, Request from starlette.responses import Response from starlette.websockets import WebSocket -from typing_extensions import Literal, get_args, get_origin from typing_inspection.typing_objects import is_typealiastype multipart_not_installed_error = ( @@ -127,8 +127,8 @@ def get_flat_dependant( dependant: Dependant, *, skip_repeats: bool = False, - visited: Optional[list[DependencyCacheKey]] = None, - parent_oauth_scopes: Optional[list[str]] = None, + visited: list[DependencyCacheKey] | None = None, + parent_oauth_scopes: list[str] | None = None, ) -> Dependant: if visited is None: visited = [] @@ -199,20 +199,17 @@ def get_flat_params(dependant: Dependant) -> list[ModelField]: def _get_signature(call: Callable[..., Any]) -> inspect.Signature: - if sys.version_info >= (3, 10): - try: - signature = inspect.signature(call, eval_str=True) - except NameError: - # Handle type annotations with if TYPE_CHECKING, not used by FastAPI - # e.g. dependency return types - if sys.version_info >= (3, 14): - from annotationlib import Format + try: + signature = inspect.signature(call, eval_str=True) + except NameError: + # Handle type annotations with if TYPE_CHECKING, not used by FastAPI + # e.g. dependency return types + 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) + signature = inspect.signature(call, annotation_format=Format.FORWARDREF) + else: + signature = inspect.signature(call) return signature @@ -258,11 +255,11 @@ def get_dependant( *, path: str, call: Callable[..., Any], - name: Optional[str] = None, - own_oauth_scopes: Optional[list[str]] = None, - parent_oauth_scopes: Optional[list[str]] = None, + name: str | None = None, + own_oauth_scopes: list[str] | None = None, + parent_oauth_scopes: list[str] | None = None, use_cache: bool = True, - scope: Union[Literal["function", "request"], None] = None, + scope: Literal["function", "request"] | None = None, ) -> Dependant: dependant = Dependant( call=call, @@ -331,7 +328,7 @@ def get_dependant( def add_non_field_param_to_dependency( *, param_name: str, type_annotation: Any, dependant: Dependant -) -> Optional[bool]: +) -> bool | None: if lenient_issubclass(type_annotation, Request): dependant.request_param_name = param_name return True @@ -356,8 +353,8 @@ def add_non_field_param_to_dependency( @dataclass class ParamDetails: type_annotation: Any - depends: Optional[params.Depends] - field: Optional[ModelField] + depends: params.Depends | None + field: ModelField | None def analyze_param( @@ -399,7 +396,7 @@ def analyze_param( ) ] if fastapi_specific_annotations: - fastapi_annotation: Union[FieldInfo, params.Depends, None] = ( + fastapi_annotation: FieldInfo | params.Depends | None = ( fastapi_specific_annotations[-1] ) else: @@ -560,20 +557,20 @@ async def _solve_generator( class SolvedDependency: values: dict[str, Any] errors: list[Any] - background_tasks: Optional[StarletteBackgroundTasks] + background_tasks: StarletteBackgroundTasks | None response: Response dependency_cache: dict[DependencyCacheKey, Any] async def solve_dependencies( *, - request: Union[Request, WebSocket], + request: Request | WebSocket, dependant: Dependant, - body: Optional[Union[dict[str, Any], FormData]] = None, - background_tasks: Optional[StarletteBackgroundTasks] = None, - response: Optional[Response] = None, - dependency_overrides_provider: Optional[Any] = None, - dependency_cache: Optional[dict[DependencyCacheKey, Any]] = None, + body: dict[str, Any] | FormData | None = None, + background_tasks: StarletteBackgroundTasks | None = None, + response: Response | None = None, + dependency_overrides_provider: Any | None = None, + dependency_cache: dict[DependencyCacheKey, Any] | None = None, # TODO: remove this parameter later, no longer used, not removing it yet as some # people might be monkey patching this function (although that's not supported) async_exit_stack: AsyncExitStack, @@ -721,7 +718,7 @@ def _is_json_field(field: ModelField) -> bool: def _get_multidict_value( - field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None + field: ModelField, values: Mapping[str, Any], alias: str | None = None ) -> Any: alias = alias or get_validation_alias(field) if ( @@ -753,7 +750,7 @@ def _get_multidict_value( def request_params_to_args( fields: Sequence[ModelField], - received_params: Union[Mapping[str, Any], QueryParams, Headers], + received_params: Mapping[str, Any] | QueryParams | Headers, ) -> tuple[dict[str, Any], list[Any]]: values: dict[str, Any] = {} errors: list[dict[str, Any]] = [] @@ -901,7 +898,7 @@ async def _extract_form_body( ): # For types assert isinstance(value, sequence_types) - results: list[Union[bytes, str]] = [] + results: list[bytes | str] = [] for sub_value in value: results.append(await sub_value.read()) value = serialize_sequence_value(field=field, value=results) @@ -920,7 +917,7 @@ async def _extract_form_body( async def request_body_to_args( body_fields: list[ModelField], - received_body: Optional[Union[dict[str, Any], FormData]], + received_body: dict[str, Any] | FormData | None, embed_body_fields: bool, ) -> tuple[dict[str, Any], list[dict[str, Any]]]: values: dict[str, Any] = {} @@ -950,7 +947,7 @@ async def request_body_to_args( return {first_field.name: v_}, errors_ for field in body_fields: loc = ("body", get_validation_alias(field)) - value: Optional[Any] = None + value: Any | None = None if body_to_process is not None: try: value = body_to_process.get(get_validation_alias(field)) @@ -970,7 +967,7 @@ async def request_body_to_args( def get_body_field( *, flat_dependant: Dependant, name: str, embed_body_fields: bool -) -> Optional[ModelField]: +) -> ModelField | None: """ Get a ModelField representing the request body for a path operation, combining all body parameters into a single field if necessary. diff --git a/pyproject.toml b/pyproject.toml index d8c43f7a4..e1ce30507 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ description = "FastAPI framework, high performance, easy to learn, fast to code, readme = "README.md" license = "MIT" license-files = ["LICENSE"] -requires-python = ">=3.9" +requires-python = ">=3.10" authors = [ { name = "Sebastián Ramírez", email = "tiangolo@gmail.com" }, ] @@ -33,7 +33,6 @@ classifiers = [ "Framework :: Pydantic :: 2", "Intended Audience :: Developers", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/tests/test_dependencies_utils.py b/tests/test_dependencies_utils.py new file mode 100644 index 000000000..9257d1c9e --- /dev/null +++ b/tests/test_dependencies_utils.py @@ -0,0 +1,8 @@ +from fastapi.dependencies.utils import get_typed_annotation + + +def test_get_typed_annotation(): + # For coverage + annotation = "None" + typed_annotation = get_typed_annotation(annotation, globals()) + assert typed_annotation is None diff --git a/uv.lock b/uv.lock index 9a807f8b4..736b0e145 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,9 @@ version = 1 revision = 3 -requires-python = ">=3.9" +requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", - "python_full_version < '3.10'", + "python_full_version < '3.14'", ] [[package]] @@ -158,23 +157,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801, upload-time = "2026-01-03T17:32:20.25Z" }, { url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523, upload-time = "2026-01-03T17:32:22.215Z" }, { url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694, upload-time = "2026-01-03T17:32:24.546Z" }, - { url = "https://files.pythonhosted.org/packages/bf/79/446655656861d3e7e2c32bfcf160c7aa9e9dc63776a691b124dba65cdd77/aiohttp-3.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31a83ea4aead760dfcb6962efb1d861db48c34379f2ff72db9ddddd4cda9ea2e", size = 741433, upload-time = "2026-01-03T17:32:26.453Z" }, - { url = "https://files.pythonhosted.org/packages/cb/49/773c4b310b5140d2fb5e79bb0bf40b7b41dad80a288ca1a8759f5f72bda9/aiohttp-3.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:988a8c5e317544fdf0d39871559e67b6341065b87fceac641108c2096d5506b7", size = 497332, upload-time = "2026-01-03T17:32:28.37Z" }, - { url = "https://files.pythonhosted.org/packages/bc/31/1dcbc4b83a4e6f76a0ad883f07f21ffbfe29750c89db97381701508c9f45/aiohttp-3.13.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b174f267b5cfb9a7dba9ee6859cecd234e9a681841eb85068059bc867fb8f02", size = 492365, upload-time = "2026-01-03T17:32:30.234Z" }, - { url = "https://files.pythonhosted.org/packages/5a/b5/b50657496c8754482cd7964e50aaf3aa84b3db61ed45daec4c1aec5b94b4/aiohttp-3.13.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:947c26539750deeaee933b000fb6517cc770bbd064bad6033f1cff4803881e43", size = 1660440, upload-time = "2026-01-03T17:32:32.586Z" }, - { url = "https://files.pythonhosted.org/packages/2a/73/9b69e5139d89d75127569298931444ad78ea86a5befd5599780b1e9a6880/aiohttp-3.13.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9ebf57d09e131f5323464bd347135a88622d1c0976e88ce15b670e7ad57e4bd6", size = 1632740, upload-time = "2026-01-03T17:32:34.793Z" }, - { url = "https://files.pythonhosted.org/packages/ef/fe/3ea9b5af694b4e3aec0d0613a806132ca744747146fca68e96bf056f61a7/aiohttp-3.13.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4ae5b5a0e1926e504c81c5b84353e7a5516d8778fbbff00429fe7b05bb25cbce", size = 1719782, upload-time = "2026-01-03T17:32:37.737Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c2/46b3b06e60851cbb71efb0f79a3267279cbef7b12c58e68a1e897f269cca/aiohttp-3.13.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ba0eea45eb5cc3172dbfc497c066f19c41bac70963ea1a67d51fc92e4cf9a80", size = 1813527, upload-time = "2026-01-03T17:32:39.973Z" }, - { url = "https://files.pythonhosted.org/packages/36/23/71ceb78c769ed65fe4c697692de232b63dab399210678d2b00961ccb0619/aiohttp-3.13.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bae5c2ed2eae26cc382020edad80d01f36cb8e746da40b292e68fec40421dc6a", size = 1661268, upload-time = "2026-01-03T17:32:42.082Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8d/86e929523d955e85ebab7c0e2b9e0cb63604cfc27dc3280e10d0063cf682/aiohttp-3.13.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a60e60746623925eab7d25823329941aee7242d559baa119ca2b253c88a7bd6", size = 1552742, upload-time = "2026-01-03T17:32:44.622Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ea/3f5987cba1bab6bd151f0d97aa60f0ce04d3c83316692a6bb6ba2fb69f92/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e50a2e1404f063427c9d027378472316201a2290959a295169bcf25992d04558", size = 1632918, upload-time = "2026-01-03T17:32:46.749Z" }, - { url = "https://files.pythonhosted.org/packages/be/2c/7e1e85121f2e31ee938cb83a8f32dfafd4908530c10fabd6d46761c12ac7/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9a9dc347e5a3dc7dfdbc1f82da0ef29e388ddb2ed281bfce9dd8248a313e62b7", size = 1644446, upload-time = "2026-01-03T17:32:49.063Z" }, - { url = "https://files.pythonhosted.org/packages/5d/35/ce6133d423ad0e8ca976a7c848f7146bca3520eea4ccf6b95e2d077c9d20/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b46020d11d23fe16551466c77823df9cc2f2c1e63cc965daf67fa5eec6ca1877", size = 1689487, upload-time = "2026-01-03T17:32:51.113Z" }, - { url = "https://files.pythonhosted.org/packages/50/f7/ff7a27c15603d460fd1366b3c22054f7ae4fa9310aca40b43bde35867fcd/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:69c56fbc1993fa17043e24a546959c0178fe2b5782405ad4559e6c13975c15e3", size = 1540715, upload-time = "2026-01-03T17:32:53.38Z" }, - { url = "https://files.pythonhosted.org/packages/17/02/053f11346e5b962e6d8a1c4f8c70c29d5970a1b4b8e7894c68e12c27a57f/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b99281b0704c103d4e11e72a76f1b543d4946fea7dd10767e7e1b5f00d4e5704", size = 1711835, upload-time = "2026-01-03T17:32:56.088Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/9b9761ddf276fd6708d13720197cbac19b8d67ecfa9116777924056cfcaa/aiohttp-3.13.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:40c5e40ecc29ba010656c18052b877a1c28f84344825efa106705e835c28530f", size = 1649593, upload-time = "2026-01-03T17:32:58.181Z" }, - { url = "https://files.pythonhosted.org/packages/ae/72/5d817e9ea218acae12a5e3b9ad1178cf0c12fc3570c0b47eea2daf95f9ea/aiohttp-3.13.3-cp39-cp39-win32.whl", hash = "sha256:56339a36b9f1fc708260c76c87e593e2afb30d26de9ae1eb445b5e051b98a7a1", size = 434831, upload-time = "2026-01-03T17:33:00.577Z" }, - { url = "https://files.pythonhosted.org/packages/39/cb/22659d9bf3149b7a2927bc2769cc9c8f8f5a80eba098398e03c199a43a85/aiohttp-3.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:c6b8568a3bb5819a0ad087f16d40e5a3fb6099f39ea1d5625a3edc1e923fc538", size = 457697, upload-time = "2026-01-03T17:33:03.167Z" }, ] [[package]] @@ -243,8 +225,7 @@ wheels = [ [package.optional-dependencies] trio = [ - { name = "trio", version = "0.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "trio", version = "0.32.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "trio" }, ] [[package]] @@ -256,31 +237,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, ] -[[package]] -name = "argon2-cffi" -version = "23.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "argon2-cffi-bindings", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798, upload-time = "2023-08-15T14:13:12.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124, upload-time = "2023-08-15T14:13:10.752Z" }, -] - [[package]] name = "argon2-cffi" version = "25.1.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "argon2-cffi-bindings", marker = "python_full_version >= '3.10'" }, + { name = "argon2-cffi-bindings" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } wheels = [ @@ -355,7 +317,7 @@ name = "authlib" version = "1.6.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography", marker = "python_full_version >= '3.10'" }, + { name = "cryptography" }, ] sdist = { url = "https://files.pythonhosted.org/packages/49/dc/ed1681bf1339dd6ea1ce56136bad4baabc6f7ad466e375810702b0237047/authlib-1.6.7.tar.gz", hash = "sha256:dbf10100011d1e1b34048c9d120e83f13b35d69a826ae762b93d2fb5aafc337b", size = 164950, upload-time = "2026-02-06T14:04:14.171Z" } wheels = [ @@ -403,69 +365,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, ] -[[package]] -name = "black" -version = "25.11.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "mypy-extensions", marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pathspec", marker = "python_full_version < '3.10'" }, - { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytokens", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8c/ad/33adf4708633d047950ff2dfdea2e215d84ac50ef95aff14a614e4b6e9b2/black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", size = 655669, upload-time = "2025-11-10T01:53:50.558Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/d2/6caccbc96f9311e8ec3378c296d4f4809429c43a6cd2394e3c390e86816d/black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e", size = 1743501, upload-time = "2025-11-10T01:59:06.202Z" }, - { url = "https://files.pythonhosted.org/packages/69/35/b986d57828b3f3dccbf922e2864223197ba32e74c5004264b1c62bc9f04d/black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0", size = 1597308, upload-time = "2025-11-10T01:57:58.633Z" }, - { url = "https://files.pythonhosted.org/packages/39/8e/8b58ef4b37073f52b64a7b2dd8c9a96c84f45d6f47d878d0aa557e9a2d35/black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37", size = 1656194, upload-time = "2025-11-10T01:57:10.909Z" }, - { url = "https://files.pythonhosted.org/packages/8d/30/9c2267a7955ecc545306534ab88923769a979ac20a27cf618d370091e5dd/black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03", size = 1347996, upload-time = "2025-11-10T01:57:22.391Z" }, - { url = "https://files.pythonhosted.org/packages/c4/62/d304786b75ab0c530b833a89ce7d997924579fb7484ecd9266394903e394/black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a", size = 1727891, upload-time = "2025-11-10T02:01:40.507Z" }, - { url = "https://files.pythonhosted.org/packages/82/5d/ffe8a006aa522c9e3f430e7b93568a7b2163f4b3f16e8feb6d8c3552761a/black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170", size = 1581875, upload-time = "2025-11-10T01:57:51.192Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c8/7c8bda3108d0bb57387ac41b4abb5c08782b26da9f9c4421ef6694dac01a/black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc", size = 1642716, upload-time = "2025-11-10T01:56:51.589Z" }, - { url = "https://files.pythonhosted.org/packages/34/b9/f17dea34eecb7cc2609a89627d480fb6caea7b86190708eaa7eb15ed25e7/black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e", size = 1352904, upload-time = "2025-11-10T01:59:26.252Z" }, - { url = "https://files.pythonhosted.org/packages/7f/12/5c35e600b515f35ffd737da7febdb2ab66bb8c24d88560d5e3ef3d28c3fd/black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac", size = 1772831, upload-time = "2025-11-10T02:03:47Z" }, - { url = "https://files.pythonhosted.org/packages/1a/75/b3896bec5a2bb9ed2f989a970ea40e7062f8936f95425879bbe162746fe5/black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96", size = 1608520, upload-time = "2025-11-10T01:58:46.895Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b5/2bfc18330eddbcfb5aab8d2d720663cd410f51b2ed01375f5be3751595b0/black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd", size = 1682719, upload-time = "2025-11-10T01:56:55.24Z" }, - { url = "https://files.pythonhosted.org/packages/96/fb/f7dc2793a22cdf74a72114b5ed77fe3349a2e09ef34565857a2f917abdf2/black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409", size = 1362684, upload-time = "2025-11-10T01:57:07.639Z" }, - { url = "https://files.pythonhosted.org/packages/ad/47/3378d6a2ddefe18553d1115e36aea98f4a90de53b6a3017ed861ba1bd3bc/black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b", size = 1772446, upload-time = "2025-11-10T02:02:16.181Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4b/0f00bfb3d1f7e05e25bfc7c363f54dc523bb6ba502f98f4ad3acf01ab2e4/black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd", size = 1607983, upload-time = "2025-11-10T02:02:52.502Z" }, - { url = "https://files.pythonhosted.org/packages/99/fe/49b0768f8c9ae57eb74cc10a1f87b4c70453551d8ad498959721cc345cb7/black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993", size = 1682481, upload-time = "2025-11-10T01:57:12.35Z" }, - { url = "https://files.pythonhosted.org/packages/55/17/7e10ff1267bfa950cc16f0a411d457cdff79678fbb77a6c73b73a5317904/black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c", size = 1363869, upload-time = "2025-11-10T01:58:24.608Z" }, - { url = "https://files.pythonhosted.org/packages/67/c0/cc865ce594d09e4cd4dfca5e11994ebb51604328489f3ca3ae7bb38a7db5/black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170", size = 1771358, upload-time = "2025-11-10T02:03:33.331Z" }, - { url = "https://files.pythonhosted.org/packages/37/77/4297114d9e2fd2fc8ab0ab87192643cd49409eb059e2940391e7d2340e57/black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545", size = 1612902, upload-time = "2025-11-10T01:59:33.382Z" }, - { url = "https://files.pythonhosted.org/packages/de/63/d45ef97ada84111e330b2b2d45e1dd163e90bd116f00ac55927fb6bf8adb/black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda", size = 1680571, upload-time = "2025-11-10T01:57:04.239Z" }, - { url = "https://files.pythonhosted.org/packages/ff/4b/5604710d61cdff613584028b4cb4607e56e148801ed9b38ee7970799dab6/black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664", size = 1382599, upload-time = "2025-11-10T01:57:57.427Z" }, - { url = "https://files.pythonhosted.org/packages/d5/9a/5b2c0e3215fe748fcf515c2dd34658973a1210bf610e24de5ba887e4f1c8/black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06", size = 1743063, upload-time = "2025-11-10T02:02:43.175Z" }, - { url = "https://files.pythonhosted.org/packages/a1/20/245164c6efc27333409c62ba54dcbfbe866c6d1957c9a6c0647786e950da/black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2", size = 1596867, upload-time = "2025-11-10T02:00:17.157Z" }, - { url = "https://files.pythonhosted.org/packages/ca/6f/1a3859a7da205f3d50cf3a8bec6bdc551a91c33ae77a045bb24c1f46ab54/black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc", size = 1655678, upload-time = "2025-11-10T01:57:09.028Z" }, - { url = "https://files.pythonhosted.org/packages/56/1a/6dec1aeb7be90753d4fcc273e69bc18bfd34b353223ed191da33f7519410/black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc", size = 1347452, upload-time = "2025-11-10T01:57:01.871Z" }, - { url = "https://files.pythonhosted.org/packages/00/5d/aed32636ed30a6e7f9efd6ad14e2a0b0d687ae7c8c7ec4e4a557174b895c/black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", size = 204918, upload-time = "2025-11-10T01:53:48.917Z" }, -] - [[package]] name = "black" version = "26.1.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mypy-extensions", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pathspec", marker = "python_full_version >= '3.10'" }, - { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pytokens", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, - { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/13/88/560b11e521c522440af991d46848a2bde64b5f7202ec14e1f46f9509d328/black-26.1.0.tar.gz", hash = "sha256:d294ac3340eef9c9eb5d29288e96dc719ff269a88e27b396340459dd85da4c58", size = 658785, upload-time = "2026-01-18T04:50:11.993Z" } wheels = [ @@ -527,8 +439,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, - { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/af/d6/def916ad1d13de5d511074afcde538a958e2e8a7c7020fb698d1f392f63b/botocore-1.42.43.tar.gz", hash = "sha256:41d04ead0b0862eec21f841811fb5764fe370a2df9b319e0d5297325c50fba1b", size = 14934077, upload-time = "2026-02-05T20:31:35.15Z" } wheels = [ @@ -564,10 +475,8 @@ dependencies = [ { name = "cairocffi" }, { name = "cssselect2" }, { name = "defusedxml" }, - { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "tinycss2", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "tinycss2", version = "1.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pillow" }, + { name = "tinycss2" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/b9/5106168bd43d7cd8b7cc2a2ee465b385f14b63f4c092bb89eee2d48c8e67/cairosvg-2.8.2.tar.gz", hash = "sha256:07cbf4e86317b27a92318a4cac2a4bb37a5e9c1b8a27355d06874b22f85bef9f", size = 8398590, upload-time = "2025-05-15T06:56:32.653Z" } wheels = [ @@ -588,8 +497,7 @@ name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser", version = "2.23", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' and implementation_name != 'PyPy'" }, - { name = "pycparser", version = "3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and implementation_name != 'PyPy'" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ @@ -664,18 +572,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" }, - { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" }, - { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" }, - { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243, upload-time = "2025-09-08T23:23:53.836Z" }, - { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158, upload-time = "2025-09-08T23:23:55.169Z" }, - { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" }, - { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" }, - { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" }, - { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" }, ] [[package]] @@ -764,50 +660,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" }, - { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" }, - { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" }, - { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" }, - { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" }, - { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" }, - { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" }, - { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" }, - { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" }, - { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" }, - { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" }, - { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" }, { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] -[[package]] -name = "click" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, -] - [[package]] name = "click" version = "8.3.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ @@ -834,8 +695,7 @@ dependencies = [ { name = "pydantic-core" }, { name = "requests" }, { name = "tokenizers" }, - { name = "types-requests", version = "2.31.0.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "types-requests", version = "2.32.4.20260107", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "types-requests" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/35/37/65af9c50b5d5772a5528c6a949799f98ae286b8ebb924e0fac0619b3ae88/cohere-5.20.4.tar.gz", hash = "sha256:3b3017048ff5d5b4f113180947d538ca3d0f274de5875f0345be4c8cb3d5119a", size = 180737, upload-time = "2026-02-05T14:47:54.639Z" } @@ -852,133 +712,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "coverage" -version = "7.10.7" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, - { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, - { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, - { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, - { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, - { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, - { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, - { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, - { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, - { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, - { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, - { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, - { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, - { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, - { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, - { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, - { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, - { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, - { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, - { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, - { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, - { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, - { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, - { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, - { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, - { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, - { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, - { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, - { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, - { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, - { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, - { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, - { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, - { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, - { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, - { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, - { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, - { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, - { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, - { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, - { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, - { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, - { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, - { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, - { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, - { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, - { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, - { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, - { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, - { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, - { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, - { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, - { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, - { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, - { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, - { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, - { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, - { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, - { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, - { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, - { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, - { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, - { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, - { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, - { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, - { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, - { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, - { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, - { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, - { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, - { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, - { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, - { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, - { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, -] - -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version < '3.10'" }, -] - [[package]] name = "coverage" version = "7.13.3" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", hash = "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", size = 826832, upload-time = "2026-02-03T14:02:30.944Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ab/07/1c8099563a8a6c389a31c2d0aa1497cee86d6248bb4b9ba5e779215db9f9/coverage-7.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b4f345f7265cdbdb5ec2521ffff15fa49de6d6c39abf89fc7ad68aa9e3a55f0", size = 219143, upload-time = "2026-02-03T13:59:40.459Z" }, @@ -1076,7 +813,7 @@ wheels = [ [package.optional-dependencies] toml = [ - { name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" }, + { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] @@ -1084,8 +821,8 @@ name = "croniter" version = "6.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, - { name = "pytz", marker = "python_full_version >= '3.10'" }, + { name = "python-dateutil" }, + { name = "pytz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ad/2f/44d1ae153a0e27be56be43465e5cb39b9650c781e001e7864389deb25090/croniter-6.0.0.tar.gz", hash = "sha256:37c504b313956114a983ece2c2b07790b1f1094fe9d81cc94739214748255577", size = 64481, upload-time = "2024-12-17T17:17:47.32Z" } wheels = [ @@ -1169,8 +906,7 @@ name = "cssselect2" version = "0.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "tinycss2", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "tinycss2", version = "1.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tinycss2" }, { name = "webencodings" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/86/fd7f58fc498b3166f3a7e8e0cddb6e620fe1da35b02248b1bd59e95dbaaa/cssselect2-0.8.0.tar.gz", hash = "sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a", size = 35716, upload-time = "2025-03-05T14:46:07.988Z" } @@ -1192,12 +928,12 @@ name = "cyclopts" version = "4.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs", marker = "python_full_version >= '3.10'" }, - { name = "docstring-parser", marker = "python_full_version >= '3.10'" }, - { name = "rich", marker = "python_full_version >= '3.10'" }, - { name = "rich-rst", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, - { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, + { name = "attrs" }, + { name = "docstring-parser" }, + { name = "rich" }, + { name = "rich-rst" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d4/93/6085aa89c3fff78a5180987354538d72e43b0db27e66a959302d0c07821a/cyclopts-4.5.1.tar.gz", hash = "sha256:fadc45304763fd9f5d6033727f176898d17a1778e194436964661a005078a3dd", size = 162075, upload-time = "2026-01-25T15:23:54.07Z" } wheels = [ @@ -1240,26 +976,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] -[[package]] -name = "dnspython" -version = "2.7.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, -] - [[package]] name = "dnspython" version = "2.8.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, @@ -1288,8 +1008,7 @@ name = "email-validator" version = "2.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "dnspython", version = "2.7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "dnspython", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "dnspython" }, { name = "idna" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } @@ -1332,9 +1051,9 @@ name = "fakeredis" version = "2.33.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "redis", marker = "python_full_version >= '3.10'" }, - { name = "sortedcontainers", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, + { name = "redis" }, + { name = "sortedcontainers" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5f/f9/57464119936414d60697fcbd32f38909bb5688b616ae13de6e98384433e0/fakeredis-2.33.0.tar.gz", hash = "sha256:d7bc9a69d21df108a6451bbffee23b3eba432c21a654afc7ff2d295428ec5770", size = 175187, upload-time = "2025-12-16T19:45:52.269Z" } wheels = [ @@ -1343,7 +1062,7 @@ wheels = [ [package.optional-dependencies] lua = [ - { name = "lupa", marker = "python_full_version >= '3.10'" }, + { name = "lupa" }, ] [[package]] @@ -1352,8 +1071,7 @@ source = { editable = "." } dependencies = [ { name = "annotated-doc" }, { name = "pydantic" }, - { name = "starlette", version = "0.49.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "starlette", version = "0.52.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "starlette" }, { name = "typing-extensions" }, { name = "typing-inspection" }, ] @@ -1365,17 +1083,13 @@ all = [ { name = "httpx" }, { name = "itsdangerous" }, { name = "jinja2" }, - { name = "orjson", version = "3.11.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "orjson", version = "3.11.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "orjson" }, { name = "pydantic-extra-types" }, - { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "python-multipart", version = "0.0.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "python-multipart", version = "0.0.22", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, { name = "pyyaml" }, { name = "ujson" }, - { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, - { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, + { name = "uvicorn", extra = ["standard"] }, ] standard = [ { name = "email-validator" }, @@ -1383,12 +1097,9 @@ standard = [ { name = "httpx" }, { name = "jinja2" }, { name = "pydantic-extra-types" }, - { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "python-multipart", version = "0.0.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "python-multipart", version = "0.0.22", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, - { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "uvicorn", extra = ["standard"] }, ] standard-no-fastapi-cloud-cli = [ { name = "email-validator" }, @@ -1396,23 +1107,18 @@ standard-no-fastapi-cloud-cli = [ { name = "httpx" }, { name = "jinja2" }, { name = "pydantic-extra-types" }, - { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "python-multipart", version = "0.0.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "python-multipart", version = "0.0.22", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, - { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "uvicorn", extra = ["standard"] }, ] [package.dev-dependencies] dev = [ { name = "a2wsgi" }, { name = "anyio", extra = ["trio"] }, - { name = "black", version = "25.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "black", version = "26.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "black" }, { name = "cairosvg" }, - { 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.3", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "coverage", extra = ["toml"] }, { name = "dirty-equals" }, { name = "flask" }, { name = "gitpython" }, @@ -1426,17 +1132,13 @@ dev = [ { name = "mkdocs-macros-plugin" }, { name = "mkdocs-material" }, { name = "mkdocs-redirects" }, - { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version < '3.10'" }, - { name = "mkdocstrings", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings", extra = ["python"] }, { name = "mypy" }, - { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pillow" }, { name = "playwright" }, { name = "prek" }, - { name = "pwdlib", version = "0.2.1", source = { registry = "https://pypi.org/simple" }, extra = ["argon2"], marker = "python_full_version < '3.10'" }, - { name = "pwdlib", version = "0.3.0", source = { registry = "https://pypi.org/simple" }, extra = ["argon2"], marker = "python_full_version >= '3.10'" }, - { name = "pydantic-ai", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pydantic-ai", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pwdlib", extra = ["argon2"] }, + { name = "pydantic-ai" }, { name = "pygithub" }, { name = "pyjwt" }, { name = "pytest" }, @@ -1445,15 +1147,13 @@ dev = [ { name = "pyyaml" }, { name = "ruff" }, { name = "sqlmodel" }, - { name = "strawberry-graphql", version = "0.283.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "strawberry-graphql", version = "0.291.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "strawberry-graphql" }, { name = "typer" }, { name = "types-orjson" }, { name = "types-ujson" }, ] docs = [ - { name = "black", version = "25.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "black", version = "26.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "black" }, { name = "cairosvg" }, { name = "griffe-typingdoc" }, { name = "griffe-warnings-deprecated" }, @@ -1464,10 +1164,8 @@ docs = [ { name = "mkdocs-macros-plugin" }, { name = "mkdocs-material" }, { name = "mkdocs-redirects" }, - { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version < '3.10'" }, - { name = "mkdocstrings", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, extra = ["python"], marker = "python_full_version >= '3.10'" }, - { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocstrings", extra = ["python"] }, + { name = "pillow" }, { name = "python-slugify" }, { name = "pyyaml" }, { name = "ruff" }, @@ -1480,8 +1178,7 @@ docs-tests = [ github-actions = [ { name = "httpx" }, { name = "pydantic" }, - { name = "pydantic-settings", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pydantic-settings" }, { name = "pygithub" }, { name = "pyyaml" }, { name = "smokeshow" }, @@ -1489,30 +1186,26 @@ github-actions = [ 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.3", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "coverage", extra = ["toml"] }, { name = "dirty-equals" }, { name = "flask" }, { name = "httpx" }, { name = "inline-snapshot" }, { name = "mypy" }, - { name = "pwdlib", version = "0.2.1", source = { registry = "https://pypi.org/simple" }, extra = ["argon2"], marker = "python_full_version < '3.10'" }, - { name = "pwdlib", version = "0.3.0", source = { registry = "https://pypi.org/simple" }, extra = ["argon2"], marker = "python_full_version >= '3.10'" }, + { name = "pwdlib", extra = ["argon2"] }, { name = "pyjwt" }, { name = "pytest" }, { name = "pytest-codspeed" }, { name = "pyyaml" }, { name = "ruff" }, { name = "sqlmodel" }, - { name = "strawberry-graphql", version = "0.283.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "strawberry-graphql", version = "0.291.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "strawberry-graphql" }, { name = "types-orjson" }, { name = "types-ujson" }, ] translations = [ { name = "gitpython" }, - { name = "pydantic-ai", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pydantic-ai", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pydantic-ai" }, { name = "pygithub" }, ] @@ -1659,8 +1352,7 @@ dependencies = [ { name = "rich-toolkit" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typer" }, - { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, - { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, + { name = "uvicorn", extra = ["standard"] }, ] sdist = { url = "https://files.pythonhosted.org/packages/d3/ca/d90fb3bfbcbd6e56c77afd9d114dd6ce8955d8bb90094399d1c70e659e40/fastapi_cli-0.0.20.tar.gz", hash = "sha256:d17c2634f7b96b6b560bc16b0035ed047d523c912011395f49f00a421692bc3a", size = 19786, upload-time = "2025-12-22T17:13:33.794Z" } wheels = [ @@ -1670,12 +1362,10 @@ wheels = [ [package.optional-dependencies] standard = [ { name = "fastapi-cloud-cli" }, - { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, - { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, + { name = "uvicorn", extra = ["standard"] }, ] standard-no-fastapi-cloud-cli = [ - { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, - { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, + { name = "uvicorn", extra = ["standard"] }, ] [[package]] @@ -1690,8 +1380,7 @@ dependencies = [ { name = "rignore" }, { name = "sentry-sdk" }, { name = "typer" }, - { name = "uvicorn", version = "0.39.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version < '3.10'" }, - { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, extra = ["standard"], marker = "python_full_version >= '3.10'" }, + { name = "uvicorn", extra = ["standard"] }, ] sdist = { url = "https://files.pythonhosted.org/packages/11/15/6c3d85d63964340fde6f36cc80f3f365d35f371e6a918d68ff3a3d588ef2/fastapi_cloud_cli-0.11.0.tar.gz", hash = "sha256:ecc83a5db106be35af528eccb01aa9bced1d29783efd48c8c1c831cf111eea99", size = 36170, upload-time = "2026-01-15T09:51:33.681Z" } wheels = [ @@ -1793,20 +1482,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/e2/dfa19a4b260b8ab3581b7484dcb80c09b25324f4daa6b6ae1c7640d1607a/fastar-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:187f61dc739afe45ac8e47ed7fd1adc45d52eac110cf27d579155720507d6fbe", size = 455767, upload-time = "2025-11-26T02:36:34.758Z" }, { url = "https://files.pythonhosted.org/packages/51/47/df65c72afc1297797b255f90c4778b5d6f1f0f80282a134d5ab610310ed9/fastar-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:40e9d763cf8bf85ce2fa256e010aa795c0fe3d3bd1326d5c3084e6ce7857127e", size = 489971, upload-time = "2025-11-26T02:36:22.081Z" }, { url = "https://files.pythonhosted.org/packages/85/11/0aa8455af26f0ae89e42be67f3a874255ee5d7f0f026fc86e8d56f76b428/fastar-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e59673307b6a08210987059a2bdea2614fe26e3335d0e5d1a3d95f49a05b1418", size = 460467, upload-time = "2025-11-26T02:36:07.978Z" }, - { url = "https://files.pythonhosted.org/packages/fd/0d/462ce073b1f87cc6bf33b656c340c103eeb67487d118b761a697f1ee454f/fastar-0.8.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:923afc2db5192e56e71952a88e3fe5965c7c9c910d385d2db7573136f064f2fa", size = 709519, upload-time = "2025-11-26T02:34:44.783Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f1/ceca1e04fd3266f866efdbb5d343fee1d8ff8fe94c64c8d1aab68b483ad0/fastar-0.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4fbe775356930f3aab0ce709fdf8ecf90c10882f5bbdcea215c89a3b14090c50", size = 632509, upload-time = "2025-11-26T02:34:29.428Z" }, - { url = "https://files.pythonhosted.org/packages/4f/db/fdb07e9dce80ebb38138f166a30dc482c82cc8dfcfda1024e8b100a53d1c/fastar-0.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ff516154e77f4bf78c31a0c11aa78a8a80e11b6964ec6f28982e42ffcbb543c", size = 871400, upload-time = "2025-11-26T02:33:58.461Z" }, - { url = "https://files.pythonhosted.org/packages/54/6d/d0a342528002cf7527339fd275bdee5e5cc90c2f193e8418d3510ffedcd7/fastar-0.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d2fdd1c987ff2300bdf39baed556f8e155f8577018775e794a268ecf1707610", size = 765781, upload-time = "2025-11-26T02:32:53.929Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/dd2721e7160eed7bc9c3b695e942868c7ba2fab41947278fa380c18569be/fastar-0.8.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d80e4dad8ee2362a71870b1e735800bb5e97f12ebbee4bd0cf15a81ad2428b5a", size = 766126, upload-time = "2025-11-26T02:33:10.075Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ac/6533d2608254def722cd8c852f4ac8fb413fc43ad3251b7fb99863ffaa83/fastar-0.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a17abee1febf5363ed2633f5e13de4be481ba1ab5f77860d39470eccdc4b65af", size = 932549, upload-time = "2025-11-26T02:33:25.866Z" }, - { url = "https://files.pythonhosted.org/packages/97/dc/aac042d0d0017b8d75ff34381cc28b482ace0d78a26d4eef9a8674f850c2/fastar-0.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64cbde8e0ece3d799090a4727f936f66c5990d3ac59416f3de76a2c676e8e568", size = 821860, upload-time = "2025-11-26T02:33:41.423Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ef/c31057572e3a5a2c90da4986d8594d0ff33097b4a44058f9f52103c33677/fastar-0.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63d98b26590d293a9d9a379bae88367a8f3a6137c28819ed6dd6e11aca4a5c6e", size = 821660, upload-time = "2025-11-26T02:34:13.84Z" }, - { url = "https://files.pythonhosted.org/packages/39/00/d42fc84e8ce8587eb746406aa74ec231f97ce659a48bc064d5ff5cd78889/fastar-0.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bf440983d4d64582bddf2f0bd3c43ea1db93a8c31cf7c20e473bffaf6d9c0b6d", size = 987026, upload-time = "2025-11-26T02:35:01.181Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d9/5e4d37faca6f2df6a0bf1efc340192a871356162a7cc53626dc117645ad6/fastar-0.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1d90cbf984a39afda27afe08e40c2d8eddc49c5e80590af641610c7b6dc20161", size = 1042165, upload-time = "2025-11-26T02:35:18.729Z" }, - { url = "https://files.pythonhosted.org/packages/77/fd/bb4731243e42f385e8db9da824971f5d0380e6b31466c36eb089d631c589/fastar-0.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ca0db5e563d84b639fe15385eeca940777b6d2f0a1f3bb7cd5b55ab7124f0554", size = 1046990, upload-time = "2025-11-26T02:35:36.158Z" }, - { url = "https://files.pythonhosted.org/packages/c4/50/2ac066f771858ea45d885cfde7262b286a223af9538d3f85d29d5159b4fb/fastar-0.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:42ff3052d74684a636423d4f040db88eebd4caf20842fa5f06020e0130c01f69", size = 995637, upload-time = "2025-11-26T02:35:54.714Z" }, - { url = "https://files.pythonhosted.org/packages/d3/04/c884ea3fa7c154ac0e89219a337c9d6e0d52d0eb5547b3f99b896eca417e/fastar-0.8.0-cp39-cp39-win32.whl", hash = "sha256:15e3dfaa769d2117ef707e5f47c62126d1b63f8e9c85133112f33f1fbdf8942f", size = 456817, upload-time = "2025-11-26T02:36:33.198Z" }, - { url = "https://files.pythonhosted.org/packages/aa/0f/3a5758cca852bb221e78950fbfece0b932c6de6981ee36fb0ac57cf66c13/fastar-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:5153aa1c194316d0f67b6884a62d122d51fce4196263e92e4bca2a6c47cd44c0", size = 490633, upload-time = "2025-11-26T02:36:19.976Z" }, { url = "https://files.pythonhosted.org/packages/25/9f/6eaa810c240236eff2edf736cd50a17c97dbab1693cda4f7bcea09d13418/fastar-0.8.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2127cf2e80ffd49744a160201e0e2f55198af6c028a7b3f750026e0b1f1caa4e", size = 710544, upload-time = "2025-11-26T02:34:46.195Z" }, { url = "https://files.pythonhosted.org/packages/1d/a5/58ff9e49a1cd5fbfc8f1238226cbf83b905376a391a6622cdd396b2cfa29/fastar-0.8.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ff85094f10003801339ac4fa9b20a3410c2d8f284d4cba2dc99de6e98c877812", size = 634020, upload-time = "2025-11-26T02:34:31.085Z" }, { url = "https://files.pythonhosted.org/packages/80/94/f839257c6600a83fbdb5a7fcc06319599086137b25ba38ca3d2c0fe14562/fastar-0.8.0-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3dbca235f0bd804cca6602fe055d3892bebf95fb802e6c6c7d872fb10f7abc6c", size = 871735, upload-time = "2025-11-26T02:34:00.088Z" }, @@ -1831,18 +1506,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/b9/9a8c3fd59958c1c8027bc075af11722cdc62c4968bb277e841d131232289/fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:382bfe82c026086487cb17fee12f4c1e2b4e67ce230f2e04487d3e7ddfd69031", size = 1042911, upload-time = "2025-11-26T02:35:21.857Z" }, { url = "https://files.pythonhosted.org/packages/e2/2f/c3f30963b47022134b8a231c12845f4d7cfba520f59bbc1a82468aea77c7/fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:908d2b9a1ff3d549cc304b32f95706a536da8f0bcb0bc0f9e4c1cce39b80e218", size = 1047464, upload-time = "2025-11-26T02:35:39.376Z" }, { url = "https://files.pythonhosted.org/packages/9e/8a/218ab6d9a2bab3b07718e6cd8405529600edc1e9c266320e8524c8f63251/fastar-0.8.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1aa7dbde2d2d73eb5b6203d0f74875cb66350f0f1b4325b4839fc8fbbf5d074e", size = 997309, upload-time = "2025-11-26T02:35:57.722Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ef/da0cbac6da78b22ddb6ec0bbf3ad1813f8dd991a911342fc20e5feabaa15/fastar-0.8.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:284036bae786a520456ad3f58e72aaf1bd5d74e309132e568343564daa4ae383", size = 710367, upload-time = "2025-11-26T02:34:48.853Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f0/951d368b4538477fe44651aaa7436318c22bf89e8e18086a68bd89e14a82/fastar-0.8.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5aba0942b4f56acdb8fa8aa7cb506f70c1a17bf13dcab318a17ffb467cb2e7ec", size = 633744, upload-time = "2025-11-26T02:34:33.723Z" }, - { url = "https://files.pythonhosted.org/packages/1e/d2/5f37b634aff1e27191874b7f0fb7306e60566d984df06ff5c2f901ef123b/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:52eda6230799db7bbd44461c622161e9bcd43603399da19b0daab2782e0030b0", size = 871412, upload-time = "2025-11-26T02:34:02.738Z" }, - { url = "https://files.pythonhosted.org/packages/b4/cd/41148c74cc164aacb8b5e7cb938a7ecc5a4e595f95e312aafcc8dbe36e48/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f860566b9f3cb1900980f46a4c3f003990c0009c11730f988f758542c17a2364", size = 766470, upload-time = "2025-11-26T02:32:58.503Z" }, - { url = "https://files.pythonhosted.org/packages/5f/89/b5715d4f969f15fe78b184ae42295e71f0ad2e0e083f0625212271cbda55/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:78f3fe5f45437c66d1dbece5f31aa487e48ef46d76b2082b873d5fa18013ebe1", size = 765285, upload-time = "2025-11-26T02:33:14.148Z" }, - { url = "https://files.pythonhosted.org/packages/39/4a/88223721278a663684c4fa6cb95ca296e0ba385362de4809a5e85cacfb36/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:82bc445202bbc53f067bb15e3b8639f01fd54d3096a0f9601240690cfd7c9684", size = 933881, upload-time = "2025-11-26T02:33:29.637Z" }, - { url = "https://files.pythonhosted.org/packages/ac/73/a13d6d57029378477c4d9232eddfb81bb2f74e0e91bdbca864c74223d877/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b1208b5453cfe7192e54765f73844b80d684bd8dc6d6acbbb60ead42590b13e", size = 822511, upload-time = "2025-11-26T02:33:45.882Z" }, - { url = "https://files.pythonhosted.org/packages/a4/96/79000571a10d93e2110e72b5e7155e6bf138c8cb896117cfb768c74d01ff/fastar-0.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8922754c66699e27d4f1ce07c9c256228054cdc9bb36363e8bb5b503453a6da", size = 822204, upload-time = "2025-11-26T02:34:18.164Z" }, - { url = "https://files.pythonhosted.org/packages/35/e4/c2066024f336349ce36f85b0df9d082981d453cfdb5c139af2c5eebcc49a/fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:92cad46dfbb9969359823c9f61165ec32d5d675d86e863889416e9b64efea95c", size = 987454, upload-time = "2025-11-26T02:35:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b7/e3a062534af02610dea226d2c14e70beb90b91a23562fed8f76bfa35e6cd/fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:f4eb9560a447ff6a4b377f08b6e5d3a31909a612b028f2c57810ffaf570eceb8", size = 1041310, upload-time = "2025-11-26T02:35:23.305Z" }, - { url = "https://files.pythonhosted.org/packages/91/73/1640476553705ee29ff9350a3b6c707ac150be53094d326e0d81d62a65ec/fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:52455794e6cc2b6a6dbf141a1c4312a1a1215d75e8849a35fcff694454da880f", size = 1047020, upload-time = "2025-11-26T02:35:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/b7/79/9b492c4da885a6699bc76032dbc285c565429ca1b6dc6b5aa5c908111d22/fastar-0.8.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8de5decfa18a03807ae26ba5af095c2c04ac31ae915e9a849363a4495463171f", size = 996379, upload-time = "2025-11-26T02:35:59.297Z" }, ] [[package]] @@ -1890,12 +1553,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/1c/6dfd082a205be4510543221b734b1191299e6a1810c452b6bc76dfa6968e/fastavro-1.12.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6a3462934b20a74f9ece1daa49c2e4e749bd9a35fa2657b53bf62898fba80f5", size = 3433972, upload-time = "2025-10-10T15:42:14.485Z" }, { url = "https://files.pythonhosted.org/packages/24/90/9de694625a1a4b727b1ad0958d220cab25a9b6cf7f16a5c7faa9ea7b2261/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1f81011d54dd47b12437b51dd93a70a9aa17b61307abf26542fc3c13efbc6c51", size = 3368752, upload-time = "2025-10-10T15:42:16.618Z" }, { url = "https://files.pythonhosted.org/packages/fa/93/b44f67589e4d439913dab6720f7e3507b0fa8b8e56d06f6fc875ced26afb/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43ded16b3f4a9f1a42f5970c2aa618acb23ea59c4fcaa06680bdf470b255e5a8", size = 3386636, upload-time = "2025-10-10T15:42:18.974Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b8/67f5682cd59cb59ec6eecb5479b132caaf69709d1e1ceda4f92d8c69d7f1/fastavro-1.12.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:02281432dcb11c78b3280da996eff61ee0eff39c5de06c6e0fbf19275093e6d4", size = 1002311, upload-time = "2025-10-10T15:42:20.415Z" }, - { url = "https://files.pythonhosted.org/packages/93/38/2f15a7ad24e4b6e0239016c068f142358732bf8ead0315ee926b88634bec/fastavro-1.12.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4128978b930aaf930332db4b3acc290783183f3be06a241ae4a482f3ed8ce892", size = 3195871, upload-time = "2025-10-10T15:42:22.675Z" }, - { url = "https://files.pythonhosted.org/packages/c1/e5/6fc0250b3006b1b42c1ab9f0511ccd44e1aeb15a63a77fc780ee97f58797/fastavro-1.12.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:546ffffda6610fca672f0ed41149808e106d8272bb246aa7539fa8bb6f117f17", size = 3204127, upload-time = "2025-10-10T15:42:24.855Z" }, - { url = "https://files.pythonhosted.org/packages/c6/43/1f3909eb096eb1066e416f0875abe783b73fabe823ad616f6956b3e80e84/fastavro-1.12.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7d840ccd9aacada3ddc80fbcc4ea079b658107fe62e9d289a0de9d54e95d366", size = 3135604, upload-time = "2025-10-10T15:42:28.899Z" }, - { url = "https://files.pythonhosted.org/packages/e6/61/ec083a3a5d7c2b97d0e2c9e137a6e667583afe884af0e49318f7ee7eaa5a/fastavro-1.12.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3100ad643e7fa658469a2a2db229981c1a000ff16b8037c0b58ce3ec4d2107e8", size = 3210932, upload-time = "2025-10-10T15:42:30.891Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ba/b814fb09b32c8f3059451c33bb11d55eb23188e6a499f5dde628d13bc076/fastavro-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:a38607444281619eda3a9c1be9f5397634012d1b237142eee1540e810b30ac8b", size = 510328, upload-time = "2025-10-10T15:42:32.415Z" }, ] [[package]] @@ -1903,50 +1560,34 @@ name = "fastmcp" version = "2.14.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "authlib", marker = "python_full_version >= '3.10'" }, - { name = "cyclopts", marker = "python_full_version >= '3.10'" }, - { name = "exceptiongroup", marker = "python_full_version >= '3.10'" }, - { name = "httpx", marker = "python_full_version >= '3.10'" }, - { name = "jsonref", marker = "python_full_version >= '3.10'" }, - { name = "jsonschema-path", marker = "python_full_version >= '3.10'" }, - { name = "mcp", marker = "python_full_version >= '3.10'" }, - { name = "openapi-pydantic", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"], marker = "python_full_version >= '3.10'" }, - { name = "pydantic", extra = ["email"], marker = "python_full_version >= '3.10'" }, - { name = "pydocket", marker = "python_full_version >= '3.10'" }, - { name = "pyperclip", marker = "python_full_version >= '3.10'" }, - { name = "python-dotenv", marker = "python_full_version >= '3.10'" }, - { name = "rich", marker = "python_full_version >= '3.10'" }, - { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "websockets", marker = "python_full_version >= '3.10'" }, + { name = "authlib" }, + { name = "cyclopts" }, + { name = "exceptiongroup" }, + { name = "httpx" }, + { name = "jsonref" }, + { name = "jsonschema-path" }, + { name = "mcp" }, + { name = "openapi-pydantic" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, + { name = "pydantic", extra = ["email"] }, + { name = "pydocket" }, + { name = "pyperclip" }, + { name = "python-dotenv" }, + { name = "rich" }, + { name = "uvicorn" }, + { name = "websockets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3b/32/982678d44f13849530a74ab101ed80e060c2ee6cf87471f062dcf61705fd/fastmcp-2.14.5.tar.gz", hash = "sha256:38944dc582c541d55357082bda2241cedb42cd3a78faea8a9d6a2662c62a42d7", size = 8296329, upload-time = "2026-02-03T15:35:21.005Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e5/c1/1a35ec68ff76ea8443aa115b18bcdee748a4ada2124537ee90522899ff9f/fastmcp-2.14.5-py3-none-any.whl", hash = "sha256:d81e8ec813f5089d3624bec93944beaefa86c0c3a4ef1111cbef676a761ebccf", size = 417784, upload-time = "2026-02-03T15:35:18.489Z" }, ] -[[package]] -name = "filelock" -version = "3.19.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, -] - [[package]] name = "filelock" version = "3.20.3" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, @@ -1958,9 +1599,7 @@ version = "3.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, - { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "click" }, { name = "itsdangerous" }, { name = "jinja2" }, { name = "markupsafe" }, @@ -2089,45 +1728,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/c2/59/ae5cdac87a00962122ea37bb346d41b66aec05f9ce328fa2b9e216f8967b/frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47", size = 86967, upload-time = "2025-10-06T05:37:55.607Z" }, - { url = "https://files.pythonhosted.org/packages/8a/10/17059b2db5a032fd9323c41c39e9d1f5f9d0c8f04d1e4e3e788573086e61/frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca", size = 49984, upload-time = "2025-10-06T05:37:57.049Z" }, - { url = "https://files.pythonhosted.org/packages/4b/de/ad9d82ca8e5fa8f0c636e64606553c79e2b859ad253030b62a21fe9986f5/frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068", size = 50240, upload-time = "2025-10-06T05:37:58.145Z" }, - { url = "https://files.pythonhosted.org/packages/4e/45/3dfb7767c2a67d123650122b62ce13c731b6c745bc14424eea67678b508c/frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95", size = 219472, upload-time = "2025-10-06T05:37:59.239Z" }, - { url = "https://files.pythonhosted.org/packages/0b/bf/5bf23d913a741b960d5c1dac7c1985d8a2a1d015772b2d18ea168b08e7ff/frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459", size = 221531, upload-time = "2025-10-06T05:38:00.521Z" }, - { url = "https://files.pythonhosted.org/packages/d0/03/27ec393f3b55860859f4b74cdc8c2a4af3dbf3533305e8eacf48a4fd9a54/frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675", size = 219211, upload-time = "2025-10-06T05:38:01.842Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ad/0fd00c404fa73fe9b169429e9a972d5ed807973c40ab6b3cf9365a33d360/frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61", size = 231775, upload-time = "2025-10-06T05:38:03.384Z" }, - { url = "https://files.pythonhosted.org/packages/8a/c3/86962566154cb4d2995358bc8331bfc4ea19d07db1a96f64935a1607f2b6/frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6", size = 236631, upload-time = "2025-10-06T05:38:04.609Z" }, - { url = "https://files.pythonhosted.org/packages/ea/9e/6ffad161dbd83782d2c66dc4d378a9103b31770cb1e67febf43aea42d202/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5", size = 218632, upload-time = "2025-10-06T05:38:05.917Z" }, - { url = "https://files.pythonhosted.org/packages/58/b2/4677eee46e0a97f9b30735e6ad0bf6aba3e497986066eb68807ac85cf60f/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3", size = 235967, upload-time = "2025-10-06T05:38:07.614Z" }, - { url = "https://files.pythonhosted.org/packages/05/f3/86e75f8639c5a93745ca7addbbc9de6af56aebb930d233512b17e46f6493/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1", size = 228799, upload-time = "2025-10-06T05:38:08.845Z" }, - { url = "https://files.pythonhosted.org/packages/30/00/39aad3a7f0d98f5eb1d99a3c311215674ed87061aecee7851974b335c050/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178", size = 230566, upload-time = "2025-10-06T05:38:10.52Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4d/aa144cac44568d137846ddc4d5210fb5d9719eb1d7ec6fa2728a54b5b94a/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda", size = 217715, upload-time = "2025-10-06T05:38:11.832Z" }, - { url = "https://files.pythonhosted.org/packages/64/4c/8f665921667509d25a0dd72540513bc86b356c95541686f6442a3283019f/frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087", size = 39933, upload-time = "2025-10-06T05:38:13.061Z" }, - { url = "https://files.pythonhosted.org/packages/79/bd/bcc926f87027fad5e59926ff12d136e1082a115025d33c032d1cd69ab377/frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a", size = 44121, upload-time = "2025-10-06T05:38:14.572Z" }, - { url = "https://files.pythonhosted.org/packages/4c/07/9c2e4eb7584af4b705237b971b89a4155a8e57599c4483a131a39256a9a0/frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103", size = 40312, upload-time = "2025-10-06T05:38:15.699Z" }, { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] -[[package]] -name = "fsspec" -version = "2025.10.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285, upload-time = "2025-10-30T14:58:44.036Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966, upload-time = "2025-10-30T14:58:42.53Z" }, -] - [[package]] name = "fsspec" version = "2026.2.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, @@ -2138,7 +1745,6 @@ name = "genai-prices" version = "0.0.52" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, { name = "httpx" }, { name = "pydantic" }, ] @@ -2177,7 +1783,6 @@ 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/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" } wheels = [ @@ -2200,50 +1805,24 @@ wheels = [ [package.optional-dependencies] requests = [ - { name = "requests", marker = "python_full_version >= '3.10'" }, -] - -[[package]] -name = "google-genai" -version = "1.47.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "anyio", marker = "python_full_version < '3.10'" }, - { name = "google-auth", marker = "python_full_version < '3.10'" }, - { name = "httpx", marker = "python_full_version < '3.10'" }, - { name = "pydantic", marker = "python_full_version < '3.10'" }, - { name = "requests", marker = "python_full_version < '3.10'" }, - { name = "tenacity", version = "9.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, - { name = "websockets", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/97/784fba9bc6c41263ff90cb9063eadfdd755dde79cfa5a8d0e397b067dcf9/google_genai-1.47.0.tar.gz", hash = "sha256:ecece00d0a04e6739ea76cc8dad82ec9593d9380aaabef078990e60574e5bf59", size = 241471, upload-time = "2025-10-29T22:01:02.88Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/ef/e080e8d67c270ea320956bb911a9359664fc46d3b87d1f029decd33e5c4c/google_genai-1.47.0-py3-none-any.whl", hash = "sha256:e3851237556cbdec96007d8028b4b1f2425cdc5c099a8dc36b72a57e42821b60", size = 241506, upload-time = "2025-10-29T22:01:00.982Z" }, + { name = "requests" }, ] [[package]] name = "google-genai" version = "1.62.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "anyio", marker = "python_full_version >= '3.10'" }, - { name = "distro", marker = "python_full_version >= '3.10'" }, - { name = "google-auth", extra = ["requests"], marker = "python_full_version >= '3.10'" }, - { name = "httpx", marker = "python_full_version >= '3.10'" }, - { name = "pydantic", marker = "python_full_version >= '3.10'" }, - { name = "requests", marker = "python_full_version >= '3.10'" }, - { name = "sniffio", marker = "python_full_version >= '3.10'" }, - { name = "tenacity", version = "9.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, - { name = "websockets", marker = "python_full_version >= '3.10'" }, + { name = "anyio" }, + { name = "distro" }, + { name = "google-auth", extra = ["requests"] }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "sniffio" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "websockets" }, ] sdist = { url = "https://files.pythonhosted.org/packages/94/4c/71b32b5c8db420cf2fd0d5ef8a672adbde97d85e5d44a0b4fca712264ef1/google_genai-1.62.0.tar.gz", hash = "sha256:709468a14c739a080bc240a4f3191df597bf64485b1ca3728e0fb67517774c18", size = 490888, upload-time = "2026-02-04T22:48:41.989Z" } wheels = [ @@ -2255,7 +1834,7 @@ name = "googleapis-common-protos" version = "1.72.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "protobuf", version = "6.33.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "protobuf" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } wheels = [ @@ -2266,98 +1845,15 @@ wheels = [ name = "graphql-core" version = "3.2.7" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/ac/9b/037a640a2983b09aed4a823f9cf1729e6d780b0671f854efa4727a7affbe/graphql_core-3.2.7.tar.gz", hash = "sha256:27b6904bdd3b43f2a0556dad5d579bdfdeab1f38e8e8788e555bdcb586a6f62c", size = 513484, upload-time = "2025-11-01T22:30:40.436Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/14/933037032608787fb92e365883ad6a741c235e0ff992865ec5d904a38f1e/graphql_core-3.2.7-py3-none-any.whl", hash = "sha256:17fc8f3ca4a42913d8e24d9ac9f08deddf0a0b2483076575757f6c412ead2ec0", size = 207262, upload-time = "2025-11-01T22:30:38.912Z" }, ] -[[package]] -name = "greenlet" -version = "3.2.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/ed/6bfa4109fcb23a58819600392564fea69cdc6551ffd5e69ccf1d52a40cbc/greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c", size = 271061, upload-time = "2025-08-07T13:17:15.373Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fc/102ec1a2fc015b3a7652abab7acf3541d58c04d3d17a8d3d6a44adae1eb1/greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590", size = 629475, upload-time = "2025-08-07T13:42:54.009Z" }, - { url = "https://files.pythonhosted.org/packages/c5/26/80383131d55a4ac0fb08d71660fd77e7660b9db6bdb4e8884f46d9f2cc04/greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c", size = 640802, upload-time = "2025-08-07T13:45:25.52Z" }, - { url = "https://files.pythonhosted.org/packages/9f/7c/e7833dbcd8f376f3326bd728c845d31dcde4c84268d3921afcae77d90d08/greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b", size = 636703, upload-time = "2025-08-07T13:53:12.622Z" }, - { url = "https://files.pythonhosted.org/packages/e9/49/547b93b7c0428ede7b3f309bc965986874759f7d89e4e04aeddbc9699acb/greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31", size = 635417, upload-time = "2025-08-07T13:18:25.189Z" }, - { url = "https://files.pythonhosted.org/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", size = 584358, upload-time = "2025-08-07T13:18:23.708Z" }, - { url = "https://files.pythonhosted.org/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", size = 1113550, upload-time = "2025-08-07T13:42:37.467Z" }, - { url = "https://files.pythonhosted.org/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", size = 1137126, upload-time = "2025-08-07T13:18:20.239Z" }, - { url = "https://files.pythonhosted.org/packages/f1/29/74242b7d72385e29bcc5563fba67dad94943d7cd03552bac320d597f29b2/greenlet-3.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7", size = 1544904, upload-time = "2025-11-04T12:42:04.763Z" }, - { url = "https://files.pythonhosted.org/packages/c8/e2/1572b8eeab0f77df5f6729d6ab6b141e4a84ee8eb9bc8c1e7918f94eda6d/greenlet-3.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8", size = 1611228, upload-time = "2025-11-04T12:42:08.423Z" }, - { url = "https://files.pythonhosted.org/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", size = 298654, upload-time = "2025-08-07T13:50:00.469Z" }, - { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, - { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, - { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, - { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, - { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, - { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, - { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" }, - { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" }, - { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, - { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, - { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, - { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, - { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, - { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, - { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, - { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, - { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, - { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, - { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, - { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, - { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, - { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, - { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, - { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, - { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, - { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, - { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, - { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, - { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, - { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, - { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, - { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, - { url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c0/93885c4106d2626bf51fdec377d6aef740dfa5c4877461889a7cf8e565cc/greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c", size = 269859, upload-time = "2025-08-07T13:16:16.003Z" }, - { url = "https://files.pythonhosted.org/packages/4d/f5/33f05dc3ba10a02dedb1485870cf81c109227d3d3aa280f0e48486cac248/greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d", size = 627610, upload-time = "2025-08-07T13:43:01.345Z" }, - { url = "https://files.pythonhosted.org/packages/b2/a7/9476decef51a0844195f99ed5dc611d212e9b3515512ecdf7321543a7225/greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58", size = 639417, upload-time = "2025-08-07T13:45:32.094Z" }, - { url = "https://files.pythonhosted.org/packages/bd/e0/849b9159cbb176f8c0af5caaff1faffdece7a8417fcc6fe1869770e33e21/greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4", size = 634751, upload-time = "2025-08-07T13:53:18.848Z" }, - { url = "https://files.pythonhosted.org/packages/5f/d3/844e714a9bbd39034144dca8b658dcd01839b72bb0ec7d8014e33e3705f0/greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433", size = 634020, upload-time = "2025-08-07T13:18:36.841Z" }, - { url = "https://files.pythonhosted.org/packages/6b/4c/f3de2a8de0e840ecb0253ad0dc7e2bb3747348e798ec7e397d783a3cb380/greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df", size = 582817, upload-time = "2025-08-07T13:18:35.48Z" }, - { url = "https://files.pythonhosted.org/packages/89/80/7332915adc766035c8980b161c2e5d50b2f941f453af232c164cff5e0aeb/greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594", size = 1111985, upload-time = "2025-08-07T13:42:42.425Z" }, - { url = "https://files.pythonhosted.org/packages/66/71/1928e2c80197353bcb9b50aa19c4d8e26ee6d7a900c564907665cf4b9a41/greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98", size = 1136137, upload-time = "2025-08-07T13:18:26.168Z" }, - { url = "https://files.pythonhosted.org/packages/4b/bf/7bd33643e48ed45dcc0e22572f650767832bd4e1287f97434943cc402148/greenlet-3.2.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:28a3c6b7cd72a96f61b0e4b2a36f681025b60ae4779cc73c1535eb5f29560b10", size = 1542941, upload-time = "2025-11-04T12:42:27.427Z" }, - { url = "https://files.pythonhosted.org/packages/9b/74/4bc433f91d0d09a1c22954a371f9df928cb85e72640870158853a83415e5/greenlet-3.2.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:52206cd642670b0b320a1fd1cbfd95bca0e043179c1d8a045f2c6109dfe973be", size = 1609685, upload-time = "2025-11-04T12:42:29.242Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/a5dc74dde38aeb2b15d418cec76ed50e1dd3d620ccda84d8199703248968/greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b", size = 281400, upload-time = "2025-08-07T14:02:20.263Z" }, - { url = "https://files.pythonhosted.org/packages/e5/44/342c4591db50db1076b8bda86ed0ad59240e3e1da17806a4cf10a6d0e447/greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb", size = 298533, upload-time = "2025-08-07T13:56:34.168Z" }, -] - [[package]] name = "greenlet" version = "3.3.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/8a/99/1cd3411c56a410994669062bd73dd58270c00cc074cac15f385a1fd91f8a/greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", size = 184690, upload-time = "2026-01-23T15:31:02.076Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fe/65/5b235b40581ad75ab97dcd8b4218022ae8e3ab77c13c919f1a1dfe9171fd/greenlet-3.3.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:04bee4775f40ecefcdaa9d115ab44736cd4b9c5fba733575bfe9379419582e13", size = 273723, upload-time = "2026-01-23T15:30:37.521Z" }, @@ -2414,31 +1910,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" }, ] -[[package]] -name = "griffe" -version = "1.14.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, -] - [[package]] name = "griffe" version = "1.15.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10'" }, + { name = "colorama" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" } wheels = [ @@ -2450,8 +1927,7 @@ name = "griffe-typingdoc" version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "griffe" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/77/d5e5fa0a8391bc2890ae45255847197299739833108dd76ee3c9b2ff0bba/griffe_typingdoc-0.3.0.tar.gz", hash = "sha256:59d9ef98d02caa7aed88d8df1119c9e48c02ed049ea50ce4018ace9331d20f8b", size = 33169, upload-time = "2025-10-23T12:01:39.037Z" } @@ -2464,8 +1940,7 @@ name = "griffe-warnings-deprecated" version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "griffe" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/0e/f034e1714eb2c694d6196c75f77a02f9c69d19f9961c4804a016397bf3e5/griffe_warnings_deprecated-1.1.0.tar.gz", hash = "sha256:7bf21de327d59c66c7ce08d0166aa4292ce0577ff113de5878f428d102b6f7c5", size = 33260, upload-time = "2024-12-10T21:02:18.395Z" } wheels = [ @@ -2494,7 +1969,7 @@ name = "grpcio" version = "1.78.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } wheels = [ @@ -2548,16 +2023,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, - { url = "https://files.pythonhosted.org/packages/58/6c/40a4bba2c753ea8eeb8d776a31e9c54f4e506edf36db93a3db5456725294/grpcio-1.78.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:86f85dd7c947baa707078a236288a289044836d4b640962018ceb9cd1f899af5", size = 5947902, upload-time = "2026-02-06T09:56:48.469Z" }, - { url = "https://files.pythonhosted.org/packages/c0/4c/ed7664a37a7008be41204c77e0d88bbc4ac531bcf0c27668cd066f9ff6e2/grpcio-1.78.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:de8cb00d1483a412a06394b8303feec5dcb3b55f81d83aa216dbb6a0b86a94f5", size = 11824772, upload-time = "2026-02-06T09:56:51.264Z" }, - { url = "https://files.pythonhosted.org/packages/9a/5b/45a5c23ba3c4a0f51352366d9b25369a2a51163ab1c93482cb8408726617/grpcio-1.78.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e888474dee2f59ff68130f8a397792d8cb8e17e6b3434339657ba4ee90845a8c", size = 6521579, upload-time = "2026-02-06T09:56:54.967Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/392e647d918004231e3d1c780ed125c48939bfc8f845adb8b5820410da3e/grpcio-1.78.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:86ce2371bfd7f212cf60d8517e5e854475c2c43ce14aa910e136ace72c6db6c1", size = 7199330, upload-time = "2026-02-06T09:56:57.611Z" }, - { url = "https://files.pythonhosted.org/packages/68/2f/42a52d78bdbdb3f1310ed690a3511cd004740281ca75d300b7bd6d9d3de3/grpcio-1.78.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0c689c02947d636bc7fab3e30cc3a3445cca99c834dfb77cd4a6cabfc1c5597", size = 6726696, upload-time = "2026-02-06T09:57:00.357Z" }, - { url = "https://files.pythonhosted.org/packages/0f/83/b3d932a4fbb2dce3056f6df2926fc2d3ddc5d5acbafbec32c84033cf3f23/grpcio-1.78.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ce7599575eeb25c0f4dc1be59cada6219f3b56176f799627f44088b21381a28a", size = 7299076, upload-time = "2026-02-06T09:57:04.124Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d9/70ea1be55efaf91fd19f7258b1292772a8226cf1b0e237717fba671073cb/grpcio-1.78.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:684083fd383e9dc04c794adb838d4faea08b291ce81f64ecd08e4577c7398adf", size = 8284493, upload-time = "2026-02-06T09:57:06.746Z" }, - { url = "https://files.pythonhosted.org/packages/d0/2f/3dddccf49e3e75564655b84175fca092d3efd81d2979fc89c4b1c1d879dc/grpcio-1.78.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ab399ef5e3cd2a721b1038a0f3021001f19c5ab279f145e1146bb0b9f1b2b12c", size = 7724340, upload-time = "2026-02-06T09:57:09.453Z" }, - { url = "https://files.pythonhosted.org/packages/79/ae/dfdb3183141db787a9363078a98764675996a7c2448883153091fd7c8527/grpcio-1.78.0-cp39-cp39-win32.whl", hash = "sha256:f3d6379493e18ad4d39537a82371c5281e153e963cecb13f953ebac155756525", size = 4077641, upload-time = "2026-02-06T09:57:11.881Z" }, - { url = "https://files.pythonhosted.org/packages/aa/aa/694b2f505345cfdd234cffb2525aa379a81695e6c02fd40d7e9193e871c6/grpcio-1.78.0-cp39-cp39-win_amd64.whl", hash = "sha256:5361a0630a7fdb58a6a97638ab70e1dae2893c4d08d7aba64ded28bb9e7a29df", size = 4799428, upload-time = "2026-02-06T09:57:14.493Z" }, ] [[package]] @@ -2661,13 +2126,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, - { url = "https://files.pythonhosted.org/packages/90/de/b1fe0e8890f0292c266117d4cd268186758a9c34e576fbd573fdf3beacff/httptools-0.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac50afa68945df63ec7a2707c506bd02239272288add34539a2ef527254626a4", size = 206454, upload-time = "2025-10-10T03:55:01.528Z" }, - { url = "https://files.pythonhosted.org/packages/57/a7/a675c90b49e550c7635ce209c01bc61daa5b08aef17da27ef4e0e78fcf3f/httptools-0.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de987bb4e7ac95b99b805b99e0aae0ad51ae61df4263459d36e07cf4052d8b3a", size = 110260, upload-time = "2025-10-10T03:55:02.418Z" }, - { url = "https://files.pythonhosted.org/packages/03/44/fb5ef8136e6e97f7b020e97e40c03a999f97e68574d4998fa52b0a62b01b/httptools-0.7.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d169162803a24425eb5e4d51d79cbf429fd7a491b9e570a55f495ea55b26f0bf", size = 441524, upload-time = "2025-10-10T03:55:03.292Z" }, - { url = "https://files.pythonhosted.org/packages/b4/62/8496a5425341867796d7e2419695f74a74607054e227bbaeabec8323e87f/httptools-0.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49794f9250188a57fa73c706b46cb21a313edb00d337ca4ce1a011fe3c760b28", size = 440877, upload-time = "2025-10-10T03:55:04.282Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f1/26c2e5214106bf6ed04d03e518ff28ca0c6b5390c5da7b12bbf94b40ae43/httptools-0.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aeefa0648362bb97a7d6b5ff770bfb774930a327d7f65f8208394856862de517", size = 425775, upload-time = "2025-10-10T03:55:05.341Z" }, - { url = "https://files.pythonhosted.org/packages/3a/34/7500a19257139725281f7939a7d1aa3701cf1ac4601a1690f9ab6f510e15/httptools-0.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0d92b10dbf0b3da4823cde6a96d18e6ae358a9daa741c71448975f6a2c339cad", size = 425001, upload-time = "2025-10-10T03:55:06.389Z" }, - { url = "https://files.pythonhosted.org/packages/71/04/31a7949d645ebf33a67f56a0024109444a52a271735e0647a210264f3e61/httptools-0.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:5ddbd045cfcb073db2449563dd479057f2c2b681ebc232380e63ef15edc9c023", size = 86818, upload-time = "2025-10-10T03:55:07.316Z" }, ] [[package]] @@ -2699,10 +2157,8 @@ name = "huggingface-hub" version = "0.36.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "filelock", version = "3.20.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "fsspec", version = "2025.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "fsspec", version = "2026.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "filelock" }, + { name = "fsspec" }, { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, { name = "packaging" }, { name = "pyyaml" }, @@ -2741,26 +2197,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, @@ -2805,7 +2245,7 @@ name = "jaraco-classes" version = "3.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "more-itertools", marker = "python_full_version >= '3.10'" }, + { name = "more-itertools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } wheels = [ @@ -2817,7 +2257,7 @@ name = "jaraco-context" version = "6.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "backports-tarfile", marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, + { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cb/9c/a788f5bb29c61e456b8ee52ce76dbdd32fd72cd73dd67bc95f42c7a8d13c/jaraco_context-6.1.0.tar.gz", hash = "sha256:129a341b0a85a7db7879e22acd66902fda67882db771754574338898b2d5d86f", size = 15850, upload-time = "2026-01-13T02:53:53.847Z" } wheels = [ @@ -2829,7 +2269,7 @@ name = "jaraco-functools" version = "4.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "more-itertools", marker = "python_full_version >= '3.10'" }, + { name = "more-itertools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } wheels = [ @@ -2950,18 +2390,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, - { url = "https://files.pythonhosted.org/packages/41/95/8e6611379c9ce8534ff94dd800c50d6d0061b2c9ae6386fbcd86c7386f0a/jiter-0.13.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:4397ee562b9f69d283e5674445551b47a5e8076fdde75e71bfac5891113dc543", size = 313635, upload-time = "2026-02-02T12:37:23.545Z" }, - { url = "https://files.pythonhosted.org/packages/70/ea/17db64dcaf84bbb187874232222030ea4d689e6008f93bda6e7c691bc4c7/jiter-0.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f90023f8f672e13ea1819507d2d21b9d2d1c18920a3b3a5f1541955a85b5504", size = 309761, upload-time = "2026-02-02T12:37:25.075Z" }, - { url = "https://files.pythonhosted.org/packages/a3/36/b2e2a7b12b94ecc7248acf2a8fe6288be893d1ebb9728655ceada22f00ad/jiter-0.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed0240dd1536a98c3ab55e929c60dfff7c899fecafcb7d01161b21a99fc8c363", size = 355245, upload-time = "2026-02-02T12:37:26.646Z" }, - { url = "https://files.pythonhosted.org/packages/77/3f/5b159663c5be622daec20074c997bb66bc1fac63c167c02aef3df476fb32/jiter-0.13.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6207fc61c395b26fffdcf637a0b06b4326f35bfa93c6e92fe1a166a21aeb6731", size = 365842, upload-time = "2026-02-02T12:37:28.207Z" }, - { url = "https://files.pythonhosted.org/packages/98/30/76a68fa2c9c815c6b7802a92fc354080d66ffba9acc4690fd85622f77ad4/jiter-0.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00203f47c214156df427b5989de74cb340c65c8180d09be1bf9de81d0abad599", size = 489223, upload-time = "2026-02-02T12:37:29.571Z" }, - { url = "https://files.pythonhosted.org/packages/a3/39/7c5cb85ccd71241513c878054c26a55828ccded6567d931a23ea4be73787/jiter-0.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c26ad6967c9dcedf10c995a21539c3aa57d4abad7001b7a84f621a263a6b605", size = 375762, upload-time = "2026-02-02T12:37:31.186Z" }, - { url = "https://files.pythonhosted.org/packages/a8/6a/381cd18d050b0102e60324e8d3f51f37ef02c56e9f4e5f0b7d26ba18958d/jiter-0.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a576f5dce9ac7de5d350b8e2f552cf364f32975ed84717c35379a51c7cb198bd", size = 364996, upload-time = "2026-02-02T12:37:32.931Z" }, - { url = "https://files.pythonhosted.org/packages/37/1e/d66310f1f7085c13ea6f1119c9566ec5d2cfd1dc90df963118a6869247bb/jiter-0.13.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b22945be8425d161f2e536cdae66da300b6b000f1c0ba3ddf237d1bfd45d21b8", size = 395463, upload-time = "2026-02-02T12:37:34.446Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ab/06ae77cb293f860b152c356c635c15aaa800ce48772865a41704d9fac80d/jiter-0.13.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6eeb7db8bc77dc20476bc2f7407a23dbe3d46d9cc664b166e3d474e1c1de4baa", size = 520944, upload-time = "2026-02-02T12:37:35.987Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8e/57b49b20361c42a80d455a6d83cb38626204508cab4298d6a22880205319/jiter-0.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:19cd6f85e1dc090277c3ce90a5b7d96f32127681d825e71c9dce28788e39fc0c", size = 554955, upload-time = "2026-02-02T12:37:37.656Z" }, - { url = "https://files.pythonhosted.org/packages/79/dd/113489973c3b4256e383321aea11bd57389e401912fa48eb145a99b38767/jiter-0.13.0-cp39-cp39-win32.whl", hash = "sha256:dc3ce84cfd4fa9628fe62c4f85d0d597a4627d4242cfafac32a12cc1455d00f7", size = 206876, upload-time = "2026-02-02T12:37:39.225Z" }, - { url = "https://files.pythonhosted.org/packages/6e/73/2bdfc7133c5ee0c8f18cfe4a7582f3cfbbf3ff672cec1b5f4ca67ff9d041/jiter-0.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:9ffda299e417dc83362963966c50cb76d42da673ee140de8a8ac762d4bb2378b", size = 206404, upload-time = "2026-02-02T12:37:40.632Z" }, { url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" }, { url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" }, { url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" }, @@ -2995,10 +2423,10 @@ name = "jsonschema" version = "4.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs", marker = "python_full_version >= '3.10'" }, - { name = "jsonschema-specifications", marker = "python_full_version >= '3.10'" }, - { name = "referencing", marker = "python_full_version >= '3.10'" }, - { name = "rpds-py", marker = "python_full_version >= '3.10'" }, + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } wheels = [ @@ -3010,10 +2438,10 @@ name = "jsonschema-path" version = "0.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pathable", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, - { name = "referencing", marker = "python_full_version >= '3.10'" }, - { name = "requests", marker = "python_full_version >= '3.10'" }, + { name = "pathable" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6e/45/41ebc679c2a4fced6a722f624c18d658dee42612b83ea24c1caf7c0eb3a8/jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001", size = 11159, upload-time = "2025-01-24T14:33:16.547Z" } wheels = [ @@ -3025,7 +2453,7 @@ name = "jsonschema-specifications" version = "2025.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "referencing", marker = "python_full_version >= '3.10'" }, + { name = "referencing" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ @@ -3037,31 +2465,19 @@ name = "keyring" version = "25.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", marker = "python_full_version >= '3.10' and python_full_version < '3.12'" }, - { name = "jaraco-classes", marker = "python_full_version >= '3.10'" }, - { name = "jaraco-context", marker = "python_full_version >= '3.10'" }, - { name = "jaraco-functools", marker = "python_full_version >= '3.10'" }, - { name = "jeepney", marker = "python_full_version >= '3.10' and sys_platform == 'linux'" }, - { name = "pywin32-ctypes", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "secretstorage", marker = "python_full_version >= '3.10' and sys_platform == 'linux'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, ] -[[package]] -name = "lia-web" -version = "0.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cross-web", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/05/3d/7d574a7a5cf5fbc5fc09c07ea3696dd400353b7702bc009cf596b8c12035/lia_web-0.3.1.tar.gz", hash = "sha256:7f551269eddd729f1437e9341ad21622a849eb0c0975d9232ccbbaadbdc74c06", size = 2021, upload-time = "2025-12-25T20:41:51.195Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/8b/b628fc18658f94b3d094708a18b71083cf47628e85cbc6b9edba54d5b2d7/lia_web-0.3.1-py3-none-any.whl", hash = "sha256:e4e6e7a9381e228aca60a6f3d67dbae9a5f4638eced242d931f95797ddba3f8b", size = 5933, upload-time = "2025-12-25T20:41:52.289Z" }, -] - [[package]] name = "librt" version = "0.7.8" @@ -3133,16 +2549,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/f0/07fb6ab5c39a4ca9af3e37554f9d42f25c464829254d72e4ebbd81da351c/librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365", size = 41173, upload-time = "2026-01-14T12:55:59.315Z" }, { url = "https://files.pythonhosted.org/packages/24/d4/7e4be20993dc6a782639625bd2f97f3c66125c7aa80c82426956811cfccf/librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32", size = 47668, upload-time = "2026-01-14T12:56:00.261Z" }, { url = "https://files.pythonhosted.org/packages/fc/85/69f92b2a7b3c0f88ffe107c86b952b397004b5b8ea5a81da3d9c04c04422/librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06", size = 40550, upload-time = "2026-01-14T12:56:01.542Z" }, - { url = "https://files.pythonhosted.org/packages/3b/9b/2668bb01f568bc89ace53736df950845f8adfcacdf6da087d5cef12110cb/librt-0.7.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c7e8f88f79308d86d8f39c491773cbb533d6cb7fa6476f35d711076ee04fceb6", size = 56680, upload-time = "2026-01-14T12:56:02.602Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d4/dbb3edf2d0ec4ba08dcaf1865833d32737ad208962d4463c022cea6e9d3c/librt-0.7.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:389bd25a0db916e1d6bcb014f11aa9676cedaa485e9ec3752dfe19f196fd377b", size = 58612, upload-time = "2026-01-14T12:56:03.616Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c9/64b029de4ac9901fcd47832c650a0fd050555a452bd455ce8deddddfbb9f/librt-0.7.8-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73fd300f501a052f2ba52ede721232212f3b06503fa12665408ecfc9d8fd149c", size = 163654, upload-time = "2026-01-14T12:56:04.975Z" }, - { url = "https://files.pythonhosted.org/packages/81/5c/95e2abb1b48eb8f8c7fc2ae945321a6b82777947eb544cc785c3f37165b2/librt-0.7.8-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d772edc6a5f7835635c7562f6688e031f0b97e31d538412a852c49c9a6c92d5", size = 172477, upload-time = "2026-01-14T12:56:06.103Z" }, - { url = "https://files.pythonhosted.org/packages/7e/27/9bdf12e05b0eb089dd008d9c8aabc05748aad9d40458ade5e627c9538158/librt-0.7.8-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde8a130bd0f239e45503ab39fab239ace094d63ee1d6b67c25a63d741c0f71", size = 186220, upload-time = "2026-01-14T12:56:09.958Z" }, - { url = "https://files.pythonhosted.org/packages/53/6a/c3774f4cc95e68ed444a39f2c8bd383fd18673db7d6b98cfa709f6634b93/librt-0.7.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fdec6e2368ae4f796fc72fad7fd4bd1753715187e6d870932b0904609e7c878e", size = 183841, upload-time = "2026-01-14T12:56:11.109Z" }, - { url = "https://files.pythonhosted.org/packages/58/6b/48702c61cf83e9c04ad5cec8cad7e5e22a2cde23a13db8ef341598897ddd/librt-0.7.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:00105e7d541a8f2ee5be52caacea98a005e0478cfe78c8080fbb7b5d2b340c63", size = 179751, upload-time = "2026-01-14T12:56:12.278Z" }, - { url = "https://files.pythonhosted.org/packages/35/87/5f607fc73a131d4753f4db948833063c6aad18e18a4e6fbf64316c37ae65/librt-0.7.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c6f8947d3dfd7f91066c5b4385812c18be26c9d5a99ca56667547f2c39149d94", size = 199319, upload-time = "2026-01-14T12:56:13.425Z" }, - { url = "https://files.pythonhosted.org/packages/6e/cc/b7c5ac28ae0f0645a9681248bae4ede665bba15d6f761c291853c5c5b78e/librt-0.7.8-cp39-cp39-win32.whl", hash = "sha256:41d7bb1e07916aeb12ae4a44e3025db3691c4149ab788d0315781b4d29b86afb", size = 43434, upload-time = "2026-01-14T12:56:14.781Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5d/dce0c92f786495adf2c1e6784d9c50a52fb7feb1cfb17af97a08281a6e82/librt-0.7.8-cp39-cp39-win_amd64.whl", hash = "sha256:e90a8e237753c83b8e484d478d9a996dc5e39fd5bd4c6ce32563bc8123f132be", size = 49801, upload-time = "2026-01-14T12:56:15.827Z" }, ] [[package]] @@ -3150,14 +2556,14 @@ name = "logfire" version = "4.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "executing", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-exporter-otlp-proto-http", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-instrumentation", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-sdk", marker = "python_full_version >= '3.10'" }, - { name = "protobuf", version = "6.33.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "rich", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "executing" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-sdk" }, + { name = "protobuf" }, + { name = "rich" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/29/4386b331b8aecad429c043bbd1b7684cdb56ff9e11a5bd96845483dc0968/logfire-4.22.0.tar.gz", hash = "sha256:284b3b955c7515d4428dbc1e04784c3e652e62acf7597bd64a0aa9ecb6a7dedd", size = 654771, upload-time = "2026-02-04T12:17:57.635Z" } wheels = [ @@ -3166,7 +2572,7 @@ wheels = [ [package.optional-dependencies] httpx = [ - { name = "opentelemetry-instrumentation-httpx", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-instrumentation-httpx" }, ] [[package]] @@ -3250,42 +2656,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/a0/89e6a024c3b4485b89ef86881c9d55e097e7cb0bdb74efb746f2fa6a9a76/lupa-2.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9abb98d5a8fd27c8285302e82199f0e56e463066f88f619d6594a450bf269d80", size = 2153693, upload-time = "2025-10-24T07:19:31.379Z" }, { url = "https://files.pythonhosted.org/packages/b6/36/a0f007dc58fc1bbf51fb85dcc82fcb1f21b8c4261361de7dab0e3d8521ef/lupa-2.6-cp314-cp314t-win32.whl", hash = "sha256:1849efeba7a8f6fb8aa2c13790bee988fd242ae404bd459509640eeea3d1e291", size = 1590104, upload-time = "2025-10-24T07:19:33.514Z" }, { url = "https://files.pythonhosted.org/packages/7d/5e/db903ce9cf82c48d6b91bf6d63ae4c8d0d17958939a4e04ba6b9f38b8643/lupa-2.6-cp314-cp314t-win_amd64.whl", hash = "sha256:fc1498d1a4fc028bc521c26d0fad4ca00ed63b952e32fb95949bda76a04bad52", size = 1913818, upload-time = "2025-10-24T07:19:36.039Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c5/918ed6c3af793764bae155d68df47bab2635ab7c94dee3dbb5d9c6d5ba38/lupa-2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8897dc6c3249786b2cdf2f83324febb436193d4581b6a71dea49f77bf8b19bb0", size = 956718, upload-time = "2025-10-24T07:20:03.086Z" }, - { url = "https://files.pythonhosted.org/packages/da/91/0ca797da854478225c0f6a8fc3c500a2f5c11826d732735beb5dffff9e85/lupa-2.6-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:4446396ca3830be0c106c70db4b4f622c37b2d447874c07952cafb9c57949a4a", size = 1923934, upload-time = "2025-10-24T07:20:05.428Z" }, - { url = "https://files.pythonhosted.org/packages/f5/7f/98a6a2535285d43457eb665822ab08447e2196b614db3152772d457ca426/lupa-2.6-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:5826e687c89995a6eaafeae242071ba16448eec1a9ee8e17ed48551b5d1e21c2", size = 987286, upload-time = "2025-10-24T07:20:07.338Z" }, - { url = "https://files.pythonhosted.org/packages/5f/50/edad7c180ab28aa543e6c3895e56a2c7a6ff92140a283316e6086f118552/lupa-2.6-cp39-cp39-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:5871935cb36d1d22f9c04ac0db75c06751bd95edcfa0d9309f732de908e297a9", size = 1176541, upload-time = "2025-10-24T07:20:09.305Z" }, - { url = "https://files.pythonhosted.org/packages/24/b3/27a0ec4c73011e86f9bd2eada010062308a4ed32921898d066ae54e064e1/lupa-2.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:43eb6e43ea8512d0d65b995d36dd9d77aa02598035e25b84c23a1b58700c9fb2", size = 1058981, upload-time = "2025-10-24T07:20:11.7Z" }, - { url = "https://files.pythonhosted.org/packages/a4/12/d55d45a8c253e7981f59ae920bac49dbd49888954b25fd1eb3a7be1321db/lupa-2.6-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:559714053018d9885cc8c36a33c5b7eb9aad30fb6357719cac3ce4dc6b39157e", size = 2103336, upload-time = "2025-10-24T07:20:13.71Z" }, - { url = "https://files.pythonhosted.org/packages/ab/17/058cc212c22d6a25990226afd02c741b2813b5f325396a9180b4accb70ac/lupa-2.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:57ac88a00ce59bd9d4ddcd4fca8e02564765725f5068786b011c9d1be3de20c5", size = 1083209, upload-time = "2025-10-24T07:20:15.587Z" }, - { url = "https://files.pythonhosted.org/packages/e6/01/8ca3a56a4e127784a15f0c7ec1f67e9894c2e9d4bf402389ddda4ea8081b/lupa-2.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:b683fbd867c2e54c44a686361b75eee7e7a790da55afdbe89f1f23b106de0274", size = 1193077, upload-time = "2025-10-24T07:20:17.857Z" }, - { url = "https://files.pythonhosted.org/packages/07/1b/c7fe79bcda6d489306bb7c1a9b4d545b7f0930b9ce80080643fc39b3fdc9/lupa-2.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d2f656903a2ed2e074bf2b7d300968028dfa327a45b055be8e3b51ef0b82f9bf", size = 2198584, upload-time = "2025-10-24T07:20:20.468Z" }, - { url = "https://files.pythonhosted.org/packages/9e/df/3f7631eea3478ac3868cbcb2763c1a4e2f7b875fcb2683f956bf7aabf65f/lupa-2.6-cp39-cp39-win32.whl", hash = "sha256:bf28f68ae231b72008523ab5ac23835ba0f76e0e99ec38b59766080a84eb596a", size = 1414693, upload-time = "2025-10-24T07:20:23.477Z" }, - { url = "https://files.pythonhosted.org/packages/08/e0/3fd9617814663129fa4a9b33a980c3fe344297337cb550c738f87f730f6b/lupa-2.6-cp39-cp39-win_amd64.whl", hash = "sha256:b4b2e9b3795a9897cf6cfcc58d08210fdc0d13ab47c9a0e13858c68932d8353c", size = 1658877, upload-time = "2025-10-24T07:20:27.086Z" }, -] - -[[package]] -name = "markdown" -version = "3.9" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, ] [[package]] name = "markdown" version = "3.10.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, @@ -3296,39 +2672,19 @@ name = "markdown-include-variants" version = "0.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/47/ec9eae4a6d2f336d95681df43720e2b25b045dc3ed44ae6d30a5ce2f5dff/markdown_include_variants-0.0.8.tar.gz", hash = "sha256:46d812340c64dcd3646b1eaa356bafb31626dd7b4955d15c44ff8c48c6357227", size = 46882, upload-time = "2025-12-12T16:11:04.254Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4e/0e/130958e7ec50d13f2ee7b6c142df5c352520a9251baf1652e41262703857/markdown_include_variants-0.0.8-py3-none-any.whl", hash = "sha256:425a300ae25fbcd598506cba67859a9dfa047333e869e0ff2e11a5e354b326dc", size = 8120, upload-time = "2025-12-12T16:11:02.881Z" }, ] -[[package]] -name = "markdown-it-py" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "mdurl", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, -] - [[package]] name = "markdown-it-py" version = "4.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "mdurl", marker = "python_full_version >= '3.10'" }, + { name = "mdurl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } wheels = [ @@ -3418,17 +2774,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, - { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, - { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, - { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, - { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, - { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, - { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, ] [[package]] @@ -3436,20 +2781,20 @@ name = "mcp" version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio", marker = "python_full_version >= '3.10'" }, - { name = "httpx", marker = "python_full_version >= '3.10'" }, - { name = "httpx-sse", marker = "python_full_version >= '3.10'" }, - { name = "jsonschema", marker = "python_full_version >= '3.10'" }, - { name = "pydantic", marker = "python_full_version >= '3.10'" }, - { name = "pydantic-settings", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pyjwt", extra = ["crypto"], marker = "python_full_version >= '3.10'" }, - { name = "python-multipart", version = "0.0.22", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pywin32", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "sse-starlette", marker = "python_full_version >= '3.10'" }, - { name = "starlette", version = "0.52.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, - { name = "typing-inspection", marker = "python_full_version >= '3.10'" }, - { name = "uvicorn", version = "0.40.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and sys_platform != 'emscripten'" }, + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } wheels = [ @@ -3471,8 +2816,7 @@ version = "1.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cyclic" }, - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown" }, { name = "rcslice" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bf/f0/f395a9cf164471d3c7bbe58cbd64d74289575a8b85a962b49a804ab7ed34/mdx_include-1.4.2.tar.gz", hash = "sha256:992f9fbc492b5cf43f7d8cb4b90b52a4e4c5fdd7fd04570290a83eea5c84f297", size = 15051, upload-time = "2022-07-26T05:46:14.129Z" } @@ -3512,14 +2856,11 @@ name = "mkdocs" version = "1.6.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'" }, - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "click" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "ghp-import" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "jinja2" }, - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown" }, { name = "markupsafe" }, { name = "mergedeep" }, { name = "mkdocs-get-deps" }, @@ -3539,8 +2880,7 @@ name = "mkdocs-autorefs" version = "1.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }, ] @@ -3554,10 +2894,8 @@ name = "mkdocs-get-deps" version = "0.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "mergedeep" }, - { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "platformdirs" }, { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } @@ -3579,8 +2917,7 @@ dependencies = [ { name = "pyyaml" }, { name = "requests" }, { name = "super-collections" }, - { 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'" }, + { name = "termcolor" }, ] 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 = [ @@ -3596,8 +2933,7 @@ dependencies = [ { name = "backrefs" }, { name = "colorama" }, { name = "jinja2" }, - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown" }, { name = "mkdocs" }, { name = "mkdocs-material-extensions" }, { name = "paginate" }, @@ -3631,47 +2967,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/ec/38443b1f2a3821bbcb24e46cd8ba979154417794d54baf949fefde1c2146/mkdocs_redirects-1.2.2-py3-none-any.whl", hash = "sha256:7dbfa5647b79a3589da4401403d69494bd1f4ad03b9c15136720367e1f340ed5", size = 6142, upload-time = "2024-11-07T14:57:19.143Z" }, ] -[[package]] -name = "mkdocstrings" -version = "0.30.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, - { name = "jinja2", marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markupsafe", marker = "python_full_version < '3.10'" }, - { name = "mkdocs", marker = "python_full_version < '3.10'" }, - { name = "mkdocs-autorefs", marker = "python_full_version < '3.10'" }, - { name = "pymdown-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/33/2fa3243439f794e685d3e694590d28469a9b8ea733af4b48c250a3ffc9a0/mkdocstrings-0.30.1.tar.gz", hash = "sha256:84a007aae9b707fb0aebfc9da23db4b26fc9ab562eb56e335e9ec480cb19744f", size = 106350, upload-time = "2025-09-19T10:49:26.446Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/2c/f0dc4e1ee7f618f5bff7e05898d20bf8b6e7fa612038f768bfa295f136a4/mkdocstrings-0.30.1-py3-none-any.whl", hash = "sha256:41bd71f284ca4d44a668816193e4025c950b002252081e387433656ae9a70a82", size = 36704, upload-time = "2025-09-19T10:49:24.805Z" }, -] - -[package.optional-dependencies] -python = [ - { name = "mkdocstrings-python", version = "1.18.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, -] - [[package]] name = "mkdocstrings" version = "1.0.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "jinja2", marker = "python_full_version >= '3.10'" }, - { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "markupsafe", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs", marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" }, - { name = "pymdown-extensions", marker = "python_full_version >= '3.10'" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/63/4d/1ca8a9432579184599714aaeb36591414cc3d3bfd9d494f6db540c995ae4/mkdocstrings-1.0.2.tar.gz", hash = "sha256:48edd0ccbcb9e30a3121684e165261a9d6af4d63385fc4f39a54a49ac3b32ea8", size = 101048, upload-time = "2026-01-24T15:57:25.735Z" } wheels = [ @@ -3680,40 +2986,18 @@ wheels = [ [package.optional-dependencies] python = [ - { name = "mkdocstrings-python", version = "2.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, -] - -[[package]] -name = "mkdocstrings-python" -version = "1.18.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "mkdocs-autorefs", marker = "python_full_version < '3.10'" }, - { name = "mkdocstrings", version = "0.30.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/ae/58ab2bfbee2792e92a98b97e872f7c003deb903071f75d8d83aa55db28fa/mkdocstrings_python-1.18.2.tar.gz", hash = "sha256:4ad536920a07b6336f50d4c6d5603316fafb1172c5c882370cbbc954770ad323", size = 207972, upload-time = "2025-08-28T16:11:19.847Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/8f/ce008599d9adebf33ed144e7736914385e8537f5fc686fdb7cceb8c22431/mkdocstrings_python-1.18.2-py3-none-any.whl", hash = "sha256:944fe6deb8f08f33fa936d538233c4036e9f53e840994f6146e8e94eb71b600d", size = 138215, upload-time = "2025-08-28T16:11:18.176Z" }, + { name = "mkdocstrings-python" }, ] [[package]] name = "mkdocstrings-python" version = "2.0.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "mkdocs-autorefs", marker = "python_full_version >= '3.10'" }, - { name = "mkdocstrings", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, + { name = "griffe" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" } wheels = [ @@ -3864,24 +3148,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ee/74525ebe3eb5fddcd6735fc03cbea3feeed4122b53bc798ac32d297ac9ae/multidict-6.7.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65573858d27cdeaca41893185677dc82395159aa28875a8867af66532d413a8f", size = 77107, upload-time = "2026-01-26T02:46:12.608Z" }, - { url = "https://files.pythonhosted.org/packages/f0/9a/ce8744e777a74b3050b1bf56be3eed1053b3457302ea055f1ea437200a23/multidict-6.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c524c6fb8fc342793708ab111c4dbc90ff9abd568de220432500e47e990c0358", size = 44943, upload-time = "2026-01-26T02:46:14.016Z" }, - { url = "https://files.pythonhosted.org/packages/83/9c/1d2a283d9c6f31e260cb6c2fccadc3edcf6c4c14ee0929cd2af4d2606dd7/multidict-6.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:aa23b001d968faef416ff70dc0f1ab045517b9b42a90edd3e9bcdb06479e31d5", size = 44603, upload-time = "2026-01-26T02:46:15.391Z" }, - { url = "https://files.pythonhosted.org/packages/87/9d/3b186201671583d8e8d6d79c07481a5aafd0ba7575e3d8566baec80c1e82/multidict-6.7.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6704fa2b7453b2fb121740555fa1ee20cd98c4d011120caf4d2b8d4e7c76eec0", size = 240573, upload-time = "2026-01-26T02:46:16.783Z" }, - { url = "https://files.pythonhosted.org/packages/42/7d/a52f5d4d0754311d1ac78478e34dff88de71259a8585e05ee14e5f877caf/multidict-6.7.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:121a34e5bfa410cdf2c8c49716de160de3b1dbcd86b49656f5681e4543bcd1a8", size = 240106, upload-time = "2026-01-26T02:46:18.432Z" }, - { url = "https://files.pythonhosted.org/packages/84/9f/d80118e6c30ff55b7d171bdc5520aad4b9626e657520b8d7c8ca8c2fad12/multidict-6.7.1-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:026d264228bcd637d4e060844e39cdc60f86c479e463d49075dedc21b18fbbe0", size = 219418, upload-time = "2026-01-26T02:46:20.526Z" }, - { url = "https://files.pythonhosted.org/packages/c7/bd/896e60b3457f194de77c7de64f9acce9f75da0518a5230ce1df534f6747b/multidict-6.7.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e697826df7eb63418ee190fd06ce9f1803593bb4b9517d08c60d9b9a7f69d8f", size = 252124, upload-time = "2026-01-26T02:46:22.157Z" }, - { url = "https://files.pythonhosted.org/packages/f4/de/ba6b30447c36a37078d0ba604aa12c1a52887af0c355236ca6e0a9d5286f/multidict-6.7.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb08271280173720e9fea9ede98e5231defcbad90f1624bea26f32ec8a956e2f", size = 249402, upload-time = "2026-01-26T02:46:23.718Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b2/50a383c96230e432895a2fd3bcfe1b65785899598259d871d5de6b93180c/multidict-6.7.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6b3228e1d80af737b72925ce5fb4daf5a335e49cd7ab77ed7b9fdfbf58c526e", size = 240346, upload-time = "2026-01-26T02:46:25.393Z" }, - { url = "https://files.pythonhosted.org/packages/89/37/16d391fd8da544b1489306e38a46785fa41dd0f0ef766837ed7d4676dde0/multidict-6.7.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3943debf0fbb57bdde5901695c11094a9a36723e5c03875f87718ee15ca2f4d2", size = 237010, upload-time = "2026-01-26T02:46:27.408Z" }, - { url = "https://files.pythonhosted.org/packages/b0/24/3152ee026eda86d5d3e3685182911e6951af7a016579da931080ce6ac9ad/multidict-6.7.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:98c5787b0a0d9a41d9311eae44c3b76e6753def8d8870ab501320efe75a6a5f8", size = 232018, upload-time = "2026-01-26T02:46:29.941Z" }, - { url = "https://files.pythonhosted.org/packages/9c/1f/48d3c27a72be7fd23a55d8847193c459959bf35a5bb5844530dab00b739b/multidict-6.7.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:08ccb2a6dc72009093ebe7f3f073e5ec5964cba9a706fa94b1a1484039b87941", size = 241498, upload-time = "2026-01-26T02:46:32.052Z" }, - { url = "https://files.pythonhosted.org/packages/1a/45/413643ae2952d0decdf6c1250f86d08a43e143271441e81027e38d598bd7/multidict-6.7.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb351f72c26dc9abe338ca7294661aa22969ad8ffe7ef7d5541d19f368dc854a", size = 247957, upload-time = "2026-01-26T02:46:33.666Z" }, - { url = "https://files.pythonhosted.org/packages/50/f8/f1d0ac23df15e0470776388bdb261506f63af1f81d28bacb5e262d6e12b6/multidict-6.7.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac1c665bad8b5d762f5f85ebe4d94130c26965f11de70c708c75671297c776de", size = 241651, upload-time = "2026-01-26T02:46:35.7Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c9/1a2a18f383cf129add66b6c36b75c3911a7ba95cf26cb141482de085cc12/multidict-6.7.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fa6609d0364f4f6f58351b4659a1f3e0e898ba2a8c5cac04cb2c7bc556b0bc5", size = 236371, upload-time = "2026-01-26T02:46:37.37Z" }, - { url = "https://files.pythonhosted.org/packages/bb/aa/77d87e3fca31325b87e0eb72d5fe9a7472dcb51391a42df7ac1f3842f6c0/multidict-6.7.1-cp39-cp39-win32.whl", hash = "sha256:6f77ce314a29263e67adadc7e7c1bc699fcb3a305059ab973d038f87caa42ed0", size = 41426, upload-time = "2026-01-26T02:46:39.026Z" }, - { url = "https://files.pythonhosted.org/packages/e3/b3/e8863e6a2da15a9d7e98976ff402e871b7352c76566df6c18d0378e0d9cf/multidict-6.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:f537b55778cd3cbee430abe3131255d3a78202e0f9ea7ffc6ada893a4bcaeea4", size = 46180, upload-time = "2026-01-26T02:46:40.422Z" }, - { url = "https://files.pythonhosted.org/packages/93/d3/dd4fa951ad5b5fa216bf30054d705683d13405eea7459833d78f31b74c9c/multidict-6.7.1-cp39-cp39-win_arm64.whl", hash = "sha256:749aa54f578f2e5f439538706a475aa844bfa8ef75854b1401e6e528e4937cf9", size = 43231, upload-time = "2026-01-26T02:46:41.945Z" }, { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] @@ -3928,12 +3194,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f7/88436084550ca9af5e610fa45286be04c3b63374df3e021c762fe8c4369f/mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3", size = 13102606, upload-time = "2025-12-15T05:02:46.833Z" }, - { url = "https://files.pythonhosted.org/packages/ca/a5/43dfad311a734b48a752790571fd9e12d61893849a01bff346a54011957f/mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a", size = 12164496, upload-time = "2025-12-15T05:03:41.947Z" }, - { url = "https://files.pythonhosted.org/packages/88/f0/efbfa391395cce2f2771f937e0620cfd185ec88f2b9cd88711028a768e96/mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67", size = 12772068, upload-time = "2025-12-15T05:02:53.689Z" }, - { url = "https://files.pythonhosted.org/packages/25/05/58b3ba28f5aed10479e899a12d2120d582ba9fa6288851b20bf1c32cbb4f/mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e", size = 13520385, upload-time = "2025-12-15T05:02:38.328Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a0/c006ccaff50b31e542ae69b92fe7e2f55d99fba3a55e01067dd564325f85/mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376", size = 13796221, upload-time = "2025-12-15T05:03:22.147Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ff/8bdb051cd710f01b880472241bd36b3f817a8e1c5d5540d0b761675b6de2/mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24", size = 10055456, upload-time = "2025-12-15T05:03:35.169Z" }, { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] @@ -3946,31 +3206,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] -[[package]] -name = "nexus-rpc" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ef/66/540687556bd28cf1ec370cc6881456203dfddb9dab047b8979c6865b5984/nexus_rpc-1.1.0.tar.gz", hash = "sha256:d65ad6a2f54f14e53ebe39ee30555eaeb894102437125733fb13034a04a44553", size = 77383, upload-time = "2025-07-07T19:03:58.368Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/2f/9e9d0dcaa4c6ffa22b7aa31069a8a264c753ff8027b36af602cce038c92f/nexus_rpc-1.1.0-py3-none-any.whl", hash = "sha256:d1b007af2aba186a27e736f8eaae39c03aed05b488084ff6c3d1785c9ba2ad38", size = 27743, upload-time = "2025-07-07T19:03:57.556Z" }, -] - [[package]] name = "nexus-rpc" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/50/95d7bc91f900da5e22662c82d9bf0f72a4b01f2a552708bf2f43807707a1/nexus_rpc-1.2.0.tar.gz", hash = "sha256:b4ddaffa4d3996aaeadf49b80dfcdfbca48fe4cb616defaf3b3c5c2c8fc61890", size = 74142, upload-time = "2025-11-17T19:17:06.798Z" } wheels = [ @@ -4001,7 +3242,7 @@ name = "openapi-pydantic" version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pydantic", marker = "python_full_version >= '3.10'" }, + { name = "pydantic" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" } wheels = [ @@ -4026,7 +3267,7 @@ name = "opentelemetry-exporter-otlp-proto-common" version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "opentelemetry-proto", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-proto" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } wheels = [ @@ -4038,13 +3279,13 @@ name = "opentelemetry-exporter-otlp-proto-http" version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "googleapis-common-protos", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-exporter-otlp-proto-common", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-proto", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-sdk", marker = "python_full_version >= '3.10'" }, - { name = "requests", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } wheels = [ @@ -4056,10 +3297,10 @@ name = "opentelemetry-instrumentation" version = "0.60b1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-semantic-conventions", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "wrapt", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, ] sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } wheels = [ @@ -4071,11 +3312,11 @@ name = "opentelemetry-instrumentation-httpx" version = "0.60b1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-instrumentation", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-semantic-conventions", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-util-http", marker = "python_full_version >= '3.10'" }, - { name = "wrapt", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "wrapt" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/08/11208bcfcab4fc2023252c3f322aa397fd9ad948355fea60f5fc98648603/opentelemetry_instrumentation_httpx-0.60b1.tar.gz", hash = "sha256:a506ebaf28c60112cbe70ad4f0338f8603f148938cb7b6794ce1051cd2b270ae", size = 20611, upload-time = "2025-12-11T13:37:01.661Z" } wheels = [ @@ -4087,7 +3328,7 @@ name = "opentelemetry-proto" version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "protobuf", version = "6.33.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "protobuf" }, ] sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } wheels = [ @@ -4099,9 +3340,9 @@ name = "opentelemetry-sdk" version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-semantic-conventions", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } wheels = [ @@ -4113,8 +3354,8 @@ name = "opentelemetry-semantic-conventions" version = "0.60b1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } wheels = [ @@ -4130,111 +3371,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/5c/d3f1733665f7cd582ef0842fb1d2ed0bc1fba10875160593342d22bba375/opentelemetry_util_http-0.60b1-py3-none-any.whl", hash = "sha256:66381ba28550c91bee14dcba8979ace443444af1ed609226634596b4b0faf199", size = 8947, upload-time = "2025-12-11T13:36:37.151Z" }, ] -[[package]] -name = "orjson" -version = "3.11.5" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/04/b8/333fdb27840f3bf04022d21b654a35f58e15407183aeb16f3b41aa053446/orjson-3.11.5.tar.gz", hash = "sha256:82393ab47b4fe44ffd0a7659fa9cfaacc717eb617c93cde83795f14af5c2e9d5", size = 5972347, upload-time = "2025-12-06T15:55:39.458Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/19/b22cf9dad4db20c8737041046054cbd4f38bb5a2d0e4bb60487832ce3d76/orjson-3.11.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:df9eadb2a6386d5ea2bfd81309c505e125cfc9ba2b1b99a97e60985b0b3665d1", size = 245719, upload-time = "2025-12-06T15:53:43.877Z" }, - { url = "https://files.pythonhosted.org/packages/03/2e/b136dd6bf30ef5143fbe76a4c142828b55ccc618be490201e9073ad954a1/orjson-3.11.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc70da619744467d8f1f49a8cadae5ec7bbe054e5232d95f92ed8737f8c5870", size = 132467, upload-time = "2025-12-06T15:53:45.379Z" }, - { url = "https://files.pythonhosted.org/packages/ae/fc/ae99bfc1e1887d20a0268f0e2686eb5b13d0ea7bbe01de2b566febcd2130/orjson-3.11.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:073aab025294c2f6fc0807201c76fdaed86f8fc4be52c440fb78fbb759a1ac09", size = 130702, upload-time = "2025-12-06T15:53:46.659Z" }, - { url = "https://files.pythonhosted.org/packages/6e/43/ef7912144097765997170aca59249725c3ab8ef6079f93f9d708dd058df5/orjson-3.11.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:835f26fa24ba0bb8c53ae2a9328d1706135b74ec653ed933869b74b6909e63fd", size = 135907, upload-time = "2025-12-06T15:53:48.487Z" }, - { url = "https://files.pythonhosted.org/packages/3f/da/24d50e2d7f4092ddd4d784e37a3fa41f22ce8ed97abc9edd222901a96e74/orjson-3.11.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667c132f1f3651c14522a119e4dd631fad98761fa960c55e8e7430bb2a1ba4ac", size = 139935, upload-time = "2025-12-06T15:53:49.88Z" }, - { url = "https://files.pythonhosted.org/packages/02/4a/b4cb6fcbfff5b95a3a019a8648255a0fac9b221fbf6b6e72be8df2361feb/orjson-3.11.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42e8961196af655bb5e63ce6c60d25e8798cd4dfbc04f4203457fa3869322c2e", size = 137541, upload-time = "2025-12-06T15:53:51.226Z" }, - { url = "https://files.pythonhosted.org/packages/a5/99/a11bd129f18c2377c27b2846a9d9be04acec981f770d711ba0aaea563984/orjson-3.11.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75412ca06e20904c19170f8a24486c4e6c7887dea591ba18a1ab572f1300ee9f", size = 139031, upload-time = "2025-12-06T15:53:52.309Z" }, - { url = "https://files.pythonhosted.org/packages/64/29/d7b77d7911574733a036bb3e8ad7053ceb2b7d6ea42208b9dbc55b23b9ed/orjson-3.11.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6af8680328c69e15324b5af3ae38abbfcf9cbec37b5346ebfd52339c3d7e8a18", size = 141622, upload-time = "2025-12-06T15:53:53.606Z" }, - { url = "https://files.pythonhosted.org/packages/93/41/332db96c1de76b2feda4f453e91c27202cd092835936ce2b70828212f726/orjson-3.11.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a86fe4ff4ea523eac8f4b57fdac319faf037d3c1be12405e6a7e86b3fbc4756a", size = 413800, upload-time = "2025-12-06T15:53:54.866Z" }, - { url = "https://files.pythonhosted.org/packages/76/e1/5a0d148dd1f89ad2f9651df67835b209ab7fcb1118658cf353425d7563e9/orjson-3.11.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e607b49b1a106ee2086633167033afbd63f76f2999e9236f638b06b112b24ea7", size = 151198, upload-time = "2025-12-06T15:53:56.383Z" }, - { url = "https://files.pythonhosted.org/packages/0d/96/8db67430d317a01ae5cf7971914f6775affdcfe99f5bff9ef3da32492ecc/orjson-3.11.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7339f41c244d0eea251637727f016b3d20050636695bc78345cce9029b189401", size = 141984, upload-time = "2025-12-06T15:53:57.746Z" }, - { url = "https://files.pythonhosted.org/packages/71/49/40d21e1aa1ac569e521069228bb29c9b5a350344ccf922a0227d93c2ed44/orjson-3.11.5-cp310-cp310-win32.whl", hash = "sha256:8be318da8413cdbbce77b8c5fac8d13f6eb0f0db41b30bb598631412619572e8", size = 135272, upload-time = "2025-12-06T15:53:59.769Z" }, - { url = "https://files.pythonhosted.org/packages/c4/7e/d0e31e78be0c100e08be64f48d2850b23bcb4d4c70d114f4e43b39f6895a/orjson-3.11.5-cp310-cp310-win_amd64.whl", hash = "sha256:b9f86d69ae822cabc2a0f6c099b43e8733dda788405cba2665595b7e8dd8d167", size = 133360, upload-time = "2025-12-06T15:54:01.25Z" }, - { url = "https://files.pythonhosted.org/packages/fd/68/6b3659daec3a81aed5ab47700adb1a577c76a5452d35b91c88efee89987f/orjson-3.11.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9c8494625ad60a923af6b2b0bd74107146efe9b55099e20d7740d995f338fcd8", size = 245318, upload-time = "2025-12-06T15:54:02.355Z" }, - { url = "https://files.pythonhosted.org/packages/e9/00/92db122261425f61803ccf0830699ea5567439d966cbc35856fe711bfe6b/orjson-3.11.5-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:7bb2ce0b82bc9fd1168a513ddae7a857994b780b2945a8c51db4ab1c4b751ebc", size = 129491, upload-time = "2025-12-06T15:54:03.877Z" }, - { url = "https://files.pythonhosted.org/packages/94/4f/ffdcb18356518809d944e1e1f77589845c278a1ebbb5a8297dfefcc4b4cb/orjson-3.11.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67394d3becd50b954c4ecd24ac90b5051ee7c903d167459f93e77fc6f5b4c968", size = 132167, upload-time = "2025-12-06T15:54:04.944Z" }, - { url = "https://files.pythonhosted.org/packages/97/c6/0a8caff96f4503f4f7dd44e40e90f4d14acf80d3b7a97cb88747bb712d3e/orjson-3.11.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:298d2451f375e5f17b897794bcc3e7b821c0f32b4788b9bcae47ada24d7f3cf7", size = 130516, upload-time = "2025-12-06T15:54:06.274Z" }, - { url = "https://files.pythonhosted.org/packages/4d/63/43d4dc9bd9954bff7052f700fdb501067f6fb134a003ddcea2a0bb3854ed/orjson-3.11.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa5e4244063db8e1d87e0f54c3f7522f14b2dc937e65d5241ef0076a096409fd", size = 135695, upload-time = "2025-12-06T15:54:07.702Z" }, - { url = "https://files.pythonhosted.org/packages/87/6f/27e2e76d110919cb7fcb72b26166ee676480a701bcf8fc53ac5d0edce32f/orjson-3.11.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1db2088b490761976c1b2e956d5d4e6409f3732e9d79cfa69f876c5248d1baf9", size = 139664, upload-time = "2025-12-06T15:54:08.828Z" }, - { url = "https://files.pythonhosted.org/packages/d4/f8/5966153a5f1be49b5fbb8ca619a529fde7bc71aa0a376f2bb83fed248bcd/orjson-3.11.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2ed66358f32c24e10ceea518e16eb3549e34f33a9d51f99ce23b0251776a1ef", size = 137289, upload-time = "2025-12-06T15:54:09.898Z" }, - { url = "https://files.pythonhosted.org/packages/a7/34/8acb12ff0299385c8bbcbb19fbe40030f23f15a6de57a9c587ebf71483fb/orjson-3.11.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2021afda46c1ed64d74b555065dbd4c2558d510d8cec5ea6a53001b3e5e82a9", size = 138784, upload-time = "2025-12-06T15:54:11.022Z" }, - { url = "https://files.pythonhosted.org/packages/ee/27/910421ea6e34a527f73d8f4ee7bdffa48357ff79c7b8d6eb6f7b82dd1176/orjson-3.11.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b42ffbed9128e547a1647a3e50bc88ab28ae9daa61713962e0d3dd35e820c125", size = 141322, upload-time = "2025-12-06T15:54:12.427Z" }, - { url = "https://files.pythonhosted.org/packages/87/a3/4b703edd1a05555d4bb1753d6ce44e1a05b7a6d7c164d5b332c795c63d70/orjson-3.11.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8d5f16195bb671a5dd3d1dbea758918bada8f6cc27de72bd64adfbd748770814", size = 413612, upload-time = "2025-12-06T15:54:13.858Z" }, - { url = "https://files.pythonhosted.org/packages/1b/36/034177f11d7eeea16d3d2c42a1883b0373978e08bc9dad387f5074c786d8/orjson-3.11.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c0e5d9f7a0227df2927d343a6e3859bebf9208b427c79bd31949abcc2fa32fa5", size = 150993, upload-time = "2025-12-06T15:54:15.189Z" }, - { url = "https://files.pythonhosted.org/packages/44/2f/ea8b24ee046a50a7d141c0227c4496b1180b215e728e3b640684f0ea448d/orjson-3.11.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23d04c4543e78f724c4dfe656b3791b5f98e4c9253e13b2636f1af5d90e4a880", size = 141774, upload-time = "2025-12-06T15:54:16.451Z" }, - { url = "https://files.pythonhosted.org/packages/8a/12/cc440554bf8200eb23348a5744a575a342497b65261cd65ef3b28332510a/orjson-3.11.5-cp311-cp311-win32.whl", hash = "sha256:c404603df4865f8e0afe981aa3c4b62b406e6d06049564d58934860b62b7f91d", size = 135109, upload-time = "2025-12-06T15:54:17.73Z" }, - { url = "https://files.pythonhosted.org/packages/a3/83/e0c5aa06ba73a6760134b169f11fb970caa1525fa4461f94d76e692299d9/orjson-3.11.5-cp311-cp311-win_amd64.whl", hash = "sha256:9645ef655735a74da4990c24ffbd6894828fbfa117bc97c1edd98c282ecb52e1", size = 133193, upload-time = "2025-12-06T15:54:19.426Z" }, - { url = "https://files.pythonhosted.org/packages/cb/35/5b77eaebc60d735e832c5b1a20b155667645d123f09d471db0a78280fb49/orjson-3.11.5-cp311-cp311-win_arm64.whl", hash = "sha256:1cbf2735722623fcdee8e712cbaaab9e372bbcb0c7924ad711b261c2eccf4a5c", size = 126830, upload-time = "2025-12-06T15:54:20.836Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a4/8052a029029b096a78955eadd68ab594ce2197e24ec50e6b6d2ab3f4e33b/orjson-3.11.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:334e5b4bff9ad101237c2d799d9fd45737752929753bf4faf4b207335a416b7d", size = 245347, upload-time = "2025-12-06T15:54:22.061Z" }, - { url = "https://files.pythonhosted.org/packages/64/67/574a7732bd9d9d79ac620c8790b4cfe0717a3d5a6eb2b539e6e8995e24a0/orjson-3.11.5-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:ff770589960a86eae279f5d8aa536196ebda8273a2a07db2a54e82b93bc86626", size = 129435, upload-time = "2025-12-06T15:54:23.615Z" }, - { url = "https://files.pythonhosted.org/packages/52/8d/544e77d7a29d90cf4d9eecd0ae801c688e7f3d1adfa2ebae5e1e94d38ab9/orjson-3.11.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed24250e55efbcb0b35bed7caaec8cedf858ab2f9f2201f17b8938c618c8ca6f", size = 132074, upload-time = "2025-12-06T15:54:24.694Z" }, - { url = "https://files.pythonhosted.org/packages/6e/57/b9f5b5b6fbff9c26f77e785baf56ae8460ef74acdb3eae4931c25b8f5ba9/orjson-3.11.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a66d7769e98a08a12a139049aac2f0ca3adae989817f8c43337455fbc7669b85", size = 130520, upload-time = "2025-12-06T15:54:26.185Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6d/d34970bf9eb33f9ec7c979a262cad86076814859e54eb9a059a52f6dc13d/orjson-3.11.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86cfc555bfd5794d24c6a1903e558b50644e5e68e6471d66502ce5cb5fdef3f9", size = 136209, upload-time = "2025-12-06T15:54:27.264Z" }, - { url = "https://files.pythonhosted.org/packages/e7/39/bc373b63cc0e117a105ea12e57280f83ae52fdee426890d57412432d63b3/orjson-3.11.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a230065027bc2a025e944f9d4714976a81e7ecfa940923283bca7bbc1f10f626", size = 139837, upload-time = "2025-12-06T15:54:28.75Z" }, - { url = "https://files.pythonhosted.org/packages/cb/aa/7c4818c8d7d324da220f4f1af55c343956003aa4d1ce1857bdc1d396ba69/orjson-3.11.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b29d36b60e606df01959c4b982729c8845c69d1963f88686608be9ced96dbfaa", size = 137307, upload-time = "2025-12-06T15:54:29.856Z" }, - { url = "https://files.pythonhosted.org/packages/46/bf/0993b5a056759ba65145effe3a79dd5a939d4a070eaa5da2ee3180fbb13f/orjson-3.11.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74099c6b230d4261fdc3169d50efc09abf38ace1a42ea2f9994b1d79153d477", size = 139020, upload-time = "2025-12-06T15:54:31.024Z" }, - { url = "https://files.pythonhosted.org/packages/65/e8/83a6c95db3039e504eda60fc388f9faedbb4f6472f5aba7084e06552d9aa/orjson-3.11.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e697d06ad57dd0c7a737771d470eedc18e68dfdefcdd3b7de7f33dfda5b6212e", size = 141099, upload-time = "2025-12-06T15:54:32.196Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b4/24fdc024abfce31c2f6812973b0a693688037ece5dc64b7a60c1ce69e2f2/orjson-3.11.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e08ca8a6c851e95aaecc32bc44a5aa75d0ad26af8cdac7c77e4ed93acf3d5b69", size = 413540, upload-time = "2025-12-06T15:54:33.361Z" }, - { url = "https://files.pythonhosted.org/packages/d9/37/01c0ec95d55ed0c11e4cae3e10427e479bba40c77312b63e1f9665e0737d/orjson-3.11.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8b5f96c05fce7d0218df3fdfeb962d6b8cfff7e3e20264306b46dd8b217c0f3", size = 151530, upload-time = "2025-12-06T15:54:34.6Z" }, - { url = "https://files.pythonhosted.org/packages/f9/d4/f9ebc57182705bb4bbe63f5bbe14af43722a2533135e1d2fb7affa0c355d/orjson-3.11.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ddbfdb5099b3e6ba6d6ea818f61997bb66de14b411357d24c4612cf1ebad08ca", size = 141863, upload-time = "2025-12-06T15:54:35.801Z" }, - { url = "https://files.pythonhosted.org/packages/0d/04/02102b8d19fdcb009d72d622bb5781e8f3fae1646bf3e18c53d1bc8115b5/orjson-3.11.5-cp312-cp312-win32.whl", hash = "sha256:9172578c4eb09dbfcf1657d43198de59b6cef4054de385365060ed50c458ac98", size = 135255, upload-time = "2025-12-06T15:54:37.209Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fb/f05646c43d5450492cb387de5549f6de90a71001682c17882d9f66476af5/orjson-3.11.5-cp312-cp312-win_amd64.whl", hash = "sha256:2b91126e7b470ff2e75746f6f6ee32b9ab67b7a93c8ba1d15d3a0caaf16ec875", size = 133252, upload-time = "2025-12-06T15:54:38.401Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/7b8c0b26ba18c793533ac1cd145e131e46fcf43952aa94c109b5b913c1f0/orjson-3.11.5-cp312-cp312-win_arm64.whl", hash = "sha256:acbc5fac7e06777555b0722b8ad5f574739e99ffe99467ed63da98f97f9ca0fe", size = 126777, upload-time = "2025-12-06T15:54:39.515Z" }, - { url = "https://files.pythonhosted.org/packages/10/43/61a77040ce59f1569edf38f0b9faadc90c8cf7e9bec2e0df51d0132c6bb7/orjson-3.11.5-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3b01799262081a4c47c035dd77c1301d40f568f77cc7ec1bb7db5d63b0a01629", size = 245271, upload-time = "2025-12-06T15:54:40.878Z" }, - { url = "https://files.pythonhosted.org/packages/55/f9/0f79be617388227866d50edd2fd320cb8fb94dc1501184bb1620981a0aba/orjson-3.11.5-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:61de247948108484779f57a9f406e4c84d636fa5a59e411e6352484985e8a7c3", size = 129422, upload-time = "2025-12-06T15:54:42.403Z" }, - { url = "https://files.pythonhosted.org/packages/77/42/f1bf1549b432d4a78bfa95735b79b5dac75b65b5bb815bba86ad406ead0a/orjson-3.11.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:894aea2e63d4f24a7f04a1908307c738d0dce992e9249e744b8f4e8dd9197f39", size = 132060, upload-time = "2025-12-06T15:54:43.531Z" }, - { url = "https://files.pythonhosted.org/packages/25/49/825aa6b929f1a6ed244c78acd7b22c1481fd7e5fda047dc8bf4c1a807eb6/orjson-3.11.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ddc21521598dbe369d83d4d40338e23d4101dad21dae0e79fa20465dbace019f", size = 130391, upload-time = "2025-12-06T15:54:45.059Z" }, - { url = "https://files.pythonhosted.org/packages/42/ec/de55391858b49e16e1aa8f0bbbb7e5997b7345d8e984a2dec3746d13065b/orjson-3.11.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cce16ae2f5fb2c53c3eafdd1706cb7b6530a67cc1c17abe8ec747f5cd7c0c51", size = 135964, upload-time = "2025-12-06T15:54:46.576Z" }, - { url = "https://files.pythonhosted.org/packages/1c/40/820bc63121d2d28818556a2d0a09384a9f0262407cf9fa305e091a8048df/orjson-3.11.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e46c762d9f0e1cfb4ccc8515de7f349abbc95b59cb5a2bd68df5973fdef913f8", size = 139817, upload-time = "2025-12-06T15:54:48.084Z" }, - { url = "https://files.pythonhosted.org/packages/09/c7/3a445ca9a84a0d59d26365fd8898ff52bdfcdcb825bcc6519830371d2364/orjson-3.11.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7345c759276b798ccd6d77a87136029e71e66a8bbf2d2755cbdde1d82e78706", size = 137336, upload-time = "2025-12-06T15:54:49.426Z" }, - { url = "https://files.pythonhosted.org/packages/9a/b3/dc0d3771f2e5d1f13368f56b339c6782f955c6a20b50465a91acb79fe961/orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75bc2e59e6a2ac1dd28901d07115abdebc4563b5b07dd612bf64260a201b1c7f", size = 138993, upload-time = "2025-12-06T15:54:50.939Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a2/65267e959de6abe23444659b6e19c888f242bf7725ff927e2292776f6b89/orjson-3.11.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:54aae9b654554c3b4edd61896b978568c6daa16af96fa4681c9b5babd469f863", size = 141070, upload-time = "2025-12-06T15:54:52.414Z" }, - { url = "https://files.pythonhosted.org/packages/63/c9/da44a321b288727a322c6ab17e1754195708786a04f4f9d2220a5076a649/orjson-3.11.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4bdd8d164a871c4ec773f9de0f6fe8769c2d6727879c37a9666ba4183b7f8228", size = 413505, upload-time = "2025-12-06T15:54:53.67Z" }, - { url = "https://files.pythonhosted.org/packages/7f/17/68dc14fa7000eefb3d4d6d7326a190c99bb65e319f02747ef3ebf2452f12/orjson-3.11.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a261fef929bcf98a60713bf5e95ad067cea16ae345d9a35034e73c3990e927d2", size = 151342, upload-time = "2025-12-06T15:54:55.113Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c5/ccee774b67225bed630a57478529fc026eda33d94fe4c0eac8fe58d4aa52/orjson-3.11.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c028a394c766693c5c9909dec76b24f37e6a1b91999e8d0c0d5feecbe93c3e05", size = 141823, upload-time = "2025-12-06T15:54:56.331Z" }, - { url = "https://files.pythonhosted.org/packages/67/80/5d00e4155d0cd7390ae2087130637671da713959bb558db9bac5e6f6b042/orjson-3.11.5-cp313-cp313-win32.whl", hash = "sha256:2cc79aaad1dfabe1bd2d50ee09814a1253164b3da4c00a78c458d82d04b3bdef", size = 135236, upload-time = "2025-12-06T15:54:57.507Z" }, - { url = "https://files.pythonhosted.org/packages/95/fe/792cc06a84808dbdc20ac6eab6811c53091b42f8e51ecebf14b540e9cfe4/orjson-3.11.5-cp313-cp313-win_amd64.whl", hash = "sha256:ff7877d376add4e16b274e35a3f58b7f37b362abf4aa31863dadacdd20e3a583", size = 133167, upload-time = "2025-12-06T15:54:58.71Z" }, - { url = "https://files.pythonhosted.org/packages/46/2c/d158bd8b50e3b1cfdcf406a7e463f6ffe3f0d167b99634717acdaf5e299f/orjson-3.11.5-cp313-cp313-win_arm64.whl", hash = "sha256:59ac72ea775c88b163ba8d21b0177628bd015c5dd060647bbab6e22da3aad287", size = 126712, upload-time = "2025-12-06T15:54:59.892Z" }, - { url = "https://files.pythonhosted.org/packages/c2/60/77d7b839e317ead7bb225d55bb50f7ea75f47afc489c81199befc5435b50/orjson-3.11.5-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e446a8ea0a4c366ceafc7d97067bfd55292969143b57e3c846d87fc701e797a0", size = 245252, upload-time = "2025-12-06T15:55:01.127Z" }, - { url = "https://files.pythonhosted.org/packages/f1/aa/d4639163b400f8044cef0fb9aa51b0337be0da3a27187a20d1166e742370/orjson-3.11.5-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:53deb5addae9c22bbe3739298f5f2196afa881ea75944e7720681c7080909a81", size = 129419, upload-time = "2025-12-06T15:55:02.723Z" }, - { url = "https://files.pythonhosted.org/packages/30/94/9eabf94f2e11c671111139edf5ec410d2f21e6feee717804f7e8872d883f/orjson-3.11.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd00d49d6063d2b8791da5d4f9d20539c5951f965e45ccf4e96d33505ce68f", size = 132050, upload-time = "2025-12-06T15:55:03.918Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c8/ca10f5c5322f341ea9a9f1097e140be17a88f88d1cfdd29df522970d9744/orjson-3.11.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fd15f9fc8c203aeceff4fda211157fad114dde66e92e24097b3647a08f4ee9e", size = 130370, upload-time = "2025-12-06T15:55:05.173Z" }, - { url = "https://files.pythonhosted.org/packages/25/d4/e96824476d361ee2edd5c6290ceb8d7edf88d81148a6ce172fc00278ca7f/orjson-3.11.5-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df95000fbe6777bf9820ae82ab7578e8662051bb5f83d71a28992f539d2cda7", size = 136012, upload-time = "2025-12-06T15:55:06.402Z" }, - { url = "https://files.pythonhosted.org/packages/85/8e/9bc3423308c425c588903f2d103cfcfe2539e07a25d6522900645a6f257f/orjson-3.11.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a8d676748fca47ade5bc3da7430ed7767afe51b2f8100e3cd65e151c0eaceb", size = 139809, upload-time = "2025-12-06T15:55:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/e9/3c/b404e94e0b02a232b957c54643ce68d0268dacb67ac33ffdee24008c8b27/orjson-3.11.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa0f513be38b40234c77975e68805506cad5d57b3dfd8fe3baa7f4f4051e15b4", size = 137332, upload-time = "2025-12-06T15:55:08.961Z" }, - { url = "https://files.pythonhosted.org/packages/51/30/cc2d69d5ce0ad9b84811cdf4a0cd5362ac27205a921da524ff42f26d65e0/orjson-3.11.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1863e75b92891f553b7922ce4ee10ed06db061e104f2b7815de80cdcb135ad", size = 138983, upload-time = "2025-12-06T15:55:10.595Z" }, - { url = "https://files.pythonhosted.org/packages/0e/87/de3223944a3e297d4707d2fe3b1ffb71437550e165eaf0ca8bbe43ccbcb1/orjson-3.11.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4be86b58e9ea262617b8ca6251a2f0d63cc132a6da4b5fcc8e0a4128782c829", size = 141069, upload-time = "2025-12-06T15:55:11.832Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/81d5087ae74be33bcae3ff2d80f5ccaa4a8fedc6d39bf65a427a95b8977f/orjson-3.11.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b923c1c13fa02084eb38c9c065afd860a5cff58026813319a06949c3af5732ac", size = 413491, upload-time = "2025-12-06T15:55:13.314Z" }, - { url = "https://files.pythonhosted.org/packages/d0/6f/f6058c21e2fc1efaf918986dbc2da5cd38044f1a2d4b7b91ad17c4acf786/orjson-3.11.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1b6bd351202b2cd987f35a13b5e16471cf4d952b42a73c391cc537974c43ef6d", size = 151375, upload-time = "2025-12-06T15:55:14.715Z" }, - { url = "https://files.pythonhosted.org/packages/54/92/c6921f17d45e110892899a7a563a925b2273d929959ce2ad89e2525b885b/orjson-3.11.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb150d529637d541e6af06bbe3d02f5498d628b7f98267ff87647584293ab439", size = 141850, upload-time = "2025-12-06T15:55:15.94Z" }, - { url = "https://files.pythonhosted.org/packages/88/86/cdecb0140a05e1a477b81f24739da93b25070ee01ce7f7242f44a6437594/orjson-3.11.5-cp314-cp314-win32.whl", hash = "sha256:9cc1e55c884921434a84a0c3dd2699eb9f92e7b441d7f53f3941079ec6ce7499", size = 135278, upload-time = "2025-12-06T15:55:17.202Z" }, - { url = "https://files.pythonhosted.org/packages/e4/97/b638d69b1e947d24f6109216997e38922d54dcdcdb1b11c18d7efd2d3c59/orjson-3.11.5-cp314-cp314-win_amd64.whl", hash = "sha256:a4f3cb2d874e03bc7767c8f88adaa1a9a05cecea3712649c3b58589ec7317310", size = 133170, upload-time = "2025-12-06T15:55:18.468Z" }, - { url = "https://files.pythonhosted.org/packages/8f/dd/f4fff4a6fe601b4f8f3ba3aa6da8ac33d17d124491a3b804c662a70e1636/orjson-3.11.5-cp314-cp314-win_arm64.whl", hash = "sha256:38b22f476c351f9a1c43e5b07d8b5a02eb24a6ab8e75f700f7d479d4568346a5", size = 126713, upload-time = "2025-12-06T15:55:19.738Z" }, - { url = "https://files.pythonhosted.org/packages/50/c7/7b682849dd4c9fb701a981669b964ea700516ecbd8e88f62aae07c6852bd/orjson-3.11.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1b280e2d2d284a6713b0cfec7b08918ebe57df23e3f76b27586197afca3cb1e9", size = 245298, upload-time = "2025-12-06T15:55:20.984Z" }, - { url = "https://files.pythonhosted.org/packages/1b/3f/194355a9335707a15fdc79ddc670148987b43d04712dd26898a694539ce6/orjson-3.11.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d8a112b274fae8c5f0f01954cb0480137072c271f3f4958127b010dfefaec", size = 132150, upload-time = "2025-12-06T15:55:22.364Z" }, - { url = "https://files.pythonhosted.org/packages/e9/08/d74b3a986d37e6c2e04b8821c62927620c9a1924bb49ea51519a87751b86/orjson-3.11.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0a2ae6f09ac7bd47d2d5a5305c1d9ed08ac057cda55bb0a49fa506f0d2da00", size = 130490, upload-time = "2025-12-06T15:55:23.619Z" }, - { url = "https://files.pythonhosted.org/packages/b2/16/ebd04c38c1db01e493a68eee442efdffc505a43112eccd481e0146c6acc2/orjson-3.11.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0d87bd1896faac0d10b4f849016db81a63e4ec5df38757ffae84d45ab38aa71", size = 135726, upload-time = "2025-12-06T15:55:24.912Z" }, - { url = "https://files.pythonhosted.org/packages/06/64/2ce4b2c09a099403081c37639c224bdcdfe401138bd66fed5c96d4f8dbd3/orjson-3.11.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:801a821e8e6099b8c459ac7540b3c32dba6013437c57fdcaec205b169754f38c", size = 139640, upload-time = "2025-12-06T15:55:26.535Z" }, - { url = "https://files.pythonhosted.org/packages/cd/e2/425796df8ee1d7cea3a7edf868920121dd09162859dbb76fffc9a5c37fd3/orjson-3.11.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a0f6ac618c98c74b7fbc8c0172ba86f9e01dbf9f62aa0b1776c2231a7bffe5", size = 137289, upload-time = "2025-12-06T15:55:27.78Z" }, - { url = "https://files.pythonhosted.org/packages/32/a2/88e482eb8e899a037dcc9eff85ef117a568e6ca1ffa1a2b2be3fcb51b7bb/orjson-3.11.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea7339bdd22e6f1060c55ac31b6a755d86a5b2ad3657f2669ec243f8e3b2bdb", size = 138761, upload-time = "2025-12-06T15:55:29.388Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fd/131dd6d32eeb74c513bfa487f434a2150811d0fbd9cb06689284f2f21b34/orjson-3.11.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4dad582bc93cef8f26513e12771e76385a7e6187fd713157e971c784112aad56", size = 141357, upload-time = "2025-12-06T15:55:31.064Z" }, - { url = "https://files.pythonhosted.org/packages/7a/90/e4a0abbcca7b53e9098ac854f27f5ed9949c796f3c760bc04af997da0eb2/orjson-3.11.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:0522003e9f7fba91982e83a97fec0708f5a714c96c4209db7104e6b9d132f111", size = 413638, upload-time = "2025-12-06T15:55:32.344Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c2/df91e385514924120001ade9cd52d6295251023d3bfa2c0a01f38cfc485a/orjson-3.11.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7403851e430a478440ecc1258bcbacbfbd8175f9ac1e39031a7121dd0de05ff8", size = 150972, upload-time = "2025-12-06T15:55:33.725Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ff/c76cc5a30a4451191ff1b868a331ad1354433335277fc40931f5fc3cab9d/orjson-3.11.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5f691263425d3177977c8d1dd896cde7b98d93cbf390b2544a090675e83a6a0a", size = 141729, upload-time = "2025-12-06T15:55:35.317Z" }, - { url = "https://files.pythonhosted.org/packages/27/c3/7830bf74389ea1eaab2b017d8b15d1cab2bb0737d9412dfa7fb8644f7d78/orjson-3.11.5-cp39-cp39-win32.whl", hash = "sha256:61026196a1c4b968e1b1e540563e277843082e9e97d78afa03eb89315af531f1", size = 135100, upload-time = "2025-12-06T15:55:36.57Z" }, - { url = "https://files.pythonhosted.org/packages/69/e6/babf31154e047e465bc194eb72d1326d7c52ad4d7f50bf92b02b3cacda5c/orjson-3.11.5-cp39-cp39-win_amd64.whl", hash = "sha256:09b94b947ac08586af635ef922d69dc9bc63321527a3a04647f4986a73f4bd30", size = 133189, upload-time = "2025-12-06T15:55:38.143Z" }, -] - [[package]] name = "orjson" version = "3.11.7" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/de/1a/a373746fa6d0e116dd9e54371a7b54622c44d12296d5d0f3ad5e3ff33490/orjson-3.11.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a02c833f38f36546ba65a452127633afce4cf0dd7296b753d3bb54e55e5c0174", size = 229140, upload-time = "2026-02-02T15:37:06.082Z" }, @@ -4369,130 +3509,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, ] -[[package]] -name = "pillow" -version = "11.3.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" }, - { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" }, - { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" }, - { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" }, - { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" }, - { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" }, - { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" }, - { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" }, - { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" }, - { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" }, - { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, - { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, - { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, - { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, - { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, - { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, - { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, - { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, - { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, - { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, - { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, - { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, - { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, - { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, - { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, - { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, - { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, - { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, - { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, - { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, - { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, - { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, - { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, - { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, - { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, - { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, - { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, - { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, - { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, - { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, - { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, - { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, - { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, - { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, - { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, - { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, - { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, - { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, - { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, - { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, - { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, - { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, - { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, - { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, - { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, - { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8e/9c089f01677d1264ab8648352dcb7773f37da6ad002542760c80107da816/pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f", size = 5316478, upload-time = "2025-07-01T09:15:52.209Z" }, - { url = "https://files.pythonhosted.org/packages/b5/a9/5749930caf674695867eb56a581e78eb5f524b7583ff10b01b6e5048acb3/pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081", size = 4686522, upload-time = "2025-07-01T09:15:54.162Z" }, - { url = "https://files.pythonhosted.org/packages/43/46/0b85b763eb292b691030795f9f6bb6fcaf8948c39413c81696a01c3577f7/pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4", size = 5853376, upload-time = "2025-07-03T13:11:01.066Z" }, - { url = "https://files.pythonhosted.org/packages/5e/c6/1a230ec0067243cbd60bc2dad5dc3ab46a8a41e21c15f5c9b52b26873069/pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc", size = 7626020, upload-time = "2025-07-03T13:11:06.479Z" }, - { url = "https://files.pythonhosted.org/packages/63/dd/f296c27ffba447bfad76c6a0c44c1ea97a90cb9472b9304c94a732e8dbfb/pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06", size = 5956732, upload-time = "2025-07-01T09:15:56.111Z" }, - { url = "https://files.pythonhosted.org/packages/a5/a0/98a3630f0b57f77bae67716562513d3032ae70414fcaf02750279c389a9e/pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a", size = 6624404, upload-time = "2025-07-01T09:15:58.245Z" }, - { url = "https://files.pythonhosted.org/packages/de/e6/83dfba5646a290edd9a21964da07674409e410579c341fc5b8f7abd81620/pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978", size = 6067760, upload-time = "2025-07-01T09:16:00.003Z" }, - { url = "https://files.pythonhosted.org/packages/bc/41/15ab268fe6ee9a2bc7391e2bbb20a98d3974304ab1a406a992dcb297a370/pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d", size = 6700534, upload-time = "2025-07-01T09:16:02.29Z" }, - { url = "https://files.pythonhosted.org/packages/64/79/6d4f638b288300bed727ff29f2a3cb63db054b33518a95f27724915e3fbc/pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71", size = 6277091, upload-time = "2025-07-01T09:16:04.4Z" }, - { url = "https://files.pythonhosted.org/packages/46/05/4106422f45a05716fd34ed21763f8ec182e8ea00af6e9cb05b93a247361a/pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada", size = 6986091, upload-time = "2025-07-01T09:16:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/63/c6/287fd55c2c12761d0591549d48885187579b7c257bef0c6660755b0b59ae/pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb", size = 2422632, upload-time = "2025-07-01T09:16:08.142Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" }, - { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" }, - { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" }, - { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" }, - { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" }, - { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, - { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, - { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, - { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, -] - [[package]] name = "pillow" version = "12.1.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" }, @@ -4587,26 +3607,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, ] -[[package]] -name = "platformdirs" -version = "4.4.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, -] - [[package]] name = "platformdirs" version = "4.5.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, @@ -4617,8 +3621,7 @@ name = "playwright" version = "1.58.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "greenlet", version = "3.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "greenlet", version = "3.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "greenlet" }, { name = "pyee" }, ] wheels = [ @@ -4797,51 +3800,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, - { url = "https://files.pythonhosted.org/packages/9b/01/0ebaec9003f5d619a7475165961f8e3083cf8644d704b60395df3601632d/propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff", size = 80277, upload-time = "2025-10-08T19:48:36.647Z" }, - { url = "https://files.pythonhosted.org/packages/34/58/04af97ac586b4ef6b9026c3fd36ee7798b737a832f5d3440a4280dcebd3a/propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb", size = 45865, upload-time = "2025-10-08T19:48:37.859Z" }, - { url = "https://files.pythonhosted.org/packages/7c/19/b65d98ae21384518b291d9939e24a8aeac4fdb5101b732576f8f7540e834/propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac", size = 47636, upload-time = "2025-10-08T19:48:39.038Z" }, - { url = "https://files.pythonhosted.org/packages/b3/0f/317048c6d91c356c7154dca5af019e6effeb7ee15fa6a6db327cc19e12b4/propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888", size = 201126, upload-time = "2025-10-08T19:48:40.774Z" }, - { url = "https://files.pythonhosted.org/packages/71/69/0b2a7a5a6ee83292b4b997dbd80549d8ce7d40b6397c1646c0d9495f5a85/propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc", size = 209837, upload-time = "2025-10-08T19:48:42.167Z" }, - { url = "https://files.pythonhosted.org/packages/a5/92/c699ac495a6698df6e497fc2de27af4b6ace10d8e76528357ce153722e45/propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a", size = 215578, upload-time = "2025-10-08T19:48:43.56Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ee/14de81c5eb02c0ee4f500b4e39c4e1bd0677c06e72379e6ab18923c773fc/propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88", size = 197187, upload-time = "2025-10-08T19:48:45.309Z" }, - { url = "https://files.pythonhosted.org/packages/1d/94/48dce9aaa6d8dd5a0859bad75158ec522546d4ac23f8e2f05fac469477dd/propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00", size = 193478, upload-time = "2025-10-08T19:48:47.743Z" }, - { url = "https://files.pythonhosted.org/packages/60/b5/0516b563e801e1ace212afde869a0596a0d7115eec0b12d296d75633fb29/propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0", size = 190650, upload-time = "2025-10-08T19:48:49.373Z" }, - { url = "https://files.pythonhosted.org/packages/24/89/e0f7d4a5978cd56f8cd67735f74052f257dc471ec901694e430f0d1572fe/propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e", size = 200251, upload-time = "2025-10-08T19:48:51.4Z" }, - { url = "https://files.pythonhosted.org/packages/06/7d/a1fac863d473876ed4406c914f2e14aa82d2f10dd207c9e16fc383cc5a24/propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781", size = 200919, upload-time = "2025-10-08T19:48:53.227Z" }, - { url = "https://files.pythonhosted.org/packages/c3/4e/f86a256ff24944cf5743e4e6c6994e3526f6acfcfb55e21694c2424f758c/propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183", size = 193211, upload-time = "2025-10-08T19:48:55.027Z" }, - { url = "https://files.pythonhosted.org/packages/6e/3f/3fbad5f4356b068f1b047d300a6ff2c66614d7030f078cd50be3fec04228/propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19", size = 38314, upload-time = "2025-10-08T19:48:56.792Z" }, - { url = "https://files.pythonhosted.org/packages/a4/45/d78d136c3a3d215677abb886785aae744da2c3005bcb99e58640c56529b1/propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f", size = 41912, upload-time = "2025-10-08T19:48:57.995Z" }, - { url = "https://files.pythonhosted.org/packages/fc/2a/b0632941f25139f4e58450b307242951f7c2717a5704977c6d5323a800af/propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938", size = 38450, upload-time = "2025-10-08T19:48:59.349Z" }, { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] -[[package]] -name = "protobuf" -version = "5.29.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/7e/57/394a763c103e0edf87f0938dafcd918d53b4c011dfc5c8ae80f3b0452dbb/protobuf-5.29.6.tar.gz", hash = "sha256:da9ee6a5424b6b30fd5e45c5ea663aef540ca95f9ad99d1e887e819cdf9b8723", size = 425623, upload-time = "2026-02-04T22:54:40.584Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/88/9ee58ff7863c479d6f8346686d4636dd4c415b0cbeed7a6a7d0617639c2a/protobuf-5.29.6-cp310-abi3-win32.whl", hash = "sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1", size = 423357, upload-time = "2026-02-04T22:54:25.805Z" }, - { url = "https://files.pythonhosted.org/packages/1c/66/2dc736a4d576847134fb6d80bd995c569b13cdc7b815d669050bf0ce2d2c/protobuf-5.29.6-cp310-abi3-win_amd64.whl", hash = "sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda", size = 435175, upload-time = "2026-02-04T22:54:28.592Z" }, - { url = "https://files.pythonhosted.org/packages/06/db/49b05966fd208ae3f44dcd33837b6243b4915c57561d730a43f881f24dea/protobuf-5.29.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269", size = 418619, upload-time = "2026-02-04T22:54:30.266Z" }, - { url = "https://files.pythonhosted.org/packages/b7/d7/48cbf6b0c3c39761e47a99cb483405f0fde2be22cf00d71ef316ce52b458/protobuf-5.29.6-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6", size = 320284, upload-time = "2026-02-04T22:54:31.782Z" }, - { url = "https://files.pythonhosted.org/packages/e3/dd/cadd6ec43069247d91f6345fa7a0d2858bef6af366dbd7ba8f05d2c77d3b/protobuf-5.29.6-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9", size = 320478, upload-time = "2026-02-04T22:54:32.909Z" }, - { url = "https://files.pythonhosted.org/packages/30/a4/ff263f5687815e1a10a9243a3a6463af42ca251224bf4b8fc4c93b9f5b80/protobuf-5.29.6-cp39-cp39-win32.whl", hash = "sha256:cb4c86de9cd8a7f3a256b9744220d87b847371c6b2f10bde87768918ef33ba49", size = 423352, upload-time = "2026-02-04T22:54:37.375Z" }, - { url = "https://files.pythonhosted.org/packages/5c/64/e943206d3b5069050d570a2c53a90631240d99adcc9a91c6ff7b41876f4d/protobuf-5.29.6-cp39-cp39-win_amd64.whl", hash = "sha256:76e07e6567f8baf827137e8d5b8204b6c7b6488bbbff1bf0a72b383f77999c18", size = 435222, upload-time = "2026-02-04T22:54:38.418Z" }, - { url = "https://files.pythonhosted.org/packages/5a/cb/e3065b447186cb70aa65acc70c86baf482d82bf75625bf5a2c4f6919c6a3/protobuf-5.29.6-py3-none-any.whl", hash = "sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86", size = 173126, upload-time = "2026-02-04T22:54:39.462Z" }, -] - [[package]] name = "protobuf" version = "6.33.5" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, @@ -4850,36 +3815,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/08/60/84d5f6dcda9165e4d6a56ac8433c9f40a8906bf2966150b8a0cfde097d78/protobuf-6.33.5-cp39-cp39-win32.whl", hash = "sha256:a3157e62729aafb8df6da2c03aa5c0937c7266c626ce11a278b6eb7963c4e37c", size = 425892, upload-time = "2026-01-29T21:51:30.382Z" }, - { url = "https://files.pythonhosted.org/packages/68/19/33d7dc2dc84439587fa1e21e1c0026c01ad2af0a62f58fd54002a7546307/protobuf-6.33.5-cp39-cp39-win_amd64.whl", hash = "sha256:8f04fa32763dcdb4973d537d6b54e615cc61108c7cb38fe59310c3192d29510a", size = 437137, upload-time = "2026-01-29T21:51:31.456Z" }, { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, ] -[[package]] -name = "pwdlib" -version = "0.2.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/82/a0/9daed437a6226f632a25d98d65d60ba02bdafa920c90dcb6454c611ead6c/pwdlib-0.2.1.tar.gz", hash = "sha256:9a1d8a8fa09a2f7ebf208265e55d7d008103cbdc82b9e4902ffdd1ade91add5e", size = 11699, upload-time = "2024-08-19T06:48:59.58Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/f3/0dae5078a486f0fdf4d4a1121e103bc42694a9da9bea7b0f2c63f29cfbd3/pwdlib-0.2.1-py3-none-any.whl", hash = "sha256:1823dc6f22eae472b540e889ecf57fd424051d6a4023ec0bcf7f0de2d9d7ef8c", size = 8082, upload-time = "2024-08-19T06:49:00.997Z" }, -] - -[package.optional-dependencies] -argon2 = [ - { name = "argon2-cffi", version = "23.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, -] - [[package]] name = "pwdlib" version = "0.3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/5f/41/a7c0d8a003c36ce3828ae3ed0391fe6a15aad65f082dbd6bec817ea95c0b/pwdlib-0.3.0.tar.gz", hash = "sha256:6ca30f9642a1467d4f5d0a4d18619de1c77f17dfccb42dd200b144127d3c83fc", size = 215810, upload-time = "2025-10-25T12:44:24.395Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/62/0c/9086a357d02a050fbb3270bf5043ac284dbfb845670e16c9389a41defc9e/pwdlib-0.3.0-py3-none-any.whl", hash = "sha256:f86c15c138858c09f3bba0a10984d4f9178158c55deaa72eac0210849b1a140d", size = 8633, upload-time = "2025-10-25T12:44:23.406Z" }, @@ -4887,7 +3829,7 @@ wheels = [ [package.optional-dependencies] argon2 = [ - { name = "argon2-cffi", version = "25.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "argon2-cffi" }, ] [[package]] @@ -4895,8 +3837,8 @@ name = "py-key-value-aio" version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "beartype", marker = "python_full_version >= '3.10'" }, - { name = "py-key-value-shared", marker = "python_full_version >= '3.10'" }, + { name = "beartype" }, + { name = "py-key-value-shared" }, ] sdist = { url = "https://files.pythonhosted.org/packages/93/ce/3136b771dddf5ac905cc193b461eb67967cf3979688c6696e1f2cdcde7ea/py_key_value_aio-0.3.0.tar.gz", hash = "sha256:858e852fcf6d696d231266da66042d3355a7f9871650415feef9fca7a6cd4155", size = 50801, upload-time = "2025-11-17T16:50:04.711Z" } wheels = [ @@ -4905,17 +3847,17 @@ wheels = [ [package.optional-dependencies] disk = [ - { name = "diskcache", marker = "python_full_version >= '3.10'" }, - { name = "pathvalidate", marker = "python_full_version >= '3.10'" }, + { name = "diskcache" }, + { name = "pathvalidate" }, ] keyring = [ - { name = "keyring", marker = "python_full_version >= '3.10'" }, + { name = "keyring" }, ] memory = [ - { name = "cachetools", marker = "python_full_version >= '3.10'" }, + { name = "cachetools" }, ] redis = [ - { name = "redis", marker = "python_full_version >= '3.10'" }, + { name = "redis" }, ] [[package]] @@ -4923,8 +3865,8 @@ name = "py-key-value-shared" version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "beartype", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "beartype" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7b/e4/1971dfc4620a3a15b4579fe99e024f5edd6e0967a71154771a059daff4db/py_key_value_shared-0.3.0.tar.gz", hash = "sha256:8fdd786cf96c3e900102945f92aa1473138ebe960ef49da1c833790160c28a4b", size = 11666, upload-time = "2025-11-17T16:50:06.849Z" } wheels = [ @@ -4952,26 +3894,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, ] -[[package]] -name = "pycparser" -version = "2.23" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, -] - [[package]] name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, @@ -4997,126 +3923,31 @@ email = [ { name = "email-validator" }, ] -[[package]] -name = "pydantic-ai" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "pydantic-ai-slim", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "mistral", "openai", "retries", "temporal", "vertexai"], marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/d7/fcc18ce80008e888404a3615f973aa3f39b98384d61b03621144c9f4c2d4/pydantic_ai-0.8.1.tar.gz", hash = "sha256:05974382082ee4f3706909d06bdfcc5e95f39e29230cc4d00e47429080099844", size = 43772581, upload-time = "2025-08-29T14:46:23.201Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/04/802b8cf834dffcda8baabb3b76c549243694a83346c3f54e47a3a4d519fb/pydantic_ai-0.8.1-py3-none-any.whl", hash = "sha256:5fa923097132aa69b4d6a310b462dc091009c7b87705edf4443d37b887d5ef9a", size = 10188, upload-time = "2025-08-29T14:46:11.137Z" }, -] - [[package]] name = "pydantic-ai" version = "1.56.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "pydantic-ai-slim", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "fastmcp", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "temporal", "ui", "vertexai", "xai"], marker = "python_full_version >= '3.10'" }, + { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "fastmcp", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "temporal", "ui", "vertexai", "xai"] }, ] sdist = { url = "https://files.pythonhosted.org/packages/60/1a/800a1e02b259152a49d4c11d9103784a7482c7e9b067eeea23e949d3d80f/pydantic_ai-1.56.0.tar.gz", hash = "sha256:643ff71612df52315b3b4c4b41543657f603f567223eb33245dc8098f005bdc4", size = 11795, upload-time = "2026-02-06T01:13:21.122Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5c/35/f4a7fd2b9962ddb9b021f76f293e74fda71da190bb74b57ed5b343c93022/pydantic_ai-1.56.0-py3-none-any.whl", hash = "sha256:b6b3ac74bdc004693834750da4420ea2cde0d3cbc3f134c0b7544f98f1c00859", size = 7222, upload-time = "2026-02-06T01:13:11.755Z" }, ] -[[package]] -name = "pydantic-ai-slim" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, - { name = "genai-prices", marker = "python_full_version < '3.10'" }, - { name = "griffe", version = "1.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "httpx", marker = "python_full_version < '3.10'" }, - { name = "opentelemetry-api", marker = "python_full_version < '3.10'" }, - { name = "pydantic", marker = "python_full_version < '3.10'" }, - { name = "pydantic-graph", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "typing-inspection", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a2/91/08137459b3745900501b3bd11852ced6c81b7ce6e628696d75b09bb786c5/pydantic_ai_slim-0.8.1.tar.gz", hash = "sha256:12ef3dcbe5e1dad195d5e256746ef960f6e59aeddda1a55bdd553ee375ff53ae", size = 218906, upload-time = "2025-08-29T14:46:27.517Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/ce/8dbadd04f578d02a9825a46e931005743fe223736296f30b55846c084fab/pydantic_ai_slim-0.8.1-py3-none-any.whl", hash = "sha256:fc7edc141b21fe42bc54a2d92c1127f8a75160c5e57a168dba154d3f4adb963f", size = 297821, upload-time = "2025-08-29T14:46:14.647Z" }, -] - -[package.optional-dependencies] -ag-ui = [ - { name = "ag-ui-protocol", marker = "python_full_version < '3.10'" }, - { name = "starlette", version = "0.49.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, -] -anthropic = [ - { name = "anthropic", marker = "python_full_version < '3.10'" }, -] -bedrock = [ - { name = "boto3", marker = "python_full_version < '3.10'" }, -] -cli = [ - { name = "argcomplete", marker = "python_full_version < '3.10'" }, - { name = "prompt-toolkit", marker = "python_full_version < '3.10'" }, - { name = "pyperclip", marker = "python_full_version < '3.10'" }, - { name = "rich", marker = "python_full_version < '3.10'" }, -] -cohere = [ - { name = "cohere", marker = "python_full_version < '3.10' and sys_platform != 'emscripten'" }, -] -evals = [ - { name = "pydantic-evals", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, -] -google = [ - { name = "google-genai", version = "1.47.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, -] -groq = [ - { name = "groq", marker = "python_full_version < '3.10'" }, -] -huggingface = [ - { name = "huggingface-hub", extra = ["inference"], marker = "python_full_version < '3.10'" }, -] -mistral = [ - { name = "mistralai", marker = "python_full_version < '3.10'" }, -] -openai = [ - { name = "openai", marker = "python_full_version < '3.10'" }, -] -retries = [ - { name = "tenacity", version = "9.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, -] -temporal = [ - { name = "temporalio", version = "1.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, -] -vertexai = [ - { name = "google-auth", marker = "python_full_version < '3.10'" }, - { name = "requests", marker = "python_full_version < '3.10'" }, -] - [[package]] name = "pydantic-ai-slim" version = "1.56.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "genai-prices", marker = "python_full_version >= '3.10'" }, - { name = "griffe", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "httpx", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, - { name = "pydantic", marker = "python_full_version >= '3.10'" }, - { name = "pydantic-graph", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "typing-inspection", marker = "python_full_version >= '3.10'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "genai-prices" }, + { name = "griffe" }, + { name = "httpx" }, + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "pydantic-graph" }, + { name = "typing-inspection" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ce/5c/3a577825b9c1da8f287be7f2ee6fe9aab48bc8a80e65c8518052c589f51c/pydantic_ai_slim-1.56.0.tar.gz", hash = "sha256:9f9f9c56b1c735837880a515ae5661b465b40207b25f3a3434178098b2137f05", size = 415265, upload-time = "2026-02-06T01:13:23.58Z" } wheels = [ @@ -5125,67 +3956,67 @@ wheels = [ [package.optional-dependencies] ag-ui = [ - { name = "ag-ui-protocol", marker = "python_full_version >= '3.10'" }, - { name = "starlette", version = "0.52.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ag-ui-protocol" }, + { name = "starlette" }, ] anthropic = [ - { name = "anthropic", marker = "python_full_version >= '3.10'" }, + { name = "anthropic" }, ] bedrock = [ - { name = "boto3", marker = "python_full_version >= '3.10'" }, + { name = "boto3" }, ] cli = [ - { name = "argcomplete", marker = "python_full_version >= '3.10'" }, - { name = "prompt-toolkit", marker = "python_full_version >= '3.10'" }, - { name = "pyperclip", marker = "python_full_version >= '3.10'" }, - { name = "rich", marker = "python_full_version >= '3.10'" }, + { name = "argcomplete" }, + { name = "prompt-toolkit" }, + { name = "pyperclip" }, + { name = "rich" }, ] cohere = [ - { name = "cohere", marker = "python_full_version >= '3.10' and sys_platform != 'emscripten'" }, + { name = "cohere", marker = "sys_platform != 'emscripten'" }, ] evals = [ - { name = "pydantic-evals", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pydantic-evals" }, ] fastmcp = [ - { name = "fastmcp", marker = "python_full_version >= '3.10'" }, + { name = "fastmcp" }, ] google = [ - { name = "google-genai", version = "1.62.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "google-genai" }, ] groq = [ - { name = "groq", marker = "python_full_version >= '3.10'" }, + { name = "groq" }, ] huggingface = [ - { name = "huggingface-hub", extra = ["inference"], marker = "python_full_version >= '3.10'" }, + { name = "huggingface-hub", extra = ["inference"] }, ] logfire = [ - { name = "logfire", extra = ["httpx"], marker = "python_full_version >= '3.10'" }, + { name = "logfire", extra = ["httpx"] }, ] mcp = [ - { name = "mcp", marker = "python_full_version >= '3.10'" }, + { name = "mcp" }, ] mistral = [ - { name = "mistralai", marker = "python_full_version >= '3.10'" }, + { name = "mistralai" }, ] openai = [ - { name = "openai", marker = "python_full_version >= '3.10'" }, - { name = "tiktoken", marker = "python_full_version >= '3.10'" }, + { name = "openai" }, + { name = "tiktoken" }, ] retries = [ - { name = "tenacity", version = "9.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tenacity" }, ] temporal = [ - { name = "temporalio", version = "1.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "temporalio" }, ] ui = [ - { name = "starlette", version = "0.52.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "starlette" }, ] vertexai = [ - { name = "google-auth", marker = "python_full_version >= '3.10'" }, - { name = "requests", marker = "python_full_version >= '3.10'" }, + { name = "google-auth" }, + { name = "requests" }, ] xai = [ - { name = "xai-sdk", marker = "python_full_version >= '3.10'" }, + { name = "xai-sdk" }, ] [[package]] @@ -5280,19 +4111,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/54/db/160dffb57ed9a3705c4cbcbff0ac03bdae45f1ca7d58ab74645550df3fbd/pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf", size = 2107999, upload-time = "2025-11-04T13:42:03.885Z" }, - { url = "https://files.pythonhosted.org/packages/a3/7d/88e7de946f60d9263cc84819f32513520b85c0f8322f9b8f6e4afc938383/pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5", size = 1929745, upload-time = "2025-11-04T13:42:06.075Z" }, - { url = "https://files.pythonhosted.org/packages/d5/c2/aef51e5b283780e85e99ff19db0f05842d2d4a8a8cd15e63b0280029b08f/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d", size = 1920220, upload-time = "2025-11-04T13:42:08.457Z" }, - { url = "https://files.pythonhosted.org/packages/c7/97/492ab10f9ac8695cd76b2fdb24e9e61f394051df71594e9bcc891c9f586e/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60", size = 2067296, upload-time = "2025-11-04T13:42:10.817Z" }, - { url = "https://files.pythonhosted.org/packages/ec/23/984149650e5269c59a2a4c41d234a9570adc68ab29981825cfaf4cfad8f4/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82", size = 2231548, upload-time = "2025-11-04T13:42:13.843Z" }, - { url = "https://files.pythonhosted.org/packages/71/0c/85bcbb885b9732c28bec67a222dbed5ed2d77baee1f8bba2002e8cd00c5c/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5", size = 2362571, upload-time = "2025-11-04T13:42:16.208Z" }, - { url = "https://files.pythonhosted.org/packages/c0/4a/412d2048be12c334003e9b823a3fa3d038e46cc2d64dd8aab50b31b65499/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3", size = 2068175, upload-time = "2025-11-04T13:42:18.911Z" }, - { url = "https://files.pythonhosted.org/packages/73/f4/c58b6a776b502d0a5540ad02e232514285513572060f0d78f7832ca3c98b/pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425", size = 2177203, upload-time = "2025-11-04T13:42:22.578Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ae/f06ea4c7e7a9eead3d165e7623cd2ea0cb788e277e4f935af63fc98fa4e6/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504", size = 2148191, upload-time = "2025-11-04T13:42:24.89Z" }, - { url = "https://files.pythonhosted.org/packages/c1/57/25a11dcdc656bf5f8b05902c3c2934ac3ea296257cc4a3f79a6319e61856/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5", size = 2343907, upload-time = "2025-11-04T13:42:27.683Z" }, - { url = "https://files.pythonhosted.org/packages/96/82/e33d5f4933d7a03327c0c43c65d575e5919d4974ffc026bc917a5f7b9f61/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3", size = 2322174, upload-time = "2025-11-04T13:42:30.776Z" }, - { url = "https://files.pythonhosted.org/packages/81/45/4091be67ce9f469e81656f880f3506f6a5624121ec5eb3eab37d7581897d/pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460", size = 1990353, upload-time = "2025-11-04T13:42:33.111Z" }, - { url = "https://files.pythonhosted.org/packages/44/8a/a98aede18db6e9cd5d66bcacd8a409fcf8134204cdede2e7de35c5a2c5ef/pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b", size = 2015698, upload-time = "2025-11-04T13:42:35.484Z" }, { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, @@ -5319,42 +4137,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] -[[package]] -name = "pydantic-evals" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "anyio", marker = "python_full_version < '3.10'" }, - { name = "eval-type-backport", marker = "python_full_version < '3.10'" }, - { name = "logfire-api", marker = "python_full_version < '3.10'" }, - { name = "pydantic", marker = "python_full_version < '3.10'" }, - { name = "pydantic-ai-slim", version = "0.8.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pyyaml", marker = "python_full_version < '3.10'" }, - { name = "rich", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6c/9d/460a1f2c9f5f263e9d8e9661acbd654ccc81ad3373ea43048d914091a817/pydantic_evals-0.8.1.tar.gz", hash = "sha256:c398a623c31c19ce70e346ad75654fcb1517c3f6a821461f64fe5cbbe0813023", size = 43933, upload-time = "2025-08-29T14:46:28.903Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/f9/1d21c4687167c4fa76fd3b1ed47f9bc2d38fd94cbacd9aa3f19e82e59830/pydantic_evals-0.8.1-py3-none-any.whl", hash = "sha256:6c76333b1d79632f619eb58a24ac656e9f402c47c75ad750ba0230d7f5514344", size = 52602, upload-time = "2025-08-29T14:46:16.602Z" }, -] - [[package]] name = "pydantic-evals" version = "1.56.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "anyio", marker = "python_full_version >= '3.10'" }, - { name = "logfire-api", marker = "python_full_version >= '3.10'" }, - { name = "pydantic", marker = "python_full_version >= '3.10'" }, - { name = "pydantic-ai-slim", version = "1.56.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, - { name = "rich", marker = "python_full_version >= '3.10'" }, + { name = "anyio" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "pydantic-ai-slim" }, + { name = "pyyaml" }, + { name = "rich" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/f2/8c59284a2978af3fbda45ae3217218eaf8b071207a9290b54b7613983e5d/pydantic_evals-1.56.0.tar.gz", hash = "sha256:206635107127af6a3ee4b1fc8f77af6afb14683615a2d6b3609f79467c1c0d28", size = 47210, upload-time = "2026-02-06T01:13:25.714Z" } wheels = [ @@ -5374,72 +4167,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/17/fabd56da47096d240dd45ba627bead0333b0cf0ee8ada9bec579287dadf3/pydantic_extra_types-2.11.0-py3-none-any.whl", hash = "sha256:84b864d250a0fc62535b7ec591e36f2c5b4d1325fa0017eb8cda9aeb63b374a6", size = 74296, upload-time = "2025-12-31T16:18:26.38Z" }, ] -[[package]] -name = "pydantic-graph" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "httpx", marker = "python_full_version < '3.10'" }, - { name = "logfire-api", marker = "python_full_version < '3.10'" }, - { name = "pydantic", marker = "python_full_version < '3.10'" }, - { name = "typing-inspection", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/97/b35b7cb82d9f1bb6d5c6d21bba54f6196a3a5f593373f3a9c163a3821fd7/pydantic_graph-0.8.1.tar.gz", hash = "sha256:c61675a05c74f661d4ff38d04b74bd652c1e0959467801986f2f85dc7585410d", size = 21675, upload-time = "2025-08-29T14:46:29.839Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/e3/5908643b049bb2384d143885725cbeb0f53707d418357d4d1ac8d2c82629/pydantic_graph-0.8.1-py3-none-any.whl", hash = "sha256:f1dd5db0fe22f4e3323c04c65e2f0013846decc312b3efc3196666764556b765", size = 27239, upload-time = "2025-08-29T14:46:18.317Z" }, -] - [[package]] name = "pydantic-graph" version = "1.56.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "httpx", marker = "python_full_version >= '3.10'" }, - { name = "logfire-api", marker = "python_full_version >= '3.10'" }, - { name = "pydantic", marker = "python_full_version >= '3.10'" }, - { name = "typing-inspection", marker = "python_full_version >= '3.10'" }, + { name = "httpx" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "typing-inspection" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ff/03/f92881cdb12d6f43e60e9bfd602e41c95408f06e2324d3729f7a194e2bcd/pydantic_graph-1.56.0.tar.gz", hash = "sha256:5e22972dbb43dbc379ab9944252ff864019abf3c7d465dcdf572fc8aec9a44a1", size = 58460, upload-time = "2026-02-06T01:13:26.708Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/08/07/8c823eb4d196137c123d4d67434e185901d3cbaea3b0c2b7667da84e72c1/pydantic_graph-1.56.0-py3-none-any.whl", hash = "sha256:ec3f0a1d6fcedd4eb9c59fef45079c2ee4d4185878d70dae26440a9c974c6bb3", size = 72346, upload-time = "2026-02-06T01:13:18.792Z" }, ] -[[package]] -name = "pydantic-settings" -version = "2.11.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "pydantic", marker = "python_full_version < '3.10'" }, - { name = "python-dotenv", marker = "python_full_version < '3.10'" }, - { name = "typing-inspection", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394, upload-time = "2025-09-24T14:19:11.764Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" }, -] - [[package]] name = "pydantic-settings" version = "2.12.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "pydantic", marker = "python_full_version >= '3.10'" }, - { name = "python-dotenv", marker = "python_full_version >= '3.10'" }, - { name = "typing-inspection", marker = "python_full_version >= '3.10'" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } wheels = [ @@ -5451,19 +4201,19 @@ name = "pydocket" version = "0.17.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle", marker = "python_full_version >= '3.10'" }, - { name = "croniter", marker = "python_full_version >= '3.10'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "fakeredis", extra = ["lua"], marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-api", marker = "python_full_version >= '3.10'" }, - { name = "prometheus-client", marker = "python_full_version >= '3.10'" }, - { name = "py-key-value-aio", extra = ["memory", "redis"], marker = "python_full_version >= '3.10'" }, - { name = "python-json-logger", marker = "python_full_version >= '3.10'" }, - { name = "redis", marker = "python_full_version >= '3.10'" }, - { name = "rich", marker = "python_full_version >= '3.10'" }, - { name = "taskgroup", marker = "python_full_version == '3.10.*'" }, - { name = "typer", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "cloudpickle" }, + { name = "croniter" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "fakeredis", extra = ["lua"] }, + { name = "opentelemetry-api" }, + { name = "prometheus-client" }, + { name = "py-key-value-aio", extra = ["memory", "redis"] }, + { name = "python-json-logger" }, + { name = "redis" }, + { name = "rich" }, + { name = "taskgroup", marker = "python_full_version < '3.11'" }, + { name = "typer" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/73/26/ac23ead3725475468b50b486939bf5feda27180050a614a7407344a0af0e/pydocket-0.17.5.tar.gz", hash = "sha256:19a6976d8fd11c1acf62feb0291a339e06beaefa100f73dd38c6499760ad3e62", size = 334829, upload-time = "2026-01-30T18:44:39.702Z" } wheels = [ @@ -5491,8 +4241,7 @@ dependencies = [ { name = "pynacl" }, { name = "requests" }, { name = "typing-extensions" }, - { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c1/74/e560bdeffea72ecb26cff27f0fad548bbff5ecc51d6a155311ea7f9e4c4c/pygithub-2.8.1.tar.gz", hash = "sha256:341b7c78521cb07324ff670afd1baa2bf5c286f8d9fd302c1798ba594a5400c9", size = 2246994, upload-time = "2025-09-02T17:41:54.674Z" } wheels = [ @@ -5527,8 +4276,7 @@ name = "pymdown-extensions" version = "10.20.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown", version = "3.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown" }, { name = "pyyaml" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1e/6c/9e370934bfa30e889d12e61d0dae009991294f40055c238980066a7fbd83/pymdown_extensions-10.20.1.tar.gz", hash = "sha256:e7e39c865727338d434b55f1dd8da51febcffcaebd6e1a0b9c836243f660740a", size = 852860, upload-time = "2026-01-24T05:56:56.758Z" } @@ -5587,8 +4335,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, @@ -5605,7 +4352,6 @@ version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "pytest" }, { name = "rich" }, ] @@ -5623,8 +4369,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/14/8d9340d7dc0ae647991b28a396e16b3403e10def883cde90d6b663d3f7ec/pytest_codspeed-4.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0ebd87f2a99467a1cfd8e83492c4712976e43d353ee0b5f71cbb057f1393aca", size = 251057, upload-time = "2025-10-24T09:02:49.102Z" }, { url = "https://files.pythonhosted.org/packages/4b/39/48cf6afbca55bc7c8c93c3d4ae926a1068bcce3f0241709db19b078d5418/pytest_codspeed-4.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbbb2d61b85bef8fc7e2193f723f9ac2db388a48259d981bbce96319043e9830", size = 267983, upload-time = "2025-10-24T09:02:50.558Z" }, { url = "https://files.pythonhosted.org/packages/33/86/4407341efb5dceb3e389635749ce1d670542d6ca148bd34f9d5334295faf/pytest_codspeed-4.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:748411c832147bfc85f805af78a1ab1684f52d08e14aabe22932bbe46c079a5f", size = 256732, upload-time = "2025-10-24T09:02:51.603Z" }, - { url = "https://files.pythonhosted.org/packages/fe/60/c395c19c14a1345d41ac3f7f0a9b372b666e88f9ba1f71988215174882bb/pytest_codspeed-4.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:238e17abe8f08d8747fa6c7acff34fefd3c40f17a56a7847ca13dc8d6e8c6009", size = 261935, upload-time = "2025-10-24T09:02:52.702Z" }, - { url = "https://files.pythonhosted.org/packages/15/ed/442fb6a1832c2c9002653f24770873839b24c091bd2ed658090c7862c563/pytest_codspeed-4.2.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0881a736285f33b9a8894da8fe8e1775aa1a4310226abe5d1f0329228efb680c", size = 250770, upload-time = "2025-10-24T09:02:53.73Z" }, { url = "https://files.pythonhosted.org/packages/25/0e/8cb71fd3ed4ed08c07aec1245aea7bc1b661ba55fd9c392db76f1978d453/pytest_codspeed-4.2.0-py3-none-any.whl", hash = "sha256:e81bbb45c130874ef99aca97929d72682733527a49f84239ba575b5cb843bab0", size = 113726, upload-time = "2025-10-24T09:02:54.785Z" }, ] @@ -5658,26 +4402,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, ] -[[package]] -name = "python-multipart" -version = "0.0.20" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, -] - [[package]] name = "python-multipart" version = "0.0.22" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, @@ -5731,11 +4459,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, - { url = "https://files.pythonhosted.org/packages/51/2a/f125667ce48105bf1f4e50e03cfa7b24b8c4f47684d7f1cf4dcb6f6b1c15/pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3", size = 161464, upload-time = "2026-01-30T01:03:39.11Z" }, - { url = "https://files.pythonhosted.org/packages/40/df/065a30790a7ca6bb48ad9018dd44668ed9135610ebf56a2a4cb8e513fd5c/pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1", size = 246159, upload-time = "2026-01-30T01:03:40.131Z" }, - { url = "https://files.pythonhosted.org/packages/a5/1c/fd09976a7e04960dabc07ab0e0072c7813d566ec67d5490a4c600683c158/pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db", size = 259120, upload-time = "2026-01-30T01:03:41.233Z" }, - { url = "https://files.pythonhosted.org/packages/52/49/59fdc6fc5a390ae9f308eadeb97dfc70fc2d804ffc49dd39fc97604622ec/pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1", size = 262196, upload-time = "2026-01-30T01:03:42.696Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e7/d6734dccf0080e3dc00a55b0827ab5af30c886f8bc127bbc04bc3445daec/pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a", size = 103510, upload-time = "2026-01-30T01:03:43.915Z" }, { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, ] @@ -5768,9 +4491,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, - { url = "https://files.pythonhosted.org/packages/59/42/b86689aac0cdaee7ae1c58d464b0ff04ca909c19bb6502d4973cdd9f9544/pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b", size = 8760837, upload-time = "2025-07-14T20:12:59.59Z" }, - { url = "https://files.pythonhosted.org/packages/9f/8a/1403d0353f8c5a2f0829d2b1c4becbf9da2f0a4d040886404fc4a5431e4d/pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91", size = 9590187, upload-time = "2025-07-14T20:13:01.419Z" }, - { url = "https://files.pythonhosted.org/packages/60/22/e0e8d802f124772cec9c75430b01a212f86f9de7546bda715e54140d5aeb/pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d", size = 8778162, upload-time = "2025-07-14T20:13:03.544Z" }, ] [[package]] @@ -5844,15 +4564,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, - { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, - { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, - { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, - { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, - { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, - { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, - { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, ] [[package]] @@ -5881,7 +4592,7 @@ name = "redis" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "async-timeout", marker = "python_full_version >= '3.10' and python_full_version < '3.11.3'" }, + { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" } wheels = [ @@ -5893,9 +4604,9 @@ name = "referencing" version = "0.36.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "attrs", marker = "python_full_version >= '3.10'" }, - { name = "rpds-py", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } wheels = [ @@ -6021,23 +4732,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/0d/3a6cfa9ae99606afb612d8fb7a66b245a9d5ff0f29bb347c8a30b6ad561b/regex-2026.1.15-cp314-cp314t-win32.whl", hash = "sha256:e90b8db97f6f2c97eb045b51a6b2c5ed69cedd8392459e0642d4199b94fabd7e", size = 274667, upload-time = "2026-01-14T23:17:19.301Z" }, { url = "https://files.pythonhosted.org/packages/5b/b2/297293bb0742fd06b8d8e2572db41a855cdf1cae0bf009b1cb74fe07e196/regex-2026.1.15-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef19071f4ac9f0834793af85bd04a920b4407715624e40cb7a0631a11137cdf", size = 284386, upload-time = "2026-01-14T23:17:21.231Z" }, { url = "https://files.pythonhosted.org/packages/95/e4/a3b9480c78cf8ee86626cb06f8d931d74d775897d44201ccb813097ae697/regex-2026.1.15-cp314-cp314t-win_arm64.whl", hash = "sha256:ca89c5e596fc05b015f27561b3793dc2fa0917ea0d7507eebb448efd35274a70", size = 274837, upload-time = "2026-01-14T23:17:23.146Z" }, - { url = "https://files.pythonhosted.org/packages/a2/e7/0e1913dc52eee9c5cf8417c9813c4c55972a3f37d27cfa2e623b79b63dbc/regex-2026.1.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:55b4ea996a8e4458dd7b584a2f89863b1655dd3d17b88b46cbb9becc495a0ec5", size = 488185, upload-time = "2026-01-14T23:17:25.2Z" }, - { url = "https://files.pythonhosted.org/packages/78/df/c52c1ff4221529faad0953e197982fe9508c6dbb42327e31bf98ea07472a/regex-2026.1.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e1e28be779884189cdd57735e997f282b64fd7ccf6e2eef3e16e57d7a34a815", size = 290628, upload-time = "2026-01-14T23:17:27.125Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d2/a2fef3717deaff647d7de2bccf899a576c7eaf042b6b271fc4474515fe97/regex-2026.1.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0057de9eaef45783ff69fa94ae9f0fd906d629d0bd4c3217048f46d1daa32e9b", size = 288509, upload-time = "2026-01-14T23:17:29.017Z" }, - { url = "https://files.pythonhosted.org/packages/70/89/faf5ee5c69168753c845a3d58b4683f61c899d162bfe1264fca88d5b3924/regex-2026.1.15-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc7cd0b2be0f0269283a45c0d8b2c35e149d1319dcb4a43c9c3689fa935c1ee6", size = 781088, upload-time = "2026-01-14T23:17:30.961Z" }, - { url = "https://files.pythonhosted.org/packages/7d/2c/707e5c380ad547c93686e21144e7e24dc2064dd84ec5b751b6dbdfc9be2b/regex-2026.1.15-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8db052bbd981e1666f09e957f3790ed74080c2229007c1dd67afdbf0b469c48b", size = 850516, upload-time = "2026-01-14T23:17:32.946Z" }, - { url = "https://files.pythonhosted.org/packages/5d/3b/baa816cdcad1c0f8195f9f40ab2b2a2246c8a2989dcd90641c0c6559e3fd/regex-2026.1.15-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:343db82cb3712c31ddf720f097ef17c11dab2f67f7a3e7be976c4f82eba4e6df", size = 898124, upload-time = "2026-01-14T23:17:36.019Z" }, - { url = "https://files.pythonhosted.org/packages/e7/74/1eb46bde30899825ed9fdf645eba16b7b97c49d12d300f5177989b9a09a4/regex-2026.1.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55e9d0118d97794367309635df398bdfd7c33b93e2fdfa0b239661cd74b4c14e", size = 791290, upload-time = "2026-01-14T23:17:38.097Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5d/b72e176fb21e2ec248baed01151a342d1f44dd43c2b6bb6a41ad183b274e/regex-2026.1.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:008b185f235acd1e53787333e5690082e4f156c44c87d894f880056089e9bc7c", size = 781996, upload-time = "2026-01-14T23:17:40.109Z" }, - { url = "https://files.pythonhosted.org/packages/61/0e/d3b3710eaafd994a4a71205d114abc38cda8691692a2ce2313abe68e7eb7/regex-2026.1.15-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fd65af65e2aaf9474e468f9e571bd7b189e1df3a61caa59dcbabd0000e4ea839", size = 767578, upload-time = "2026-01-14T23:17:42.134Z" }, - { url = "https://files.pythonhosted.org/packages/09/51/c6a6311833e040f95d229a34d82ac1cec2af8a5c00d58b244f2fceecef87/regex-2026.1.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f42e68301ff4afee63e365a5fc302b81bb8ba31af625a671d7acb19d10168a8c", size = 774354, upload-time = "2026-01-14T23:17:44.392Z" }, - { url = "https://files.pythonhosted.org/packages/cc/97/c522d1f19fb2c549aaf680b115c110cd124c02062bc8c95f33db8583b4bb/regex-2026.1.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f7792f27d3ee6e0244ea4697d92b825f9a329ab5230a78c1a68bd274e64b5077", size = 845297, upload-time = "2026-01-14T23:17:47.145Z" }, - { url = "https://files.pythonhosted.org/packages/99/a0/99468c386ab68a5e24c946c5c353c29c33a95523e275c17839f2446db15d/regex-2026.1.15-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:dbaf3c3c37ef190439981648ccbf0c02ed99ae066087dd117fcb616d80b010a4", size = 755132, upload-time = "2026-01-14T23:17:49.796Z" }, - { url = "https://files.pythonhosted.org/packages/70/33/d5748c7b6c9d3621f12570583561ba529e2d1b12e4f70b8f17979b133e65/regex-2026.1.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:adc97a9077c2696501443d8ad3fa1b4fc6d131fc8fd7dfefd1a723f89071cf0a", size = 835662, upload-time = "2026-01-14T23:17:52.559Z" }, - { url = "https://files.pythonhosted.org/packages/ad/15/1986972c276672505437f1ba3c9706c2d91f321cfb9b2f4d06e8bff1b999/regex-2026.1.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:069f56a7bf71d286a6ff932a9e6fb878f151c998ebb2519a9f6d1cee4bffdba3", size = 779513, upload-time = "2026-01-14T23:17:54.711Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f9/124f6a5cb3969d8e30471ed4f46cfc17c47aef1a9863ee8b4ba1d98b1bc4/regex-2026.1.15-cp39-cp39-win32.whl", hash = "sha256:ea4e6b3566127fda5e007e90a8fd5a4169f0cf0619506ed426db647f19c8454a", size = 265923, upload-time = "2026-01-14T23:17:56.69Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c2/bb8fad7d27f1d71fc9772befd544bccd22eddc62a6735f57b003b4aff005/regex-2026.1.15-cp39-cp39-win_amd64.whl", hash = "sha256:cda1ed70d2b264952e88adaa52eea653a33a1b98ac907ae2f86508eb44f65cdc", size = 277900, upload-time = "2026-01-14T23:17:58.72Z" }, - { url = "https://files.pythonhosted.org/packages/f7/fa/4e033327c1d8350bc812cac906d873984d3d4b39529252f392a47ccc356d/regex-2026.1.15-cp39-cp39-win_arm64.whl", hash = "sha256:b325d4714c3c48277bfea1accd94e193ad6ed42b4bad79ad64f3b8f8a31260a5", size = 270413, upload-time = "2026-01-14T23:18:00.764Z" }, ] [[package]] @@ -6048,8 +4742,7 @@ dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, - { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ @@ -6061,8 +4754,7 @@ name = "rich" version = "14.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markdown-it-py" }, { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } @@ -6075,8 +4767,8 @@ name = "rich-rst" version = "1.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "docutils", marker = "python_full_version >= '3.10'" }, - { name = "rich", marker = "python_full_version >= '3.10'" }, + { name = "docutils" }, + { name = "rich" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bc/6d/a506aaa4a9eaa945ed8ab2b7347859f53593864289853c5d6d62b77246e0/rich_rst-1.3.2.tar.gz", hash = "sha256:a1196fdddf1e364b02ec68a05e8ff8f6914fee10fbca2e6b6735f166bb0da8d4", size = 14936, upload-time = "2025-10-14T16:49:45.332Z" } wheels = [ @@ -6088,8 +4780,7 @@ name = "rich-toolkit" version = "0.18.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'" }, - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "click" }, { name = "rich" }, { name = "typing-extensions" }, ] @@ -6193,20 +4884,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fc/d3/18210222b37e87e36357f7b300b7d98c6dd62b133771e71ae27acba83a4f/rignore-0.7.6-cp314-cp314t-win32.whl", hash = "sha256:c1d8f117f7da0a4a96a8daef3da75bc090e3792d30b8b12cfadc240c631353f9", size = 647033, upload-time = "2025-11-05T21:42:00.095Z" }, { url = "https://files.pythonhosted.org/packages/3e/87/033eebfbee3ec7d92b3bb1717d8f68c88e6fc7de54537040f3b3a405726f/rignore-0.7.6-cp314-cp314t-win_amd64.whl", hash = "sha256:ca36e59408bec81de75d307c568c2d0d410fb880b1769be43611472c61e85c96", size = 725647, upload-time = "2025-11-05T21:41:44.449Z" }, { url = "https://files.pythonhosted.org/packages/79/62/b88e5879512c55b8ee979c666ee6902adc4ed05007226de266410ae27965/rignore-0.7.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b83adabeb3e8cf662cabe1931b83e165b88c526fa6af6b3aa90429686e474896", size = 656035, upload-time = "2025-11-05T21:41:31.13Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b4/e7577504d926ced2d6a3fa5ec5f27756639a1ed58a6a3fbefcf3a5659721/rignore-0.7.6-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b3746bda73f2fe6a9c3ab2f20b792e7d810b30acbdba044313fbd2d0174802e7", size = 886535, upload-time = "2025-11-05T20:42:49.317Z" }, - { url = "https://files.pythonhosted.org/packages/2b/74/098bc71a33e2997bc3291d500760123d23e3a6d354380d26c8a7ddc036de/rignore-0.7.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:67a99cf19a5137cc12f14b78dc1bb3f48500f1d5580702c623297d5297bf2752", size = 826621, upload-time = "2025-11-05T20:42:32.421Z" }, - { url = "https://files.pythonhosted.org/packages/7b/73/5f8c276d71009a7e73fb3af6ec3bb930269efeae5830de5c796fa1fb020f/rignore-0.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9e851cfa87033c0c3fd9d35dd8b102aff2981db8bc6e0cab27b460bfe38bf3f", size = 900335, upload-time = "2025-11-05T20:40:59.178Z" }, - { url = "https://files.pythonhosted.org/packages/0d/5f/dde3758084a087e6a5cd981c5277c6171d12127deed64fc4fbf12fb8ceaa/rignore-0.7.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e9b0def154665036516114437a5d603274e5451c0dc9694f622cc3b7e94603e7", size = 874274, upload-time = "2025-11-05T20:41:14.512Z" }, - { url = "https://files.pythonhosted.org/packages/58/b9/da85646824ab728036378ce62c330316108a52f30f36e6c69cac6ceda376/rignore-0.7.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b81274a47e8121224f7f637392b5dfcd9558e32a53e67ba7d04007d8b5281da9", size = 1171639, upload-time = "2025-11-05T20:41:31.206Z" }, - { url = "https://files.pythonhosted.org/packages/35/d1/8c12b779b7f0302c03c1d41511f2ab47012afecdfcd684fbec80af06b331/rignore-0.7.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d75d0b0696fb476664bea1169c8e67b13197750b91eceb4f10b3c7f379c7a204", size = 943985, upload-time = "2025-11-05T20:41:45.598Z" }, - { url = "https://files.pythonhosted.org/packages/79/bf/c233a85d31e4f94b911e92ee7e2dd2b962a5c2528f5ebd79a702596f0626/rignore-0.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ad3aa4dca77cef9168d0c142f72376f5bd27d1d4b8a81561bd01276d3ad9fe1", size = 961707, upload-time = "2025-11-05T20:42:16.461Z" }, - { url = "https://files.pythonhosted.org/packages/9d/eb/cadee9316a5f2a52b4aa7051967ecb94ec17938d6b425bd842d9317559eb/rignore-0.7.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00f8a59e19d219f44a93af7173de197e0d0e61c386364da20ebe98a303cbe38c", size = 986638, upload-time = "2025-11-05T20:42:00.65Z" }, - { url = "https://files.pythonhosted.org/packages/d0/f0/2c3042c8c9639056593def5e99c3bfe850fbb9a38d061ba67b6314315bad/rignore-0.7.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6c682f3cdd741e7a30af2581f6a382ac910080977cd1f97c651467b6268352", size = 1080136, upload-time = "2025-11-05T21:40:21.551Z" }, - { url = "https://files.pythonhosted.org/packages/fc/28/7237b9eb1257b593ee51cd7ef8eed7cc32f50ccff18cb4d7cfe1e6dc54d7/rignore-0.7.6-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ae4e93193f75ebf6b820241594a78f347785cfd5a5fbbac94634052589418352", size = 1139413, upload-time = "2025-11-05T21:40:39.025Z" }, - { url = "https://files.pythonhosted.org/packages/a5/df/c3f382a31ad7ed68510b411c28fec42354d2c43fecb7c053d998ee9410ed/rignore-0.7.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1163d8b5d3a320d4d7cc8635213328850dc41f60e438c7869d540061adf66c98", size = 1120204, upload-time = "2025-11-05T21:40:56.062Z" }, - { url = "https://files.pythonhosted.org/packages/9c/3d/e8585c4e9c0077255ba599684aee78326176ab13ff13805ea62aa7e3235f/rignore-0.7.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3e685f47b4c58b2df7dee81ebc1ec9dbb7f798b9455c3f22be6d75ac6bddee30", size = 1129757, upload-time = "2025-11-05T21:41:14.148Z" }, - { url = "https://files.pythonhosted.org/packages/fd/56/852226c13f89ddbbf12d639900941dc55dcbcf79f5d15294796fd3279d73/rignore-0.7.6-cp39-cp39-win32.whl", hash = "sha256:2af6a0a76575220863cd838693c808a94e750640e0c8a3e9f707e93c2f131fdf", size = 648265, upload-time = "2025-11-05T21:41:58.589Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c6/14e7585dc453a870fe99b1270ee95e2adff02ea0d297cd6e2c4aa46cd43a/rignore-0.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:a326eab6db9ab85b4afb5e6eb28736a9f2b885a9246d9e8c1989bc693dd059a0", size = 728715, upload-time = "2025-11-05T21:41:42.823Z" }, { url = "https://files.pythonhosted.org/packages/85/12/62d690b4644c330d7ac0f739b7f078190ab4308faa909a60842d0e4af5b2/rignore-0.7.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3d3a523af1cd4ed2c0cba8d277a32d329b0c96ef9901fb7ca45c8cfaccf31a5", size = 887462, upload-time = "2025-11-05T20:42:50.804Z" }, { url = "https://files.pythonhosted.org/packages/05/bc/6528a0e97ed2bd7a7c329183367d1ffbc5b9762ae8348d88dae72cc9d1f5/rignore-0.7.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:990853566e65184a506e1e2af2d15045afad3ebaebb8859cb85b882081915110", size = 826918, upload-time = "2025-11-05T20:42:33.689Z" }, { url = "https://files.pythonhosted.org/packages/3e/2c/7d7bad116e09a04e9e1688c6f891fa2d4fd33f11b69ac0bd92419ddebeae/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cab9ff2e436ce7240d7ee301c8ef806ed77c1fd6b8a8239ff65f9bbbcb5b8a3", size = 900922, upload-time = "2025-11-05T20:41:00.361Z" }, @@ -6231,18 +4908,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/56/36d5d34210e5e7dfcd134eed8335b19e80ae940ee758f493e4f2b344dd70/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c081f17290d8a2b96052b79207622aa635686ea39d502b976836384ede3d303c", size = 1139789, upload-time = "2025-11-05T21:40:42.119Z" }, { url = "https://files.pythonhosted.org/packages/6b/5b/bb4f9420802bf73678033a4a55ab1bede36ce2e9b41fec5f966d83d932b3/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:57e8327aacc27f921968cb2a174f9e47b084ce9a7dd0122c8132d22358f6bd79", size = 1120308, upload-time = "2025-11-05T21:40:59.402Z" }, { url = "https://files.pythonhosted.org/packages/ce/8b/a1299085b28a2f6135e30370b126e3c5055b61908622f2488ade67641479/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d8955b57e42f2a5434670d5aa7b75eaf6e74602ccd8955dddf7045379cd762fb", size = 1129444, upload-time = "2025-11-05T21:41:17.906Z" }, - { url = "https://files.pythonhosted.org/packages/47/98/80ef6fda78161e88ef9336fcbe851afccf78c48e69e8266a23fb7922b5aa/rignore-0.7.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e6ba1511c0ab8cd1ed8d6055bb0a6e629f48bfe04854293e0cd2dd88bd7153f8", size = 887180, upload-time = "2025-11-05T21:40:07.665Z" }, - { url = "https://files.pythonhosted.org/packages/21/d7/8666e7081f8476b003d8d2c8f39ecc17c93b7efd261740d15b6830acde82/rignore-0.7.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:50586d90be15f9aa8a2e2ee5a042ee6c51e28848812a35f0c95d4bfc0533d469", size = 827029, upload-time = "2025-11-05T20:42:36.628Z" }, - { url = "https://files.pythonhosted.org/packages/01/aa/3aba657d17b1737f4180b143866fedd269de15f361a8cb26ba363c0c3c13/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b129873dd0ade248e67f25a09b5b72288cbef76ba1a9aae6bac193ee1d8be72", size = 901338, upload-time = "2025-11-05T20:41:03.059Z" }, - { url = "https://files.pythonhosted.org/packages/90/cc/d8c2c9770f5f61b28999c582804f282f2227c155ba13dfb0e9ea03daeaaf/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9d6dd947556ddebfd62753005104986ee14a4e0663818aed19cdf2c33a6b5d5", size = 877563, upload-time = "2025-11-05T20:41:19.209Z" }, - { url = "https://files.pythonhosted.org/packages/55/63/42dd625bf96989be4a928b5444ddec9101ee63a98a15646e611b3ce58b82/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91b95faa532efba888b196331e9af69e693635d469185ac52c796e435e2484e5", size = 1171087, upload-time = "2025-11-05T20:41:35.558Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1e/4130fb622c2081c5322caf7a8888d1d265b99cd5d62cb714b512b8911233/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1016f430fb56f7e400838bbc56fdf43adddb6fcb7bf2a14731dfd725c2fae6c", size = 944335, upload-time = "2025-11-05T20:41:49.859Z" }, - { url = "https://files.pythonhosted.org/packages/0f/b9/3d3ef7773da85e002fab53b1fdd9e9bb111cc86792b761cb38bd00c1532e/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f00c519861926dc703ecbb7bbeb884be67099f96f98b175671fa0a54718f55d1", size = 961500, upload-time = "2025-11-05T20:42:20.798Z" }, - { url = "https://files.pythonhosted.org/packages/1f/bc/346c874a31a721064935c60666a19016b6b01cd716cf73d52dc64e467b30/rignore-0.7.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e34d172bf50e881b7c02e530ae8b1ea96093f0b16634c344f637227b39707b41", size = 987741, upload-time = "2025-11-05T20:42:05.071Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b8/d12dc548da8fdb63292a38727b035153495220cd93730019ee8ed3bdcffb/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:101d3143619898db1e7bede2e3e647daf19bb867c4fb25978016d67978d14868", size = 1081057, upload-time = "2025-11-05T21:40:26.53Z" }, - { url = "https://files.pythonhosted.org/packages/8e/51/7eea5d949212709740ad07e01c524336e44608ef0614a2a1cb31c9a0ea30/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c9f3b420f54199a2b2b3b532d8c7e0860be3fa51f67501113cca6c7bfc392840", size = 1141653, upload-time = "2025-11-05T21:40:43.676Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2b/76ec843cc392fcb4e37d6a8340e823a0bf644872e191d2f5652a4c2c18ee/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:1c6795e3694d750ae5ef172eab7d68a52aefbd9168d2e06647df691db2b03a50", size = 1121465, upload-time = "2025-11-05T21:41:00.904Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9d/e69ad5cf03211a1076f9fe04ca2698c9cb8208b63419c928c26646bdf1d9/rignore-0.7.6-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:750a83a254b020e1193bfa7219dc7edca26bd8888a94cdc59720cbe386ab0c72", size = 1130110, upload-time = "2025-11-05T21:41:20.263Z" }, ] [[package]] @@ -6421,8 +5086,8 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography", marker = "python_full_version >= '3.10'" }, - { name = "jeepney", marker = "python_full_version >= '3.10'" }, + { name = "cryptography" }, + { name = "jeepney" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -6435,8 +5100,7 @@ version = "2.52.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, - { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/59/eb/1b497650eb564701f9a7b8a95c51b2abe9347ed2c0b290ba78f027ebe4ea/sentry_sdk-2.52.0.tar.gz", hash = "sha256:fa0bec872cfec0302970b2996825723d67390cdd5f0229fb9efed93bd5384899", size = 410273, upload-time = "2026-02-04T15:03:54.706Z" } wheels = [ @@ -6506,8 +5170,7 @@ name = "sqlalchemy" version = "2.0.46" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "greenlet", version = "3.2.4", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.10' and platform_machine == 'AMD64') or (python_full_version < '3.10' and platform_machine == 'WIN32') or (python_full_version < '3.10' and platform_machine == 'aarch64') or (python_full_version < '3.10' and platform_machine == 'amd64') or (python_full_version < '3.10' and platform_machine == 'ppc64le') or (python_full_version < '3.10' and platform_machine == 'win32') or (python_full_version < '3.10' and platform_machine == 'x86_64')" }, - { name = "greenlet", version = "3.3.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and platform_machine == 'AMD64') or (python_full_version >= '3.10' and platform_machine == 'WIN32') or (python_full_version >= '3.10' and platform_machine == 'aarch64') or (python_full_version >= '3.10' and platform_machine == 'amd64') or (python_full_version >= '3.10' and platform_machine == 'ppc64le') or (python_full_version >= '3.10' and platform_machine == 'win32') or (python_full_version >= '3.10' and platform_machine == 'x86_64')" }, + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" } @@ -6555,13 +5218,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296, upload-time = "2026-01-21T18:45:12.657Z" }, { url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008, upload-time = "2026-01-21T18:33:11.725Z" }, { url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137, upload-time = "2026-01-21T18:45:15.001Z" }, - { url = "https://files.pythonhosted.org/packages/9a/06/a29b51a577cc5746712ed8a2870794659a6bf405264b32dd5ccc380844d1/sqlalchemy-2.0.46-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:90bde6c6b1827565a95fde597da001212ab436f1b2e0c2dcc7246e14db26e2a3", size = 2158097, upload-time = "2026-01-21T18:24:45.892Z" }, - { url = "https://files.pythonhosted.org/packages/be/55/44689ed21b5a82708502243310878cfc76e0f326ed16103f4336f605055b/sqlalchemy-2.0.46-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b1e5f3a5f1ff4f42d5daab047428cd45a3380e51e191360a35cef71c9a7a2a", size = 3233722, upload-time = "2026-01-21T18:30:56.334Z" }, - { url = "https://files.pythonhosted.org/packages/be/11/1d6024d9cdd2108d500b399bdc77a1738119789aa70c83d68e1012d32596/sqlalchemy-2.0.46-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93bb0aae40b52c57fd74ef9c6933c08c040ba98daf23ad33c3f9893494b8d3ce", size = 3233038, upload-time = "2026-01-21T18:32:26.945Z" }, - { url = "https://files.pythonhosted.org/packages/38/6d/f813e3204baea710f2d82a61821bdf7b39cebda6dbba7cdeb976b0552239/sqlalchemy-2.0.46-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c4e2cc868b7b5208aec6c960950b7bb821f82c2fe66446c92ee0a571765e91a5", size = 3183163, upload-time = "2026-01-21T18:30:58.647Z" }, - { url = "https://files.pythonhosted.org/packages/8d/5d/32b70643ef73c1bb3723a98316b89182cad2b9a6744d5335f1d69fcdb3f2/sqlalchemy-2.0.46-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:965c62be8256d10c11f8907e7a8d3e18127a4c527a5919d85fa87fd9ecc2cfdc", size = 3205174, upload-time = "2026-01-21T18:32:28.684Z" }, - { url = "https://files.pythonhosted.org/packages/99/a9/b9f7bd299b7550925e1e7d71d634e1eee23c035abed7de125fda7c74b0c8/sqlalchemy-2.0.46-cp39-cp39-win32.whl", hash = "sha256:9397b381dcee8a2d6b99447ae85ea2530dcac82ca494d1db877087a13e38926d", size = 2117095, upload-time = "2026-01-21T18:34:02.596Z" }, - { url = "https://files.pythonhosted.org/packages/04/0b/2e376b34a7c2f3d9d40811c3412fdc65cd35c6da2d660c283ad24bd9bab1/sqlalchemy-2.0.46-cp39-cp39-win_amd64.whl", hash = "sha256:4396c948d8217e83e2c202fbdcc0389cf8c93d2c1c5e60fa5c5a955eae0e64be", size = 2140517, upload-time = "2026-01-21T18:34:03.958Z" }, { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" }, ] @@ -6583,80 +5239,37 @@ name = "sse-starlette" version = "3.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio", marker = "python_full_version >= '3.10'" }, - { name = "starlette", version = "0.52.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "anyio" }, + { name = "starlette" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8b/8d/00d280c03ffd39aaee0e86ec81e2d3b9253036a0f93f51d10503adef0e65/sse_starlette-3.2.0.tar.gz", hash = "sha256:8127594edfb51abe44eac9c49e59b0b01f1039d0c7461c6fd91d4e03b70da422", size = 27253, upload-time = "2026-01-17T13:11:05.62Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/96/7f/832f015020844a8b8f7a9cbc103dd76ba8e3875004c41e08440ea3a2b41a/sse_starlette-3.2.0-py3-none-any.whl", hash = "sha256:5876954bd51920fc2cd51baee47a080eb88a37b5b784e615abb0b283f801cdbf", size = 12763, upload-time = "2026-01-17T13:11:03.775Z" }, ] -[[package]] -name = "starlette" -version = "0.49.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "anyio", marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/1a/608df0b10b53b0beb96a37854ee05864d182ddd4b1156a22f1ad3860425a/starlette-0.49.3.tar.gz", hash = "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284", size = 2655031, upload-time = "2025-11-01T15:12:26.13Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/e0/021c772d6a662f43b63044ab481dc6ac7592447605b5b35a957785363122/starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f", size = 74340, upload-time = "2025-11-01T15:12:24.387Z" }, -] - [[package]] name = "starlette" version = "0.52.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "anyio", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" }, + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, ] -[[package]] -name = "strawberry-graphql" -version = "0.283.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "graphql-core", marker = "python_full_version < '3.10'" }, - { name = "lia-web", marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "python-dateutil", marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/74/729c227b1e7fce28678290a5013ddceb543f350b6c14ae83400ab2c727d1/strawberry_graphql-0.283.3.tar.gz", hash = "sha256:375e545856b7587debd4e0f1e2a6fca19d09cc126238a07b9e5164e5eb09342a", size = 212141, upload-time = "2025-10-10T20:03:46.985Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/1b/aa358ef730d727d2e42810bf943542a8cc4c15aa2401f8629d356643a06f/strawberry_graphql-0.283.3-py3-none-any.whl", hash = "sha256:3751d86a219d81a16a13f335bb7d2fa3f57a85fab83d7d284b8ea88e2261d68b", size = 309885, upload-time = "2025-10-10T20:03:44.051Z" }, -] - [[package]] name = "strawberry-graphql" version = "0.291.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "cross-web", marker = "python_full_version >= '3.10'" }, - { name = "graphql-core", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "python-dateutil", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "cross-web" }, + { name = "graphql-core" }, + { name = "packaging" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/dd/e0e68f4b17da6ff5773fcd4bebf86fc4ff8620c854be816d047e9af8c4aa/strawberry_graphql-0.291.2.tar.gz", hash = "sha256:e6076604a786e8437bc64a27348584c082113442f072daf757b56e4863543a97", size = 217730, upload-time = "2026-02-06T14:40:51.173Z" } wheels = [ @@ -6680,50 +5293,24 @@ name = "taskgroup" version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "exceptiongroup", marker = "python_full_version >= '3.10' and python_full_version < '3.14'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.14'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.14'" }, + { name = "typing-extensions", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f0/8d/e218e0160cc1b692e6e0e5ba34e8865dbb171efeb5fc9a704544b3020605/taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d", size = 11504, upload-time = "2025-01-03T09:24:13.761Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/b1/74babcc824a57904e919f3af16d86c08b524c0691504baf038ef2d7f655c/taskgroup-0.2.2-py2.py3-none-any.whl", hash = "sha256:e2c53121609f4ae97303e9ea1524304b4de6faf9eb2c9280c7f87976479a52fb", size = 14237, upload-time = "2025-01-03T09:24:11.41Z" }, ] -[[package]] -name = "temporalio" -version = "1.16.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "nexus-rpc", version = "1.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "protobuf", version = "5.29.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "python-dateutil", marker = "python_full_version < '3.10'" }, - { name = "types-protobuf", marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/32/375ab75d0ebb468cf9c8abbc450a03d3a8c66401fc320b338bd8c00d36b4/temporalio-1.16.0.tar.gz", hash = "sha256:dd926f3e30626fd4edf5e0ce596b75ecb5bbe0e4a0281e545ac91b5577967c91", size = 1733873, upload-time = "2025-08-21T22:12:50.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/36/12bb7234c83ddca4b8b032c8f1a9e07a03067c6ed6d2ddb39c770a4c87c6/temporalio-1.16.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:547c0853310350d3e5b5b9c806246cbf2feb523f685b05bf14ec1b0ece8a7bb6", size = 12540769, upload-time = "2025-08-21T22:11:24.551Z" }, - { url = "https://files.pythonhosted.org/packages/3c/16/a7d402435b8f994979abfeffd3f5ffcaaeada467ac16438e61c51c9f7abe/temporalio-1.16.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b05bb0d06025645aed6f936615311a6774eb8dc66280f32a810aac2283e1258", size = 12968631, upload-time = "2025-08-21T22:11:48.375Z" }, - { url = "https://files.pythonhosted.org/packages/11/6f/16663eef877b61faa5fd917b3a63497416ec4319195af75f6169a1594479/temporalio-1.16.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a08aed4e0f6c2b6bfc779b714e91dfe8c8491a0ddb4c4370627bb07f9bddcfd", size = 13164612, upload-time = "2025-08-21T22:12:16.366Z" }, - { url = "https://files.pythonhosted.org/packages/af/0e/8c6704ca7033aa09dc084f285d70481d758972cc341adc3c84d5f82f7b01/temporalio-1.16.0-cp39-abi3-win_amd64.whl", hash = "sha256:7c190362b0d7254f1f93fb71456063e7b299ac85a89f6227758af82c6a5aa65b", size = 13177058, upload-time = "2025-08-21T22:12:44.239Z" }, -] - [[package]] name = "temporalio" version = "1.20.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "nexus-rpc", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "protobuf", version = "6.33.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "python-dateutil", marker = "python_full_version == '3.10.*'" }, - { name = "types-protobuf", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.10'" }, + { name = "nexus-rpc" }, + { name = "protobuf" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, + { name = "types-protobuf" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/21/db/7d5118d28b0918888e1ec98f56f659fdb006351e06d95f30f4274962a76f/temporalio-1.20.0.tar.gz", hash = "sha256:5a6a85b7d298b7359bffa30025f7deac83c74ac095a4c6952fbf06c249a2a67c", size = 1850498, upload-time = "2025-11-25T21:25:20.225Z" } wheels = [ @@ -6734,51 +5321,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/23/5689c014a76aff3b744b3ee0d80815f63b1362637814f5fbb105244df09b/temporalio-1.20.0-cp310-abi3-win_amd64.whl", hash = "sha256:eacfd571b653e0a0f4aa6593f4d06fc628797898f0900d400e833a1f40cad03a", size = 12745027, upload-time = "2025-11-25T21:25:16.827Z" }, ] -[[package]] -name = "tenacity" -version = "9.1.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, -] - [[package]] name = "tenacity" version = "9.1.3" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/1e/4a/c3357c8742f361785e3702bb4c9c68c4cb37a80aa657640b820669be5af1/tenacity-9.1.3.tar.gz", hash = "sha256:a6724c947aa717087e2531f883bde5c9188f603f6669a9b8d54eb998e604c12a", size = 49002, upload-time = "2026-02-05T06:33:12.866Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/64/6b/cdc85edb15e384d8e934aad89638cc8646e118c80de94c60125d0fc0a185/tenacity-9.1.3-py3-none-any.whl", hash = "sha256:51171cfc6b8a7826551e2f029426b10a6af189c5ac6986adcd7eb36d42f17954", size = 28858, upload-time = "2026-02-05T06:33:11.219Z" }, ] -[[package]] -name = "termcolor" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" }, -] - [[package]] name = "termcolor" version = "3.3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, @@ -6798,8 +5353,8 @@ name = "tiktoken" version = "0.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "regex", marker = "python_full_version >= '3.10'" }, - { name = "requests", marker = "python_full_version >= '3.10'" }, + { name = "regex" }, + { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } wheels = [ @@ -6852,40 +5407,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, - { url = "https://files.pythonhosted.org/packages/c7/d1/7507bfb9c2ceef52ae3ae813013215c185648e21127538aae66dedd3af9c/tiktoken-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d51d75a5bffbf26f86554d28e78bfb921eae998edc2675650fd04c7e1f0cdc1e", size = 1053407, upload-time = "2025-10-06T20:22:35.492Z" }, - { url = "https://files.pythonhosted.org/packages/ee/4a/8ea1da602ac39dee4356b4cd6040a2325507482c36043044b6f581597b4f/tiktoken-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:09eb4eae62ae7e4c62364d9ec3a57c62eea707ac9a2b2c5d6bd05de6724ea179", size = 997150, upload-time = "2025-10-06T20:22:37.286Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1a/62d1d36b167eccd441aff2f0091551ca834295541b949d161021aa658167/tiktoken-0.12.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:df37684ace87d10895acb44b7f447d4700349b12197a526da0d4a4149fde074c", size = 1131575, upload-time = "2025-10-06T20:22:39.023Z" }, - { url = "https://files.pythonhosted.org/packages/f7/16/544207d63c8c50edd2321228f21d236e4e49d235128bb7e3e0f69eed0807/tiktoken-0.12.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4c9614597ac94bb294544345ad8cf30dac2129c05e2db8dc53e082f355857af7", size = 1154920, upload-time = "2025-10-06T20:22:40.175Z" }, - { url = "https://files.pythonhosted.org/packages/99/4c/0a3504157c81364fc0c64cada54efef0567961357e786706ea63bc8946e1/tiktoken-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:20cf97135c9a50de0b157879c3c4accbb29116bcf001283d26e073ff3b345946", size = 1196766, upload-time = "2025-10-06T20:22:41.365Z" }, - { url = "https://files.pythonhosted.org/packages/d4/46/8e6a258ae65447c75770fe5ea8968acab369e8c9f537f727c91f83772325/tiktoken-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:15d875454bbaa3728be39880ddd11a5a2a9e548c29418b41e8fd8a767172b5ec", size = 1258278, upload-time = "2025-10-06T20:22:42.846Z" }, - { url = "https://files.pythonhosted.org/packages/35/43/3b95de4f5e76f3cafc70dac9b1b9cfe759ff3bfd494ac91a280e93772e90/tiktoken-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cff3688ba3c639ebe816f8d58ffbbb0aa7433e23e08ab1cade5d175fc973fb3", size = 881888, upload-time = "2025-10-06T20:22:44.059Z" }, -] - -[[package]] -name = "tinycss2" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "webencodings", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, ] [[package]] name = "tinycss2" version = "1.5.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "webencodings", marker = "python_full_version >= '3.10'" }, + { name = "webencodings" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a3/ae/2ca4913e5c0f09781d75482874c3a95db9105462a92ddd303c7d285d3df2/tinycss2-1.5.1.tar.gz", hash = "sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957", size = 88195, upload-time = "2025-11-23T10:29:10.082Z" } wheels = [ @@ -6920,10 +5449,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/cd/e4851401f3d8f6f45d8480262ab6a5c8cb9c4302a790a35aa14eeed6d2fd/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c", size = 3161308, upload-time = "2026-01-05T10:40:40.737Z" }, { url = "https://files.pythonhosted.org/packages/6f/6e/55553992a89982cd12d4a66dddb5e02126c58677ea3931efcbe601d419db/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195", size = 3718964, upload-time = "2026-01-05T10:40:46.56Z" }, { url = "https://files.pythonhosted.org/packages/59/8c/b1c87148aa15e099243ec9f0cf9d0e970cc2234c3257d558c25a2c5304e6/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5", size = 3373542, upload-time = "2026-01-05T10:40:52.803Z" }, - { url = "https://files.pythonhosted.org/packages/27/46/8d7db1dff181be50b207ab0a7483a22d5c3a4f903a9afc7cf7e465ad8109/tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319f659ee992222f04e58f84cbf407cfa66a65fe3a8de44e8ad2bc53e7d99012", size = 3287784, upload-time = "2026-01-05T10:40:37.108Z" }, - { url = "https://files.pythonhosted.org/packages/5b/6e/3bc33cae8bf114afa5a98e35eb065c72b7c37d01d370906a893f33881767/tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e50f8554d504f617d9e9d6e4c2c2884a12b388a97c5c77f0bc6cf4cd032feee", size = 3164301, upload-time = "2026-01-05T10:40:42.367Z" }, - { url = "https://files.pythonhosted.org/packages/91/fc/6aa749d7d443aab4daa6f8bc00338389149fd2534e25b772285c3301993e/tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a62ba2c5faa2dd175aaeed7b15abf18d20266189fb3406c5d0550dd34dd5f37", size = 3717771, upload-time = "2026-01-05T10:40:49.076Z" }, - { url = "https://files.pythonhosted.org/packages/fc/60/5b440d251863bd33f9b0a416c695b0309487b83abf6f2dafe9163a3aeac2/tokenizers-0.22.2-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143b999bdc46d10febb15cbffb4207ddd1f410e2c755857b5a0797961bbdc113", size = 3377740, upload-time = "2026-01-05T10:40:54.859Z" }, ] [[package]] @@ -6992,43 +5517,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] -[[package]] -name = "trio" -version = "0.31.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "attrs", marker = "python_full_version < '3.10'" }, - { name = "cffi", marker = "python_full_version < '3.10' and implementation_name != 'pypy' and os_name == 'nt'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, - { name = "idna", marker = "python_full_version < '3.10'" }, - { name = "outcome", marker = "python_full_version < '3.10'" }, - { name = "sniffio", marker = "python_full_version < '3.10'" }, - { name = "sortedcontainers", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/8f/c6e36dd11201e2a565977d8b13f0b027ba4593c1a80bed5185489178e257/trio-0.31.0.tar.gz", hash = "sha256:f71d551ccaa79d0cb73017a33ef3264fde8335728eb4c6391451fe5d253a9d5b", size = 605825, upload-time = "2025-09-09T15:17:15.242Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/5b/94237a3485620dbff9741df02ff6d8acaa5fdec67d81ab3f62e4d8511bf7/trio-0.31.0-py3-none-any.whl", hash = "sha256:b5d14cd6293d79298b49c3485ffd9c07e3ce03a6da8c7dfbe0cb3dd7dc9a4774", size = 512679, upload-time = "2025-09-09T15:17:13.821Z" }, -] - [[package]] name = "trio" version = "0.32.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "attrs", marker = "python_full_version >= '3.10'" }, - { name = "cffi", marker = "python_full_version >= '3.10' and implementation_name != 'pypy' and os_name == 'nt'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "idna", marker = "python_full_version >= '3.10'" }, - { name = "outcome", marker = "python_full_version >= '3.10'" }, - { name = "sniffio", marker = "python_full_version >= '3.10'" }, - { name = "sortedcontainers", marker = "python_full_version >= '3.10'" }, + { name = "attrs" }, + { name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "outcome" }, + { name = "sniffio" }, + { name = "sortedcontainers" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d8/ce/0041ddd9160aac0031bcf5ab786c7640d795c797e67c438e15cfedf815c8/trio-0.32.0.tar.gz", hash = "sha256:150f29ec923bcd51231e1d4c71c7006e65247d68759dd1c19af4ea815a25806b", size = 605323, upload-time = "2025-10-31T07:18:17.466Z" } wheels = [ @@ -7040,8 +5540,7 @@ name = "typer" 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'" }, - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "click" }, { name = "rich" }, { name = "shellingham" }, { name = "typing-extensions" }, @@ -7069,31 +5568,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/43/58e75bac4219cbafee83179505ff44cae3153ec279be0e30583a73b8f108/types_protobuf-6.32.1.20251210-py3-none-any.whl", hash = "sha256:2641f78f3696822a048cfb8d0ff42ccd85c25f12f871fbebe86da63793692140", size = 77921, upload-time = "2025-12-10T03:14:24.477Z" }, ] -[[package]] -name = "types-requests" -version = "2.31.0.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "types-urllib3", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f9/b8/c1e8d39996b4929b918aba10dba5de07a8b3f4c8487bb61bb79882544e69/types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0", size = 15535, upload-time = "2023-09-27T06:19:38.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/a1/6f8dc74d9069e790d604ddae70cb46dcbac668f1bb08136e7b0f2f5cd3bf/types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9", size = 14516, upload-time = "2023-09-27T06:19:36.373Z" }, -] - [[package]] name = "types-requests" version = "2.32.4.20260107" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "urllib3" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } wheels = [ @@ -7109,15 +5589,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/f2/d812543c350674d8b3f6e17c8922248ee3bb752c2a76f64beb8c538b40cf/types_ujson-5.10.0.20250822-py3-none-any.whl", hash = "sha256:3e9e73a6dc62ccc03449d9ac2c580cd1b7a8e4873220db498f7dd056754be080", size = 7657, upload-time = "2025-08-22T03:02:18.699Z" }, ] -[[package]] -name = "types-urllib3" -version = "1.26.25.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/de/b9d7a68ad39092368fb21dd6194b362b98a1daeea5dcfef5e1adb5031c7e/types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f", size = 11239, upload-time = "2023-07-20T15:19:31.307Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/7b/3fc711b2efea5e85a7a0bbfe269ea944aa767bbba5ec52f9ee45d362ccf3/types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e", size = 15377, upload-time = "2023-07-20T15:19:30.379Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" @@ -7211,17 +5682,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/ed/5a057199fb0a5deabe0957073a1c1c1c02a3e99476cd03daee98ea21fa57/ujson-5.11.0-cp314-cp314t-win32.whl", hash = "sha256:aa6d7a5e09217ff93234e050e3e380da62b084e26b9f2e277d2606406a2fc2e5", size = 41859, upload-time = "2025-08-20T11:56:30.495Z" }, { url = "https://files.pythonhosted.org/packages/aa/03/b19c6176bdf1dc13ed84b886e99677a52764861b6cc023d5e7b6ebda249d/ujson-5.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:48055e1061c1bb1f79e75b4ac39e821f3f35a9b82de17fce92c3140149009bec", size = 46183, upload-time = "2025-08-20T11:56:31.574Z" }, { url = "https://files.pythonhosted.org/packages/5d/ca/a0413a3874b2dc1708b8796ca895bf363292f9c70b2e8ca482b7dbc0259d/ujson-5.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1194b943e951092db611011cb8dbdb6cf94a3b816ed07906e14d3bc6ce0e90ab", size = 40264, upload-time = "2025-08-20T11:56:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/39/bf/c6f59cdf74ce70bd937b97c31c42fd04a5ed1a9222d0197e77e4bd899841/ujson-5.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65f3c279f4ed4bf9131b11972040200c66ae040368abdbb21596bf1564899694", size = 55283, upload-time = "2025-08-20T11:56:33.947Z" }, - { url = "https://files.pythonhosted.org/packages/8d/c1/a52d55638c0c644b8a63059f95ad5ffcb4ad8f60d8bc3e8680f78e77cc75/ujson-5.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:99c49400572cd77050894e16864a335225191fd72a818ea6423ae1a06467beac", size = 53168, upload-time = "2025-08-20T11:56:35.141Z" }, - { url = "https://files.pythonhosted.org/packages/75/6c/e64e19a01d59c8187d01ffc752ee3792a09f5edaaac2a0402de004459dd7/ujson-5.11.0-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0654a2691fc252c3c525e3d034bb27b8a7546c9d3eb33cd29ce6c9feda361a6a", size = 57809, upload-time = "2025-08-20T11:56:36.293Z" }, - { url = "https://files.pythonhosted.org/packages/9f/36/910117b7a8a1c188396f6194ca7bc8fd75e376d8f7e3cf5eb6219fc8b09d/ujson-5.11.0-cp39-cp39-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:6b6ec7e7321d7fc19abdda3ad809baef935f49673951a8bab486aea975007e02", size = 59797, upload-time = "2025-08-20T11:56:37.746Z" }, - { url = "https://files.pythonhosted.org/packages/c7/17/bcc85d282ee2f4cdef5f577e0a43533eedcae29cc6405edf8c62a7a50368/ujson-5.11.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f62b9976fabbcde3ab6e413f4ec2ff017749819a0786d84d7510171109f2d53c", size = 57378, upload-time = "2025-08-20T11:56:39.123Z" }, - { url = "https://files.pythonhosted.org/packages/ef/39/120bb76441bf835f3c3f42db9c206f31ba875711637a52a8209949ab04b0/ujson-5.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f1a27ab91083b4770e160d17f61b407f587548f2c2b5fbf19f94794c495594a", size = 1036515, upload-time = "2025-08-20T11:56:40.848Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ae/fe1b4ff6388f681f6710e9494656957725b1e73ae50421ec04567df9fb75/ujson-5.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ecd6ff8a3b5a90c292c2396c2d63c687fd0ecdf17de390d852524393cd9ed052", size = 1195753, upload-time = "2025-08-20T11:56:42.341Z" }, - { url = "https://files.pythonhosted.org/packages/92/20/005b93f2cf846ae50b46812fcf24bbdd127521197e5f1e1a82e3b3e730a1/ujson-5.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9aacbeb23fdbc4b256a7d12e0beb9063a1ba5d9e0dbb2cfe16357c98b4334596", size = 1088844, upload-time = "2025-08-20T11:56:43.777Z" }, - { url = "https://files.pythonhosted.org/packages/41/9e/3142023c30008e2b24d7368a389b26d28d62fcd3f596d3d898a72dd09173/ujson-5.11.0-cp39-cp39-win32.whl", hash = "sha256:674f306e3e6089f92b126eb2fe41bcb65e42a15432c143365c729fdb50518547", size = 39652, upload-time = "2025-08-20T11:56:45.034Z" }, - { url = "https://files.pythonhosted.org/packages/ca/89/f4de0a3c485d0163f85f552886251876645fb62cbbe24fcdc0874b9fae03/ujson-5.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:c6618f480f7c9ded05e78a1938873fde68baf96cdd74e6d23c7e0a8441175c4b", size = 43783, upload-time = "2025-08-20T11:56:46.156Z" }, - { url = "https://files.pythonhosted.org/packages/48/b1/2d50987a7b7cccb5c1fbe9ae7b184211106237b32c7039118c41d79632ea/ujson-5.11.0-cp39-cp39-win_arm64.whl", hash = "sha256:5600202a731af24a25e2d7b6eb3f648e4ecd4bb67c4d5cf12f8fab31677469c9", size = 38430, upload-time = "2025-08-20T11:56:47.653Z" }, { url = "https://files.pythonhosted.org/packages/50/17/30275aa2933430d8c0c4ead951cc4fdb922f575a349aa0b48a6f35449e97/ujson-5.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:abae0fb58cc820092a0e9e8ba0051ac4583958495bfa5262a12f628249e3b362", size = 51206, upload-time = "2025-08-20T11:56:48.797Z" }, { url = "https://files.pythonhosted.org/packages/c3/15/42b3924258eac2551f8f33fa4e35da20a06a53857ccf3d4deb5e5d7c0b6c/ujson-5.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fac6c0649d6b7c3682a0a6e18d3de6857977378dce8d419f57a0b20e3d775b39", size = 48907, upload-time = "2025-08-20T11:56:50.136Z" }, { url = "https://files.pythonhosted.org/packages/94/7e/0519ff7955aba581d1fe1fb1ca0e452471250455d182f686db5ac9e46119/ujson-5.11.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b42c115c7c6012506e8168315150d1e3f76e7ba0f4f95616f4ee599a1372bbc", size = 50319, upload-time = "2025-08-20T11:56:51.63Z" }, @@ -7230,71 +5690,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/5b/8c5e33228f7f83f05719964db59f3f9f276d272dc43752fa3bbf0df53e7b/ujson-5.11.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:416389ec19ef5f2013592f791486bef712ebce0cd59299bf9df1ba40bb2f6e04", size = 43835, upload-time = "2025-08-20T11:56:55.237Z" }, ] -[[package]] -name = "urllib3" -version = "1.26.20" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380, upload-time = "2024-08-29T15:43:11.37Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225, upload-time = "2024-08-29T15:43:08.921Z" }, -] - [[package]] name = "urllib3" version = "2.6.3" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] -[[package]] -name = "uvicorn" -version = "0.39.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "h11", marker = "python_full_version < '3.10'" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/4f/f9fdac7cf6dd79790eb165639b5c452ceeabc7bbabbba4569155470a287d/uvicorn-0.39.0.tar.gz", hash = "sha256:610512b19baa93423d2892d7823741f6d27717b642c8964000d7194dded19302", size = 82001, upload-time = "2025-12-21T13:05:17.973Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/25/db2b1c6c35bf22e17fe5412d2ee5d3fd7a20d07ebc9dac8b58f7db2e23a0/uvicorn-0.39.0-py3-none-any.whl", hash = "sha256:7beec21bd2693562b386285b188a7963b06853c0d006302b3e4cfed950c9929a", size = 68491, upload-time = "2025-12-21T13:05:16.291Z" }, -] - -[package.optional-dependencies] -standard = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "httptools", marker = "python_full_version < '3.10'" }, - { name = "python-dotenv", marker = "python_full_version < '3.10'" }, - { name = "pyyaml", marker = "python_full_version < '3.10'" }, - { name = "uvloop", marker = "python_full_version < '3.10' and platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, - { name = "watchfiles", marker = "python_full_version < '3.10'" }, - { name = "websockets", marker = "python_full_version < '3.10'" }, -] - [[package]] name = "uvicorn" version = "0.40.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14'", - "python_full_version >= '3.10' and python_full_version < '3.14'", -] dependencies = [ - { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "h11", marker = "python_full_version >= '3.10'" }, - { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" } wheels = [ @@ -7303,13 +5715,13 @@ wheels = [ [package.optional-dependencies] standard = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "httptools", marker = "python_full_version >= '3.10'" }, - { name = "python-dotenv", marker = "python_full_version >= '3.10'" }, - { name = "pyyaml", marker = "python_full_version >= '3.10'" }, - { name = "uvloop", marker = "python_full_version >= '3.10' and platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, - { name = "watchfiles", marker = "python_full_version >= '3.10'" }, - { name = "websockets", marker = "python_full_version >= '3.10'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, ] [[package]] @@ -7354,12 +5766,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, - { url = "https://files.pythonhosted.org/packages/bd/1b/6fbd611aeba01ef802c5876c94d7be603a9710db055beacbad39e75a31aa/uvloop-0.22.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b45649628d816c030dba3c80f8e2689bab1c89518ed10d426036cdc47874dfc4", size = 1345858, upload-time = "2025-10-16T22:17:11.106Z" }, - { url = "https://files.pythonhosted.org/packages/9e/91/2c84f00bdbe3c51023cc83b027bac1fe959ba4a552e970da5ef0237f7945/uvloop-0.22.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ea721dd3203b809039fcc2983f14608dae82b212288b346e0bfe46ec2fab0b7c", size = 743913, upload-time = "2025-10-16T22:17:12.165Z" }, - { url = "https://files.pythonhosted.org/packages/cc/10/76aec83886d41a88aca5681db6a2c0601622d0d2cb66cd0d200587f962ad/uvloop-0.22.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ae676de143db2b2f60a9696d7eca5bb9d0dd6cc3ac3dad59a8ae7e95f9e1b54", size = 3635818, upload-time = "2025-10-16T22:17:13.812Z" }, - { url = "https://files.pythonhosted.org/packages/d5/9a/733fcb815d345979fc54d3cdc3eb50bc75a47da3e4003ea7ada58e6daa65/uvloop-0.22.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17d4e97258b0172dfa107b89aa1eeba3016f4b1974ce85ca3ef6a66b35cbf659", size = 3685477, upload-time = "2025-10-16T22:17:15.307Z" }, - { url = "https://files.pythonhosted.org/packages/83/fb/bee1eb11cc92bd91f76d97869bb6a816e80d59fd73721b0a3044dc703d9c/uvloop-0.22.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:05e4b5f86e621cf3927631789999e697e58f0d2d32675b67d9ca9eb0bca55743", size = 3496128, upload-time = "2025-10-16T22:17:16.558Z" }, - { url = "https://files.pythonhosted.org/packages/76/ee/3fdfeaa9776c0fd585d358c92b1dbca669720ffa476f0bbe64ed8f245bd7/uvloop-0.22.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:286322a90bea1f9422a470d5d2ad82d38080be0a29c4dd9b3e6384320a4d11e7", size = 3602565, upload-time = "2025-10-16T22:17:17.755Z" }, ] [[package]] @@ -7380,13 +5786,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, - { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" }, - { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" }, - { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" }, { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, - { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" }, { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, @@ -7492,18 +5893,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, - { url = "https://files.pythonhosted.org/packages/a4/68/a7303a15cc797ab04d58f1fea7f67c50bd7f80090dfd7e750e7576e07582/watchfiles-1.1.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c882d69f6903ef6092bedfb7be973d9319940d56b8427ab9187d1ecd73438a70", size = 409220, upload-time = "2025-10-14T15:05:51.917Z" }, - { url = "https://files.pythonhosted.org/packages/99/b8/d1857ce9ac76034c053fa7ef0e0ef92d8bd031e842ea6f5171725d31e88f/watchfiles-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6ff426a7cb54f310d51bfe83fe9f2bbe40d540c741dc974ebc30e6aa238f52e", size = 396712, upload-time = "2025-10-14T15:05:53.437Z" }, - { url = "https://files.pythonhosted.org/packages/41/7a/da7ada566f48beaa6a30b13335b49d1f6febaf3a5ddbd1d92163a1002cf4/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79ff6c6eadf2e3fc0d7786331362e6ef1e51125892c75f1004bd6b52155fb956", size = 451462, upload-time = "2025-10-14T15:05:54.742Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b2/7cb9e0d5445a8d45c4cccd68a590d9e3a453289366b96ff37d1075aaebef/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c1f5210f1b8fc91ead1283c6fd89f70e76fb07283ec738056cf34d51e9c1d62c", size = 460811, upload-time = "2025-10-14T15:05:55.743Z" }, - { url = "https://files.pythonhosted.org/packages/04/9d/b07d4491dde6db6ea6c680fdec452f4be363d65c82004faf2d853f59b76f/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9c4702f29ca48e023ffd9b7ff6b822acdf47cb1ff44cb490a3f1d5ec8987e9c", size = 490576, upload-time = "2025-10-14T15:05:56.983Z" }, - { url = "https://files.pythonhosted.org/packages/56/03/e64dcab0a1806157db272a61b7891b062f441a30580a581ae72114259472/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acb08650863767cbc58bca4813b92df4d6c648459dcaa3d4155681962b2aa2d3", size = 597726, upload-time = "2025-10-14T15:05:57.986Z" }, - { url = "https://files.pythonhosted.org/packages/5c/8e/a827cf4a8d5f2903a19a934dcf512082eb07675253e154d4cd9367978a58/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08af70fd77eee58549cd69c25055dc344f918d992ff626068242259f98d598a2", size = 474900, upload-time = "2025-10-14T15:05:59.378Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/94fed0b346b85b22303a12eee5f431006fae6af70d841cac2f4403245533/watchfiles-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c3631058c37e4a0ec440bf583bc53cdbd13e5661bb6f465bc1d88ee9a0a4d02", size = 457521, upload-time = "2025-10-14T15:06:00.419Z" }, - { url = "https://files.pythonhosted.org/packages/c4/64/bc3331150e8f3c778d48a4615d4b72b3d2d87868635e6c54bbd924946189/watchfiles-1.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cf57a27fb986c6243d2ee78392c503826056ffe0287e8794503b10fb51b881be", size = 632191, upload-time = "2025-10-14T15:06:01.621Z" }, - { url = "https://files.pythonhosted.org/packages/e4/84/f39e19549c2f3ec97225dcb2ceb9a7bb3c5004ed227aad1f321bf0ff2051/watchfiles-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d7e7067c98040d646982daa1f37a33d3544138ea155536c2e0e63e07ff8a7e0f", size = 623923, upload-time = "2025-10-14T15:06:02.671Z" }, - { url = "https://files.pythonhosted.org/packages/0e/24/0759ae15d9a0c9c5fe946bd4cf45ab9e7bad7cfede2c06dc10f59171b29f/watchfiles-1.1.1-cp39-cp39-win32.whl", hash = "sha256:6c9c9262f454d1c4d8aaa7050121eb4f3aea197360553699520767daebf2180b", size = 274010, upload-time = "2025-10-14T15:06:03.779Z" }, - { url = "https://files.pythonhosted.org/packages/7e/3b/eb26cddd4dfa081e2bf6918be3b2fc05ee3b55c1d21331d5562ee0c6aaad/watchfiles-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:74472234c8370669850e1c312490f6026d132ca2d396abfad8830b4f1c096957", size = 289090, upload-time = "2025-10-14T15:06:04.821Z" }, { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" }, { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" }, { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" }, @@ -7512,10 +5901,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, - { url = "https://files.pythonhosted.org/packages/00/db/38a2c52fdbbfe2fc7ffaaaaaebc927d52b9f4d5139bba3186c19a7463001/watchfiles-1.1.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdab464fee731e0884c35ae3588514a9bcf718d0e2c82169c1c4a85cc19c3c7f", size = 409210, upload-time = "2025-10-14T15:06:14.492Z" }, - { url = "https://files.pythonhosted.org/packages/d1/43/d7e8b71f6c21ff813ee8da1006f89b6c7fff047fb4c8b16ceb5e840599c5/watchfiles-1.1.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3dbd8cbadd46984f802f6d479b7e3afa86c42d13e8f0f322d669d79722c8ec34", size = 397286, upload-time = "2025-10-14T15:06:16.177Z" }, - { url = "https://files.pythonhosted.org/packages/1f/5d/884074a5269317e75bd0b915644b702b89de73e61a8a7446e2b225f45b1f/watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5524298e3827105b61951a29c3512deb9578586abf3a7c5da4a8069df247cccc", size = 451768, upload-time = "2025-10-14T15:06:18.266Z" }, - { url = "https://files.pythonhosted.org/packages/17/71/7ffcaa9b5e8961a25026058058c62ec8f604d2a6e8e1e94bee8a09e1593f/watchfiles-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b943d3668d61cfa528eb949577479d3b077fd25fb83c641235437bc0b5bc60e", size = 458561, upload-time = "2025-10-14T15:06:19.323Z" }, ] [[package]] @@ -7586,29 +5971,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/36/db/3fff0bcbe339a6fa6a3b9e3fbc2bfb321ec2f4cd233692272c5a8d6cf801/websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5", size = 175424, upload-time = "2025-03-05T20:02:56.505Z" }, - { url = "https://files.pythonhosted.org/packages/46/e6/519054c2f477def4165b0ec060ad664ed174e140b0d1cbb9fafa4a54f6db/websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a", size = 173077, upload-time = "2025-03-05T20:02:58.37Z" }, - { url = "https://files.pythonhosted.org/packages/1a/21/c0712e382df64c93a0d16449ecbf87b647163485ca1cc3f6cbadb36d2b03/websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b", size = 173324, upload-time = "2025-03-05T20:02:59.773Z" }, - { url = "https://files.pythonhosted.org/packages/1c/cb/51ba82e59b3a664df54beed8ad95517c1b4dc1a913730e7a7db778f21291/websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770", size = 182094, upload-time = "2025-03-05T20:03:01.827Z" }, - { url = "https://files.pythonhosted.org/packages/fb/0f/bf3788c03fec679bcdaef787518dbe60d12fe5615a544a6d4cf82f045193/websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb", size = 181094, upload-time = "2025-03-05T20:03:03.123Z" }, - { url = "https://files.pythonhosted.org/packages/5e/da/9fb8c21edbc719b66763a571afbaf206cb6d3736d28255a46fc2fe20f902/websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054", size = 181397, upload-time = "2025-03-05T20:03:04.443Z" }, - { url = "https://files.pythonhosted.org/packages/2e/65/65f379525a2719e91d9d90c38fe8b8bc62bd3c702ac651b7278609b696c4/websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee", size = 181794, upload-time = "2025-03-05T20:03:06.708Z" }, - { url = "https://files.pythonhosted.org/packages/d9/26/31ac2d08f8e9304d81a1a7ed2851c0300f636019a57cbaa91342015c72cc/websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed", size = 181194, upload-time = "2025-03-05T20:03:08.844Z" }, - { url = "https://files.pythonhosted.org/packages/98/72/1090de20d6c91994cd4b357c3f75a4f25ee231b63e03adea89671cc12a3f/websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880", size = 181164, upload-time = "2025-03-05T20:03:10.242Z" }, - { url = "https://files.pythonhosted.org/packages/2d/37/098f2e1c103ae8ed79b0e77f08d83b0ec0b241cf4b7f2f10edd0126472e1/websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411", size = 176381, upload-time = "2025-03-05T20:03:12.77Z" }, - { url = "https://files.pythonhosted.org/packages/75/8b/a32978a3ab42cebb2ebdd5b05df0696a09f4d436ce69def11893afa301f0/websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4", size = 176841, upload-time = "2025-03-05T20:03:14.367Z" }, { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/b7/48/4b67623bac4d79beb3a6bb27b803ba75c1bdedc06bd827e465803690a4b2/websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940", size = 173106, upload-time = "2025-03-05T20:03:29.404Z" }, - { url = "https://files.pythonhosted.org/packages/ed/f0/adb07514a49fe5728192764e04295be78859e4a537ab8fcc518a3dbb3281/websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e", size = 173339, upload-time = "2025-03-05T20:03:30.755Z" }, - { url = "https://files.pythonhosted.org/packages/87/28/bd23c6344b18fb43df40d0700f6d3fffcd7cef14a6995b4f976978b52e62/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9", size = 174597, upload-time = "2025-03-05T20:03:32.247Z" }, - { url = "https://files.pythonhosted.org/packages/6d/79/ca288495863d0f23a60f546f0905ae8f3ed467ad87f8b6aceb65f4c013e4/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b", size = 174205, upload-time = "2025-03-05T20:03:33.731Z" }, - { url = "https://files.pythonhosted.org/packages/04/e4/120ff3180b0872b1fe6637f6f995bcb009fb5c87d597c1fc21456f50c848/websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f", size = 174150, upload-time = "2025-03-05T20:03:35.757Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c3/30e2f9c539b8da8b1d76f64012f3b19253271a63413b2d3adb94b143407f/websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123", size = 176877, upload-time = "2025-03-05T20:03:37.199Z" }, { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] @@ -7690,16 +6058,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/41/be/be9b3b0a461ee3e30278706f3f3759b9b69afeedef7fe686036286c04ac6/wrapt-1.17.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc", size = 53485, upload-time = "2025-08-12T05:51:53.11Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a8/8f61d6b8f526efc8c10e12bf80b4206099fea78ade70427846a37bc9cbea/wrapt-1.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9", size = 38675, upload-time = "2025-08-12T05:51:42.885Z" }, - { url = "https://files.pythonhosted.org/packages/48/f1/23950c29a25637b74b322f9e425a17cc01a478f6afb35138ecb697f9558d/wrapt-1.17.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d", size = 38956, upload-time = "2025-08-12T05:52:03.149Z" }, - { url = "https://files.pythonhosted.org/packages/43/46/dd0791943613885f62619f18ee6107e6133237a6b6ed8a9ecfac339d0b4f/wrapt-1.17.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a", size = 81745, upload-time = "2025-08-12T05:52:49.62Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ec/bb2d19bd1a614cc4f438abac13ae26c57186197920432d2a915183b15a8b/wrapt-1.17.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139", size = 82833, upload-time = "2025-08-12T05:52:27.738Z" }, - { url = "https://files.pythonhosted.org/packages/8d/eb/66579aea6ad36f07617fedca8e282e49c7c9bab64c63b446cfe4f7f47a49/wrapt-1.17.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df", size = 81889, upload-time = "2025-08-12T05:52:29.023Z" }, - { url = "https://files.pythonhosted.org/packages/04/9c/a56b5ac0e2473bdc3fb11b22dd69ff423154d63861cf77911cdde5e38fd2/wrapt-1.17.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b", size = 81344, upload-time = "2025-08-12T05:52:50.869Z" }, - { url = "https://files.pythonhosted.org/packages/93/4c/9bd735c42641d81cb58d7bfb142c58f95c833962d15113026705add41a07/wrapt-1.17.3-cp39-cp39-win32.whl", hash = "sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81", size = 36462, upload-time = "2025-08-12T05:53:19.623Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ea/0b72f29cb5ebc16eb55c57dc0c98e5de76fc97f435fd407f7d409459c0a6/wrapt-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f", size = 38740, upload-time = "2025-08-12T05:53:18.271Z" }, - { url = "https://files.pythonhosted.org/packages/c3/8b/9eae65fb92321e38dbfec7719b87d840a4b92fde83fd1bbf238c5488d055/wrapt-1.17.3-cp39-cp39-win_arm64.whl", hash = "sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f", size = 36806, upload-time = "2025-08-12T05:52:58.765Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] @@ -7708,14 +6066,14 @@ name = "xai-sdk" version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiohttp", marker = "python_full_version >= '3.10'" }, - { name = "googleapis-common-protos", marker = "python_full_version >= '3.10'" }, - { name = "grpcio", marker = "python_full_version >= '3.10'" }, - { name = "opentelemetry-sdk", marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "protobuf", version = "6.33.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "pydantic", marker = "python_full_version >= '3.10'" }, - { name = "requests", marker = "python_full_version >= '3.10'" }, + { name = "aiohttp" }, + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9e/66/1e0163eac090733d0ed0836a0cd3c14f5b59abeaa6fdba71c7b56b1916e4/xai_sdk-1.6.1.tar.gz", hash = "sha256:b55528df188f8c8448484021d735f75b0e7d71719ddeb432c5f187ac67e3c983", size = 388223, upload-time = "2026-01-29T03:13:07.373Z" } wheels = [ @@ -7845,22 +6203,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, - { url = "https://files.pythonhosted.org/packages/94/fd/6480106702a79bcceda5fd9c63cb19a04a6506bd5ce7fd8d9b63742f0021/yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748", size = 141301, upload-time = "2025-10-06T14:12:19.01Z" }, - { url = "https://files.pythonhosted.org/packages/42/e1/6d95d21b17a93e793e4ec420a925fe1f6a9342338ca7a563ed21129c0990/yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859", size = 93864, upload-time = "2025-10-06T14:12:21.05Z" }, - { url = "https://files.pythonhosted.org/packages/32/58/b8055273c203968e89808413ea4c984988b6649baabf10f4522e67c22d2f/yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9", size = 94706, upload-time = "2025-10-06T14:12:23.287Z" }, - { url = "https://files.pythonhosted.org/packages/18/91/d7bfbc28a88c2895ecd0da6a874def0c147de78afc52c773c28e1aa233a3/yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054", size = 347100, upload-time = "2025-10-06T14:12:28.527Z" }, - { url = "https://files.pythonhosted.org/packages/bd/e8/37a1e7b99721c0564b1fc7b0a4d1f595ef6fb8060d82ca61775b644185f7/yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b", size = 318902, upload-time = "2025-10-06T14:12:30.528Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ef/34724449d7ef2db4f22df644f2dac0b8a275d20f585e526937b3ae47b02d/yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60", size = 363302, upload-time = "2025-10-06T14:12:32.295Z" }, - { url = "https://files.pythonhosted.org/packages/8a/04/88a39a5dad39889f192cce8d66cc4c58dbeca983e83f9b6bf23822a7ed91/yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890", size = 370816, upload-time = "2025-10-06T14:12:34.01Z" }, - { url = "https://files.pythonhosted.org/packages/6b/1f/5e895e547129413f56c76be2c3ce4b96c797d2d0ff3e16a817d9269b12e6/yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba", size = 346465, upload-time = "2025-10-06T14:12:35.977Z" }, - { url = "https://files.pythonhosted.org/packages/11/13/a750e9fd6f9cc9ed3a52a70fe58ffe505322f0efe0d48e1fd9ffe53281f5/yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca", size = 341506, upload-time = "2025-10-06T14:12:37.788Z" }, - { url = "https://files.pythonhosted.org/packages/3c/67/bb6024de76e7186611ebe626aec5b71a2d2ecf9453e795f2dbd80614784c/yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba", size = 335030, upload-time = "2025-10-06T14:12:39.775Z" }, - { url = "https://files.pythonhosted.org/packages/a2/be/50b38447fd94a7992996a62b8b463d0579323fcfc08c61bdba949eef8a5d/yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b", size = 358560, upload-time = "2025-10-06T14:12:41.547Z" }, - { url = "https://files.pythonhosted.org/packages/e2/89/c020b6f547578c4e3dbb6335bf918f26e2f34ad0d1e515d72fd33ac0c635/yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e", size = 357290, upload-time = "2025-10-06T14:12:43.861Z" }, - { url = "https://files.pythonhosted.org/packages/8c/52/c49a619ee35a402fa3a7019a4fa8d26878fec0d1243f6968bbf516789578/yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8", size = 350700, upload-time = "2025-10-06T14:12:46.868Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c9/f5042d87777bf6968435f04a2bbb15466b2f142e6e47fa4f34d1a3f32f0c/yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b", size = 82323, upload-time = "2025-10-06T14:12:48.633Z" }, - { url = "https://files.pythonhosted.org/packages/fd/58/d00f7cad9eba20c4eefac2682f34661d1d1b3a942fc0092eb60e78cfb733/yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed", size = 87145, upload-time = "2025-10-06T14:12:50.241Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a3/70904f365080780d38b919edd42d224b8c4ce224a86950d2eaa2a24366ad/yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2", size = 82173, upload-time = "2025-10-06T14:12:51.869Z" }, { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, ] From cc903bd440a4a443510c7b10ef7e9a1f88f44e0e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Feb 2026 18:32:39 +0000 Subject: [PATCH 100/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index be11b7827..e0ca3de72 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Breaking Changes + +* ➖ Drop support for Python 3.9. PR [#14897](https://github.com/fastapi/fastapi/pull/14897) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.8 ### Docs From 3da206c06df1283518a54b18f27bed33f2d22612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 11 Feb 2026 10:41:21 -0800 Subject: [PATCH 101/195] =?UTF-8?q?=F0=9F=8E=A8=20Update=20internal=20type?= =?UTF-8?q?s=20for=20Python=203.10=20(#14898)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/v2.py | 26 +- fastapi/applications.py | 354 +++++++++---------- fastapi/background.py | 3 +- fastapi/datastructures.py | 10 +- fastapi/dependencies/models.py | 32 +- fastapi/encoders.py | 13 +- fastapi/exceptions.py | 14 +- fastapi/openapi/docs.py | 8 +- fastapi/openapi/models.py | 326 ++++++++--------- fastapi/openapi/utils.py | 31 +- fastapi/param_functions.py | 380 ++++++++++---------- fastapi/params.py | 446 ++++++++++++------------ fastapi/routing.py | 412 +++++++++++----------- fastapi/security/api_key.py | 26 +- fastapi/security/http.py | 36 +- fastapi/security/oauth2.py | 42 +-- fastapi/security/open_id_connect_url.py | 8 +- fastapi/security/utils.py | 5 +- fastapi/types.py | 7 +- fastapi/utils.py | 18 +- 20 files changed, 1087 insertions(+), 1110 deletions(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 87b9fb47f..b83bc1b55 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -8,8 +8,11 @@ from functools import lru_cache from typing import ( Annotated, Any, + Literal, Union, cast, + get_args, + get_origin, ) from fastapi._compat import lenient_issubclass, shared @@ -32,7 +35,6 @@ from pydantic_core import Url as Url from pydantic_core.core_schema import ( with_info_plain_validator_function as with_info_plain_validator_function, ) -from typing_extensions import Literal, get_args, get_origin RequiredParam = PydanticUndefined Undefined = PydanticUndefined @@ -83,7 +85,7 @@ class ModelField: field_info: FieldInfo name: str mode: Literal["validation", "serialization"] = "validation" - config: Union[ConfigDict, None] = None + config: ConfigDict | None = None @property def alias(self) -> str: @@ -91,14 +93,14 @@ class ModelField: return a if a is not None else self.name @property - def validation_alias(self) -> Union[str, None]: + def validation_alias(self) -> str | None: va = self.field_info.validation_alias if isinstance(va, str) and va: return va return None @property - def serialization_alias(self) -> Union[str, None]: + def serialization_alias(self) -> str | None: sa = self.field_info.serialization_alias return sa or None @@ -143,7 +145,7 @@ class ModelField: value: Any, values: dict[str, Any] = {}, # noqa: B006 *, - loc: tuple[Union[int, str], ...] = (), + loc: tuple[int | str, ...] = (), ) -> tuple[Any, list[dict[str, Any]]]: try: return ( @@ -160,8 +162,8 @@ class ModelField: value: Any, *, mode: Literal["json", "python"] = "json", - include: Union[IncEx, None] = None, - exclude: Union[IncEx, None] = None, + include: IncEx | None = None, + exclude: IncEx | None = None, by_alias: bool = True, exclude_unset: bool = False, exclude_defaults: bool = False, @@ -202,7 +204,7 @@ def get_schema_from_model_field( ], separate_input_output_schemas: bool = True, ) -> dict[str, Any]: - override_mode: Union[Literal["validation"], None] = ( + override_mode: Literal["validation"] | None = ( None if (separate_input_output_schemas or _has_computed_fields(field)) else "validation" @@ -318,7 +320,7 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return,index] -def get_missing_field_error(loc: tuple[Union[int, str], ...]) -> dict[str, Any]: +def get_missing_field_error(loc: tuple[int | str, ...]) -> dict[str, Any]: error = ValidationError.from_exception_data( "Field required", [{"type": "missing", "loc": loc, "input": {}}] ).errors(include_url=False)[0] @@ -360,7 +362,7 @@ def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]: # Duplicate of several schema functions from Pydantic v1 to make them compatible with # Pydantic v2 and allow mixing the models -TypeModelOrEnum = Union[type["BaseModel"], type[Enum]] +TypeModelOrEnum = type["BaseModel"] | type[Enum] TypeModelSet = set[TypeModelOrEnum] @@ -377,7 +379,7 @@ def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str def get_flat_models_from_model( - model: type["BaseModel"], known_models: Union[TypeModelSet, None] = None + model: type["BaseModel"], known_models: TypeModelSet | None = None ) -> TypeModelSet: known_models = known_models or set() fields = get_model_fields(model) @@ -426,7 +428,7 @@ def get_flat_models_from_fields( def _regenerate_error_with_loc( - *, errors: Sequence[Any], loc_prefix: tuple[Union[str, int], ...] + *, errors: Sequence[Any], loc_prefix: tuple[str | int, ...] ) -> list[dict[str, Any]]: updated_loc_errors: list[Any] = [ {**err, "loc": loc_prefix + err.get("loc", ())} for err in errors diff --git a/fastapi/applications.py b/fastapi/applications.py index 340cabfc2..41d86143e 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -1,12 +1,9 @@ -from collections.abc import Awaitable, Coroutine, Sequence +from collections.abc import Awaitable, Callable, Coroutine, Sequence from enum import Enum from typing import ( Annotated, Any, - Callable, - Optional, TypeVar, - Union, ) from annotated_doc import Doc @@ -77,7 +74,7 @@ class FastAPI(Starlette): ), ] = False, routes: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -120,7 +117,7 @@ class FastAPI(Starlette): ), ] = "FastAPI", summary: Annotated[ - Optional[str], + str | None, Doc( """ A short summary of the API. @@ -203,7 +200,7 @@ class FastAPI(Starlette): ), ] = "0.1.0", openapi_url: Annotated[ - Optional[str], + str | None, Doc( """ The URL where the OpenAPI schema will be served from. @@ -226,7 +223,7 @@ class FastAPI(Starlette): ), ] = "/openapi.json", openapi_tags: Annotated[ - Optional[list[dict[str, Any]]], + list[dict[str, Any]] | None, Doc( """ A list of tags used by OpenAPI, these are the same `tags` you can set @@ -286,7 +283,7 @@ class FastAPI(Starlette): ), ] = None, servers: Annotated[ - Optional[list[dict[str, Union[str, Any]]]], + list[dict[str, str | Any]] | None, Doc( """ A `list` of `dict`s with connectivity information to a target server. @@ -335,7 +332,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of global dependencies, they will be applied to each @@ -402,7 +399,7 @@ class FastAPI(Starlette): ), ] = True, docs_url: Annotated[ - Optional[str], + str | None, Doc( """ The path to the automatic interactive API documentation. @@ -426,7 +423,7 @@ class FastAPI(Starlette): ), ] = "/docs", redoc_url: Annotated[ - Optional[str], + str | None, Doc( """ The path to the alternative automatic interactive API documentation @@ -450,7 +447,7 @@ class FastAPI(Starlette): ), ] = "/redoc", swagger_ui_oauth2_redirect_url: Annotated[ - Optional[str], + str | None, Doc( """ The OAuth2 redirect endpoint for the Swagger UI. @@ -463,7 +460,7 @@ class FastAPI(Starlette): ), ] = "/docs/oauth2-redirect", swagger_ui_init_oauth: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ OAuth2 configuration for the Swagger UI, by default shown at `/docs`. @@ -474,7 +471,7 @@ class FastAPI(Starlette): ), ] = None, middleware: Annotated[ - Optional[Sequence[Middleware]], + Sequence[Middleware] | None, Doc( """ List of middleware to be added when creating the application. @@ -488,12 +485,11 @@ class FastAPI(Starlette): ), ] = None, exception_handlers: Annotated[ - Optional[ - dict[ - Union[int, type[Exception]], - Callable[[Request, Any], Coroutine[Any, Any, Response]], - ] - ], + dict[ + int | type[Exception], + Callable[[Request, Any], Coroutine[Any, Any, Response]], + ] + | None, Doc( """ A dictionary with handlers for exceptions. @@ -507,7 +503,7 @@ class FastAPI(Starlette): ), ] = None, on_startup: Annotated[ - Optional[Sequence[Callable[[], Any]]], + Sequence[Callable[[], Any]] | None, Doc( """ A list of startup event handler functions. @@ -519,7 +515,7 @@ class FastAPI(Starlette): ), ] = None, on_shutdown: Annotated[ - Optional[Sequence[Callable[[], Any]]], + Sequence[Callable[[], Any]] | None, Doc( """ A list of shutdown event handler functions. @@ -532,7 +528,7 @@ class FastAPI(Starlette): ), ] = None, lifespan: Annotated[ - Optional[Lifespan[AppType]], + Lifespan[AppType] | None, Doc( """ A `Lifespan` context manager handler. This replaces `startup` and @@ -544,7 +540,7 @@ class FastAPI(Starlette): ), ] = None, terms_of_service: Annotated[ - Optional[str], + str | None, Doc( """ A URL to the Terms of Service for your API. @@ -563,7 +559,7 @@ class FastAPI(Starlette): ), ] = None, contact: Annotated[ - Optional[dict[str, Union[str, Any]]], + dict[str, str | Any] | None, Doc( """ A dictionary with the contact information for the exposed API. @@ -596,7 +592,7 @@ class FastAPI(Starlette): ), ] = None, license_info: Annotated[ - Optional[dict[str, Union[str, Any]]], + dict[str, str | Any] | None, Doc( """ A dictionary with the license information for the exposed API. @@ -685,7 +681,7 @@ class FastAPI(Starlette): ), ] = True, responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses to be shown in OpenAPI. @@ -701,7 +697,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ OpenAPI callbacks that should apply to all *path operations*. @@ -714,7 +710,7 @@ class FastAPI(Starlette): ), ] = None, webhooks: Annotated[ - Optional[routing.APIRouter], + routing.APIRouter | None, Doc( """ Add OpenAPI webhooks. This is similar to `callbacks` but it doesn't @@ -730,7 +726,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark all *path operations* as deprecated. You probably don't need it, @@ -758,7 +754,7 @@ class FastAPI(Starlette): ), ] = True, swagger_ui_parameters: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Parameters to configure Swagger UI, the autogenerated interactive API @@ -819,7 +815,7 @@ class FastAPI(Starlette): ), ] = True, openapi_external_docs: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ This field allows you to provide additional external documentation links. @@ -905,7 +901,7 @@ class FastAPI(Starlette): """ ), ] = "3.1.0" - self.openapi_schema: Optional[dict[str, Any]] = None + self.openapi_schema: dict[str, Any] | None = None if self.openapi_url: assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'" assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'" @@ -980,7 +976,7 @@ class FastAPI(Starlette): generate_unique_id_function=generate_unique_id_function, ) self.exception_handlers: dict[ - Any, Callable[[Request, Any], Union[Response, Awaitable[Response]]] + Any, Callable[[Request, Any], Response | Awaitable[Response]] ] = {} if exception_handlers is None else dict(exception_handlers) self.exception_handlers.setdefault(HTTPException, http_exception_handler) self.exception_handlers.setdefault( @@ -995,7 +991,7 @@ class FastAPI(Starlette): self.user_middleware: list[Middleware] = ( [] if middleware is None else list(middleware) ) - self.middleware_stack: Union[ASGIApp, None] = None + self.middleware_stack: ASGIApp | None = None self.setup() def build_middleware_stack(self) -> ASGIApp: @@ -1143,28 +1139,26 @@ class FastAPI(Starlette): endpoint: Callable[..., Any], *, response_model: Any = Default(None), - status_code: Optional[int] = None, - tags: Optional[list[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[Depends]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, + status_code: int | None = None, + tags: list[str | Enum] | None = None, + dependencies: Sequence[Depends] | None = None, + summary: str | None = None, + description: str | None = None, response_description: str = "Successful Response", - responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, - deprecated: Optional[bool] = None, - methods: Optional[list[str]] = None, - operation_id: Optional[str] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + responses: dict[int | str, dict[str, Any]] | None = None, + deprecated: bool | None = None, + methods: list[str] | None = None, + operation_id: str | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Union[type[Response], DefaultPlaceholder] = Default( - JSONResponse - ), - name: Optional[str] = None, - openapi_extra: Optional[dict[str, Any]] = None, + response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), + name: str | None = None, + openapi_extra: dict[str, Any] | None = None, generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), @@ -1201,26 +1195,26 @@ class FastAPI(Starlette): path: str, *, response_model: Any = Default(None), - status_code: Optional[int] = None, - tags: Optional[list[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[Depends]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, + status_code: int | None = None, + tags: list[str | Enum] | None = None, + dependencies: Sequence[Depends] | None = None, + summary: str | None = None, + description: str | None = None, response_description: str = "Successful Response", - responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, - deprecated: Optional[bool] = None, - methods: Optional[list[str]] = None, - operation_id: Optional[str] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + responses: dict[int | str, dict[str, Any]] | None = None, + deprecated: bool | None = None, + methods: list[str] | None = None, + operation_id: str | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, response_class: type[Response] = Default(JSONResponse), - name: Optional[str] = None, - openapi_extra: Optional[dict[str, Any]] = None, + name: str | None = None, + openapi_extra: dict[str, Any] | None = None, generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), @@ -1260,9 +1254,9 @@ class FastAPI(Starlette): self, path: str, endpoint: Callable[..., Any], - name: Optional[str] = None, + name: str | None = None, *, - dependencies: Optional[Sequence[Depends]] = None, + dependencies: Sequence[Depends] | None = None, ) -> None: self.router.add_api_websocket_route( path, @@ -1282,7 +1276,7 @@ class FastAPI(Starlette): ), ], name: Annotated[ - Optional[str], + str | None, Doc( """ A name for the WebSocket. Only used internally. @@ -1291,7 +1285,7 @@ class FastAPI(Starlette): ] = None, *, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be used for this @@ -1342,7 +1336,7 @@ class FastAPI(Starlette): *, prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to all the *path operations* in this @@ -1356,7 +1350,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to all the @@ -1384,7 +1378,7 @@ class FastAPI(Starlette): ), ] = None, responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses to be shown in OpenAPI. @@ -1400,7 +1394,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark all the *path operations* in this router as deprecated. @@ -1479,7 +1473,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -1589,7 +1583,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -1602,7 +1596,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -1615,7 +1609,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -1627,7 +1621,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -1640,7 +1634,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -1668,7 +1662,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -1678,7 +1672,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -1688,7 +1682,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -1708,7 +1702,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -1720,7 +1714,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -1822,7 +1816,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -1830,7 +1824,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -1846,7 +1840,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -1962,7 +1956,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -1975,7 +1969,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -1988,7 +1982,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2000,7 +1994,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -2013,7 +2007,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2041,7 +2035,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2051,7 +2045,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2061,7 +2055,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2081,7 +2075,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2093,7 +2087,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2195,7 +2189,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2203,7 +2197,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2219,7 +2213,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2340,7 +2334,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -2353,7 +2347,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -2366,7 +2360,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2378,7 +2372,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -2391,7 +2385,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2419,7 +2413,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2429,7 +2423,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2439,7 +2433,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2459,7 +2453,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2471,7 +2465,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2573,7 +2567,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2581,7 +2575,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2597,7 +2591,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2718,7 +2712,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -2731,7 +2725,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -2744,7 +2738,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2756,7 +2750,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -2769,7 +2763,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2797,7 +2791,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2807,7 +2801,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2817,7 +2811,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2837,7 +2831,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2849,7 +2843,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2951,7 +2945,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2959,7 +2953,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2975,7 +2969,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3091,7 +3085,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3104,7 +3098,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3117,7 +3111,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3129,7 +3123,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3142,7 +3136,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3170,7 +3164,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3180,7 +3174,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3190,7 +3184,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3210,7 +3204,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3222,7 +3216,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -3324,7 +3318,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -3332,7 +3326,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3348,7 +3342,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3464,7 +3458,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3477,7 +3471,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3490,7 +3484,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3502,7 +3496,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3515,7 +3509,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3543,7 +3537,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3553,7 +3547,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3563,7 +3557,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3583,7 +3577,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3595,7 +3589,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -3697,7 +3691,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -3705,7 +3699,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3721,7 +3715,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3837,7 +3831,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3850,7 +3844,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3863,7 +3857,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3875,7 +3869,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3888,7 +3882,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3916,7 +3910,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3926,7 +3920,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3936,7 +3930,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3956,7 +3950,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3968,7 +3962,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -4070,7 +4064,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -4078,7 +4072,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4094,7 +4088,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -4215,7 +4209,7 @@ class FastAPI(Starlette): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -4228,7 +4222,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -4241,7 +4235,7 @@ class FastAPI(Starlette): ), ] = None, dependencies: Annotated[ - Optional[Sequence[Depends]], + Sequence[Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -4253,7 +4247,7 @@ class FastAPI(Starlette): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -4266,7 +4260,7 @@ class FastAPI(Starlette): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -4294,7 +4288,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -4304,7 +4298,7 @@ class FastAPI(Starlette): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -4314,7 +4308,7 @@ class FastAPI(Starlette): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -4334,7 +4328,7 @@ class FastAPI(Starlette): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -4346,7 +4340,7 @@ class FastAPI(Starlette): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -4448,7 +4442,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -4456,7 +4450,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4472,7 +4466,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -4541,7 +4535,7 @@ class FastAPI(Starlette): ) def websocket_route( - self, path: str, name: Union[str, None] = None + self, path: str, name: str | None = None ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.router.add_websocket_route(path, func, name=name) @@ -4627,7 +4621,7 @@ class FastAPI(Starlette): def exception_handler( self, exc_class_or_status_code: Annotated[ - Union[int, type[Exception]], + int | type[Exception], Doc( """ The Exception class this would handle, or a status code. diff --git a/fastapi/background.py b/fastapi/background.py index 20803ba67..7677058c4 100644 --- a/fastapi/background.py +++ b/fastapi/background.py @@ -1,4 +1,5 @@ -from typing import Annotated, Any, Callable +from collections.abc import Callable +from typing import Annotated, Any from annotated_doc import Doc from starlette.background import BackgroundTasks as StarletteBackgroundTasks diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index 2bf5fdb26..c04b5f0f3 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -1,10 +1,8 @@ -from collections.abc import Mapping +from collections.abc import Callable, Mapping from typing import ( Annotated, Any, BinaryIO, - Callable, - Optional, TypeVar, cast, ) @@ -58,11 +56,11 @@ class UploadFile(StarletteUploadFile): BinaryIO, Doc("The standard Python file object (non-async)."), ] - filename: Annotated[Optional[str], Doc("The original file name.")] - size: Annotated[Optional[int], Doc("The size of the file in bytes.")] + filename: Annotated[str | None, Doc("The original file name.")] + size: Annotated[int | None, Doc("The size of the file in bytes.")] headers: Annotated[Headers, Doc("The headers of the request.")] content_type: Annotated[ - Optional[str], Doc("The content type of the request, from the headers.") + str | None, Doc("The content type of the request, from the headers.") ] async def write( diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 58392326d..25ffb0d2d 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -1,13 +1,13 @@ import inspect import sys +from collections.abc import Callable from dataclasses import dataclass, field from functools import cached_property, partial -from typing import Any, Callable, Optional, Union +from typing import Any, Literal from fastapi._compat import ModelField from fastapi.security.base import SecurityBase from fastapi.types import DependencyCacheKey -from typing_extensions import Literal if sys.version_info >= (3, 13): # pragma: no cover from inspect import iscoroutinefunction @@ -15,7 +15,7 @@ else: # pragma: no cover from asyncio import iscoroutinefunction -def _unwrapped_call(call: Optional[Callable[..., Any]]) -> Any: +def _unwrapped_call(call: Callable[..., Any] | None) -> Any: if call is None: return call # pragma: no cover unwrapped = inspect.unwrap(_impartial(call)) @@ -36,19 +36,19 @@ class Dependant: cookie_params: list[ModelField] = field(default_factory=list) body_params: list[ModelField] = field(default_factory=list) dependencies: list["Dependant"] = field(default_factory=list) - name: Optional[str] = None - call: Optional[Callable[..., Any]] = None - request_param_name: Optional[str] = None - websocket_param_name: Optional[str] = None - http_connection_param_name: Optional[str] = None - response_param_name: Optional[str] = None - background_tasks_param_name: Optional[str] = None - security_scopes_param_name: Optional[str] = None - own_oauth_scopes: Optional[list[str]] = None - parent_oauth_scopes: Optional[list[str]] = None + name: str | None = None + call: Callable[..., Any] | None = None + request_param_name: str | None = None + websocket_param_name: str | None = None + http_connection_param_name: str | None = None + response_param_name: str | None = None + background_tasks_param_name: str | None = None + security_scopes_param_name: str | None = None + own_oauth_scopes: list[str] | None = None + parent_oauth_scopes: list[str] | None = None use_cache: bool = True - path: Optional[str] = None - scope: Union[Literal["function", "request"], None] = None + path: str | None = None + scope: Literal["function", "request"] | None = None @cached_property def oauth_scopes(self) -> list[str]: @@ -185,7 +185,7 @@ class Dependant: return False @cached_property - def computed_scope(self) -> Union[str, None]: + def computed_scope(self) -> str | None: if self.scope: return self.scope if self.is_gen_callable or self.is_async_gen_callable: diff --git a/fastapi/encoders.py b/fastapi/encoders.py index b4661be4f..e20255c11 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -1,6 +1,7 @@ import dataclasses import datetime from collections import defaultdict, deque +from collections.abc import Callable from decimal import Decimal from enum import Enum from ipaddress import ( @@ -14,7 +15,7 @@ from ipaddress import ( from pathlib import Path, PurePath from re import Pattern from types import GeneratorType -from typing import Annotated, Any, Callable, Optional, Union +from typing import Annotated, Any from uuid import UUID from annotated_doc import Doc @@ -33,13 +34,13 @@ from ._compat import ( # Taken from Pydantic v1 as is -def isoformat(o: Union[datetime.date, datetime.time]) -> str: +def isoformat(o: datetime.date | datetime.time) -> str: return o.isoformat() # Adapted from Pydantic v1 # TODO: pv2 should this return strings instead? -def decimal_encoder(dec_value: Decimal) -> Union[int, float]: +def decimal_encoder(dec_value: Decimal) -> int | float: """ Encodes a Decimal as int if there's no exponent, otherwise float @@ -118,7 +119,7 @@ def jsonable_encoder( ), ], include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Pydantic's `include` parameter, passed to Pydantic models to set the @@ -127,7 +128,7 @@ def jsonable_encoder( ), ] = None, exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Pydantic's `exclude` parameter, passed to Pydantic models to set the @@ -177,7 +178,7 @@ def jsonable_encoder( ), ] = False, custom_encoder: Annotated[ - Optional[dict[Any, Callable[[Any], Any]]], + dict[Any, Callable[[Any], Any]] | None, Doc( """ Pydantic's `custom_encoder` parameter, passed to Pydantic models to define diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index 9924d0e2b..d7065c52f 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -1,5 +1,5 @@ from collections.abc import Mapping, Sequence -from typing import Annotated, Any, Optional, TypedDict, Union +from typing import Annotated, Any, TypedDict from annotated_doc import Doc from pydantic import BaseModel, create_model @@ -68,7 +68,7 @@ class HTTPException(StarletteHTTPException): ), ] = None, headers: Annotated[ - Optional[Mapping[str, str]], + Mapping[str, str] | None, Doc( """ Any headers to send to the client in the response. @@ -137,7 +137,7 @@ class WebSocketException(StarletteWebSocketException): ), ], reason: Annotated[ - Union[str, None], + str | None, Doc( """ The reason to close the WebSocket connection. @@ -176,7 +176,7 @@ class ValidationException(Exception): self, errors: Sequence[Any], *, - endpoint_ctx: Optional[EndpointContext] = None, + endpoint_ctx: EndpointContext | None = None, ) -> None: self._errors = errors self.endpoint_ctx = endpoint_ctx @@ -215,7 +215,7 @@ class RequestValidationError(ValidationException): errors: Sequence[Any], *, body: Any = None, - endpoint_ctx: Optional[EndpointContext] = None, + endpoint_ctx: EndpointContext | None = None, ) -> None: super().__init__(errors, endpoint_ctx=endpoint_ctx) self.body = body @@ -226,7 +226,7 @@ class WebSocketRequestValidationError(ValidationException): self, errors: Sequence[Any], *, - endpoint_ctx: Optional[EndpointContext] = None, + endpoint_ctx: EndpointContext | None = None, ) -> None: super().__init__(errors, endpoint_ctx=endpoint_ctx) @@ -237,7 +237,7 @@ class ResponseValidationError(ValidationException): errors: Sequence[Any], *, body: Any = None, - endpoint_ctx: Optional[EndpointContext] = None, + endpoint_ctx: EndpointContext | None = None, ) -> None: super().__init__(errors, endpoint_ctx=endpoint_ctx) self.body = body diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py index bb387c609..b845f87c1 100644 --- a/fastapi/openapi/docs.py +++ b/fastapi/openapi/docs.py @@ -1,5 +1,5 @@ import json -from typing import Annotated, Any, Optional +from typing import Annotated, Any from annotated_doc import Doc from fastapi.encoders import jsonable_encoder @@ -85,7 +85,7 @@ def get_swagger_ui_html( ), ] = "https://fastapi.tiangolo.com/img/favicon.png", oauth2_redirect_url: Annotated[ - Optional[str], + str | None, Doc( """ The OAuth2 redirect URL, it is normally automatically handled by FastAPI. @@ -96,7 +96,7 @@ def get_swagger_ui_html( ), ] = None, init_oauth: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ A dictionary with Swagger UI OAuth2 initialization configurations. @@ -107,7 +107,7 @@ def get_swagger_ui_html( ), ] = None, swagger_ui_parameters: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Configuration parameters for Swagger UI. diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index 095990639..d7950241f 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -1,6 +1,6 @@ -from collections.abc import Iterable, Mapping +from collections.abc import Callable, Iterable, Mapping from enum import Enum -from typing import Annotated, Any, Callable, Optional, Union +from typing import Annotated, Any, Literal, Optional, Union from fastapi._compat import with_info_plain_validator_function from fastapi.logger import logger @@ -10,7 +10,7 @@ from pydantic import ( Field, GetJsonSchemaHandler, ) -from typing_extensions import Literal, TypedDict +from typing_extensions import TypedDict from typing_extensions import deprecated as typing_deprecated try: @@ -59,37 +59,37 @@ class BaseModelWithConfig(BaseModel): class Contact(BaseModelWithConfig): - name: Optional[str] = None - url: Optional[AnyUrl] = None - email: Optional[EmailStr] = None + name: str | None = None + url: AnyUrl | None = None + email: EmailStr | None = None class License(BaseModelWithConfig): name: str - identifier: Optional[str] = None - url: Optional[AnyUrl] = None + identifier: str | None = None + url: AnyUrl | None = None class Info(BaseModelWithConfig): title: str - summary: Optional[str] = None - description: Optional[str] = None - termsOfService: Optional[str] = None - contact: Optional[Contact] = None - license: Optional[License] = None + summary: str | None = None + description: str | None = None + termsOfService: str | None = None + contact: Contact | None = None + license: License | None = None version: str class ServerVariable(BaseModelWithConfig): - enum: Annotated[Optional[list[str]], Field(min_length=1)] = None + enum: Annotated[list[str] | None, Field(min_length=1)] = None default: str - description: Optional[str] = None + description: str | None = None class Server(BaseModelWithConfig): - url: Union[AnyUrl, str] - description: Optional[str] = None - variables: Optional[dict[str, ServerVariable]] = None + url: AnyUrl | str + description: str | None = None + variables: dict[str, ServerVariable] | None = None class Reference(BaseModel): @@ -98,19 +98,19 @@ class Reference(BaseModel): class Discriminator(BaseModel): propertyName: str - mapping: Optional[dict[str, str]] = None + mapping: dict[str, str] | None = None class XML(BaseModelWithConfig): - name: Optional[str] = None - namespace: Optional[str] = None - prefix: Optional[str] = None - attribute: Optional[bool] = None - wrapped: Optional[bool] = None + name: str | None = None + namespace: str | None = None + prefix: str | None = None + attribute: bool | None = None + wrapped: bool | None = None class ExternalDocumentation(BaseModelWithConfig): - description: Optional[str] = None + description: str | None = None url: AnyUrl @@ -123,80 +123,80 @@ SchemaType = Literal[ class Schema(BaseModelWithConfig): # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu # Core Vocabulary - schema_: Optional[str] = Field(default=None, alias="$schema") - vocabulary: Optional[str] = Field(default=None, alias="$vocabulary") - id: Optional[str] = Field(default=None, alias="$id") - anchor: Optional[str] = Field(default=None, alias="$anchor") - dynamicAnchor: Optional[str] = Field(default=None, alias="$dynamicAnchor") - ref: Optional[str] = Field(default=None, alias="$ref") - dynamicRef: Optional[str] = Field(default=None, alias="$dynamicRef") - defs: Optional[dict[str, "SchemaOrBool"]] = Field(default=None, alias="$defs") - comment: Optional[str] = Field(default=None, alias="$comment") + schema_: str | None = Field(default=None, alias="$schema") + vocabulary: str | None = Field(default=None, alias="$vocabulary") + id: str | None = Field(default=None, alias="$id") + anchor: str | None = Field(default=None, alias="$anchor") + dynamicAnchor: str | None = Field(default=None, alias="$dynamicAnchor") + ref: str | None = Field(default=None, alias="$ref") + dynamicRef: str | None = Field(default=None, alias="$dynamicRef") + defs: dict[str, "SchemaOrBool"] | None = Field(default=None, alias="$defs") + comment: str | None = Field(default=None, alias="$comment") # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s # A Vocabulary for Applying Subschemas - allOf: Optional[list["SchemaOrBool"]] = None - anyOf: Optional[list["SchemaOrBool"]] = None - oneOf: Optional[list["SchemaOrBool"]] = None + allOf: list["SchemaOrBool"] | None = None + anyOf: list["SchemaOrBool"] | None = None + oneOf: list["SchemaOrBool"] | None = None not_: Optional["SchemaOrBool"] = Field(default=None, alias="not") if_: Optional["SchemaOrBool"] = Field(default=None, alias="if") then: Optional["SchemaOrBool"] = None else_: Optional["SchemaOrBool"] = Field(default=None, alias="else") - dependentSchemas: Optional[dict[str, "SchemaOrBool"]] = None - prefixItems: Optional[list["SchemaOrBool"]] = None + dependentSchemas: dict[str, "SchemaOrBool"] | None = None + prefixItems: list["SchemaOrBool"] | None = None items: Optional["SchemaOrBool"] = None contains: Optional["SchemaOrBool"] = None - properties: Optional[dict[str, "SchemaOrBool"]] = None - patternProperties: Optional[dict[str, "SchemaOrBool"]] = None + properties: dict[str, "SchemaOrBool"] | None = None + patternProperties: dict[str, "SchemaOrBool"] | None = None additionalProperties: Optional["SchemaOrBool"] = None propertyNames: Optional["SchemaOrBool"] = None unevaluatedItems: Optional["SchemaOrBool"] = None unevaluatedProperties: Optional["SchemaOrBool"] = None # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural # A Vocabulary for Structural Validation - type: Optional[Union[SchemaType, list[SchemaType]]] = None - enum: Optional[list[Any]] = None - const: Optional[Any] = None - multipleOf: Optional[float] = Field(default=None, gt=0) - maximum: Optional[float] = None - exclusiveMaximum: Optional[float] = None - minimum: Optional[float] = None - exclusiveMinimum: Optional[float] = None - maxLength: Optional[int] = Field(default=None, ge=0) - minLength: Optional[int] = Field(default=None, ge=0) - pattern: Optional[str] = None - maxItems: Optional[int] = Field(default=None, ge=0) - minItems: Optional[int] = Field(default=None, ge=0) - uniqueItems: Optional[bool] = None - maxContains: Optional[int] = Field(default=None, ge=0) - minContains: Optional[int] = Field(default=None, ge=0) - maxProperties: Optional[int] = Field(default=None, ge=0) - minProperties: Optional[int] = Field(default=None, ge=0) - required: Optional[list[str]] = None - dependentRequired: Optional[dict[str, set[str]]] = None + type: SchemaType | list[SchemaType] | None = None + enum: list[Any] | None = None + const: Any | None = None + multipleOf: float | None = Field(default=None, gt=0) + maximum: float | None = None + exclusiveMaximum: float | None = None + minimum: float | None = None + exclusiveMinimum: float | None = None + maxLength: int | None = Field(default=None, ge=0) + minLength: int | None = Field(default=None, ge=0) + pattern: str | None = None + maxItems: int | None = Field(default=None, ge=0) + minItems: int | None = Field(default=None, ge=0) + uniqueItems: bool | None = None + maxContains: int | None = Field(default=None, ge=0) + minContains: int | None = Field(default=None, ge=0) + maxProperties: int | None = Field(default=None, ge=0) + minProperties: int | None = Field(default=None, ge=0) + required: list[str] | None = None + dependentRequired: dict[str, set[str]] | None = None # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c # Vocabularies for Semantic Content With "format" - format: Optional[str] = None + format: str | None = None # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten # A Vocabulary for the Contents of String-Encoded Data - contentEncoding: Optional[str] = None - contentMediaType: Optional[str] = None + contentEncoding: str | None = None + contentMediaType: str | None = None contentSchema: Optional["SchemaOrBool"] = None # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta # A Vocabulary for Basic Meta-Data Annotations - title: Optional[str] = None - description: Optional[str] = None - default: Optional[Any] = None - deprecated: Optional[bool] = None - readOnly: Optional[bool] = None - writeOnly: Optional[bool] = None - examples: Optional[list[Any]] = None + title: str | None = None + description: str | None = None + default: Any | None = None + deprecated: bool | None = None + readOnly: bool | None = None + writeOnly: bool | None = None + examples: list[Any] | None = None # Ref: OpenAPI 3.1.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object # Schema Object - discriminator: Optional[Discriminator] = None - xml: Optional[XML] = None - externalDocs: Optional[ExternalDocumentation] = None + discriminator: Discriminator | None = None + xml: XML | None = None + externalDocs: ExternalDocumentation | None = None example: Annotated[ - Optional[Any], + Any | None, typing_deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." @@ -206,14 +206,14 @@ class Schema(BaseModelWithConfig): # Ref: https://json-schema.org/draft/2020-12/json-schema-core.html#name-json-schema-documents # A JSON Schema MUST be an object or a boolean. -SchemaOrBool = Union[Schema, bool] +SchemaOrBool = Schema | bool class Example(TypedDict, total=False): - summary: Optional[str] - description: Optional[str] - value: Optional[Any] - externalValue: Optional[AnyUrl] + summary: str | None + description: str | None + value: Any | None + externalValue: AnyUrl | None __pydantic_config__ = {"extra": "allow"} # type: ignore[misc] @@ -226,33 +226,33 @@ class ParameterInType(Enum): class Encoding(BaseModelWithConfig): - contentType: Optional[str] = None - headers: Optional[dict[str, Union["Header", Reference]]] = None - style: Optional[str] = None - explode: Optional[bool] = None - allowReserved: Optional[bool] = None + contentType: str | None = None + headers: dict[str, Union["Header", Reference]] | None = None + style: str | None = None + explode: bool | None = None + allowReserved: bool | None = None class MediaType(BaseModelWithConfig): - schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") - example: Optional[Any] = None - examples: Optional[dict[str, Union[Example, Reference]]] = None - encoding: Optional[dict[str, Encoding]] = None + schema_: Schema | Reference | None = Field(default=None, alias="schema") + example: Any | None = None + examples: dict[str, Example | Reference] | None = None + encoding: dict[str, Encoding] | None = None class ParameterBase(BaseModelWithConfig): - description: Optional[str] = None - required: Optional[bool] = None - deprecated: Optional[bool] = None + description: str | None = None + required: bool | None = None + deprecated: bool | None = None # Serialization rules for simple scenarios - style: Optional[str] = None - explode: Optional[bool] = None - allowReserved: Optional[bool] = None - schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") - example: Optional[Any] = None - examples: Optional[dict[str, Union[Example, Reference]]] = None + style: str | None = None + explode: bool | None = None + allowReserved: bool | None = None + schema_: Schema | Reference | None = Field(default=None, alias="schema") + example: Any | None = None + examples: dict[str, Example | Reference] | None = None # Serialization rules for more complex scenarios - content: Optional[dict[str, MediaType]] = None + content: dict[str, MediaType] | None = None class Parameter(ParameterBase): @@ -265,57 +265,57 @@ class Header(ParameterBase): class RequestBody(BaseModelWithConfig): - description: Optional[str] = None + description: str | None = None content: dict[str, MediaType] - required: Optional[bool] = None + required: bool | None = None class Link(BaseModelWithConfig): - operationRef: Optional[str] = None - operationId: Optional[str] = None - parameters: Optional[dict[str, Union[Any, str]]] = None - requestBody: Optional[Union[Any, str]] = None - description: Optional[str] = None - server: Optional[Server] = None + operationRef: str | None = None + operationId: str | None = None + parameters: dict[str, Any | str] | None = None + requestBody: Any | str | None = None + description: str | None = None + server: Server | None = None class Response(BaseModelWithConfig): description: str - headers: Optional[dict[str, Union[Header, Reference]]] = None - content: Optional[dict[str, MediaType]] = None - links: Optional[dict[str, Union[Link, Reference]]] = None + headers: dict[str, Header | Reference] | None = None + content: dict[str, MediaType] | None = None + links: dict[str, Link | Reference] | None = None class Operation(BaseModelWithConfig): - tags: Optional[list[str]] = None - summary: Optional[str] = None - description: Optional[str] = None - externalDocs: Optional[ExternalDocumentation] = None - operationId: Optional[str] = None - parameters: Optional[list[Union[Parameter, Reference]]] = None - requestBody: Optional[Union[RequestBody, Reference]] = None + tags: list[str] | None = None + summary: str | None = None + description: str | None = None + externalDocs: ExternalDocumentation | None = None + operationId: str | None = None + parameters: list[Parameter | Reference] | None = None + requestBody: RequestBody | Reference | None = None # Using Any for Specification Extensions - responses: Optional[dict[str, Union[Response, Any]]] = None - callbacks: Optional[dict[str, Union[dict[str, "PathItem"], Reference]]] = None - deprecated: Optional[bool] = None - security: Optional[list[dict[str, list[str]]]] = None - servers: Optional[list[Server]] = None + responses: dict[str, Response | Any] | None = None + callbacks: dict[str, dict[str, "PathItem"] | Reference] | None = None + deprecated: bool | None = None + security: list[dict[str, list[str]]] | None = None + servers: list[Server] | None = None class PathItem(BaseModelWithConfig): - ref: Optional[str] = Field(default=None, alias="$ref") - summary: Optional[str] = None - description: Optional[str] = None - get: Optional[Operation] = None - put: Optional[Operation] = None - post: Optional[Operation] = None - delete: Optional[Operation] = None - options: Optional[Operation] = None - head: Optional[Operation] = None - patch: Optional[Operation] = None - trace: Optional[Operation] = None - servers: Optional[list[Server]] = None - parameters: Optional[list[Union[Parameter, Reference]]] = None + ref: str | None = Field(default=None, alias="$ref") + summary: str | None = None + description: str | None = None + get: Operation | None = None + put: Operation | None = None + post: Operation | None = None + delete: Operation | None = None + options: Operation | None = None + head: Operation | None = None + patch: Operation | None = None + trace: Operation | None = None + servers: list[Server] | None = None + parameters: list[Parameter | Reference] | None = None class SecuritySchemeType(Enum): @@ -327,7 +327,7 @@ class SecuritySchemeType(Enum): class SecurityBase(BaseModelWithConfig): type_: SecuritySchemeType = Field(alias="type") - description: Optional[str] = None + description: str | None = None class APIKeyIn(Enum): @@ -349,11 +349,11 @@ class HTTPBase(SecurityBase): class HTTPBearer(HTTPBase): scheme: Literal["bearer"] = "bearer" - bearerFormat: Optional[str] = None + bearerFormat: str | None = None class OAuthFlow(BaseModelWithConfig): - refreshUrl: Optional[str] = None + refreshUrl: str | None = None scopes: dict[str, str] = {} @@ -375,10 +375,10 @@ class OAuthFlowAuthorizationCode(OAuthFlow): class OAuthFlows(BaseModelWithConfig): - implicit: Optional[OAuthFlowImplicit] = None - password: Optional[OAuthFlowPassword] = None - clientCredentials: Optional[OAuthFlowClientCredentials] = None - authorizationCode: Optional[OAuthFlowAuthorizationCode] = None + implicit: OAuthFlowImplicit | None = None + password: OAuthFlowPassword | None = None + clientCredentials: OAuthFlowClientCredentials | None = None + authorizationCode: OAuthFlowAuthorizationCode | None = None class OAuth2(SecurityBase): @@ -393,41 +393,41 @@ class OpenIdConnect(SecurityBase): openIdConnectUrl: str -SecurityScheme = Union[APIKey, HTTPBase, OAuth2, OpenIdConnect, HTTPBearer] +SecurityScheme = APIKey | HTTPBase | OAuth2 | OpenIdConnect | HTTPBearer class Components(BaseModelWithConfig): - schemas: Optional[dict[str, Union[Schema, Reference]]] = None - responses: Optional[dict[str, Union[Response, Reference]]] = None - parameters: Optional[dict[str, Union[Parameter, Reference]]] = None - examples: Optional[dict[str, Union[Example, Reference]]] = None - requestBodies: Optional[dict[str, Union[RequestBody, Reference]]] = None - headers: Optional[dict[str, Union[Header, Reference]]] = None - securitySchemes: Optional[dict[str, Union[SecurityScheme, Reference]]] = None - links: Optional[dict[str, Union[Link, Reference]]] = None + schemas: dict[str, Schema | Reference] | None = None + responses: dict[str, Response | Reference] | None = None + parameters: dict[str, Parameter | Reference] | None = None + examples: dict[str, Example | Reference] | None = None + requestBodies: dict[str, RequestBody | Reference] | None = None + headers: dict[str, Header | Reference] | None = None + securitySchemes: dict[str, SecurityScheme | Reference] | None = None + links: dict[str, Link | Reference] | None = None # Using Any for Specification Extensions - callbacks: Optional[dict[str, Union[dict[str, PathItem], Reference, Any]]] = None - pathItems: Optional[dict[str, Union[PathItem, Reference]]] = None + callbacks: dict[str, dict[str, PathItem] | Reference | Any] | None = None + pathItems: dict[str, PathItem | Reference] | None = None class Tag(BaseModelWithConfig): name: str - description: Optional[str] = None - externalDocs: Optional[ExternalDocumentation] = None + description: str | None = None + externalDocs: ExternalDocumentation | None = None class OpenAPI(BaseModelWithConfig): openapi: str info: Info - jsonSchemaDialect: Optional[str] = None - servers: Optional[list[Server]] = None + jsonSchemaDialect: str | None = None + servers: list[Server] | None = None # Using Any for Specification Extensions - paths: Optional[dict[str, Union[PathItem, Any]]] = None - webhooks: Optional[dict[str, Union[PathItem, Reference]]] = None - components: Optional[Components] = None - security: Optional[list[dict[str, list[str]]]] = None - tags: Optional[list[Tag]] = None - externalDocs: Optional[ExternalDocumentation] = None + paths: dict[str, PathItem | Any] | None = None + webhooks: dict[str, PathItem | Reference] | None = None + components: Components | None = None + security: list[dict[str, list[str]]] | None = None + tags: list[Tag] | None = None + externalDocs: ExternalDocumentation | None = None Schema.model_rebuild() diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index bcad0be75..812003aee 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -3,7 +3,7 @@ import http.client import inspect import warnings from collections.abc import Sequence -from typing import Any, Optional, Union, cast +from typing import Any, Literal, cast from fastapi import routing from fastapi._compat import ( @@ -38,7 +38,6 @@ from fastapi.utils import ( from pydantic import BaseModel from starlette.responses import JSONResponse from starlette.routing import BaseRoute -from typing_extensions import Literal validation_error_definition = { "title": "ValidationError", @@ -180,13 +179,13 @@ def _get_openapi_operation_parameters( def get_openapi_operation_request_body( *, - body_field: Optional[ModelField], + body_field: ModelField | None, model_name_map: ModelNameMap, field_mapping: dict[ tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any] ], separate_input_output_schemas: bool = True, -) -> Optional[dict[str, Any]]: +) -> dict[str, Any] | None: if not body_field: return None assert isinstance(body_field, ModelField) @@ -279,7 +278,7 @@ def get_openapi_path( else: current_response_class = route.response_class assert current_response_class, "A response class is needed to generate OpenAPI" - route_response_media_type: Optional[str] = current_response_class.media_type + route_response_media_type: str | None = current_response_class.media_type if route.include_in_schema: for method in route.methods: operation = get_openapi_operation_metadata( @@ -393,7 +392,7 @@ def get_openapi_path( "An additional response must be a dict" ) field = route.response_fields.get(additional_status_code) - additional_field_schema: Optional[dict[str, Any]] = None + additional_field_schema: dict[str, Any] | None = None if field: additional_field_schema = get_schema_from_model_field( field=field, @@ -408,7 +407,7 @@ def get_openapi_path( .setdefault("schema", {}) ) deep_dict_update(additional_schema, additional_field_schema) - status_text: Optional[str] = status_code_ranges.get( + status_text: str | None = status_code_ranges.get( str(additional_status_code).upper() ) or http.client.responses.get(int(additional_status_code)) description = ( @@ -482,17 +481,17 @@ def get_openapi( title: str, version: str, openapi_version: str = "3.1.0", - summary: Optional[str] = None, - description: Optional[str] = None, + summary: str | None = None, + description: str | None = None, routes: Sequence[BaseRoute], - webhooks: Optional[Sequence[BaseRoute]] = None, - tags: Optional[list[dict[str, Any]]] = None, - servers: Optional[list[dict[str, Union[str, Any]]]] = None, - terms_of_service: Optional[str] = None, - contact: Optional[dict[str, Union[str, Any]]] = None, - license_info: Optional[dict[str, Union[str, Any]]] = None, + webhooks: Sequence[BaseRoute] | None = None, + tags: list[dict[str, Any]] | None = None, + servers: list[dict[str, str | Any]] | None = None, + terms_of_service: str | None = None, + contact: dict[str, str | Any] | None = None, + license_info: dict[str, str | Any] | None = None, separate_input_output_schemas: bool = True, - external_docs: Optional[dict[str, Any]] = None, + external_docs: dict[str, Any] | None = None, ) -> dict[str, Any]: info: dict[str, Any] = {"title": title, "version": version} if summary: diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index 9bd92be4c..4be504f43 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -1,12 +1,12 @@ -from collections.abc import Sequence -from typing import Annotated, Any, Callable, Optional, Union +from collections.abc import Callable, Sequence +from typing import Annotated, Any, Literal from annotated_doc import Doc from fastapi import params from fastapi._compat import Undefined from fastapi.openapi.models import Example from pydantic import AliasChoices, AliasPath -from typing_extensions import Literal, deprecated +from typing_extensions import deprecated _Unset: Any = Undefined @@ -25,7 +25,7 @@ def Path( # noqa: N802 ] = ..., *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -36,7 +36,7 @@ def Path( # noqa: N802 ), ] = _Unset, alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -48,7 +48,7 @@ def Path( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -56,7 +56,7 @@ def Path( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -65,7 +65,7 @@ def Path( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -75,7 +75,7 @@ def Path( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -86,7 +86,7 @@ def Path( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -94,7 +94,7 @@ def Path( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -106,7 +106,7 @@ def Path( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -118,7 +118,7 @@ def Path( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -129,7 +129,7 @@ def Path( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -141,7 +141,7 @@ def Path( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -149,7 +149,7 @@ def Path( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -157,7 +157,7 @@ def Path( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -165,7 +165,7 @@ def Path( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -176,7 +176,7 @@ def Path( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -184,7 +184,7 @@ def Path( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -192,7 +192,7 @@ def Path( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -200,7 +200,7 @@ def Path( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -208,7 +208,7 @@ def Path( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -216,7 +216,7 @@ def Path( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -224,7 +224,7 @@ def Path( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -235,14 +235,14 @@ def Path( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -259,7 +259,7 @@ def Path( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -280,7 +280,7 @@ def Path( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -369,7 +369,7 @@ def Query( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -380,7 +380,7 @@ def Query( # noqa: N802 ), ] = _Unset, alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -395,7 +395,7 @@ def Query( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -403,7 +403,7 @@ def Query( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -412,7 +412,7 @@ def Query( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -422,7 +422,7 @@ def Query( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -433,7 +433,7 @@ def Query( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -444,7 +444,7 @@ def Query( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -456,7 +456,7 @@ def Query( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -468,7 +468,7 @@ def Query( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -479,7 +479,7 @@ def Query( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -491,7 +491,7 @@ def Query( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -502,7 +502,7 @@ def Query( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -513,7 +513,7 @@ def Query( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -524,7 +524,7 @@ def Query( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -535,7 +535,7 @@ def Query( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -543,7 +543,7 @@ def Query( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -551,7 +551,7 @@ def Query( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -559,7 +559,7 @@ def Query( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -567,7 +567,7 @@ def Query( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -575,7 +575,7 @@ def Query( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -583,7 +583,7 @@ def Query( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -594,14 +594,14 @@ def Query( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -618,7 +618,7 @@ def Query( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -645,7 +645,7 @@ def Query( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -710,7 +710,7 @@ def Header( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -721,7 +721,7 @@ def Header( # noqa: N802 ), ] = _Unset, alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -733,7 +733,7 @@ def Header( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -741,7 +741,7 @@ def Header( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -750,7 +750,7 @@ def Header( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -771,7 +771,7 @@ def Header( # noqa: N802 ), ] = True, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -779,7 +779,7 @@ def Header( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -787,7 +787,7 @@ def Header( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -796,7 +796,7 @@ def Header( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -805,7 +805,7 @@ def Header( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -813,7 +813,7 @@ def Header( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -822,7 +822,7 @@ def Header( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -830,7 +830,7 @@ def Header( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -838,7 +838,7 @@ def Header( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -846,7 +846,7 @@ def Header( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -857,7 +857,7 @@ def Header( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -865,7 +865,7 @@ def Header( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -873,7 +873,7 @@ def Header( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -881,7 +881,7 @@ def Header( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -889,7 +889,7 @@ def Header( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -897,7 +897,7 @@ def Header( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -905,7 +905,7 @@ def Header( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -916,14 +916,14 @@ def Header( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -940,7 +940,7 @@ def Header( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -961,7 +961,7 @@ def Header( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -1027,7 +1027,7 @@ def Cookie( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -1038,7 +1038,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -1050,7 +1050,7 @@ def Cookie( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -1058,7 +1058,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -1067,7 +1067,7 @@ def Cookie( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -1077,7 +1077,7 @@ def Cookie( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -1085,7 +1085,7 @@ def Cookie( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -1093,7 +1093,7 @@ def Cookie( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -1102,7 +1102,7 @@ def Cookie( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -1111,7 +1111,7 @@ def Cookie( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -1119,7 +1119,7 @@ def Cookie( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -1128,7 +1128,7 @@ def Cookie( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -1136,7 +1136,7 @@ def Cookie( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -1144,7 +1144,7 @@ def Cookie( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1152,7 +1152,7 @@ def Cookie( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1163,7 +1163,7 @@ def Cookie( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -1171,7 +1171,7 @@ def Cookie( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -1179,7 +1179,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -1187,7 +1187,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -1195,7 +1195,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -1203,7 +1203,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -1211,7 +1211,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -1222,14 +1222,14 @@ def Cookie( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -1246,7 +1246,7 @@ def Cookie( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -1267,7 +1267,7 @@ def Cookie( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -1332,7 +1332,7 @@ def Body( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -1343,7 +1343,7 @@ def Body( # noqa: N802 ), ] = _Unset, embed: Annotated[ - Union[bool, None], + bool | None, Doc( """ When `embed` is `True`, the parameter will be expected in a JSON body as a @@ -1366,7 +1366,7 @@ def Body( # noqa: N802 ), ] = "application/json", alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -1378,7 +1378,7 @@ def Body( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -1386,7 +1386,7 @@ def Body( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -1395,7 +1395,7 @@ def Body( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -1405,7 +1405,7 @@ def Body( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -1413,7 +1413,7 @@ def Body( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -1421,7 +1421,7 @@ def Body( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -1430,7 +1430,7 @@ def Body( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -1439,7 +1439,7 @@ def Body( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -1447,7 +1447,7 @@ def Body( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -1456,7 +1456,7 @@ def Body( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -1464,7 +1464,7 @@ def Body( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -1472,7 +1472,7 @@ def Body( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1480,7 +1480,7 @@ def Body( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1491,7 +1491,7 @@ def Body( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -1499,7 +1499,7 @@ def Body( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -1507,7 +1507,7 @@ def Body( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -1515,7 +1515,7 @@ def Body( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -1523,7 +1523,7 @@ def Body( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -1531,7 +1531,7 @@ def Body( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -1539,7 +1539,7 @@ def Body( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -1550,14 +1550,14 @@ def Body( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -1574,7 +1574,7 @@ def Body( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -1595,7 +1595,7 @@ def Body( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -1662,7 +1662,7 @@ def Form( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -1682,7 +1682,7 @@ def Form( # noqa: N802 ), ] = "application/x-www-form-urlencoded", alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -1694,7 +1694,7 @@ def Form( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -1702,7 +1702,7 @@ def Form( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -1711,7 +1711,7 @@ def Form( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -1721,7 +1721,7 @@ def Form( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -1729,7 +1729,7 @@ def Form( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -1737,7 +1737,7 @@ def Form( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -1746,7 +1746,7 @@ def Form( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -1755,7 +1755,7 @@ def Form( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -1763,7 +1763,7 @@ def Form( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -1772,7 +1772,7 @@ def Form( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -1780,7 +1780,7 @@ def Form( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -1788,7 +1788,7 @@ def Form( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1796,7 +1796,7 @@ def Form( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -1807,7 +1807,7 @@ def Form( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -1815,7 +1815,7 @@ def Form( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -1823,7 +1823,7 @@ def Form( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -1831,7 +1831,7 @@ def Form( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -1839,7 +1839,7 @@ def Form( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -1847,7 +1847,7 @@ def Form( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -1855,7 +1855,7 @@ def Form( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -1866,14 +1866,14 @@ def Form( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -1890,7 +1890,7 @@ def Form( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -1911,7 +1911,7 @@ def Form( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -1977,7 +1977,7 @@ def File( # noqa: N802 ] = Undefined, *, default_factory: Annotated[ - Union[Callable[[], Any], None], + Callable[[], Any] | None, Doc( """ A callable to generate the default value. @@ -1997,7 +1997,7 @@ def File( # noqa: N802 ), ] = "multipart/form-data", alias: Annotated[ - Optional[str], + str | None, Doc( """ An alternative name for the parameter field. @@ -2009,7 +2009,7 @@ def File( # noqa: N802 ), ] = None, alias_priority: Annotated[ - Union[int, None], + int | None, Doc( """ Priority of the alias. This affects whether an alias generator is used. @@ -2017,7 +2017,7 @@ def File( # noqa: N802 ), ] = _Unset, validation_alias: Annotated[ - Union[str, AliasPath, AliasChoices, None], + str | AliasPath | AliasChoices | None, Doc( """ 'Whitelist' validation step. The parameter field will be the single one @@ -2026,7 +2026,7 @@ def File( # noqa: N802 ), ] = None, serialization_alias: Annotated[ - Union[str, None], + str | None, Doc( """ 'Blacklist' validation step. The vanilla parameter field will be the @@ -2036,7 +2036,7 @@ def File( # noqa: N802 ), ] = None, title: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable title. @@ -2044,7 +2044,7 @@ def File( # noqa: N802 ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Human-readable description. @@ -2052,7 +2052,7 @@ def File( # noqa: N802 ), ] = None, gt: Annotated[ - Optional[float], + float | None, Doc( """ Greater than. If set, value must be greater than this. Only applicable to @@ -2061,7 +2061,7 @@ def File( # noqa: N802 ), ] = None, ge: Annotated[ - Optional[float], + float | None, Doc( """ Greater than or equal. If set, value must be greater than or equal to @@ -2070,7 +2070,7 @@ def File( # noqa: N802 ), ] = None, lt: Annotated[ - Optional[float], + float | None, Doc( """ Less than. If set, value must be less than this. Only applicable to numbers. @@ -2078,7 +2078,7 @@ def File( # noqa: N802 ), ] = None, le: Annotated[ - Optional[float], + float | None, Doc( """ Less than or equal. If set, value must be less than or equal to this. @@ -2087,7 +2087,7 @@ def File( # noqa: N802 ), ] = None, min_length: Annotated[ - Optional[int], + int | None, Doc( """ Minimum length for strings. @@ -2095,7 +2095,7 @@ def File( # noqa: N802 ), ] = None, max_length: Annotated[ - Optional[int], + int | None, Doc( """ Maximum length for strings. @@ -2103,7 +2103,7 @@ def File( # noqa: N802 ), ] = None, pattern: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -2111,7 +2111,7 @@ def File( # noqa: N802 ), ] = None, regex: Annotated[ - Optional[str], + str | None, Doc( """ RegEx pattern for strings. @@ -2122,7 +2122,7 @@ def File( # noqa: N802 ), ] = None, discriminator: Annotated[ - Union[str, None], + str | None, Doc( """ Parameter field name for discriminating the type in a tagged union. @@ -2130,7 +2130,7 @@ def File( # noqa: N802 ), ] = None, strict: Annotated[ - Union[bool, None], + bool | None, Doc( """ If `True`, strict validation is applied to the field. @@ -2138,7 +2138,7 @@ def File( # noqa: N802 ), ] = _Unset, multiple_of: Annotated[ - Union[float, None], + float | None, Doc( """ Value must be a multiple of this. Only applicable to numbers. @@ -2146,7 +2146,7 @@ def File( # noqa: N802 ), ] = _Unset, allow_inf_nan: Annotated[ - Union[bool, None], + bool | None, Doc( """ Allow `inf`, `-inf`, `nan`. Only applicable to numbers. @@ -2154,7 +2154,7 @@ def File( # noqa: N802 ), ] = _Unset, max_digits: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of allow digits for strings. @@ -2162,7 +2162,7 @@ def File( # noqa: N802 ), ] = _Unset, decimal_places: Annotated[ - Union[int, None], + int | None, Doc( """ Maximum number of decimal places allowed for numbers. @@ -2170,7 +2170,7 @@ def File( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[list[Any]], + list[Any] | None, Doc( """ Example values for this field. @@ -2181,14 +2181,14 @@ def File( # noqa: N802 ), ] = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, openapi_examples: Annotated[ - Optional[dict[str, Example]], + dict[str, Example] | None, Doc( """ OpenAPI-specific examples. @@ -2205,7 +2205,7 @@ def File( # noqa: N802 ), ] = None, deprecated: Annotated[ - Union[deprecated, str, bool, None], + deprecated | str | bool | None, Doc( """ Mark this parameter field as deprecated. @@ -2226,7 +2226,7 @@ def File( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[dict[str, Any], None], + dict[str, Any] | None, Doc( """ Any additional JSON schema data. @@ -2283,7 +2283,7 @@ def File( # noqa: N802 def Depends( # noqa: N802 dependency: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ A "dependable" callable (like a function). @@ -2315,7 +2315,7 @@ def Depends( # noqa: N802 ), ] = True, scope: Annotated[ - Union[Literal["function", "request"], None], + Literal["function", "request"] | None, Doc( """ Mainly for dependencies with `yield`, define when the dependency function @@ -2372,7 +2372,7 @@ def Depends( # noqa: N802 def Security( # noqa: N802 dependency: Annotated[ - Optional[Callable[..., Any]], + Callable[..., Any] | None, Doc( """ A "dependable" callable (like a function). @@ -2387,7 +2387,7 @@ def Security( # noqa: N802 ] = None, *, scopes: Annotated[ - Optional[Sequence[str]], + Sequence[str] | None, Doc( """ OAuth2 scopes required for the *path operation* that uses this Security diff --git a/fastapi/params.py b/fastapi/params.py index 72e797f83..68f987081 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -1,14 +1,14 @@ import warnings -from collections.abc import Sequence +from collections.abc import Callable, Sequence from dataclasses import dataclass from enum import Enum -from typing import Annotated, Any, Callable, Optional, Union +from typing import Annotated, Any, Literal from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.openapi.models import Example from pydantic import AliasChoices, AliasPath from pydantic.fields import FieldInfo -from typing_extensions import Literal, deprecated +from typing_extensions import deprecated from ._compat import ( Undefined, @@ -31,45 +31,45 @@ class Param(FieldInfo): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): if example is not _Unset: @@ -142,45 +142,45 @@ class Path(Param): # type: ignore[misc] self, default: Any = ..., *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): assert default is ..., "Path parameters cannot have a default value" @@ -226,45 +226,45 @@ class Query(Param): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): super().__init__( @@ -308,46 +308,46 @@ class Header(Param): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, convert_underscores: bool = True, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): self.convert_underscores = convert_underscores @@ -392,45 +392,45 @@ class Cookie(Param): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): super().__init__( @@ -472,47 +472,47 @@ class Body(FieldInfo): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, - embed: Union[bool, None] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, + embed: bool | None = None, media_type: str = "application/json", - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): self.embed = embed @@ -584,46 +584,46 @@ class Form(Body): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, media_type: str = "application/x-www-form-urlencoded", - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): super().__init__( @@ -666,46 +666,46 @@ class File(Form): # type: ignore[misc] self, default: Any = Undefined, *, - default_factory: Union[Callable[[], Any], None] = _Unset, - annotation: Optional[Any] = None, + default_factory: Callable[[], Any] | None = _Unset, + annotation: Any | None = None, media_type: str = "multipart/form-data", - alias: Optional[str] = None, - alias_priority: Union[int, None] = _Unset, - validation_alias: Union[str, AliasPath, AliasChoices, None] = None, - serialization_alias: Union[str, None] = None, - title: Optional[str] = None, - description: Optional[str] = None, - gt: Optional[float] = None, - ge: Optional[float] = None, - lt: Optional[float] = None, - le: Optional[float] = None, - min_length: Optional[int] = None, - max_length: Optional[int] = None, - pattern: Optional[str] = None, + alias: str | None = None, + alias_priority: int | None = _Unset, + validation_alias: str | AliasPath | AliasChoices | None = None, + serialization_alias: str | None = None, + title: str | None = None, + description: str | None = None, + gt: float | None = None, + ge: float | None = None, + lt: float | None = None, + le: float | None = None, + min_length: int | None = None, + max_length: int | None = None, + pattern: str | None = None, regex: Annotated[ - Optional[str], + str | None, deprecated( "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." ), ] = None, - discriminator: Union[str, None] = None, - strict: Union[bool, None] = _Unset, - multiple_of: Union[float, None] = _Unset, - allow_inf_nan: Union[bool, None] = _Unset, - max_digits: Union[int, None] = _Unset, - decimal_places: Union[int, None] = _Unset, - examples: Optional[list[Any]] = None, + discriminator: str | None = None, + strict: bool | None = _Unset, + multiple_of: float | None = _Unset, + allow_inf_nan: bool | None = _Unset, + max_digits: int | None = _Unset, + decimal_places: int | None = _Unset, + examples: list[Any] | None = None, example: Annotated[ - Optional[Any], + Any | None, deprecated( "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[dict[str, Example]] = None, - deprecated: Union[deprecated, str, bool, None] = None, + openapi_examples: dict[str, Example] | None = None, + deprecated: deprecated | str | bool | None = None, include_in_schema: bool = True, - json_schema_extra: Union[dict[str, Any], None] = None, + json_schema_extra: dict[str, Any] | None = None, **extra: Any, ): super().__init__( @@ -745,11 +745,11 @@ class File(Form): # type: ignore[misc] @dataclass(frozen=True) class Depends: - dependency: Optional[Callable[..., Any]] = None + dependency: Callable[..., Any] | None = None use_cache: bool = True - scope: Union[Literal["function", "request"], None] = None + scope: Literal["function", "request"] | None = None @dataclass(frozen=True) class Security(Depends): - scopes: Optional[Sequence[str]] = None + scopes: Sequence[str] | None = None diff --git a/fastapi/routing.py b/fastapi/routing.py index ed873fda8..ea82ab14a 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -7,6 +7,7 @@ import types from collections.abc import ( AsyncIterator, Awaitable, + Callable, Collection, Coroutine, Generator, @@ -23,10 +24,7 @@ from enum import Enum, IntEnum from typing import ( Annotated, Any, - Callable, - Optional, TypeVar, - Union, ) from annotated_doc import Doc @@ -84,7 +82,7 @@ from typing_extensions import deprecated # Copy of starlette.routing.request_response modified to include the # dependencies' AsyncExitStack def request_response( - func: Callable[[Request], Union[Awaitable[Response], Response]], + func: Callable[[Request], Awaitable[Response] | Response], ) -> ASGIApp: """ Takes a function or coroutine `func(request) -> response`, @@ -168,10 +166,10 @@ class _AsyncLiftContextManager(AbstractAsyncContextManager[_T]): async def __aexit__( self, - exc_type: Optional[type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[types.TracebackType], - ) -> Optional[bool]: + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> bool | None: return self._cm.__exit__(exc_type, exc_value, traceback) @@ -199,7 +197,7 @@ def _merge_lifespan_context( @asynccontextmanager async def merged_lifespan( app: AppType, - ) -> AsyncIterator[Optional[Mapping[str, Any]]]: + ) -> AsyncIterator[Mapping[str, Any] | None]: async with original_context(app) as maybe_original_state: async with nested_context(app) as maybe_nested_state: if maybe_nested_state is None and maybe_original_state is None: @@ -263,16 +261,16 @@ def _extract_endpoint_context(func: Any) -> EndpointContext: async def serialize_response( *, - field: Optional[ModelField] = None, + field: ModelField | None = None, response_content: Any, - include: Optional[IncEx] = None, - exclude: Optional[IncEx] = None, + include: IncEx | None = None, + exclude: IncEx | None = None, by_alias: bool = True, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, is_coroutine: bool = True, - endpoint_ctx: Optional[EndpointContext] = None, + endpoint_ctx: EndpointContext | None = None, ) -> Any: if field: if is_coroutine: @@ -318,17 +316,17 @@ async def run_endpoint_function( def get_request_handler( dependant: Dependant, - body_field: Optional[ModelField] = None, - status_code: Optional[int] = None, - response_class: Union[type[Response], DefaultPlaceholder] = Default(JSONResponse), - response_field: Optional[ModelField] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + body_field: ModelField | None = None, + status_code: int | None = None, + response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), + response_field: ModelField | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, - dependency_overrides_provider: Optional[Any] = None, + dependency_overrides_provider: Any | None = None, embed_body_fields: bool = False, ) -> Callable[[Request], Coroutine[Any, Any, Response]]: assert dependant.call is not None, "dependant.call must be a function" @@ -340,7 +338,7 @@ def get_request_handler( actual_response_class = response_class async def app(request: Request) -> Response: - response: Union[Response, None] = None + response: Response | None = None file_stack = request.scope.get("fastapi_middleware_astack") assert isinstance(file_stack, AsyncExitStack), ( "fastapi_middleware_astack not found in request scope" @@ -476,7 +474,7 @@ def get_request_handler( def get_websocket_app( dependant: Dependant, - dependency_overrides_provider: Optional[Any] = None, + dependency_overrides_provider: Any | None = None, embed_body_fields: bool = False, ) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]: async def app(websocket: WebSocket) -> None: @@ -517,9 +515,9 @@ class APIWebSocketRoute(routing.WebSocketRoute): path: str, endpoint: Callable[..., Any], *, - name: Optional[str] = None, - dependencies: Optional[Sequence[params.Depends]] = None, - dependency_overrides_provider: Optional[Any] = None, + name: str | None = None, + dependencies: Sequence[params.Depends] | None = None, + dependency_overrides_provider: Any | None = None, ) -> None: self.path = path self.endpoint = endpoint @@ -560,33 +558,30 @@ class APIRoute(routing.Route): endpoint: Callable[..., Any], *, response_model: Any = Default(None), - status_code: Optional[int] = None, - tags: Optional[list[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[params.Depends]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, + status_code: int | None = None, + tags: list[str | Enum] | None = None, + dependencies: Sequence[params.Depends] | None = None, + summary: str | None = None, + description: str | None = None, response_description: str = "Successful Response", - responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, - deprecated: Optional[bool] = None, - name: Optional[str] = None, - methods: Optional[Union[set[str], list[str]]] = None, - operation_id: Optional[str] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + responses: dict[int | str, dict[str, Any]] | None = None, + deprecated: bool | None = None, + name: str | None = None, + methods: set[str] | list[str] | None = None, + operation_id: str | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Union[type[Response], DefaultPlaceholder] = Default( - JSONResponse - ), - dependency_overrides_provider: Optional[Any] = None, - callbacks: Optional[list[BaseRoute]] = None, - openapi_extra: Optional[dict[str, Any]] = None, - generate_unique_id_function: Union[ - Callable[["APIRoute"], str], DefaultPlaceholder - ] = Default(generate_unique_id), + response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), + dependency_overrides_provider: Any | None = None, + callbacks: list[BaseRoute] | None = None, + openapi_extra: dict[str, Any] | None = None, + generate_unique_id_function: Callable[["APIRoute"], str] + | DefaultPlaceholder = Default(generate_unique_id), ) -> None: self.path = path self.endpoint = endpoint @@ -662,7 +657,7 @@ class APIRoute(routing.Route): ) response_fields[additional_status_code] = response_field if response_fields: - self.response_fields: dict[Union[int, str], ModelField] = response_fields + self.response_fields: dict[int | str, ModelField] = response_fields else: self.response_fields = {} @@ -742,7 +737,7 @@ class APIRouter(routing.Router): *, prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to all the *path operations* in this @@ -756,7 +751,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to all the @@ -779,7 +774,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses to be shown in OpenAPI. @@ -795,7 +790,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ OpenAPI callbacks that should apply to all *path operations* in this @@ -809,7 +804,7 @@ class APIRouter(routing.Router): ), ] = None, routes: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -840,7 +835,7 @@ class APIRouter(routing.Router): ), ] = True, default: Annotated[ - Optional[ASGIApp], + ASGIApp | None, Doc( """ Default function handler for this router. Used to handle @@ -849,7 +844,7 @@ class APIRouter(routing.Router): ), ] = None, dependency_overrides_provider: Annotated[ - Optional[Any], + Any | None, Doc( """ Only used internally by FastAPI to handle dependency overrides. @@ -871,7 +866,7 @@ class APIRouter(routing.Router): ), ] = APIRoute, on_startup: Annotated[ - Optional[Sequence[Callable[[], Any]]], + Sequence[Callable[[], Any]] | None, Doc( """ A list of startup event handler functions. @@ -883,7 +878,7 @@ class APIRouter(routing.Router): ), ] = None, on_shutdown: Annotated[ - Optional[Sequence[Callable[[], Any]]], + Sequence[Callable[[], Any]] | None, Doc( """ A list of shutdown event handler functions. @@ -898,7 +893,7 @@ class APIRouter(routing.Router): # the generic to Lifespan[AppType] is the type of the top level application # which the router cannot know statically, so we use typing.Any lifespan: Annotated[ - Optional[Lifespan[Any]], + Lifespan[Any] | None, Doc( """ A `Lifespan` context manager handler. This replaces `startup` and @@ -910,7 +905,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark all *path operations* in this router as deprecated. @@ -987,7 +982,7 @@ class APIRouter(routing.Router): ) self.prefix = prefix - self.tags: list[Union[str, Enum]] = tags or [] + self.tags: list[str | Enum] = tags or [] self.dependencies = list(dependencies or []) self.deprecated = deprecated self.include_in_schema = include_in_schema @@ -1001,8 +996,8 @@ class APIRouter(routing.Router): def route( self, path: str, - methods: Optional[Collection[str]] = None, - name: Optional[str] = None, + methods: Collection[str] | None = None, + name: str | None = None, include_in_schema: bool = True, ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: @@ -1023,33 +1018,30 @@ class APIRouter(routing.Router): endpoint: Callable[..., Any], *, response_model: Any = Default(None), - status_code: Optional[int] = None, - tags: Optional[list[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[params.Depends]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, + status_code: int | None = None, + tags: list[str | Enum] | None = None, + dependencies: Sequence[params.Depends] | None = None, + summary: str | None = None, + description: str | None = None, response_description: str = "Successful Response", - responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, - deprecated: Optional[bool] = None, - methods: Optional[Union[set[str], list[str]]] = None, - operation_id: Optional[str] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + responses: dict[int | str, dict[str, Any]] | None = None, + deprecated: bool | None = None, + methods: set[str] | list[str] | None = None, + operation_id: str | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Union[type[Response], DefaultPlaceholder] = Default( - JSONResponse - ), - name: Optional[str] = None, - route_class_override: Optional[type[APIRoute]] = None, - callbacks: Optional[list[BaseRoute]] = None, - openapi_extra: Optional[dict[str, Any]] = None, - generate_unique_id_function: Union[ - Callable[[APIRoute], str], DefaultPlaceholder - ] = Default(generate_unique_id), + response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), + name: str | None = None, + route_class_override: type[APIRoute] | None = None, + callbacks: list[BaseRoute] | None = None, + openapi_extra: dict[str, Any] | None = None, + generate_unique_id_function: Callable[[APIRoute], str] + | DefaultPlaceholder = Default(generate_unique_id), ) -> None: route_class = route_class_override or self.route_class responses = responses or {} @@ -1104,27 +1096,27 @@ class APIRouter(routing.Router): path: str, *, response_model: Any = Default(None), - status_code: Optional[int] = None, - tags: Optional[list[Union[str, Enum]]] = None, - dependencies: Optional[Sequence[params.Depends]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, + status_code: int | None = None, + tags: list[str | Enum] | None = None, + dependencies: Sequence[params.Depends] | None = None, + summary: str | None = None, + description: str | None = None, response_description: str = "Successful Response", - responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, - deprecated: Optional[bool] = None, - methods: Optional[list[str]] = None, - operation_id: Optional[str] = None, - response_model_include: Optional[IncEx] = None, - response_model_exclude: Optional[IncEx] = None, + responses: dict[int | str, dict[str, Any]] | None = None, + deprecated: bool | None = None, + methods: list[str] | None = None, + operation_id: str | None = None, + response_model_include: IncEx | None = None, + response_model_exclude: IncEx | None = None, response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, response_class: type[Response] = Default(JSONResponse), - name: Optional[str] = None, - callbacks: Optional[list[BaseRoute]] = None, - openapi_extra: Optional[dict[str, Any]] = None, + name: str | None = None, + callbacks: list[BaseRoute] | None = None, + openapi_extra: dict[str, Any] | None = None, generate_unique_id_function: Callable[[APIRoute], str] = Default( generate_unique_id ), @@ -1165,9 +1157,9 @@ class APIRouter(routing.Router): self, path: str, endpoint: Callable[..., Any], - name: Optional[str] = None, + name: str | None = None, *, - dependencies: Optional[Sequence[params.Depends]] = None, + dependencies: Sequence[params.Depends] | None = None, ) -> None: current_dependencies = self.dependencies.copy() if dependencies: @@ -1193,7 +1185,7 @@ class APIRouter(routing.Router): ), ], name: Annotated[ - Optional[str], + str | None, Doc( """ A name for the WebSocket. Only used internally. @@ -1202,7 +1194,7 @@ class APIRouter(routing.Router): ] = None, *, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be used for this @@ -1250,7 +1242,7 @@ class APIRouter(routing.Router): return decorator def websocket_route( - self, path: str, name: Union[str, None] = None + self, path: str, name: str | None = None ) -> Callable[[DecoratedCallable], DecoratedCallable]: def decorator(func: DecoratedCallable) -> DecoratedCallable: self.add_websocket_route(path, func, name=name) @@ -1264,7 +1256,7 @@ class APIRouter(routing.Router): *, prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to all the *path operations* in this @@ -1278,7 +1270,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to all the @@ -1301,7 +1293,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses to be shown in OpenAPI. @@ -1317,7 +1309,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ OpenAPI callbacks that should apply to all *path operations* in this @@ -1331,7 +1323,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark all *path operations* in this router as deprecated. @@ -1554,7 +1546,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -1567,7 +1559,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -1580,7 +1572,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -1592,7 +1584,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -1605,7 +1597,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -1633,7 +1625,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -1643,7 +1635,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -1653,7 +1645,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -1673,7 +1665,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -1685,7 +1677,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -1787,7 +1779,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -1795,7 +1787,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -1811,7 +1803,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -1931,7 +1923,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -1944,7 +1936,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -1957,7 +1949,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -1969,7 +1961,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -1982,7 +1974,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2010,7 +2002,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2020,7 +2012,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2030,7 +2022,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2050,7 +2042,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2062,7 +2054,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2164,7 +2156,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2172,7 +2164,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2188,7 +2180,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2313,7 +2305,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -2326,7 +2318,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -2339,7 +2331,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2351,7 +2343,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -2364,7 +2356,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2392,7 +2384,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2402,7 +2394,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2412,7 +2404,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2432,7 +2424,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2444,7 +2436,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2546,7 +2538,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2554,7 +2546,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2570,7 +2562,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2695,7 +2687,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -2708,7 +2700,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -2721,7 +2713,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -2733,7 +2725,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -2746,7 +2738,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -2774,7 +2766,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -2784,7 +2776,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -2794,7 +2786,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -2814,7 +2806,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -2826,7 +2818,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -2928,7 +2920,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -2936,7 +2928,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2952,7 +2944,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3072,7 +3064,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3085,7 +3077,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3098,7 +3090,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3110,7 +3102,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3123,7 +3115,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3151,7 +3143,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3161,7 +3153,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3171,7 +3163,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3191,7 +3183,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3203,7 +3195,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -3305,7 +3297,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -3313,7 +3305,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3329,7 +3321,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3449,7 +3441,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3462,7 +3454,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3475,7 +3467,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3487,7 +3479,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3500,7 +3492,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3528,7 +3520,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3538,7 +3530,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3548,7 +3540,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3568,7 +3560,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3580,7 +3572,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -3682,7 +3674,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -3690,7 +3682,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3706,7 +3698,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3831,7 +3823,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -3844,7 +3836,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -3857,7 +3849,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -3869,7 +3861,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -3882,7 +3874,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -3910,7 +3902,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -3920,7 +3912,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -3930,7 +3922,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -3950,7 +3942,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -3962,7 +3954,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -4064,7 +4056,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -4072,7 +4064,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4088,7 +4080,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -4213,7 +4205,7 @@ class APIRouter(routing.Router): ), ] = Default(None), status_code: Annotated[ - Optional[int], + int | None, Doc( """ The default status code to be used for the response. @@ -4226,7 +4218,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[list[Union[str, Enum]]], + list[str | Enum] | None, Doc( """ A list of tags to be applied to the *path operation*. @@ -4239,7 +4231,7 @@ class APIRouter(routing.Router): ), ] = None, dependencies: Annotated[ - Optional[Sequence[params.Depends]], + Sequence[params.Depends] | None, Doc( """ A list of dependencies (using `Depends()`) to be applied to the @@ -4251,7 +4243,7 @@ class APIRouter(routing.Router): ), ] = None, summary: Annotated[ - Optional[str], + str | None, Doc( """ A summary for the *path operation*. @@ -4264,7 +4256,7 @@ class APIRouter(routing.Router): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ A description for the *path operation*. @@ -4292,7 +4284,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[dict[Union[int, str], dict[str, Any]]], + dict[int | str, dict[str, Any]] | None, Doc( """ Additional responses that could be returned by this *path operation*. @@ -4302,7 +4294,7 @@ class APIRouter(routing.Router): ), ] = None, deprecated: Annotated[ - Optional[bool], + bool | None, Doc( """ Mark this *path operation* as deprecated. @@ -4312,7 +4304,7 @@ class APIRouter(routing.Router): ), ] = None, operation_id: Annotated[ - Optional[str], + str | None, Doc( """ Custom operation ID to be used by this *path operation*. @@ -4332,7 +4324,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_include: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to include only certain fields in the @@ -4344,7 +4336,7 @@ class APIRouter(routing.Router): ), ] = None, response_model_exclude: Annotated[ - Optional[IncEx], + IncEx | None, Doc( """ Configuration passed to Pydantic to exclude certain fields in the @@ -4446,7 +4438,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), name: Annotated[ - Optional[str], + str | None, Doc( """ Name for this *path operation*. Only used internally. @@ -4454,7 +4446,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[list[BaseRoute]], + list[BaseRoute] | None, Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4470,7 +4462,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[dict[str, Any]], + dict[str, Any] | None, Doc( """ Extra metadata to be included in the OpenAPI schema for this *path diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py index 18dfb8e61..89358a913 100644 --- a/fastapi/security/api_key.py +++ b/fastapi/security/api_key.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional, Union +from typing import Annotated from annotated_doc import Doc from fastapi.openapi.models import APIKey, APIKeyIn @@ -13,8 +13,8 @@ class APIKeyBase(SecurityBase): self, location: APIKeyIn, name: str, - description: Union[str, None], - scheme_name: Union[str, None], + description: str | None, + scheme_name: str | None, auto_error: bool, ): self.auto_error = auto_error @@ -42,7 +42,7 @@ class APIKeyBase(SecurityBase): headers={"WWW-Authenticate": "APIKey"}, ) - def check_api_key(self, api_key: Optional[str]) -> Optional[str]: + def check_api_key(self, api_key: str | None) -> str | None: if not api_key: if self.auto_error: raise self.make_not_authenticated_error() @@ -90,7 +90,7 @@ class APIKeyQuery(APIKeyBase): Doc("Query parameter name."), ], scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -100,7 +100,7 @@ class APIKeyQuery(APIKeyBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -137,7 +137,7 @@ class APIKeyQuery(APIKeyBase): auto_error=auto_error, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: api_key = request.query_params.get(self.model.name) return self.check_api_key(api_key) @@ -179,7 +179,7 @@ class APIKeyHeader(APIKeyBase): *, name: Annotated[str, Doc("Header name.")], scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -189,7 +189,7 @@ class APIKeyHeader(APIKeyBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -225,7 +225,7 @@ class APIKeyHeader(APIKeyBase): auto_error=auto_error, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: api_key = request.headers.get(self.model.name) return self.check_api_key(api_key) @@ -267,7 +267,7 @@ class APIKeyCookie(APIKeyBase): *, name: Annotated[str, Doc("Cookie name.")], scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -277,7 +277,7 @@ class APIKeyCookie(APIKeyBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -313,6 +313,6 @@ class APIKeyCookie(APIKeyBase): auto_error=auto_error, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: api_key = request.cookies.get(self.model.name) return self.check_api_key(api_key) diff --git a/fastapi/security/http.py b/fastapi/security/http.py index b4c3bc6d8..05299323c 100644 --- a/fastapi/security/http.py +++ b/fastapi/security/http.py @@ -1,6 +1,6 @@ import binascii from base64 import b64decode -from typing import Annotated, Optional +from typing import Annotated from annotated_doc import Doc from fastapi.exceptions import HTTPException @@ -71,8 +71,8 @@ class HTTPBase(SecurityBase): self, *, scheme: str, - scheme_name: Optional[str] = None, - description: Optional[str] = None, + scheme_name: str | None = None, + description: str | None = None, auto_error: bool = True, ): self.model: HTTPBaseModel = HTTPBaseModel( @@ -91,9 +91,7 @@ class HTTPBase(SecurityBase): headers=self.make_authenticate_headers(), ) - async def __call__( - self, request: Request - ) -> Optional[HTTPAuthorizationCredentials]: + async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None: authorization = request.headers.get("Authorization") scheme, credentials = get_authorization_scheme_param(authorization) if not (authorization and scheme and credentials): @@ -143,7 +141,7 @@ class HTTPBasic(HTTPBase): self, *, scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -153,7 +151,7 @@ class HTTPBasic(HTTPBase): ), ] = None, realm: Annotated[ - Optional[str], + str | None, Doc( """ HTTP Basic authentication realm. @@ -161,7 +159,7 @@ class HTTPBasic(HTTPBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -203,7 +201,7 @@ class HTTPBasic(HTTPBase): async def __call__( # type: ignore self, request: Request - ) -> Optional[HTTPBasicCredentials]: + ) -> HTTPBasicCredentials | None: authorization = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if not authorization or scheme.lower() != "basic": @@ -256,9 +254,9 @@ class HTTPBearer(HTTPBase): def __init__( self, *, - bearerFormat: Annotated[Optional[str], Doc("Bearer token format.")] = None, + bearerFormat: Annotated[str | None, Doc("Bearer token format.")] = None, scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -268,7 +266,7 @@ class HTTPBearer(HTTPBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -302,9 +300,7 @@ class HTTPBearer(HTTPBase): self.scheme_name = scheme_name or self.__class__.__name__ self.auto_error = auto_error - async def __call__( - self, request: Request - ) -> Optional[HTTPAuthorizationCredentials]: + async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None: authorization = request.headers.get("Authorization") scheme, credentials = get_authorization_scheme_param(authorization) if not (authorization and scheme and credentials): @@ -362,7 +358,7 @@ class HTTPDigest(HTTPBase): self, *, scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -372,7 +368,7 @@ class HTTPDigest(HTTPBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -405,9 +401,7 @@ class HTTPDigest(HTTPBase): self.scheme_name = scheme_name or self.__class__.__name__ self.auto_error = auto_error - async def __call__( - self, request: Request - ) -> Optional[HTTPAuthorizationCredentials]: + async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None: authorization = request.headers.get("Authorization") scheme, credentials = get_authorization_scheme_param(authorization) if not (authorization and scheme and credentials): diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py index 58ffc5c76..661043ce7 100644 --- a/fastapi/security/oauth2.py +++ b/fastapi/security/oauth2.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Optional, Union, cast +from typing import Annotated, Any, cast from annotated_doc import Doc from fastapi.exceptions import HTTPException @@ -60,7 +60,7 @@ class OAuth2PasswordRequestForm: self, *, grant_type: Annotated[ - Union[str, None], + str | None, Form(pattern="^password$"), Doc( """ @@ -128,7 +128,7 @@ class OAuth2PasswordRequestForm: ), ] = "", client_id: Annotated[ - Union[str, None], + str | None, Form(), Doc( """ @@ -139,7 +139,7 @@ class OAuth2PasswordRequestForm: ), ] = None, client_secret: Annotated[ - Union[str, None], + str | None, Form(json_schema_extra={"format": "password"}), Doc( """ @@ -294,7 +294,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): ), ] = "", client_id: Annotated[ - Union[str, None], + str | None, Form(), Doc( """ @@ -305,7 +305,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): ), ] = None, client_secret: Annotated[ - Union[str, None], + str | None, Form(), Doc( """ @@ -344,7 +344,7 @@ class OAuth2(SecurityBase): self, *, flows: Annotated[ - Union[OAuthFlowsModel, dict[str, dict[str, Any]]], + OAuthFlowsModel | dict[str, dict[str, Any]], Doc( """ The dictionary of OAuth2 flows. @@ -352,7 +352,7 @@ class OAuth2(SecurityBase): ), ] = OAuthFlowsModel(), scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -362,7 +362,7 @@ class OAuth2(SecurityBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -420,7 +420,7 @@ class OAuth2(SecurityBase): headers={"WWW-Authenticate": "Bearer"}, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: authorization = request.headers.get("Authorization") if not authorization: if self.auto_error: @@ -454,7 +454,7 @@ class OAuth2PasswordBearer(OAuth2): ), ], scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -464,7 +464,7 @@ class OAuth2PasswordBearer(OAuth2): ), ] = None, scopes: Annotated[ - Optional[dict[str, str]], + dict[str, str] | None, Doc( """ The OAuth2 scopes that would be required by the *path operations* that @@ -476,7 +476,7 @@ class OAuth2PasswordBearer(OAuth2): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -506,7 +506,7 @@ class OAuth2PasswordBearer(OAuth2): ), ] = True, refreshUrl: Annotated[ - Optional[str], + str | None, Doc( """ The URL to refresh the token and obtain a new one. @@ -533,7 +533,7 @@ class OAuth2PasswordBearer(OAuth2): auto_error=auto_error, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: authorization = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if not authorization or scheme.lower() != "bearer": @@ -562,7 +562,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): ), ], refreshUrl: Annotated[ - Optional[str], + str | None, Doc( """ The URL to refresh the token and obtain a new one. @@ -570,7 +570,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): ), ] = None, scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -580,7 +580,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): ), ] = None, scopes: Annotated[ - Optional[dict[str, str]], + dict[str, str] | None, Doc( """ The OAuth2 scopes that would be required by the *path operations* that @@ -589,7 +589,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -639,7 +639,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): auto_error=auto_error, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: authorization = request.headers.get("Authorization") scheme, param = get_authorization_scheme_param(authorization) if not authorization or scheme.lower() != "bearer": @@ -666,7 +666,7 @@ class SecurityScopes: def __init__( self, scopes: Annotated[ - Optional[list[str]], + list[str] | None, Doc( """ This will be filled by FastAPI. diff --git a/fastapi/security/open_id_connect_url.py b/fastapi/security/open_id_connect_url.py index f4d953351..1c6fcc744 100644 --- a/fastapi/security/open_id_connect_url.py +++ b/fastapi/security/open_id_connect_url.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from annotated_doc import Doc from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel @@ -31,7 +31,7 @@ class OpenIdConnect(SecurityBase): ), ], scheme_name: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme name. @@ -41,7 +41,7 @@ class OpenIdConnect(SecurityBase): ), ] = None, description: Annotated[ - Optional[str], + str | None, Doc( """ Security scheme description. @@ -84,7 +84,7 @@ class OpenIdConnect(SecurityBase): headers={"WWW-Authenticate": "Bearer"}, ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: authorization = request.headers.get("Authorization") if not authorization: if self.auto_error: diff --git a/fastapi/security/utils.py b/fastapi/security/utils.py index fd349aec7..8ee66fd38 100644 --- a/fastapi/security/utils.py +++ b/fastapi/security/utils.py @@ -1,8 +1,5 @@ -from typing import Optional - - def get_authorization_scheme_param( - authorization_header_value: Optional[str], + authorization_header_value: str | None, ) -> tuple[str, str]: if not authorization_header_value: return "", "" diff --git a/fastapi/types.py b/fastapi/types.py index 1c3a6de74..1fb86e13b 100644 --- a/fastapi/types.py +++ b/fastapi/types.py @@ -1,11 +1,12 @@ import types +from collections.abc import Callable from enum import Enum -from typing import Any, Callable, Optional, TypeVar, Union +from typing import Any, 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] -DependencyCacheKey = tuple[Optional[Callable[..., Any]], tuple[str, ...], str] +ModelNameMap = dict[type[BaseModel] | type[Enum], str] +DependencyCacheKey = tuple[Callable[..., Any] | None, tuple[str, ...], str] diff --git a/fastapi/utils.py b/fastapi/utils.py index 28c7cdfcc..12eaa2bf0 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -3,8 +3,7 @@ import warnings from typing import ( TYPE_CHECKING, Any, - Optional, - Union, + Literal, ) import fastapi @@ -17,7 +16,6 @@ from fastapi._compat import ( from fastapi.datastructures import DefaultPlaceholder, DefaultType from fastapi.exceptions import FastAPIDeprecationWarning, PydanticV1NotSupportedError from pydantic.fields import FieldInfo -from typing_extensions import Literal from ._compat import v2 @@ -25,7 +23,7 @@ if TYPE_CHECKING: # pragma: nocover from .routing import APIRoute -def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool: +def is_body_allowed_for_status_code(status_code: int | str | None) -> bool: if status_code is None: return True # Ref: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#patterned-fields-1 @@ -60,9 +58,9 @@ _invalid_args_message = ( def create_model_field( name: str, type_: Any, - default: Optional[Any] = Undefined, - field_info: Optional[FieldInfo] = None, - alias: Optional[str] = None, + default: Any | None = Undefined, + field_info: FieldInfo | None = None, + alias: str | None = None, mode: Literal["validation", "serialization"] = "validation", ) -> ModelField: if annotation_is_pydantic_v1(type_): @@ -121,9 +119,9 @@ def deep_dict_update(main_dict: dict[Any, Any], update_dict: dict[Any, Any]) -> def get_value_or_default( - first_item: Union[DefaultPlaceholder, DefaultType], - *extra_items: Union[DefaultPlaceholder, DefaultType], -) -> Union[DefaultPlaceholder, DefaultType]: + first_item: DefaultPlaceholder | DefaultType, + *extra_items: DefaultPlaceholder | DefaultType, +) -> DefaultPlaceholder | DefaultType: """ Pass items or `DefaultPlaceholder`s by descending priority. From d06ab3f5c7c1b6229e28af37905eb51ab4b4cdb7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 11 Feb 2026 18:41:46 +0000 Subject: [PATCH 102/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e0ca3de72..7640bd6fa 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -11,6 +11,10 @@ hide: * ➖ Drop support for Python 3.9. PR [#14897](https://github.com/fastapi/fastapi/pull/14897) by [@tiangolo](https://github.com/tiangolo). +### Refactors + +* 🎨 Update internal types for Python 3.10. PR [#14898](https://github.com/fastapi/fastapi/pull/14898) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.8 ### Docs From c9e2277d8b3b0feead4bdad97fca7cf05c9263fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 12 Feb 2026 05:19:43 -0800 Subject: [PATCH 103/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20source=20exampl?= =?UTF-8?q?es=20and=20docs=20from=20Python=203.9=20to=203.10=20(#14900)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/additional-responses.md | 4 +- .../en/docs/advanced/advanced-dependencies.md | 8 +- docs/en/docs/advanced/async-tests.md | 8 +- docs/en/docs/advanced/behind-a-proxy.md | 12 +- docs/en/docs/advanced/custom-response.md | 32 +-- docs/en/docs/advanced/events.md | 12 +- docs/en/docs/advanced/generate-clients.md | 8 +- docs/en/docs/advanced/middleware.md | 6 +- docs/en/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 14 +- .../advanced/response-change-status-code.md | 2 +- docs/en/docs/advanced/response-cookies.md | 4 +- docs/en/docs/advanced/response-directly.md | 2 +- docs/en/docs/advanced/response-headers.md | 4 +- .../docs/advanced/security/http-basic-auth.md | 6 +- docs/en/docs/advanced/settings.md | 20 +- docs/en/docs/advanced/sub-applications.md | 6 +- docs/en/docs/advanced/templates.md | 2 +- docs/en/docs/advanced/testing-events.md | 4 +- docs/en/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/en/docs/advanced/websockets.md | 8 +- docs/en/docs/advanced/wsgi.md | 2 +- .../authentication-error-status-code.md | 2 +- docs/en/docs/how-to/conditional-openapi.md | 2 +- docs/en/docs/how-to/configure-swagger-ui.md | 6 +- docs/en/docs/how-to/custom-docs-ui-assets.md | 14 +- docs/en/docs/how-to/extending-openapi.md | 10 +- docs/en/docs/how-to/graphql.md | 2 +- docs/en/docs/python-types.md | 24 +- docs/en/docs/tutorial/background-tasks.md | 6 +- docs/en/docs/tutorial/bigger-applications.md | 26 +- docs/en/docs/tutorial/body-nested-models.md | 4 +- docs/en/docs/tutorial/cors.md | 2 +- docs/en/docs/tutorial/debugging.md | 2 +- ...pendencies-in-path-operation-decorators.md | 8 +- .../dependencies/dependencies-with-yield.md | 22 +- .../dependencies/global-dependencies.md | 2 +- docs/en/docs/tutorial/extra-models.md | 4 +- docs/en/docs/tutorial/first-steps.md | 14 +- docs/en/docs/tutorial/handling-errors.md | 16 +- docs/en/docs/tutorial/metadata.md | 12 +- docs/en/docs/tutorial/middleware.md | 4 +- .../tutorial/path-operation-configuration.md | 4 +- .../path-params-numeric-validations.md | 14 +- docs/en/docs/tutorial/path-params.md | 20 +- .../tutorial/query-params-str-validations.md | 8 +- docs/en/docs/tutorial/query-params.md | 4 +- docs/en/docs/tutorial/request-files.md | 12 +- docs/en/docs/tutorial/request-form-models.md | 4 +- .../docs/tutorial/request-forms-and-files.md | 4 +- docs/en/docs/tutorial/request-forms.md | 4 +- docs/en/docs/tutorial/response-model.md | 4 +- docs/en/docs/tutorial/response-status-code.md | 6 +- docs/en/docs/tutorial/security/first-steps.md | 6 +- .../tutorial/security/get-current-user.md | 2 +- docs/en/docs/tutorial/static-files.md | 2 +- docs/en/docs/tutorial/testing.md | 6 +- .../additional_responses/tutorial001_py310.py | 22 ++ .../additional_responses/tutorial002_py39.py | 30 --- .../additional_responses/tutorial003_py310.py | 37 +++ .../additional_responses/tutorial004_py39.py | 32 --- .../tutorial001_an_py39.py | 25 -- .../tutorial001_py39.py | 25 -- .../advanced_middleware/tutorial001_py310.py | 11 + .../advanced_middleware/tutorial002_py310.py | 13 + .../advanced_middleware/tutorial003_py310.py | 11 + .../__init__.py | 0 docs_src/app_testing/app_a_py310/main.py | 8 + docs_src/app_testing/app_a_py310/test_main.py | 11 + docs_src/app_testing/app_b_an_py39/main.py | 38 --- .../app_testing/app_b_an_py39/test_main.py | 65 ----- docs_src/app_testing/app_b_py39/main.py | 38 --- docs_src/app_testing/app_b_py39/test_main.py | 65 ----- docs_src/app_testing/tutorial001_py310.py | 18 ++ docs_src/app_testing/tutorial002_py310.py | 31 +++ docs_src/app_testing/tutorial003_py310.py | 24 ++ docs_src/app_testing/tutorial004_py310.py | 43 +++ .../app_a_py310}/__init__.py | 0 docs_src/async_tests/app_a_py310/main.py | 8 + docs_src/async_tests/app_a_py310/test_main.py | 14 + .../tutorial001_an_py310.py | 21 ++ .../background_tasks/tutorial001_py310.py | 15 ++ .../background_tasks/tutorial002_an_py39.py | 26 -- docs_src/background_tasks/tutorial002_py39.py | 26 -- .../behind_a_proxy/tutorial001_01_py310.py | 8 + docs_src/behind_a_proxy/tutorial001_py310.py | 8 + docs_src/behind_a_proxy/tutorial002_py310.py | 8 + docs_src/behind_a_proxy/tutorial003_py310.py | 14 + docs_src/behind_a_proxy/tutorial004_py310.py | 15 ++ .../internal => app_an_py310}/__init__.py | 0 .../app_an_py310/dependencies.py | 13 + .../internal}/__init__.py | 0 .../internal/admin.py | 0 .../bigger_applications/app_an_py310/main.py | 23 ++ .../app_an_py310/routers/__init__.py | 0 .../routers/items.py | 0 .../routers/users.py | 0 docs_src/body/tutorial001_py39.py | 19 -- docs_src/body/tutorial002_py39.py | 23 -- docs_src/body/tutorial003_py39.py | 19 -- docs_src/body/tutorial004_py39.py | 22 -- docs_src/body_fields/tutorial001_an_py39.py | 21 -- docs_src/body_fields/tutorial001_py39.py | 21 -- .../tutorial001_an_py39.py | 27 -- .../body_multiple_params/tutorial001_py39.py | 28 -- .../body_multiple_params/tutorial002_py39.py | 24 -- .../tutorial003_an_py39.py | 26 -- .../body_multiple_params/tutorial003_py39.py | 24 -- .../tutorial004_an_py39.py | 33 --- .../body_multiple_params/tutorial004_py39.py | 33 --- .../tutorial005_an_py39.py | 19 -- .../body_multiple_params/tutorial005_py39.py | 19 -- .../body_nested_models/tutorial001_py39.py | 20 -- .../body_nested_models/tutorial002_py39.py | 20 -- .../body_nested_models/tutorial003_py39.py | 20 -- .../body_nested_models/tutorial004_py39.py | 26 -- .../body_nested_models/tutorial005_py39.py | 26 -- .../body_nested_models/tutorial006_py39.py | 26 -- .../body_nested_models/tutorial007_py39.py | 32 --- .../body_nested_models/tutorial008_py310.py | 14 + .../body_nested_models/tutorial009_py310.py | 8 + docs_src/body_updates/tutorial001_py39.py | 34 --- docs_src/body_updates/tutorial002_py39.py | 37 --- .../conditional_openapi/tutorial001_py310.py | 16 ++ .../configure_swagger_ui/tutorial001_py310.py | 8 + .../configure_swagger_ui/tutorial002_py310.py | 8 + .../configure_swagger_ui/tutorial003_py310.py | 8 + .../tutorial001_an_py39.py | 17 -- .../cookie_param_models/tutorial001_py39.py | 17 -- .../tutorial002_an_py39.py | 19 -- .../cookie_param_models/tutorial002_py39.py | 19 -- docs_src/cookie_params/tutorial001_an_py39.py | 10 - docs_src/cookie_params/tutorial001_py39.py | 10 - docs_src/cors/tutorial001_py310.py | 24 ++ docs_src/custom_docs_ui/tutorial001_py310.py | 38 +++ docs_src/custom_docs_ui/tutorial002_py310.py | 41 +++ .../tutorial001_an_py39.py | 35 --- .../tutorial001_py39.py | 35 --- .../tutorial002_an_py39.py | 29 -- .../tutorial002_py39.py | 29 -- .../tutorial003_py39.py | 39 --- docs_src/custom_response/tutorial001_py310.py | 9 + .../custom_response/tutorial001b_py310.py | 9 + docs_src/custom_response/tutorial002_py310.py | 18 ++ docs_src/custom_response/tutorial003_py310.py | 19 ++ docs_src/custom_response/tutorial004_py310.py | 23 ++ docs_src/custom_response/tutorial005_py310.py | 9 + docs_src/custom_response/tutorial006_py310.py | 9 + .../custom_response/tutorial006b_py310.py | 9 + .../custom_response/tutorial006c_py310.py | 9 + docs_src/custom_response/tutorial007_py310.py | 14 + docs_src/custom_response/tutorial008_py310.py | 14 + docs_src/custom_response/tutorial009_py310.py | 10 + .../custom_response/tutorial009b_py310.py | 10 + .../custom_response/tutorial009c_py310.py | 19 ++ docs_src/custom_response/tutorial010_py310.py | 9 + docs_src/dataclasses_/tutorial001_py39.py | 20 -- docs_src/dataclasses_/tutorial002_py39.py | 26 -- docs_src/dataclasses_/tutorial003_py39.py | 55 ---- docs_src/debugging/tutorial001_py310.py | 15 ++ .../dependencies/tutorial001_02_an_py39.py | 24 -- docs_src/dependencies/tutorial001_an_py39.py | 21 -- docs_src/dependencies/tutorial001_py39.py | 21 -- docs_src/dependencies/tutorial002_an_py39.py | 25 -- docs_src/dependencies/tutorial002_py39.py | 25 -- docs_src/dependencies/tutorial003_an_py39.py | 25 -- docs_src/dependencies/tutorial003_py39.py | 25 -- docs_src/dependencies/tutorial004_an_py39.py | 25 -- docs_src/dependencies/tutorial004_py39.py | 25 -- docs_src/dependencies/tutorial005_an_py39.py | 25 -- docs_src/dependencies/tutorial005_py39.py | 23 -- docs_src/dependencies/tutorial006_an_py310.py | 21 ++ docs_src/dependencies/tutorial006_py310.py | 19 ++ docs_src/dependencies/tutorial007_py310.py | 6 + docs_src/dependencies/tutorial008_an_py310.py | 27 ++ docs_src/dependencies/tutorial008_py310.py | 25 ++ .../dependencies/tutorial008b_an_py310.py | 32 +++ docs_src/dependencies/tutorial008b_py310.py | 30 +++ .../dependencies/tutorial008c_an_py310.py | 29 ++ docs_src/dependencies/tutorial008c_py310.py | 27 ++ .../dependencies/tutorial008d_an_py310.py | 30 +++ docs_src/dependencies/tutorial008d_py310.py | 28 ++ .../dependencies/tutorial008e_an_py310.py | 17 ++ docs_src/dependencies/tutorial008e_py310.py | 15 ++ docs_src/dependencies/tutorial010_py310.py | 14 + docs_src/dependencies/tutorial011_an_py310.py | 23 ++ docs_src/dependencies/tutorial011_py310.py | 21 ++ docs_src/dependencies/tutorial012_an_py310.py | 27 ++ docs_src/dependencies/tutorial012_py310.py | 25 ++ .../dependency_testing/tutorial001_an_py39.py | 59 ----- .../dependency_testing/tutorial001_py39.py | 59 ----- docs_src/encoder/tutorial001_py39.py | 23 -- docs_src/events/tutorial001_py310.py | 16 ++ docs_src/events/tutorial002_py310.py | 14 + docs_src/events/tutorial003_py310.py | 28 ++ .../extending_openapi/tutorial001_py310.py | 29 ++ .../extra_data_types/tutorial001_an_py39.py | 28 -- docs_src/extra_data_types/tutorial001_py39.py | 28 -- docs_src/extra_models/tutorial001_py39.py | 43 --- docs_src/extra_models/tutorial002_py39.py | 41 --- docs_src/extra_models/tutorial003_py39.py | 35 --- docs_src/extra_models/tutorial004_py310.py | 20 ++ docs_src/extra_models/tutorial005_py310.py | 8 + docs_src/first_steps/tutorial001_py310.py | 8 + docs_src/first_steps/tutorial003_py310.py | 8 + .../generate_clients/tutorial001_py310.py | 26 ++ .../generate_clients/tutorial002_py310.py | 36 +++ .../generate_clients/tutorial003_py310.py | 42 +++ .../generate_clients/tutorial004_py310.py | 15 ++ docs_src/graphql_/tutorial001_py310.py | 25 ++ docs_src/handling_errors/tutorial001_py310.py | 12 + docs_src/handling_errors/tutorial002_py310.py | 16 ++ docs_src/handling_errors/tutorial003_py310.py | 25 ++ docs_src/handling_errors/tutorial004_py310.py | 26 ++ docs_src/handling_errors/tutorial005_py310.py | 25 ++ docs_src/handling_errors/tutorial006_py310.py | 28 ++ .../tutorial001_an_py39.py | 19 -- .../header_param_models/tutorial001_py39.py | 19 -- .../tutorial002_an_py39.py | 21 -- .../header_param_models/tutorial002_py39.py | 21 -- .../tutorial003_an_py39.py | 21 -- .../header_param_models/tutorial003_py39.py | 19 -- docs_src/header_params/tutorial001_an_py39.py | 10 - docs_src/header_params/tutorial001_py39.py | 10 - docs_src/header_params/tutorial002_an_py39.py | 14 - docs_src/header_params/tutorial002_py39.py | 12 - docs_src/header_params/tutorial003_an_py39.py | 10 - docs_src/header_params/tutorial003_py39.py | 10 - docs_src/metadata/tutorial001_1_py310.py | 38 +++ docs_src/metadata/tutorial001_py310.py | 38 +++ docs_src/metadata/tutorial002_py310.py | 8 + docs_src/metadata/tutorial003_py310.py | 8 + docs_src/metadata/tutorial004_py310.py | 28 ++ docs_src/middleware/tutorial001_py310.py | 14 + .../openapi_callbacks/tutorial001_py39.py | 53 ---- .../openapi_webhooks/tutorial001_py310.py | 25 ++ .../tutorial001_py310.py | 8 + .../tutorial002_py310.py | 24 ++ .../tutorial003_py310.py | 8 + .../tutorial004_py39.py | 30 --- .../tutorial005_py310.py | 8 + .../tutorial006_py310.py | 41 +++ ...al007_pv1_py39.py => tutorial007_py310.py} | 8 +- .../tutorial001_py39.py | 19 -- .../tutorial002_py39.py | 29 -- .../tutorial002b_py310.py | 20 ++ .../tutorial003_py39.py | 23 -- .../tutorial004_py39.py | 28 -- .../tutorial005_py39.py | 32 --- .../tutorial006_py310.py | 18 ++ docs_src/path_params/tutorial001_py310.py | 8 + docs_src/path_params/tutorial002_py310.py | 8 + docs_src/path_params/tutorial003_py310.py | 13 + docs_src/path_params/tutorial003b_py310.py | 13 + docs_src/path_params/tutorial004_py310.py | 8 + docs_src/path_params/tutorial005_py310.py | 23 ++ .../tutorial002_an_py310.py | 15 ++ .../tutorial002_py310.py | 11 + .../tutorial003_an_py310.py | 15 ++ .../tutorial003_py310.py | 11 + .../tutorial004_an_py310.py | 15 ++ .../tutorial004_py310.py | 13 + .../tutorial005_an_py310.py | 16 ++ .../tutorial005_py310.py | 15 ++ ...001_an_py39.py => tutorial006_an_py310.py} | 10 +- ...torial001_py39.py => tutorial006_py310.py} | 10 +- .../pydantic_v1_in_v2/tutorial001_an_py39.py | 9 - .../pydantic_v1_in_v2/tutorial002_an_py39.py | 18 -- .../pydantic_v1_in_v2/tutorial003_an_py39.py | 25 -- .../pydantic_v1_in_v2/tutorial004_an_py39.py | 19 -- docs_src/python_types/tutorial001_py310.py | 6 + docs_src/python_types/tutorial002_py310.py | 6 + docs_src/python_types/tutorial003_py310.py | 3 + docs_src/python_types/tutorial004_py310.py | 3 + docs_src/python_types/tutorial005_py310.py | 2 + docs_src/python_types/tutorial006_py310.py | 3 + docs_src/python_types/tutorial007_py310.py | 2 + docs_src/python_types/tutorial008_py310.py | 4 + docs_src/python_types/tutorial010_py310.py | 7 + docs_src/python_types/tutorial011_py39.py | 23 -- docs_src/python_types/tutorial012_py39.py | 8 - docs_src/python_types/tutorial013_py310.py | 5 + .../query_param_models/tutorial001_an_py39.py | 18 -- .../query_param_models/tutorial001_py39.py | 18 -- .../query_param_models/tutorial002_an_py39.py | 20 -- .../query_param_models/tutorial002_py39.py | 20 -- docs_src/query_params/tutorial001_py310.py | 10 + docs_src/query_params/tutorial002_py39.py | 12 - docs_src/query_params/tutorial003_py39.py | 17 -- docs_src/query_params/tutorial004_py39.py | 19 -- docs_src/query_params/tutorial005_py310.py | 9 + docs_src/query_params/tutorial006_py39.py | 13 - .../tutorial001_py39.py | 13 - .../tutorial002_an_py39.py | 13 - .../tutorial003_an_py39.py | 15 -- .../tutorial003_py39.py | 15 -- .../tutorial004_an_py39.py | 17 -- .../tutorial004_py39.py | 17 -- .../tutorial004_regex_an_py310.py | 17 -- ...ial002_py39.py => tutorial005_an_py310.py} | 4 +- ...torial009_py39.py => tutorial005_py310.py} | 4 +- ...al006c_py39.py => tutorial006_an_py310.py} | 4 +- ...al006c_an_py39.py => tutorial006_py310.py} | 4 +- .../tutorial007_an_py39.py | 15 -- .../tutorial007_py39.py | 15 -- .../tutorial008_an_py39.py | 22 -- .../tutorial008_py39.py | 20 -- .../tutorial009_an_py39.py | 13 - .../tutorial010_an_py39.py | 26 -- .../tutorial010_py39.py | 24 -- ...011_an_py39.py => tutorial012_an_py310.py} | 4 +- .../tutorial012_py310.py | 9 + ...ial011_py39.py => tutorial013_an_py310.py} | 4 +- .../tutorial013_py310.py | 9 + .../tutorial014_an_py39.py | 15 -- .../tutorial014_py39.py | 15 -- .../tutorial015_an_py39.py | 30 --- .../request_files/tutorial001_02_an_py39.py | 21 -- docs_src/request_files/tutorial001_02_py39.py | 21 -- .../request_files/tutorial001_03_an_py310.py | 17 ++ .../request_files/tutorial001_03_py310.py | 15 ++ .../request_files/tutorial001_an_py310.py | 15 ++ docs_src/request_files/tutorial001_py310.py | 13 + .../request_files/tutorial002_an_py310.py | 33 +++ docs_src/request_files/tutorial002_py310.py | 31 +++ .../request_files/tutorial003_an_py310.py | 39 +++ docs_src/request_files/tutorial003_py310.py | 35 +++ .../tutorial001_an_py310.py | 16 ++ .../request_form_models/tutorial001_py310.py | 14 + .../tutorial002_an_py310.py | 17 ++ .../request_form_models/tutorial002_py310.py | 15 ++ .../request_forms/tutorial001_an_py310.py | 10 + docs_src/request_forms/tutorial001_py310.py | 8 + .../tutorial001_an_py310.py | 18 ++ .../tutorial001_py310.py | 14 + .../tutorial001_py310.py | 13 + .../response_cookies/tutorial001_py310.py | 12 + .../response_cookies/tutorial002_py310.py | 9 + .../response_directly/tutorial001_py39.py | 22 -- .../response_directly/tutorial002_py310.py | 18 ++ .../response_headers/tutorial001_py310.py | 11 + .../response_headers/tutorial002_py310.py | 9 + .../response_model/tutorial001_01_py39.py | 27 -- docs_src/response_model/tutorial001_py39.py | 27 -- docs_src/response_model/tutorial002_py39.py | 19 -- .../response_model/tutorial003_01_py39.py | 21 -- .../response_model/tutorial003_02_py310.py | 11 + .../response_model/tutorial003_03_py310.py | 9 + .../response_model/tutorial003_04_py39.py | 13 - .../response_model/tutorial003_05_py39.py | 13 - docs_src/response_model/tutorial003_py39.py | 24 -- docs_src/response_model/tutorial004_py39.py | 26 -- docs_src/response_model/tutorial005_py39.py | 39 --- docs_src/response_model/tutorial006_py39.py | 39 --- .../response_status_code/tutorial001_py310.py | 8 + .../response_status_code/tutorial002_py310.py | 8 + .../tutorial001_pv1_py310.py | 29 -- .../tutorial001_pv1_py39.py | 31 --- .../schema_extra_example/tutorial001_py39.py | 32 --- .../schema_extra_example/tutorial002_py39.py | 19 -- .../tutorial003_an_py39.py | 34 --- .../schema_extra_example/tutorial003_py39.py | 31 --- .../tutorial004_an_py39.py | 43 --- .../schema_extra_example/tutorial004_py39.py | 40 --- .../tutorial005_an_py39.py | 54 ---- .../schema_extra_example/tutorial005_py39.py | 51 ---- docs_src/security/tutorial001_an_py310.py | 13 + docs_src/security/tutorial001_py310.py | 11 + docs_src/security/tutorial002_an_py39.py | 32 --- docs_src/security/tutorial002_py39.py | 32 --- docs_src/security/tutorial003_an_py39.py | 94 ------- docs_src/security/tutorial003_py39.py | 90 ------- docs_src/security/tutorial004_an_py39.py | 147 ----------- docs_src/security/tutorial004_py39.py | 141 ---------- docs_src/security/tutorial005_an_py39.py | 179 ------------- docs_src/security/tutorial005_py39.py | 177 ------------- docs_src/security/tutorial006_an_py310.py | 13 + docs_src/security/tutorial006_py310.py | 11 + docs_src/security/tutorial007_an_py310.py | 36 +++ docs_src/security/tutorial007_py310.py | 33 +++ .../tutorial001_py39.py | 28 -- .../tutorial002_py39.py | 28 -- docs_src/settings/app01_py310/__init__.py | 0 .../config_pv1.py => app01_py310/config.py} | 6 +- docs_src/settings/app01_py310/main.py | 14 + docs_src/settings/app02_an_py310/__init__.py | 0 .../config.py} | 5 +- docs_src/settings/app02_an_py310/main.py | 22 ++ docs_src/settings/app02_an_py310/test_main.py | 23 ++ docs_src/settings/app02_py310/__init__.py | 0 docs_src/settings/app02_py310/config.py | 7 + docs_src/settings/app02_py310/main.py | 21 ++ docs_src/settings/app02_py310/test_main.py | 23 ++ docs_src/settings/app03_an_py310/__init__.py | 0 docs_src/settings/app03_an_py310/config.py | 9 + docs_src/settings/app03_an_py310/main.py | 22 ++ docs_src/settings/app03_py310/__init__.py | 0 docs_src/settings/app03_py310/config.py | 9 + docs_src/settings/app03_py310/main.py | 21 ++ ...al001_pv1_py39.py => tutorial001_py310.py} | 2 +- docs_src/sql_databases/tutorial001_an_py39.py | 73 ------ docs_src/sql_databases/tutorial001_py39.py | 71 ----- docs_src/sql_databases/tutorial002_an_py39.py | 103 -------- docs_src/sql_databases/tutorial002_py39.py | 104 -------- docs_src/static_files/tutorial001_py310.py | 6 + .../sub_applications/tutorial001_py310.py | 19 ++ docs_src/templates/tutorial001_py310.py | 18 ++ .../tutorial001_py310.py | 9 + docs_src/websockets/tutorial001_py310.py | 51 ++++ docs_src/websockets/tutorial002_an_py39.py | 92 ------- docs_src/websockets/tutorial002_py39.py | 91 ------- docs_src/websockets/tutorial003_py310.py | 81 ++++++ docs_src/wsgi/tutorial001_py310.py | 23 ++ pyproject.toml | 247 ++++++++++++++++-- .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 1 - .../test_tutorial003.py | 2 +- .../test_tutorial004.py | 1 - .../test_tutorial001.py | 2 - .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial003.py | 2 +- .../test_async_tests/test_main_a.py | 2 +- .../test_tutorial001.py | 2 +- .../test_background_tasks/test_tutorial001.py | 2 +- .../test_background_tasks/test_tutorial002.py | 2 - .../test_behind_a_proxy/test_tutorial001.py | 2 +- .../test_tutorial001_01.py | 2 +- .../test_behind_a_proxy/test_tutorial002.py | 2 +- .../test_behind_a_proxy/test_tutorial003.py | 2 +- .../test_behind_a_proxy/test_tutorial004.py | 2 +- .../test_bigger_applications/test_main.py | 3 +- .../test_body/test_tutorial001.py | 1 - .../test_body/test_tutorial002.py | 6 +- .../test_body/test_tutorial003.py | 1 - .../test_body/test_tutorial004.py | 1 - .../test_body_fields/test_tutorial001.py | 2 - .../test_tutorial001.py | 2 - .../test_tutorial002.py | 1 - .../test_tutorial003.py | 2 - .../test_tutorial004.py | 2 - .../test_tutorial005.py | 2 - ...est_tutorial001_tutorial002_tutorial003.py | 3 - .../test_tutorial004.py | 1 - .../test_tutorial005.py | 1 - .../test_tutorial006.py | 1 - .../test_tutorial007.py | 1 - .../test_tutorial008.py | 2 +- .../test_tutorial009.py | 2 +- .../test_body_updates/test_tutorial001.py | 1 - .../test_body_updates/test_tutorial002.py | 1 - .../test_tutorial001.py | 6 +- .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial003.py | 2 +- .../test_tutorial001.py | 2 - .../test_tutorial002.py | 2 - .../test_cookie_params/test_tutorial001.py | 2 - .../test_cors/test_tutorial001.py | 2 +- .../test_custom_docs_ui/test_tutorial001.py | 2 +- .../test_custom_docs_ui/test_tutorial002.py | 2 +- .../test_tutorial001.py | 2 - .../test_tutorial002.py | 2 - .../test_tutorial003.py | 1 - .../test_custom_response/test_tutorial001.py | 4 +- .../test_custom_response/test_tutorial001b.py | 2 +- ...est_tutorial002_tutorial003_tutorial004.py | 6 +- .../test_custom_response/test_tutorial005.py | 2 +- .../test_custom_response/test_tutorial006.py | 2 +- .../test_custom_response/test_tutorial006b.py | 2 +- .../test_custom_response/test_tutorial006c.py | 2 +- .../test_custom_response/test_tutorial007.py | 2 +- .../test_custom_response/test_tutorial008.py | 6 +- .../test_custom_response/test_tutorial009.py | 6 +- .../test_custom_response/test_tutorial009b.py | 6 +- .../test_custom_response/test_tutorial009c.py | 2 +- .../test_dataclasses/test_tutorial001.py | 1 - .../test_dataclasses/test_tutorial002.py | 1 - .../test_dataclasses/test_tutorial003.py | 1 - .../test_debugging/test_tutorial001.py | 2 +- .../test_tutorial001_tutorial001_02.py | 3 - ...est_tutorial002_tutorial003_tutorial004.py | 6 - .../test_dependencies/test_tutorial005.py | 2 - .../test_dependencies/test_tutorial006.py | 4 +- .../test_dependencies/test_tutorial007.py | 4 +- .../test_dependencies/test_tutorial008.py | 4 +- .../test_dependencies/test_tutorial008b.py | 4 +- .../test_dependencies/test_tutorial008c.py | 4 +- .../test_dependencies/test_tutorial008d.py | 4 +- .../test_dependencies/test_tutorial008e.py | 4 +- .../test_dependencies/test_tutorial010.py | 4 +- .../test_dependencies/test_tutorial011.py | 4 +- .../test_dependencies/test_tutorial012.py | 4 +- .../test_encoder/test_tutorial001.py | 1 - .../test_events/test_tutorial001.py | 2 +- .../test_events/test_tutorial002.py | 2 +- .../test_events/test_tutorial003.py | 2 +- .../test_tutorial001.py | 2 +- .../test_extra_data_types/test_tutorial001.py | 2 - .../test_tutorial001_tutorial002.py | 2 - .../test_extra_models/test_tutorial003.py | 1 - .../test_extra_models/test_tutorial004.py | 2 +- .../test_extra_models/test_tutorial005.py | 2 +- ...est_tutorial001_tutorial002_tutorial003.py | 4 +- .../test_generate_clients/test_tutorial001.py | 2 +- .../test_generate_clients/test_tutorial002.py | 2 +- .../test_generate_clients/test_tutorial003.py | 2 +- .../test_generate_clients/test_tutorial004.py | 6 +- .../test_graphql/test_tutorial001.py | 2 +- .../test_handling_errors/test_tutorial001.py | 2 +- .../test_handling_errors/test_tutorial002.py | 2 +- .../test_handling_errors/test_tutorial003.py | 2 +- .../test_handling_errors/test_tutorial004.py | 2 +- .../test_handling_errors/test_tutorial005.py | 2 +- .../test_handling_errors/test_tutorial006.py | 2 +- .../test_tutorial001.py | 2 - .../test_tutorial002.py | 2 - .../test_tutorial003.py | 2 - .../test_header_params/test_tutorial001.py | 2 - .../test_header_params/test_tutorial002.py | 2 - .../test_header_params/test_tutorial003.py | 2 - .../test_metadata/test_tutorial001.py | 2 +- .../test_metadata/test_tutorial001_1.py | 2 +- .../test_metadata/test_tutorial002.py | 2 +- .../test_metadata/test_tutorial003.py | 2 +- .../test_metadata/test_tutorial004.py | 2 +- .../test_middleware/test_tutorial001.py | 2 +- .../test_tutorial001.py | 1 - .../test_openapi_webhooks/test_tutorial001.py | 2 +- .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial003.py | 2 +- .../test_tutorial004.py | 1 - .../test_tutorial005.py | 2 +- .../test_tutorial006.py | 2 +- .../test_tutorial007.py | 2 +- .../test_tutorial001.py | 1 - .../test_tutorial002.py | 1 - .../test_tutorial002b.py | 2 +- .../test_tutorial003_tutorial004.py | 2 - .../test_tutorial005.py | 1 - .../test_tutorial006.py | 2 +- .../test_path_params/test_tutorial001.py | 2 +- .../test_path_params/test_tutorial002.py | 2 +- .../test_path_params/test_tutorial003.py | 2 +- .../test_path_params/test_tutorial003b.py | 2 +- .../test_path_params/test_tutorial004.py | 2 +- .../test_path_params/test_tutorial005.py | 2 +- .../test_tutorial001.py | 2 - .../test_tutorial002_tutorial003.py | 8 +- .../test_tutorial004.py | 4 +- .../test_tutorial005.py | 4 +- .../test_tutorial006.py | 4 +- .../test_tutorial001_tutorial002.py | 4 +- .../test_python_types/test_tutorial003.py | 2 +- .../test_python_types/test_tutorial004.py | 2 +- .../test_python_types/test_tutorial005.py | 2 +- .../test_python_types/test_tutorial006.py | 2 +- .../test_python_types/test_tutorial007.py | 2 +- .../test_python_types/test_tutorial008.py | 2 +- .../test_python_types/test_tutorial008b.py | 2 +- .../test_tutorial009_tutorial009b.py | 3 +- .../test_python_types/test_tutorial009c.py | 2 +- .../test_python_types/test_tutorial010.py | 2 +- .../test_python_types/test_tutorial011.py | 1 - .../test_python_types/test_tutorial012.py | 7 - .../test_python_types/test_tutorial013.py | 2 +- .../test_tutorial001.py | 2 - .../test_tutorial002.py | 2 - .../test_query_params/test_tutorial001.py | 2 +- .../test_query_params/test_tutorial002.py | 1 - .../test_query_params/test_tutorial003.py | 1 - .../test_query_params/test_tutorial004.py | 1 - .../test_query_params/test_tutorial005.py | 2 +- .../test_query_params/test_tutorial006.py | 1 - .../test_tutorial001.py | 1 - .../test_tutorial002.py | 2 - .../test_tutorial003.py | 2 - .../test_tutorial004.py | 11 - .../test_tutorial005.py | 4 +- .../test_tutorial006.py | 4 +- .../test_tutorial006c.py | 2 - .../test_tutorial007.py | 2 - .../test_tutorial008.py | 2 - .../test_tutorial009.py | 2 - .../test_tutorial010.py | 2 - .../test_tutorial011.py | 2 - .../test_tutorial012.py | 4 +- .../test_tutorial013.py | 4 +- .../test_tutorial014.py | 2 - .../test_tutorial015.py | 1 - .../test_request_files/test_tutorial001.py | 4 +- .../test_request_files/test_tutorial001_02.py | 2 - .../test_request_files/test_tutorial001_03.py | 4 +- .../test_request_files/test_tutorial002.py | 4 +- .../test_request_files/test_tutorial003.py | 4 +- .../test_tutorial001.py | 4 +- .../test_tutorial002.py | 4 +- .../test_request_forms/test_tutorial001.py | 4 +- .../test_tutorial001.py | 4 +- .../test_tutorial001.py | 2 +- .../test_response_cookies/test_tutorial001.py | 2 +- .../test_response_cookies/test_tutorial002.py | 2 +- .../test_tutorial001.py | 1 - .../test_tutorial002.py | 2 +- .../test_response_headers/test_tutorial001.py | 2 +- .../test_response_headers/test_tutorial002.py | 2 +- .../test_tutorial001_tutorial001_01.py | 2 - .../test_response_model/test_tutorial002.py | 1 - .../test_response_model/test_tutorial003.py | 1 - .../test_tutorial003_01.py | 1 - .../test_tutorial003_02.py | 2 +- .../test_tutorial003_03.py | 2 +- .../test_tutorial003_04.py | 1 - .../test_tutorial003_05.py | 1 - .../test_response_model/test_tutorial004.py | 1 - .../test_response_model/test_tutorial005.py | 1 - .../test_response_model/test_tutorial006.py | 1 - .../test_tutorial001_tutorial002.py | 4 +- .../test_tutorial001.py | 1 - .../test_tutorial002.py | 1 - .../test_tutorial003.py | 2 - .../test_tutorial004.py | 2 - .../test_tutorial005.py | 2 - .../test_security/test_tutorial001.py | 4 +- .../test_security/test_tutorial002.py | 2 - .../test_security/test_tutorial003.py | 2 - .../test_security/test_tutorial004.py | 2 - .../test_security/test_tutorial005.py | 2 - .../test_security/test_tutorial006.py | 4 +- .../test_security/test_tutorial007.py | 4 +- .../test_tutorial001.py | 1 - .../test_tutorial002.py | 1 - .../test_tutorial/test_settings/test_app01.py | 2 +- .../test_tutorial/test_settings/test_app02.py | 4 +- .../test_tutorial/test_settings/test_app03.py | 4 +- .../test_settings/test_tutorial001.py | 2 +- .../test_sql_databases/test_tutorial001.py | 2 - .../test_sql_databases/test_tutorial002.py | 2 - .../test_static_files/test_tutorial001.py | 2 +- .../test_sub_applications/test_tutorial001.py | 2 +- .../test_templates/test_tutorial001.py | 2 +- .../test_tutorial/test_testing/test_main_a.py | 2 +- .../test_tutorial/test_testing/test_main_b.py | 2 - .../test_testing/test_tutorial001.py | 2 +- .../test_testing/test_tutorial002.py | 2 +- .../test_testing/test_tutorial003.py | 2 +- .../test_testing/test_tutorial004.py | 2 +- .../test_tutorial001.py | 2 - .../test_tutorial001.py | 2 +- .../test_websockets/test_tutorial001.py | 2 +- .../test_websockets/test_tutorial002.py | 2 - .../test_websockets/test_tutorial003.py | 2 +- .../test_wsgi/test_tutorial001.py | 2 +- 655 files changed, 3703 insertions(+), 5660 deletions(-) create mode 100644 docs_src/additional_responses/tutorial001_py310.py delete mode 100644 docs_src/additional_responses/tutorial002_py39.py create mode 100644 docs_src/additional_responses/tutorial003_py310.py delete mode 100644 docs_src/additional_responses/tutorial004_py39.py delete mode 100644 docs_src/additional_status_codes/tutorial001_an_py39.py delete mode 100644 docs_src/additional_status_codes/tutorial001_py39.py create mode 100644 docs_src/advanced_middleware/tutorial001_py310.py create mode 100644 docs_src/advanced_middleware/tutorial002_py310.py create mode 100644 docs_src/advanced_middleware/tutorial003_py310.py rename docs_src/app_testing/{app_b_an_py39 => app_a_py310}/__init__.py (100%) create mode 100644 docs_src/app_testing/app_a_py310/main.py create mode 100644 docs_src/app_testing/app_a_py310/test_main.py delete mode 100644 docs_src/app_testing/app_b_an_py39/main.py delete mode 100644 docs_src/app_testing/app_b_an_py39/test_main.py delete mode 100644 docs_src/app_testing/app_b_py39/main.py delete mode 100644 docs_src/app_testing/app_b_py39/test_main.py create mode 100644 docs_src/app_testing/tutorial001_py310.py create mode 100644 docs_src/app_testing/tutorial002_py310.py create mode 100644 docs_src/app_testing/tutorial003_py310.py create mode 100644 docs_src/app_testing/tutorial004_py310.py rename docs_src/{app_testing/app_b_py39 => async_tests/app_a_py310}/__init__.py (100%) create mode 100644 docs_src/async_tests/app_a_py310/main.py create mode 100644 docs_src/async_tests/app_a_py310/test_main.py create mode 100644 docs_src/authentication_error_status_code/tutorial001_an_py310.py create mode 100644 docs_src/background_tasks/tutorial001_py310.py delete mode 100644 docs_src/background_tasks/tutorial002_an_py39.py delete mode 100644 docs_src/background_tasks/tutorial002_py39.py create mode 100644 docs_src/behind_a_proxy/tutorial001_01_py310.py create mode 100644 docs_src/behind_a_proxy/tutorial001_py310.py create mode 100644 docs_src/behind_a_proxy/tutorial002_py310.py create mode 100644 docs_src/behind_a_proxy/tutorial003_py310.py create mode 100644 docs_src/behind_a_proxy/tutorial004_py310.py rename docs_src/bigger_applications/{app_py39/internal => app_an_py310}/__init__.py (100%) create mode 100644 docs_src/bigger_applications/app_an_py310/dependencies.py rename docs_src/bigger_applications/{app_py39/routers => app_an_py310/internal}/__init__.py (100%) rename docs_src/bigger_applications/{app_py39 => app_an_py310}/internal/admin.py (100%) create mode 100644 docs_src/bigger_applications/app_an_py310/main.py create mode 100644 docs_src/bigger_applications/app_an_py310/routers/__init__.py rename docs_src/bigger_applications/{app_py39 => app_an_py310}/routers/items.py (100%) rename docs_src/bigger_applications/{app_py39 => app_an_py310}/routers/users.py (100%) delete mode 100644 docs_src/body/tutorial001_py39.py delete mode 100644 docs_src/body/tutorial002_py39.py delete mode 100644 docs_src/body/tutorial003_py39.py delete mode 100644 docs_src/body/tutorial004_py39.py delete mode 100644 docs_src/body_fields/tutorial001_an_py39.py delete mode 100644 docs_src/body_fields/tutorial001_py39.py delete mode 100644 docs_src/body_multiple_params/tutorial001_an_py39.py delete mode 100644 docs_src/body_multiple_params/tutorial001_py39.py delete mode 100644 docs_src/body_multiple_params/tutorial002_py39.py delete mode 100644 docs_src/body_multiple_params/tutorial003_an_py39.py delete mode 100644 docs_src/body_multiple_params/tutorial003_py39.py delete mode 100644 docs_src/body_multiple_params/tutorial004_an_py39.py delete mode 100644 docs_src/body_multiple_params/tutorial004_py39.py delete mode 100644 docs_src/body_multiple_params/tutorial005_an_py39.py delete mode 100644 docs_src/body_multiple_params/tutorial005_py39.py delete mode 100644 docs_src/body_nested_models/tutorial001_py39.py delete mode 100644 docs_src/body_nested_models/tutorial002_py39.py delete mode 100644 docs_src/body_nested_models/tutorial003_py39.py delete mode 100644 docs_src/body_nested_models/tutorial004_py39.py delete mode 100644 docs_src/body_nested_models/tutorial005_py39.py delete mode 100644 docs_src/body_nested_models/tutorial006_py39.py delete mode 100644 docs_src/body_nested_models/tutorial007_py39.py create mode 100644 docs_src/body_nested_models/tutorial008_py310.py create mode 100644 docs_src/body_nested_models/tutorial009_py310.py delete mode 100644 docs_src/body_updates/tutorial001_py39.py delete mode 100644 docs_src/body_updates/tutorial002_py39.py create mode 100644 docs_src/conditional_openapi/tutorial001_py310.py create mode 100644 docs_src/configure_swagger_ui/tutorial001_py310.py create mode 100644 docs_src/configure_swagger_ui/tutorial002_py310.py create mode 100644 docs_src/configure_swagger_ui/tutorial003_py310.py delete mode 100644 docs_src/cookie_param_models/tutorial001_an_py39.py delete mode 100644 docs_src/cookie_param_models/tutorial001_py39.py delete mode 100644 docs_src/cookie_param_models/tutorial002_an_py39.py delete mode 100644 docs_src/cookie_param_models/tutorial002_py39.py delete mode 100644 docs_src/cookie_params/tutorial001_an_py39.py delete mode 100644 docs_src/cookie_params/tutorial001_py39.py create mode 100644 docs_src/cors/tutorial001_py310.py create mode 100644 docs_src/custom_docs_ui/tutorial001_py310.py create mode 100644 docs_src/custom_docs_ui/tutorial002_py310.py delete mode 100644 docs_src/custom_request_and_route/tutorial001_an_py39.py delete mode 100644 docs_src/custom_request_and_route/tutorial001_py39.py delete mode 100644 docs_src/custom_request_and_route/tutorial002_an_py39.py delete mode 100644 docs_src/custom_request_and_route/tutorial002_py39.py delete mode 100644 docs_src/custom_request_and_route/tutorial003_py39.py create mode 100644 docs_src/custom_response/tutorial001_py310.py create mode 100644 docs_src/custom_response/tutorial001b_py310.py create mode 100644 docs_src/custom_response/tutorial002_py310.py create mode 100644 docs_src/custom_response/tutorial003_py310.py create mode 100644 docs_src/custom_response/tutorial004_py310.py create mode 100644 docs_src/custom_response/tutorial005_py310.py create mode 100644 docs_src/custom_response/tutorial006_py310.py create mode 100644 docs_src/custom_response/tutorial006b_py310.py create mode 100644 docs_src/custom_response/tutorial006c_py310.py create mode 100644 docs_src/custom_response/tutorial007_py310.py create mode 100644 docs_src/custom_response/tutorial008_py310.py create mode 100644 docs_src/custom_response/tutorial009_py310.py create mode 100644 docs_src/custom_response/tutorial009b_py310.py create mode 100644 docs_src/custom_response/tutorial009c_py310.py create mode 100644 docs_src/custom_response/tutorial010_py310.py delete mode 100644 docs_src/dataclasses_/tutorial001_py39.py delete mode 100644 docs_src/dataclasses_/tutorial002_py39.py delete mode 100644 docs_src/dataclasses_/tutorial003_py39.py create mode 100644 docs_src/debugging/tutorial001_py310.py delete mode 100644 docs_src/dependencies/tutorial001_02_an_py39.py delete mode 100644 docs_src/dependencies/tutorial001_an_py39.py delete mode 100644 docs_src/dependencies/tutorial001_py39.py delete mode 100644 docs_src/dependencies/tutorial002_an_py39.py delete mode 100644 docs_src/dependencies/tutorial002_py39.py delete mode 100644 docs_src/dependencies/tutorial003_an_py39.py delete mode 100644 docs_src/dependencies/tutorial003_py39.py delete mode 100644 docs_src/dependencies/tutorial004_an_py39.py delete mode 100644 docs_src/dependencies/tutorial004_py39.py delete mode 100644 docs_src/dependencies/tutorial005_an_py39.py delete mode 100644 docs_src/dependencies/tutorial005_py39.py create mode 100644 docs_src/dependencies/tutorial006_an_py310.py create mode 100644 docs_src/dependencies/tutorial006_py310.py create mode 100644 docs_src/dependencies/tutorial007_py310.py create mode 100644 docs_src/dependencies/tutorial008_an_py310.py create mode 100644 docs_src/dependencies/tutorial008_py310.py create mode 100644 docs_src/dependencies/tutorial008b_an_py310.py create mode 100644 docs_src/dependencies/tutorial008b_py310.py create mode 100644 docs_src/dependencies/tutorial008c_an_py310.py create mode 100644 docs_src/dependencies/tutorial008c_py310.py create mode 100644 docs_src/dependencies/tutorial008d_an_py310.py create mode 100644 docs_src/dependencies/tutorial008d_py310.py create mode 100644 docs_src/dependencies/tutorial008e_an_py310.py create mode 100644 docs_src/dependencies/tutorial008e_py310.py create mode 100644 docs_src/dependencies/tutorial010_py310.py create mode 100644 docs_src/dependencies/tutorial011_an_py310.py create mode 100644 docs_src/dependencies/tutorial011_py310.py create mode 100644 docs_src/dependencies/tutorial012_an_py310.py create mode 100644 docs_src/dependencies/tutorial012_py310.py delete mode 100644 docs_src/dependency_testing/tutorial001_an_py39.py delete mode 100644 docs_src/dependency_testing/tutorial001_py39.py delete mode 100644 docs_src/encoder/tutorial001_py39.py create mode 100644 docs_src/events/tutorial001_py310.py create mode 100644 docs_src/events/tutorial002_py310.py create mode 100644 docs_src/events/tutorial003_py310.py create mode 100644 docs_src/extending_openapi/tutorial001_py310.py delete mode 100644 docs_src/extra_data_types/tutorial001_an_py39.py delete mode 100644 docs_src/extra_data_types/tutorial001_py39.py delete mode 100644 docs_src/extra_models/tutorial001_py39.py delete mode 100644 docs_src/extra_models/tutorial002_py39.py delete mode 100644 docs_src/extra_models/tutorial003_py39.py create mode 100644 docs_src/extra_models/tutorial004_py310.py create mode 100644 docs_src/extra_models/tutorial005_py310.py create mode 100644 docs_src/first_steps/tutorial001_py310.py create mode 100644 docs_src/first_steps/tutorial003_py310.py create mode 100644 docs_src/generate_clients/tutorial001_py310.py create mode 100644 docs_src/generate_clients/tutorial002_py310.py create mode 100644 docs_src/generate_clients/tutorial003_py310.py create mode 100644 docs_src/generate_clients/tutorial004_py310.py create mode 100644 docs_src/graphql_/tutorial001_py310.py create mode 100644 docs_src/handling_errors/tutorial001_py310.py create mode 100644 docs_src/handling_errors/tutorial002_py310.py create mode 100644 docs_src/handling_errors/tutorial003_py310.py create mode 100644 docs_src/handling_errors/tutorial004_py310.py create mode 100644 docs_src/handling_errors/tutorial005_py310.py create mode 100644 docs_src/handling_errors/tutorial006_py310.py delete mode 100644 docs_src/header_param_models/tutorial001_an_py39.py delete mode 100644 docs_src/header_param_models/tutorial001_py39.py delete mode 100644 docs_src/header_param_models/tutorial002_an_py39.py delete mode 100644 docs_src/header_param_models/tutorial002_py39.py delete mode 100644 docs_src/header_param_models/tutorial003_an_py39.py delete mode 100644 docs_src/header_param_models/tutorial003_py39.py delete mode 100644 docs_src/header_params/tutorial001_an_py39.py delete mode 100644 docs_src/header_params/tutorial001_py39.py delete mode 100644 docs_src/header_params/tutorial002_an_py39.py delete mode 100644 docs_src/header_params/tutorial002_py39.py delete mode 100644 docs_src/header_params/tutorial003_an_py39.py delete mode 100644 docs_src/header_params/tutorial003_py39.py create mode 100644 docs_src/metadata/tutorial001_1_py310.py create mode 100644 docs_src/metadata/tutorial001_py310.py create mode 100644 docs_src/metadata/tutorial002_py310.py create mode 100644 docs_src/metadata/tutorial003_py310.py create mode 100644 docs_src/metadata/tutorial004_py310.py create mode 100644 docs_src/middleware/tutorial001_py310.py delete mode 100644 docs_src/openapi_callbacks/tutorial001_py39.py create mode 100644 docs_src/openapi_webhooks/tutorial001_py310.py create mode 100644 docs_src/path_operation_advanced_configuration/tutorial001_py310.py create mode 100644 docs_src/path_operation_advanced_configuration/tutorial002_py310.py create mode 100644 docs_src/path_operation_advanced_configuration/tutorial003_py310.py delete mode 100644 docs_src/path_operation_advanced_configuration/tutorial004_py39.py create mode 100644 docs_src/path_operation_advanced_configuration/tutorial005_py310.py create mode 100644 docs_src/path_operation_advanced_configuration/tutorial006_py310.py rename docs_src/path_operation_advanced_configuration/{tutorial007_pv1_py39.py => tutorial007_py310.py} (68%) delete mode 100644 docs_src/path_operation_configuration/tutorial001_py39.py delete mode 100644 docs_src/path_operation_configuration/tutorial002_py39.py create mode 100644 docs_src/path_operation_configuration/tutorial002b_py310.py delete mode 100644 docs_src/path_operation_configuration/tutorial003_py39.py delete mode 100644 docs_src/path_operation_configuration/tutorial004_py39.py delete mode 100644 docs_src/path_operation_configuration/tutorial005_py39.py create mode 100644 docs_src/path_operation_configuration/tutorial006_py310.py create mode 100644 docs_src/path_params/tutorial001_py310.py create mode 100644 docs_src/path_params/tutorial002_py310.py create mode 100644 docs_src/path_params/tutorial003_py310.py create mode 100644 docs_src/path_params/tutorial003b_py310.py create mode 100644 docs_src/path_params/tutorial004_py310.py create mode 100644 docs_src/path_params/tutorial005_py310.py create mode 100644 docs_src/path_params_numeric_validations/tutorial002_an_py310.py create mode 100644 docs_src/path_params_numeric_validations/tutorial002_py310.py create mode 100644 docs_src/path_params_numeric_validations/tutorial003_an_py310.py create mode 100644 docs_src/path_params_numeric_validations/tutorial003_py310.py create mode 100644 docs_src/path_params_numeric_validations/tutorial004_an_py310.py create mode 100644 docs_src/path_params_numeric_validations/tutorial004_py310.py create mode 100644 docs_src/path_params_numeric_validations/tutorial005_an_py310.py create mode 100644 docs_src/path_params_numeric_validations/tutorial005_py310.py rename docs_src/path_params_numeric_validations/{tutorial001_an_py39.py => tutorial006_an_py310.py} (61%) rename docs_src/path_params_numeric_validations/{tutorial001_py39.py => tutorial006_py310.py} (53%) delete mode 100644 docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py delete mode 100644 docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py delete mode 100644 docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py delete mode 100644 docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py create mode 100644 docs_src/python_types/tutorial001_py310.py create mode 100644 docs_src/python_types/tutorial002_py310.py create mode 100644 docs_src/python_types/tutorial003_py310.py create mode 100644 docs_src/python_types/tutorial004_py310.py create mode 100644 docs_src/python_types/tutorial005_py310.py create mode 100644 docs_src/python_types/tutorial006_py310.py create mode 100644 docs_src/python_types/tutorial007_py310.py create mode 100644 docs_src/python_types/tutorial008_py310.py create mode 100644 docs_src/python_types/tutorial010_py310.py delete mode 100644 docs_src/python_types/tutorial011_py39.py delete mode 100644 docs_src/python_types/tutorial012_py39.py create mode 100644 docs_src/python_types/tutorial013_py310.py delete mode 100644 docs_src/query_param_models/tutorial001_an_py39.py delete mode 100644 docs_src/query_param_models/tutorial001_py39.py delete mode 100644 docs_src/query_param_models/tutorial002_an_py39.py delete mode 100644 docs_src/query_param_models/tutorial002_py39.py create mode 100644 docs_src/query_params/tutorial001_py310.py delete mode 100644 docs_src/query_params/tutorial002_py39.py delete mode 100644 docs_src/query_params/tutorial003_py39.py delete mode 100644 docs_src/query_params/tutorial004_py39.py create mode 100644 docs_src/query_params/tutorial005_py310.py delete mode 100644 docs_src/query_params/tutorial006_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial001_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial002_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial003_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial003_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial004_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial004_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial004_regex_an_py310.py rename docs_src/query_params_str_validations/{tutorial002_py39.py => tutorial005_an_py310.py} (65%) rename docs_src/query_params_str_validations/{tutorial009_py39.py => tutorial005_py310.py} (64%) rename docs_src/query_params_str_validations/{tutorial006c_py39.py => tutorial006_an_py310.py} (69%) rename docs_src/query_params_str_validations/{tutorial006c_an_py39.py => tutorial006_py310.py} (64%) delete mode 100644 docs_src/query_params_str_validations/tutorial007_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial007_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial008_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial008_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial009_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial010_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial010_py39.py rename docs_src/query_params_str_validations/{tutorial011_an_py39.py => tutorial012_an_py310.py} (52%) create mode 100644 docs_src/query_params_str_validations/tutorial012_py310.py rename docs_src/query_params_str_validations/{tutorial011_py39.py => tutorial013_an_py310.py} (56%) create mode 100644 docs_src/query_params_str_validations/tutorial013_py310.py delete mode 100644 docs_src/query_params_str_validations/tutorial014_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial014_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial015_an_py39.py delete mode 100644 docs_src/request_files/tutorial001_02_an_py39.py delete mode 100644 docs_src/request_files/tutorial001_02_py39.py create mode 100644 docs_src/request_files/tutorial001_03_an_py310.py create mode 100644 docs_src/request_files/tutorial001_03_py310.py create mode 100644 docs_src/request_files/tutorial001_an_py310.py create mode 100644 docs_src/request_files/tutorial001_py310.py create mode 100644 docs_src/request_files/tutorial002_an_py310.py create mode 100644 docs_src/request_files/tutorial002_py310.py create mode 100644 docs_src/request_files/tutorial003_an_py310.py create mode 100644 docs_src/request_files/tutorial003_py310.py create mode 100644 docs_src/request_form_models/tutorial001_an_py310.py create mode 100644 docs_src/request_form_models/tutorial001_py310.py create mode 100644 docs_src/request_form_models/tutorial002_an_py310.py create mode 100644 docs_src/request_form_models/tutorial002_py310.py create mode 100644 docs_src/request_forms/tutorial001_an_py310.py create mode 100644 docs_src/request_forms/tutorial001_py310.py create mode 100644 docs_src/request_forms_and_files/tutorial001_an_py310.py create mode 100644 docs_src/request_forms_and_files/tutorial001_py310.py create mode 100644 docs_src/response_change_status_code/tutorial001_py310.py create mode 100644 docs_src/response_cookies/tutorial001_py310.py create mode 100644 docs_src/response_cookies/tutorial002_py310.py delete mode 100644 docs_src/response_directly/tutorial001_py39.py create mode 100644 docs_src/response_directly/tutorial002_py310.py create mode 100644 docs_src/response_headers/tutorial001_py310.py create mode 100644 docs_src/response_headers/tutorial002_py310.py delete mode 100644 docs_src/response_model/tutorial001_01_py39.py delete mode 100644 docs_src/response_model/tutorial001_py39.py delete mode 100644 docs_src/response_model/tutorial002_py39.py delete mode 100644 docs_src/response_model/tutorial003_01_py39.py create mode 100644 docs_src/response_model/tutorial003_02_py310.py create mode 100644 docs_src/response_model/tutorial003_03_py310.py delete mode 100644 docs_src/response_model/tutorial003_04_py39.py delete mode 100644 docs_src/response_model/tutorial003_05_py39.py delete mode 100644 docs_src/response_model/tutorial003_py39.py delete mode 100644 docs_src/response_model/tutorial004_py39.py delete mode 100644 docs_src/response_model/tutorial005_py39.py delete mode 100644 docs_src/response_model/tutorial006_py39.py create mode 100644 docs_src/response_status_code/tutorial001_py310.py create mode 100644 docs_src/response_status_code/tutorial002_py310.py delete mode 100644 docs_src/schema_extra_example/tutorial001_pv1_py310.py delete mode 100644 docs_src/schema_extra_example/tutorial001_pv1_py39.py delete mode 100644 docs_src/schema_extra_example/tutorial001_py39.py delete mode 100644 docs_src/schema_extra_example/tutorial002_py39.py delete mode 100644 docs_src/schema_extra_example/tutorial003_an_py39.py delete mode 100644 docs_src/schema_extra_example/tutorial003_py39.py delete mode 100644 docs_src/schema_extra_example/tutorial004_an_py39.py delete mode 100644 docs_src/schema_extra_example/tutorial004_py39.py delete mode 100644 docs_src/schema_extra_example/tutorial005_an_py39.py delete mode 100644 docs_src/schema_extra_example/tutorial005_py39.py create mode 100644 docs_src/security/tutorial001_an_py310.py create mode 100644 docs_src/security/tutorial001_py310.py delete mode 100644 docs_src/security/tutorial002_an_py39.py delete mode 100644 docs_src/security/tutorial002_py39.py delete mode 100644 docs_src/security/tutorial003_an_py39.py delete mode 100644 docs_src/security/tutorial003_py39.py delete mode 100644 docs_src/security/tutorial004_an_py39.py delete mode 100644 docs_src/security/tutorial004_py39.py delete mode 100644 docs_src/security/tutorial005_an_py39.py delete mode 100644 docs_src/security/tutorial005_py39.py create mode 100644 docs_src/security/tutorial006_an_py310.py create mode 100644 docs_src/security/tutorial006_py310.py create mode 100644 docs_src/security/tutorial007_an_py310.py create mode 100644 docs_src/security/tutorial007_py310.py delete mode 100644 docs_src/separate_openapi_schemas/tutorial001_py39.py delete mode 100644 docs_src/separate_openapi_schemas/tutorial002_py39.py create mode 100644 docs_src/settings/app01_py310/__init__.py rename docs_src/settings/{app03_an_py39/config_pv1.py => app01_py310/config.py} (59%) create mode 100644 docs_src/settings/app01_py310/main.py create mode 100644 docs_src/settings/app02_an_py310/__init__.py rename docs_src/settings/{app03_py39/config_pv1.py => app02_an_py310/config.py} (58%) create mode 100644 docs_src/settings/app02_an_py310/main.py create mode 100644 docs_src/settings/app02_an_py310/test_main.py create mode 100644 docs_src/settings/app02_py310/__init__.py create mode 100644 docs_src/settings/app02_py310/config.py create mode 100644 docs_src/settings/app02_py310/main.py create mode 100644 docs_src/settings/app02_py310/test_main.py create mode 100644 docs_src/settings/app03_an_py310/__init__.py create mode 100644 docs_src/settings/app03_an_py310/config.py create mode 100644 docs_src/settings/app03_an_py310/main.py create mode 100644 docs_src/settings/app03_py310/__init__.py create mode 100644 docs_src/settings/app03_py310/config.py create mode 100644 docs_src/settings/app03_py310/main.py rename docs_src/settings/{tutorial001_pv1_py39.py => tutorial001_py310.py} (89%) delete mode 100644 docs_src/sql_databases/tutorial001_an_py39.py delete mode 100644 docs_src/sql_databases/tutorial001_py39.py delete mode 100644 docs_src/sql_databases/tutorial002_an_py39.py delete mode 100644 docs_src/sql_databases/tutorial002_py39.py create mode 100644 docs_src/static_files/tutorial001_py310.py create mode 100644 docs_src/sub_applications/tutorial001_py310.py create mode 100644 docs_src/templates/tutorial001_py310.py create mode 100644 docs_src/using_request_directly/tutorial001_py310.py create mode 100644 docs_src/websockets/tutorial001_py310.py delete mode 100644 docs_src/websockets/tutorial002_an_py39.py delete mode 100644 docs_src/websockets/tutorial002_py39.py create mode 100644 docs_src/websockets/tutorial003_py310.py create mode 100644 docs_src/wsgi/tutorial001_py310.py delete mode 100644 tests/test_tutorial/test_python_types/test_tutorial012.py diff --git a/docs/en/docs/advanced/additional-responses.md b/docs/en/docs/advanced/additional-responses.md index bb70753ed..6306bd1f9 100644 --- a/docs/en/docs/advanced/additional-responses.md +++ b/docs/en/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ Each of those response `dict`s can have a key `model`, containing a Pydantic mod For example, to declare another response with a status code `404` and a Pydantic model `Message`, you can write: -{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} /// note @@ -203,7 +203,7 @@ For example, you can declare a response with a status code `404` that uses a Pyd And a response with a status code `200` that uses your `response_model`, but includes a custom `example`: -{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} It will all be combined and included in your OpenAPI, and shown in the API docs: diff --git a/docs/en/docs/advanced/advanced-dependencies.md b/docs/en/docs/advanced/advanced-dependencies.md index 0d03507b7..3a23a6d1a 100644 --- a/docs/en/docs/advanced/advanced-dependencies.md +++ b/docs/en/docs/advanced/advanced-dependencies.md @@ -18,7 +18,7 @@ Not the class itself (which is already a callable), but an instance of that clas To do that, we declare a method `__call__`: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} In this case, this `__call__` is what **FastAPI** will use to check for additional parameters and sub-dependencies, and this is what will be called to pass a value to the parameter in your *path operation function* later. @@ -26,7 +26,7 @@ In this case, this `__call__` is what **FastAPI** will use to check for addition And now, we can use `__init__` to declare the parameters of the instance that we can use to "parameterize" the dependency: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} In this case, **FastAPI** won't ever touch or care about `__init__`, we will use it directly in our code. @@ -34,7 +34,7 @@ In this case, **FastAPI** won't ever touch or care about `__init__`, we will use We could create an instance of this class with: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} And that way we are able to "parameterize" our dependency, that now has `"bar"` inside of it, as the attribute `checker.fixed_content`. @@ -50,7 +50,7 @@ checker(q="somequery") ...and pass whatever that returns as the value of the dependency in our *path operation function* as the parameter `fixed_content_included`: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} /// tip diff --git a/docs/en/docs/advanced/async-tests.md b/docs/en/docs/advanced/async-tests.md index 65ddc60b2..cefb1d184 100644 --- a/docs/en/docs/advanced/async-tests.md +++ b/docs/en/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ For a simple example, let's consider a file structure similar to the one describ The file `main.py` would have: -{* ../../docs_src/async_tests/app_a_py39/main.py *} +{* ../../docs_src/async_tests/app_a_py310/main.py *} The file `test_main.py` would have the tests for `main.py`, it could look like this now: -{* ../../docs_src/async_tests/app_a_py39/test_main.py *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} ## Run it { #run-it } @@ -56,7 +56,7 @@ $ pytest The marker `@pytest.mark.anyio` tells pytest that this test function should be called asynchronously: -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} /// tip @@ -66,7 +66,7 @@ Note that the test function is now `async def` instead of just `def` as before w Then we can create an `AsyncClient` with the app, and send async requests to it, using `await`. -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} This is the equivalent to: diff --git a/docs/en/docs/advanced/behind-a-proxy.md b/docs/en/docs/advanced/behind-a-proxy.md index 4fef02bd1..770e9cd3c 100644 --- a/docs/en/docs/advanced/behind-a-proxy.md +++ b/docs/en/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" For example, let's say you define a *path operation* `/items/`: -{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} If the client tries to go to `/items`, by default, it would be redirected to `/items/`. @@ -115,7 +115,7 @@ In this case, the original path `/app` would actually be served at `/api/v1/app` Even though all your code is written assuming there's just `/app`. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} And the proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to the app server (probably Uvicorn via FastAPI CLI), keeping your application convinced that it is being served at `/app`, so that you don't have to update all your code to include the prefix `/api/v1`. @@ -193,7 +193,7 @@ You can get the current `root_path` used by your application for each request, i Here we are including it in the message just for demonstration purposes. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} Then, if you start Uvicorn with: @@ -220,7 +220,7 @@ The response would be something like: Alternatively, if you don't have a way to provide a command line option like `--root-path` or equivalent, you can set the `root_path` parameter when creating your FastAPI app: -{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} Passing the `root_path` to `FastAPI` would be the equivalent of passing the `--root-path` command line option to Uvicorn or Hypercorn. @@ -400,7 +400,7 @@ If you pass a custom list of `servers` and there's a `root_path` (because your A For example: -{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} Will generate an OpenAPI schema like: @@ -455,7 +455,7 @@ If you don't specify the `servers` parameter and `root_path` is equal to `/`, th If you don't want **FastAPI** to include an automatic server using the `root_path`, you can use the parameter `root_path_in_servers=False`: -{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} and then it won't include it in the OpenAPI schema. diff --git a/docs/en/docs/advanced/custom-response.md b/docs/en/docs/advanced/custom-response.md index e53409c39..8b4b3da33 100644 --- a/docs/en/docs/advanced/custom-response.md +++ b/docs/en/docs/advanced/custom-response.md @@ -30,7 +30,7 @@ This is because by default, FastAPI will inspect every item inside and make sure But if you are certain that the content that you are returning is **serializable with JSON**, you can pass it directly to the response class and avoid the extra overhead that FastAPI would have by passing your return content through the `jsonable_encoder` before passing it to the response class. -{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} /// info @@ -55,7 +55,7 @@ To return a response with HTML directly from **FastAPI**, use `HTMLResponse`. * Import `HTMLResponse`. * Pass `HTMLResponse` as the parameter `response_class` of your *path operation decorator*. -{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} /// info @@ -73,7 +73,7 @@ As seen in [Return a Response directly](response-directly.md){.internal-link tar The same example from above, returning an `HTMLResponse`, could look like: -{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} /// warning @@ -97,7 +97,7 @@ The `response_class` will then be used only to document the OpenAPI *path operat For example, it could be something like: -{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} In this example, the function `generate_html_response()` already generates and returns a `Response` instead of returning the HTML in a `str`. @@ -136,7 +136,7 @@ It accepts the following parameters: FastAPI (actually Starlette) will automatically include a Content-Length header. It will also include a Content-Type header, based on the `media_type` and appending a charset for text types. -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ### `HTMLResponse` { #htmlresponse } @@ -146,7 +146,7 @@ Takes some text or bytes and returns an HTML response, as you read above. Takes some text or bytes and returns a plain text response. -{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} ### `JSONResponse` { #jsonresponse } @@ -180,7 +180,7 @@ This requires installing `ujson` for example with `pip install ujson`. /// -{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} /// tip @@ -194,14 +194,14 @@ Returns an HTTP redirect. Uses a 307 status code (Temporary Redirect) by default You can return a `RedirectResponse` directly: -{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} --- Or you can use it in the `response_class` parameter: -{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} If you do that, then you can return the URL directly from your *path operation* function. @@ -211,13 +211,13 @@ In this case, the `status_code` used will be the default one for the `RedirectRe You can also use the `status_code` parameter combined with the `response_class` parameter: -{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} ### `StreamingResponse` { #streamingresponse } Takes an async generator or a normal generator/iterator and streams the response body. -{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} #### Using `StreamingResponse` with file-like objects { #using-streamingresponse-with-file-like-objects } @@ -227,7 +227,7 @@ That way, you don't have to read it all first in memory, and you can pass that g This includes many libraries to interact with cloud storage, video processing, and others. -{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} 1. This is the generator function. It's a "generator function" because it contains `yield` statements inside. 2. By using a `with` block, we make sure that the file-like object is closed after the generator function is done. So, after it finishes sending the response. @@ -256,11 +256,11 @@ Takes a different set of arguments to instantiate than the other response types: File responses will include appropriate `Content-Length`, `Last-Modified` and `ETag` headers. -{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} You can also use the `response_class` parameter: -{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} In this case, you can return the file path directly from your *path operation* function. @@ -274,7 +274,7 @@ Let's say you want it to return indented and formatted JSON, so you want to use You could create a `CustomORJSONResponse`. The main thing you have to do is create a `Response.render(content)` method that returns the content as `bytes`: -{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} Now instead of returning: @@ -300,7 +300,7 @@ The parameter that defines this is `default_response_class`. In the example below, **FastAPI** will use `ORJSONResponse` by default, in all *path operations*, instead of `JSONResponse`. -{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} /// tip diff --git a/docs/en/docs/advanced/events.md b/docs/en/docs/advanced/events.md index 9414b7a3f..302e96325 100644 --- a/docs/en/docs/advanced/events.md +++ b/docs/en/docs/advanced/events.md @@ -30,7 +30,7 @@ Let's start with an example and then see it in detail. We create an async function `lifespan()` with `yield` like this: -{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} Here we are simulating the expensive *startup* operation of loading the model by putting the (fake) model function in the dictionary with machine learning models before the `yield`. This code will be executed **before** the application **starts taking requests**, during the *startup*. @@ -48,7 +48,7 @@ Maybe you need to start a new version, or you just got tired of running it. 🤷 The first thing to notice, is that we are defining an async function with `yield`. This is very similar to Dependencies with `yield`. -{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} The first part of the function, before the `yield`, will be executed **before** the application starts. @@ -60,7 +60,7 @@ If you check, the function is decorated with an `@asynccontextmanager`. That converts the function into something called an "**async context manager**". -{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} A **context manager** in Python is something that you can use in a `with` statement, for example, `open()` can be used as a context manager: @@ -82,7 +82,7 @@ In our code example above, we don't use it directly, but we pass it to FastAPI f The `lifespan` parameter of the `FastAPI` app takes an **async context manager**, so we can pass our new `lifespan` async context manager to it. -{* ../../docs_src/events/tutorial003_py39.py hl[22] *} +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} ## Alternative Events (deprecated) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ These functions can be declared with `async def` or normal `def`. To add a function that should be run before the application starts, declare it with the event `"startup"`: -{* ../../docs_src/events/tutorial001_py39.py hl[8] *} +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} In this case, the `startup` event handler function will initialize the items "database" (just a `dict`) with some values. @@ -116,7 +116,7 @@ And your application won't start receiving requests until all the `startup` even To add a function that should be run when the application is shutting down, declare it with the event `"shutdown"`: -{* ../../docs_src/events/tutorial002_py39.py hl[6] *} +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} Here, the `shutdown` event handler function will write a text line `"Application shutdown"` to a file `log.txt`. diff --git a/docs/en/docs/advanced/generate-clients.md b/docs/en/docs/advanced/generate-clients.md index 2d0c2aa0c..1e6173c9a 100644 --- a/docs/en/docs/advanced/generate-clients.md +++ b/docs/en/docs/advanced/generate-clients.md @@ -40,7 +40,7 @@ Some of these solutions may also be open source or offer free tiers, so you can Let's start with a simple FastAPI application: -{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} Notice that the *path operations* define the models they use for request payload and response payload, using the models `Item` and `ResponseMessage`. @@ -98,7 +98,7 @@ In many cases, your FastAPI app will be bigger, and you will probably use tags t For example, you could have a section for **items** and another section for **users**, and they could be separated by tags: -{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} ### Generate a TypeScript Client with Tags { #generate-a-typescript-client-with-tags } @@ -145,7 +145,7 @@ For example, here it is using the first tag (you will probably have only one tag You can then pass that custom function to **FastAPI** as the `generate_unique_id_function` parameter: -{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} ### Generate a TypeScript Client with Custom Operation IDs { #generate-a-typescript-client-with-custom-operation-ids } @@ -167,7 +167,7 @@ But for the generated client, we could **modify** the OpenAPI operation IDs righ We could download the OpenAPI JSON to a file `openapi.json` and then we could **remove that prefixed tag** with a script like this: -{* ../../docs_src/generate_clients/tutorial004_py39.py *} +{* ../../docs_src/generate_clients/tutorial004_py310.py *} //// tab | Node.js diff --git a/docs/en/docs/advanced/middleware.md b/docs/en/docs/advanced/middleware.md index 765b38932..b448f5c52 100644 --- a/docs/en/docs/advanced/middleware.md +++ b/docs/en/docs/advanced/middleware.md @@ -57,13 +57,13 @@ Enforces that all incoming requests must either be `https` or `wss`. Any incoming request to `http` or `ws` will be redirected to the secure scheme instead. -{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} ## `TrustedHostMiddleware` { #trustedhostmiddleware } Enforces that all incoming requests have a correctly set `Host` header, in order to guard against HTTP Host Header attacks. -{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} The following arguments are supported: @@ -78,7 +78,7 @@ Handles GZip responses for any request that includes `"gzip"` in the `Accept-Enc The middleware will handle both standard and streaming responses. -{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} The following arguments are supported: diff --git a/docs/en/docs/advanced/openapi-webhooks.md b/docs/en/docs/advanced/openapi-webhooks.md index 59f060c03..f4f586f84 100644 --- a/docs/en/docs/advanced/openapi-webhooks.md +++ b/docs/en/docs/advanced/openapi-webhooks.md @@ -32,7 +32,7 @@ Webhooks are available in OpenAPI 3.1.0 and above, supported by FastAPI `0.99.0` When you create a **FastAPI** application, there is a `webhooks` attribute that you can use to define *webhooks*, the same way you would define *path operations*, for example with `@app.webhooks.post()`. -{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:13,36:53] *} The webhooks that you define will end up in the **OpenAPI** schema and the automatic **docs UI**. diff --git a/docs/en/docs/advanced/path-operation-advanced-configuration.md b/docs/en/docs/advanced/path-operation-advanced-configuration.md index 3d7bfdee5..fdc77c8a2 100644 --- a/docs/en/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/en/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ You can set the OpenAPI `operationId` to be used in your *path operation* with t You would have to make sure that it is unique for each operation. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} ### Using the *path operation function* name as the operationId { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ If you want to use your APIs' function names as `operationId`s, you can iterate You should do it after adding all your *path operations*. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} /// tip @@ -40,7 +40,7 @@ Even if they are in different modules (Python files). To exclude a *path operation* from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} ## Advanced description from docstring { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ You can extend the OpenAPI schema for a *path operation* using the parameter `op This `openapi_extra` can be helpful, for example, to declare [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): -{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *} If you open the automatic API docs, your extension will show up at the bottom of the specific *path operation*. @@ -139,7 +139,7 @@ For example, you could decide to read and validate the request with your own cod You could do that with `openapi_extra`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *} In this example, we didn't declare any Pydantic model. In fact, the request body is not even parsed as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way. @@ -153,7 +153,7 @@ And you could do this even if the data type in the request is not JSON. For example, in this application we don't use FastAPI's integrated functionality to extract the JSON Schema from Pydantic models nor the automatic validation for JSON. In fact, we are declaring the request content type as YAML, not JSON: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *} Nevertheless, although we are not using the default integrated functionality, we are still using a Pydantic model to manually generate the JSON Schema for the data that we want to receive in YAML. @@ -161,7 +161,7 @@ Then we use the request directly, and extract the body as `bytes`. This means th And then in our code, we parse that YAML content directly, and then we are again using the same Pydantic model to validate the YAML content: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *} /// tip diff --git a/docs/en/docs/advanced/response-change-status-code.md b/docs/en/docs/advanced/response-change-status-code.md index d9708aa62..8aa601e7b 100644 --- a/docs/en/docs/advanced/response-change-status-code.md +++ b/docs/en/docs/advanced/response-change-status-code.md @@ -20,7 +20,7 @@ You can declare a parameter of type `Response` in your *path operation function* And then you can set the `status_code` in that *temporal* response object. -{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *} And then you can return any object you need, as you normally would (a `dict`, a database model, etc). diff --git a/docs/en/docs/advanced/response-cookies.md b/docs/en/docs/advanced/response-cookies.md index 5b6fab112..cfc615d7e 100644 --- a/docs/en/docs/advanced/response-cookies.md +++ b/docs/en/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ You can declare a parameter of type `Response` in your *path operation function* And then you can set cookies in that *temporal* response object. -{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *} And then you can return any object you need, as you normally would (a `dict`, a database model, etc). @@ -24,7 +24,7 @@ To do that, you can create a response as described in [Return a Response Directl Then set Cookies in it, and then return it: -{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *} /// tip diff --git a/docs/en/docs/advanced/response-directly.md b/docs/en/docs/advanced/response-directly.md index 4374cb963..76cc50d03 100644 --- a/docs/en/docs/advanced/response-directly.md +++ b/docs/en/docs/advanced/response-directly.md @@ -54,7 +54,7 @@ Let's say that you want to return an Strawberry documentation. diff --git a/docs/en/docs/python-types.md b/docs/en/docs/python-types.md index f2ad35d71..90c32cec8 100644 --- a/docs/en/docs/python-types.md +++ b/docs/en/docs/python-types.md @@ -22,7 +22,7 @@ If you are a Python expert, and you already know everything about type hints, sk Let's start with a simple example: -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} Calling this program outputs: @@ -36,7 +36,7 @@ The function does the following: * Converts the first letter of each one to upper case with `title()`. * Concatenates them with a space in the middle. -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### Edit it { #edit-it } @@ -78,7 +78,7 @@ That's it. Those are the "type hints": -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} That is not the same as declaring default values like would be with: @@ -106,7 +106,7 @@ With that, you can scroll, seeing the options, until you find the one that "ring Check this function, it already has type hints: -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} Because the editor knows the types of the variables, you don't only get completion, you also get error checks: @@ -114,7 +114,7 @@ Because the editor knows the types of the variables, you don't only get completi Now you know that you have to fix it, convert `age` to a string with `str(age)`: -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} ## Declaring types { #declaring-types } @@ -133,7 +133,7 @@ You can use, for example: * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} ### `typing` module { #typing-module } @@ -170,7 +170,7 @@ As the type, put `list`. As the list is a type that contains some internal types, you put them in square brackets: -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} /// info @@ -196,7 +196,7 @@ And still, the editor knows it is a `str`, and provides support for that. You would do the same to declare `tuple`s and `set`s: -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} This means: @@ -211,7 +211,7 @@ The first type parameter is for the keys of the `dict`. The second type parameter is for the values of the `dict`: -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} This means: @@ -253,11 +253,11 @@ You can also declare a class as the type of a variable. Let's say you have a class `Person`, with a name: -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} Then you can declare a variable to be of type `Person`: -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} And then, again, you get all the editor support: @@ -299,7 +299,7 @@ Python also has a feature that allows putting **additional deprecated, but without removing it, pass the parameter `deprecated`: -{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} It will be clearly marked as deprecated in the interactive docs: diff --git a/docs/en/docs/tutorial/path-params-numeric-validations.md b/docs/en/docs/tutorial/path-params-numeric-validations.md index 8b1b8a839..de63a51f5 100644 --- a/docs/en/docs/tutorial/path-params-numeric-validations.md +++ b/docs/en/docs/tutorial/path-params-numeric-validations.md @@ -54,11 +54,11 @@ It doesn't matter for **FastAPI**. It will detect the parameters by their names, So, you can declare your function as: -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} But keep in mind that if you use `Annotated`, you won't have this problem, it won't matter as you're not using the function parameter default values for `Query()` or `Path()`. -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## Order the parameters as you need, tricks { #order-the-parameters-as-you-need-tricks } @@ -83,13 +83,13 @@ Pass `*`, as the first parameter of the function. Python won't do anything with that `*`, but it will know that all the following parameters should be called as keyword arguments (key-value pairs), also known as kwargs. Even if they don't have a default value. -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### Better with `Annotated` { #better-with-annotated } Keep in mind that if you use `Annotated`, as you are not using function parameter default values, you won't have this problem, and you probably won't need to use `*`. -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## Number validations: greater than or equal { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ With `Query` and `Path` (and others you'll see later) you can declare number con Here, with `ge=1`, `item_id` will need to be an integer number "`g`reater than or `e`qual" to `1`. -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## Number validations: greater than and less than or equal { #number-validations-greater-than-and-less-than-or-equal } @@ -106,7 +106,7 @@ The same applies for: * `gt`: `g`reater `t`han * `le`: `l`ess than or `e`qual -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## Number validations: floats, greater than and less than { #number-validations-floats-greater-than-and-less-than } @@ -118,7 +118,7 @@ So, `0.5` would be a valid value. But `0.0` or `0` would not. And the same for lt. -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## Recap { #recap } diff --git a/docs/en/docs/tutorial/path-params.md b/docs/en/docs/tutorial/path-params.md index cf312f1fe..8adbbcfa1 100644 --- a/docs/en/docs/tutorial/path-params.md +++ b/docs/en/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ You can declare path "parameters" or "variables" with the same syntax used by Python format strings: -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} The value of the path parameter `item_id` will be passed to your function as the argument `item_id`. @@ -16,7 +16,7 @@ So, if you run this example and go to 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.model_dump() - return item diff --git a/docs_src/app_testing/app_b_an_py39/test_main.py b/docs_src/app_testing/app_b_an_py39/test_main.py deleted file mode 100644 index 4e1c51ecc..000000000 --- a/docs_src/app_testing/app_b_an_py39/test_main.py +++ /dev/null @@ -1,65 +0,0 @@ -from fastapi.testclient import TestClient - -from .main import app - -client = TestClient(app) - - -def test_read_item(): - response = client.get("/items/foo", headers={"X-Token": "coneofsilence"}) - assert response.status_code == 200 - assert response.json() == { - "id": "foo", - "title": "Foo", - "description": "There goes my hero", - } - - -def test_read_item_bad_token(): - response = client.get("/items/foo", headers={"X-Token": "hailhydra"}) - assert response.status_code == 400 - assert response.json() == {"detail": "Invalid X-Token header"} - - -def test_read_nonexistent_item(): - response = client.get("/items/baz", headers={"X-Token": "coneofsilence"}) - assert response.status_code == 404 - assert response.json() == {"detail": "Item not found"} - - -def test_create_item(): - response = client.post( - "/items/", - headers={"X-Token": "coneofsilence"}, - json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"}, - ) - assert response.status_code == 200 - assert response.json() == { - "id": "foobar", - "title": "Foo Bar", - "description": "The Foo Barters", - } - - -def test_create_item_bad_token(): - response = client.post( - "/items/", - headers={"X-Token": "hailhydra"}, - json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"}, - ) - assert response.status_code == 400 - assert response.json() == {"detail": "Invalid X-Token header"} - - -def test_create_existing_item(): - response = client.post( - "/items/", - headers={"X-Token": "coneofsilence"}, - json={ - "id": "foo", - "title": "The Foo ID Stealers", - "description": "There goes my stealer", - }, - ) - assert response.status_code == 409 - assert response.json() == {"detail": "Item already exists"} diff --git a/docs_src/app_testing/app_b_py39/main.py b/docs_src/app_testing/app_b_py39/main.py deleted file mode 100644 index ed38f4721..000000000 --- a/docs_src/app_testing/app_b_py39/main.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header, HTTPException -from pydantic import BaseModel - -fake_secret_token = "coneofsilence" - -fake_db = { - "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"}, - "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"}, -} - -app = FastAPI() - - -class Item(BaseModel): - id: str - title: str - description: Union[str, None] = None - - -@app.get("/items/{item_id}", response_model=Item) -async def read_main(item_id: str, x_token: str = Header()): - if x_token != fake_secret_token: - raise HTTPException(status_code=400, detail="Invalid X-Token header") - if item_id not in fake_db: - raise HTTPException(status_code=404, detail="Item not found") - return fake_db[item_id] - - -@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.model_dump() - return item diff --git a/docs_src/app_testing/app_b_py39/test_main.py b/docs_src/app_testing/app_b_py39/test_main.py deleted file mode 100644 index 4e1c51ecc..000000000 --- a/docs_src/app_testing/app_b_py39/test_main.py +++ /dev/null @@ -1,65 +0,0 @@ -from fastapi.testclient import TestClient - -from .main import app - -client = TestClient(app) - - -def test_read_item(): - response = client.get("/items/foo", headers={"X-Token": "coneofsilence"}) - assert response.status_code == 200 - assert response.json() == { - "id": "foo", - "title": "Foo", - "description": "There goes my hero", - } - - -def test_read_item_bad_token(): - response = client.get("/items/foo", headers={"X-Token": "hailhydra"}) - assert response.status_code == 400 - assert response.json() == {"detail": "Invalid X-Token header"} - - -def test_read_nonexistent_item(): - response = client.get("/items/baz", headers={"X-Token": "coneofsilence"}) - assert response.status_code == 404 - assert response.json() == {"detail": "Item not found"} - - -def test_create_item(): - response = client.post( - "/items/", - headers={"X-Token": "coneofsilence"}, - json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"}, - ) - assert response.status_code == 200 - assert response.json() == { - "id": "foobar", - "title": "Foo Bar", - "description": "The Foo Barters", - } - - -def test_create_item_bad_token(): - response = client.post( - "/items/", - headers={"X-Token": "hailhydra"}, - json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"}, - ) - assert response.status_code == 400 - assert response.json() == {"detail": "Invalid X-Token header"} - - -def test_create_existing_item(): - response = client.post( - "/items/", - headers={"X-Token": "coneofsilence"}, - json={ - "id": "foo", - "title": "The Foo ID Stealers", - "description": "There goes my stealer", - }, - ) - assert response.status_code == 409 - assert response.json() == {"detail": "Item already exists"} diff --git a/docs_src/app_testing/tutorial001_py310.py b/docs_src/app_testing/tutorial001_py310.py new file mode 100644 index 000000000..79a853b48 --- /dev/null +++ b/docs_src/app_testing/tutorial001_py310.py @@ -0,0 +1,18 @@ +from fastapi import FastAPI +from fastapi.testclient import TestClient + +app = FastAPI() + + +@app.get("/") +async def read_main(): + return {"msg": "Hello World"} + + +client = TestClient(app) + + +def test_read_main(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"msg": "Hello World"} diff --git a/docs_src/app_testing/tutorial002_py310.py b/docs_src/app_testing/tutorial002_py310.py new file mode 100644 index 000000000..71c898b3c --- /dev/null +++ b/docs_src/app_testing/tutorial002_py310.py @@ -0,0 +1,31 @@ +from fastapi import FastAPI +from fastapi.testclient import TestClient +from fastapi.websockets import WebSocket + +app = FastAPI() + + +@app.get("/") +async def read_main(): + return {"msg": "Hello World"} + + +@app.websocket("/ws") +async def websocket(websocket: WebSocket): + await websocket.accept() + await websocket.send_json({"msg": "Hello WebSocket"}) + await websocket.close() + + +def test_read_main(): + client = TestClient(app) + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"msg": "Hello World"} + + +def test_websocket(): + client = TestClient(app) + with client.websocket_connect("/ws") as websocket: + data = websocket.receive_json() + assert data == {"msg": "Hello WebSocket"} diff --git a/docs_src/app_testing/tutorial003_py310.py b/docs_src/app_testing/tutorial003_py310.py new file mode 100644 index 000000000..ca6b45ce0 --- /dev/null +++ b/docs_src/app_testing/tutorial003_py310.py @@ -0,0 +1,24 @@ +from fastapi import FastAPI +from fastapi.testclient import TestClient + +app = FastAPI() + +items = {} + + +@app.on_event("startup") +async def startup_event(): + items["foo"] = {"name": "Fighters"} + items["bar"] = {"name": "Tenders"} + + +@app.get("/items/{item_id}") +async def read_items(item_id: str): + return items[item_id] + + +def test_read_items(): + with TestClient(app) as client: + response = client.get("/items/foo") + assert response.status_code == 200 + assert response.json() == {"name": "Fighters"} diff --git a/docs_src/app_testing/tutorial004_py310.py b/docs_src/app_testing/tutorial004_py310.py new file mode 100644 index 000000000..f83ac9ae9 --- /dev/null +++ b/docs_src/app_testing/tutorial004_py310.py @@ -0,0 +1,43 @@ +from contextlib import asynccontextmanager + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +items = {} + + +@asynccontextmanager +async def lifespan(app: FastAPI): + items["foo"] = {"name": "Fighters"} + items["bar"] = {"name": "Tenders"} + yield + # clean up items + items.clear() + + +app = FastAPI(lifespan=lifespan) + + +@app.get("/items/{item_id}") +async def read_items(item_id: str): + return items[item_id] + + +def test_read_items(): + # Before the lifespan starts, "items" is still empty + assert items == {} + + with TestClient(app) as client: + # Inside the "with TestClient" block, the lifespan starts and items added + assert items == {"foo": {"name": "Fighters"}, "bar": {"name": "Tenders"}} + + response = client.get("/items/foo") + assert response.status_code == 200 + assert response.json() == {"name": "Fighters"} + + # After the requests is done, the items are still there + assert items == {"foo": {"name": "Fighters"}, "bar": {"name": "Tenders"}} + + # The end of the "with TestClient" block simulates terminating the app, so + # the lifespan ends and items are cleaned up + assert items == {} diff --git a/docs_src/app_testing/app_b_py39/__init__.py b/docs_src/async_tests/app_a_py310/__init__.py similarity index 100% rename from docs_src/app_testing/app_b_py39/__init__.py rename to docs_src/async_tests/app_a_py310/__init__.py diff --git a/docs_src/async_tests/app_a_py310/main.py b/docs_src/async_tests/app_a_py310/main.py new file mode 100644 index 000000000..9594f859c --- /dev/null +++ b/docs_src/async_tests/app_a_py310/main.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Tomato"} diff --git a/docs_src/async_tests/app_a_py310/test_main.py b/docs_src/async_tests/app_a_py310/test_main.py new file mode 100644 index 000000000..a57a31f7d --- /dev/null +++ b/docs_src/async_tests/app_a_py310/test_main.py @@ -0,0 +1,14 @@ +import pytest +from httpx import ASGITransport, AsyncClient + +from .main import app + + +@pytest.mark.anyio +async def test_root(): + async with AsyncClient( + transport=ASGITransport(app=app), base_url="http://test" + ) as ac: + response = await ac.get("/") + assert response.status_code == 200 + assert response.json() == {"message": "Tomato"} diff --git a/docs_src/authentication_error_status_code/tutorial001_an_py310.py b/docs_src/authentication_error_status_code/tutorial001_an_py310.py new file mode 100644 index 000000000..7bbc2f717 --- /dev/null +++ b/docs_src/authentication_error_status_code/tutorial001_an_py310.py @@ -0,0 +1,21 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException, status +from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer + +app = FastAPI() + + +class HTTPBearer403(HTTPBearer): + def make_not_authenticated_error(self) -> HTTPException: + return HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Not authenticated" + ) + + +CredentialsDep = Annotated[HTTPAuthorizationCredentials, Depends(HTTPBearer403())] + + +@app.get("/me") +def read_me(credentials: CredentialsDep): + return {"message": "You are authenticated", "token": credentials.credentials} diff --git a/docs_src/background_tasks/tutorial001_py310.py b/docs_src/background_tasks/tutorial001_py310.py new file mode 100644 index 000000000..1720a7433 --- /dev/null +++ b/docs_src/background_tasks/tutorial001_py310.py @@ -0,0 +1,15 @@ +from fastapi import BackgroundTasks, FastAPI + +app = FastAPI() + + +def write_notification(email: str, message=""): + with open("log.txt", mode="w") as email_file: + content = f"notification for {email}: {message}" + email_file.write(content) + + +@app.post("/send-notification/{email}") +async def send_notification(email: str, background_tasks: BackgroundTasks): + background_tasks.add_task(write_notification, email, message="some notification") + return {"message": "Notification sent in the background"} diff --git a/docs_src/background_tasks/tutorial002_an_py39.py b/docs_src/background_tasks/tutorial002_an_py39.py deleted file mode 100644 index bfdd14875..000000000 --- a/docs_src/background_tasks/tutorial002_an_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Annotated, Union - -from fastapi import BackgroundTasks, Depends, FastAPI - -app = FastAPI() - - -def write_log(message: str): - with open("log.txt", mode="a") as log: - log.write(message) - - -def get_query(background_tasks: BackgroundTasks, q: Union[str, None] = None): - if q: - message = f"found query: {q}\n" - background_tasks.add_task(write_log, message) - return q - - -@app.post("/send-notification/{email}") -async def send_notification( - email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)] -): - message = f"message to {email}\n" - background_tasks.add_task(write_log, message) - return {"message": "Message sent"} diff --git a/docs_src/background_tasks/tutorial002_py39.py b/docs_src/background_tasks/tutorial002_py39.py deleted file mode 100644 index 2e1b2f6c6..000000000 --- a/docs_src/background_tasks/tutorial002_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Union - -from fastapi import BackgroundTasks, Depends, FastAPI - -app = FastAPI() - - -def write_log(message: str): - with open("log.txt", mode="a") as log: - log.write(message) - - -def get_query(background_tasks: BackgroundTasks, q: Union[str, None] = None): - if q: - message = f"found query: {q}\n" - background_tasks.add_task(write_log, message) - return q - - -@app.post("/send-notification/{email}") -async def send_notification( - email: str, background_tasks: BackgroundTasks, q: str = Depends(get_query) -): - message = f"message to {email}\n" - background_tasks.add_task(write_log, message) - return {"message": "Message sent"} diff --git a/docs_src/behind_a_proxy/tutorial001_01_py310.py b/docs_src/behind_a_proxy/tutorial001_01_py310.py new file mode 100644 index 000000000..52b114395 --- /dev/null +++ b/docs_src/behind_a_proxy/tutorial001_01_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/") +def read_items(): + return ["plumbus", "portal gun"] diff --git a/docs_src/behind_a_proxy/tutorial001_py310.py b/docs_src/behind_a_proxy/tutorial001_py310.py new file mode 100644 index 000000000..ede59ada1 --- /dev/null +++ b/docs_src/behind_a_proxy/tutorial001_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI, Request + +app = FastAPI() + + +@app.get("/app") +def read_main(request: Request): + return {"message": "Hello World", "root_path": request.scope.get("root_path")} diff --git a/docs_src/behind_a_proxy/tutorial002_py310.py b/docs_src/behind_a_proxy/tutorial002_py310.py new file mode 100644 index 000000000..c1600cde9 --- /dev/null +++ b/docs_src/behind_a_proxy/tutorial002_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI, Request + +app = FastAPI(root_path="/api/v1") + + +@app.get("/app") +def read_main(request: Request): + return {"message": "Hello World", "root_path": request.scope.get("root_path")} diff --git a/docs_src/behind_a_proxy/tutorial003_py310.py b/docs_src/behind_a_proxy/tutorial003_py310.py new file mode 100644 index 000000000..3b7d8be01 --- /dev/null +++ b/docs_src/behind_a_proxy/tutorial003_py310.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI, Request + +app = FastAPI( + servers=[ + {"url": "https://stag.example.com", "description": "Staging environment"}, + {"url": "https://prod.example.com", "description": "Production environment"}, + ], + root_path="/api/v1", +) + + +@app.get("/app") +def read_main(request: Request): + return {"message": "Hello World", "root_path": request.scope.get("root_path")} diff --git a/docs_src/behind_a_proxy/tutorial004_py310.py b/docs_src/behind_a_proxy/tutorial004_py310.py new file mode 100644 index 000000000..51bd5babc --- /dev/null +++ b/docs_src/behind_a_proxy/tutorial004_py310.py @@ -0,0 +1,15 @@ +from fastapi import FastAPI, Request + +app = FastAPI( + servers=[ + {"url": "https://stag.example.com", "description": "Staging environment"}, + {"url": "https://prod.example.com", "description": "Production environment"}, + ], + root_path="/api/v1", + root_path_in_servers=False, +) + + +@app.get("/app") +def read_main(request: Request): + return {"message": "Hello World", "root_path": request.scope.get("root_path")} diff --git a/docs_src/bigger_applications/app_py39/internal/__init__.py b/docs_src/bigger_applications/app_an_py310/__init__.py similarity index 100% rename from docs_src/bigger_applications/app_py39/internal/__init__.py rename to docs_src/bigger_applications/app_an_py310/__init__.py diff --git a/docs_src/bigger_applications/app_an_py310/dependencies.py b/docs_src/bigger_applications/app_an_py310/dependencies.py new file mode 100644 index 000000000..5c7612aa0 --- /dev/null +++ b/docs_src/bigger_applications/app_an_py310/dependencies.py @@ -0,0 +1,13 @@ +from typing import Annotated + +from fastapi import Header, HTTPException + + +async def get_token_header(x_token: Annotated[str, Header()]): + if x_token != "fake-super-secret-token": + raise HTTPException(status_code=400, detail="X-Token header invalid") + + +async def get_query_token(token: str): + if token != "jessica": + raise HTTPException(status_code=400, detail="No Jessica token provided") diff --git a/docs_src/bigger_applications/app_py39/routers/__init__.py b/docs_src/bigger_applications/app_an_py310/internal/__init__.py similarity index 100% rename from docs_src/bigger_applications/app_py39/routers/__init__.py rename to docs_src/bigger_applications/app_an_py310/internal/__init__.py diff --git a/docs_src/bigger_applications/app_py39/internal/admin.py b/docs_src/bigger_applications/app_an_py310/internal/admin.py similarity index 100% rename from docs_src/bigger_applications/app_py39/internal/admin.py rename to docs_src/bigger_applications/app_an_py310/internal/admin.py diff --git a/docs_src/bigger_applications/app_an_py310/main.py b/docs_src/bigger_applications/app_an_py310/main.py new file mode 100644 index 000000000..ae544a3aa --- /dev/null +++ b/docs_src/bigger_applications/app_an_py310/main.py @@ -0,0 +1,23 @@ +from fastapi import Depends, FastAPI + +from .dependencies import get_query_token, get_token_header +from .internal import admin +from .routers import items, users + +app = FastAPI(dependencies=[Depends(get_query_token)]) + + +app.include_router(users.router) +app.include_router(items.router) +app.include_router( + admin.router, + prefix="/admin", + tags=["admin"], + dependencies=[Depends(get_token_header)], + responses={418: {"description": "I'm a teapot"}}, +) + + +@app.get("/") +async def root(): + return {"message": "Hello Bigger Applications!"} diff --git a/docs_src/bigger_applications/app_an_py310/routers/__init__.py b/docs_src/bigger_applications/app_an_py310/routers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs_src/bigger_applications/app_py39/routers/items.py b/docs_src/bigger_applications/app_an_py310/routers/items.py similarity index 100% rename from docs_src/bigger_applications/app_py39/routers/items.py rename to docs_src/bigger_applications/app_an_py310/routers/items.py diff --git a/docs_src/bigger_applications/app_py39/routers/users.py b/docs_src/bigger_applications/app_an_py310/routers/users.py similarity index 100% rename from docs_src/bigger_applications/app_py39/routers/users.py rename to docs_src/bigger_applications/app_an_py310/routers/users.py diff --git a/docs_src/body/tutorial001_py39.py b/docs_src/body/tutorial001_py39.py deleted file mode 100644 index f93317274..000000000 --- a/docs_src/body/tutorial001_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -app = FastAPI() - - -@app.post("/items/") -async def create_item(item: Item): - return item diff --git a/docs_src/body/tutorial002_py39.py b/docs_src/body/tutorial002_py39.py deleted file mode 100644 index fb212e8e7..000000000 --- a/docs_src/body/tutorial002_py39.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -app = FastAPI() - - -@app.post("/items/") -async def create_item(item: Item): - item_dict = item.model_dump() - if item.tax is not None: - price_with_tax = item.price + item.tax - item_dict.update({"price_with_tax": price_with_tax}) - return item_dict diff --git a/docs_src/body/tutorial003_py39.py b/docs_src/body/tutorial003_py39.py deleted file mode 100644 index 636ba2275..000000000 --- a/docs_src/body/tutorial003_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -app = FastAPI() - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - return {"item_id": item_id, **item.model_dump()} diff --git a/docs_src/body/tutorial004_py39.py b/docs_src/body/tutorial004_py39.py deleted file mode 100644 index 2c157abfa..000000000 --- a/docs_src/body/tutorial004_py39.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -app = FastAPI() - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item, q: Union[str, None] = None): - result = {"item_id": item_id, **item.model_dump()} - if q: - result.update({"q": q}) - return result diff --git a/docs_src/body_fields/tutorial001_an_py39.py b/docs_src/body_fields/tutorial001_an_py39.py deleted file mode 100644 index 6ef14470c..000000000 --- a/docs_src/body_fields/tutorial001_an_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel, Field - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = Field( - default=None, title="The description of the item", max_length=300 - ) - price: float = Field(gt=0, description="The price must be greater than zero") - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_fields/tutorial001_py39.py b/docs_src/body_fields/tutorial001_py39.py deleted file mode 100644 index cbeebd614..000000000 --- a/docs_src/body_fields/tutorial001_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel, Field - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = Field( - default=None, title="The description of the item", max_length=300 - ) - price: float = Field(gt=0, description="The price must be greater than zero") - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item = Body(embed=True)): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_multiple_params/tutorial001_an_py39.py b/docs_src/body_multiple_params/tutorial001_an_py39.py deleted file mode 100644 index 1c0ac3a7b..000000000 --- a/docs_src/body_multiple_params/tutorial001_an_py39.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Path -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)], - q: Union[str, None] = None, - item: Union[Item, None] = None, -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - if item: - results.update({"item": item}) - return results diff --git a/docs_src/body_multiple_params/tutorial001_py39.py b/docs_src/body_multiple_params/tutorial001_py39.py deleted file mode 100644 index a73975b3a..000000000 --- a/docs_src/body_multiple_params/tutorial001_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Path -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - *, - item_id: int = Path(title="The ID of the item to get", ge=0, le=1000), - q: Union[str, None] = None, - item: Union[Item, None] = None, -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - if item: - results.update({"item": item}) - return results diff --git a/docs_src/body_multiple_params/tutorial002_py39.py b/docs_src/body_multiple_params/tutorial002_py39.py deleted file mode 100644 index 2d7160ae8..000000000 --- a/docs_src/body_multiple_params/tutorial002_py39.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -class User(BaseModel): - username: str - full_name: Union[str, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item, user: User): - results = {"item_id": item_id, "item": item, "user": user} - return results diff --git a/docs_src/body_multiple_params/tutorial003_an_py39.py b/docs_src/body_multiple_params/tutorial003_an_py39.py deleted file mode 100644 index 042351e0b..000000000 --- a/docs_src/body_multiple_params/tutorial003_an_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -class User(BaseModel): - username: str - full_name: Union[str, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - item_id: int, item: Item, user: User, importance: Annotated[int, Body()] -): - results = {"item_id": item_id, "item": item, "user": user, "importance": importance} - return results diff --git a/docs_src/body_multiple_params/tutorial003_py39.py b/docs_src/body_multiple_params/tutorial003_py39.py deleted file mode 100644 index cf344e6c5..000000000 --- a/docs_src/body_multiple_params/tutorial003_py39.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -class User(BaseModel): - username: str - full_name: Union[str, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item, user: User, importance: int = Body()): - results = {"item_id": item_id, "item": item, "user": user, "importance": importance} - return results diff --git a/docs_src/body_multiple_params/tutorial004_an_py39.py b/docs_src/body_multiple_params/tutorial004_an_py39.py deleted file mode 100644 index 567427c03..000000000 --- a/docs_src/body_multiple_params/tutorial004_an_py39.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -class User(BaseModel): - username: str - full_name: Union[str, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - *, - item_id: int, - item: Item, - user: User, - importance: Annotated[int, Body(gt=0)], - q: Union[str, None] = None, -): - results = {"item_id": item_id, "item": item, "user": user, "importance": importance} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/body_multiple_params/tutorial004_py39.py b/docs_src/body_multiple_params/tutorial004_py39.py deleted file mode 100644 index 8ce4c7a97..000000000 --- a/docs_src/body_multiple_params/tutorial004_py39.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -class User(BaseModel): - username: str - full_name: Union[str, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - *, - item_id: int, - item: Item, - user: User, - importance: int = Body(gt=0), - q: Union[str, None] = None, -): - results = {"item_id": item_id, "item": item, "user": user, "importance": importance} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/body_multiple_params/tutorial005_an_py39.py b/docs_src/body_multiple_params/tutorial005_an_py39.py deleted file mode 100644 index 9a52425c1..000000000 --- a/docs_src/body_multiple_params/tutorial005_an_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_multiple_params/tutorial005_py39.py b/docs_src/body_multiple_params/tutorial005_py39.py deleted file mode 100644 index 29e6e14b7..000000000 --- a/docs_src/body_multiple_params/tutorial005_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item = Body(embed=True)): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial001_py39.py b/docs_src/body_nested_models/tutorial001_py39.py deleted file mode 100644 index 37ef6dda5..000000000 --- a/docs_src/body_nested_models/tutorial001_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: list = [] - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial002_py39.py b/docs_src/body_nested_models/tutorial002_py39.py deleted file mode 100644 index 8a93a7233..000000000 --- a/docs_src/body_nested_models/tutorial002_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: list[str] = [] - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial003_py39.py b/docs_src/body_nested_models/tutorial003_py39.py deleted file mode 100644 index b590ece36..000000000 --- a/docs_src/body_nested_models/tutorial003_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial004_py39.py b/docs_src/body_nested_models/tutorial004_py39.py deleted file mode 100644 index dc2b175fb..000000000 --- a/docs_src/body_nested_models/tutorial004_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Image(BaseModel): - url: str - name: str - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - image: Union[Image, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial005_py39.py b/docs_src/body_nested_models/tutorial005_py39.py deleted file mode 100644 index 47db90008..000000000 --- a/docs_src/body_nested_models/tutorial005_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel, HttpUrl - -app = FastAPI() - - -class Image(BaseModel): - url: HttpUrl - name: str - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - image: Union[Image, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial006_py39.py b/docs_src/body_nested_models/tutorial006_py39.py deleted file mode 100644 index b14409703..000000000 --- a/docs_src/body_nested_models/tutorial006_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel, HttpUrl - -app = FastAPI() - - -class Image(BaseModel): - url: HttpUrl - name: str - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - images: Union[list[Image], None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial007_py39.py b/docs_src/body_nested_models/tutorial007_py39.py deleted file mode 100644 index 59cf01e23..000000000 --- a/docs_src/body_nested_models/tutorial007_py39.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel, HttpUrl - -app = FastAPI() - - -class Image(BaseModel): - url: HttpUrl - name: str - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - images: Union[list[Image], None] = None - - -class Offer(BaseModel): - name: str - description: Union[str, None] = None - price: float - items: list[Item] - - -@app.post("/offers/") -async def create_offer(offer: Offer): - return offer diff --git a/docs_src/body_nested_models/tutorial008_py310.py b/docs_src/body_nested_models/tutorial008_py310.py new file mode 100644 index 000000000..854a7a5a4 --- /dev/null +++ b/docs_src/body_nested_models/tutorial008_py310.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI +from pydantic import BaseModel, HttpUrl + +app = FastAPI() + + +class Image(BaseModel): + url: HttpUrl + name: str + + +@app.post("/images/multiple/") +async def create_multiple_images(images: list[Image]): + return images diff --git a/docs_src/body_nested_models/tutorial009_py310.py b/docs_src/body_nested_models/tutorial009_py310.py new file mode 100644 index 000000000..59c1e5082 --- /dev/null +++ b/docs_src/body_nested_models/tutorial009_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.post("/index-weights/") +async def create_index_weights(weights: dict[int, float]): + return weights diff --git a/docs_src/body_updates/tutorial001_py39.py b/docs_src/body_updates/tutorial001_py39.py deleted file mode 100644 index 999bcdb82..000000000 --- a/docs_src/body_updates/tutorial001_py39.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from fastapi.encoders import jsonable_encoder -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: Union[str, None] = None - description: Union[str, None] = None - price: Union[float, None] = None - tax: float = 10.5 - tags: list[str] = [] - - -items = { - "foo": {"name": "Foo", "price": 50.2}, - "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, - "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []}, -} - - -@app.get("/items/{item_id}", response_model=Item) -async def read_item(item_id: str): - return items[item_id] - - -@app.put("/items/{item_id}", response_model=Item) -async def update_item(item_id: str, item: Item): - update_item_encoded = jsonable_encoder(item) - items[item_id] = update_item_encoded - return update_item_encoded diff --git a/docs_src/body_updates/tutorial002_py39.py b/docs_src/body_updates/tutorial002_py39.py deleted file mode 100644 index 3714b5a55..000000000 --- a/docs_src/body_updates/tutorial002_py39.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from fastapi.encoders import jsonable_encoder -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: Union[str, None] = None - description: Union[str, None] = None - price: Union[float, None] = None - tax: float = 10.5 - tags: list[str] = [] - - -items = { - "foo": {"name": "Foo", "price": 50.2}, - "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, - "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []}, -} - - -@app.get("/items/{item_id}", response_model=Item) -async def read_item(item_id: str): - return items[item_id] - - -@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) - updated_item = stored_item_model.model_copy(update=update_data) - items[item_id] = jsonable_encoder(updated_item) - return updated_item diff --git a/docs_src/conditional_openapi/tutorial001_py310.py b/docs_src/conditional_openapi/tutorial001_py310.py new file mode 100644 index 000000000..eedb0d274 --- /dev/null +++ b/docs_src/conditional_openapi/tutorial001_py310.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + openapi_url: str = "/openapi.json" + + +settings = Settings() + +app = FastAPI(openapi_url=settings.openapi_url) + + +@app.get("/") +def root(): + return {"message": "Hello World"} diff --git a/docs_src/configure_swagger_ui/tutorial001_py310.py b/docs_src/configure_swagger_ui/tutorial001_py310.py new file mode 100644 index 000000000..6c24ce758 --- /dev/null +++ b/docs_src/configure_swagger_ui/tutorial001_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI(swagger_ui_parameters={"syntaxHighlight": False}) + + +@app.get("/users/{username}") +async def read_user(username: str): + return {"message": f"Hello {username}"} diff --git a/docs_src/configure_swagger_ui/tutorial002_py310.py b/docs_src/configure_swagger_ui/tutorial002_py310.py new file mode 100644 index 000000000..cc75c2196 --- /dev/null +++ b/docs_src/configure_swagger_ui/tutorial002_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI(swagger_ui_parameters={"syntaxHighlight": {"theme": "obsidian"}}) + + +@app.get("/users/{username}") +async def read_user(username: str): + return {"message": f"Hello {username}"} diff --git a/docs_src/configure_swagger_ui/tutorial003_py310.py b/docs_src/configure_swagger_ui/tutorial003_py310.py new file mode 100644 index 000000000..b4449f5c6 --- /dev/null +++ b/docs_src/configure_swagger_ui/tutorial003_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI(swagger_ui_parameters={"deepLinking": False}) + + +@app.get("/users/{username}") +async def read_user(username: str): + return {"message": f"Hello {username}"} diff --git a/docs_src/cookie_param_models/tutorial001_an_py39.py b/docs_src/cookie_param_models/tutorial001_an_py39.py deleted file mode 100644 index 3d90c2007..000000000 --- a/docs_src/cookie_param_models/tutorial001_an_py39.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Cookie, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Cookies(BaseModel): - session_id: str - fatebook_tracker: Union[str, None] = None - googall_tracker: Union[str, None] = None - - -@app.get("/items/") -async def read_items(cookies: Annotated[Cookies, Cookie()]): - return cookies diff --git a/docs_src/cookie_param_models/tutorial001_py39.py b/docs_src/cookie_param_models/tutorial001_py39.py deleted file mode 100644 index cc65c43e1..000000000 --- a/docs_src/cookie_param_models/tutorial001_py39.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Union - -from fastapi import Cookie, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Cookies(BaseModel): - session_id: str - fatebook_tracker: Union[str, None] = None - googall_tracker: Union[str, None] = None - - -@app.get("/items/") -async def read_items(cookies: Cookies = Cookie()): - return cookies diff --git a/docs_src/cookie_param_models/tutorial002_an_py39.py b/docs_src/cookie_param_models/tutorial002_an_py39.py deleted file mode 100644 index a906ce6a1..000000000 --- a/docs_src/cookie_param_models/tutorial002_an_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Cookie, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Cookies(BaseModel): - model_config = {"extra": "forbid"} - - session_id: str - fatebook_tracker: Union[str, None] = None - googall_tracker: Union[str, None] = None - - -@app.get("/items/") -async def read_items(cookies: Annotated[Cookies, Cookie()]): - return cookies diff --git a/docs_src/cookie_param_models/tutorial002_py39.py b/docs_src/cookie_param_models/tutorial002_py39.py deleted file mode 100644 index 9679e890f..000000000 --- a/docs_src/cookie_param_models/tutorial002_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi import Cookie, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Cookies(BaseModel): - model_config = {"extra": "forbid"} - - session_id: str - fatebook_tracker: Union[str, None] = None - googall_tracker: Union[str, None] = None - - -@app.get("/items/") -async def read_items(cookies: Cookies = Cookie()): - return cookies diff --git a/docs_src/cookie_params/tutorial001_an_py39.py b/docs_src/cookie_params/tutorial001_an_py39.py deleted file mode 100644 index e18d0a332..000000000 --- a/docs_src/cookie_params/tutorial001_an_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Cookie, FastAPI - -app = FastAPI() - - -@app.get("/items/") -async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None): - return {"ads_id": ads_id} diff --git a/docs_src/cookie_params/tutorial001_py39.py b/docs_src/cookie_params/tutorial001_py39.py deleted file mode 100644 index c4a497fda..000000000 --- a/docs_src/cookie_params/tutorial001_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Union - -from fastapi import Cookie, FastAPI - -app = FastAPI() - - -@app.get("/items/") -async def read_items(ads_id: Union[str, None] = Cookie(default=None)): - return {"ads_id": ads_id} diff --git a/docs_src/cors/tutorial001_py310.py b/docs_src/cors/tutorial001_py310.py new file mode 100644 index 000000000..d59ab27ac --- /dev/null +++ b/docs_src/cors/tutorial001_py310.py @@ -0,0 +1,24 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +app = FastAPI() + +origins = [ + "http://localhost.tiangolo.com", + "https://localhost.tiangolo.com", + "http://localhost", + "http://localhost:8080", +] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/") +async def main(): + return {"message": "Hello World"} diff --git a/docs_src/custom_docs_ui/tutorial001_py310.py b/docs_src/custom_docs_ui/tutorial001_py310.py new file mode 100644 index 000000000..1cfcce19a --- /dev/null +++ b/docs_src/custom_docs_ui/tutorial001_py310.py @@ -0,0 +1,38 @@ +from fastapi import FastAPI +from fastapi.openapi.docs import ( + get_redoc_html, + get_swagger_ui_html, + get_swagger_ui_oauth2_redirect_html, +) + +app = FastAPI(docs_url=None, redoc_url=None) + + +@app.get("/docs", include_in_schema=False) +async def custom_swagger_ui_html(): + return get_swagger_ui_html( + openapi_url=app.openapi_url, + title=app.title + " - Swagger UI", + oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, + swagger_js_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js", + swagger_css_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css", + ) + + +@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False) +async def swagger_ui_redirect(): + return get_swagger_ui_oauth2_redirect_html() + + +@app.get("/redoc", include_in_schema=False) +async def redoc_html(): + return get_redoc_html( + openapi_url=app.openapi_url, + title=app.title + " - ReDoc", + redoc_js_url="https://unpkg.com/redoc@2/bundles/redoc.standalone.js", + ) + + +@app.get("/users/{username}") +async def read_user(username: str): + return {"message": f"Hello {username}"} diff --git a/docs_src/custom_docs_ui/tutorial002_py310.py b/docs_src/custom_docs_ui/tutorial002_py310.py new file mode 100644 index 000000000..23ea368f8 --- /dev/null +++ b/docs_src/custom_docs_ui/tutorial002_py310.py @@ -0,0 +1,41 @@ +from fastapi import FastAPI +from fastapi.openapi.docs import ( + get_redoc_html, + get_swagger_ui_html, + get_swagger_ui_oauth2_redirect_html, +) +from fastapi.staticfiles import StaticFiles + +app = FastAPI(docs_url=None, redoc_url=None) + +app.mount("/static", StaticFiles(directory="static"), name="static") + + +@app.get("/docs", include_in_schema=False) +async def custom_swagger_ui_html(): + return get_swagger_ui_html( + openapi_url=app.openapi_url, + title=app.title + " - Swagger UI", + oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, + swagger_js_url="/static/swagger-ui-bundle.js", + swagger_css_url="/static/swagger-ui.css", + ) + + +@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False) +async def swagger_ui_redirect(): + return get_swagger_ui_oauth2_redirect_html() + + +@app.get("/redoc", include_in_schema=False) +async def redoc_html(): + return get_redoc_html( + openapi_url=app.openapi_url, + title=app.title + " - ReDoc", + redoc_js_url="/static/redoc.standalone.js", + ) + + +@app.get("/users/{username}") +async def read_user(username: str): + return {"message": f"Hello {username}"} diff --git a/docs_src/custom_request_and_route/tutorial001_an_py39.py b/docs_src/custom_request_and_route/tutorial001_an_py39.py deleted file mode 100644 index 076727e64..000000000 --- a/docs_src/custom_request_and_route/tutorial001_an_py39.py +++ /dev/null @@ -1,35 +0,0 @@ -import gzip -from typing import Annotated, Callable - -from fastapi import Body, FastAPI, Request, Response -from fastapi.routing import APIRoute - - -class GzipRequest(Request): - async def body(self) -> bytes: - if not hasattr(self, "_body"): - body = await super().body() - if "gzip" in self.headers.getlist("Content-Encoding"): - body = gzip.decompress(body) - self._body = body - return self._body - - -class GzipRoute(APIRoute): - def get_route_handler(self) -> Callable: - original_route_handler = super().get_route_handler() - - async def custom_route_handler(request: Request) -> Response: - request = GzipRequest(request.scope, request.receive) - return await original_route_handler(request) - - return custom_route_handler - - -app = FastAPI() -app.router.route_class = GzipRoute - - -@app.post("/sum") -async def sum_numbers(numbers: Annotated[list[int], Body()]): - return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial001_py39.py b/docs_src/custom_request_and_route/tutorial001_py39.py deleted file mode 100644 index 54b20b942..000000000 --- a/docs_src/custom_request_and_route/tutorial001_py39.py +++ /dev/null @@ -1,35 +0,0 @@ -import gzip -from typing import Callable - -from fastapi import Body, FastAPI, Request, Response -from fastapi.routing import APIRoute - - -class GzipRequest(Request): - async def body(self) -> bytes: - if not hasattr(self, "_body"): - body = await super().body() - if "gzip" in self.headers.getlist("Content-Encoding"): - body = gzip.decompress(body) - self._body = body - return self._body - - -class GzipRoute(APIRoute): - def get_route_handler(self) -> Callable: - original_route_handler = super().get_route_handler() - - async def custom_route_handler(request: Request) -> Response: - request = GzipRequest(request.scope, request.receive) - return await original_route_handler(request) - - return custom_route_handler - - -app = FastAPI() -app.router.route_class = GzipRoute - - -@app.post("/sum") -async def sum_numbers(numbers: list[int] = Body()): - return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial002_an_py39.py b/docs_src/custom_request_and_route/tutorial002_an_py39.py deleted file mode 100644 index e7de09de4..000000000 --- a/docs_src/custom_request_and_route/tutorial002_an_py39.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Annotated, Callable - -from fastapi import Body, FastAPI, HTTPException, Request, Response -from fastapi.exceptions import RequestValidationError -from fastapi.routing import APIRoute - - -class ValidationErrorLoggingRoute(APIRoute): - def get_route_handler(self) -> Callable: - original_route_handler = super().get_route_handler() - - async def custom_route_handler(request: Request) -> Response: - try: - return await original_route_handler(request) - except RequestValidationError as exc: - body = await request.body() - detail = {"errors": exc.errors(), "body": body.decode()} - raise HTTPException(status_code=422, detail=detail) - - return custom_route_handler - - -app = FastAPI() -app.router.route_class = ValidationErrorLoggingRoute - - -@app.post("/") -async def sum_numbers(numbers: Annotated[list[int], Body()]): - return sum(numbers) diff --git a/docs_src/custom_request_and_route/tutorial002_py39.py b/docs_src/custom_request_and_route/tutorial002_py39.py deleted file mode 100644 index c4e474828..000000000 --- a/docs_src/custom_request_and_route/tutorial002_py39.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Callable - -from fastapi import Body, FastAPI, HTTPException, Request, Response -from fastapi.exceptions import RequestValidationError -from fastapi.routing import APIRoute - - -class ValidationErrorLoggingRoute(APIRoute): - def get_route_handler(self) -> Callable: - original_route_handler = super().get_route_handler() - - async def custom_route_handler(request: Request) -> Response: - try: - return await original_route_handler(request) - except RequestValidationError as exc: - body = await request.body() - detail = {"errors": exc.errors(), "body": body.decode()} - raise HTTPException(status_code=422, detail=detail) - - return custom_route_handler - - -app = FastAPI() -app.router.route_class = ValidationErrorLoggingRoute - - -@app.post("/") -async def sum_numbers(numbers: list[int] = Body()): - return sum(numbers) diff --git a/docs_src/custom_request_and_route/tutorial003_py39.py b/docs_src/custom_request_and_route/tutorial003_py39.py deleted file mode 100644 index aabe76068..000000000 --- a/docs_src/custom_request_and_route/tutorial003_py39.py +++ /dev/null @@ -1,39 +0,0 @@ -import time -from typing import Callable - -from fastapi import APIRouter, FastAPI, Request, Response -from fastapi.routing import APIRoute - - -class TimedRoute(APIRoute): - def get_route_handler(self) -> Callable: - original_route_handler = super().get_route_handler() - - async def custom_route_handler(request: Request) -> Response: - before = time.time() - response: Response = await original_route_handler(request) - duration = time.time() - before - response.headers["X-Response-Time"] = str(duration) - print(f"route duration: {duration}") - print(f"route response: {response}") - print(f"route response headers: {response.headers}") - return response - - return custom_route_handler - - -app = FastAPI() -router = APIRouter(route_class=TimedRoute) - - -@app.get("/") -async def not_timed(): - return {"message": "Not timed"} - - -@router.get("/timed") -async def timed(): - return {"message": "It's the time of my life"} - - -app.include_router(router) diff --git a/docs_src/custom_response/tutorial001_py310.py b/docs_src/custom_response/tutorial001_py310.py new file mode 100644 index 000000000..0f09bdf77 --- /dev/null +++ b/docs_src/custom_response/tutorial001_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from fastapi.responses import UJSONResponse + +app = FastAPI() + + +@app.get("/items/", response_class=UJSONResponse) +async def read_items(): + return [{"item_id": "Foo"}] diff --git a/docs_src/custom_response/tutorial001b_py310.py b/docs_src/custom_response/tutorial001b_py310.py new file mode 100644 index 000000000..95e6ca763 --- /dev/null +++ b/docs_src/custom_response/tutorial001b_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from fastapi.responses import ORJSONResponse + +app = FastAPI() + + +@app.get("/items/", response_class=ORJSONResponse) +async def read_items(): + return ORJSONResponse([{"item_id": "Foo"}]) diff --git a/docs_src/custom_response/tutorial002_py310.py b/docs_src/custom_response/tutorial002_py310.py new file mode 100644 index 000000000..23c495867 --- /dev/null +++ b/docs_src/custom_response/tutorial002_py310.py @@ -0,0 +1,18 @@ +from fastapi import FastAPI +from fastapi.responses import HTMLResponse + +app = FastAPI() + + +@app.get("/items/", response_class=HTMLResponse) +async def read_items(): + return """ + + + Some HTML in here + + +

Look ma! HTML!

+ + + """ diff --git a/docs_src/custom_response/tutorial003_py310.py b/docs_src/custom_response/tutorial003_py310.py new file mode 100644 index 000000000..51ad3c146 --- /dev/null +++ b/docs_src/custom_response/tutorial003_py310.py @@ -0,0 +1,19 @@ +from fastapi import FastAPI +from fastapi.responses import HTMLResponse + +app = FastAPI() + + +@app.get("/items/") +async def read_items(): + html_content = """ + + + Some HTML in here + + +

Look ma! HTML!

+ + + """ + return HTMLResponse(content=html_content, status_code=200) diff --git a/docs_src/custom_response/tutorial004_py310.py b/docs_src/custom_response/tutorial004_py310.py new file mode 100644 index 000000000..0e90f2012 --- /dev/null +++ b/docs_src/custom_response/tutorial004_py310.py @@ -0,0 +1,23 @@ +from fastapi import FastAPI +from fastapi.responses import HTMLResponse + +app = FastAPI() + + +def generate_html_response(): + html_content = """ + + + Some HTML in here + + +

Look ma! HTML!

+ + + """ + return HTMLResponse(content=html_content, status_code=200) + + +@app.get("/items/", response_class=HTMLResponse) +async def read_items(): + return generate_html_response() diff --git a/docs_src/custom_response/tutorial005_py310.py b/docs_src/custom_response/tutorial005_py310.py new file mode 100644 index 000000000..3d58f57fb --- /dev/null +++ b/docs_src/custom_response/tutorial005_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from fastapi.responses import PlainTextResponse + +app = FastAPI() + + +@app.get("/", response_class=PlainTextResponse) +async def main(): + return "Hello World" diff --git a/docs_src/custom_response/tutorial006_py310.py b/docs_src/custom_response/tutorial006_py310.py new file mode 100644 index 000000000..332f8f87f --- /dev/null +++ b/docs_src/custom_response/tutorial006_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from fastapi.responses import RedirectResponse + +app = FastAPI() + + +@app.get("/typer") +async def redirect_typer(): + return RedirectResponse("https://typer.tiangolo.com") diff --git a/docs_src/custom_response/tutorial006b_py310.py b/docs_src/custom_response/tutorial006b_py310.py new file mode 100644 index 000000000..03a7be399 --- /dev/null +++ b/docs_src/custom_response/tutorial006b_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from fastapi.responses import RedirectResponse + +app = FastAPI() + + +@app.get("/fastapi", response_class=RedirectResponse) +async def redirect_fastapi(): + return "https://fastapi.tiangolo.com" diff --git a/docs_src/custom_response/tutorial006c_py310.py b/docs_src/custom_response/tutorial006c_py310.py new file mode 100644 index 000000000..87c720364 --- /dev/null +++ b/docs_src/custom_response/tutorial006c_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from fastapi.responses import RedirectResponse + +app = FastAPI() + + +@app.get("/pydantic", response_class=RedirectResponse, status_code=302) +async def redirect_pydantic(): + return "https://docs.pydantic.dev/" diff --git a/docs_src/custom_response/tutorial007_py310.py b/docs_src/custom_response/tutorial007_py310.py new file mode 100644 index 000000000..e2a53a211 --- /dev/null +++ b/docs_src/custom_response/tutorial007_py310.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI +from fastapi.responses import StreamingResponse + +app = FastAPI() + + +async def fake_video_streamer(): + for i in range(10): + yield b"some fake video bytes" + + +@app.get("/") +async def main(): + return StreamingResponse(fake_video_streamer()) diff --git a/docs_src/custom_response/tutorial008_py310.py b/docs_src/custom_response/tutorial008_py310.py new file mode 100644 index 000000000..fc071cbee --- /dev/null +++ b/docs_src/custom_response/tutorial008_py310.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI +from fastapi.responses import StreamingResponse + +some_file_path = "large-video-file.mp4" +app = FastAPI() + + +@app.get("/") +def main(): + def iterfile(): # (1) + with open(some_file_path, mode="rb") as file_like: # (2) + yield from file_like # (3) + + return StreamingResponse(iterfile(), media_type="video/mp4") diff --git a/docs_src/custom_response/tutorial009_py310.py b/docs_src/custom_response/tutorial009_py310.py new file mode 100644 index 000000000..71cf50cc1 --- /dev/null +++ b/docs_src/custom_response/tutorial009_py310.py @@ -0,0 +1,10 @@ +from fastapi import FastAPI +from fastapi.responses import FileResponse + +some_file_path = "large-video-file.mp4" +app = FastAPI() + + +@app.get("/") +async def main(): + return FileResponse(some_file_path) diff --git a/docs_src/custom_response/tutorial009b_py310.py b/docs_src/custom_response/tutorial009b_py310.py new file mode 100644 index 000000000..27200ee4b --- /dev/null +++ b/docs_src/custom_response/tutorial009b_py310.py @@ -0,0 +1,10 @@ +from fastapi import FastAPI +from fastapi.responses import FileResponse + +some_file_path = "large-video-file.mp4" +app = FastAPI() + + +@app.get("/", response_class=FileResponse) +async def main(): + return some_file_path diff --git a/docs_src/custom_response/tutorial009c_py310.py b/docs_src/custom_response/tutorial009c_py310.py new file mode 100644 index 000000000..de6b6688e --- /dev/null +++ b/docs_src/custom_response/tutorial009c_py310.py @@ -0,0 +1,19 @@ +from typing import Any + +import orjson +from fastapi import FastAPI, Response + +app = FastAPI() + + +class CustomORJSONResponse(Response): + media_type = "application/json" + + def render(self, content: Any) -> bytes: + assert orjson is not None, "orjson must be installed" + return orjson.dumps(content, option=orjson.OPT_INDENT_2) + + +@app.get("/", response_class=CustomORJSONResponse) +async def main(): + return {"message": "Hello World"} diff --git a/docs_src/custom_response/tutorial010_py310.py b/docs_src/custom_response/tutorial010_py310.py new file mode 100644 index 000000000..57cb06260 --- /dev/null +++ b/docs_src/custom_response/tutorial010_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from fastapi.responses import ORJSONResponse + +app = FastAPI(default_response_class=ORJSONResponse) + + +@app.get("/items/") +async def read_items(): + return [{"item_id": "Foo"}] diff --git a/docs_src/dataclasses_/tutorial001_py39.py b/docs_src/dataclasses_/tutorial001_py39.py deleted file mode 100644 index 2954c391f..000000000 --- a/docs_src/dataclasses_/tutorial001_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from dataclasses import dataclass -from typing import Union - -from fastapi import FastAPI - - -@dataclass -class Item: - name: str - price: float - description: Union[str, None] = None - tax: Union[float, None] = None - - -app = FastAPI() - - -@app.post("/items/") -async def create_item(item: Item): - return item diff --git a/docs_src/dataclasses_/tutorial002_py39.py b/docs_src/dataclasses_/tutorial002_py39.py deleted file mode 100644 index 0c23765d8..000000000 --- a/docs_src/dataclasses_/tutorial002_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from dataclasses import dataclass, field -from typing import Union - -from fastapi import FastAPI - - -@dataclass -class Item: - name: str - price: float - tags: list[str] = field(default_factory=list) - description: Union[str, None] = None - tax: Union[float, None] = None - - -app = FastAPI() - - -@app.get("/items/next", response_model=Item) -async def read_next_item(): - return { - "name": "Island In The Moon", - "price": 12.99, - "description": "A place to be playin' and havin' fun", - "tags": ["breater"], - } diff --git a/docs_src/dataclasses_/tutorial003_py39.py b/docs_src/dataclasses_/tutorial003_py39.py deleted file mode 100644 index 991708c00..000000000 --- a/docs_src/dataclasses_/tutorial003_py39.py +++ /dev/null @@ -1,55 +0,0 @@ -from dataclasses import field # (1) -from typing import Union - -from fastapi import FastAPI -from pydantic.dataclasses import dataclass # (2) - - -@dataclass -class Item: - name: str - description: Union[str, None] = None - - -@dataclass -class Author: - name: str - items: list[Item] = field(default_factory=list) # (3) - - -app = FastAPI() - - -@app.post("/authors/{author_id}/items/", response_model=Author) # (4) -async def create_author_items(author_id: str, items: list[Item]): # (5) - return {"name": author_id, "items": items} # (6) - - -@app.get("/authors/", response_model=list[Author]) # (7) -def get_authors(): # (8) - return [ # (9) - { - "name": "Breaters", - "items": [ - { - "name": "Island In The Moon", - "description": "A place to be playin' and havin' fun", - }, - {"name": "Holy Buddies"}, - ], - }, - { - "name": "System of an Up", - "items": [ - { - "name": "Salt", - "description": "The kombucha mushroom people's favorite", - }, - {"name": "Pad Thai"}, - { - "name": "Lonely Night", - "description": "The mostests lonliest nightiest of allest", - }, - ], - }, - ] diff --git a/docs_src/debugging/tutorial001_py310.py b/docs_src/debugging/tutorial001_py310.py new file mode 100644 index 000000000..3de21d2a8 --- /dev/null +++ b/docs_src/debugging/tutorial001_py310.py @@ -0,0 +1,15 @@ +import uvicorn +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def root(): + a = "a" + b = "b" + a + return {"hello world": b} + + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/docs_src/dependencies/tutorial001_02_an_py39.py b/docs_src/dependencies/tutorial001_02_an_py39.py deleted file mode 100644 index df969ae9c..000000000 --- a/docs_src/dependencies/tutorial001_02_an_py39.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -async def common_parameters( - q: Union[str, None] = None, skip: int = 0, limit: int = 100 -): - return {"q": q, "skip": skip, "limit": limit} - - -CommonsDep = Annotated[dict, Depends(common_parameters)] - - -@app.get("/items/") -async def read_items(commons: CommonsDep): - return commons - - -@app.get("/users/") -async def read_users(commons: CommonsDep): - return commons diff --git a/docs_src/dependencies/tutorial001_an_py39.py b/docs_src/dependencies/tutorial001_an_py39.py deleted file mode 100644 index 5d9fe6ddf..000000000 --- a/docs_src/dependencies/tutorial001_an_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -async def common_parameters( - q: Union[str, None] = None, skip: int = 0, limit: int = 100 -): - return {"q": q, "skip": skip, "limit": limit} - - -@app.get("/items/") -async def read_items(commons: Annotated[dict, Depends(common_parameters)]): - return commons - - -@app.get("/users/") -async def read_users(commons: Annotated[dict, Depends(common_parameters)]): - return commons diff --git a/docs_src/dependencies/tutorial001_py39.py b/docs_src/dependencies/tutorial001_py39.py deleted file mode 100644 index b1275103a..000000000 --- a/docs_src/dependencies/tutorial001_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -async def common_parameters( - q: Union[str, None] = None, skip: int = 0, limit: int = 100 -): - return {"q": q, "skip": skip, "limit": limit} - - -@app.get("/items/") -async def read_items(commons: dict = Depends(common_parameters)): - return commons - - -@app.get("/users/") -async def read_users(commons: dict = Depends(common_parameters)): - return commons diff --git a/docs_src/dependencies/tutorial002_an_py39.py b/docs_src/dependencies/tutorial002_an_py39.py deleted file mode 100644 index 844a23c5a..000000000 --- a/docs_src/dependencies/tutorial002_an_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] - - -class CommonQueryParams: - def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): - self.q = q - self.skip = skip - self.limit = limit - - -@app.get("/items/") -async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]): - response = {} - if commons.q: - response.update({"q": commons.q}) - items = fake_items_db[commons.skip : commons.skip + commons.limit] - response.update({"items": items}) - return response diff --git a/docs_src/dependencies/tutorial002_py39.py b/docs_src/dependencies/tutorial002_py39.py deleted file mode 100644 index 8e863e4fa..000000000 --- a/docs_src/dependencies/tutorial002_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] - - -class CommonQueryParams: - def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): - self.q = q - self.skip = skip - self.limit = limit - - -@app.get("/items/") -async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)): - response = {} - if commons.q: - response.update({"q": commons.q}) - items = fake_items_db[commons.skip : commons.skip + commons.limit] - response.update({"items": items}) - return response diff --git a/docs_src/dependencies/tutorial003_an_py39.py b/docs_src/dependencies/tutorial003_an_py39.py deleted file mode 100644 index 9e9123ad2..000000000 --- a/docs_src/dependencies/tutorial003_an_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Annotated, Any, Union - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] - - -class CommonQueryParams: - def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): - self.q = q - self.skip = skip - self.limit = limit - - -@app.get("/items/") -async def read_items(commons: Annotated[Any, Depends(CommonQueryParams)]): - response = {} - if commons.q: - response.update({"q": commons.q}) - items = fake_items_db[commons.skip : commons.skip + commons.limit] - response.update({"items": items}) - return response diff --git a/docs_src/dependencies/tutorial003_py39.py b/docs_src/dependencies/tutorial003_py39.py deleted file mode 100644 index 34614e539..000000000 --- a/docs_src/dependencies/tutorial003_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] - - -class CommonQueryParams: - def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): - self.q = q - self.skip = skip - self.limit = limit - - -@app.get("/items/") -async def read_items(commons=Depends(CommonQueryParams)): - response = {} - if commons.q: - response.update({"q": commons.q}) - items = fake_items_db[commons.skip : commons.skip + commons.limit] - response.update({"items": items}) - return response diff --git a/docs_src/dependencies/tutorial004_an_py39.py b/docs_src/dependencies/tutorial004_an_py39.py deleted file mode 100644 index 74268626b..000000000 --- a/docs_src/dependencies/tutorial004_an_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] - - -class CommonQueryParams: - def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): - self.q = q - self.skip = skip - self.limit = limit - - -@app.get("/items/") -async def read_items(commons: Annotated[CommonQueryParams, Depends()]): - response = {} - if commons.q: - response.update({"q": commons.q}) - items = fake_items_db[commons.skip : commons.skip + commons.limit] - response.update({"items": items}) - return response diff --git a/docs_src/dependencies/tutorial004_py39.py b/docs_src/dependencies/tutorial004_py39.py deleted file mode 100644 index d9fe88148..000000000 --- a/docs_src/dependencies/tutorial004_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] - - -class CommonQueryParams: - def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): - self.q = q - self.skip = skip - self.limit = limit - - -@app.get("/items/") -async def read_items(commons: CommonQueryParams = Depends()): - response = {} - if commons.q: - response.update({"q": commons.q}) - items = fake_items_db[commons.skip : commons.skip + commons.limit] - response.update({"items": items}) - return response diff --git a/docs_src/dependencies/tutorial005_an_py39.py b/docs_src/dependencies/tutorial005_an_py39.py deleted file mode 100644 index d5dd8dca9..000000000 --- a/docs_src/dependencies/tutorial005_an_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Cookie, Depends, FastAPI - -app = FastAPI() - - -def query_extractor(q: Union[str, None] = None): - return q - - -def query_or_cookie_extractor( - q: Annotated[str, Depends(query_extractor)], - last_query: Annotated[Union[str, None], Cookie()] = None, -): - if not q: - return last_query - return q - - -@app.get("/items/") -async def read_query( - query_or_default: Annotated[str, Depends(query_or_cookie_extractor)], -): - return {"q_or_cookie": query_or_default} diff --git a/docs_src/dependencies/tutorial005_py39.py b/docs_src/dependencies/tutorial005_py39.py deleted file mode 100644 index 697332b5b..000000000 --- a/docs_src/dependencies/tutorial005_py39.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Union - -from fastapi import Cookie, Depends, FastAPI - -app = FastAPI() - - -def query_extractor(q: Union[str, None] = None): - return q - - -def query_or_cookie_extractor( - q: str = Depends(query_extractor), - last_query: Union[str, None] = Cookie(default=None), -): - if not q: - return last_query - return q - - -@app.get("/items/") -async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)): - return {"q_or_cookie": query_or_default} diff --git a/docs_src/dependencies/tutorial006_an_py310.py b/docs_src/dependencies/tutorial006_an_py310.py new file mode 100644 index 000000000..11976ed6a --- /dev/null +++ b/docs_src/dependencies/tutorial006_an_py310.py @@ -0,0 +1,21 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI, Header, HTTPException + +app = FastAPI() + + +async def verify_token(x_token: Annotated[str, Header()]): + if x_token != "fake-super-secret-token": + raise HTTPException(status_code=400, detail="X-Token header invalid") + + +async def verify_key(x_key: Annotated[str, Header()]): + if x_key != "fake-super-secret-key": + raise HTTPException(status_code=400, detail="X-Key header invalid") + return x_key + + +@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)]) +async def read_items(): + return [{"item": "Foo"}, {"item": "Bar"}] diff --git a/docs_src/dependencies/tutorial006_py310.py b/docs_src/dependencies/tutorial006_py310.py new file mode 100644 index 000000000..9aff4154f --- /dev/null +++ b/docs_src/dependencies/tutorial006_py310.py @@ -0,0 +1,19 @@ +from fastapi import Depends, FastAPI, Header, HTTPException + +app = FastAPI() + + +async def verify_token(x_token: str = Header()): + if x_token != "fake-super-secret-token": + raise HTTPException(status_code=400, detail="X-Token header invalid") + + +async def verify_key(x_key: str = Header()): + if x_key != "fake-super-secret-key": + raise HTTPException(status_code=400, detail="X-Key header invalid") + return x_key + + +@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)]) +async def read_items(): + return [{"item": "Foo"}, {"item": "Bar"}] diff --git a/docs_src/dependencies/tutorial007_py310.py b/docs_src/dependencies/tutorial007_py310.py new file mode 100644 index 000000000..2e4ab4777 --- /dev/null +++ b/docs_src/dependencies/tutorial007_py310.py @@ -0,0 +1,6 @@ +async def get_db(): + db = DBSession() + try: + yield db + finally: + db.close() diff --git a/docs_src/dependencies/tutorial008_an_py310.py b/docs_src/dependencies/tutorial008_an_py310.py new file mode 100644 index 000000000..acc804c32 --- /dev/null +++ b/docs_src/dependencies/tutorial008_an_py310.py @@ -0,0 +1,27 @@ +from typing import Annotated + +from fastapi import Depends + + +async def dependency_a(): + dep_a = generate_dep_a() + try: + yield dep_a + finally: + dep_a.close() + + +async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]): + dep_b = generate_dep_b() + try: + yield dep_b + finally: + dep_b.close(dep_a) + + +async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]): + dep_c = generate_dep_c() + try: + yield dep_c + finally: + dep_c.close(dep_b) diff --git a/docs_src/dependencies/tutorial008_py310.py b/docs_src/dependencies/tutorial008_py310.py new file mode 100644 index 000000000..8472f642d --- /dev/null +++ b/docs_src/dependencies/tutorial008_py310.py @@ -0,0 +1,25 @@ +from fastapi import Depends + + +async def dependency_a(): + dep_a = generate_dep_a() + try: + yield dep_a + finally: + dep_a.close() + + +async def dependency_b(dep_a=Depends(dependency_a)): + dep_b = generate_dep_b() + try: + yield dep_b + finally: + dep_b.close(dep_a) + + +async def dependency_c(dep_b=Depends(dependency_b)): + dep_c = generate_dep_c() + try: + yield dep_c + finally: + dep_c.close(dep_b) diff --git a/docs_src/dependencies/tutorial008b_an_py310.py b/docs_src/dependencies/tutorial008b_an_py310.py new file mode 100644 index 000000000..3b8434c81 --- /dev/null +++ b/docs_src/dependencies/tutorial008b_an_py310.py @@ -0,0 +1,32 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException + +app = FastAPI() + + +data = { + "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"}, + "portal-gun": {"description": "Gun to create portals", "owner": "Rick"}, +} + + +class OwnerError(Exception): + pass + + +def get_username(): + try: + yield "Rick" + except OwnerError as e: + raise HTTPException(status_code=400, detail=f"Owner error: {e}") + + +@app.get("/items/{item_id}") +def get_item(item_id: str, username: Annotated[str, Depends(get_username)]): + if item_id not in data: + raise HTTPException(status_code=404, detail="Item not found") + item = data[item_id] + if item["owner"] != username: + raise OwnerError(username) + return item diff --git a/docs_src/dependencies/tutorial008b_py310.py b/docs_src/dependencies/tutorial008b_py310.py new file mode 100644 index 000000000..163e96600 --- /dev/null +++ b/docs_src/dependencies/tutorial008b_py310.py @@ -0,0 +1,30 @@ +from fastapi import Depends, FastAPI, HTTPException + +app = FastAPI() + + +data = { + "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"}, + "portal-gun": {"description": "Gun to create portals", "owner": "Rick"}, +} + + +class OwnerError(Exception): + pass + + +def get_username(): + try: + yield "Rick" + except OwnerError as e: + raise HTTPException(status_code=400, detail=f"Owner error: {e}") + + +@app.get("/items/{item_id}") +def get_item(item_id: str, username: str = Depends(get_username)): + if item_id not in data: + raise HTTPException(status_code=404, detail="Item not found") + item = data[item_id] + if item["owner"] != username: + raise OwnerError(username) + return item diff --git a/docs_src/dependencies/tutorial008c_an_py310.py b/docs_src/dependencies/tutorial008c_an_py310.py new file mode 100644 index 000000000..da92efa9c --- /dev/null +++ b/docs_src/dependencies/tutorial008c_an_py310.py @@ -0,0 +1,29 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException + +app = FastAPI() + + +class InternalError(Exception): + pass + + +def get_username(): + try: + yield "Rick" + except InternalError: + print("Oops, we didn't raise again, Britney 😱") + + +@app.get("/items/{item_id}") +def get_item(item_id: str, username: Annotated[str, Depends(get_username)]): + if item_id == "portal-gun": + raise InternalError( + f"The portal gun is too dangerous to be owned by {username}" + ) + if item_id != "plumbus": + raise HTTPException( + status_code=404, detail="Item not found, there's only a plumbus here" + ) + return item_id diff --git a/docs_src/dependencies/tutorial008c_py310.py b/docs_src/dependencies/tutorial008c_py310.py new file mode 100644 index 000000000..4b99a5a31 --- /dev/null +++ b/docs_src/dependencies/tutorial008c_py310.py @@ -0,0 +1,27 @@ +from fastapi import Depends, FastAPI, HTTPException + +app = FastAPI() + + +class InternalError(Exception): + pass + + +def get_username(): + try: + yield "Rick" + except InternalError: + print("Oops, we didn't raise again, Britney 😱") + + +@app.get("/items/{item_id}") +def get_item(item_id: str, username: str = Depends(get_username)): + if item_id == "portal-gun": + raise InternalError( + f"The portal gun is too dangerous to be owned by {username}" + ) + if item_id != "plumbus": + raise HTTPException( + status_code=404, detail="Item not found, there's only a plumbus here" + ) + return item_id diff --git a/docs_src/dependencies/tutorial008d_an_py310.py b/docs_src/dependencies/tutorial008d_an_py310.py new file mode 100644 index 000000000..99bd5cb91 --- /dev/null +++ b/docs_src/dependencies/tutorial008d_an_py310.py @@ -0,0 +1,30 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException + +app = FastAPI() + + +class InternalError(Exception): + pass + + +def get_username(): + try: + yield "Rick" + except InternalError: + print("We don't swallow the internal error here, we raise again 😎") + raise + + +@app.get("/items/{item_id}") +def get_item(item_id: str, username: Annotated[str, Depends(get_username)]): + if item_id == "portal-gun": + raise InternalError( + f"The portal gun is too dangerous to be owned by {username}" + ) + if item_id != "plumbus": + raise HTTPException( + status_code=404, detail="Item not found, there's only a plumbus here" + ) + return item_id diff --git a/docs_src/dependencies/tutorial008d_py310.py b/docs_src/dependencies/tutorial008d_py310.py new file mode 100644 index 000000000..93039343d --- /dev/null +++ b/docs_src/dependencies/tutorial008d_py310.py @@ -0,0 +1,28 @@ +from fastapi import Depends, FastAPI, HTTPException + +app = FastAPI() + + +class InternalError(Exception): + pass + + +def get_username(): + try: + yield "Rick" + except InternalError: + print("We don't swallow the internal error here, we raise again 😎") + raise + + +@app.get("/items/{item_id}") +def get_item(item_id: str, username: str = Depends(get_username)): + if item_id == "portal-gun": + raise InternalError( + f"The portal gun is too dangerous to be owned by {username}" + ) + if item_id != "plumbus": + raise HTTPException( + status_code=404, detail="Item not found, there's only a plumbus here" + ) + return item_id diff --git a/docs_src/dependencies/tutorial008e_an_py310.py b/docs_src/dependencies/tutorial008e_an_py310.py new file mode 100644 index 000000000..80a44c7e2 --- /dev/null +++ b/docs_src/dependencies/tutorial008e_an_py310.py @@ -0,0 +1,17 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI + +app = FastAPI() + + +def get_username(): + try: + yield "Rick" + finally: + print("Cleanup up before response is sent") + + +@app.get("/users/me") +def get_user_me(username: Annotated[str, Depends(get_username, scope="function")]): + return username diff --git a/docs_src/dependencies/tutorial008e_py310.py b/docs_src/dependencies/tutorial008e_py310.py new file mode 100644 index 000000000..1ed056e91 --- /dev/null +++ b/docs_src/dependencies/tutorial008e_py310.py @@ -0,0 +1,15 @@ +from fastapi import Depends, FastAPI + +app = FastAPI() + + +def get_username(): + try: + yield "Rick" + finally: + print("Cleanup up before response is sent") + + +@app.get("/users/me") +def get_user_me(username: str = Depends(get_username, scope="function")): + return username diff --git a/docs_src/dependencies/tutorial010_py310.py b/docs_src/dependencies/tutorial010_py310.py new file mode 100644 index 000000000..c27f1b170 --- /dev/null +++ b/docs_src/dependencies/tutorial010_py310.py @@ -0,0 +1,14 @@ +class MySuperContextManager: + def __init__(self): + self.db = DBSession() + + def __enter__(self): + return self.db + + def __exit__(self, exc_type, exc_value, traceback): + self.db.close() + + +async def get_db(): + with MySuperContextManager() as db: + yield db diff --git a/docs_src/dependencies/tutorial011_an_py310.py b/docs_src/dependencies/tutorial011_an_py310.py new file mode 100644 index 000000000..68b7434ec --- /dev/null +++ b/docs_src/dependencies/tutorial011_an_py310.py @@ -0,0 +1,23 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI + +app = FastAPI() + + +class FixedContentQueryChecker: + def __init__(self, fixed_content: str): + self.fixed_content = fixed_content + + def __call__(self, q: str = ""): + if q: + return self.fixed_content in q + return False + + +checker = FixedContentQueryChecker("bar") + + +@app.get("/query-checker/") +async def read_query_check(fixed_content_included: Annotated[bool, Depends(checker)]): + return {"fixed_content_in_query": fixed_content_included} diff --git a/docs_src/dependencies/tutorial011_py310.py b/docs_src/dependencies/tutorial011_py310.py new file mode 100644 index 000000000..5d22f6823 --- /dev/null +++ b/docs_src/dependencies/tutorial011_py310.py @@ -0,0 +1,21 @@ +from fastapi import Depends, FastAPI + +app = FastAPI() + + +class FixedContentQueryChecker: + def __init__(self, fixed_content: str): + self.fixed_content = fixed_content + + def __call__(self, q: str = ""): + if q: + return self.fixed_content in q + return False + + +checker = FixedContentQueryChecker("bar") + + +@app.get("/query-checker/") +async def read_query_check(fixed_content_included: bool = Depends(checker)): + return {"fixed_content_in_query": fixed_content_included} diff --git a/docs_src/dependencies/tutorial012_an_py310.py b/docs_src/dependencies/tutorial012_an_py310.py new file mode 100644 index 000000000..6503591fc --- /dev/null +++ b/docs_src/dependencies/tutorial012_an_py310.py @@ -0,0 +1,27 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI, Header, HTTPException + + +async def verify_token(x_token: Annotated[str, Header()]): + if x_token != "fake-super-secret-token": + raise HTTPException(status_code=400, detail="X-Token header invalid") + + +async def verify_key(x_key: Annotated[str, Header()]): + if x_key != "fake-super-secret-key": + raise HTTPException(status_code=400, detail="X-Key header invalid") + return x_key + + +app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)]) + + +@app.get("/items/") +async def read_items(): + return [{"item": "Portal Gun"}, {"item": "Plumbus"}] + + +@app.get("/users/") +async def read_users(): + return [{"username": "Rick"}, {"username": "Morty"}] diff --git a/docs_src/dependencies/tutorial012_py310.py b/docs_src/dependencies/tutorial012_py310.py new file mode 100644 index 000000000..36ce6c711 --- /dev/null +++ b/docs_src/dependencies/tutorial012_py310.py @@ -0,0 +1,25 @@ +from fastapi import Depends, FastAPI, Header, HTTPException + + +async def verify_token(x_token: str = Header()): + if x_token != "fake-super-secret-token": + raise HTTPException(status_code=400, detail="X-Token header invalid") + + +async def verify_key(x_key: str = Header()): + if x_key != "fake-super-secret-key": + raise HTTPException(status_code=400, detail="X-Key header invalid") + return x_key + + +app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)]) + + +@app.get("/items/") +async def read_items(): + return [{"item": "Portal Gun"}, {"item": "Plumbus"}] + + +@app.get("/users/") +async def read_users(): + return [{"username": "Rick"}, {"username": "Morty"}] diff --git a/docs_src/dependency_testing/tutorial001_an_py39.py b/docs_src/dependency_testing/tutorial001_an_py39.py deleted file mode 100644 index bccb0cdb1..000000000 --- a/docs_src/dependency_testing/tutorial001_an_py39.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Depends, FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() - - -async def common_parameters( - q: Union[str, None] = None, skip: int = 0, limit: int = 100 -): - return {"q": q, "skip": skip, "limit": limit} - - -@app.get("/items/") -async def read_items(commons: Annotated[dict, Depends(common_parameters)]): - return {"message": "Hello Items!", "params": commons} - - -@app.get("/users/") -async def read_users(commons: Annotated[dict, Depends(common_parameters)]): - return {"message": "Hello Users!", "params": commons} - - -client = TestClient(app) - - -async def override_dependency(q: Union[str, None] = None): - return {"q": q, "skip": 5, "limit": 10} - - -app.dependency_overrides[common_parameters] = override_dependency - - -def test_override_in_items(): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == { - "message": "Hello Items!", - "params": {"q": None, "skip": 5, "limit": 10}, - } - - -def test_override_in_items_with_q(): - response = client.get("/items/?q=foo") - assert response.status_code == 200 - assert response.json() == { - "message": "Hello Items!", - "params": {"q": "foo", "skip": 5, "limit": 10}, - } - - -def test_override_in_items_with_params(): - response = client.get("/items/?q=foo&skip=100&limit=200") - assert response.status_code == 200 - assert response.json() == { - "message": "Hello Items!", - "params": {"q": "foo", "skip": 5, "limit": 10}, - } diff --git a/docs_src/dependency_testing/tutorial001_py39.py b/docs_src/dependency_testing/tutorial001_py39.py deleted file mode 100644 index a5fe1d9bf..000000000 --- a/docs_src/dependency_testing/tutorial001_py39.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() - - -async def common_parameters( - q: Union[str, None] = None, skip: int = 0, limit: int = 100 -): - return {"q": q, "skip": skip, "limit": limit} - - -@app.get("/items/") -async def read_items(commons: dict = Depends(common_parameters)): - return {"message": "Hello Items!", "params": commons} - - -@app.get("/users/") -async def read_users(commons: dict = Depends(common_parameters)): - return {"message": "Hello Users!", "params": commons} - - -client = TestClient(app) - - -async def override_dependency(q: Union[str, None] = None): - return {"q": q, "skip": 5, "limit": 10} - - -app.dependency_overrides[common_parameters] = override_dependency - - -def test_override_in_items(): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == { - "message": "Hello Items!", - "params": {"q": None, "skip": 5, "limit": 10}, - } - - -def test_override_in_items_with_q(): - response = client.get("/items/?q=foo") - assert response.status_code == 200 - assert response.json() == { - "message": "Hello Items!", - "params": {"q": "foo", "skip": 5, "limit": 10}, - } - - -def test_override_in_items_with_params(): - response = client.get("/items/?q=foo&skip=100&limit=200") - assert response.status_code == 200 - assert response.json() == { - "message": "Hello Items!", - "params": {"q": "foo", "skip": 5, "limit": 10}, - } diff --git a/docs_src/encoder/tutorial001_py39.py b/docs_src/encoder/tutorial001_py39.py deleted file mode 100644 index 5f7e7061e..000000000 --- a/docs_src/encoder/tutorial001_py39.py +++ /dev/null @@ -1,23 +0,0 @@ -from datetime import datetime -from typing import Union - -from fastapi import FastAPI -from fastapi.encoders import jsonable_encoder -from pydantic import BaseModel - -fake_db = {} - - -class Item(BaseModel): - title: str - timestamp: datetime - description: Union[str, None] = None - - -app = FastAPI() - - -@app.put("/items/{id}") -def update_item(id: str, item: Item): - json_compatible_item_data = jsonable_encoder(item) - fake_db[id] = json_compatible_item_data diff --git a/docs_src/events/tutorial001_py310.py b/docs_src/events/tutorial001_py310.py new file mode 100644 index 000000000..128004c9f --- /dev/null +++ b/docs_src/events/tutorial001_py310.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI + +app = FastAPI() + +items = {} + + +@app.on_event("startup") +async def startup_event(): + items["foo"] = {"name": "Fighters"} + items["bar"] = {"name": "Tenders"} + + +@app.get("/items/{item_id}") +async def read_items(item_id: str): + return items[item_id] diff --git a/docs_src/events/tutorial002_py310.py b/docs_src/events/tutorial002_py310.py new file mode 100644 index 000000000..a71fea802 --- /dev/null +++ b/docs_src/events/tutorial002_py310.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.on_event("shutdown") +def shutdown_event(): + with open("log.txt", mode="a") as log: + log.write("Application shutdown") + + +@app.get("/items/") +async def read_items(): + return [{"name": "Foo"}] diff --git a/docs_src/events/tutorial003_py310.py b/docs_src/events/tutorial003_py310.py new file mode 100644 index 000000000..2b650590b --- /dev/null +++ b/docs_src/events/tutorial003_py310.py @@ -0,0 +1,28 @@ +from contextlib import asynccontextmanager + +from fastapi import FastAPI + + +def fake_answer_to_everything_ml_model(x: float): + return x * 42 + + +ml_models = {} + + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Load the ML model + ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model + yield + # Clean up the ML models and release the resources + ml_models.clear() + + +app = FastAPI(lifespan=lifespan) + + +@app.get("/predict") +async def predict(x: float): + result = ml_models["answer_to_everything"](x) + return {"result": result} diff --git a/docs_src/extending_openapi/tutorial001_py310.py b/docs_src/extending_openapi/tutorial001_py310.py new file mode 100644 index 000000000..35e31c0e0 --- /dev/null +++ b/docs_src/extending_openapi/tutorial001_py310.py @@ -0,0 +1,29 @@ +from fastapi import FastAPI +from fastapi.openapi.utils import get_openapi + +app = FastAPI() + + +@app.get("/items/") +async def read_items(): + return [{"name": "Foo"}] + + +def custom_openapi(): + if app.openapi_schema: + return app.openapi_schema + openapi_schema = get_openapi( + title="Custom title", + version="2.5.0", + summary="This is a very custom OpenAPI schema", + description="Here's a longer description of the custom **OpenAPI** schema", + routes=app.routes, + ) + openapi_schema["info"]["x-logo"] = { + "url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" + } + app.openapi_schema = openapi_schema + return app.openapi_schema + + +app.openapi = custom_openapi diff --git a/docs_src/extra_data_types/tutorial001_an_py39.py b/docs_src/extra_data_types/tutorial001_an_py39.py deleted file mode 100644 index fa3551d66..000000000 --- a/docs_src/extra_data_types/tutorial001_an_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from datetime import datetime, time, timedelta -from typing import Annotated, Union -from uuid import UUID - -from fastapi import Body, FastAPI - -app = FastAPI() - - -@app.put("/items/{item_id}") -async def read_items( - item_id: UUID, - start_datetime: Annotated[datetime, Body()], - end_datetime: Annotated[datetime, Body()], - process_after: Annotated[timedelta, Body()], - repeat_at: Annotated[Union[time, None], Body()] = None, -): - start_process = start_datetime + process_after - duration = end_datetime - start_process - return { - "item_id": item_id, - "start_datetime": start_datetime, - "end_datetime": end_datetime, - "process_after": process_after, - "repeat_at": repeat_at, - "start_process": start_process, - "duration": duration, - } diff --git a/docs_src/extra_data_types/tutorial001_py39.py b/docs_src/extra_data_types/tutorial001_py39.py deleted file mode 100644 index 71de958ff..000000000 --- a/docs_src/extra_data_types/tutorial001_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from datetime import datetime, time, timedelta -from typing import Union -from uuid import UUID - -from fastapi import Body, FastAPI - -app = FastAPI() - - -@app.put("/items/{item_id}") -async def read_items( - item_id: UUID, - start_datetime: datetime = Body(), - end_datetime: datetime = Body(), - process_after: timedelta = Body(), - repeat_at: Union[time, None] = Body(default=None), -): - start_process = start_datetime + process_after - duration = end_datetime - start_process - return { - "item_id": item_id, - "start_datetime": start_datetime, - "end_datetime": end_datetime, - "process_after": process_after, - "repeat_at": repeat_at, - "start_process": start_process, - "duration": duration, - } diff --git a/docs_src/extra_models/tutorial001_py39.py b/docs_src/extra_models/tutorial001_py39.py deleted file mode 100644 index 327ffcdf0..000000000 --- a/docs_src/extra_models/tutorial001_py39.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel, EmailStr - -app = FastAPI() - - -class UserIn(BaseModel): - username: str - password: str - email: EmailStr - full_name: Union[str, None] = None - - -class UserOut(BaseModel): - username: str - email: EmailStr - full_name: Union[str, None] = None - - -class UserInDB(BaseModel): - username: str - hashed_password: str - email: EmailStr - full_name: Union[str, None] = None - - -def fake_password_hasher(raw_password: str): - return "supersecret" + raw_password - - -def fake_save_user(user_in: UserIn): - hashed_password = fake_password_hasher(user_in.password) - user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password) - print("User saved! ..not really") - return user_in_db - - -@app.post("/user/", response_model=UserOut) -async def create_user(user_in: UserIn): - user_saved = fake_save_user(user_in) - return user_saved diff --git a/docs_src/extra_models/tutorial002_py39.py b/docs_src/extra_models/tutorial002_py39.py deleted file mode 100644 index 654379601..000000000 --- a/docs_src/extra_models/tutorial002_py39.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel, EmailStr - -app = FastAPI() - - -class UserBase(BaseModel): - username: str - email: EmailStr - full_name: Union[str, None] = None - - -class UserIn(UserBase): - password: str - - -class UserOut(UserBase): - pass - - -class UserInDB(UserBase): - hashed_password: str - - -def fake_password_hasher(raw_password: str): - return "supersecret" + raw_password - - -def fake_save_user(user_in: UserIn): - hashed_password = fake_password_hasher(user_in.password) - user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password) - print("User saved! ..not really") - return user_in_db - - -@app.post("/user/", response_model=UserOut) -async def create_user(user_in: UserIn): - user_saved = fake_save_user(user_in) - return user_saved diff --git a/docs_src/extra_models/tutorial003_py39.py b/docs_src/extra_models/tutorial003_py39.py deleted file mode 100644 index 06675cbc0..000000000 --- a/docs_src/extra_models/tutorial003_py39.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class BaseItem(BaseModel): - description: str - type: str - - -class CarItem(BaseItem): - type: str = "car" - - -class PlaneItem(BaseItem): - type: str = "plane" - size: int - - -items = { - "item1": {"description": "All my friends drive a low rider", "type": "car"}, - "item2": { - "description": "Music is my aeroplane, it's my aeroplane", - "type": "plane", - "size": 5, - }, -} - - -@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem]) -async def read_item(item_id: str): - return items[item_id] diff --git a/docs_src/extra_models/tutorial004_py310.py b/docs_src/extra_models/tutorial004_py310.py new file mode 100644 index 000000000..28cacde4d --- /dev/null +++ b/docs_src/extra_models/tutorial004_py310.py @@ -0,0 +1,20 @@ +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: str + + +items = [ + {"name": "Foo", "description": "There comes my hero"}, + {"name": "Red", "description": "It's my aeroplane"}, +] + + +@app.get("/items/", response_model=list[Item]) +async def read_items(): + return items diff --git a/docs_src/extra_models/tutorial005_py310.py b/docs_src/extra_models/tutorial005_py310.py new file mode 100644 index 000000000..9da2a0a0f --- /dev/null +++ b/docs_src/extra_models/tutorial005_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/keyword-weights/", response_model=dict[str, float]) +async def read_keyword_weights(): + return {"foo": 2.3, "bar": 3.4} diff --git a/docs_src/first_steps/tutorial001_py310.py b/docs_src/first_steps/tutorial001_py310.py new file mode 100644 index 000000000..ee60be1f9 --- /dev/null +++ b/docs_src/first_steps/tutorial001_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} diff --git a/docs_src/first_steps/tutorial003_py310.py b/docs_src/first_steps/tutorial003_py310.py new file mode 100644 index 000000000..e30b827ea --- /dev/null +++ b/docs_src/first_steps/tutorial003_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def root(): + return {"message": "Hello World"} diff --git a/docs_src/generate_clients/tutorial001_py310.py b/docs_src/generate_clients/tutorial001_py310.py new file mode 100644 index 000000000..6a5ae2320 --- /dev/null +++ b/docs_src/generate_clients/tutorial001_py310.py @@ -0,0 +1,26 @@ +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + + +class ResponseMessage(BaseModel): + message: str + + +@app.post("/items/", response_model=ResponseMessage) +async def create_item(item: Item): + return {"message": "item received"} + + +@app.get("/items/", response_model=list[Item]) +async def get_items(): + return [ + {"name": "Plumbus", "price": 3}, + {"name": "Portal Gun", "price": 9001}, + ] diff --git a/docs_src/generate_clients/tutorial002_py310.py b/docs_src/generate_clients/tutorial002_py310.py new file mode 100644 index 000000000..83309760b --- /dev/null +++ b/docs_src/generate_clients/tutorial002_py310.py @@ -0,0 +1,36 @@ +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + + +class ResponseMessage(BaseModel): + message: str + + +class User(BaseModel): + username: str + email: str + + +@app.post("/items/", response_model=ResponseMessage, tags=["items"]) +async def create_item(item: Item): + return {"message": "Item received"} + + +@app.get("/items/", response_model=list[Item], tags=["items"]) +async def get_items(): + return [ + {"name": "Plumbus", "price": 3}, + {"name": "Portal Gun", "price": 9001}, + ] + + +@app.post("/users/", response_model=ResponseMessage, tags=["users"]) +async def create_user(user: User): + return {"message": "User received"} diff --git a/docs_src/generate_clients/tutorial003_py310.py b/docs_src/generate_clients/tutorial003_py310.py new file mode 100644 index 000000000..40722cf10 --- /dev/null +++ b/docs_src/generate_clients/tutorial003_py310.py @@ -0,0 +1,42 @@ +from fastapi import FastAPI +from fastapi.routing import APIRoute +from pydantic import BaseModel + + +def custom_generate_unique_id(route: APIRoute): + return f"{route.tags[0]}-{route.name}" + + +app = FastAPI(generate_unique_id_function=custom_generate_unique_id) + + +class Item(BaseModel): + name: str + price: float + + +class ResponseMessage(BaseModel): + message: str + + +class User(BaseModel): + username: str + email: str + + +@app.post("/items/", response_model=ResponseMessage, tags=["items"]) +async def create_item(item: Item): + return {"message": "Item received"} + + +@app.get("/items/", response_model=list[Item], tags=["items"]) +async def get_items(): + return [ + {"name": "Plumbus", "price": 3}, + {"name": "Portal Gun", "price": 9001}, + ] + + +@app.post("/users/", response_model=ResponseMessage, tags=["users"]) +async def create_user(user: User): + return {"message": "User received"} diff --git a/docs_src/generate_clients/tutorial004_py310.py b/docs_src/generate_clients/tutorial004_py310.py new file mode 100644 index 000000000..894dc7f8d --- /dev/null +++ b/docs_src/generate_clients/tutorial004_py310.py @@ -0,0 +1,15 @@ +import json +from pathlib import Path + +file_path = Path("./openapi.json") +openapi_content = json.loads(file_path.read_text()) + +for path_data in openapi_content["paths"].values(): + for operation in path_data.values(): + tag = operation["tags"][0] + operation_id = operation["operationId"] + to_remove = f"{tag}-" + new_operation_id = operation_id[len(to_remove) :] + operation["operationId"] = new_operation_id + +file_path.write_text(json.dumps(openapi_content)) diff --git a/docs_src/graphql_/tutorial001_py310.py b/docs_src/graphql_/tutorial001_py310.py new file mode 100644 index 000000000..e92b2d71c --- /dev/null +++ b/docs_src/graphql_/tutorial001_py310.py @@ -0,0 +1,25 @@ +import strawberry +from fastapi import FastAPI +from strawberry.fastapi import GraphQLRouter + + +@strawberry.type +class User: + name: str + age: int + + +@strawberry.type +class Query: + @strawberry.field + def user(self) -> User: + return User(name="Patrick", age=100) + + +schema = strawberry.Schema(query=Query) + + +graphql_app = GraphQLRouter(schema) + +app = FastAPI() +app.include_router(graphql_app, prefix="/graphql") diff --git a/docs_src/handling_errors/tutorial001_py310.py b/docs_src/handling_errors/tutorial001_py310.py new file mode 100644 index 000000000..e5f32aac2 --- /dev/null +++ b/docs_src/handling_errors/tutorial001_py310.py @@ -0,0 +1,12 @@ +from fastapi import FastAPI, HTTPException + +app = FastAPI() + +items = {"foo": "The Foo Wrestlers"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: str): + if item_id not in items: + raise HTTPException(status_code=404, detail="Item not found") + return {"item": items[item_id]} diff --git a/docs_src/handling_errors/tutorial002_py310.py b/docs_src/handling_errors/tutorial002_py310.py new file mode 100644 index 000000000..e48c295c9 --- /dev/null +++ b/docs_src/handling_errors/tutorial002_py310.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI, HTTPException + +app = FastAPI() + +items = {"foo": "The Foo Wrestlers"} + + +@app.get("/items-header/{item_id}") +async def read_item_header(item_id: str): + if item_id not in items: + raise HTTPException( + status_code=404, + detail="Item not found", + headers={"X-Error": "There goes my error"}, + ) + return {"item": items[item_id]} diff --git a/docs_src/handling_errors/tutorial003_py310.py b/docs_src/handling_errors/tutorial003_py310.py new file mode 100644 index 000000000..791cd6838 --- /dev/null +++ b/docs_src/handling_errors/tutorial003_py310.py @@ -0,0 +1,25 @@ +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse + + +class UnicornException(Exception): + def __init__(self, name: str): + self.name = name + + +app = FastAPI() + + +@app.exception_handler(UnicornException) +async def unicorn_exception_handler(request: Request, exc: UnicornException): + return JSONResponse( + status_code=418, + content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."}, + ) + + +@app.get("/unicorns/{name}") +async def read_unicorn(name: str): + if name == "yolo": + raise UnicornException(name=name) + return {"unicorn_name": name} diff --git a/docs_src/handling_errors/tutorial004_py310.py b/docs_src/handling_errors/tutorial004_py310.py new file mode 100644 index 000000000..ae50807e9 --- /dev/null +++ b/docs_src/handling_errors/tutorial004_py310.py @@ -0,0 +1,26 @@ +from fastapi import FastAPI, HTTPException +from fastapi.exceptions import RequestValidationError +from fastapi.responses import PlainTextResponse +from starlette.exceptions import HTTPException as StarletteHTTPException + +app = FastAPI() + + +@app.exception_handler(StarletteHTTPException) +async def http_exception_handler(request, exc): + return PlainTextResponse(str(exc.detail), status_code=exc.status_code) + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request, exc: RequestValidationError): + message = "Validation errors:" + for error in exc.errors(): + message += f"\nField: {error['loc']}, Error: {error['msg']}" + return PlainTextResponse(message, status_code=400) + + +@app.get("/items/{item_id}") +async def read_item(item_id: int): + if item_id == 3: + raise HTTPException(status_code=418, detail="Nope! I don't like 3.") + return {"item_id": item_id} diff --git a/docs_src/handling_errors/tutorial005_py310.py b/docs_src/handling_errors/tutorial005_py310.py new file mode 100644 index 000000000..0e04fa086 --- /dev/null +++ b/docs_src/handling_errors/tutorial005_py310.py @@ -0,0 +1,25 @@ +from fastapi import FastAPI, Request +from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import RequestValidationError +from fastapi.responses import JSONResponse +from pydantic import BaseModel + +app = FastAPI() + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request: Request, exc: RequestValidationError): + return JSONResponse( + status_code=422, + content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}), + ) + + +class Item(BaseModel): + title: str + size: int + + +@app.post("/items/") +async def create_item(item: Item): + return item diff --git a/docs_src/handling_errors/tutorial006_py310.py b/docs_src/handling_errors/tutorial006_py310.py new file mode 100644 index 000000000..e05160d7e --- /dev/null +++ b/docs_src/handling_errors/tutorial006_py310.py @@ -0,0 +1,28 @@ +from fastapi import FastAPI, HTTPException +from fastapi.exception_handlers import ( + http_exception_handler, + request_validation_exception_handler, +) +from fastapi.exceptions import RequestValidationError +from starlette.exceptions import HTTPException as StarletteHTTPException + +app = FastAPI() + + +@app.exception_handler(StarletteHTTPException) +async def custom_http_exception_handler(request, exc): + print(f"OMG! An HTTP error!: {repr(exc)}") + return await http_exception_handler(request, exc) + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request, exc): + print(f"OMG! The client sent invalid data!: {exc}") + return await request_validation_exception_handler(request, exc) + + +@app.get("/items/{item_id}") +async def read_item(item_id: int): + if item_id == 3: + raise HTTPException(status_code=418, detail="Nope! I don't like 3.") + return {"item_id": item_id} diff --git a/docs_src/header_param_models/tutorial001_an_py39.py b/docs_src/header_param_models/tutorial001_an_py39.py deleted file mode 100644 index 51a5f94fc..000000000 --- a/docs_src/header_param_models/tutorial001_an_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: list[str] = [] - - -@app.get("/items/") -async def read_items(headers: Annotated[CommonHeaders, Header()]): - return headers diff --git a/docs_src/header_param_models/tutorial001_py39.py b/docs_src/header_param_models/tutorial001_py39.py deleted file mode 100644 index 4c1137813..000000000 --- a/docs_src/header_param_models/tutorial001_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: list[str] = [] - - -@app.get("/items/") -async def read_items(headers: CommonHeaders = Header()): - return headers diff --git a/docs_src/header_param_models/tutorial002_an_py39.py b/docs_src/header_param_models/tutorial002_an_py39.py deleted file mode 100644 index ca5208c9d..000000000 --- a/docs_src/header_param_models/tutorial002_an_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - model_config = {"extra": "forbid"} - - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: list[str] = [] - - -@app.get("/items/") -async def read_items(headers: Annotated[CommonHeaders, Header()]): - return headers diff --git a/docs_src/header_param_models/tutorial002_py39.py b/docs_src/header_param_models/tutorial002_py39.py deleted file mode 100644 index f8ce559a7..000000000 --- a/docs_src/header_param_models/tutorial002_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - model_config = {"extra": "forbid"} - - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: list[str] = [] - - -@app.get("/items/") -async def read_items(headers: CommonHeaders = Header()): - return headers diff --git a/docs_src/header_param_models/tutorial003_an_py39.py b/docs_src/header_param_models/tutorial003_an_py39.py deleted file mode 100644 index 8be6b01d0..000000000 --- a/docs_src/header_param_models/tutorial003_an_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: list[str] = [] - - -@app.get("/items/") -async def read_items( - headers: Annotated[CommonHeaders, Header(convert_underscores=False)], -): - return headers diff --git a/docs_src/header_param_models/tutorial003_py39.py b/docs_src/header_param_models/tutorial003_py39.py deleted file mode 100644 index 848c34111..000000000 --- a/docs_src/header_param_models/tutorial003_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: list[str] = [] - - -@app.get("/items/") -async def read_items(headers: CommonHeaders = Header(convert_underscores=False)): - return headers diff --git a/docs_src/header_params/tutorial001_an_py39.py b/docs_src/header_params/tutorial001_an_py39.py deleted file mode 100644 index 1fbe3bb99..000000000 --- a/docs_src/header_params/tutorial001_an_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Header - -app = FastAPI() - - -@app.get("/items/") -async def read_items(user_agent: Annotated[Union[str, None], Header()] = None): - return {"User-Agent": user_agent} diff --git a/docs_src/header_params/tutorial001_py39.py b/docs_src/header_params/tutorial001_py39.py deleted file mode 100644 index 74429c8e2..000000000 --- a/docs_src/header_params/tutorial001_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header - -app = FastAPI() - - -@app.get("/items/") -async def read_items(user_agent: Union[str, None] = Header(default=None)): - return {"User-Agent": user_agent} diff --git a/docs_src/header_params/tutorial002_an_py39.py b/docs_src/header_params/tutorial002_an_py39.py deleted file mode 100644 index 008e4b6e1..000000000 --- a/docs_src/header_params/tutorial002_an_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Header - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - strange_header: Annotated[ - Union[str, None], Header(convert_underscores=False) - ] = None, -): - return {"strange_header": strange_header} diff --git a/docs_src/header_params/tutorial002_py39.py b/docs_src/header_params/tutorial002_py39.py deleted file mode 100644 index 0a34f17cc..000000000 --- a/docs_src/header_params/tutorial002_py39.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - strange_header: Union[str, None] = Header(default=None, convert_underscores=False), -): - return {"strange_header": strange_header} diff --git a/docs_src/header_params/tutorial003_an_py39.py b/docs_src/header_params/tutorial003_an_py39.py deleted file mode 100644 index 5aad89407..000000000 --- a/docs_src/header_params/tutorial003_an_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Header - -app = FastAPI() - - -@app.get("/items/") -async def read_items(x_token: Annotated[Union[list[str], None], Header()] = None): - return {"X-Token values": x_token} diff --git a/docs_src/header_params/tutorial003_py39.py b/docs_src/header_params/tutorial003_py39.py deleted file mode 100644 index 34437db16..000000000 --- a/docs_src/header_params/tutorial003_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header - -app = FastAPI() - - -@app.get("/items/") -async def read_items(x_token: Union[list[str], None] = Header(default=None)): - return {"X-Token values": x_token} diff --git a/docs_src/metadata/tutorial001_1_py310.py b/docs_src/metadata/tutorial001_1_py310.py new file mode 100644 index 000000000..419232d86 --- /dev/null +++ b/docs_src/metadata/tutorial001_1_py310.py @@ -0,0 +1,38 @@ +from fastapi import FastAPI + +description = """ +ChimichangApp API helps you do awesome stuff. 🚀 + +## Items + +You can **read items**. + +## Users + +You will be able to: + +* **Create users** (_not implemented_). +* **Read users** (_not implemented_). +""" + +app = FastAPI( + title="ChimichangApp", + description=description, + summary="Deadpool's favorite app. Nuff said.", + version="0.0.1", + terms_of_service="http://example.com/terms/", + contact={ + "name": "Deadpoolio the Amazing", + "url": "http://x-force.example.com/contact/", + "email": "dp@x-force.example.com", + }, + license_info={ + "name": "Apache 2.0", + "identifier": "Apache-2.0", + }, +) + + +@app.get("/items/") +async def read_items(): + return [{"name": "Katana"}] diff --git a/docs_src/metadata/tutorial001_py310.py b/docs_src/metadata/tutorial001_py310.py new file mode 100644 index 000000000..76656e81b --- /dev/null +++ b/docs_src/metadata/tutorial001_py310.py @@ -0,0 +1,38 @@ +from fastapi import FastAPI + +description = """ +ChimichangApp API helps you do awesome stuff. 🚀 + +## Items + +You can **read items**. + +## Users + +You will be able to: + +* **Create users** (_not implemented_). +* **Read users** (_not implemented_). +""" + +app = FastAPI( + title="ChimichangApp", + description=description, + summary="Deadpool's favorite app. Nuff said.", + version="0.0.1", + terms_of_service="http://example.com/terms/", + contact={ + "name": "Deadpoolio the Amazing", + "url": "http://x-force.example.com/contact/", + "email": "dp@x-force.example.com", + }, + license_info={ + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html", + }, +) + + +@app.get("/items/") +async def read_items(): + return [{"name": "Katana"}] diff --git a/docs_src/metadata/tutorial002_py310.py b/docs_src/metadata/tutorial002_py310.py new file mode 100644 index 000000000..cf9ed7087 --- /dev/null +++ b/docs_src/metadata/tutorial002_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI(openapi_url="/api/v1/openapi.json") + + +@app.get("/items/") +async def read_items(): + return [{"name": "Foo"}] diff --git a/docs_src/metadata/tutorial003_py310.py b/docs_src/metadata/tutorial003_py310.py new file mode 100644 index 000000000..ee09c7f37 --- /dev/null +++ b/docs_src/metadata/tutorial003_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI(docs_url="/documentation", redoc_url=None) + + +@app.get("/items/") +async def read_items(): + return [{"name": "Foo"}] diff --git a/docs_src/metadata/tutorial004_py310.py b/docs_src/metadata/tutorial004_py310.py new file mode 100644 index 000000000..465bd659d --- /dev/null +++ b/docs_src/metadata/tutorial004_py310.py @@ -0,0 +1,28 @@ +from fastapi import FastAPI + +tags_metadata = [ + { + "name": "users", + "description": "Operations with users. The **login** logic is also here.", + }, + { + "name": "items", + "description": "Manage items. So _fancy_ they have their own docs.", + "externalDocs": { + "description": "Items external docs", + "url": "https://fastapi.tiangolo.com/", + }, + }, +] + +app = FastAPI(openapi_tags=tags_metadata) + + +@app.get("/users/", tags=["users"]) +async def get_users(): + return [{"name": "Harry"}, {"name": "Ron"}] + + +@app.get("/items/", tags=["items"]) +async def get_items(): + return [{"name": "wand"}, {"name": "flying broom"}] diff --git a/docs_src/middleware/tutorial001_py310.py b/docs_src/middleware/tutorial001_py310.py new file mode 100644 index 000000000..e65a7dade --- /dev/null +++ b/docs_src/middleware/tutorial001_py310.py @@ -0,0 +1,14 @@ +import time + +from fastapi import FastAPI, Request + +app = FastAPI() + + +@app.middleware("http") +async def add_process_time_header(request: Request, call_next): + start_time = time.perf_counter() + response = await call_next(request) + process_time = time.perf_counter() - start_time + response.headers["X-Process-Time"] = str(process_time) + return response diff --git a/docs_src/openapi_callbacks/tutorial001_py39.py b/docs_src/openapi_callbacks/tutorial001_py39.py deleted file mode 100644 index 3f1bac6e2..000000000 --- a/docs_src/openapi_callbacks/tutorial001_py39.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Union - -from fastapi import APIRouter, FastAPI -from pydantic import BaseModel, HttpUrl - -app = FastAPI() - - -class Invoice(BaseModel): - id: str - title: Union[str, None] = None - customer: str - total: float - - -class InvoiceEvent(BaseModel): - description: str - paid: bool - - -class InvoiceEventReceived(BaseModel): - ok: bool - - -invoices_callback_router = APIRouter() - - -@invoices_callback_router.post( - "{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived -) -def invoice_notification(body: InvoiceEvent): - pass - - -@app.post("/invoices/", callbacks=invoices_callback_router.routes) -def create_invoice(invoice: Invoice, callback_url: Union[HttpUrl, None] = None): - """ - Create an invoice. - - This will (let's imagine) let the API user (some external developer) create an - invoice. - - And this path operation will: - - * Send the invoice to the client. - * Collect the money from the client. - * Send a notification back to the API user (the external developer), as a callback. - * At this point is that the API will somehow send a POST request to the - external API with the notification of the invoice event - (e.g. "payment successful"). - """ - # Send the invoice, collect the money, send the notification (the callback) - return {"msg": "Invoice received"} diff --git a/docs_src/openapi_webhooks/tutorial001_py310.py b/docs_src/openapi_webhooks/tutorial001_py310.py new file mode 100644 index 000000000..55822bb48 --- /dev/null +++ b/docs_src/openapi_webhooks/tutorial001_py310.py @@ -0,0 +1,25 @@ +from datetime import datetime + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Subscription(BaseModel): + username: str + monthly_fee: float + start_date: datetime + + +@app.webhooks.post("new-subscription") +def new_subscription(body: Subscription): + """ + When a new user subscribes to your service we'll send you a POST request with this + data to the URL that you register for the event `new-subscription` in the dashboard. + """ + + +@app.get("/users/") +def read_users(): + return ["Rick", "Morty"] diff --git a/docs_src/path_operation_advanced_configuration/tutorial001_py310.py b/docs_src/path_operation_advanced_configuration/tutorial001_py310.py new file mode 100644 index 000000000..fafa8ffb8 --- /dev/null +++ b/docs_src/path_operation_advanced_configuration/tutorial001_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/", operation_id="some_specific_id_you_define") +async def read_items(): + return [{"item_id": "Foo"}] diff --git a/docs_src/path_operation_advanced_configuration/tutorial002_py310.py b/docs_src/path_operation_advanced_configuration/tutorial002_py310.py new file mode 100644 index 000000000..3aaae9b37 --- /dev/null +++ b/docs_src/path_operation_advanced_configuration/tutorial002_py310.py @@ -0,0 +1,24 @@ +from fastapi import FastAPI +from fastapi.routing import APIRoute + +app = FastAPI() + + +@app.get("/items/") +async def read_items(): + return [{"item_id": "Foo"}] + + +def use_route_names_as_operation_ids(app: FastAPI) -> None: + """ + Simplify operation IDs so that generated API clients have simpler function + names. + + Should be called only after all routes have been added. + """ + for route in app.routes: + if isinstance(route, APIRoute): + route.operation_id = route.name # in this case, 'read_items' + + +use_route_names_as_operation_ids(app) diff --git a/docs_src/path_operation_advanced_configuration/tutorial003_py310.py b/docs_src/path_operation_advanced_configuration/tutorial003_py310.py new file mode 100644 index 000000000..dcc358e32 --- /dev/null +++ b/docs_src/path_operation_advanced_configuration/tutorial003_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/", include_in_schema=False) +async def read_items(): + return [{"item_id": "Foo"}] diff --git a/docs_src/path_operation_advanced_configuration/tutorial004_py39.py b/docs_src/path_operation_advanced_configuration/tutorial004_py39.py deleted file mode 100644 index 8fabe7cb8..000000000 --- a/docs_src/path_operation_advanced_configuration/tutorial004_py39.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - - -@app.post("/items/", summary="Create an item") -async def create_item(item: Item) -> Item: - """ - Create an item with all the information: - - - **name**: each item must have a name - - **description**: a long description - - **price**: required - - **tax**: if the item doesn't have tax, you can omit this - - **tags**: a set of unique tag strings for this item - \f - :param item: User input. - """ - return item diff --git a/docs_src/path_operation_advanced_configuration/tutorial005_py310.py b/docs_src/path_operation_advanced_configuration/tutorial005_py310.py new file mode 100644 index 000000000..5837ad835 --- /dev/null +++ b/docs_src/path_operation_advanced_configuration/tutorial005_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/", openapi_extra={"x-aperture-labs-portal": "blue"}) +async def read_items(): + return [{"item_id": "portal-gun"}] diff --git a/docs_src/path_operation_advanced_configuration/tutorial006_py310.py b/docs_src/path_operation_advanced_configuration/tutorial006_py310.py new file mode 100644 index 000000000..403c3ee3f --- /dev/null +++ b/docs_src/path_operation_advanced_configuration/tutorial006_py310.py @@ -0,0 +1,41 @@ +from fastapi import FastAPI, Request + +app = FastAPI() + + +def magic_data_reader(raw_body: bytes): + return { + "size": len(raw_body), + "content": { + "name": "Maaaagic", + "price": 42, + "description": "Just kiddin', no magic here. ✨", + }, + } + + +@app.post( + "/items/", + openapi_extra={ + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"type": "string"}, + "price": {"type": "number"}, + "description": {"type": "string"}, + }, + } + } + }, + "required": True, + }, + }, +) +async def create_item(request: Request): + raw_body = await request.body() + data = magic_data_reader(raw_body) + return data diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py b/docs_src/path_operation_advanced_configuration/tutorial007_py310.py similarity index 68% rename from docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py rename to docs_src/path_operation_advanced_configuration/tutorial007_py310.py index 849f648e1..ff64ef792 100644 --- a/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py +++ b/docs_src/path_operation_advanced_configuration/tutorial007_py310.py @@ -1,6 +1,6 @@ import yaml from fastapi import FastAPI, HTTPException, Request -from pydantic.v1 import BaseModel, ValidationError +from pydantic import BaseModel, ValidationError app = FastAPI() @@ -14,7 +14,7 @@ class Item(BaseModel): "/items/", openapi_extra={ "requestBody": { - "content": {"application/x-yaml": {"schema": Item.schema()}}, + "content": {"application/x-yaml": {"schema": Item.model_json_schema()}}, "required": True, }, }, @@ -26,7 +26,7 @@ async def create_item(request: Request): except yaml.YAMLError: raise HTTPException(status_code=422, detail="Invalid YAML") try: - item = Item.parse_obj(data) + item = Item.model_validate(data) except ValidationError as e: - raise HTTPException(status_code=422, detail=e.errors()) + raise HTTPException(status_code=422, detail=e.errors(include_url=False)) return item diff --git a/docs_src/path_operation_configuration/tutorial001_py39.py b/docs_src/path_operation_configuration/tutorial001_py39.py deleted file mode 100644 index 09b318282..000000000 --- a/docs_src/path_operation_configuration/tutorial001_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, status -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - - -@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_py39.py b/docs_src/path_operation_configuration/tutorial002_py39.py deleted file mode 100644 index fca3b0de9..000000000 --- a/docs_src/path_operation_configuration/tutorial002_py39.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - - -@app.post("/items/", tags=["items"]) -async def create_item(item: Item) -> Item: - return item - - -@app.get("/items/", tags=["items"]) -async def read_items(): - return [{"name": "Foo", "price": 42}] - - -@app.get("/users/", tags=["users"]) -async def read_users(): - return [{"username": "johndoe"}] diff --git a/docs_src/path_operation_configuration/tutorial002b_py310.py b/docs_src/path_operation_configuration/tutorial002b_py310.py new file mode 100644 index 000000000..d53b4d817 --- /dev/null +++ b/docs_src/path_operation_configuration/tutorial002b_py310.py @@ -0,0 +1,20 @@ +from enum import Enum + +from fastapi import FastAPI + +app = FastAPI() + + +class Tags(Enum): + items = "items" + users = "users" + + +@app.get("/items/", tags=[Tags.items]) +async def get_items(): + return ["Portal gun", "Plumbus"] + + +@app.get("/users/", tags=[Tags.users]) +async def read_users(): + return ["Rick", "Morty"] diff --git a/docs_src/path_operation_configuration/tutorial003_py39.py b/docs_src/path_operation_configuration/tutorial003_py39.py deleted file mode 100644 index a77fb34d8..000000000 --- a/docs_src/path_operation_configuration/tutorial003_py39.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - - -@app.post( - "/items/", - 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) -> Item: - return item diff --git a/docs_src/path_operation_configuration/tutorial004_py39.py b/docs_src/path_operation_configuration/tutorial004_py39.py deleted file mode 100644 index 31dfbff1d..000000000 --- a/docs_src/path_operation_configuration/tutorial004_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - - -@app.post("/items/", summary="Create an item") -async def create_item(item: Item) -> Item: - """ - Create an item with all the information: - - - **name**: each item must have a name - - **description**: a long description - - **price**: required - - **tax**: if the item doesn't have tax, you can omit this - - **tags**: a set of unique tag strings for this item - """ - return item diff --git a/docs_src/path_operation_configuration/tutorial005_py39.py b/docs_src/path_operation_configuration/tutorial005_py39.py deleted file mode 100644 index 0a53a8f2d..000000000 --- a/docs_src/path_operation_configuration/tutorial005_py39.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: set[str] = set() - - -@app.post( - "/items/", - summary="Create an item", - response_description="The created item", -) -async def create_item(item: Item) -> Item: - """ - Create an item with all the information: - - - **name**: each item must have a name - - **description**: a long description - - **price**: required - - **tax**: if the item doesn't have tax, you can omit this - - **tags**: a set of unique tag strings for this item - """ - return item diff --git a/docs_src/path_operation_configuration/tutorial006_py310.py b/docs_src/path_operation_configuration/tutorial006_py310.py new file mode 100644 index 000000000..7c1aa9b20 --- /dev/null +++ b/docs_src/path_operation_configuration/tutorial006_py310.py @@ -0,0 +1,18 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/", tags=["items"]) +async def read_items(): + return [{"name": "Foo", "price": 42}] + + +@app.get("/users/", tags=["users"]) +async def read_users(): + return [{"username": "johndoe"}] + + +@app.get("/elements/", tags=["items"], deprecated=True) +async def read_elements(): + return [{"item_id": "Foo"}] diff --git a/docs_src/path_params/tutorial001_py310.py b/docs_src/path_params/tutorial001_py310.py new file mode 100644 index 000000000..7bbf70e6c --- /dev/null +++ b/docs_src/path_params/tutorial001_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_item(item_id): + return {"item_id": item_id} diff --git a/docs_src/path_params/tutorial002_py310.py b/docs_src/path_params/tutorial002_py310.py new file mode 100644 index 000000000..8272ad70d --- /dev/null +++ b/docs_src/path_params/tutorial002_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_item(item_id: int): + return {"item_id": item_id} diff --git a/docs_src/path_params/tutorial003_py310.py b/docs_src/path_params/tutorial003_py310.py new file mode 100644 index 000000000..5f0aa0923 --- /dev/null +++ b/docs_src/path_params/tutorial003_py310.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/users/me") +async def read_user_me(): + return {"user_id": "the current user"} + + +@app.get("/users/{user_id}") +async def read_user(user_id: str): + return {"user_id": user_id} diff --git a/docs_src/path_params/tutorial003b_py310.py b/docs_src/path_params/tutorial003b_py310.py new file mode 100644 index 000000000..822d37369 --- /dev/null +++ b/docs_src/path_params/tutorial003b_py310.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/users") +async def read_users(): + return ["Rick", "Morty"] + + +@app.get("/users") +async def read_users2(): + return ["Bean", "Elfo"] diff --git a/docs_src/path_params/tutorial004_py310.py b/docs_src/path_params/tutorial004_py310.py new file mode 100644 index 000000000..2961e6178 --- /dev/null +++ b/docs_src/path_params/tutorial004_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/files/{file_path:path}") +async def read_file(file_path: str): + return {"file_path": file_path} diff --git a/docs_src/path_params/tutorial005_py310.py b/docs_src/path_params/tutorial005_py310.py new file mode 100644 index 000000000..9a24a4963 --- /dev/null +++ b/docs_src/path_params/tutorial005_py310.py @@ -0,0 +1,23 @@ +from enum import Enum + +from fastapi import FastAPI + + +class ModelName(str, Enum): + alexnet = "alexnet" + resnet = "resnet" + lenet = "lenet" + + +app = FastAPI() + + +@app.get("/models/{model_name}") +async def get_model(model_name: ModelName): + if model_name is ModelName.alexnet: + return {"model_name": model_name, "message": "Deep Learning FTW!"} + + if model_name.value == "lenet": + return {"model_name": model_name, "message": "LeCNN all the images"} + + return {"model_name": model_name, "message": "Have some residuals"} diff --git a/docs_src/path_params_numeric_validations/tutorial002_an_py310.py b/docs_src/path_params_numeric_validations/tutorial002_an_py310.py new file mode 100644 index 000000000..cd882abb2 --- /dev/null +++ b/docs_src/path_params_numeric_validations/tutorial002_an_py310.py @@ -0,0 +1,15 @@ +from typing import Annotated + +from fastapi import FastAPI, Path + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items( + q: str, item_id: Annotated[int, Path(title="The ID of the item to get")] +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/path_params_numeric_validations/tutorial002_py310.py b/docs_src/path_params_numeric_validations/tutorial002_py310.py new file mode 100644 index 000000000..63ac691a8 --- /dev/null +++ b/docs_src/path_params_numeric_validations/tutorial002_py310.py @@ -0,0 +1,11 @@ +from fastapi import FastAPI, Path + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items(q: str, item_id: int = Path(title="The ID of the item to get")): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/path_params_numeric_validations/tutorial003_an_py310.py b/docs_src/path_params_numeric_validations/tutorial003_an_py310.py new file mode 100644 index 000000000..1588556e7 --- /dev/null +++ b/docs_src/path_params_numeric_validations/tutorial003_an_py310.py @@ -0,0 +1,15 @@ +from typing import Annotated + +from fastapi import FastAPI, Path + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items( + item_id: Annotated[int, Path(title="The ID of the item to get")], q: str +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/path_params_numeric_validations/tutorial003_py310.py b/docs_src/path_params_numeric_validations/tutorial003_py310.py new file mode 100644 index 000000000..8df0ffc62 --- /dev/null +++ b/docs_src/path_params_numeric_validations/tutorial003_py310.py @@ -0,0 +1,11 @@ +from fastapi import FastAPI, Path + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/path_params_numeric_validations/tutorial004_an_py310.py b/docs_src/path_params_numeric_validations/tutorial004_an_py310.py new file mode 100644 index 000000000..f67f6450e --- /dev/null +++ b/docs_src/path_params_numeric_validations/tutorial004_an_py310.py @@ -0,0 +1,15 @@ +from typing import Annotated + +from fastapi import FastAPI, Path + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items( + item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)], q: str +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/path_params_numeric_validations/tutorial004_py310.py b/docs_src/path_params_numeric_validations/tutorial004_py310.py new file mode 100644 index 000000000..86651d47c --- /dev/null +++ b/docs_src/path_params_numeric_validations/tutorial004_py310.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI, Path + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items( + *, item_id: int = Path(title="The ID of the item to get", ge=1), q: str +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/path_params_numeric_validations/tutorial005_an_py310.py b/docs_src/path_params_numeric_validations/tutorial005_an_py310.py new file mode 100644 index 000000000..571dd583c --- /dev/null +++ b/docs_src/path_params_numeric_validations/tutorial005_an_py310.py @@ -0,0 +1,16 @@ +from typing import Annotated + +from fastapi import FastAPI, Path + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items( + item_id: Annotated[int, Path(title="The ID of the item to get", gt=0, le=1000)], + q: str, +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/path_params_numeric_validations/tutorial005_py310.py b/docs_src/path_params_numeric_validations/tutorial005_py310.py new file mode 100644 index 000000000..8f12f2da0 --- /dev/null +++ b/docs_src/path_params_numeric_validations/tutorial005_py310.py @@ -0,0 +1,15 @@ +from fastapi import FastAPI, Path + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items( + *, + item_id: int = Path(title="The ID of the item to get", gt=0, le=1000), + q: str, +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results diff --git a/docs_src/path_params_numeric_validations/tutorial001_an_py39.py b/docs_src/path_params_numeric_validations/tutorial006_an_py310.py similarity index 61% rename from docs_src/path_params_numeric_validations/tutorial001_an_py39.py rename to docs_src/path_params_numeric_validations/tutorial006_an_py310.py index b36315a46..426ec3776 100644 --- a/docs_src/path_params_numeric_validations/tutorial001_an_py39.py +++ b/docs_src/path_params_numeric_validations/tutorial006_an_py310.py @@ -1,4 +1,4 @@ -from typing import Annotated, Union +from typing import Annotated from fastapi import FastAPI, Path, Query @@ -7,10 +7,14 @@ app = FastAPI() @app.get("/items/{item_id}") async def read_items( - item_id: Annotated[int, Path(title="The ID of the item to get")], - q: Annotated[Union[str, None], Query(alias="item-query")] = None, + *, + item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)], + q: str, + size: Annotated[float, Query(gt=0, lt=10.5)], ): results = {"item_id": item_id} if q: results.update({"q": q}) + if size: + results.update({"size": size}) return results diff --git a/docs_src/path_params_numeric_validations/tutorial001_py39.py b/docs_src/path_params_numeric_validations/tutorial006_py310.py similarity index 53% rename from docs_src/path_params_numeric_validations/tutorial001_py39.py rename to docs_src/path_params_numeric_validations/tutorial006_py310.py index 530147028..f07629aa0 100644 --- a/docs_src/path_params_numeric_validations/tutorial001_py39.py +++ b/docs_src/path_params_numeric_validations/tutorial006_py310.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import FastAPI, Path, Query app = FastAPI() @@ -7,10 +5,14 @@ app = FastAPI() @app.get("/items/{item_id}") async def read_items( - item_id: int = Path(title="The ID of the item to get"), - q: Union[str, None] = Query(default=None, alias="item-query"), + *, + item_id: int = Path(title="The ID of the item to get", ge=0, le=1000), + q: str, + size: float = Query(gt=0, lt=10.5), ): results = {"item_id": item_id} if q: results.update({"q": q}) + if size: + results.update({"size": size}) return results diff --git a/docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py b/docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py deleted file mode 100644 index 62a4b2c21..000000000 --- a/docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import Union - -from pydantic.v1 import BaseModel - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - size: float diff --git a/docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py b/docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py deleted file mode 100644 index 3c6a06080..000000000 --- a/docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic.v1 import BaseModel - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - size: float - - -app = FastAPI() - - -@app.post("/items/") -async def create_item(item: Item) -> Item: - return item diff --git a/docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py b/docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py deleted file mode 100644 index 117d6f7a4..000000000 --- a/docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel as BaseModelV2 -from pydantic.v1 import BaseModel - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - size: float - - -class ItemV2(BaseModelV2): - name: str - description: Union[str, None] = None - size: float - - -app = FastAPI() - - -@app.post("/items/", response_model=ItemV2) -async def create_item(item: Item): - return item diff --git a/docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py b/docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py deleted file mode 100644 index 150ab20ae..000000000 --- a/docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI -from fastapi.temp_pydantic_v1_params import Body -from pydantic.v1 import BaseModel - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - size: float - - -app = FastAPI() - - -@app.post("/items/") -async def create_item(item: Annotated[Item, Body(embed=True)]) -> Item: - return item diff --git a/docs_src/python_types/tutorial001_py310.py b/docs_src/python_types/tutorial001_py310.py new file mode 100644 index 000000000..09039435f --- /dev/null +++ b/docs_src/python_types/tutorial001_py310.py @@ -0,0 +1,6 @@ +def get_full_name(first_name, last_name): + full_name = first_name.title() + " " + last_name.title() + return full_name + + +print(get_full_name("john", "doe")) diff --git a/docs_src/python_types/tutorial002_py310.py b/docs_src/python_types/tutorial002_py310.py new file mode 100644 index 000000000..c0857a116 --- /dev/null +++ b/docs_src/python_types/tutorial002_py310.py @@ -0,0 +1,6 @@ +def get_full_name(first_name: str, last_name: str): + full_name = first_name.title() + " " + last_name.title() + return full_name + + +print(get_full_name("john", "doe")) diff --git a/docs_src/python_types/tutorial003_py310.py b/docs_src/python_types/tutorial003_py310.py new file mode 100644 index 000000000..d021d8211 --- /dev/null +++ b/docs_src/python_types/tutorial003_py310.py @@ -0,0 +1,3 @@ +def get_name_with_age(name: str, age: int): + name_with_age = name + " is this old: " + age + return name_with_age diff --git a/docs_src/python_types/tutorial004_py310.py b/docs_src/python_types/tutorial004_py310.py new file mode 100644 index 000000000..9400269e2 --- /dev/null +++ b/docs_src/python_types/tutorial004_py310.py @@ -0,0 +1,3 @@ +def get_name_with_age(name: str, age: int): + name_with_age = name + " is this old: " + str(age) + return name_with_age diff --git a/docs_src/python_types/tutorial005_py310.py b/docs_src/python_types/tutorial005_py310.py new file mode 100644 index 000000000..6c8edb0ec --- /dev/null +++ b/docs_src/python_types/tutorial005_py310.py @@ -0,0 +1,2 @@ +def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes): + return item_a, item_b, item_c, item_d, item_e diff --git a/docs_src/python_types/tutorial006_py310.py b/docs_src/python_types/tutorial006_py310.py new file mode 100644 index 000000000..486b67caf --- /dev/null +++ b/docs_src/python_types/tutorial006_py310.py @@ -0,0 +1,3 @@ +def process_items(items: list[str]): + for item in items: + print(item) diff --git a/docs_src/python_types/tutorial007_py310.py b/docs_src/python_types/tutorial007_py310.py new file mode 100644 index 000000000..ea96c7964 --- /dev/null +++ b/docs_src/python_types/tutorial007_py310.py @@ -0,0 +1,2 @@ +def process_items(items_t: tuple[int, int, str], items_s: set[bytes]): + return items_t, items_s diff --git a/docs_src/python_types/tutorial008_py310.py b/docs_src/python_types/tutorial008_py310.py new file mode 100644 index 000000000..a393385b0 --- /dev/null +++ b/docs_src/python_types/tutorial008_py310.py @@ -0,0 +1,4 @@ +def process_items(prices: dict[str, float]): + for item_name, item_price in prices.items(): + print(item_name) + print(item_price) diff --git a/docs_src/python_types/tutorial010_py310.py b/docs_src/python_types/tutorial010_py310.py new file mode 100644 index 000000000..468cffc2d --- /dev/null +++ b/docs_src/python_types/tutorial010_py310.py @@ -0,0 +1,7 @@ +class Person: + def __init__(self, name: str): + self.name = name + + +def get_person_name(one_person: Person): + return one_person.name diff --git a/docs_src/python_types/tutorial011_py39.py b/docs_src/python_types/tutorial011_py39.py deleted file mode 100644 index 4eb40b405..000000000 --- a/docs_src/python_types/tutorial011_py39.py +++ /dev/null @@ -1,23 +0,0 @@ -from datetime import datetime -from typing import Union - -from pydantic import BaseModel - - -class User(BaseModel): - id: int - name: str = "John Doe" - signup_ts: Union[datetime, None] = None - friends: list[int] = [] - - -external_data = { - "id": "123", - "signup_ts": "2017-06-01 12:22", - "friends": [1, "2", b"3"], -} -user = User(**external_data) -print(user) -# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3] -print(user.id) -# > 123 diff --git a/docs_src/python_types/tutorial012_py39.py b/docs_src/python_types/tutorial012_py39.py deleted file mode 100644 index 74fa94c43..000000000 --- a/docs_src/python_types/tutorial012_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel - - -class User(BaseModel): - name: str - age: Optional[int] diff --git a/docs_src/python_types/tutorial013_py310.py b/docs_src/python_types/tutorial013_py310.py new file mode 100644 index 000000000..65a0eaa93 --- /dev/null +++ b/docs_src/python_types/tutorial013_py310.py @@ -0,0 +1,5 @@ +from typing import Annotated + + +def say_hello(name: Annotated[str, "this is just metadata"]) -> str: + return f"Hello {name}" diff --git a/docs_src/query_param_models/tutorial001_an_py39.py b/docs_src/query_param_models/tutorial001_an_py39.py deleted file mode 100644 index 71427acae..000000000 --- a/docs_src/query_param_models/tutorial001_an_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Annotated, Literal - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field - -app = FastAPI() - - -class FilterParams(BaseModel): - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: list[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: Annotated[FilterParams, Query()]): - return filter_query diff --git a/docs_src/query_param_models/tutorial001_py39.py b/docs_src/query_param_models/tutorial001_py39.py deleted file mode 100644 index 3ebf9f4d7..000000000 --- a/docs_src/query_param_models/tutorial001_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Literal - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field - -app = FastAPI() - - -class FilterParams(BaseModel): - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: list[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: FilterParams = Query()): - return filter_query diff --git a/docs_src/query_param_models/tutorial002_an_py39.py b/docs_src/query_param_models/tutorial002_an_py39.py deleted file mode 100644 index 975956502..000000000 --- a/docs_src/query_param_models/tutorial002_an_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Annotated, Literal - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field - -app = FastAPI() - - -class FilterParams(BaseModel): - model_config = {"extra": "forbid"} - - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: list[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: Annotated[FilterParams, Query()]): - return filter_query diff --git a/docs_src/query_param_models/tutorial002_py39.py b/docs_src/query_param_models/tutorial002_py39.py deleted file mode 100644 index 6ec418499..000000000 --- a/docs_src/query_param_models/tutorial002_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Literal - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field - -app = FastAPI() - - -class FilterParams(BaseModel): - model_config = {"extra": "forbid"} - - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: list[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: FilterParams = Query()): - return filter_query diff --git a/docs_src/query_params/tutorial001_py310.py b/docs_src/query_params/tutorial001_py310.py new file mode 100644 index 000000000..74e1a1760 --- /dev/null +++ b/docs_src/query_params/tutorial001_py310.py @@ -0,0 +1,10 @@ +from fastapi import FastAPI + +app = FastAPI() + +fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] + + +@app.get("/items/") +async def read_item(skip: int = 0, limit: int = 10): + return fake_items_db[skip : skip + limit] diff --git a/docs_src/query_params/tutorial002_py39.py b/docs_src/query_params/tutorial002_py39.py deleted file mode 100644 index 8465f45ee..000000000 --- a/docs_src/query_params/tutorial002_py39.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_item(item_id: str, q: Union[str, None] = None): - if q: - return {"item_id": item_id, "q": q} - return {"item_id": item_id} diff --git a/docs_src/query_params/tutorial003_py39.py b/docs_src/query_params/tutorial003_py39.py deleted file mode 100644 index 3362715b3..000000000 --- a/docs_src/query_params/tutorial003_py39.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_item(item_id: str, q: Union[str, None] = None, short: bool = False): - item = {"item_id": item_id} - if q: - item.update({"q": q}) - if not short: - item.update( - {"description": "This is an amazing item that has a long description"} - ) - return item diff --git a/docs_src/query_params/tutorial004_py39.py b/docs_src/query_params/tutorial004_py39.py deleted file mode 100644 index 049c3ae93..000000000 --- a/docs_src/query_params/tutorial004_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/users/{user_id}/items/{item_id}") -async def read_user_item( - user_id: int, item_id: str, q: Union[str, None] = None, short: bool = False -): - item = {"item_id": item_id, "owner_id": user_id} - if q: - item.update({"q": q}) - if not short: - item.update( - {"description": "This is an amazing item that has a long description"} - ) - return item diff --git a/docs_src/query_params/tutorial005_py310.py b/docs_src/query_params/tutorial005_py310.py new file mode 100644 index 000000000..e16a40574 --- /dev/null +++ b/docs_src/query_params/tutorial005_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_user_item(item_id: str, needy: str): + item = {"item_id": item_id, "needy": needy} + return item diff --git a/docs_src/query_params/tutorial006_py39.py b/docs_src/query_params/tutorial006_py39.py deleted file mode 100644 index f0dbfe08f..000000000 --- a/docs_src/query_params/tutorial006_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_user_item( - item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None -): - item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} - return item diff --git a/docs_src/query_params_str_validations/tutorial001_py39.py b/docs_src/query_params_str_validations/tutorial001_py39.py deleted file mode 100644 index e38326b18..000000000 --- a/docs_src/query_params_str_validations/tutorial001_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Union[str, None] = None): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial002_an_py39.py b/docs_src/query_params_str_validations/tutorial002_an_py39.py deleted file mode 100644 index 2d8fc9798..000000000 --- a/docs_src/query_params_str_validations/tutorial002_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(max_length=50)] = None): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial003_an_py39.py b/docs_src/query_params_str_validations/tutorial003_an_py39.py deleted file mode 100644 index 3d6697793..000000000 --- a/docs_src/query_params_str_validations/tutorial003_an_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[Union[str, None], Query(min_length=3, max_length=50)] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial003_py39.py b/docs_src/query_params_str_validations/tutorial003_py39.py deleted file mode 100644 index 7d4917373..000000000 --- a/docs_src/query_params_str_validations/tutorial003_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Union[str, None] = Query(default=None, min_length=3, max_length=50), -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial004_an_py39.py b/docs_src/query_params_str_validations/tutorial004_an_py39.py deleted file mode 100644 index de27097b3..000000000 --- a/docs_src/query_params_str_validations/tutorial004_an_py39.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[ - Union[str, None], Query(min_length=3, max_length=50, pattern="^fixedquery$") - ] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial004_py39.py b/docs_src/query_params_str_validations/tutorial004_py39.py deleted file mode 100644 index 64a647a16..000000000 --- a/docs_src/query_params_str_validations/tutorial004_py39.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Union[str, None] = Query( - default=None, min_length=3, max_length=50, pattern="^fixedquery$" - ), -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial004_regex_an_py310.py b/docs_src/query_params_str_validations/tutorial004_regex_an_py310.py deleted file mode 100644 index 21e0d3eb8..000000000 --- a/docs_src/query_params_str_validations/tutorial004_regex_an_py310.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[ - str | None, Query(min_length=3, max_length=50, regex="^fixedquery$") - ] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial002_py39.py b/docs_src/query_params_str_validations/tutorial005_an_py310.py similarity index 65% rename from docs_src/query_params_str_validations/tutorial002_py39.py rename to docs_src/query_params_str_validations/tutorial005_an_py310.py index 17e017b7e..b1f6046b5 100644 --- a/docs_src/query_params_str_validations/tutorial002_py39.py +++ b/docs_src/query_params_str_validations/tutorial005_an_py310.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Annotated from fastapi import FastAPI, Query @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Union[str, None] = Query(default=None, max_length=50)): +async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial009_py39.py b/docs_src/query_params_str_validations/tutorial005_py310.py similarity index 64% rename from docs_src/query_params_str_validations/tutorial009_py39.py rename to docs_src/query_params_str_validations/tutorial005_py310.py index 8a6bfe2d9..8ab42869e 100644 --- a/docs_src/query_params_str_validations/tutorial009_py39.py +++ b/docs_src/query_params_str_validations/tutorial005_py310.py @@ -1,12 +1,10 @@ -from typing import Union - from fastapi import FastAPI, Query app = FastAPI() @app.get("/items/") -async def read_items(q: Union[str, None] = Query(default=None, alias="item-query")): +async def read_items(q: str = Query(default="fixedquery", min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_py39.py b/docs_src/query_params_str_validations/tutorial006_an_py310.py similarity index 69% rename from docs_src/query_params_str_validations/tutorial006c_py39.py rename to docs_src/query_params_str_validations/tutorial006_an_py310.py index 0a0e820da..3b4a676d2 100644 --- a/docs_src/query_params_str_validations/tutorial006c_py39.py +++ b/docs_src/query_params_str_validations/tutorial006_an_py310.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Annotated from fastapi import FastAPI, Query @@ -6,7 +6,7 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Union[str, None] = Query(min_length=3)): +async def read_items(q: Annotated[str, Query(min_length=3)]): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial006c_an_py39.py b/docs_src/query_params_str_validations/tutorial006_py310.py similarity index 64% rename from docs_src/query_params_str_validations/tutorial006c_an_py39.py rename to docs_src/query_params_str_validations/tutorial006_py310.py index 76a1cd49a..9a90eb64e 100644 --- a/docs_src/query_params_str_validations/tutorial006c_an_py39.py +++ b/docs_src/query_params_str_validations/tutorial006_py310.py @@ -1,12 +1,10 @@ -from typing import Annotated, Union - from fastapi import FastAPI, Query app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): +async def read_items(q: str = Query(min_length=3)): results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} if q: results.update({"q": q}) diff --git a/docs_src/query_params_str_validations/tutorial007_an_py39.py b/docs_src/query_params_str_validations/tutorial007_an_py39.py deleted file mode 100644 index 8d7a82c46..000000000 --- a/docs_src/query_params_str_validations/tutorial007_an_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[Union[str, None], Query(title="Query string", min_length=3)] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial007_py39.py b/docs_src/query_params_str_validations/tutorial007_py39.py deleted file mode 100644 index 27b649e14..000000000 --- a/docs_src/query_params_str_validations/tutorial007_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Union[str, None] = Query(default=None, title="Query string", min_length=3), -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial008_an_py39.py b/docs_src/query_params_str_validations/tutorial008_an_py39.py deleted file mode 100644 index f3f2f2c0e..000000000 --- a/docs_src/query_params_str_validations/tutorial008_an_py39.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[ - Union[str, None], - Query( - title="Query string", - description="Query string for the items to search in the database that have a good match", - min_length=3, - ), - ] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial008_py39.py b/docs_src/query_params_str_validations/tutorial008_py39.py deleted file mode 100644 index e3e0b50aa..000000000 --- a/docs_src/query_params_str_validations/tutorial008_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Union[str, None] = Query( - default=None, - title="Query string", - description="Query string for the items to search in the database that have a good match", - min_length=3, - ), -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial009_an_py39.py b/docs_src/query_params_str_validations/tutorial009_an_py39.py deleted file mode 100644 index 70a89e613..000000000 --- a/docs_src/query_params_str_validations/tutorial009_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(alias="item-query")] = None): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial010_an_py39.py b/docs_src/query_params_str_validations/tutorial010_an_py39.py deleted file mode 100644 index b126c116f..000000000 --- a/docs_src/query_params_str_validations/tutorial010_an_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[ - Union[str, None], - Query( - alias="item-query", - title="Query string", - description="Query string for the items to search in the database that have a good match", - min_length=3, - max_length=50, - pattern="^fixedquery$", - deprecated=True, - ), - ] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial010_py39.py b/docs_src/query_params_str_validations/tutorial010_py39.py deleted file mode 100644 index ff29176fe..000000000 --- a/docs_src/query_params_str_validations/tutorial010_py39.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Union[str, None] = Query( - default=None, - alias="item-query", - title="Query string", - description="Query string for the items to search in the database that have a good match", - min_length=3, - max_length=50, - pattern="^fixedquery$", - deprecated=True, - ), -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial011_an_py39.py b/docs_src/query_params_str_validations/tutorial012_an_py310.py similarity index 52% rename from docs_src/query_params_str_validations/tutorial011_an_py39.py rename to docs_src/query_params_str_validations/tutorial012_an_py310.py index 416e3990d..9b5a9c2fb 100644 --- a/docs_src/query_params_str_validations/tutorial011_an_py39.py +++ b/docs_src/query_params_str_validations/tutorial012_an_py310.py @@ -1,4 +1,4 @@ -from typing import Annotated, Union +from typing import Annotated from fastapi import FastAPI, Query @@ -6,6 +6,6 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Annotated[Union[list[str], None], Query()] = None): +async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]): query_items = {"q": q} return query_items diff --git a/docs_src/query_params_str_validations/tutorial012_py310.py b/docs_src/query_params_str_validations/tutorial012_py310.py new file mode 100644 index 000000000..070d0b04b --- /dev/null +++ b/docs_src/query_params_str_validations/tutorial012_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: list[str] = Query(default=["foo", "bar"])): + query_items = {"q": q} + return query_items diff --git a/docs_src/query_params_str_validations/tutorial011_py39.py b/docs_src/query_params_str_validations/tutorial013_an_py310.py similarity index 56% rename from docs_src/query_params_str_validations/tutorial011_py39.py rename to docs_src/query_params_str_validations/tutorial013_an_py310.py index 878f95c79..602734145 100644 --- a/docs_src/query_params_str_validations/tutorial011_py39.py +++ b/docs_src/query_params_str_validations/tutorial013_an_py310.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Annotated from fastapi import FastAPI, Query @@ -6,6 +6,6 @@ app = FastAPI() @app.get("/items/") -async def read_items(q: Union[list[str], None] = Query(default=None)): +async def read_items(q: Annotated[list, Query()] = []): query_items = {"q": q} return query_items diff --git a/docs_src/query_params_str_validations/tutorial013_py310.py b/docs_src/query_params_str_validations/tutorial013_py310.py new file mode 100644 index 000000000..0b0f44869 --- /dev/null +++ b/docs_src/query_params_str_validations/tutorial013_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: list = Query(default=[])): + query_items = {"q": q} + return query_items diff --git a/docs_src/query_params_str_validations/tutorial014_an_py39.py b/docs_src/query_params_str_validations/tutorial014_an_py39.py deleted file mode 100644 index aaf7703a5..000000000 --- a/docs_src/query_params_str_validations/tutorial014_an_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - hidden_query: Annotated[Union[str, None], Query(include_in_schema=False)] = None, -): - if hidden_query: - return {"hidden_query": hidden_query} - else: - return {"hidden_query": "Not found"} diff --git a/docs_src/query_params_str_validations/tutorial014_py39.py b/docs_src/query_params_str_validations/tutorial014_py39.py deleted file mode 100644 index 779db1c80..000000000 --- a/docs_src/query_params_str_validations/tutorial014_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - hidden_query: Union[str, None] = Query(default=None, include_in_schema=False), -): - if hidden_query: - return {"hidden_query": hidden_query} - else: - return {"hidden_query": "Not found"} diff --git a/docs_src/query_params_str_validations/tutorial015_an_py39.py b/docs_src/query_params_str_validations/tutorial015_an_py39.py deleted file mode 100644 index 989b6d2c2..000000000 --- a/docs_src/query_params_str_validations/tutorial015_an_py39.py +++ /dev/null @@ -1,30 +0,0 @@ -import random -from typing import Annotated, Union - -from fastapi import FastAPI -from pydantic import AfterValidator - -app = FastAPI() - -data = { - "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy", - "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy", - "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2", -} - - -def check_valid_id(id: str): - if not id.startswith(("isbn-", "imdb-")): - raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"') - return id - - -@app.get("/items/") -async def read_items( - id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None, -): - if id: - item = data.get(id) - else: - id, item = random.choice(list(data.items())) - return {"id": id, "name": item} diff --git a/docs_src/request_files/tutorial001_02_an_py39.py b/docs_src/request_files/tutorial001_02_an_py39.py deleted file mode 100644 index bb090ff6c..000000000 --- a/docs_src/request_files/tutorial001_02_an_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, File, UploadFile - -app = FastAPI() - - -@app.post("/files/") -async def create_file(file: Annotated[Union[bytes, None], File()] = None): - if not file: - return {"message": "No file sent"} - else: - return {"file_size": len(file)} - - -@app.post("/uploadfile/") -async def create_upload_file(file: Union[UploadFile, None] = None): - if not file: - return {"message": "No upload file sent"} - else: - return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001_02_py39.py b/docs_src/request_files/tutorial001_02_py39.py deleted file mode 100644 index ac30be2d3..000000000 --- a/docs_src/request_files/tutorial001_02_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, File, UploadFile - -app = FastAPI() - - -@app.post("/files/") -async def create_file(file: Union[bytes, None] = File(default=None)): - if not file: - return {"message": "No file sent"} - else: - return {"file_size": len(file)} - - -@app.post("/uploadfile/") -async def create_upload_file(file: Union[UploadFile, None] = None): - if not file: - return {"message": "No upload file sent"} - else: - return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001_03_an_py310.py b/docs_src/request_files/tutorial001_03_an_py310.py new file mode 100644 index 000000000..93098a677 --- /dev/null +++ b/docs_src/request_files/tutorial001_03_an_py310.py @@ -0,0 +1,17 @@ +from typing import Annotated + +from fastapi import FastAPI, File, UploadFile + +app = FastAPI() + + +@app.post("/files/") +async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]): + return {"file_size": len(file)} + + +@app.post("/uploadfile/") +async def create_upload_file( + file: Annotated[UploadFile, File(description="A file read as UploadFile")], +): + return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001_03_py310.py b/docs_src/request_files/tutorial001_03_py310.py new file mode 100644 index 000000000..d8005cc7d --- /dev/null +++ b/docs_src/request_files/tutorial001_03_py310.py @@ -0,0 +1,15 @@ +from fastapi import FastAPI, File, UploadFile + +app = FastAPI() + + +@app.post("/files/") +async def create_file(file: bytes = File(description="A file read as bytes")): + return {"file_size": len(file)} + + +@app.post("/uploadfile/") +async def create_upload_file( + file: UploadFile = File(description="A file read as UploadFile"), +): + return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001_an_py310.py b/docs_src/request_files/tutorial001_an_py310.py new file mode 100644 index 000000000..26a767221 --- /dev/null +++ b/docs_src/request_files/tutorial001_an_py310.py @@ -0,0 +1,15 @@ +from typing import Annotated + +from fastapi import FastAPI, File, UploadFile + +app = FastAPI() + + +@app.post("/files/") +async def create_file(file: Annotated[bytes, File()]): + return {"file_size": len(file)} + + +@app.post("/uploadfile/") +async def create_upload_file(file: UploadFile): + return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001_py310.py b/docs_src/request_files/tutorial001_py310.py new file mode 100644 index 000000000..2e0ea6391 --- /dev/null +++ b/docs_src/request_files/tutorial001_py310.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI, File, UploadFile + +app = FastAPI() + + +@app.post("/files/") +async def create_file(file: bytes = File()): + return {"file_size": len(file)} + + +@app.post("/uploadfile/") +async def create_upload_file(file: UploadFile): + return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial002_an_py310.py b/docs_src/request_files/tutorial002_an_py310.py new file mode 100644 index 000000000..db524ceab --- /dev/null +++ b/docs_src/request_files/tutorial002_an_py310.py @@ -0,0 +1,33 @@ +from typing import Annotated + +from fastapi import FastAPI, File, UploadFile +from fastapi.responses import HTMLResponse + +app = FastAPI() + + +@app.post("/files/") +async def create_files(files: Annotated[list[bytes], File()]): + return {"file_sizes": [len(file) for file in files]} + + +@app.post("/uploadfiles/") +async def create_upload_files(files: list[UploadFile]): + return {"filenames": [file.filename for file in files]} + + +@app.get("/") +async def main(): + content = """ + +
+ + +
+
+ + +
+ + """ + return HTMLResponse(content=content) diff --git a/docs_src/request_files/tutorial002_py310.py b/docs_src/request_files/tutorial002_py310.py new file mode 100644 index 000000000..b64cf5598 --- /dev/null +++ b/docs_src/request_files/tutorial002_py310.py @@ -0,0 +1,31 @@ +from fastapi import FastAPI, File, UploadFile +from fastapi.responses import HTMLResponse + +app = FastAPI() + + +@app.post("/files/") +async def create_files(files: list[bytes] = File()): + return {"file_sizes": [len(file) for file in files]} + + +@app.post("/uploadfiles/") +async def create_upload_files(files: list[UploadFile]): + return {"filenames": [file.filename for file in files]} + + +@app.get("/") +async def main(): + content = """ + +
+ + +
+
+ + +
+ + """ + return HTMLResponse(content=content) diff --git a/docs_src/request_files/tutorial003_an_py310.py b/docs_src/request_files/tutorial003_an_py310.py new file mode 100644 index 000000000..5a8c5dab5 --- /dev/null +++ b/docs_src/request_files/tutorial003_an_py310.py @@ -0,0 +1,39 @@ +from typing import Annotated + +from fastapi import FastAPI, File, UploadFile +from fastapi.responses import HTMLResponse + +app = FastAPI() + + +@app.post("/files/") +async def create_files( + files: Annotated[list[bytes], File(description="Multiple files as bytes")], +): + return {"file_sizes": [len(file) for file in files]} + + +@app.post("/uploadfiles/") +async def create_upload_files( + files: Annotated[ + list[UploadFile], File(description="Multiple files as UploadFile") + ], +): + return {"filenames": [file.filename for file in files]} + + +@app.get("/") +async def main(): + content = """ + +
+ + +
+
+ + +
+ + """ + return HTMLResponse(content=content) diff --git a/docs_src/request_files/tutorial003_py310.py b/docs_src/request_files/tutorial003_py310.py new file mode 100644 index 000000000..96f5e8742 --- /dev/null +++ b/docs_src/request_files/tutorial003_py310.py @@ -0,0 +1,35 @@ +from fastapi import FastAPI, File, UploadFile +from fastapi.responses import HTMLResponse + +app = FastAPI() + + +@app.post("/files/") +async def create_files( + files: list[bytes] = File(description="Multiple files as bytes"), +): + return {"file_sizes": [len(file) for file in files]} + + +@app.post("/uploadfiles/") +async def create_upload_files( + files: list[UploadFile] = File(description="Multiple files as UploadFile"), +): + return {"filenames": [file.filename for file in files]} + + +@app.get("/") +async def main(): + content = """ + +
+ + +
+
+ + +
+ + """ + return HTMLResponse(content=content) diff --git a/docs_src/request_form_models/tutorial001_an_py310.py b/docs_src/request_form_models/tutorial001_an_py310.py new file mode 100644 index 000000000..7cc81aae9 --- /dev/null +++ b/docs_src/request_form_models/tutorial001_an_py310.py @@ -0,0 +1,16 @@ +from typing import Annotated + +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + + +@app.post("/login/") +async def login(data: Annotated[FormData, Form()]): + return data diff --git a/docs_src/request_form_models/tutorial001_py310.py b/docs_src/request_form_models/tutorial001_py310.py new file mode 100644 index 000000000..98feff0b9 --- /dev/null +++ b/docs_src/request_form_models/tutorial001_py310.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + + +@app.post("/login/") +async def login(data: FormData = Form()): + return data diff --git a/docs_src/request_form_models/tutorial002_an_py310.py b/docs_src/request_form_models/tutorial002_an_py310.py new file mode 100644 index 000000000..3004e0852 --- /dev/null +++ b/docs_src/request_form_models/tutorial002_an_py310.py @@ -0,0 +1,17 @@ +from typing import Annotated + +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + model_config = {"extra": "forbid"} + + +@app.post("/login/") +async def login(data: Annotated[FormData, Form()]): + return data diff --git a/docs_src/request_form_models/tutorial002_py310.py b/docs_src/request_form_models/tutorial002_py310.py new file mode 100644 index 000000000..59b329e8d --- /dev/null +++ b/docs_src/request_form_models/tutorial002_py310.py @@ -0,0 +1,15 @@ +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + model_config = {"extra": "forbid"} + + +@app.post("/login/") +async def login(data: FormData = Form()): + return data diff --git a/docs_src/request_forms/tutorial001_an_py310.py b/docs_src/request_forms/tutorial001_an_py310.py new file mode 100644 index 000000000..8e9d2ea53 --- /dev/null +++ b/docs_src/request_forms/tutorial001_an_py310.py @@ -0,0 +1,10 @@ +from typing import Annotated + +from fastapi import FastAPI, Form + +app = FastAPI() + + +@app.post("/login/") +async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]): + return {"username": username} diff --git a/docs_src/request_forms/tutorial001_py310.py b/docs_src/request_forms/tutorial001_py310.py new file mode 100644 index 000000000..a53770001 --- /dev/null +++ b/docs_src/request_forms/tutorial001_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI, Form + +app = FastAPI() + + +@app.post("/login/") +async def login(username: str = Form(), password: str = Form()): + return {"username": username} diff --git a/docs_src/request_forms_and_files/tutorial001_an_py310.py b/docs_src/request_forms_and_files/tutorial001_an_py310.py new file mode 100644 index 000000000..12cc43e50 --- /dev/null +++ b/docs_src/request_forms_and_files/tutorial001_an_py310.py @@ -0,0 +1,18 @@ +from typing import Annotated + +from fastapi import FastAPI, File, Form, UploadFile + +app = FastAPI() + + +@app.post("/files/") +async def create_file( + file: Annotated[bytes, File()], + fileb: Annotated[UploadFile, File()], + token: Annotated[str, Form()], +): + return { + "file_size": len(file), + "token": token, + "fileb_content_type": fileb.content_type, + } diff --git a/docs_src/request_forms_and_files/tutorial001_py310.py b/docs_src/request_forms_and_files/tutorial001_py310.py new file mode 100644 index 000000000..7b5224ce5 --- /dev/null +++ b/docs_src/request_forms_and_files/tutorial001_py310.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI, File, Form, UploadFile + +app = FastAPI() + + +@app.post("/files/") +async def create_file( + file: bytes = File(), fileb: UploadFile = File(), token: str = Form() +): + return { + "file_size": len(file), + "token": token, + "fileb_content_type": fileb.content_type, + } diff --git a/docs_src/response_change_status_code/tutorial001_py310.py b/docs_src/response_change_status_code/tutorial001_py310.py new file mode 100644 index 000000000..197decbfb --- /dev/null +++ b/docs_src/response_change_status_code/tutorial001_py310.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI, Response, status + +app = FastAPI() + +tasks = {"foo": "Listen to the Bar Fighters"} + + +@app.put("/get-or-create-task/{task_id}", status_code=200) +def get_or_create_task(task_id: str, response: Response): + if task_id not in tasks: + tasks[task_id] = "This didn't exist before" + response.status_code = status.HTTP_201_CREATED + return tasks[task_id] diff --git a/docs_src/response_cookies/tutorial001_py310.py b/docs_src/response_cookies/tutorial001_py310.py new file mode 100644 index 000000000..33f8e8f6e --- /dev/null +++ b/docs_src/response_cookies/tutorial001_py310.py @@ -0,0 +1,12 @@ +from fastapi import FastAPI +from fastapi.responses import JSONResponse + +app = FastAPI() + + +@app.post("/cookie/") +def create_cookie(): + content = {"message": "Come to the dark side, we have cookies"} + response = JSONResponse(content=content) + response.set_cookie(key="fakesession", value="fake-cookie-session-value") + return response diff --git a/docs_src/response_cookies/tutorial002_py310.py b/docs_src/response_cookies/tutorial002_py310.py new file mode 100644 index 000000000..76c06fdb9 --- /dev/null +++ b/docs_src/response_cookies/tutorial002_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI, Response + +app = FastAPI() + + +@app.post("/cookie-and-object/") +def create_cookie(response: Response): + response.set_cookie(key="fakesession", value="fake-cookie-session-value") + return {"message": "Come to the dark side, we have cookies"} diff --git a/docs_src/response_directly/tutorial001_py39.py b/docs_src/response_directly/tutorial001_py39.py deleted file mode 100644 index 5ab655a8a..000000000 --- a/docs_src/response_directly/tutorial001_py39.py +++ /dev/null @@ -1,22 +0,0 @@ -from datetime import datetime -from typing import Union - -from fastapi import FastAPI -from fastapi.encoders import jsonable_encoder -from fastapi.responses import JSONResponse -from pydantic import BaseModel - - -class Item(BaseModel): - title: str - timestamp: datetime - description: Union[str, None] = None - - -app = FastAPI() - - -@app.put("/items/{id}") -def update_item(id: str, item: Item): - json_compatible_item_data = jsonable_encoder(item) - return JSONResponse(content=json_compatible_item_data) diff --git a/docs_src/response_directly/tutorial002_py310.py b/docs_src/response_directly/tutorial002_py310.py new file mode 100644 index 000000000..6643da6e6 --- /dev/null +++ b/docs_src/response_directly/tutorial002_py310.py @@ -0,0 +1,18 @@ +from fastapi import FastAPI, Response + +app = FastAPI() + + +@app.get("/legacy/") +def get_legacy_data(): + data = """ + +
+ Apply shampoo here. +
+ + You'll have to use soap here. + +
+ """ + return Response(content=data, media_type="application/xml") diff --git a/docs_src/response_headers/tutorial001_py310.py b/docs_src/response_headers/tutorial001_py310.py new file mode 100644 index 000000000..2da02a470 --- /dev/null +++ b/docs_src/response_headers/tutorial001_py310.py @@ -0,0 +1,11 @@ +from fastapi import FastAPI +from fastapi.responses import JSONResponse + +app = FastAPI() + + +@app.get("/headers/") +def get_headers(): + content = {"message": "Hello World"} + headers = {"X-Cat-Dog": "alone in the world", "Content-Language": "en-US"} + return JSONResponse(content=content, headers=headers) diff --git a/docs_src/response_headers/tutorial002_py310.py b/docs_src/response_headers/tutorial002_py310.py new file mode 100644 index 000000000..d2c498305 --- /dev/null +++ b/docs_src/response_headers/tutorial002_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI, Response + +app = FastAPI() + + +@app.get("/headers-and-object/") +def get_headers(response: Response): + response.headers["X-Cat-Dog"] = "alone in the world" + return {"message": "Hello World"} diff --git a/docs_src/response_model/tutorial001_01_py39.py b/docs_src/response_model/tutorial001_01_py39.py deleted file mode 100644 index 16c78aa3f..000000000 --- a/docs_src/response_model/tutorial001_01_py39.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: list[str] = [] - - -@app.post("/items/") -async def create_item(item: Item) -> Item: - return item - - -@app.get("/items/") -async def read_items() -> list[Item]: - return [ - Item(name="Portal Gun", price=42.0), - Item(name="Plumbus", price=32.0), - ] diff --git a/docs_src/response_model/tutorial001_py39.py b/docs_src/response_model/tutorial001_py39.py deleted file mode 100644 index 261e252d0..000000000 --- a/docs_src/response_model/tutorial001_py39.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Any, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: list[str] = [] - - -@app.post("/items/", response_model=Item) -async def create_item(item: Item) -> Any: - return item - - -@app.get("/items/", response_model=list[Item]) -async def read_items() -> Any: - return [ - {"name": "Portal Gun", "price": 42.0}, - {"name": "Plumbus", "price": 32.0}, - ] diff --git a/docs_src/response_model/tutorial002_py39.py b/docs_src/response_model/tutorial002_py39.py deleted file mode 100644 index a58668f9e..000000000 --- a/docs_src/response_model/tutorial002_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel, EmailStr - -app = FastAPI() - - -class UserIn(BaseModel): - username: str - password: str - email: EmailStr - full_name: Union[str, None] = None - - -# Don't do this in production! -@app.post("/user/") -async def create_user(user: UserIn) -> UserIn: - return user diff --git a/docs_src/response_model/tutorial003_01_py39.py b/docs_src/response_model/tutorial003_01_py39.py deleted file mode 100644 index 52694b551..000000000 --- a/docs_src/response_model/tutorial003_01_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel, EmailStr - -app = FastAPI() - - -class BaseUser(BaseModel): - username: str - email: EmailStr - full_name: Union[str, None] = None - - -class UserIn(BaseUser): - password: str - - -@app.post("/user/") -async def create_user(user: UserIn) -> BaseUser: - return user diff --git a/docs_src/response_model/tutorial003_02_py310.py b/docs_src/response_model/tutorial003_02_py310.py new file mode 100644 index 000000000..df6a09646 --- /dev/null +++ b/docs_src/response_model/tutorial003_02_py310.py @@ -0,0 +1,11 @@ +from fastapi import FastAPI, Response +from fastapi.responses import JSONResponse, RedirectResponse + +app = FastAPI() + + +@app.get("/portal") +async def get_portal(teleport: bool = False) -> Response: + if teleport: + return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") + return JSONResponse(content={"message": "Here's your interdimensional portal."}) diff --git a/docs_src/response_model/tutorial003_03_py310.py b/docs_src/response_model/tutorial003_03_py310.py new file mode 100644 index 000000000..0d4bd8de5 --- /dev/null +++ b/docs_src/response_model/tutorial003_03_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI +from fastapi.responses import RedirectResponse + +app = FastAPI() + + +@app.get("/teleport") +async def get_teleport() -> RedirectResponse: + return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") diff --git a/docs_src/response_model/tutorial003_04_py39.py b/docs_src/response_model/tutorial003_04_py39.py deleted file mode 100644 index b13a92692..000000000 --- a/docs_src/response_model/tutorial003_04_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Response -from fastapi.responses import RedirectResponse - -app = FastAPI() - - -@app.get("/portal") -async def get_portal(teleport: bool = False) -> Union[Response, dict]: - if teleport: - return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") - return {"message": "Here's your interdimensional portal."} diff --git a/docs_src/response_model/tutorial003_05_py39.py b/docs_src/response_model/tutorial003_05_py39.py deleted file mode 100644 index 0962061a6..000000000 --- a/docs_src/response_model/tutorial003_05_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Response -from fastapi.responses import RedirectResponse - -app = FastAPI() - - -@app.get("/portal", response_model=None) -async def get_portal(teleport: bool = False) -> Union[Response, dict]: - if teleport: - return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") - return {"message": "Here's your interdimensional portal."} diff --git a/docs_src/response_model/tutorial003_py39.py b/docs_src/response_model/tutorial003_py39.py deleted file mode 100644 index c42dbc707..000000000 --- a/docs_src/response_model/tutorial003_py39.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Any, Union - -from fastapi import FastAPI -from pydantic import BaseModel, EmailStr - -app = FastAPI() - - -class UserIn(BaseModel): - username: str - password: str - email: EmailStr - full_name: Union[str, None] = None - - -class UserOut(BaseModel): - username: str - email: EmailStr - full_name: Union[str, None] = None - - -@app.post("/user/", response_model=UserOut) -async def create_user(user: UserIn) -> Any: - return user diff --git a/docs_src/response_model/tutorial004_py39.py b/docs_src/response_model/tutorial004_py39.py deleted file mode 100644 index 9463b45ec..000000000 --- a/docs_src/response_model/tutorial004_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: float = 10.5 - tags: list[str] = [] - - -items = { - "foo": {"name": "Foo", "price": 50.2}, - "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, - "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []}, -} - - -@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True) -async def read_item(item_id: str): - return items[item_id] diff --git a/docs_src/response_model/tutorial005_py39.py b/docs_src/response_model/tutorial005_py39.py deleted file mode 100644 index 30eb9f8e3..000000000 --- a/docs_src/response_model/tutorial005_py39.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: float = 10.5 - - -items = { - "foo": {"name": "Foo", "price": 50.2}, - "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2}, - "baz": { - "name": "Baz", - "description": "There goes my baz", - "price": 50.2, - "tax": 10.5, - }, -} - - -@app.get( - "/items/{item_id}/name", - response_model=Item, - response_model_include={"name", "description"}, -) -async def read_item_name(item_id: str): - return items[item_id] - - -@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"}) -async def read_item_public_data(item_id: str): - return items[item_id] diff --git a/docs_src/response_model/tutorial006_py39.py b/docs_src/response_model/tutorial006_py39.py deleted file mode 100644 index 3ffdb512b..000000000 --- a/docs_src/response_model/tutorial006_py39.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: float = 10.5 - - -items = { - "foo": {"name": "Foo", "price": 50.2}, - "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2}, - "baz": { - "name": "Baz", - "description": "There goes my baz", - "price": 50.2, - "tax": 10.5, - }, -} - - -@app.get( - "/items/{item_id}/name", - response_model=Item, - response_model_include=["name", "description"], -) -async def read_item_name(item_id: str): - return items[item_id] - - -@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"]) -async def read_item_public_data(item_id: str): - return items[item_id] diff --git a/docs_src/response_status_code/tutorial001_py310.py b/docs_src/response_status_code/tutorial001_py310.py new file mode 100644 index 000000000..14b6d6e67 --- /dev/null +++ b/docs_src/response_status_code/tutorial001_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.post("/items/", status_code=201) +async def create_item(name: str): + return {"name": name} diff --git a/docs_src/response_status_code/tutorial002_py310.py b/docs_src/response_status_code/tutorial002_py310.py new file mode 100644 index 000000000..4fcc9829d --- /dev/null +++ b/docs_src/response_status_code/tutorial002_py310.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI, status + +app = FastAPI() + + +@app.post("/items/", status_code=status.HTTP_201_CREATED) +async def create_item(name: str): + return {"name": name} diff --git a/docs_src/schema_extra_example/tutorial001_pv1_py310.py b/docs_src/schema_extra_example/tutorial001_pv1_py310.py deleted file mode 100644 index b13b8a8c2..000000000 --- a/docs_src/schema_extra_example/tutorial001_pv1_py310.py +++ /dev/null @@ -1,29 +0,0 @@ -from fastapi import FastAPI -from pydantic.v1 import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: str | None = None - price: float - tax: float | None = None - - class Config: - schema_extra = { - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } - ] - } - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial001_pv1_py39.py b/docs_src/schema_extra_example/tutorial001_pv1_py39.py deleted file mode 100644 index 3240b35d6..000000000 --- a/docs_src/schema_extra_example/tutorial001_pv1_py39.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic.v1 import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - class Config: - schema_extra = { - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } - ] - } - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial001_py39.py b/docs_src/schema_extra_example/tutorial001_py39.py deleted file mode 100644 index 32a66db3a..000000000 --- a/docs_src/schema_extra_example/tutorial001_py39.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - model_config = { - "json_schema_extra": { - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } - ] - } - } - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial002_py39.py b/docs_src/schema_extra_example/tutorial002_py39.py deleted file mode 100644 index 70f06567c..000000000 --- a/docs_src/schema_extra_example/tutorial002_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel, Field - -app = FastAPI() - - -class Item(BaseModel): - name: str = Field(examples=["Foo"]) - description: Union[str, None] = Field(default=None, examples=["A very nice Item"]) - price: float = Field(examples=[35.4]) - tax: Union[float, None] = Field(default=None, examples=[3.2]) - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial003_an_py39.py b/docs_src/schema_extra_example/tutorial003_an_py39.py deleted file mode 100644 index 472808561..000000000 --- a/docs_src/schema_extra_example/tutorial003_an_py39.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - item_id: int, - item: Annotated[ - Item, - Body( - examples=[ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } - ], - ), - ], -): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial003_py39.py b/docs_src/schema_extra_example/tutorial003_py39.py deleted file mode 100644 index 385f3de8a..000000000 --- a/docs_src/schema_extra_example/tutorial003_py39.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - item_id: int, - item: Item = Body( - examples=[ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } - ], - ), -): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial004_an_py39.py b/docs_src/schema_extra_example/tutorial004_an_py39.py deleted file mode 100644 index dc5a8fe49..000000000 --- a/docs_src/schema_extra_example/tutorial004_an_py39.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - *, - item_id: int, - item: Annotated[ - Item, - Body( - examples=[ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - { - "name": "Bar", - "price": "35.4", - }, - { - "name": "Baz", - "price": "thirty five point four", - }, - ], - ), - ], -): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial004_py39.py b/docs_src/schema_extra_example/tutorial004_py39.py deleted file mode 100644 index 75514a3e9..000000000 --- a/docs_src/schema_extra_example/tutorial004_py39.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - *, - item_id: int, - item: Item = Body( - examples=[ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - { - "name": "Bar", - "price": "35.4", - }, - { - "name": "Baz", - "price": "thirty five point four", - }, - ], - ), -): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial005_an_py39.py b/docs_src/schema_extra_example/tutorial005_an_py39.py deleted file mode 100644 index edeb1affc..000000000 --- a/docs_src/schema_extra_example/tutorial005_an_py39.py +++ /dev/null @@ -1,54 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - *, - item_id: int, - item: Annotated[ - Item, - Body( - openapi_examples={ - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": { - "name": "Bar", - "price": "35.4", - }, - }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, - }, - }, - ), - ], -): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial005_py39.py b/docs_src/schema_extra_example/tutorial005_py39.py deleted file mode 100644 index b8217c27e..000000000 --- a/docs_src/schema_extra_example/tutorial005_py39.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - *, - item_id: int, - item: Item = Body( - openapi_examples={ - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": { - "name": "Bar", - "price": "35.4", - }, - }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, - }, - }, - ), -): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/security/tutorial001_an_py310.py b/docs_src/security/tutorial001_an_py310.py new file mode 100644 index 000000000..de110402e --- /dev/null +++ b/docs_src/security/tutorial001_an_py310.py @@ -0,0 +1,13 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI +from fastapi.security import OAuth2PasswordBearer + +app = FastAPI() + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + + +@app.get("/items/") +async def read_items(token: Annotated[str, Depends(oauth2_scheme)]): + return {"token": token} diff --git a/docs_src/security/tutorial001_py310.py b/docs_src/security/tutorial001_py310.py new file mode 100644 index 000000000..224e59602 --- /dev/null +++ b/docs_src/security/tutorial001_py310.py @@ -0,0 +1,11 @@ +from fastapi import Depends, FastAPI +from fastapi.security import OAuth2PasswordBearer + +app = FastAPI() + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + + +@app.get("/items/") +async def read_items(token: str = Depends(oauth2_scheme)): + return {"token": token} diff --git a/docs_src/security/tutorial002_an_py39.py b/docs_src/security/tutorial002_an_py39.py deleted file mode 100644 index 7ff1c470b..000000000 --- a/docs_src/security/tutorial002_an_py39.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Depends, FastAPI -from fastapi.security import OAuth2PasswordBearer -from pydantic import BaseModel - -app = FastAPI() - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -def fake_decode_token(token): - return User( - username=token + "fakedecoded", email="john@example.com", full_name="John Doe" - ) - - -async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): - user = fake_decode_token(token) - return user - - -@app.get("/users/me") -async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]): - return current_user diff --git a/docs_src/security/tutorial002_py39.py b/docs_src/security/tutorial002_py39.py deleted file mode 100644 index bfd035221..000000000 --- a/docs_src/security/tutorial002_py39.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI -from fastapi.security import OAuth2PasswordBearer -from pydantic import BaseModel - -app = FastAPI() - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -def fake_decode_token(token): - return User( - username=token + "fakedecoded", email="john@example.com", full_name="John Doe" - ) - - -async def get_current_user(token: str = Depends(oauth2_scheme)): - user = fake_decode_token(token) - return user - - -@app.get("/users/me") -async def read_users_me(current_user: User = Depends(get_current_user)): - return current_user diff --git a/docs_src/security/tutorial003_an_py39.py b/docs_src/security/tutorial003_an_py39.py deleted file mode 100644 index b396210c8..000000000 --- a/docs_src/security/tutorial003_an_py39.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from pydantic import BaseModel - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "fakehashedsecret", - "disabled": False, - }, - "alice": { - "username": "alice", - "full_name": "Alice Wonderson", - "email": "alice@example.com", - "hashed_password": "fakehashedsecret2", - "disabled": True, - }, -} - -app = FastAPI() - - -def fake_hash_password(password: str): - return "fakehashed" + password - - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - hashed_password: str - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def fake_decode_token(token): - # This doesn't provide any security at all - # Check the next version - user = get_user(fake_users_db, token) - return user - - -async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): - user = fake_decode_token(token) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Not authenticated", - headers={"WWW-Authenticate": "Bearer"}, - ) - return user - - -async def get_current_active_user( - current_user: Annotated[User, Depends(get_current_user)], -): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token") -async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]): - user_dict = fake_users_db.get(form_data.username) - if not user_dict: - raise HTTPException(status_code=400, detail="Incorrect username or password") - user = UserInDB(**user_dict) - hashed_password = fake_hash_password(form_data.password) - if not hashed_password == user.hashed_password: - raise HTTPException(status_code=400, detail="Incorrect username or password") - - return {"access_token": user.username, "token_type": "bearer"} - - -@app.get("/users/me") -async def read_users_me( - current_user: Annotated[User, Depends(get_current_active_user)], -): - return current_user diff --git a/docs_src/security/tutorial003_py39.py b/docs_src/security/tutorial003_py39.py deleted file mode 100644 index ce7a71b68..000000000 --- a/docs_src/security/tutorial003_py39.py +++ /dev/null @@ -1,90 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from pydantic import BaseModel - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "fakehashedsecret", - "disabled": False, - }, - "alice": { - "username": "alice", - "full_name": "Alice Wonderson", - "email": "alice@example.com", - "hashed_password": "fakehashedsecret2", - "disabled": True, - }, -} - -app = FastAPI() - - -def fake_hash_password(password: str): - return "fakehashed" + password - - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - hashed_password: str - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def fake_decode_token(token): - # This doesn't provide any security at all - # Check the next version - user = get_user(fake_users_db, token) - return user - - -async def get_current_user(token: str = Depends(oauth2_scheme)): - user = fake_decode_token(token) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Not authenticated", - headers={"WWW-Authenticate": "Bearer"}, - ) - return user - - -async def get_current_active_user(current_user: User = Depends(get_current_user)): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token") -async def login(form_data: OAuth2PasswordRequestForm = Depends()): - user_dict = fake_users_db.get(form_data.username) - if not user_dict: - raise HTTPException(status_code=400, detail="Incorrect username or password") - user = UserInDB(**user_dict) - hashed_password = fake_hash_password(form_data.password) - if not hashed_password == user.hashed_password: - raise HTTPException(status_code=400, detail="Incorrect username or password") - - return {"access_token": user.username, "token_type": "bearer"} - - -@app.get("/users/me") -async def read_users_me(current_user: User = Depends(get_current_active_user)): - return current_user diff --git a/docs_src/security/tutorial004_an_py39.py b/docs_src/security/tutorial004_an_py39.py deleted file mode 100644 index 73b3d456d..000000000 --- a/docs_src/security/tutorial004_an_py39.py +++ /dev/null @@ -1,147 +0,0 @@ -from datetime import datetime, timedelta, timezone -from typing import Annotated, Union - -import jwt -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from jwt.exceptions import InvalidTokenError -from pwdlib import PasswordHash -from pydantic import BaseModel - -# to get a string like this run: -# openssl rand -hex 32 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", - "disabled": False, - } -} - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenData(BaseModel): - username: Union[str, None] = None - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - hashed_password: str - - -password_hash = PasswordHash.recommended() - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - -app = FastAPI() - - -def verify_password(plain_password, hashed_password): - return password_hash.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return password_hash.hash(password) - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def authenticate_user(fake_db, username: str, password: str): - user = get_user(fake_db, username) - if not user: - return False - if not verify_password(password, user.hashed_password): - return False - return user - - -def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.now(timezone.utc) + expires_delta - else: - expire = datetime.now(timezone.utc) + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username = payload.get("sub") - if username is None: - raise credentials_exception - token_data = TokenData(username=username) - except InvalidTokenError: - raise credentials_exception - user = get_user(fake_users_db, username=token_data.username) - if user is None: - raise credentials_exception - return user - - -async def get_current_active_user( - current_user: Annotated[User, Depends(get_current_user)], -): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token") -async def login_for_access_token( - form_data: Annotated[OAuth2PasswordRequestForm, Depends()], -) -> Token: - user = authenticate_user(fake_users_db, form_data.username, form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username}, expires_delta=access_token_expires - ) - return Token(access_token=access_token, token_type="bearer") - - -@app.get("/users/me/") -async def read_users_me( - current_user: Annotated[User, Depends(get_current_active_user)], -) -> User: - return current_user - - -@app.get("/users/me/items/") -async def read_own_items( - current_user: Annotated[User, Depends(get_current_active_user)], -): - return [{"item_id": "Foo", "owner": current_user.username}] diff --git a/docs_src/security/tutorial004_py39.py b/docs_src/security/tutorial004_py39.py deleted file mode 100644 index e67403d5d..000000000 --- a/docs_src/security/tutorial004_py39.py +++ /dev/null @@ -1,141 +0,0 @@ -from datetime import datetime, timedelta, timezone -from typing import Union - -import jwt -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from jwt.exceptions import InvalidTokenError -from pwdlib import PasswordHash -from pydantic import BaseModel - -# to get a string like this run: -# openssl rand -hex 32 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", - "disabled": False, - } -} - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenData(BaseModel): - username: Union[str, None] = None - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - hashed_password: str - - -password_hash = PasswordHash.recommended() - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - -app = FastAPI() - - -def verify_password(plain_password, hashed_password): - return password_hash.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return password_hash.hash(password) - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def authenticate_user(fake_db, username: str, password: str): - user = get_user(fake_db, username) - if not user: - return False - if not verify_password(password, user.hashed_password): - return False - return user - - -def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.now(timezone.utc) + expires_delta - else: - expire = datetime.now(timezone.utc) + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -async def get_current_user(token: str = Depends(oauth2_scheme)): - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username = payload.get("sub") - if username is None: - raise credentials_exception - token_data = TokenData(username=username) - except InvalidTokenError: - raise credentials_exception - user = get_user(fake_users_db, username=token_data.username) - if user is None: - raise credentials_exception - return user - - -async def get_current_active_user(current_user: User = Depends(get_current_user)): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token") -async def login_for_access_token( - form_data: OAuth2PasswordRequestForm = Depends(), -) -> Token: - user = authenticate_user(fake_users_db, form_data.username, form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username}, expires_delta=access_token_expires - ) - return Token(access_token=access_token, token_type="bearer") - - -@app.get("/users/me/") -async def read_users_me(current_user: User = Depends(get_current_active_user)) -> User: - return current_user - - -@app.get("/users/me/items/") -async def read_own_items(current_user: User = Depends(get_current_active_user)): - return [{"item_id": "Foo", "owner": current_user.username}] diff --git a/docs_src/security/tutorial005_an_py39.py b/docs_src/security/tutorial005_an_py39.py deleted file mode 100644 index 1aeba688a..000000000 --- a/docs_src/security/tutorial005_an_py39.py +++ /dev/null @@ -1,179 +0,0 @@ -from datetime import datetime, timedelta, timezone -from typing import Annotated, Union - -import jwt -from fastapi import Depends, FastAPI, HTTPException, Security, status -from fastapi.security import ( - OAuth2PasswordBearer, - OAuth2PasswordRequestForm, - SecurityScopes, -) -from jwt.exceptions import InvalidTokenError -from pwdlib import PasswordHash -from pydantic import BaseModel, ValidationError - -# to get a string like this run: -# openssl rand -hex 32 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", - "disabled": False, - }, - "alice": { - "username": "alice", - "full_name": "Alice Chains", - "email": "alicechains@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE", - "disabled": True, - }, -} - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenData(BaseModel): - username: Union[str, None] = None - scopes: list[str] = [] - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - hashed_password: str - - -password_hash = PasswordHash.recommended() - -oauth2_scheme = OAuth2PasswordBearer( - tokenUrl="token", - scopes={"me": "Read information about the current user.", "items": "Read items."}, -) - -app = FastAPI() - - -def verify_password(plain_password, hashed_password): - return password_hash.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return password_hash.hash(password) - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def authenticate_user(fake_db, username: str, password: str): - user = get_user(fake_db, username) - if not user: - return False - if not verify_password(password, user.hashed_password): - return False - return user - - -def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.now(timezone.utc) + expires_delta - else: - expire = datetime.now(timezone.utc) + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -async def get_current_user( - security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)] -): - if security_scopes.scopes: - authenticate_value = f'Bearer scope="{security_scopes.scope_str}"' - else: - authenticate_value = "Bearer" - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": authenticate_value}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username = payload.get("sub") - if username is None: - raise credentials_exception - scope: str = payload.get("scope", "") - token_scopes = scope.split(" ") - token_data = TokenData(scopes=token_scopes, username=username) - except (InvalidTokenError, ValidationError): - raise credentials_exception - user = get_user(fake_users_db, username=token_data.username) - if user is None: - raise credentials_exception - for scope in security_scopes.scopes: - if scope not in token_data.scopes: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Not enough permissions", - headers={"WWW-Authenticate": authenticate_value}, - ) - return user - - -async def get_current_active_user( - current_user: Annotated[User, Security(get_current_user, scopes=["me"])], -): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token") -async def login_for_access_token( - form_data: Annotated[OAuth2PasswordRequestForm, Depends()], -) -> Token: - user = authenticate_user(fake_users_db, form_data.username, form_data.password) - if not user: - raise HTTPException(status_code=400, detail="Incorrect username or password") - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username, "scope": " ".join(form_data.scopes)}, - expires_delta=access_token_expires, - ) - return Token(access_token=access_token, token_type="bearer") - - -@app.get("/users/me/") -async def read_users_me( - current_user: Annotated[User, Depends(get_current_active_user)], -) -> User: - return current_user - - -@app.get("/users/me/items/") -async def read_own_items( - current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])], -): - return [{"item_id": "Foo", "owner": current_user.username}] - - -@app.get("/status/") -async def read_system_status(current_user: Annotated[User, Depends(get_current_user)]): - return {"status": "ok"} diff --git a/docs_src/security/tutorial005_py39.py b/docs_src/security/tutorial005_py39.py deleted file mode 100644 index 32280aa48..000000000 --- a/docs_src/security/tutorial005_py39.py +++ /dev/null @@ -1,177 +0,0 @@ -from datetime import datetime, timedelta, timezone -from typing import Union - -import jwt -from fastapi import Depends, FastAPI, HTTPException, Security, status -from fastapi.security import ( - OAuth2PasswordBearer, - OAuth2PasswordRequestForm, - SecurityScopes, -) -from jwt.exceptions import InvalidTokenError -from pwdlib import PasswordHash -from pydantic import BaseModel, ValidationError - -# to get a string like this run: -# openssl rand -hex 32 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", - "disabled": False, - }, - "alice": { - "username": "alice", - "full_name": "Alice Chains", - "email": "alicechains@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE", - "disabled": True, - }, -} - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenData(BaseModel): - username: Union[str, None] = None - scopes: list[str] = [] - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - hashed_password: str - - -password_hash = PasswordHash.recommended() - -oauth2_scheme = OAuth2PasswordBearer( - tokenUrl="token", - scopes={"me": "Read information about the current user.", "items": "Read items."}, -) - -app = FastAPI() - - -def verify_password(plain_password, hashed_password): - return password_hash.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return password_hash.hash(password) - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def authenticate_user(fake_db, username: str, password: str): - user = get_user(fake_db, username) - if not user: - return False - if not verify_password(password, user.hashed_password): - return False - return user - - -def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.now(timezone.utc) + expires_delta - else: - expire = datetime.now(timezone.utc) + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -async def get_current_user( - security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme) -): - if security_scopes.scopes: - authenticate_value = f'Bearer scope="{security_scopes.scope_str}"' - else: - authenticate_value = "Bearer" - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": authenticate_value}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") - if username is None: - raise credentials_exception - scope: str = payload.get("scope", "") - token_scopes = scope.split(" ") - token_data = TokenData(scopes=token_scopes, username=username) - except (InvalidTokenError, ValidationError): - raise credentials_exception - user = get_user(fake_users_db, username=token_data.username) - if user is None: - raise credentials_exception - for scope in security_scopes.scopes: - if scope not in token_data.scopes: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Not enough permissions", - headers={"WWW-Authenticate": authenticate_value}, - ) - return user - - -async def get_current_active_user( - current_user: User = Security(get_current_user, scopes=["me"]), -): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token") -async def login_for_access_token( - form_data: OAuth2PasswordRequestForm = Depends(), -) -> Token: - user = authenticate_user(fake_users_db, form_data.username, form_data.password) - if not user: - raise HTTPException(status_code=400, detail="Incorrect username or password") - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username, "scope": " ".join(form_data.scopes)}, - expires_delta=access_token_expires, - ) - return Token(access_token=access_token, token_type="bearer") - - -@app.get("/users/me/") -async def read_users_me(current_user: User = Depends(get_current_active_user)) -> User: - return current_user - - -@app.get("/users/me/items/") -async def read_own_items( - current_user: User = Security(get_current_active_user, scopes=["items"]), -): - return [{"item_id": "Foo", "owner": current_user.username}] - - -@app.get("/status/") -async def read_system_status(current_user: User = Depends(get_current_user)): - return {"status": "ok"} diff --git a/docs_src/security/tutorial006_an_py310.py b/docs_src/security/tutorial006_an_py310.py new file mode 100644 index 000000000..03c696a4b --- /dev/null +++ b/docs_src/security/tutorial006_an_py310.py @@ -0,0 +1,13 @@ +from typing import Annotated + +from fastapi import Depends, FastAPI +from fastapi.security import HTTPBasic, HTTPBasicCredentials + +app = FastAPI() + +security = HTTPBasic() + + +@app.get("/users/me") +def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]): + return {"username": credentials.username, "password": credentials.password} diff --git a/docs_src/security/tutorial006_py310.py b/docs_src/security/tutorial006_py310.py new file mode 100644 index 000000000..29121ffd6 --- /dev/null +++ b/docs_src/security/tutorial006_py310.py @@ -0,0 +1,11 @@ +from fastapi import Depends, FastAPI +from fastapi.security import HTTPBasic, HTTPBasicCredentials + +app = FastAPI() + +security = HTTPBasic() + + +@app.get("/users/me") +def read_current_user(credentials: HTTPBasicCredentials = Depends(security)): + return {"username": credentials.username, "password": credentials.password} diff --git a/docs_src/security/tutorial007_an_py310.py b/docs_src/security/tutorial007_an_py310.py new file mode 100644 index 000000000..87ef98657 --- /dev/null +++ b/docs_src/security/tutorial007_an_py310.py @@ -0,0 +1,36 @@ +import secrets +from typing import Annotated + +from fastapi import Depends, FastAPI, HTTPException, status +from fastapi.security import HTTPBasic, HTTPBasicCredentials + +app = FastAPI() + +security = HTTPBasic() + + +def get_current_username( + credentials: Annotated[HTTPBasicCredentials, Depends(security)], +): + current_username_bytes = credentials.username.encode("utf8") + correct_username_bytes = b"stanleyjobson" + is_correct_username = secrets.compare_digest( + current_username_bytes, correct_username_bytes + ) + current_password_bytes = credentials.password.encode("utf8") + correct_password_bytes = b"swordfish" + is_correct_password = secrets.compare_digest( + current_password_bytes, correct_password_bytes + ) + if not (is_correct_username and is_correct_password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Basic"}, + ) + return credentials.username + + +@app.get("/users/me") +def read_current_user(username: Annotated[str, Depends(get_current_username)]): + return {"username": username} diff --git a/docs_src/security/tutorial007_py310.py b/docs_src/security/tutorial007_py310.py new file mode 100644 index 000000000..ac816eb0c --- /dev/null +++ b/docs_src/security/tutorial007_py310.py @@ -0,0 +1,33 @@ +import secrets + +from fastapi import Depends, FastAPI, HTTPException, status +from fastapi.security import HTTPBasic, HTTPBasicCredentials + +app = FastAPI() + +security = HTTPBasic() + + +def get_current_username(credentials: HTTPBasicCredentials = Depends(security)): + current_username_bytes = credentials.username.encode("utf8") + correct_username_bytes = b"stanleyjobson" + is_correct_username = secrets.compare_digest( + current_username_bytes, correct_username_bytes + ) + current_password_bytes = credentials.password.encode("utf8") + correct_password_bytes = b"swordfish" + is_correct_password = secrets.compare_digest( + current_password_bytes, correct_password_bytes + ) + if not (is_correct_username and is_correct_password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Basic"}, + ) + return credentials.username + + +@app.get("/users/me") +def read_current_user(username: str = Depends(get_current_username)): + return {"username": username} diff --git a/docs_src/separate_openapi_schemas/tutorial001_py39.py b/docs_src/separate_openapi_schemas/tutorial001_py39.py deleted file mode 100644 index 63cffd1e3..000000000 --- a/docs_src/separate_openapi_schemas/tutorial001_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Optional - -from fastapi import FastAPI -from pydantic import BaseModel - - -class Item(BaseModel): - name: str - description: Optional[str] = None - - -app = FastAPI() - - -@app.post("/items/") -def create_item(item: Item): - return item - - -@app.get("/items/") -def read_items() -> list[Item]: - return [ - Item( - name="Portal Gun", - description="Device to travel through the multi-rick-verse", - ), - Item(name="Plumbus"), - ] diff --git a/docs_src/separate_openapi_schemas/tutorial002_py39.py b/docs_src/separate_openapi_schemas/tutorial002_py39.py deleted file mode 100644 index 50d997d92..000000000 --- a/docs_src/separate_openapi_schemas/tutorial002_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Optional - -from fastapi import FastAPI -from pydantic import BaseModel - - -class Item(BaseModel): - name: str - description: Optional[str] = None - - -app = FastAPI(separate_input_output_schemas=False) - - -@app.post("/items/") -def create_item(item: Item): - return item - - -@app.get("/items/") -def read_items() -> list[Item]: - return [ - Item( - name="Portal Gun", - description="Device to travel through the multi-rick-verse", - ), - Item(name="Plumbus"), - ] diff --git a/docs_src/settings/app01_py310/__init__.py b/docs_src/settings/app01_py310/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs_src/settings/app03_an_py39/config_pv1.py b/docs_src/settings/app01_py310/config.py similarity index 59% rename from docs_src/settings/app03_an_py39/config_pv1.py rename to docs_src/settings/app01_py310/config.py index 7ae66ef77..b31b8811d 100644 --- a/docs_src/settings/app03_an_py39/config_pv1.py +++ b/docs_src/settings/app01_py310/config.py @@ -1,4 +1,4 @@ -from pydantic.v1 import BaseSettings +from pydantic_settings import BaseSettings class Settings(BaseSettings): @@ -6,5 +6,5 @@ class Settings(BaseSettings): admin_email: str items_per_user: int = 50 - class Config: - env_file = ".env" + +settings = Settings() diff --git a/docs_src/settings/app01_py310/main.py b/docs_src/settings/app01_py310/main.py new file mode 100644 index 000000000..4a3a86ce2 --- /dev/null +++ b/docs_src/settings/app01_py310/main.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI + +from .config import settings + +app = FastAPI() + + +@app.get("/info") +async def info(): + return { + "app_name": settings.app_name, + "admin_email": settings.admin_email, + "items_per_user": settings.items_per_user, + } diff --git a/docs_src/settings/app02_an_py310/__init__.py b/docs_src/settings/app02_an_py310/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs_src/settings/app03_py39/config_pv1.py b/docs_src/settings/app02_an_py310/config.py similarity index 58% rename from docs_src/settings/app03_py39/config_pv1.py rename to docs_src/settings/app02_an_py310/config.py index 7ae66ef77..e17b5035d 100644 --- a/docs_src/settings/app03_py39/config_pv1.py +++ b/docs_src/settings/app02_an_py310/config.py @@ -1,10 +1,7 @@ -from pydantic.v1 import BaseSettings +from pydantic_settings import BaseSettings class Settings(BaseSettings): app_name: str = "Awesome API" admin_email: str items_per_user: int = 50 - - class Config: - env_file = ".env" diff --git a/docs_src/settings/app02_an_py310/main.py b/docs_src/settings/app02_an_py310/main.py new file mode 100644 index 000000000..6d5db12a8 --- /dev/null +++ b/docs_src/settings/app02_an_py310/main.py @@ -0,0 +1,22 @@ +from functools import lru_cache +from typing import Annotated + +from fastapi import Depends, FastAPI + +from .config import Settings + +app = FastAPI() + + +@lru_cache +def get_settings(): + return Settings() + + +@app.get("/info") +async def info(settings: Annotated[Settings, Depends(get_settings)]): + return { + "app_name": settings.app_name, + "admin_email": settings.admin_email, + "items_per_user": settings.items_per_user, + } diff --git a/docs_src/settings/app02_an_py310/test_main.py b/docs_src/settings/app02_an_py310/test_main.py new file mode 100644 index 000000000..7a04d7e8e --- /dev/null +++ b/docs_src/settings/app02_an_py310/test_main.py @@ -0,0 +1,23 @@ +from fastapi.testclient import TestClient + +from .config import Settings +from .main import app, get_settings + +client = TestClient(app) + + +def get_settings_override(): + return Settings(admin_email="testing_admin@example.com") + + +app.dependency_overrides[get_settings] = get_settings_override + + +def test_app(): + response = client.get("/info") + data = response.json() + assert data == { + "app_name": "Awesome API", + "admin_email": "testing_admin@example.com", + "items_per_user": 50, + } diff --git a/docs_src/settings/app02_py310/__init__.py b/docs_src/settings/app02_py310/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs_src/settings/app02_py310/config.py b/docs_src/settings/app02_py310/config.py new file mode 100644 index 000000000..e17b5035d --- /dev/null +++ b/docs_src/settings/app02_py310/config.py @@ -0,0 +1,7 @@ +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + app_name: str = "Awesome API" + admin_email: str + items_per_user: int = 50 diff --git a/docs_src/settings/app02_py310/main.py b/docs_src/settings/app02_py310/main.py new file mode 100644 index 000000000..941f82e6b --- /dev/null +++ b/docs_src/settings/app02_py310/main.py @@ -0,0 +1,21 @@ +from functools import lru_cache + +from fastapi import Depends, FastAPI + +from .config import Settings + +app = FastAPI() + + +@lru_cache +def get_settings(): + return Settings() + + +@app.get("/info") +async def info(settings: Settings = Depends(get_settings)): + return { + "app_name": settings.app_name, + "admin_email": settings.admin_email, + "items_per_user": settings.items_per_user, + } diff --git a/docs_src/settings/app02_py310/test_main.py b/docs_src/settings/app02_py310/test_main.py new file mode 100644 index 000000000..7a04d7e8e --- /dev/null +++ b/docs_src/settings/app02_py310/test_main.py @@ -0,0 +1,23 @@ +from fastapi.testclient import TestClient + +from .config import Settings +from .main import app, get_settings + +client = TestClient(app) + + +def get_settings_override(): + return Settings(admin_email="testing_admin@example.com") + + +app.dependency_overrides[get_settings] = get_settings_override + + +def test_app(): + response = client.get("/info") + data = response.json() + assert data == { + "app_name": "Awesome API", + "admin_email": "testing_admin@example.com", + "items_per_user": 50, + } diff --git a/docs_src/settings/app03_an_py310/__init__.py b/docs_src/settings/app03_an_py310/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs_src/settings/app03_an_py310/config.py b/docs_src/settings/app03_an_py310/config.py new file mode 100644 index 000000000..08f8f88c2 --- /dev/null +++ b/docs_src/settings/app03_an_py310/config.py @@ -0,0 +1,9 @@ +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + app_name: str = "Awesome API" + admin_email: str + items_per_user: int = 50 + + model_config = SettingsConfigDict(env_file=".env") diff --git a/docs_src/settings/app03_an_py310/main.py b/docs_src/settings/app03_an_py310/main.py new file mode 100644 index 000000000..2f64b9cd1 --- /dev/null +++ b/docs_src/settings/app03_an_py310/main.py @@ -0,0 +1,22 @@ +from functools import lru_cache +from typing import Annotated + +from fastapi import Depends, FastAPI + +from . import config + +app = FastAPI() + + +@lru_cache +def get_settings(): + return config.Settings() + + +@app.get("/info") +async def info(settings: Annotated[config.Settings, Depends(get_settings)]): + return { + "app_name": settings.app_name, + "admin_email": settings.admin_email, + "items_per_user": settings.items_per_user, + } diff --git a/docs_src/settings/app03_py310/__init__.py b/docs_src/settings/app03_py310/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs_src/settings/app03_py310/config.py b/docs_src/settings/app03_py310/config.py new file mode 100644 index 000000000..08f8f88c2 --- /dev/null +++ b/docs_src/settings/app03_py310/config.py @@ -0,0 +1,9 @@ +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + app_name: str = "Awesome API" + admin_email: str + items_per_user: int = 50 + + model_config = SettingsConfigDict(env_file=".env") diff --git a/docs_src/settings/app03_py310/main.py b/docs_src/settings/app03_py310/main.py new file mode 100644 index 000000000..ea64a5709 --- /dev/null +++ b/docs_src/settings/app03_py310/main.py @@ -0,0 +1,21 @@ +from functools import lru_cache + +from fastapi import Depends, FastAPI + +from . import config + +app = FastAPI() + + +@lru_cache +def get_settings(): + return config.Settings() + + +@app.get("/info") +async def info(settings: config.Settings = Depends(get_settings)): + return { + "app_name": settings.app_name, + "admin_email": settings.admin_email, + "items_per_user": settings.items_per_user, + } diff --git a/docs_src/settings/tutorial001_pv1_py39.py b/docs_src/settings/tutorial001_py310.py similarity index 89% rename from docs_src/settings/tutorial001_pv1_py39.py rename to docs_src/settings/tutorial001_py310.py index 20ad2bbf6..d48c4c060 100644 --- a/docs_src/settings/tutorial001_pv1_py39.py +++ b/docs_src/settings/tutorial001_py310.py @@ -1,5 +1,5 @@ from fastapi import FastAPI -from pydantic.v1 import BaseSettings +from pydantic_settings import BaseSettings class Settings(BaseSettings): diff --git a/docs_src/sql_databases/tutorial001_an_py39.py b/docs_src/sql_databases/tutorial001_an_py39.py deleted file mode 100644 index 595892746..000000000 --- a/docs_src/sql_databases/tutorial001_an_py39.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Depends, FastAPI, HTTPException, Query -from sqlmodel import Field, Session, SQLModel, create_engine, select - - -class Hero(SQLModel, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) - name: str = Field(index=True) - age: Union[int, None] = Field(default=None, index=True) - secret_name: str - - -sqlite_file_name = "database.db" -sqlite_url = f"sqlite:///{sqlite_file_name}" - -connect_args = {"check_same_thread": False} -engine = create_engine(sqlite_url, connect_args=connect_args) - - -def create_db_and_tables(): - SQLModel.metadata.create_all(engine) - - -def get_session(): - with Session(engine) as session: - yield session - - -SessionDep = Annotated[Session, Depends(get_session)] - -app = FastAPI() - - -@app.on_event("startup") -def on_startup(): - create_db_and_tables() - - -@app.post("/heroes/") -def create_hero(hero: Hero, session: SessionDep) -> Hero: - session.add(hero) - session.commit() - session.refresh(hero) - return hero - - -@app.get("/heroes/") -def read_heroes( - session: SessionDep, - offset: int = 0, - limit: Annotated[int, Query(le=100)] = 100, -) -> list[Hero]: - heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() - return heroes - - -@app.get("/heroes/{hero_id}") -def read_hero(hero_id: int, session: SessionDep) -> Hero: - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - return hero - - -@app.delete("/heroes/{hero_id}") -def delete_hero(hero_id: int, session: SessionDep): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - session.delete(hero) - session.commit() - return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_py39.py b/docs_src/sql_databases/tutorial001_py39.py deleted file mode 100644 index 410a52d0c..000000000 --- a/docs_src/sql_databases/tutorial001_py39.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI, HTTPException, Query -from sqlmodel import Field, Session, SQLModel, create_engine, select - - -class Hero(SQLModel, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) - name: str = Field(index=True) - age: Union[int, None] = Field(default=None, index=True) - secret_name: str - - -sqlite_file_name = "database.db" -sqlite_url = f"sqlite:///{sqlite_file_name}" - -connect_args = {"check_same_thread": False} -engine = create_engine(sqlite_url, connect_args=connect_args) - - -def create_db_and_tables(): - SQLModel.metadata.create_all(engine) - - -def get_session(): - with Session(engine) as session: - yield session - - -app = FastAPI() - - -@app.on_event("startup") -def on_startup(): - create_db_and_tables() - - -@app.post("/heroes/") -def create_hero(hero: Hero, session: Session = Depends(get_session)) -> Hero: - session.add(hero) - session.commit() - session.refresh(hero) - return hero - - -@app.get("/heroes/") -def read_heroes( - session: Session = Depends(get_session), - offset: int = 0, - limit: int = Query(default=100, le=100), -) -> list[Hero]: - heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() - return heroes - - -@app.get("/heroes/{hero_id}") -def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero: - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - return hero - - -@app.delete("/heroes/{hero_id}") -def delete_hero(hero_id: int, session: Session = Depends(get_session)): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - session.delete(hero) - session.commit() - return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_an_py39.py b/docs_src/sql_databases/tutorial002_an_py39.py deleted file mode 100644 index a8a0721ff..000000000 --- a/docs_src/sql_databases/tutorial002_an_py39.py +++ /dev/null @@ -1,103 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Depends, FastAPI, HTTPException, Query -from sqlmodel import Field, Session, SQLModel, create_engine, select - - -class HeroBase(SQLModel): - name: str = Field(index=True) - age: Union[int, None] = Field(default=None, index=True) - - -class Hero(HeroBase, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) - secret_name: str - - -class HeroPublic(HeroBase): - id: int - - -class HeroCreate(HeroBase): - secret_name: str - - -class HeroUpdate(HeroBase): - name: Union[str, None] = None - age: Union[int, None] = None - secret_name: Union[str, None] = None - - -sqlite_file_name = "database.db" -sqlite_url = f"sqlite:///{sqlite_file_name}" - -connect_args = {"check_same_thread": False} -engine = create_engine(sqlite_url, connect_args=connect_args) - - -def create_db_and_tables(): - SQLModel.metadata.create_all(engine) - - -def get_session(): - with Session(engine) as session: - yield session - - -SessionDep = Annotated[Session, Depends(get_session)] -app = FastAPI() - - -@app.on_event("startup") -def on_startup(): - create_db_and_tables() - - -@app.post("/heroes/", response_model=HeroPublic) -def create_hero(hero: HeroCreate, session: SessionDep): - db_hero = Hero.model_validate(hero) - session.add(db_hero) - session.commit() - session.refresh(db_hero) - return db_hero - - -@app.get("/heroes/", response_model=list[HeroPublic]) -def read_heroes( - session: SessionDep, - offset: int = 0, - limit: Annotated[int, Query(le=100)] = 100, -): - heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() - return heroes - - -@app.get("/heroes/{hero_id}", response_model=HeroPublic) -def read_hero(hero_id: int, session: SessionDep): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - return hero - - -@app.patch("/heroes/{hero_id}", response_model=HeroPublic) -def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep): - hero_db = session.get(Hero, hero_id) - if not hero_db: - raise HTTPException(status_code=404, detail="Hero not found") - hero_data = hero.model_dump(exclude_unset=True) - hero_db.sqlmodel_update(hero_data) - session.add(hero_db) - session.commit() - session.refresh(hero_db) - return hero_db - - -@app.delete("/heroes/{hero_id}") -def delete_hero(hero_id: int, session: SessionDep): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - session.delete(hero) - session.commit() - return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_py39.py b/docs_src/sql_databases/tutorial002_py39.py deleted file mode 100644 index d8f5dd090..000000000 --- a/docs_src/sql_databases/tutorial002_py39.py +++ /dev/null @@ -1,104 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI, HTTPException, Query -from sqlmodel import Field, Session, SQLModel, create_engine, select - - -class HeroBase(SQLModel): - name: str = Field(index=True) - age: Union[int, None] = Field(default=None, index=True) - - -class Hero(HeroBase, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) - secret_name: str - - -class HeroPublic(HeroBase): - id: int - - -class HeroCreate(HeroBase): - secret_name: str - - -class HeroUpdate(HeroBase): - name: Union[str, None] = None - age: Union[int, None] = None - secret_name: Union[str, None] = None - - -sqlite_file_name = "database.db" -sqlite_url = f"sqlite:///{sqlite_file_name}" - -connect_args = {"check_same_thread": False} -engine = create_engine(sqlite_url, connect_args=connect_args) - - -def create_db_and_tables(): - SQLModel.metadata.create_all(engine) - - -def get_session(): - with Session(engine) as session: - yield session - - -app = FastAPI() - - -@app.on_event("startup") -def on_startup(): - create_db_and_tables() - - -@app.post("/heroes/", response_model=HeroPublic) -def create_hero(hero: HeroCreate, session: Session = Depends(get_session)): - db_hero = Hero.model_validate(hero) - session.add(db_hero) - session.commit() - session.refresh(db_hero) - return db_hero - - -@app.get("/heroes/", response_model=list[HeroPublic]) -def read_heroes( - session: Session = Depends(get_session), - offset: int = 0, - limit: int = Query(default=100, le=100), -): - heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() - return heroes - - -@app.get("/heroes/{hero_id}", response_model=HeroPublic) -def read_hero(hero_id: int, session: Session = Depends(get_session)): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - return hero - - -@app.patch("/heroes/{hero_id}", response_model=HeroPublic) -def update_hero( - hero_id: int, hero: HeroUpdate, session: Session = Depends(get_session) -): - hero_db = session.get(Hero, hero_id) - if not hero_db: - raise HTTPException(status_code=404, detail="Hero not found") - hero_data = hero.model_dump(exclude_unset=True) - hero_db.sqlmodel_update(hero_data) - session.add(hero_db) - session.commit() - session.refresh(hero_db) - return hero_db - - -@app.delete("/heroes/{hero_id}") -def delete_hero(hero_id: int, session: Session = Depends(get_session)): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - session.delete(hero) - session.commit() - return {"ok": True} diff --git a/docs_src/static_files/tutorial001_py310.py b/docs_src/static_files/tutorial001_py310.py new file mode 100644 index 000000000..460352c7e --- /dev/null +++ b/docs_src/static_files/tutorial001_py310.py @@ -0,0 +1,6 @@ +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles + +app = FastAPI() + +app.mount("/static", StaticFiles(directory="static"), name="static") diff --git a/docs_src/sub_applications/tutorial001_py310.py b/docs_src/sub_applications/tutorial001_py310.py new file mode 100644 index 000000000..57e627e80 --- /dev/null +++ b/docs_src/sub_applications/tutorial001_py310.py @@ -0,0 +1,19 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/app") +def read_main(): + return {"message": "Hello World from main app"} + + +subapi = FastAPI() + + +@subapi.get("/sub") +def read_sub(): + return {"message": "Hello World from sub API"} + + +app.mount("/subapi", subapi) diff --git a/docs_src/templates/tutorial001_py310.py b/docs_src/templates/tutorial001_py310.py new file mode 100644 index 000000000..81ccc8d4d --- /dev/null +++ b/docs_src/templates/tutorial001_py310.py @@ -0,0 +1,18 @@ +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates + +app = FastAPI() + +app.mount("/static", StaticFiles(directory="static"), name="static") + + +templates = Jinja2Templates(directory="templates") + + +@app.get("/items/{id}", response_class=HTMLResponse) +async def read_item(request: Request, id: str): + return templates.TemplateResponse( + request=request, name="item.html", context={"id": id} + ) diff --git a/docs_src/using_request_directly/tutorial001_py310.py b/docs_src/using_request_directly/tutorial001_py310.py new file mode 100644 index 000000000..2d7288b54 --- /dev/null +++ b/docs_src/using_request_directly/tutorial001_py310.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI, Request + +app = FastAPI() + + +@app.get("/items/{item_id}") +def read_root(item_id: str, request: Request): + client_host = request.client.host + return {"client_host": client_host, "item_id": item_id} diff --git a/docs_src/websockets/tutorial001_py310.py b/docs_src/websockets/tutorial001_py310.py new file mode 100644 index 000000000..a43a2be17 --- /dev/null +++ b/docs_src/websockets/tutorial001_py310.py @@ -0,0 +1,51 @@ +from fastapi import FastAPI, WebSocket +from fastapi.responses import HTMLResponse + +app = FastAPI() + +html = """ + + + + Chat + + +

WebSocket Chat

+
+ + +
+
    +
+ + + +""" + + +@app.get("/") +async def get(): + return HTMLResponse(html) + + +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + await websocket.accept() + while True: + data = await websocket.receive_text() + await websocket.send_text(f"Message text was: {data}") diff --git a/docs_src/websockets/tutorial002_an_py39.py b/docs_src/websockets/tutorial002_an_py39.py deleted file mode 100644 index 606d355fe..000000000 --- a/docs_src/websockets/tutorial002_an_py39.py +++ /dev/null @@ -1,92 +0,0 @@ -from typing import Annotated, Union - -from fastapi import ( - Cookie, - Depends, - FastAPI, - Query, - WebSocket, - WebSocketException, - status, -) -from fastapi.responses import HTMLResponse - -app = FastAPI() - -html = """ - - - - Chat - - -

WebSocket Chat

-
- - - -
- - -
-
    -
- - - -""" - - -@app.get("/") -async def get(): - return HTMLResponse(html) - - -async def get_cookie_or_token( - websocket: WebSocket, - session: Annotated[Union[str, None], Cookie()] = None, - token: Annotated[Union[str, None], Query()] = None, -): - if session is None and token is None: - raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) - return session or token - - -@app.websocket("/items/{item_id}/ws") -async def websocket_endpoint( - *, - websocket: WebSocket, - item_id: str, - q: Union[int, None] = None, - cookie_or_token: Annotated[str, Depends(get_cookie_or_token)], -): - await websocket.accept() - while True: - data = await websocket.receive_text() - await websocket.send_text( - f"Session cookie or query token value is: {cookie_or_token}" - ) - if q is not None: - await websocket.send_text(f"Query parameter q is: {q}") - await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}") diff --git a/docs_src/websockets/tutorial002_py39.py b/docs_src/websockets/tutorial002_py39.py deleted file mode 100644 index cab749e4d..000000000 --- a/docs_src/websockets/tutorial002_py39.py +++ /dev/null @@ -1,91 +0,0 @@ -from typing import Union - -from fastapi import ( - Cookie, - Depends, - FastAPI, - Query, - WebSocket, - WebSocketException, - status, -) -from fastapi.responses import HTMLResponse - -app = FastAPI() - -html = """ - - - - Chat - - -

WebSocket Chat

-
- - - -
- - -
-
    -
- - - -""" - - -@app.get("/") -async def get(): - return HTMLResponse(html) - - -async def get_cookie_or_token( - websocket: WebSocket, - session: Union[str, None] = Cookie(default=None), - token: Union[str, None] = Query(default=None), -): - if session is None and token is None: - raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) - return session or token - - -@app.websocket("/items/{item_id}/ws") -async def websocket_endpoint( - websocket: WebSocket, - item_id: str, - q: Union[int, None] = None, - cookie_or_token: str = Depends(get_cookie_or_token), -): - await websocket.accept() - while True: - data = await websocket.receive_text() - await websocket.send_text( - f"Session cookie or query token value is: {cookie_or_token}" - ) - if q is not None: - await websocket.send_text(f"Query parameter q is: {q}") - await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}") diff --git a/docs_src/websockets/tutorial003_py310.py b/docs_src/websockets/tutorial003_py310.py new file mode 100644 index 000000000..316218088 --- /dev/null +++ b/docs_src/websockets/tutorial003_py310.py @@ -0,0 +1,81 @@ +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.responses import HTMLResponse + +app = FastAPI() + +html = """ + + + + Chat + + +

WebSocket Chat

+

Your ID:

+
+ + +
+
    +
+ + + +""" + + +class ConnectionManager: + def __init__(self): + self.active_connections: list[WebSocket] = [] + + async def connect(self, websocket: WebSocket): + await websocket.accept() + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_personal_message(self, message: str, websocket: WebSocket): + await websocket.send_text(message) + + async def broadcast(self, message: str): + for connection in self.active_connections: + await connection.send_text(message) + + +manager = ConnectionManager() + + +@app.get("/") +async def get(): + return HTMLResponse(html) + + +@app.websocket("/ws/{client_id}") +async def websocket_endpoint(websocket: WebSocket, client_id: int): + await manager.connect(websocket) + try: + while True: + data = await websocket.receive_text() + await manager.send_personal_message(f"You wrote: {data}", websocket) + await manager.broadcast(f"Client #{client_id} says: {data}") + except WebSocketDisconnect: + manager.disconnect(websocket) + await manager.broadcast(f"Client #{client_id} left the chat") diff --git a/docs_src/wsgi/tutorial001_py310.py b/docs_src/wsgi/tutorial001_py310.py new file mode 100644 index 000000000..8eeceb829 --- /dev/null +++ b/docs_src/wsgi/tutorial001_py310.py @@ -0,0 +1,23 @@ +from a2wsgi import WSGIMiddleware +from fastapi import FastAPI +from flask import Flask, request +from markupsafe import escape + +flask_app = Flask(__name__) + + +@flask_app.route("/") +def flask_main(): + name = request.args.get("name", "World") + return f"Hello, {escape(name)} from Flask!" + + +app = FastAPI() + + +@app.get("/v2") +def read_main(): + return {"message": "Hello World"} + + +app.mount("/v1", WSGIMiddleware(flask_app)) diff --git a/pyproject.toml b/pyproject.toml index e1ce30507..1e6fda3b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -285,20 +285,205 @@ omit = [ "docs_src/dependencies/tutorial014_an_py310.py", # temporary code example? # Pydantic v1 migration, no longer tested "docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py", - "docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py", "docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py", - "docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py", "docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py", - "docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py", "docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py", - "docs_src/pydantic_v1_in_v2/tutorial004_an_py39.py", - # TODO: remove when removing this file, after updating translations, Pydantic v1 - "docs_src/schema_extra_example/tutorial001_pv1_py310.py", - "docs_src/schema_extra_example/tutorial001_pv1_py39.py", - "docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py", - "docs_src/settings/app03_py39/config_pv1.py", - "docs_src/settings/app03_an_py39/config_pv1.py", - "docs_src/settings/tutorial001_pv1_py39.py", + # TODO: remove all the ignores below when all translations use the new Python 3.10 files + "docs_src/additional_responses/tutorial001_py39.py", + "docs_src/additional_responses/tutorial003_py39.py", + "docs_src/advanced_middleware/tutorial001_py39.py", + "docs_src/advanced_middleware/tutorial002_py39.py", + "docs_src/advanced_middleware/tutorial003_py39.py", + "docs_src/app_testing/app_a_py39/main.py", + "docs_src/app_testing/app_a_py39/test_main.py", + "docs_src/app_testing/tutorial001_py39.py", + "docs_src/app_testing/tutorial002_py39.py", + "docs_src/app_testing/tutorial003_py39.py", + "docs_src/app_testing/tutorial004_py39.py", + "docs_src/async_tests/app_a_py39/main.py", + "docs_src/async_tests/app_a_py39/test_main.py", + "docs_src/authentication_error_status_code/tutorial001_an_py39.py", + "docs_src/background_tasks/tutorial001_py39.py", + "docs_src/behind_a_proxy/tutorial001_01_py39.py", + "docs_src/behind_a_proxy/tutorial001_py39.py", + "docs_src/behind_a_proxy/tutorial002_py39.py", + "docs_src/behind_a_proxy/tutorial003_py39.py", + "docs_src/behind_a_proxy/tutorial004_py39.py", + "docs_src/bigger_applications/app_an_py39/dependencies.py", + "docs_src/bigger_applications/app_an_py39/internal/admin.py", + "docs_src/bigger_applications/app_an_py39/main.py", + "docs_src/bigger_applications/app_an_py39/routers/items.py", + "docs_src/bigger_applications/app_an_py39/routers/users.py", + "docs_src/bigger_applications/app_py39/dependencies.py", + "docs_src/bigger_applications/app_py39/main.py", + "docs_src/body_nested_models/tutorial008_py39.py", + "docs_src/body_nested_models/tutorial009_py39.py", + "docs_src/conditional_openapi/tutorial001_py39.py", + "docs_src/configure_swagger_ui/tutorial001_py39.py", + "docs_src/configure_swagger_ui/tutorial002_py39.py", + "docs_src/configure_swagger_ui/tutorial003_py39.py", + "docs_src/cors/tutorial001_py39.py", + "docs_src/custom_docs_ui/tutorial001_py39.py", + "docs_src/custom_docs_ui/tutorial002_py39.py", + "docs_src/custom_response/tutorial001_py39.py", + "docs_src/custom_response/tutorial001b_py39.py", + "docs_src/custom_response/tutorial002_py39.py", + "docs_src/custom_response/tutorial003_py39.py", + "docs_src/custom_response/tutorial004_py39.py", + "docs_src/custom_response/tutorial005_py39.py", + "docs_src/custom_response/tutorial006_py39.py", + "docs_src/custom_response/tutorial006b_py39.py", + "docs_src/custom_response/tutorial006c_py39.py", + "docs_src/custom_response/tutorial007_py39.py", + "docs_src/custom_response/tutorial008_py39.py", + "docs_src/custom_response/tutorial009_py39.py", + "docs_src/custom_response/tutorial009b_py39.py", + "docs_src/custom_response/tutorial009c_py39.py", + "docs_src/custom_response/tutorial010_py39.py", + "docs_src/debugging/tutorial001_py39.py", + "docs_src/dependencies/tutorial006_an_py39.py", + "docs_src/dependencies/tutorial006_py39.py", + "docs_src/dependencies/tutorial007_py39.py", + "docs_src/dependencies/tutorial008_py39.py", + "docs_src/dependencies/tutorial008b_an_py39.py", + "docs_src/dependencies/tutorial008b_py39.py", + "docs_src/dependencies/tutorial008c_an_py39.py", + "docs_src/dependencies/tutorial008c_py39.py", + "docs_src/dependencies/tutorial008d_an_py39.py", + "docs_src/dependencies/tutorial008d_py39.py", + "docs_src/dependencies/tutorial008e_an_py39.py", + "docs_src/dependencies/tutorial008e_py39.py", + "docs_src/dependencies/tutorial010_py39.py", + "docs_src/dependencies/tutorial011_an_py39.py", + "docs_src/dependencies/tutorial011_py39.py", + "docs_src/dependencies/tutorial012_an_py39.py", + "docs_src/dependencies/tutorial012_py39.py", + "docs_src/events/tutorial001_py39.py", + "docs_src/events/tutorial002_py39.py", + "docs_src/events/tutorial003_py39.py", + "docs_src/extending_openapi/tutorial001_py39.py", + "docs_src/extra_models/tutorial004_py39.py", + "docs_src/extra_models/tutorial005_py39.py", + "docs_src/first_steps/tutorial001_py39.py", + "docs_src/first_steps/tutorial003_py39.py", + "docs_src/generate_clients/tutorial001_py39.py", + "docs_src/generate_clients/tutorial002_py39.py", + "docs_src/generate_clients/tutorial003_py39.py", + "docs_src/generate_clients/tutorial004_py39.py", + "docs_src/graphql_/tutorial001_py39.py", + "docs_src/handling_errors/tutorial001_py39.py", + "docs_src/handling_errors/tutorial002_py39.py", + "docs_src/handling_errors/tutorial003_py39.py", + "docs_src/handling_errors/tutorial004_py39.py", + "docs_src/handling_errors/tutorial005_py39.py", + "docs_src/handling_errors/tutorial006_py39.py", + "docs_src/metadata/tutorial001_1_py39.py", + "docs_src/metadata/tutorial001_py39.py", + "docs_src/metadata/tutorial002_py39.py", + "docs_src/metadata/tutorial003_py39.py", + "docs_src/metadata/tutorial004_py39.py", + "docs_src/middleware/tutorial001_py39.py", + "docs_src/openapi_webhooks/tutorial001_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial001_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial002_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial003_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial005_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial006_py39.py", + "docs_src/path_operation_advanced_configuration/tutorial007_py39.py", + "docs_src/path_operation_configuration/tutorial002b_py39.py", + "docs_src/path_operation_configuration/tutorial006_py39.py", + "docs_src/path_params/tutorial001_py39.py", + "docs_src/path_params/tutorial002_py39.py", + "docs_src/path_params/tutorial003_py39.py", + "docs_src/path_params/tutorial003b_py39.py", + "docs_src/path_params/tutorial004_py39.py", + "docs_src/path_params/tutorial005_py39.py", + "docs_src/path_params_numeric_validations/tutorial002_an_py39.py", + "docs_src/path_params_numeric_validations/tutorial002_py39.py", + "docs_src/path_params_numeric_validations/tutorial003_an_py39.py", + "docs_src/path_params_numeric_validations/tutorial003_py39.py", + "docs_src/path_params_numeric_validations/tutorial004_an_py39.py", + "docs_src/path_params_numeric_validations/tutorial004_py39.py", + "docs_src/path_params_numeric_validations/tutorial005_an_py39.py", + "docs_src/path_params_numeric_validations/tutorial005_py39.py", + "docs_src/path_params_numeric_validations/tutorial006_an_py39.py", + "docs_src/path_params_numeric_validations/tutorial006_py39.py", + "docs_src/python_types/tutorial001_py39.py", + "docs_src/python_types/tutorial002_py39.py", + "docs_src/python_types/tutorial003_py39.py", + "docs_src/python_types/tutorial004_py39.py", + "docs_src/python_types/tutorial005_py39.py", + "docs_src/python_types/tutorial006_py39.py", + "docs_src/python_types/tutorial007_py39.py", + "docs_src/python_types/tutorial008_py39.py", + "docs_src/python_types/tutorial008b_py39.py", + "docs_src/python_types/tutorial009_py39.py", + "docs_src/python_types/tutorial009b_py39.py", + "docs_src/python_types/tutorial009c_py39.py", + "docs_src/python_types/tutorial010_py39.py", + "docs_src/python_types/tutorial013_py39.py", + "docs_src/query_params/tutorial001_py39.py", + "docs_src/query_params/tutorial005_py39.py", + "docs_src/query_params_str_validations/tutorial005_an_py39.py", + "docs_src/query_params_str_validations/tutorial005_py39.py", + "docs_src/query_params_str_validations/tutorial006_an_py39.py", + "docs_src/query_params_str_validations/tutorial006_py39.py", + "docs_src/query_params_str_validations/tutorial012_an_py39.py", + "docs_src/query_params_str_validations/tutorial012_py39.py", + "docs_src/query_params_str_validations/tutorial013_an_py39.py", + "docs_src/query_params_str_validations/tutorial013_py39.py", + "docs_src/request_files/tutorial001_03_an_py39.py", + "docs_src/request_files/tutorial001_03_py39.py", + "docs_src/request_files/tutorial001_an_py39.py", + "docs_src/request_files/tutorial001_py39.py", + "docs_src/request_files/tutorial002_an_py39.py", + "docs_src/request_files/tutorial002_py39.py", + "docs_src/request_files/tutorial003_an_py39.py", + "docs_src/request_files/tutorial003_py39.py", + "docs_src/request_form_models/tutorial001_an_py39.py", + "docs_src/request_form_models/tutorial001_py39.py", + "docs_src/request_form_models/tutorial002_an_py39.py", + "docs_src/request_form_models/tutorial002_py39.py", + "docs_src/request_forms/tutorial001_an_py39.py", + "docs_src/request_forms/tutorial001_py39.py", + "docs_src/request_forms_and_files/tutorial001_an_py39.py", + "docs_src/request_forms_and_files/tutorial001_py39.py", + "docs_src/response_change_status_code/tutorial001_py39.py", + "docs_src/response_cookies/tutorial001_py39.py", + "docs_src/response_cookies/tutorial002_py39.py", + "docs_src/response_directly/tutorial002_py39.py", + "docs_src/response_headers/tutorial001_py39.py", + "docs_src/response_headers/tutorial002_py39.py", + "docs_src/response_model/tutorial003_02_py39.py", + "docs_src/response_model/tutorial003_03_py39.py", + "docs_src/response_status_code/tutorial001_py39.py", + "docs_src/response_status_code/tutorial002_py39.py", + "docs_src/security/tutorial001_an_py39.py", + "docs_src/security/tutorial001_py39.py", + "docs_src/security/tutorial006_an_py39.py", + "docs_src/security/tutorial006_py39.py", + "docs_src/security/tutorial007_an_py39.py", + "docs_src/security/tutorial007_py39.py", + "docs_src/settings/app01_py39/config.py", + "docs_src/settings/app01_py39/main.py", + "docs_src/settings/app02_an_py39/config.py", + "docs_src/settings/app02_an_py39/main.py", + "docs_src/settings/app02_an_py39/test_main.py", + "docs_src/settings/app02_py39/config.py", + "docs_src/settings/app02_py39/main.py", + "docs_src/settings/app02_py39/test_main.py", + "docs_src/settings/app03_an_py39/config.py", + "docs_src/settings/app03_an_py39/main.py", + "docs_src/settings/app03_py39/config.py", + "docs_src/settings/app03_py39/main.py", + "docs_src/settings/tutorial001_py39.py", + "docs_src/static_files/tutorial001_py39.py", + "docs_src/sub_applications/tutorial001_py39.py", + "docs_src/templates/tutorial001_py39.py", + "docs_src/using_request_directly/tutorial001_py39.py", + "docs_src/websockets/tutorial001_py39.py", + "docs_src/websockets/tutorial003_py39.py", + "docs_src/wsgi/tutorial001_py39.py", ] [tool.coverage.report] @@ -327,32 +512,42 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] -"docs_src/dependencies/tutorial007_py39.py" = ["F821"] -"docs_src/dependencies/tutorial008_py39.py" = ["F821"] -"docs_src/dependencies/tutorial009_py39.py" = ["F821"] -"docs_src/dependencies/tutorial010_py39.py" = ["F821"] +"docs_src/custom_request_and_route/tutorial002_an_py310.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_an_py39.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_py310.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_py39.py" = ["B904"] +"docs_src/custom_response/tutorial007_py310.py" = ["B007"] "docs_src/custom_response/tutorial007_py39.py" = ["B007"] "docs_src/dataclasses/tutorial003_py39.py" = ["I001"] -"docs_src/path_operation_advanced_configuration/tutorial007_py39.py" = ["B904"] -"docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py" = ["B904"] -"docs_src/custom_request_and_route/tutorial002_py39.py" = ["B904"] -"docs_src/custom_request_and_route/tutorial002_py310.py" = ["B904"] -"docs_src/custom_request_and_route/tutorial002_an_py39.py" = ["B904"] -"docs_src/custom_request_and_route/tutorial002_an_py310.py" = ["B904"] +"docs_src/dependencies/tutorial007_py310.py" = ["F821"] +"docs_src/dependencies/tutorial007_py39.py" = ["F821"] +"docs_src/dependencies/tutorial008_an_py310.py" = ["F821"] "docs_src/dependencies/tutorial008_an_py39.py" = ["F821"] +"docs_src/dependencies/tutorial008_py310.py" = ["F821"] +"docs_src/dependencies/tutorial008_py39.py" = ["F821"] +"docs_src/dependencies/tutorial008b_an_py310.py" = ["B904"] +"docs_src/dependencies/tutorial008b_an_py39.py" = ["B904"] +"docs_src/dependencies/tutorial008b_py310.py" = ["B904"] +"docs_src/dependencies/tutorial008b_py39.py" = ["B904"] +"docs_src/dependencies/tutorial009_py310.py" = ["F821"] +"docs_src/dependencies/tutorial009_py39.py" = ["F821"] +"docs_src/dependencies/tutorial010_py310.py" = ["F821"] +"docs_src/dependencies/tutorial010_py39.py" = ["F821"] +"docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py" = ["B904"] +"docs_src/path_operation_advanced_configuration/tutorial007_py310.py" = ["B904"] +"docs_src/path_operation_advanced_configuration/tutorial007_py39.py" = ["B904"] +"docs_src/query_params_str_validations/tutorial012_an_py310.py" = ["B006"] "docs_src/query_params_str_validations/tutorial012_an_py39.py" = ["B006"] +"docs_src/query_params_str_validations/tutorial013_an_py310.py" = ["B006"] "docs_src/query_params_str_validations/tutorial013_an_py39.py" = ["B006"] -"docs_src/security/tutorial004_py39.py" = ["B904"] -"docs_src/security/tutorial004_an_py39.py" = ["B904"] "docs_src/security/tutorial004_an_py310.py" = ["B904"] +"docs_src/security/tutorial004_an_py39.py" = ["B904"] "docs_src/security/tutorial004_py310.py" = ["B904"] +"docs_src/security/tutorial004_py39.py" = ["B904"] "docs_src/security/tutorial005_an_py310.py" = ["B904"] "docs_src/security/tutorial005_an_py39.py" = ["B904"] "docs_src/security/tutorial005_py310.py" = ["B904"] "docs_src/security/tutorial005_py39.py" = ["B904"] -"docs_src/dependencies/tutorial008b_py39.py" = ["B904"] -"docs_src/dependencies/tutorial008b_an_py39.py" = ["B904"] - [tool.ruff.lint.isort] known-third-party = ["fastapi", "pydantic", "starlette"] diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial001.py b/tests/test_tutorial/test_additional_responses/test_tutorial001.py index d8d9d4130..456070c9c 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial001.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial001.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.additional_responses.tutorial001_py39 import app +from docs_src.additional_responses.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py index 4fae59d22..586779e44 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py @@ -12,7 +12,6 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial003.py b/tests/test_tutorial/test_additional_responses/test_tutorial003.py index e888819df..56eeacb60 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial003.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial003.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.additional_responses.tutorial003_py39 import app +from docs_src.additional_responses.tutorial003_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py index 9df326a5c..fe56fbb8b 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py @@ -12,7 +12,6 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py b/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py index bced1f6df..f57ea2bcf 100644 --- a/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py +++ b/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py @@ -9,9 +9,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py index f17391956..6231cf4cb 100644 --- a/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py +++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.advanced_middleware.tutorial001_py39 import app +from docs_src.advanced_middleware.tutorial001_py310 import app def test_middleware(): diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py index bae915406..fa8ac41d7 100644 --- a/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py +++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.advanced_middleware.tutorial002_py39 import app +from docs_src.advanced_middleware.tutorial002_py310 import app def test_middleware(): diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py index 66697997c..8602e1569 100644 --- a/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py +++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py @@ -1,7 +1,7 @@ from fastapi.responses import PlainTextResponse from fastapi.testclient import TestClient -from docs_src.advanced_middleware.tutorial003_py39 import app +from docs_src.advanced_middleware.tutorial003_py310 import app @app.get("/large") diff --git a/tests/test_tutorial/test_async_tests/test_main_a.py b/tests/test_tutorial/test_async_tests/test_main_a.py index f29acaa9a..4e33692d0 100644 --- a/tests/test_tutorial/test_async_tests/test_main_a.py +++ b/tests/test_tutorial/test_async_tests/test_main_a.py @@ -1,6 +1,6 @@ import pytest -from docs_src.async_tests.app_a_py39.test_main import test_root +from docs_src.async_tests.app_a_py310.test_main import test_root @pytest.mark.anyio diff --git a/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py b/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py index 6f5811631..7e1588d67 100644 --- a/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py +++ b/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py @@ -8,7 +8,7 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "tutorial001_an_py39", + "tutorial001_an_py310", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial001.py b/tests/test_tutorial/test_background_tasks/test_tutorial001.py index c0ad27a6f..100583f77 100644 --- a/tests/test_tutorial/test_background_tasks/test_tutorial001.py +++ b/tests/test_tutorial/test_background_tasks/test_tutorial001.py @@ -3,7 +3,7 @@ from pathlib import Path from fastapi.testclient import TestClient -from docs_src.background_tasks.tutorial001_py39 import app +from docs_src.background_tasks.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002.py b/tests/test_tutorial/test_background_tasks/test_tutorial002.py index 288a1c244..f29402c83 100644 --- a/tests/test_tutorial/test_background_tasks/test_tutorial002.py +++ b/tests/test_tutorial/test_background_tasks/test_tutorial002.py @@ -11,9 +11,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial002_py39", pytest.param("tutorial002_py310", marks=needs_py310), - "tutorial002_an_py39", pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py index 31adaa56a..38ae460fb 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.behind_a_proxy.tutorial001_py39 import app +from docs_src.behind_a_proxy.tutorial001_py310 import app client = TestClient(app, root_path="/api/v1") diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py index da4acb28c..190b33c50 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.behind_a_proxy.tutorial001_01_py39 import app +from docs_src.behind_a_proxy.tutorial001_01_py310 import app client = TestClient( app, diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py index 56e6f2f9d..c9356a513 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.behind_a_proxy.tutorial002_py39 import app +from docs_src.behind_a_proxy.tutorial002_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py index a164bb80b..d74da5b73 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.behind_a_proxy.tutorial003_py39 import app +from docs_src.behind_a_proxy.tutorial003_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py index 01bba9fed..f5e27c118 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.behind_a_proxy.tutorial004_py39 import app +from docs_src.behind_a_proxy.tutorial004_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py index 18845e470..abe008d9e 100644 --- a/tests/test_tutorial/test_bigger_applications/test_main.py +++ b/tests/test_tutorial/test_bigger_applications/test_main.py @@ -8,8 +8,7 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "app_py39.main", - "app_an_py39.main", + "app_an_py310.main", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py index c1877a72e..bdabf8d68 100644 --- a/tests/test_tutorial/test_body/test_tutorial001.py +++ b/tests/test_tutorial/test_body/test_tutorial001.py @@ -11,7 +11,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body/test_tutorial002.py b/tests/test_tutorial/test_body/test_tutorial002.py index 94bf213e3..f1e1236c9 100644 --- a/tests/test_tutorial/test_body/test_tutorial002.py +++ b/tests/test_tutorial/test_body/test_tutorial002.py @@ -1,5 +1,4 @@ import importlib -from typing import Union import pytest from fastapi.testclient import TestClient @@ -11,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) @@ -23,7 +21,7 @@ def get_client(request: pytest.FixtureRequest): @pytest.mark.parametrize("price", ["50.5", 50.5]) -def test_post_with_tax(client: TestClient, price: Union[str, float]): +def test_post_with_tax(client: TestClient, price: str | float): response = client.post( "/items/", json={"name": "Foo", "price": price, "description": "Some Foo", "tax": 0.3}, @@ -39,7 +37,7 @@ def test_post_with_tax(client: TestClient, price: Union[str, float]): @pytest.mark.parametrize("price", ["50.5", 50.5]) -def test_post_without_tax(client: TestClient, price: Union[str, float]): +def test_post_without_tax(client: TestClient, price: str | float): response = client.post( "/items/", json={"name": "Foo", "price": price, "description": "Some Foo"} ) diff --git a/tests/test_tutorial/test_body/test_tutorial003.py b/tests/test_tutorial/test_body/test_tutorial003.py index 832c211f6..b4767cf28 100644 --- a/tests/test_tutorial/test_body/test_tutorial003.py +++ b/tests/test_tutorial/test_body/test_tutorial003.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body/test_tutorial004.py b/tests/test_tutorial/test_body/test_tutorial004.py index 1019a168c..f0a50a64c 100644 --- a/tests/test_tutorial/test_body/test_tutorial004.py +++ b/tests/test_tutorial/test_body/test_tutorial004.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py index 0116dcb09..eb94d24cd 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py index 2e8ba457b..f510f1c15 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py index 0c94e9dd1..b23402c3c 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial002.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py index c27f3d5ba..89fabd3ba 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial003_py39", pytest.param("tutorial003_py310", marks=needs_py310), - "tutorial003_an_py39", pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py index 2a39f3d71..c5b4b452d 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial004.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), - pytest.param("tutorial004_an_py39"), pytest.param("tutorial004_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py index d600e0767..b8fc98cd7 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial005.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), - pytest.param("tutorial005_an_py39"), pytest.param("tutorial005_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py index 18bce279b..2e2f329f7 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py @@ -17,11 +17,8 @@ SET_OF_STR_SCHEMA = {"type": "array", "items": {"type": "string"}, "uniqueItems" @pytest.fixture( name="mod_name", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial004.py b/tests/test_tutorial/test_body_nested_models/test_tutorial004.py index 6a70779b6..4605a757d 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial004.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial004.py @@ -11,7 +11,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial005.py b/tests/test_tutorial/test_body_nested_models/test_tutorial005.py index 2ff3d4f22..ffa02cd65 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial005.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial005.py @@ -11,7 +11,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial006.py b/tests/test_tutorial/test_body_nested_models/test_tutorial006.py index 229216fc5..4551a72b3 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial006.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial006.py @@ -11,7 +11,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial006_py39"), pytest.param("tutorial006_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial007.py b/tests/test_tutorial/test_body_nested_models/test_tutorial007.py index 5a7763f59..5b99b41b6 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial007.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial007.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial007_py39"), pytest.param("tutorial007_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial008.py b/tests/test_tutorial/test_body_nested_models/test_tutorial008.py index 26f48f1d5..d038805bd 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial008.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial008.py @@ -8,7 +8,7 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial008_py39"), + pytest.param("tutorial008_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py index 492dcecd2..a747e2a64 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py @@ -8,7 +8,7 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "tutorial009_py39", + "tutorial009_py310", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py index feb07b859..ba8d7baf3 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_updates/test_tutorial002.py b/tests/test_tutorial/test_body_updates/test_tutorial002.py index a34d08b52..cf0ee7ada 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial002.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial002.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py index 644b82ad9..a7aa9dbe2 100644 --- a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py @@ -5,11 +5,11 @@ from inline_snapshot import snapshot def get_client() -> TestClient: - from docs_src.conditional_openapi import tutorial001_py39 + from docs_src.conditional_openapi import tutorial001_py310 - importlib.reload(tutorial001_py39) + importlib.reload(tutorial001_py310) - client = TestClient(tutorial001_py39.app) + client = TestClient(tutorial001_py310.app) return client diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py index 1fa9419a7..e2f1fbf90 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.configure_swagger_ui.tutorial001_py39 import app +from docs_src.configure_swagger_ui.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py index c218cc858..ce1cb4bac 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.configure_swagger_ui.tutorial002_py39 import app +from docs_src.configure_swagger_ui.tutorial002_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py index 8b7368549..6f28e7734 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.configure_swagger_ui.tutorial003_py39 import app +from docs_src.configure_swagger_ui.tutorial003_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py index f391c569a..c4de7ad45 100644 --- a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py @@ -10,9 +10,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py index 6583045dc..37be0f1dd 100644 --- a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py @@ -10,9 +10,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=[needs_py310]), - pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=[needs_py310]), ], ) diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py index 4d7adf0d6..db0765ea5 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py @@ -11,9 +11,7 @@ from ...utils import needs_py310 @pytest.fixture( name="mod", params=[ - "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_cors/test_tutorial001.py b/tests/test_tutorial/test_cors/test_tutorial001.py index 6a733693a..428378cd4 100644 --- a/tests/test_tutorial/test_cors/test_tutorial001.py +++ b/tests/test_tutorial/test_cors/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.cors.tutorial001_py39 import app +from docs_src.cors.tutorial001_py310 import app def test_cors(): diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py index 1816e5d97..f7eb7fd97 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py @@ -10,7 +10,7 @@ def client(): static_dir: Path = Path(os.getcwd()) / "static" print(static_dir) static_dir.mkdir(exist_ok=True) - from docs_src.custom_docs_ui.tutorial001_py39 import app + from docs_src.custom_docs_ui.tutorial001_py310 import app with TestClient(app) as client: yield client diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py index e8b7eb7aa..28945d838 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py @@ -10,7 +10,7 @@ def client(): static_dir: Path = Path(os.getcwd()) / "static" print(static_dir) static_dir.mkdir(exist_ok=True) - from docs_src.custom_docs_ui.tutorial002_py39 import app + from docs_src.custom_docs_ui.tutorial002_py310 import app with TestClient(app) as client: yield client diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py index b4ea53784..39d3bd406 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py @@ -12,9 +12,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py index a9c7ae638..746d6c9d2 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py @@ -10,9 +10,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py index 6cad7bd3f..c11ea1259 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py @@ -9,7 +9,6 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py index 20244bef4..a5fe4c8f4 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), - pytest.param("tutorial010_py39"), + pytest.param("tutorial001_py310"), + pytest.param("tutorial010_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001b.py b/tests/test_tutorial/test_custom_response/test_tutorial001b.py index 746801df1..32437db86 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001b.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.custom_response.tutorial001b_py39 import app +from docs_src.custom_response.tutorial001b_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py index 68e046ac5..42fd85583 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py @@ -8,9 +8,9 @@ from inline_snapshot import Is, snapshot @pytest.fixture( name="mod_name", params=[ - pytest.param("tutorial002_py39"), - pytest.param("tutorial003_py39"), - pytest.param("tutorial004_py39"), + pytest.param("tutorial002_py310"), + pytest.param("tutorial003_py310"), + pytest.param("tutorial004_py310"), ], ) def get_mod_name(request: pytest.FixtureRequest) -> str: diff --git a/tests/test_tutorial/test_custom_response/test_tutorial005.py b/tests/test_tutorial/test_custom_response/test_tutorial005.py index b4919af6c..297e48822 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial005.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial005.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.custom_response.tutorial005_py39 import app +from docs_src.custom_response.tutorial005_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006.py b/tests/test_tutorial/test_custom_response/test_tutorial006.py index ea2d366aa..4365090c2 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.custom_response.tutorial006_py39 import app +from docs_src.custom_response.tutorial006_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006b.py b/tests/test_tutorial/test_custom_response/test_tutorial006b.py index 133a591b1..a77618847 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006b.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.custom_response.tutorial006b_py39 import app +from docs_src.custom_response.tutorial006b_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006c.py b/tests/test_tutorial/test_custom_response/test_tutorial006c.py index 5b17d815d..f29bda2c2 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006c.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006c.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.custom_response.tutorial006c_py39 import app +from docs_src.custom_response.tutorial006c_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial007.py b/tests/test_tutorial/test_custom_response/test_tutorial007.py index a62476ec1..5f2e233f8 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial007.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial007.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial007_py39 import app +from docs_src.custom_response.tutorial007_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial008.py b/tests/test_tutorial/test_custom_response/test_tutorial008.py index d9fe61f53..1be7c1a62 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial008.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial008.py @@ -2,15 +2,15 @@ from pathlib import Path from fastapi.testclient import TestClient -from docs_src.custom_response import tutorial008_py39 -from docs_src.custom_response.tutorial008_py39 import app +from docs_src.custom_response import tutorial008_py310 +from docs_src.custom_response.tutorial008_py310 import app client = TestClient(app) def test_get(tmp_path: Path): file_path: Path = tmp_path / "large-video-file.mp4" - tutorial008_py39.some_file_path = str(file_path) + tutorial008_py310.some_file_path = str(file_path) test_content = b"Fake video bytes" file_path.write_bytes(test_content) response = client.get("/") diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009.py b/tests/test_tutorial/test_custom_response/test_tutorial009.py index cb6a514be..a6564d551 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial009.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial009.py @@ -2,15 +2,15 @@ from pathlib import Path from fastapi.testclient import TestClient -from docs_src.custom_response import tutorial009_py39 -from docs_src.custom_response.tutorial009_py39 import app +from docs_src.custom_response import tutorial009_py310 +from docs_src.custom_response.tutorial009_py310 import app client = TestClient(app) def test_get(tmp_path: Path): file_path: Path = tmp_path / "large-video-file.mp4" - tutorial009_py39.some_file_path = str(file_path) + tutorial009_py310.some_file_path = str(file_path) test_content = b"Fake video bytes" file_path.write_bytes(test_content) response = client.get("/") diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009b.py b/tests/test_tutorial/test_custom_response/test_tutorial009b.py index 9918bdb1a..05ac62f3d 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial009b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial009b.py @@ -2,15 +2,15 @@ from pathlib import Path from fastapi.testclient import TestClient -from docs_src.custom_response import tutorial009b_py39 -from docs_src.custom_response.tutorial009b_py39 import app +from docs_src.custom_response import tutorial009b_py310 +from docs_src.custom_response.tutorial009b_py310 import app client = TestClient(app) def test_get(tmp_path: Path): file_path: Path = tmp_path / "large-video-file.mp4" - tutorial009b_py39.some_file_path = str(file_path) + tutorial009b_py310.some_file_path = str(file_path) test_content = b"Fake video bytes" file_path.write_bytes(test_content) response = client.get("/") diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009c.py b/tests/test_tutorial/test_custom_response/test_tutorial009c.py index efc3a6b4a..7a1b71307 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial009c.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial009c.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial009c_py39 import app +from docs_src.custom_response.tutorial009c_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py index a3d2cf515..62de135dc 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py @@ -10,7 +10,6 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py index 210d743bb..f6607c8e0 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py @@ -10,7 +10,6 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py index e023271bc..5a35e5c4a 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_debugging/test_tutorial001.py b/tests/test_tutorial/test_debugging/test_tutorial001.py index 1a0ca6cad..fbf2bfa02 100644 --- a/tests/test_tutorial/test_debugging/test_tutorial001.py +++ b/tests/test_tutorial/test_debugging/test_tutorial001.py @@ -7,7 +7,7 @@ import pytest from fastapi.testclient import TestClient from inline_snapshot import snapshot -MOD_NAME = "docs_src.debugging.tutorial001_py39" +MOD_NAME = "docs_src.debugging.tutorial001_py310" @pytest.fixture(name="client") diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py b/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py index 74f4a8f3a..11e732e6d 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001_tutorial001_02.py @@ -10,11 +10,8 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), - pytest.param("tutorial001_02_an_py39"), pytest.param("tutorial001_02_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py index 6e1cea5a0..a9ddf8a52 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py @@ -10,17 +10,11 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), - pytest.param("tutorial003_an_py39"), pytest.param("tutorial003_an_py310", marks=needs_py310), - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), - pytest.param("tutorial004_an_py39"), pytest.param("tutorial004_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial005.py b/tests/test_tutorial/test_dependencies/test_tutorial005.py index 31054744a..3cd083b35 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial005.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial005.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), - pytest.param("tutorial005_an_py39"), pytest.param("tutorial005_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py index 8d6cfa2d6..f6e6f8368 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial006.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial006_py39"), - pytest.param("tutorial006_an_py39"), + pytest.param("tutorial006_py310"), + pytest.param("tutorial006_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial007.py b/tests/test_tutorial/test_dependencies/test_tutorial007.py index 3e188abcf..c98485611 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial007.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial007.py @@ -2,7 +2,7 @@ import asyncio from contextlib import asynccontextmanager from unittest.mock import Mock, patch -from docs_src.dependencies.tutorial007_py39 import get_db +from docs_src.dependencies.tutorial007_py310 import get_db def test_get_db(): # Just for coverage @@ -14,7 +14,7 @@ def test_get_db(): # Just for coverage dbsession_moock = Mock() with patch( - "docs_src.dependencies.tutorial007_py39.DBSession", + "docs_src.dependencies.tutorial007_py310.DBSession", return_value=dbsession_moock, create=True, ): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008.py b/tests/test_tutorial/test_dependencies/test_tutorial008.py index 5a2d226bf..e23ac0ac5 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008.py @@ -12,9 +12,9 @@ from fastapi.testclient import TestClient @pytest.fixture( name="module", params=[ - "tutorial008_py39", + "tutorial008_py310", pytest.param( - "tutorial008_an_py39", + "tutorial008_an_py310", marks=pytest.mark.xfail( sys.version_info < (3, 14), reason="Fails with `NameError: name 'DepA' is not defined`", diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008b.py b/tests/test_tutorial/test_dependencies/test_tutorial008b.py index 91e00b370..d2493d0cc 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008b.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008b.py @@ -7,8 +7,8 @@ from fastapi.testclient import TestClient @pytest.fixture( name="client", params=[ - pytest.param("tutorial008b_py39"), - pytest.param("tutorial008b_an_py39"), + pytest.param("tutorial008b_py310"), + pytest.param("tutorial008b_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008c.py b/tests/test_tutorial/test_dependencies/test_tutorial008c.py index aede6f8d2..b7713c73b 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008c.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008c.py @@ -9,8 +9,8 @@ from fastapi.testclient import TestClient @pytest.fixture( name="mod", params=[ - pytest.param("tutorial008c_py39"), - pytest.param("tutorial008c_an_py39"), + pytest.param("tutorial008c_py310"), + pytest.param("tutorial008c_an_py310"), ], ) def get_mod(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008d.py b/tests/test_tutorial/test_dependencies/test_tutorial008d.py index 5477f8b95..e831c055e 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008d.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008d.py @@ -8,8 +8,8 @@ from fastapi.testclient import TestClient @pytest.fixture( name="mod", params=[ - pytest.param("tutorial008d_py39"), - pytest.param("tutorial008d_an_py39"), + pytest.param("tutorial008d_py310"), + pytest.param("tutorial008d_an_py310"), ], ) def get_mod(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008e.py b/tests/test_tutorial/test_dependencies/test_tutorial008e.py index c433157ea..18eba9f77 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008e.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008e.py @@ -7,8 +7,8 @@ from fastapi.testclient import TestClient @pytest.fixture( name="client", params=[ - pytest.param("tutorial008e_py39"), - pytest.param("tutorial008e_an_py39"), + pytest.param("tutorial008e_py310"), + pytest.param("tutorial008e_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial010.py b/tests/test_tutorial/test_dependencies/test_tutorial010.py index 6d3815ada..954d09f20 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial010.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial010.py @@ -4,7 +4,7 @@ from unittest.mock import Mock, patch from fastapi import Depends, FastAPI from fastapi.testclient import TestClient -from docs_src.dependencies.tutorial010_py39 import get_db +from docs_src.dependencies.tutorial010_py310 import get_db def test_get_db(): @@ -19,7 +19,7 @@ def test_get_db(): dbsession_mock = Mock() with patch( - "docs_src.dependencies.tutorial010_py39.DBSession", + "docs_src.dependencies.tutorial010_py310.DBSession", return_value=dbsession_mock, create=True, ): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial011.py b/tests/test_tutorial/test_dependencies/test_tutorial011.py index 383422a7e..16167fd2e 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial011.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial011.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "tutorial011_py39", - pytest.param("tutorial011_an_py39"), + "tutorial011_py310", + pytest.param("tutorial011_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012.py b/tests/test_tutorial/test_dependencies/test_tutorial012.py index a15688924..e0148e052 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial012.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial012.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial012_py39"), - pytest.param("tutorial012_an_py39"), + pytest.param("tutorial012_py310"), + pytest.param("tutorial012_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_encoder/test_tutorial001.py b/tests/test_tutorial/test_encoder/test_tutorial001.py index a3d672333..e8c1f3c73 100644 --- a/tests/test_tutorial/test_encoder/test_tutorial001.py +++ b/tests/test_tutorial/test_encoder/test_tutorial001.py @@ -11,7 +11,6 @@ from ...utils import needs_py310 @pytest.fixture( name="mod", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_events/test_tutorial001.py b/tests/test_tutorial/test_events/test_tutorial001.py index 63215c00d..e392cdcc9 100644 --- a/tests/test_tutorial/test_events/test_tutorial001.py +++ b/tests/test_tutorial/test_events/test_tutorial001.py @@ -7,7 +7,7 @@ from inline_snapshot import snapshot @pytest.fixture(name="app", scope="module") def get_app(): with pytest.warns(DeprecationWarning): - from docs_src.events.tutorial001_py39 import app + from docs_src.events.tutorial001_py310 import app yield app diff --git a/tests/test_tutorial/test_events/test_tutorial002.py b/tests/test_tutorial/test_events/test_tutorial002.py index f98d8921f..47af7a952 100644 --- a/tests/test_tutorial/test_events/test_tutorial002.py +++ b/tests/test_tutorial/test_events/test_tutorial002.py @@ -7,7 +7,7 @@ from inline_snapshot import snapshot @pytest.fixture(name="app", scope="module") def get_app(): with pytest.warns(DeprecationWarning): - from docs_src.events.tutorial002_py39 import app + from docs_src.events.tutorial002_py310 import app yield app diff --git a/tests/test_tutorial/test_events/test_tutorial003.py b/tests/test_tutorial/test_events/test_tutorial003.py index 4fc848e11..3da0b2ca5 100644 --- a/tests/test_tutorial/test_events/test_tutorial003.py +++ b/tests/test_tutorial/test_events/test_tutorial003.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.events.tutorial003_py39 import ( +from docs_src.events.tutorial003_py310 import ( app, fake_answer_to_everything_ml_model, ml_models, diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py index bc10c888c..06c56565f 100644 --- a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.extending_openapi.tutorial001_py39 import app +from docs_src.extending_openapi.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py index 28fe68f28..2e7f0e522 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py index 4248878b5..4fcb7ff78 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial001_tutorial002.py @@ -11,9 +11,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003.py b/tests/test_tutorial/test_extra_models/test_tutorial003.py index 38e874158..2a2cf8afa 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial003.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial003.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004.py b/tests/test_tutorial/test_extra_models/test_tutorial004.py index c64371e3b..3ded20959 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial004.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial004.py @@ -8,7 +8,7 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), + pytest.param("tutorial004_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005.py b/tests/test_tutorial/test_extra_models/test_tutorial005.py index 43b8bae8e..265b76832 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial005.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial005.py @@ -8,7 +8,7 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial005_py39"), + pytest.param("tutorial005_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py b/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py index 5457ad132..30eab21e9 100644 --- a/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py +++ b/tests/test_tutorial/test_first_steps/test_tutorial001_tutorial002_tutorial003.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "tutorial001_py39", - "tutorial003_py39", + "tutorial001_py310", + "tutorial003_py310", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial001.py b/tests/test_tutorial/test_generate_clients/test_tutorial001.py index 62799d259..40ed3eeea 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial001.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial001.py @@ -8,7 +8,7 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), + pytest.param("tutorial001_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial002.py b/tests/test_tutorial/test_generate_clients/test_tutorial002.py index f64f5f866..6557b8c27 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial002.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial002.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.generate_clients.tutorial002_py39 import app +from docs_src.generate_clients.tutorial002_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial003.py b/tests/test_tutorial/test_generate_clients/test_tutorial003.py index 34ede6194..cded241ae 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial003.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial003.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.generate_clients.tutorial003_py39 import app +from docs_src.generate_clients.tutorial003_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial004.py b/tests/test_tutorial/test_generate_clients/test_tutorial004.py index d04bfbfe8..1d71de869 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial004.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial004.py @@ -5,16 +5,16 @@ from unittest.mock import patch from inline_snapshot import snapshot -from docs_src.generate_clients import tutorial003_py39 +from docs_src.generate_clients import tutorial003_py310 def test_remove_tags(tmp_path: pathlib.Path): tmp_file = tmp_path / "openapi.json" - openapi_json = tutorial003_py39.app.openapi() + openapi_json = tutorial003_py310.app.openapi() tmp_file.write_text(json.dumps(openapi_json)) with patch("pathlib.Path", return_value=tmp_file): - importlib.import_module("docs_src.generate_clients.tutorial004_py39") + importlib.import_module("docs_src.generate_clients.tutorial004_py310") modified_openapi = json.loads(tmp_file.read_text()) assert modified_openapi == snapshot( diff --git a/tests/test_tutorial/test_graphql/test_tutorial001.py b/tests/test_tutorial/test_graphql/test_tutorial001.py index cc3be6a19..c93347081 100644 --- a/tests/test_tutorial/test_graphql/test_tutorial001.py +++ b/tests/test_tutorial/test_graphql/test_tutorial001.py @@ -10,7 +10,7 @@ warnings.filterwarnings( category=DeprecationWarning, ) -from docs_src.graphql_.tutorial001_py39 import app # noqa: E402 +from docs_src.graphql_.tutorial001_py310 import app # noqa: E402 @pytest.fixture(name="client") diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial001.py b/tests/test_tutorial/test_handling_errors/test_tutorial001.py index 8283cdc73..001a02a0f 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial001.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial001.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.handling_errors.tutorial001_py39 import app +from docs_src.handling_errors.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial002.py b/tests/test_tutorial/test_handling_errors/test_tutorial002.py index c437693d3..0e7321ee7 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial002.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial002.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.handling_errors.tutorial002_py39 import app +from docs_src.handling_errors.tutorial002_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial003.py b/tests/test_tutorial/test_handling_errors/test_tutorial003.py index 959729e53..cc11985be 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial003.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial003.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.handling_errors.tutorial003_py39 import app +from docs_src.handling_errors.tutorial003_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial004.py b/tests/test_tutorial/test_handling_errors/test_tutorial004.py index 16165bb3d..5471cb0d8 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial004.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial004.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.handling_errors.tutorial004_py39 import app +from docs_src.handling_errors.tutorial004_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial005.py b/tests/test_tutorial/test_handling_errors/test_tutorial005.py index af924a9f7..c04b510c3 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial005.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial005.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.handling_errors.tutorial005_py39 import app +from docs_src.handling_errors.tutorial005_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial006.py b/tests/test_tutorial/test_handling_errors/test_tutorial006.py index 4c069d81e..666e44f3a 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial006.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial006.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.handling_errors.tutorial006_py39 import app +from docs_src.handling_errors.tutorial006_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial001.py b/tests/test_tutorial/test_header_param_models/test_tutorial001.py index 2d14c698e..8391bd119 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial001.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial001.py @@ -10,9 +10,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial002.py b/tests/test_tutorial/test_header_param_models/test_tutorial002.py index 478ac8408..3d66dca08 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial002.py @@ -10,9 +10,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=[needs_py310]), - pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=[needs_py310]), ], ) diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial003.py b/tests/test_tutorial/test_header_param_models/test_tutorial003.py index 00636c2b5..7d0da24fd 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial003.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial003.py @@ -10,9 +10,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), - pytest.param("tutorial003_an_py39"), pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_header_params/test_tutorial001.py b/tests/test_tutorial/test_header_params/test_tutorial001.py index 2e97a8322..900d35adc 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_header_params/test_tutorial002.py b/tests/test_tutorial/test_header_params/test_tutorial002.py index ba86c55bf..c756eae71 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial002.py +++ b/tests/test_tutorial/test_header_params/test_tutorial002.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_header_params/test_tutorial003.py b/tests/test_tutorial/test_header_params/test_tutorial003.py index 30e5133c0..1f71322b6 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial003.py +++ b/tests/test_tutorial/test_header_params/test_tutorial003.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), - pytest.param("tutorial003_an_py39"), pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_metadata/test_tutorial001.py b/tests/test_tutorial/test_metadata/test_tutorial001.py index ba3c09ce4..ce0c1643a 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial001.py +++ b/tests/test_tutorial/test_metadata/test_tutorial001.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.metadata.tutorial001_py39 import app +from docs_src.metadata.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_metadata/test_tutorial001_1.py b/tests/test_tutorial/test_metadata/test_tutorial001_1.py index 933954973..ba9ba9dcb 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial001_1.py +++ b/tests/test_tutorial/test_metadata/test_tutorial001_1.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.metadata.tutorial001_1_py39 import app +from docs_src.metadata.tutorial001_1_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_metadata/test_tutorial002.py b/tests/test_tutorial/test_metadata/test_tutorial002.py index 5a252475e..979a6a7b8 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial002.py +++ b/tests/test_tutorial/test_metadata/test_tutorial002.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.metadata.tutorial002_py39 import app +from docs_src.metadata.tutorial002_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_metadata/test_tutorial003.py b/tests/test_tutorial/test_metadata/test_tutorial003.py index 778781e66..ee2978028 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial003.py +++ b/tests/test_tutorial/test_metadata/test_tutorial003.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.metadata.tutorial003_py39 import app +from docs_src.metadata.tutorial003_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_metadata/test_tutorial004.py b/tests/test_tutorial/test_metadata/test_tutorial004.py index 5c309a830..ef0533d50 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial004.py +++ b/tests/test_tutorial/test_metadata/test_tutorial004.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.metadata.tutorial004_py39 import app +from docs_src.metadata.tutorial004_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_middleware/test_tutorial001.py b/tests/test_tutorial/test_middleware/test_tutorial001.py index 18047f343..1fa1e5a50 100644 --- a/tests/test_tutorial/test_middleware/test_tutorial001.py +++ b/tests/test_tutorial/test_middleware/test_tutorial001.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.middleware.tutorial001_py39 import app +from docs_src.middleware.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py index 1e937d0c9..62374ef27 100644 --- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py +++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py @@ -11,7 +11,6 @@ from tests.utils import needs_py310 @pytest.fixture( name="mod", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py b/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py index 0482c94bf..696a2bed5 100644 --- a/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py +++ b/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.openapi_webhooks.tutorial001_py39 import app +from docs_src.openapi_webhooks.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py index 3a4f64824..ecda8a315 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_operation_advanced_configuration.tutorial001_py39 import app +from docs_src.path_operation_advanced_configuration.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py index c26c36030..c5067e233 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_operation_advanced_configuration.tutorial002_py39 import app +from docs_src.path_operation_advanced_configuration.tutorial002_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py index f060647b2..36fe21678 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_operation_advanced_configuration.tutorial003_py39 import app +from docs_src.path_operation_advanced_configuration.tutorial003_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py index bf7934544..894cd9749 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py index 779e54f9e..f129ec6f4 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_operation_advanced_configuration.tutorial005_py39 import app +from docs_src.path_operation_advanced_configuration.tutorial005_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py index d63b5f912..f3bf2b5bf 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_operation_advanced_configuration.tutorial006_py39 import app +from docs_src.path_operation_advanced_configuration.tutorial006_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py index ec0c91bda..ad06e6c05 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py @@ -8,7 +8,7 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial007_py39"), + pytest.param("tutorial007_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py index 500cb057c..1b81b6f86 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial001.py @@ -11,7 +11,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py index 1cc560cb9..675f3b1ae 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002.py @@ -11,7 +11,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py index 98319645c..f9a5ff760 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_operation_configuration.tutorial002b_py39 import app +from docs_src.path_operation_configuration.tutorial002b_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py index c13a3c38c..6d1afecc8 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py @@ -25,9 +25,7 @@ DESCRIPTIONS = { @pytest.fixture( name="mod_name", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py index 07152755f..b186c0a68 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py index 5f56ab929..27a93f563 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py @@ -2,7 +2,7 @@ import pytest from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_operation_configuration.tutorial006_py39 import app +from docs_src.path_operation_configuration.tutorial006_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_params/test_tutorial001.py b/tests/test_tutorial/test_path_params/test_tutorial001.py index ec598832b..75c47c855 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial001.py +++ b/tests/test_tutorial/test_path_params/test_tutorial001.py @@ -2,7 +2,7 @@ import pytest from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_params.tutorial001_py39 import app +from docs_src.path_params.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_params/test_tutorial002.py b/tests/test_tutorial/test_path_params/test_tutorial002.py index 8384ec8ef..416c70558 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial002.py +++ b/tests/test_tutorial/test_path_params/test_tutorial002.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_params.tutorial002_py39 import app +from docs_src.path_params.tutorial002_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_params/test_tutorial003.py b/tests/test_tutorial/test_path_params/test_tutorial003.py index 432ccde49..2c07ebbc8 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial003.py +++ b/tests/test_tutorial/test_path_params/test_tutorial003.py @@ -2,7 +2,7 @@ import pytest from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_params.tutorial003_py39 import app +from docs_src.path_params.tutorial003_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_params/test_tutorial003b.py b/tests/test_tutorial/test_path_params/test_tutorial003b.py index 1cf39eca9..debf98503 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial003b.py +++ b/tests/test_tutorial/test_path_params/test_tutorial003b.py @@ -3,7 +3,7 @@ import asyncio from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_params.tutorial003b_py39 import app, read_users2 +from docs_src.path_params.tutorial003b_py310 import app, read_users2 client = TestClient(app) diff --git a/tests/test_tutorial/test_path_params/test_tutorial004.py b/tests/test_tutorial/test_path_params/test_tutorial004.py index b691e821d..2f2a72e64 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial004.py +++ b/tests/test_tutorial/test_path_params/test_tutorial004.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_params.tutorial004_py39 import app +from docs_src.path_params.tutorial004_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_params/test_tutorial005.py b/tests/test_tutorial/test_path_params/test_tutorial005.py index c60fee3f0..bf54b59a9 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial005.py +++ b/tests/test_tutorial/test_path_params/test_tutorial005.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.path_params.tutorial005_py39 import app +from docs_src.path_params.tutorial005_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py index b43de7032..868b0ea78 100644 --- a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial001.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py index 1bb2a3ea8..55d5b418c 100644 --- a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial002_tutorial003.py @@ -8,10 +8,10 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), - pytest.param("tutorial002_an_py39"), - pytest.param("tutorial003_py39"), - pytest.param("tutorial003_an_py39"), + pytest.param("tutorial002_py310"), + pytest.param("tutorial002_an_py310"), + pytest.param("tutorial003_py310"), + pytest.param("tutorial003_an_py310"), ], ) def get_client(request: pytest.FixtureRequest) -> TestClient: diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py index 37c16d295..7b1272fa1 100644 --- a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial004.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), - pytest.param("tutorial004_an_py39"), + pytest.param("tutorial004_py310"), + pytest.param("tutorial004_an_py310"), ], ) def get_client(request: pytest.FixtureRequest) -> TestClient: diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py index 0afc9dcbc..d2c9b6077 100644 --- a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial005.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial005_py39"), - pytest.param("tutorial005_an_py39"), + pytest.param("tutorial005_py310"), + pytest.param("tutorial005_an_py310"), ], ) def get_client(request: pytest.FixtureRequest) -> TestClient: diff --git a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py index e0a9694c1..9fb1f8b3b 100644 --- a/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_params_numeric_validations/test_tutorial006.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial006_py39"), - pytest.param("tutorial006_an_py39"), + pytest.param("tutorial006_py310"), + pytest.param("tutorial006_an_py310"), ], ) def get_client(request: pytest.FixtureRequest) -> TestClient: diff --git a/tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py index ccb096857..d7bf599fe 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py +++ b/tests/test_tutorial/test_python_types/test_tutorial001_tutorial002.py @@ -7,8 +7,8 @@ import pytest @pytest.mark.parametrize( "module_name", [ - "tutorial001_py39", - "tutorial002_py39", + "tutorial001_py310", + "tutorial002_py310", ], ) def test_run_module(module_name: str): diff --git a/tests/test_tutorial/test_python_types/test_tutorial003.py b/tests/test_tutorial/test_python_types/test_tutorial003.py index 34d264917..20b1e7101 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial003.py +++ b/tests/test_tutorial/test_python_types/test_tutorial003.py @@ -1,6 +1,6 @@ import pytest -from docs_src.python_types.tutorial003_py39 import get_name_with_age +from docs_src.python_types.tutorial003_py310 import get_name_with_age def test_get_name_with_age_pass_int(): diff --git a/tests/test_tutorial/test_python_types/test_tutorial004.py b/tests/test_tutorial/test_python_types/test_tutorial004.py index 24af32883..0b16c0cb4 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial004.py +++ b/tests/test_tutorial/test_python_types/test_tutorial004.py @@ -1,4 +1,4 @@ -from docs_src.python_types.tutorial004_py39 import get_name_with_age +from docs_src.python_types.tutorial004_py310 import get_name_with_age def test_get_name_with_age_pass_int(): diff --git a/tests/test_tutorial/test_python_types/test_tutorial005.py b/tests/test_tutorial/test_python_types/test_tutorial005.py index 6d67ec471..b5c847523 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial005.py +++ b/tests/test_tutorial/test_python_types/test_tutorial005.py @@ -1,4 +1,4 @@ -from docs_src.python_types.tutorial005_py39 import get_items +from docs_src.python_types.tutorial005_py310 import get_items def test_get_items(): diff --git a/tests/test_tutorial/test_python_types/test_tutorial006.py b/tests/test_tutorial/test_python_types/test_tutorial006.py index 50976926e..fbe929a76 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial006.py +++ b/tests/test_tutorial/test_python_types/test_tutorial006.py @@ -1,6 +1,6 @@ from unittest.mock import patch -from docs_src.python_types.tutorial006_py39 import process_items +from docs_src.python_types.tutorial006_py310 import process_items def test_process_items(): diff --git a/tests/test_tutorial/test_python_types/test_tutorial007.py b/tests/test_tutorial/test_python_types/test_tutorial007.py index c04529465..b664d2784 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial007.py +++ b/tests/test_tutorial/test_python_types/test_tutorial007.py @@ -1,4 +1,4 @@ -from docs_src.python_types.tutorial007_py39 import process_items +from docs_src.python_types.tutorial007_py310 import process_items def test_process_items(): diff --git a/tests/test_tutorial/test_python_types/test_tutorial008.py b/tests/test_tutorial/test_python_types/test_tutorial008.py index 33cf6cbfb..74a4e9cac 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial008.py +++ b/tests/test_tutorial/test_python_types/test_tutorial008.py @@ -1,6 +1,6 @@ from unittest.mock import patch -from docs_src.python_types.tutorial008_py39 import process_items +from docs_src.python_types.tutorial008_py310 import process_items def test_process_items(): diff --git a/tests/test_tutorial/test_python_types/test_tutorial008b.py b/tests/test_tutorial/test_python_types/test_tutorial008b.py index 1ef0d4ea1..60d7e51c1 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial008b.py +++ b/tests/test_tutorial/test_python_types/test_tutorial008b.py @@ -10,7 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="module", params=[ - pytest.param("tutorial008b_py39"), + pytest.param("tutorial008b_py310"), pytest.param("tutorial008b_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py b/tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py index 34046c5c4..eec51b1be 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py +++ b/tests/test_tutorial/test_python_types/test_tutorial009_tutorial009b.py @@ -10,9 +10,8 @@ from ...utils import needs_py310 @pytest.fixture( name="module", params=[ - pytest.param("tutorial009_py39"), + pytest.param("tutorial009_py310"), pytest.param("tutorial009_py310", marks=needs_py310), - pytest.param("tutorial009b_py39"), ], ) def get_module(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_python_types/test_tutorial009c.py b/tests/test_tutorial/test_python_types/test_tutorial009c.py index 7bd404911..17c4b9e0c 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial009c.py +++ b/tests/test_tutorial/test_python_types/test_tutorial009c.py @@ -11,7 +11,7 @@ from ...utils import needs_py310 @pytest.fixture( name="module", params=[ - pytest.param("tutorial009c_py39"), + pytest.param("tutorial009c_py310"), pytest.param("tutorial009c_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_python_types/test_tutorial010.py b/tests/test_tutorial/test_python_types/test_tutorial010.py index 9e4d2e36b..8525e6d84 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial010.py +++ b/tests/test_tutorial/test_python_types/test_tutorial010.py @@ -1,4 +1,4 @@ -from docs_src.python_types.tutorial010_py39 import Person, get_person_name +from docs_src.python_types.tutorial010_py310 import Person, get_person_name def test_get_person_name(): diff --git a/tests/test_tutorial/test_python_types/test_tutorial011.py b/tests/test_tutorial/test_python_types/test_tutorial011.py index a05751b97..3761eb924 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial011.py +++ b/tests/test_tutorial/test_python_types/test_tutorial011.py @@ -9,7 +9,6 @@ from ...utils import needs_py310 @pytest.mark.parametrize( "module_name", [ - pytest.param("tutorial011_py39"), pytest.param("tutorial011_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_python_types/test_tutorial012.py b/tests/test_tutorial/test_python_types/test_tutorial012.py deleted file mode 100644 index e57804820..000000000 --- a/tests/test_tutorial/test_python_types/test_tutorial012.py +++ /dev/null @@ -1,7 +0,0 @@ -from docs_src.python_types.tutorial012_py39 import User - - -def test_user(): - user = User(name="John Doe", age=30) - assert user.name == "John Doe" - assert user.age == 30 diff --git a/tests/test_tutorial/test_python_types/test_tutorial013.py b/tests/test_tutorial/test_python_types/test_tutorial013.py index 5602ef76f..d8669b485 100644 --- a/tests/test_tutorial/test_python_types/test_tutorial013.py +++ b/tests/test_tutorial/test_python_types/test_tutorial013.py @@ -1,4 +1,4 @@ -from docs_src.python_types.tutorial013_py39 import say_hello +from docs_src.python_types.tutorial013_py310 import say_hello def test_say_hello(): diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial001.py b/tests/test_tutorial/test_query_param_models/test_tutorial001.py index 38b767154..75d1b9762 100644 --- a/tests/test_tutorial/test_query_param_models/test_tutorial001.py +++ b/tests/test_tutorial/test_query_param_models/test_tutorial001.py @@ -10,9 +10,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial002.py b/tests/test_tutorial/test_query_param_models/test_tutorial002.py index b173a2df4..da2b6608a 100644 --- a/tests/test_tutorial/test_query_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_query_param_models/test_tutorial002.py @@ -10,9 +10,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=[needs_py310]), - pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=[needs_py310]), ], ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial001.py b/tests/test_tutorial/test_query_params/test_tutorial001.py index 40ada0e8e..d68702587 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial001.py +++ b/tests/test_tutorial/test_query_params/test_tutorial001.py @@ -8,7 +8,7 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), + pytest.param("tutorial001_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_query_params/test_tutorial002.py b/tests/test_tutorial/test_query_params/test_tutorial002.py index 1da308a7e..9be507fba 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial002.py +++ b/tests/test_tutorial/test_query_params/test_tutorial002.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial003.py b/tests/test_tutorial/test_query_params/test_tutorial003.py index 9bb58ff9f..2951f1adf 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial003.py +++ b/tests/test_tutorial/test_query_params/test_tutorial003.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial004.py b/tests/test_tutorial/test_query_params/test_tutorial004.py index 20aadb3ac..9786f861f 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial004.py +++ b/tests/test_tutorial/test_query_params/test_tutorial004.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial005.py b/tests/test_tutorial/test_query_params/test_tutorial005.py index e023fe6d8..b21e97f0b 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial005.py +++ b/tests/test_tutorial/test_query_params/test_tutorial005.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.query_params.tutorial005_py39 import app +from docs_src.query_params.tutorial005_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py index b28e12655..a27f2bbb0 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial006.py +++ b/tests/test_tutorial/test_query_params/test_tutorial006.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial006_py39"), pytest.param("tutorial006_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py index ed73b7329..1eb424e4f 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py index 3eac1f2b3..feb59f169 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial002.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py index 59d5160ac..4e1ef1da7 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial003.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), - pytest.param("tutorial003_an_py39"), pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py index abf08c932..9c2d442c8 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial004.py @@ -10,19 +10,8 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), - pytest.param("tutorial004_an_py39"), pytest.param("tutorial004_an_py310", marks=needs_py310), - pytest.param( - "tutorial004_regex_an_py310", - marks=( - needs_py310, - pytest.mark.filterwarnings( - "ignore:`regex` has been deprecated, please use `pattern` instead:fastapi.exceptions.FastAPIDeprecationWarning" - ), - ), - ), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py index 7b5368abc..61f07631b 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial005.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial005_py39"), - pytest.param("tutorial005_an_py39"), + pytest.param("tutorial005_py310"), + pytest.param("tutorial005_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py index 2c1df2c08..18ee8dbdc 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial006_py39"), - pytest.param("tutorial006_an_py39"), + pytest.param("tutorial006_py310"), + pytest.param("tutorial006_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py index 3df9efa83..74c10b885 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial006c.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial006c_py39"), pytest.param("tutorial006c_py310", marks=needs_py310), - pytest.param("tutorial006c_an_py39"), pytest.param("tutorial006c_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py index 874002b17..3a23ce4a4 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial007.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial007_py39"), pytest.param("tutorial007_py310", marks=needs_py310), - pytest.param("tutorial007_an_py39"), pytest.param("tutorial007_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py index b9613a17b..430d1bc18 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial008.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial008_py39"), pytest.param("tutorial008_py310", marks=needs_py310), - pytest.param("tutorial008_an_py39"), pytest.param("tutorial008_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py index d749d85f7..a81e1c40e 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial009.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial009_py39"), pytest.param("tutorial009_py310", marks=needs_py310), - pytest.param("tutorial009_an_py39"), pytest.param("tutorial009_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py index efe9f1fa6..bb8fbfcee 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py @@ -11,9 +11,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial010_py39"), pytest.param("tutorial010_py310", marks=needs_py310), - pytest.param("tutorial010_an_py39"), pytest.param("tutorial010_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py index c25357fdd..8d558efec 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial011_py39"), pytest.param("tutorial011_py310", marks=needs_py310), - pytest.param("tutorial011_an_py39"), pytest.param("tutorial011_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py index c3d01e90e..5aae7fcc6 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial012_py39"), - pytest.param("tutorial012_an_py39"), + pytest.param("tutorial012_py310"), + pytest.param("tutorial012_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py index 74efa4695..29d49a5b7 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "tutorial013_py39", - "tutorial013_an_py39", + "tutorial013_py310", + "tutorial013_an_py310", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py index 6dff18b78..683e39795 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial014_py39"), pytest.param("tutorial014_py310", marks=needs_py310), - pytest.param("tutorial014_an_py39"), pytest.param("tutorial014_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py index 32a990e74..01d7c983a 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py @@ -11,7 +11,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial015_an_py39"), pytest.param("tutorial015_an_py310", marks=[needs_py310]), ], ) diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py index fd5c3b055..4d3c35d65 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "tutorial001_py39", - "tutorial001_an_py39", + "tutorial001_py310", + "tutorial001_an_py310", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py index feeb5363e..f199b992a 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py @@ -11,9 +11,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_02_py39"), pytest.param("tutorial001_02_py310", marks=needs_py310), - pytest.param("tutorial001_02_an_py39"), pytest.param("tutorial001_02_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py index 5ef8df178..ce22c1b5c 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "tutorial001_03_py39", - "tutorial001_03_an_py39", + "tutorial001_03_py310", + "tutorial001_03_an_py310", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py index 52655fd63..ebf76b3a0 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002.py @@ -9,8 +9,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="app", params=[ - "tutorial002_py39", - "tutorial002_an_py39", + "tutorial002_py310", + "tutorial002_an_py310", ], ) def get_app(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_files/test_tutorial003.py b/tests/test_tutorial/test_request_files/test_tutorial003.py index a1425ba33..f11658d27 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial003.py +++ b/tests/test_tutorial/test_request_files/test_tutorial003.py @@ -9,8 +9,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="app", params=[ - "tutorial003_py39", - "tutorial003_an_py39", + "tutorial003_py310", + "tutorial003_an_py310", ], ) def get_app(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001.py b/tests/test_tutorial/test_request_form_models/test_tutorial001.py index 68fa074dc..3122e97e8 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial001.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial001.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "tutorial001_py39", - "tutorial001_an_py39", + "tutorial001_py310", + "tutorial001_an_py310", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002.py b/tests/test_tutorial/test_request_form_models/test_tutorial002.py index ed5ae109a..e5dc6e486 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial002.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial002.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "tutorial002_py39", - "tutorial002_an_py39", + "tutorial002_py310", + "tutorial002_an_py310", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_forms/test_tutorial001.py b/tests/test_tutorial/test_request_forms/test_tutorial001.py index d1596f137..ff3421aac 100644 --- a/tests/test_tutorial/test_request_forms/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms/test_tutorial001.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - "tutorial001_py39", - "tutorial001_an_py39", + "tutorial001_py310", + "tutorial001_an_py310", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py index 6e9165c42..e2462e040 100644 --- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py @@ -9,8 +9,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="app", params=[ - "tutorial001_py39", - "tutorial001_an_py39", + "tutorial001_py310", + "tutorial001_an_py310", ], ) def get_app(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py b/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py index 05d5ca619..d046bd266 100644 --- a/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py +++ b/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_change_status_code.tutorial001_py39 import app +from docs_src.response_change_status_code.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_cookies/test_tutorial001.py b/tests/test_tutorial/test_response_cookies/test_tutorial001.py index 6b931c8bd..106898339 100644 --- a/tests/test_tutorial/test_response_cookies/test_tutorial001.py +++ b/tests/test_tutorial/test_response_cookies/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_cookies.tutorial001_py39 import app +from docs_src.response_cookies.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_cookies/test_tutorial002.py b/tests/test_tutorial/test_response_cookies/test_tutorial002.py index 3ee5f500c..66812e480 100644 --- a/tests/test_tutorial/test_response_cookies/test_tutorial002.py +++ b/tests/test_tutorial/test_response_cookies/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_cookies.tutorial002_py39 import app +from docs_src.response_cookies.tutorial002_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_directly/test_tutorial001.py b/tests/test_tutorial/test_response_directly/test_tutorial001.py index 2b034f1c9..54406684d 100644 --- a/tests/test_tutorial/test_response_directly/test_tutorial001.py +++ b/tests/test_tutorial/test_response_directly/test_tutorial001.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_directly/test_tutorial002.py b/tests/test_tutorial/test_response_directly/test_tutorial002.py index 047e82c14..7dcb8d292 100644 --- a/tests/test_tutorial/test_response_directly/test_tutorial002.py +++ b/tests/test_tutorial/test_response_directly/test_tutorial002.py @@ -8,7 +8,7 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_response_headers/test_tutorial001.py b/tests/test_tutorial/test_response_headers/test_tutorial001.py index 6232aad23..ea182246f 100644 --- a/tests/test_tutorial/test_response_headers/test_tutorial001.py +++ b/tests/test_tutorial/test_response_headers/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_headers.tutorial001_py39 import app +from docs_src.response_headers.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_headers/test_tutorial002.py b/tests/test_tutorial/test_response_headers/test_tutorial002.py index 2ac2226cb..9b90459cb 100644 --- a/tests/test_tutorial/test_response_headers/test_tutorial002.py +++ b/tests/test_tutorial/test_response_headers/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_headers.tutorial002_py39 import app +from docs_src.response_headers.tutorial002_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py b/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py index 914f53783..bbdcc6820 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py +++ b/tests/test_tutorial/test_response_model/test_tutorial001_tutorial001_01.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_01_py39"), pytest.param("tutorial001_01_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial002.py b/tests/test_tutorial/test_response_model/test_tutorial002.py index 10a4e37cd..31b465990 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial002.py +++ b/tests/test_tutorial/test_response_model/test_tutorial002.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003.py b/tests/test_tutorial/test_response_model/test_tutorial003.py index a1477b7df..8178c8196 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01.py b/tests/test_tutorial/test_response_model/test_tutorial003_01.py index a60a14ae8..1a0c4f260 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_01.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_01.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_01_py39"), pytest.param("tutorial003_01_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_02.py b/tests/test_tutorial/test_response_model/test_tutorial003_02.py index 460c88251..a28c56be9 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_02.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_02.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.response_model.tutorial003_02_py39 import app +from docs_src.response_model.tutorial003_02_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_03.py b/tests/test_tutorial/test_response_model/test_tutorial003_03.py index c83d78c4f..65e747022 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_03.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_03.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.response_model.tutorial003_03_py39 import app +from docs_src.response_model.tutorial003_03_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_04.py b/tests/test_tutorial/test_response_model/test_tutorial003_04.py index 145af126f..44f2e504e 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_04.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_04.py @@ -9,7 +9,6 @@ from ...utils import needs_py310 @pytest.mark.parametrize( "module_name", [ - pytest.param("tutorial003_04_py39"), pytest.param("tutorial003_04_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05.py b/tests/test_tutorial/test_response_model/test_tutorial003_05.py index 187b6491f..0f0e94fda 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_05.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_05.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_05_py39"), pytest.param("tutorial003_05_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial004.py b/tests/test_tutorial/test_response_model/test_tutorial004.py index d40bce261..e7c0e79f2 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial004.py +++ b/tests/test_tutorial/test_response_model/test_tutorial004.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial005.py b/tests/test_tutorial/test_response_model/test_tutorial005.py index 55b2334d4..75c8fc0ac 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial005.py +++ b/tests/test_tutorial/test_response_model/test_tutorial005.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial006.py b/tests/test_tutorial/test_response_model/test_tutorial006.py index 5d6f542b5..f80ce1b28 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial006.py +++ b/tests/test_tutorial/test_response_model/test_tutorial006.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial006_py39"), pytest.param("tutorial006_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py b/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py index 7199ceed6..69c32f92e 100644 --- a/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py +++ b/tests/test_tutorial/test_response_status_code/test_tutorial001_tutorial002.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), - pytest.param("tutorial002_py39"), + pytest.param("tutorial001_py310"), + pytest.param("tutorial002_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py index f19346a3a..126dfbe7d 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py index 8313cb14f..cac3337a2 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial002.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py index 3c45a64bc..1ccca4da8 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial003.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), - pytest.param("tutorial003_an_py39"), pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py index 4b8e15fa1..cffd27066 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), - pytest.param("tutorial004_an_py39"), pytest.param("tutorial004_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py index 7f8e36df2..0bc17a00e 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), - pytest.param("tutorial005_an_py39"), pytest.param("tutorial005_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_security/test_tutorial001.py b/tests/test_tutorial/test_security/test_tutorial001.py index 5742b51bf..c1a15ae00 100644 --- a/tests/test_tutorial/test_security/test_tutorial001.py +++ b/tests/test_tutorial/test_security/test_tutorial001.py @@ -8,8 +8,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), - pytest.param("tutorial001_an_py39"), + pytest.param("tutorial001_py310"), + pytest.param("tutorial001_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_security/test_tutorial002.py b/tests/test_tutorial/test_security/test_tutorial002.py index 1e8c6e018..4d1202c32 100644 --- a/tests/test_tutorial/test_security/test_tutorial002.py +++ b/tests/test_tutorial/test_security/test_tutorial002.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py index acd910c71..45f4e57bb 100644 --- a/tests/test_tutorial/test_security/test_tutorial003.py +++ b/tests/test_tutorial/test_security/test_tutorial003.py @@ -10,9 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), - pytest.param("tutorial003_an_py39"), pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_security/test_tutorial004.py b/tests/test_tutorial/test_security/test_tutorial004.py index e842c92a1..e52a029bd 100644 --- a/tests/test_tutorial/test_security/test_tutorial004.py +++ b/tests/test_tutorial/test_security/test_tutorial004.py @@ -12,9 +12,7 @@ from ...utils import needs_py310 @pytest.fixture( name="mod", params=[ - pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), - pytest.param("tutorial004_an_py39"), pytest.param("tutorial004_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_security/test_tutorial005.py b/tests/test_tutorial/test_security/test_tutorial005.py index 76b08860f..c6e0e78e0 100644 --- a/tests/test_tutorial/test_security/test_tutorial005.py +++ b/tests/test_tutorial/test_security/test_tutorial005.py @@ -11,9 +11,7 @@ from ...utils import needs_py310 @pytest.fixture( name="mod", params=[ - pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), - pytest.param("tutorial005_an_py39"), pytest.param("tutorial005_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_security/test_tutorial006.py b/tests/test_tutorial/test_security/test_tutorial006.py index 1c96f62ae..671946d97 100644 --- a/tests/test_tutorial/test_security/test_tutorial006.py +++ b/tests/test_tutorial/test_security/test_tutorial006.py @@ -9,8 +9,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial006_py39"), - pytest.param("tutorial006_an_py39"), + pytest.param("tutorial006_py310"), + pytest.param("tutorial006_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_security/test_tutorial007.py b/tests/test_tutorial/test_security/test_tutorial007.py index 24667422f..e73545753 100644 --- a/tests/test_tutorial/test_security/test_tutorial007.py +++ b/tests/test_tutorial/test_security/test_tutorial007.py @@ -9,8 +9,8 @@ from inline_snapshot import snapshot @pytest.fixture( name="client", params=[ - pytest.param("tutorial007_py39"), - pytest.param("tutorial007_an_py39"), + pytest.param("tutorial007_py310"), + pytest.param("tutorial007_an_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py index 0be3b59f9..653b20c7d 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py index 87c63f69e..0ef706a4d 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py @@ -10,7 +10,6 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_settings/test_app01.py b/tests/test_tutorial/test_settings/test_app01.py index 52b9b1e62..da27308e8 100644 --- a/tests/test_tutorial/test_settings/test_app01.py +++ b/tests/test_tutorial/test_settings/test_app01.py @@ -12,7 +12,7 @@ from pytest import MonkeyPatch @pytest.fixture( name="mod_name", params=[ - pytest.param("app01_py39"), + pytest.param("app01_py310"), ], ) def get_mod_name(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_settings/test_app02.py b/tests/test_tutorial/test_settings/test_app02.py index 9cbed4fd1..20594ebb7 100644 --- a/tests/test_tutorial/test_settings/test_app02.py +++ b/tests/test_tutorial/test_settings/test_app02.py @@ -8,8 +8,8 @@ from pytest import MonkeyPatch @pytest.fixture( name="mod_path", params=[ - pytest.param("app02_py39"), - pytest.param("app02_an_py39"), + pytest.param("app02_py310"), + pytest.param("app02_an_py310"), ], ) def get_mod_path(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_settings/test_app03.py b/tests/test_tutorial/test_settings/test_app03.py index 72de49796..f4539c96d 100644 --- a/tests/test_tutorial/test_settings/test_app03.py +++ b/tests/test_tutorial/test_settings/test_app03.py @@ -9,8 +9,8 @@ from pytest import MonkeyPatch @pytest.fixture( name="mod_path", params=[ - pytest.param("app03_py39"), - pytest.param("app03_an_py39"), + pytest.param("app03_py310"), + pytest.param("app03_an_py310"), ], ) def get_mod_path(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_settings/test_tutorial001.py b/tests/test_tutorial/test_settings/test_tutorial001.py index f4576a0d2..8857c35df 100644 --- a/tests/test_tutorial/test_settings/test_tutorial001.py +++ b/tests/test_tutorial/test_settings/test_tutorial001.py @@ -5,7 +5,7 @@ from fastapi.testclient import TestClient from pytest import MonkeyPatch -@pytest.fixture(name="app", params=[pytest.param("tutorial001_py39")]) +@pytest.fixture(name="app", params=[pytest.param("tutorial001_py310")]) def get_app(request: pytest.FixtureRequest, monkeypatch: MonkeyPatch): monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") mod = importlib.import_module(f"docs_src.settings.{request.param}") diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_sql_databases/test_tutorial001.py index aec20e42e..3407ec758 100644 --- a/tests/test_tutorial/test_sql_databases/test_tutorial001.py +++ b/tests/test_tutorial/test_sql_databases/test_tutorial001.py @@ -22,9 +22,7 @@ def clear_sqlmodel(): @pytest.fixture( name="client", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial002.py b/tests/test_tutorial/test_sql_databases/test_tutorial002.py index 4ea7d5f64..51a36b6c7 100644 --- a/tests/test_tutorial/test_sql_databases/test_tutorial002.py +++ b/tests/test_tutorial/test_sql_databases/test_tutorial002.py @@ -22,9 +22,7 @@ def clear_sqlmodel(): @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_static_files/test_tutorial001.py b/tests/test_tutorial/test_static_files/test_tutorial001.py index dd93fee79..1d57310f3 100644 --- a/tests/test_tutorial/test_static_files/test_tutorial001.py +++ b/tests/test_tutorial/test_static_files/test_tutorial001.py @@ -12,7 +12,7 @@ def client(): static_dir.mkdir(exist_ok=True) sample_file = static_dir / "sample.txt" sample_file.write_text("This is a sample static file.") - from docs_src.static_files.tutorial001_py39 import app + from docs_src.static_files.tutorial001_py310 import app with TestClient(app) as client: yield client diff --git a/tests/test_tutorial/test_sub_applications/test_tutorial001.py b/tests/test_tutorial/test_sub_applications/test_tutorial001.py index a8fb3dc36..63fb68fb2 100644 --- a/tests/test_tutorial/test_sub_applications/test_tutorial001.py +++ b/tests/test_tutorial/test_sub_applications/test_tutorial001.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.sub_applications.tutorial001_py39 import app +from docs_src.sub_applications.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_templates/test_tutorial001.py b/tests/test_tutorial/test_templates/test_tutorial001.py index 818508037..04bc3fce8 100644 --- a/tests/test_tutorial/test_templates/test_tutorial001.py +++ b/tests/test_tutorial/test_templates/test_tutorial001.py @@ -11,7 +11,7 @@ def test_main(): shutil.rmtree("./templates") shutil.copytree("./docs_src/templates/templates/", "./templates") shutil.copytree("./docs_src/templates/static/", "./static") - from docs_src.templates.tutorial001_py39 import app + from docs_src.templates.tutorial001_py310 import app client = TestClient(app) response = client.get("/items/foo") diff --git a/tests/test_tutorial/test_testing/test_main_a.py b/tests/test_tutorial/test_testing/test_main_a.py index 528488805..d071d5309 100644 --- a/tests/test_tutorial/test_testing/test_main_a.py +++ b/tests/test_tutorial/test_testing/test_main_a.py @@ -1,6 +1,6 @@ from inline_snapshot import snapshot -from docs_src.app_testing.app_a_py39.test_main import client, test_read_main +from docs_src.app_testing.app_a_py310.test_main import client, test_read_main def test_main(): diff --git a/tests/test_tutorial/test_testing/test_main_b.py b/tests/test_tutorial/test_testing/test_main_b.py index 3d679cd5a..2d042fd69 100644 --- a/tests/test_tutorial/test_testing/test_main_b.py +++ b/tests/test_tutorial/test_testing/test_main_b.py @@ -9,9 +9,7 @@ from ...utils import needs_py310 @pytest.fixture( name="test_module", params=[ - "app_b_py39.test_main", pytest.param("app_b_py310.test_main", marks=needs_py310), - "app_b_an_py39.test_main", pytest.param("app_b_an_py310.test_main", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_testing/test_tutorial001.py b/tests/test_tutorial/test_testing/test_tutorial001.py index 2b501d36a..b9bb182b7 100644 --- a/tests/test_tutorial/test_testing/test_tutorial001.py +++ b/tests/test_tutorial/test_testing/test_tutorial001.py @@ -1,6 +1,6 @@ from inline_snapshot import snapshot -from docs_src.app_testing.tutorial001_py39 import client, test_read_main +from docs_src.app_testing.tutorial001_py310 import client, test_read_main def test_main(): diff --git a/tests/test_tutorial/test_testing/test_tutorial002.py b/tests/test_tutorial/test_testing/test_tutorial002.py index cc9b5ba27..840248dc3 100644 --- a/tests/test_tutorial/test_testing/test_tutorial002.py +++ b/tests/test_tutorial/test_testing/test_tutorial002.py @@ -1,4 +1,4 @@ -from docs_src.app_testing.tutorial002_py39 import test_read_main, test_websocket +from docs_src.app_testing.tutorial002_py310 import test_read_main, test_websocket def test_main(): diff --git a/tests/test_tutorial/test_testing/test_tutorial003.py b/tests/test_tutorial/test_testing/test_tutorial003.py index 4faa820e9..196fc4555 100644 --- a/tests/test_tutorial/test_testing/test_tutorial003.py +++ b/tests/test_tutorial/test_testing/test_tutorial003.py @@ -3,5 +3,5 @@ import pytest def test_main(): with pytest.warns(DeprecationWarning): - from docs_src.app_testing.tutorial003_py39 import test_read_items + from docs_src.app_testing.tutorial003_py310 import test_read_items test_read_items() diff --git a/tests/test_tutorial/test_testing/test_tutorial004.py b/tests/test_tutorial/test_testing/test_tutorial004.py index c95214ffe..d5de1bb60 100644 --- a/tests/test_tutorial/test_testing/test_tutorial004.py +++ b/tests/test_tutorial/test_testing/test_tutorial004.py @@ -1,4 +1,4 @@ -from docs_src.app_testing.tutorial004_py39 import test_read_items +from docs_src.app_testing.tutorial004_py310 import test_read_items def test_main(): diff --git a/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py b/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py index 6e9656bf5..81491276e 100644 --- a/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py +++ b/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py @@ -9,9 +9,7 @@ from ...utils import needs_py310 @pytest.fixture( name="test_module", params=[ - pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_using_request_directly/test_tutorial001.py b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py index cb8ae8b05..4145177e4 100644 --- a/tests/test_tutorial/test_using_request_directly/test_tutorial001.py +++ b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py @@ -1,7 +1,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.using_request_directly.tutorial001_py39 import app +from docs_src.using_request_directly.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_websockets/test_tutorial001.py b/tests/test_tutorial/test_websockets/test_tutorial001.py index 4f8368db2..6f3bf1429 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial001.py +++ b/tests/test_tutorial/test_websockets/test_tutorial001.py @@ -2,7 +2,7 @@ import pytest from fastapi.testclient import TestClient from fastapi.websockets import WebSocketDisconnect -from docs_src.websockets.tutorial001_py39 import app +from docs_src.websockets.tutorial001_py310 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_websockets/test_tutorial002.py b/tests/test_tutorial/test_websockets/test_tutorial002.py index ebf1fc8e8..0509b0d0d 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial002.py +++ b/tests/test_tutorial/test_websockets/test_tutorial002.py @@ -11,9 +11,7 @@ from ...utils import needs_py310 @pytest.fixture( name="app", params=[ - pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_websockets/test_tutorial003.py b/tests/test_tutorial/test_websockets/test_tutorial003.py index 0be1fc81d..781146371 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial003.py +++ b/tests/test_tutorial/test_websockets/test_tutorial003.py @@ -8,7 +8,7 @@ from fastapi.testclient import TestClient @pytest.fixture( name="mod", params=[ - pytest.param("tutorial003_py39"), + pytest.param("tutorial003_py310"), ], ) def get_mod(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_wsgi/test_tutorial001.py b/tests/test_tutorial/test_wsgi/test_tutorial001.py index 9fe8c2a4b..9bcd56dfc 100644 --- a/tests/test_tutorial/test_wsgi/test_tutorial001.py +++ b/tests/test_tutorial/test_wsgi/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.wsgi.tutorial001_py39 import app +from docs_src.wsgi.tutorial001_py310 import app client = TestClient(app) From 0e460654af7101866b7d0f22735ef6337fe13d0c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Feb 2026 13:20:10 +0000 Subject: [PATCH 104/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7640bd6fa..52cc3836a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,10 @@ hide: * 🎨 Update internal types for Python 3.10. PR [#14898](https://github.com/fastapi/fastapi/pull/14898) by [@tiangolo](https://github.com/tiangolo). +### Docs + +* 📝 Update source examples and docs from Python 3.9 to 3.10. PR [#14900](https://github.com/fastapi/fastapi/pull/14900) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.8 ### Docs From c82a3d8a13af58e092ba95ffb4b8a64b9ca82726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 12 Feb 2026 05:24:28 -0800 Subject: [PATCH 105/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20highlights=20in?= =?UTF-8?q?=20webhooks=20docs=20(#14905)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/openapi-webhooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/advanced/openapi-webhooks.md b/docs/en/docs/advanced/openapi-webhooks.md index f4f586f84..d9b73ea4a 100644 --- a/docs/en/docs/advanced/openapi-webhooks.md +++ b/docs/en/docs/advanced/openapi-webhooks.md @@ -32,7 +32,7 @@ Webhooks are available in OpenAPI 3.1.0 and above, supported by FastAPI `0.99.0` When you create a **FastAPI** application, there is a `webhooks` attribute that you can use to define *webhooks*, the same way you would define *path operations*, for example with `@app.webhooks.post()`. -{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} The webhooks that you define will end up in the **OpenAPI** schema and the automatic **docs UI**. From 109cc8aff126b60971ec27d68d32b8713f0ec72a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Feb 2026 13:24:54 +0000 Subject: [PATCH 106/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 52cc3836a..e3804c6e3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -17,6 +17,7 @@ hide: ### Docs +* 📝 Update highlights in webhooks docs. PR [#14905](https://github.com/fastapi/fastapi/pull/14905) by [@tiangolo](https://github.com/tiangolo). * 📝 Update source examples and docs from Python 3.9 to 3.10. PR [#14900](https://github.com/fastapi/fastapi/pull/14900) by [@tiangolo](https://github.com/tiangolo). ## 0.128.8 From 31d9750aceaff347bd55be37880b7f36bc26c1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 12 Feb 2026 05:31:33 -0800 Subject: [PATCH 107/195] =?UTF-8?q?=F0=9F=94=A8=20Update=20docs.py=20scrip?= =?UTF-8?q?ts=20to=20migrate=20Python=203.9=20to=20Python=203.10=20(#14906?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/docs.py | 211 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 206 insertions(+), 5 deletions(-) diff --git a/scripts/docs.py b/scripts/docs.py index 3cf62f084..23d74aaf4 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -8,7 +8,7 @@ from html.parser import HTMLParser from http.server import HTTPServer, SimpleHTTPRequestHandler from multiprocessing import Pool from pathlib import Path -from typing import Any, Optional, Union +from typing import Any import mkdocs.utils import typer @@ -103,7 +103,7 @@ def get_lang_paths() -> list[Path]: return sorted(docs_path.iterdir()) -def lang_callback(lang: Optional[str]) -> Union[str, None]: +def lang_callback(lang: str | None) -> str | None: if lang is None: return None lang = lang.lower() @@ -412,6 +412,13 @@ def langs_json(): @app.command() def generate_docs_src_versions_for_file(file_path: Path) -> None: target_versions = ["py39", "py310"] + full_path_str = str(file_path) + for target_version in target_versions: + if f"_{target_version}" in full_path_str: + logging.info( + f"Skipping {file_path}, already a version file for {target_version}" + ) + return base_content = file_path.read_text(encoding="utf-8") previous_content = {base_content} for target_version in target_versions: @@ -438,13 +445,207 @@ def generate_docs_src_versions_for_file(file_path: Path) -> None: if content_format in previous_content: continue previous_content.add(content_format) - version_file = file_path.with_name( - file_path.name.replace(".py", f"_{target_version}.py") - ) + # Determine where the version label should go: in the parent directory + # name or in the file name, matching the source structure. + label_in_parent = False + for v in target_versions: + if f"_{v}" in file_path.parent.name: + label_in_parent = True + break + if label_in_parent: + parent_name = file_path.parent.name + for v in target_versions: + parent_name = parent_name.replace(f"_{v}", "") + new_parent = file_path.parent.parent / f"{parent_name}_{target_version}" + new_parent.mkdir(parents=True, exist_ok=True) + version_file = new_parent / file_path.name + else: + base_name = file_path.stem + for v in target_versions: + if base_name.endswith(f"_{v}"): + base_name = base_name[: -len(f"_{v}")] + break + version_file = file_path.with_name(f"{base_name}_{target_version}.py") logging.info(f"Writing to {version_file}") version_file.write_text(content_format, encoding="utf-8") +@app.command() +def generate_docs_src_versions() -> None: + """ + Generate Python version-specific files for all .py files in docs_src. + """ + docs_src_path = Path("docs_src") + for py_file in sorted(docs_src_path.rglob("*.py")): + generate_docs_src_versions_for_file(py_file) + + +@app.command() +def copy_py39_to_py310() -> None: + """ + For each docs_src file/directory with a _py39 label that has no _py310 + counterpart, copy it with the _py310 label. + """ + docs_src_path = Path("docs_src") + # Handle directory-level labels (e.g. app_b_an_py39/) + for dir_path in sorted(docs_src_path.rglob("*_py39")): + if not dir_path.is_dir(): + continue + py310_dir = dir_path.parent / dir_path.name.replace("_py39", "_py310") + if py310_dir.exists(): + continue + logging.info(f"Copying directory {dir_path} -> {py310_dir}") + shutil.copytree(dir_path, py310_dir) + # Handle file-level labels (e.g. tutorial001_py39.py) + for file_path in sorted(docs_src_path.rglob("*_py39.py")): + if not file_path.is_file(): + continue + # Skip files inside _py39 directories (already handled above) + if "_py39" in file_path.parent.name: + continue + py310_file = file_path.with_name( + file_path.name.replace("_py39.py", "_py310.py") + ) + if py310_file.exists(): + continue + logging.info(f"Copying file {file_path} -> {py310_file}") + shutil.copy2(file_path, py310_file) + + +@app.command() +def update_docs_includes_py39_to_py310() -> None: + """ + Update .md files in docs/en/ to replace _py39 includes with _py310 versions. + + For each include line referencing a _py39 file or directory in docs_src, replace + the _py39 label with _py310. + """ + include_pattern = re.compile(r"\{[^}]*docs_src/[^}]*_py39[^}]*\.py[^}]*\}") + count = 0 + for md_file in sorted(en_docs_path.rglob("*.md")): + content = md_file.read_text(encoding="utf-8") + if "_py39" not in content: + continue + new_content = include_pattern.sub( + lambda m: m.group(0).replace("_py39", "_py310"), content + ) + if new_content != content: + md_file.write_text(new_content, encoding="utf-8") + count += 1 + logging.info(f"Updated includes in {md_file}") + print(f"Updated {count} file(s) ✅") + + +@app.command() +def remove_unused_docs_src() -> None: + """ + Delete .py files in docs_src that are not included in any .md file under docs/. + """ + docs_src_path = Path("docs_src") + # Collect all docs .md content referencing docs_src + all_docs_content = "" + for md_file in docs_path.rglob("*.md"): + all_docs_content += md_file.read_text(encoding="utf-8") + # Build a set of directory-based package roots (e.g. docs_src/bigger_applications/app_py39) + # where at least one file is referenced in docs. All files in these directories + # should be kept since they may be internally imported by the referenced files. + used_package_dirs: set[Path] = set() + for py_file in docs_src_path.rglob("*.py"): + if py_file.name == "__init__.py": + continue + rel_path = str(py_file) + if rel_path in all_docs_content: + # Walk up from the file's parent to find the package root + # (a subdirectory under docs_src//) + parts = py_file.relative_to(docs_src_path).parts + if len(parts) > 2: + # File is inside a sub-package like docs_src/topic/app_xxx/... + package_root = docs_src_path / parts[0] / parts[1] + used_package_dirs.add(package_root) + removed = 0 + for py_file in sorted(docs_src_path.rglob("*.py")): + if py_file.name == "__init__.py": + continue + # Build the relative path as it appears in includes (e.g. docs_src/first_steps/tutorial001.py) + rel_path = str(py_file) + if rel_path in all_docs_content: + continue + # If this file is inside a directory-based package where any sibling is + # referenced, keep it (it's likely imported internally). + parts = py_file.relative_to(docs_src_path).parts + if len(parts) > 2: + package_root = docs_src_path / parts[0] / parts[1] + if package_root in used_package_dirs: + continue + # Check if the _an counterpart (or non-_an counterpart) is referenced. + # If either variant is included, keep both. + # Handle both file-level _an (tutorial001_an.py) and directory-level _an + # (app_an/main.py) + counterpart_found = False + full_path_str = str(py_file) + if "_an" in py_file.stem: + # This is an _an file, check if the non-_an version is referenced + counterpart = full_path_str.replace( + f"/{py_file.stem}", f"/{py_file.stem.replace('_an', '', 1)}" + ) + if counterpart in all_docs_content: + counterpart_found = True + else: + # This is a non-_an file, check if there's an _an version referenced + # Insert _an before any version suffix or at the end of the stem + stem = py_file.stem + for suffix in ("_py39", "_py310"): + if suffix in stem: + an_stem = stem.replace(suffix, f"_an{suffix}", 1) + break + else: + an_stem = f"{stem}_an" + counterpart = full_path_str.replace(f"/{stem}.", f"/{an_stem}.") + if counterpart in all_docs_content: + counterpart_found = True + # Also check directory-level _an counterparts + if not counterpart_found: + parent_name = py_file.parent.name + if "_an" in parent_name: + counterpart_parent = parent_name.replace("_an", "", 1) + counterpart_dir = str(py_file).replace( + f"/{parent_name}/", f"/{counterpart_parent}/" + ) + if counterpart_dir in all_docs_content: + counterpart_found = True + else: + # Try inserting _an into parent directory name + for suffix in ("_py39", "_py310"): + if suffix in parent_name: + an_parent = parent_name.replace(suffix, f"_an{suffix}", 1) + break + else: + an_parent = f"{parent_name}_an" + counterpart_dir = str(py_file).replace( + f"/{parent_name}/", f"/{an_parent}/" + ) + if counterpart_dir in all_docs_content: + counterpart_found = True + if counterpart_found: + continue + logging.info(f"Removing unused file: {py_file}") + py_file.unlink() + removed += 1 + # Clean up directories that are empty or only contain __init__.py / __pycache__ + for dir_path in sorted(docs_src_path.rglob("*"), reverse=True): + if not dir_path.is_dir(): + continue + remaining = [ + f + for f in dir_path.iterdir() + if f.name != "__pycache__" and f.name != "__init__.py" + ] + if not remaining: + logging.info(f"Removing empty/init-only directory: {dir_path}") + shutil.rmtree(dir_path) + print(f"Removed {removed} unused file(s) ✅") + + @app.command() def add_permalinks_page(path: Path, update_existing: bool = False): """ From b7ce02ae86ee466380d2db09be008cfe83a1ca35 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Feb 2026 13:32:02 +0000 Subject: [PATCH 108/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e3804c6e3..a428a4d86 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -20,6 +20,10 @@ hide: * 📝 Update highlights in webhooks docs. PR [#14905](https://github.com/fastapi/fastapi/pull/14905) by [@tiangolo](https://github.com/tiangolo). * 📝 Update source examples and docs from Python 3.9 to 3.10. PR [#14900](https://github.com/fastapi/fastapi/pull/14900) by [@tiangolo](https://github.com/tiangolo). +### Internal + +* 🔨 Update docs.py scripts to migrate Python 3.9 to Python 3.10. PR [#14906](https://github.com/fastapi/fastapi/pull/14906) by [@tiangolo](https://github.com/tiangolo). + ## 0.128.8 ### Docs From a2e51363c721ee6fccb148bff95ca328b4efdfed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 12 Feb 2026 14:52:27 +0100 Subject: [PATCH 109/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?9.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a428a4d86..e85ed4b3e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.129.0 + ### Breaking Changes * ➖ Drop support for Python 3.9. PR [#14897](https://github.com/fastapi/fastapi/pull/14897) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 9931216ee..de5a0be38 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.128.8" +__version__ = "0.129.0" from starlette import status as status From d11f820ac38bdea38e50c9ede001a6c9b57eac6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 12 Feb 2026 10:10:35 -0800 Subject: [PATCH 110/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20for=20JW?= =?UTF-8?q?T=20to=20prevent=20timing=20attacks=20(#14908)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/tutorial/security/oauth2-jwt.md | 12 ++++++++---- docs_src/security/tutorial004_an_py310.py | 3 +++ docs_src/security/tutorial004_py310.py | 3 +++ docs_src/security/tutorial005_an_py310.py | 3 +++ docs_src/security/tutorial005_py310.py | 3 +++ 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/en/docs/tutorial/security/oauth2-jwt.md b/docs/en/docs/tutorial/security/oauth2-jwt.md index 95baf871c..26894ab28 100644 --- a/docs/en/docs/tutorial/security/oauth2-jwt.md +++ b/docs/en/docs/tutorial/security/oauth2-jwt.md @@ -116,7 +116,11 @@ And another utility to verify if a received password matches the hash stored. And another one to authenticate and return a user. -{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +When `authenticate_user` is called with a username that doesn't exist in the database, we still run `verify_password` against a dummy hash. + +This ensures the endpoint takes roughly the same amount of time to respond whether the username is valid or not, preventing **timing attacks** that could be used to enumerate existing usernames. /// note @@ -152,7 +156,7 @@ Define a Pydantic Model that will be used in the token endpoint for the response Create a utility function to generate a new access token. -{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} ## Update the dependencies { #update-the-dependencies } @@ -162,7 +166,7 @@ Decode the received token, verify it, and return the current user. If the token is invalid, return an HTTP error right away. -{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} ## Update the `/token` *path operation* { #update-the-token-path-operation } @@ -170,7 +174,7 @@ Create a `timedelta` with the expiration time of the token. Create a real JWT access token and return it. -{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} ### Technical details about the JWT "subject" `sub` { #technical-details-about-the-jwt-subject-sub } diff --git a/docs_src/security/tutorial004_an_py310.py b/docs_src/security/tutorial004_an_py310.py index 368c743bf..685cb034e 100644 --- a/docs_src/security/tutorial004_an_py310.py +++ b/docs_src/security/tutorial004_an_py310.py @@ -48,6 +48,8 @@ class UserInDB(User): password_hash = PasswordHash.recommended() +DUMMY_HASH = password_hash.hash("dummypassword") + oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") app = FastAPI() @@ -70,6 +72,7 @@ def get_user(db, username: str): def authenticate_user(fake_db, username: str, password: str): user = get_user(fake_db, username) if not user: + verify_password(password, DUMMY_HASH) return False if not verify_password(password, user.hashed_password): return False diff --git a/docs_src/security/tutorial004_py310.py b/docs_src/security/tutorial004_py310.py index 8d0785b40..dc7f1c9e2 100644 --- a/docs_src/security/tutorial004_py310.py +++ b/docs_src/security/tutorial004_py310.py @@ -47,6 +47,8 @@ class UserInDB(User): password_hash = PasswordHash.recommended() +DUMMY_HASH = password_hash.hash("dummypassword") + oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") app = FastAPI() @@ -69,6 +71,7 @@ def get_user(db, username: str): def authenticate_user(fake_db, username: str, password: str): user = get_user(fake_db, username) if not user: + verify_password(password, DUMMY_HASH) return False if not verify_password(password, user.hashed_password): return False diff --git a/docs_src/security/tutorial005_an_py310.py b/docs_src/security/tutorial005_an_py310.py index fef0ab71c..9911723db 100644 --- a/docs_src/security/tutorial005_an_py310.py +++ b/docs_src/security/tutorial005_an_py310.py @@ -60,6 +60,8 @@ class UserInDB(User): password_hash = PasswordHash.recommended() +DUMMY_HASH = password_hash.hash("dummypassword") + oauth2_scheme = OAuth2PasswordBearer( tokenUrl="token", scopes={"me": "Read information about the current user.", "items": "Read items."}, @@ -85,6 +87,7 @@ def get_user(db, username: str): def authenticate_user(fake_db, username: str, password: str): user = get_user(fake_db, username) if not user: + verify_password(password, DUMMY_HASH) return False if not verify_password(password, user.hashed_password): return False diff --git a/docs_src/security/tutorial005_py310.py b/docs_src/security/tutorial005_py310.py index 412fbf798..710cdac32 100644 --- a/docs_src/security/tutorial005_py310.py +++ b/docs_src/security/tutorial005_py310.py @@ -59,6 +59,8 @@ class UserInDB(User): password_hash = PasswordHash.recommended() +DUMMY_HASH = password_hash.hash("dummypassword") + oauth2_scheme = OAuth2PasswordBearer( tokenUrl="token", scopes={"me": "Read information about the current user.", "items": "Read items."}, @@ -84,6 +86,7 @@ def get_user(db, username: str): def authenticate_user(fake_db, username: str, password: str): user = get_user(fake_db, username) if not user: + verify_password(password, DUMMY_HASH) return False if not verify_password(password, user.hashed_password): return False From c206f19b5d39fe9fbf90b67229290b41f8fabeec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Feb 2026 18:10:57 +0000 Subject: [PATCH 111/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e85ed4b3e..d8bc3a26f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Docs + +* 📝 Update docs for JWT to prevent timing attacks. PR [#14908](https://github.com/fastapi/fastapi/pull/14908) by [@tiangolo](https://github.com/tiangolo). + ## 0.129.0 ### Breaking Changes From 55a9eee13a7c228b358bd110976b3aeab3185fe2 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:57:34 +0100 Subject: [PATCH 112/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20ru=20(update-outdated)=20(#14909)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all * Reflect latest changes in `docs/en/docs/tutorial/security/oauth2-jwt.md` --- docs/ru/docs/advanced/additional-responses.md | 4 +- .../ru/docs/advanced/advanced-dependencies.md | 8 +- .../ru/docs/advanced/advanced-python-types.md | 61 ++++++ docs/ru/docs/advanced/async-tests.md | 10 +- docs/ru/docs/advanced/behind-a-proxy.md | 22 +- docs/ru/docs/advanced/custom-response.md | 36 ++-- docs/ru/docs/advanced/dataclasses.md | 2 +- docs/ru/docs/advanced/events.md | 12 +- docs/ru/docs/advanced/generate-clients.md | 12 +- docs/ru/docs/advanced/middleware.md | 6 +- docs/ru/docs/advanced/openapi-webhooks.md | 6 +- .../path-operation-advanced-configuration.md | 16 +- .../advanced/response-change-status-code.md | 2 +- docs/ru/docs/advanced/response-cookies.md | 4 +- docs/ru/docs/advanced/response-directly.md | 2 +- docs/ru/docs/advanced/response-headers.md | 4 +- .../docs/advanced/security/http-basic-auth.md | 6 +- docs/ru/docs/advanced/settings.md | 20 +- docs/ru/docs/advanced/sub-applications.md | 6 +- docs/ru/docs/advanced/templates.md | 2 +- docs/ru/docs/advanced/testing-events.md | 4 +- docs/ru/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/ru/docs/advanced/websockets.md | 8 +- docs/ru/docs/advanced/wsgi.md | 2 +- docs/ru/docs/alternatives.md | 12 +- docs/ru/docs/async.md | 12 +- docs/ru/docs/benchmarks.md | 10 +- docs/ru/docs/deployment/concepts.md | 10 +- docs/ru/docs/deployment/docker.md | 20 +- docs/ru/docs/deployment/https.md | 2 +- docs/ru/docs/deployment/index.md | 2 +- docs/ru/docs/deployment/versions.md | 4 +- docs/ru/docs/environment-variables.md | 2 +- docs/ru/docs/fastapi-cli.md | 2 +- docs/ru/docs/features.md | 24 +-- docs/ru/docs/history-design-future.md | 8 +- .../authentication-error-status-code.md | 2 +- docs/ru/docs/how-to/conditional-openapi.md | 6 +- docs/ru/docs/how-to/configure-swagger-ui.md | 6 +- docs/ru/docs/how-to/custom-docs-ui-assets.md | 14 +- docs/ru/docs/how-to/extending-openapi.md | 12 +- docs/ru/docs/how-to/graphql.md | 2 +- docs/ru/docs/how-to/index.md | 6 +- docs/ru/docs/index.md | 12 +- docs/ru/docs/project-generation.md | 2 +- docs/ru/docs/python-types.md | 190 ++++-------------- docs/ru/docs/tutorial/background-tasks.md | 10 +- docs/ru/docs/tutorial/bigger-applications.md | 26 +-- docs/ru/docs/tutorial/body-multiple-params.md | 8 +- docs/ru/docs/tutorial/body-nested-models.md | 4 +- docs/ru/docs/tutorial/body.md | 6 +- docs/ru/docs/tutorial/cookie-param-models.md | 8 +- docs/ru/docs/tutorial/cookie-params.md | 6 +- docs/ru/docs/tutorial/cors.md | 4 +- docs/ru/docs/tutorial/debugging.md | 8 +- .../dependencies/classes-as-dependencies.md | 28 +-- ...pendencies-in-path-operation-decorators.md | 12 +- .../dependencies/dependencies-with-yield.md | 24 +-- .../dependencies/global-dependencies.md | 4 +- docs/ru/docs/tutorial/dependencies/index.md | 2 +- .../tutorial/dependencies/sub-dependencies.md | 6 +- docs/ru/docs/tutorial/encoder.md | 4 +- docs/ru/docs/tutorial/extra-models.md | 8 +- docs/ru/docs/tutorial/first-steps.md | 16 +- docs/ru/docs/tutorial/handling-errors.md | 20 +- docs/ru/docs/tutorial/metadata.md | 12 +- docs/ru/docs/tutorial/middleware.md | 32 ++- .../tutorial/path-operation-configuration.md | 10 +- .../path-params-numeric-validations.md | 16 +- docs/ru/docs/tutorial/path-params.md | 30 +-- docs/ru/docs/tutorial/query-param-models.md | 2 +- .../tutorial/query-params-str-validations.md | 42 +--- docs/ru/docs/tutorial/query-params.md | 8 +- docs/ru/docs/tutorial/request-files.md | 14 +- docs/ru/docs/tutorial/request-form-models.md | 4 +- .../docs/tutorial/request-forms-and-files.md | 4 +- docs/ru/docs/tutorial/request-forms.md | 10 +- docs/ru/docs/tutorial/response-model.md | 6 +- docs/ru/docs/tutorial/response-status-code.md | 10 +- docs/ru/docs/tutorial/schema-extra-example.md | 2 +- docs/ru/docs/tutorial/security/first-steps.md | 6 +- .../tutorial/security/get-current-user.md | 2 +- docs/ru/docs/tutorial/security/index.md | 84 ++++---- docs/ru/docs/tutorial/security/oauth2-jwt.md | 28 ++- .../docs/tutorial/security/simple-oauth2.md | 2 +- docs/ru/docs/tutorial/sql-databases.md | 6 +- docs/ru/docs/tutorial/static-files.md | 2 +- docs/ru/docs/tutorial/testing.md | 10 +- docs/ru/docs/virtual-environments.md | 4 +- 90 files changed, 549 insertions(+), 620 deletions(-) create mode 100644 docs/ru/docs/advanced/advanced-python-types.md diff --git a/docs/ru/docs/advanced/additional-responses.md b/docs/ru/docs/advanced/additional-responses.md index fca4f072d..ca36ba20e 100644 --- a/docs/ru/docs/advanced/additional-responses.md +++ b/docs/ru/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ Например, чтобы объявить ещё один ответ со статус-кодом `404` и Pydantic-моделью `Message`, можно написать: -{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} /// note | Примечание @@ -203,7 +203,7 @@ А также ответ со статус-кодом `200`, который использует ваш `response_model`, но включает пользовательский `example`: -{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} Всё это будет объединено и включено в ваш OpenAPI и отображено в документации API: diff --git a/docs/ru/docs/advanced/advanced-dependencies.md b/docs/ru/docs/advanced/advanced-dependencies.md index fb2643cd5..686a0cf91 100644 --- a/docs/ru/docs/advanced/advanced-dependencies.md +++ b/docs/ru/docs/advanced/advanced-dependencies.md @@ -18,7 +18,7 @@ Для этого объявляем метод `__call__`: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} В этом случае именно `__call__` **FastAPI** использует для проверки дополнительных параметров и подзависимостей, и именно он будет вызван, чтобы позже передать значение параметру в вашей *функции-обработчике пути*. @@ -26,7 +26,7 @@ Теперь мы можем использовать `__init__`, чтобы объявить параметры экземпляра, с помощью которых будем «параметризовать» зависимость: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} В этом случае **FastAPI** вовсе не трогает `__init__` и не зависит от него — мы используем его напрямую в нашем коде. @@ -34,7 +34,7 @@ Мы можем создать экземпляр этого класса так: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} Так мы «параметризуем» нашу зависимость: теперь внутри неё хранится "bar" в атрибуте `checker.fixed_content`. @@ -50,7 +50,7 @@ checker(q="somequery") …и передаст возвращённое значение как значение зависимости в параметр `fixed_content_included` нашей *функции-обработчика пути*: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} /// tip | Совет diff --git a/docs/ru/docs/advanced/advanced-python-types.md b/docs/ru/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..62dcf8c4f --- /dev/null +++ b/docs/ru/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# Продвинутые типы Python { #advanced-python-types } + +Ниже несколько дополнительных идей, которые могут быть полезны при работе с типами Python. + +## Использование `Union` или `Optional` { #using-union-or-optional } + +Если по какой-то причине ваш код не может использовать `|`, например, если это не аннотация типов, а что-то вроде `response_model=`, вместо вертикальной черты (`|`) можно использовать `Union` из `typing`. + +Например, вы можете объявить, что значение может быть `str` или `None`: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +В `typing` также есть сокращение, чтобы объявить, что значение может быть `None`, — `Optional`. + +Вот совет с моей очень субъективной точки зрения: + +- 🚨 Избегайте использования `Optional[SomeType]` +- Вместо этого ✨ используйте **`Union[SomeType, None]`** ✨. + +Оба варианта эквивалентны и под капотом это одно и то же, но я бы рекомендовал `Union` вместо `Optional`, потому что слово «optional» может наводить на мысль, что значение необязательное, тогда как на самом деле это означает «значение может быть `None`», даже если оно не является необязательным и по-прежнему требуется. + +По-моему, `Union[SomeType, None]` более явно передаёт смысл. + +Речь только о словах и названиях. Но эти слова могут влиять на то, как вы и ваша команда думаете о коде. + +В качестве примера возьмём такую функцию: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +Параметр `name` объявлен как `Optional[str]`, но он не является необязательным: вы не можете вызвать функцию без этого параметра: + +```Python +say_hi() # О нет, это вызывает ошибку! 😱 +``` + +Параметр `name` по-прежнему обязателен (не «optional»), так как у него нет значения по умолчанию. При этом `name` принимает `None` в качестве значения: + +```Python +say_hi(name=None) # Это работает, None допустим 🎉 +``` + +Хорошая новость: в большинстве случаев вы сможете просто использовать `|` для объявления объединений типов: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +Так что обычно вам не о чем переживать из‑за названий вроде `Optional` и `Union`. 😎 diff --git a/docs/ru/docs/advanced/async-tests.md b/docs/ru/docs/advanced/async-tests.md index e68970406..52939c255 100644 --- a/docs/ru/docs/advanced/async-tests.md +++ b/docs/ru/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ Файл `main.py`: -{* ../../docs_src/async_tests/app_a_py39/main.py *} +{* ../../docs_src/async_tests/app_a_py310/main.py *} Файл `test_main.py` содержит тесты для `main.py`, теперь он может выглядеть так: -{* ../../docs_src/async_tests/app_a_py39/test_main.py *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} ## Запуск тестов { #run-it } @@ -56,7 +56,7 @@ $ pytest Маркер `@pytest.mark.anyio` говорит pytest, что тестовая функция должна быть вызвана асинхронно: -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} /// tip | Подсказка @@ -66,7 +66,7 @@ $ pytest Затем мы можем создать `AsyncClient` со ссылкой на приложение и посылать асинхронные запросы, используя `await`. -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} Это эквивалентно следующему: @@ -94,6 +94,6 @@ response = client.get('/') /// tip | Подсказка -Если вы столкнулись с `RuntimeError: Task attached to a different loop` при вызове асинхронных функций в ваших тестах (например, при использовании
MongoDB's MotorClient), то не забывайте инициализировать объекты, которым нужен цикл событий (event loop), только внутри асинхронных функций, например, в `'@app.on_event("startup")` callback. +Если вы столкнулись с `RuntimeError: Task attached to a different loop` при вызове асинхронных функций в ваших тестах (например, при использовании MongoDB's MotorClient), то не забывайте инициализировать объекты, которым нужен цикл событий (event loop), только внутри асинхронных функций, например, в `@app.on_event("startup")` callback. /// diff --git a/docs/ru/docs/advanced/behind-a-proxy.md b/docs/ru/docs/advanced/behind-a-proxy.md index f78da01a0..ec75ed369 100644 --- a/docs/ru/docs/advanced/behind-a-proxy.md +++ b/docs/ru/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" Например, вы объявили операцию пути `/items/`: -{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} Если клиент обратится к `/items`, по умолчанию произойдёт редирект на `/items/`. @@ -115,7 +115,7 @@ sequenceDiagram Хотя весь ваш код написан с расчётом, что путь один — `/app`. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} Прокси будет «обрезать» префикс пути на лету перед передачей запроса на сервер приложения (скорее всего Uvicorn, запущенный через FastAPI CLI), поддерживая у вашего приложения иллюзию, что его обслуживают по `/app`, чтобы вам не пришлось менять весь код и добавлять префикс `/api/v1`. @@ -193,7 +193,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 Здесь мы добавляем его в сообщение лишь для демонстрации. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} Затем, если вы запустите Uvicorn так: @@ -220,7 +220,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 Если нет возможности передать опцию командной строки `--root-path` (или аналог), вы можете указать параметр `root_path` при создании приложения FastAPI: -{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} Передача `root_path` в `FastAPI` эквивалентна опции командной строки `--root-path` для Uvicorn или Hypercorn. @@ -241,17 +241,17 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 Uvicorn ожидает, что прокси обратится к нему по `http://127.0.0.1:8000/app`, а уже задача прокси — добавить сверху префикс `/api/v1`. -## О прокси с урезанным префиксом пути { #about-proxies-with-a-stripped-path-prefix } +## О прокси с функцией удаления префикса пути { #about-proxies-with-a-stripped-path-prefix } -Помните, что прокси с урезанным префиксом пути — лишь один из вариантов настройки. +Помните, что прокси с функцией удаления префикса пути — лишь один из вариантов настройки. -Во многих случаях по умолчанию прокси будет без урезанного префикса пути. +Во многих случаях по умолчанию прокси будет без функции удаления префикса пути. -В таком случае (без урезанного префикса) прокси слушает, например, по адресу `https://myawesomeapp.com`, и если браузер идёт на `https://myawesomeapp.com/api/v1/app`, а ваш сервер (например, Uvicorn) слушает на `http://127.0.0.1:8000`, то прокси (без урезанного префикса) обратится к Uvicorn по тому же пути: `http://127.0.0.1:8000/api/v1/app`. +В таком случае (без функции удаления префикса пути) прокси слушает, например, по адресу `https://myawesomeapp.com`, и если браузер идёт на `https://myawesomeapp.com/api/v1/app`, а ваш сервер (например, Uvicorn) слушает на `http://127.0.0.1:8000`, то прокси (без урезанного префикса) обратится к Uvicorn по тому же пути: `http://127.0.0.1:8000/api/v1/app`. ## Локальное тестирование с Traefik { #testing-locally-with-traefik } -Вы можете легко поэкспериментировать локально с урезанным префиксом пути, используя Traefik. +Вы можете легко поэкспериментировать локально с функцией удаления префикса пути, используя Traefik. Скачайте Traefik — это один бинарный файл; распакуйте архив и запустите его прямо из терминала. @@ -400,7 +400,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 Например: -{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} Будет сгенерирована схема OpenAPI примерно такая: @@ -455,7 +455,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 Если вы не хотите, чтобы FastAPI добавлял автоматический сервер, используя `root_path`, укажите параметр `root_path_in_servers=False`: -{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} и тогда этот сервер не будет добавлен в схему OpenAPI. diff --git a/docs/ru/docs/advanced/custom-response.md b/docs/ru/docs/advanced/custom-response.md index 49550b49f..b9f91373d 100644 --- a/docs/ru/docs/advanced/custom-response.md +++ b/docs/ru/docs/advanced/custom-response.md @@ -30,7 +30,7 @@ Но если вы уверены, что содержимое, которое вы возвращаете, **сериализуемо в JSON**, вы можете передать его напрямую в класс ответа и избежать дополнительных накладных расходов, которые FastAPI понёс бы, пропуская возвращаемое содержимое через `jsonable_encoder` перед передачей в класс ответа. -{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} /// info | Информация @@ -55,7 +55,7 @@ - Импортируйте `HTMLResponse`. - Передайте `HTMLResponse` в параметр `response_class` вашего декоратора операции пути. -{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} /// info | Информация @@ -73,17 +73,17 @@ Тот же пример сверху, возвращающий `HTMLResponse`, может выглядеть так: -{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} /// warning | Предупреждение -`Response`, возвращённый напрямую вашей функцией-обработчиком пути, не будет задокументирован в OpenAPI (например, `Content-Type` нне будет задокументирова) и не будет виден в автоматически сгенерированной интерактивной документации. +`Response`, возвращённый напрямую вашей функцией-обработчиком пути, не будет задокументирован в OpenAPI (например, `Content-Type` не будет задокументирован) и не будет виден в автоматически сгенерированной интерактивной документации. /// /// info | Информация -Разумеется, фактические заголовок `Content-Type`, статус-код и т.д. возьмутся из объекта `Response`, который вы вернули. +Разумеется, фактический заголовок `Content-Type`, статус-код и т.д. возьмутся из объекта `Response`, который вы вернули. /// @@ -97,7 +97,7 @@ Например, это может быть что-то вроде: -{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} В этом примере функция `generate_html_response()` уже генерирует и возвращает `Response` вместо возврата HTML в `str`. @@ -136,7 +136,7 @@ FastAPI (фактически Starlette) автоматически добавит заголовок Content-Length. Также будет добавлен заголовок Content-Type, основанный на `media_type` и с добавлением charset для текстовых типов. -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ### `HTMLResponse` { #htmlresponse } @@ -146,7 +146,7 @@ FastAPI (фактически Starlette) автоматически добави Принимает текст или байты и возвращает ответ в виде простого текста. -{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} ### `JSONResponse` { #jsonresponse } @@ -180,7 +180,7 @@ FastAPI (фактически Starlette) автоматически добави /// -{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} /// tip | Совет @@ -194,13 +194,13 @@ FastAPI (фактически Starlette) автоматически добави Вы можете вернуть `RedirectResponse` напрямую: -{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} --- Или можно использовать его в параметре `response_class`: -{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} Если вы сделаете так, то сможете возвращать URL напрямую из своей функции-обработчика пути. @@ -210,13 +210,13 @@ FastAPI (фактически Starlette) автоматически добави Также вы можете использовать параметр `status_code` в сочетании с параметром `response_class`: -{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} ### `StreamingResponse` { #streamingresponse } Принимает асинхронный генератор или обычный генератор/итератор и отправляет тело ответа потоково. -{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} #### Использование `StreamingResponse` с файлоподобными объектами { #using-streamingresponse-with-file-like-objects } @@ -226,7 +226,7 @@ FastAPI (фактически Starlette) автоматически добави Это включает многие библиотеки для работы с облачным хранилищем, обработки видео и т.д. -{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} 1. Это функция-генератор. Она является «функцией-генератором», потому что содержит оператор(ы) `yield` внутри. 2. Используя блок `with`, мы гарантируем, что файлоподобный объект будет закрыт после завершения работы функции-генератора. То есть после того, как она закончит отправку ответа. @@ -255,11 +255,11 @@ FastAPI (фактически Starlette) автоматически добави Файловые ответы будут содержать соответствующие заголовки `Content-Length`, `Last-Modified` и `ETag`. -{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} Вы также можете использовать параметр `response_class`: -{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} В этом случае вы можете возвращать путь к файлу напрямую из своей функции-обработчика пути. @@ -273,7 +273,7 @@ FastAPI (фактически Starlette) автоматически добави Вы могли бы создать `CustomORJSONResponse`. Главное, что вам нужно сделать — реализовать метод `Response.render(content)`, который возвращает содержимое как `bytes`: -{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} Теперь вместо того, чтобы возвращать: @@ -299,7 +299,7 @@ FastAPI (фактически Starlette) автоматически добави В примере ниже **FastAPI** будет использовать `ORJSONResponse` по умолчанию во всех операциях пути вместо `JSONResponse`. -{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} /// tip | Совет diff --git a/docs/ru/docs/advanced/dataclasses.md b/docs/ru/docs/advanced/dataclasses.md index b3ced37c1..87a5763c1 100644 --- a/docs/ru/docs/advanced/dataclasses.md +++ b/docs/ru/docs/advanced/dataclasses.md @@ -64,7 +64,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в 6. Здесь мы возвращаем словарь, содержащий `items`, который является списком dataclass. - FastAPI по-прежнему способен сериализовать данные в JSON. + FastAPI по-прежнему способен сериализовать данные в JSON. 7. Здесь `response_model` использует аннотацию типа — список dataclass `Author`. diff --git a/docs/ru/docs/advanced/events.md b/docs/ru/docs/advanced/events.md index db73d9094..bcb5b000a 100644 --- a/docs/ru/docs/advanced/events.md +++ b/docs/ru/docs/advanced/events.md @@ -30,7 +30,7 @@ Мы создаём асинхронную функцию `lifespan()` с `yield` примерно так: -{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} Здесь мы симулируем дорогую операцию startup по загрузке модели, помещая (фиктивную) функцию модели в словарь с моделями Машинного обучения до `yield`. Этот код будет выполнен до того, как приложение начнет принимать запросы, во время startup. @@ -48,7 +48,7 @@ Первое, на что стоит обратить внимание, — мы определяем асинхронную функцию с `yield`. Это очень похоже на Зависимости с `yield`. -{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} Первая часть функции, до `yield`, будет выполнена до запуска приложения. @@ -60,7 +60,7 @@ Это превращает функцию в «асинхронный менеджер контекста». -{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} Менеджер контекста в Python — это то, что можно использовать в операторе `with`. Например, `open()` можно использовать как менеджер контекста: @@ -82,7 +82,7 @@ async with lifespan(app): Параметр `lifespan` приложения `FastAPI` принимает асинхронный менеджер контекста, поэтому мы можем передать ему наш новый асинхронный менеджер контекста `lifespan`. -{* ../../docs_src/events/tutorial003_py39.py hl[22] *} +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} ## Альтернативные события (устаревшие) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ async with lifespan(app): Чтобы добавить функцию, которую нужно запустить до старта приложения, объявите её как обработчик события `"startup"`: -{* ../../docs_src/events/tutorial001_py39.py hl[8] *} +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} В этом случае функция-обработчик события `startup` инициализирует «базу данных» items (это просто `dict`) некоторыми значениями. @@ -116,7 +116,7 @@ async with lifespan(app): Чтобы добавить функцию, которую нужно запустить при завершении работы приложения, объявите её как обработчик события `"shutdown"`: -{* ../../docs_src/events/tutorial002_py39.py hl[6] *} +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} Здесь функция-обработчик события `shutdown` запишет строку текста `"Application shutdown"` в файл `log.txt`. diff --git a/docs/ru/docs/advanced/generate-clients.md b/docs/ru/docs/advanced/generate-clients.md index 00bdd31fe..4eb098a88 100644 --- a/docs/ru/docs/advanced/generate-clients.md +++ b/docs/ru/docs/advanced/generate-clients.md @@ -2,7 +2,7 @@ Поскольку **FastAPI** основан на спецификации **OpenAPI**, его API можно описать в стандартном формате, понятном множеству инструментов. -Это упрощает генерацию актуальной **документации**, клиентских библиотек (**SDKs**) на разных языках, а также **тестирования** или **воркфлоу автоматизации**, которые остаются синхронизированными с вашим кодом. +Это упрощает генерацию актуальной **документации**, клиентских библиотек (**SDKs**) на разных языках, а также **тестирования** или **воркфлоу автоматизации**, которые остаются синхронизированными с вашим кодом. В этом руководстве вы узнаете, как сгенерировать **TypeScript SDK** для вашего бэкенда на FastAPI. @@ -40,7 +40,7 @@ FastAPI автоматически генерирует спецификации Начнём с простого приложения FastAPI: -{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} Обратите внимание, что *операции пути (обработчики пути)* определяют модели, которые они используют для полезной нагрузки запроса и полезной нагрузки ответа, с помощью моделей `Item` и `ResponseMessage`. @@ -98,7 +98,7 @@ npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client Например, у вас может быть раздел для **items** и другой раздел для **users**, и они могут быть разделены тегами: -{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} ### Генерация TypeScript‑клиента с тегами { #generate-a-typescript-client-with-tags } @@ -145,7 +145,7 @@ FastAPI использует **уникальный ID** для каждой *о Затем вы можете передать эту пользовательскую функцию в **FastAPI** через параметр `generate_unique_id_function`: -{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} ### Генерация TypeScript‑клиента с пользовательскими ID операций { #generate-a-typescript-client-with-custom-operation-ids } @@ -157,7 +157,7 @@ FastAPI использует **уникальный ID** для каждой *о ### Предобработка спецификации OpenAPI для генератора клиента { #preprocess-the-openapi-specification-for-the-client-generator } -Сгенерированном коде всё ещё есть **дублирующаяся информация**. +В сгенерированном коде всё ещё есть **дублирующаяся информация**. Мы уже знаем, что этот метод относится к **items**, потому что это слово есть в `ItemsService` (взято из тега), но при этом имя тега всё ещё добавлено префиксом к имени метода. 😕 @@ -167,7 +167,7 @@ FastAPI использует **уникальный ID** для каждой *о Мы можем скачать OpenAPI JSON в файл `openapi.json`, а затем **убрать этот префикс‑тег** таким скриптом: -{* ../../docs_src/generate_clients/tutorial004_py39.py *} +{* ../../docs_src/generate_clients/tutorial004_py310.py *} //// tab | Node.js diff --git a/docs/ru/docs/advanced/middleware.md b/docs/ru/docs/advanced/middleware.md index 5ebe01078..034feae7e 100644 --- a/docs/ru/docs/advanced/middleware.md +++ b/docs/ru/docs/advanced/middleware.md @@ -57,13 +57,13 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") Любой входящий запрос по `http` или `ws` будет перенаправлен на безопасную схему. -{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} ## `TrustedHostMiddleware` { #trustedhostmiddleware } Гарантирует, что во всех входящих запросах корректно установлен `Host`‑заголовок, чтобы защититься от атак на HTTP‑заголовок Host. -{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} Поддерживаются следующие аргументы: @@ -78,7 +78,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") Это middleware обрабатывает как обычные, так и потоковые ответы. -{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} Поддерживаются следующие аргументы: diff --git a/docs/ru/docs/advanced/openapi-webhooks.md b/docs/ru/docs/advanced/openapi-webhooks.md index 3a2b9fff7..b477075c1 100644 --- a/docs/ru/docs/advanced/openapi-webhooks.md +++ b/docs/ru/docs/advanced/openapi-webhooks.md @@ -16,9 +16,9 @@ Вся логика регистрации URL-адресов для вебхуков и код, который реально отправляет эти запросы, целиком на вашей стороне. Вы пишете это так, как вам нужно, в своем собственном коде. -## Документирование вебхуков с помощью FastAPI и OpenAPI { #documenting-webhooks-with-fastapi-and-openapi } +## Документирование вебхуков с помощью **FastAPI** и OpenAPI { #documenting-webhooks-with-fastapi-and-openapi } -С FastAPI, используя OpenAPI, вы можете определить имена этих вебхуков, типы HTTP-операций, которые ваше приложение может отправлять (например, `POST`, `PUT` и т.д.), а также тела запросов, которые ваше приложение будет отправлять. +С **FastAPI**, используя OpenAPI, вы можете определить имена этих вебхуков, типы HTTP-операций, которые ваше приложение может отправлять (например, `POST`, `PUT` и т.д.), а также тела запросов, которые ваше приложение будет отправлять. Это значительно упростит вашим пользователям реализацию их API для приема ваших вебхук-запросов; возможно, они даже смогут автоматически сгенерировать часть кода своего API. @@ -32,7 +32,7 @@ При создании приложения на **FastAPI** есть атрибут `webhooks`, с помощью которого можно объявлять вебхуки так же, как вы объявляете операции пути (обработчики пути), например с `@app.webhooks.post()`. -{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} Определенные вами вебхуки попадут в схему **OpenAPI** и в автоматический **интерфейс документации**. diff --git a/docs/ru/docs/advanced/path-operation-advanced-configuration.md b/docs/ru/docs/advanced/path-operation-advanced-configuration.md index 86d3a5b63..b8c879bf6 100644 --- a/docs/ru/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/ru/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ Нужно убедиться, что он уникален для каждой операции. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} ### Использование имени *функции-обработчика пути* как operationId { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ Делать это следует после добавления всех *операций пути*. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} /// tip | Совет @@ -40,7 +40,7 @@ Чтобы исключить *операцию пути* из генерируемой схемы OpenAPI (а значит, и из автоматических систем документации), используйте параметр `include_in_schema` и установите его в `False`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} ## Расширенное описание из docstring { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ `openapi_extra` может пригодиться, например, чтобы объявить [Расширения OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): -{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *} Если вы откроете автоматическую документацию API, ваше расширение появится внизу страницы конкретной *операции пути*. @@ -139,9 +139,9 @@ Это можно сделать с помощью `openapi_extra`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *} -В этом примере мы не объявляли никакую Pydantic-модель. Фактически тело запроса даже не распарсено как JSON, оно читается напрямую как `bytes`, а функция `magic_data_reader()` будет отвечать за его парсинг каким-то способом. +В этом примере мы не объявляли никакую Pydantic-модель. Фактически тело запроса даже не распарсено как JSON, оно читается напрямую как `bytes`, а функция `magic_data_reader()` будет отвечать за его парсинг каким-то способом. Тем не менее, мы можем объявить ожидаемую схему для тела запроса. @@ -153,7 +153,7 @@ Например, в этом приложении мы не используем встроенную функциональность FastAPI для извлечения JSON Schema из моделей Pydantic, равно как и автоматическую валидацию JSON. Мы объявляем тип содержимого HTTP-запроса как YAML, а не JSON: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *} Тем не менее, хотя мы не используем встроенную функциональность по умолчанию, мы всё равно используем Pydantic-модель, чтобы вручную сгенерировать JSON Schema для данных, которые мы хотим получить в YAML. @@ -161,7 +161,7 @@ А затем в нашем коде мы напрямую парсим это содержимое YAML и снова используем ту же Pydantic-модель, чтобы валидировать YAML-содержимое: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *} /// tip | Совет diff --git a/docs/ru/docs/advanced/response-change-status-code.md b/docs/ru/docs/advanced/response-change-status-code.md index 85d9050ff..273862bae 100644 --- a/docs/ru/docs/advanced/response-change-status-code.md +++ b/docs/ru/docs/advanced/response-change-status-code.md @@ -20,7 +20,7 @@ И затем вы можете установить `status_code` в этом *временном* объекте ответа. -{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *} После этого вы можете вернуть любой объект, который вам нужен, как обычно (`dict`, модель базы данных и т.д.). diff --git a/docs/ru/docs/advanced/response-cookies.md b/docs/ru/docs/advanced/response-cookies.md index 2872d6c0a..d3662ef8e 100644 --- a/docs/ru/docs/advanced/response-cookies.md +++ b/docs/ru/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ Затем установить cookies в этом временном объекте ответа. -{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *} После этого можно вернуть любой объект, как и раньше (например, `dict`, объект модели базы данных и так далее). @@ -24,7 +24,7 @@ Затем установите cookies и верните этот объект: -{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *} /// tip | Совет diff --git a/docs/ru/docs/advanced/response-directly.md b/docs/ru/docs/advanced/response-directly.md index b45281071..60facdd85 100644 --- a/docs/ru/docs/advanced/response-directly.md +++ b/docs/ru/docs/advanced/response-directly.md @@ -54,7 +54,7 @@ Вы можете поместить ваш XML-контент в строку, поместить её в `Response` и вернуть: -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ## Примечания { #notes } diff --git a/docs/ru/docs/advanced/response-headers.md b/docs/ru/docs/advanced/response-headers.md index 8f24f05b0..dc821983b 100644 --- a/docs/ru/docs/advanced/response-headers.md +++ b/docs/ru/docs/advanced/response-headers.md @@ -6,7 +6,7 @@ А затем вы можете устанавливать HTTP-заголовки в этом *временном* объекте ответа. -{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *} +{* ../../docs_src/response_headers/tutorial002_py310.py hl[1, 7:8] *} После этого вы можете вернуть любой нужный объект, как обычно (например, `dict`, модель из базы данных и т.д.). @@ -22,7 +22,7 @@ Создайте ответ, как описано в [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}, и передайте заголовки как дополнительный параметр: -{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *} +{* ../../docs_src/response_headers/tutorial001_py310.py hl[10:12] *} /// note | Технические детали diff --git a/docs/ru/docs/advanced/security/http-basic-auth.md b/docs/ru/docs/advanced/security/http-basic-auth.md index 41e62d4bf..a6bfb7c54 100644 --- a/docs/ru/docs/advanced/security/http-basic-auth.md +++ b/docs/ru/docs/advanced/security/http-basic-auth.md @@ -20,7 +20,7 @@ * Она возвращает объект типа `HTTPBasicCredentials`: * Он содержит отправленные `username` и `password`. -{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} +{* ../../docs_src/security/tutorial006_an_py310.py hl[4,8,12] *} Когда вы впервые откроете URL (или нажмёте кнопку «Execute» в документации), браузер попросит ввести имя пользователя и пароль: @@ -40,7 +40,7 @@ Затем можно использовать `secrets.compare_digest()`, чтобы убедиться, что `credentials.username` равен `"stanleyjobson"`, а `credentials.password` — `"swordfish"`. -{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} +{* ../../docs_src/security/tutorial007_an_py310.py hl[1,12:24] *} Это было бы похоже на: @@ -104,4 +104,4 @@ Pythonу придётся сравнить весь общий префикс `s После того как обнаружено, что учётные данные некорректны, верните `HTTPException` со статус-кодом ответа 401 (тем же, что и при отсутствии учётных данных) и добавьте HTTP-заголовок `WWW-Authenticate`, чтобы браузер снова показал окно входа: -{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} +{* ../../docs_src/security/tutorial007_an_py310.py hl[26:30] *} diff --git a/docs/ru/docs/advanced/settings.md b/docs/ru/docs/advanced/settings.md index 8408faebf..15537e2b4 100644 --- a/docs/ru/docs/advanced/settings.md +++ b/docs/ru/docs/advanced/settings.md @@ -54,7 +54,7 @@ $ pip install "fastapi[all]" Вы можете использовать все те же возможности валидации и инструменты, что и для Pydantic‑моделей, например разные типы данных и дополнительную валидацию через `Field()`. -{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} +{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *} /// tip | Совет @@ -70,7 +70,7 @@ $ pip install "fastapi[all]" Затем вы можете использовать новый объект `settings` в вашем приложении: -{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *} +{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *} ### Запуск сервера { #run-the-server } @@ -104,11 +104,11 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p Например, у вас может быть файл `config.py` со следующим содержимым: -{* ../../docs_src/settings/app01_py39/config.py *} +{* ../../docs_src/settings/app01_py310/config.py *} А затем использовать его в файле `main.py`: -{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *} +{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *} /// tip | Совет @@ -126,7 +126,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p Продолжая предыдущий пример, ваш файл `config.py` может выглядеть так: -{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} +{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *} Обратите внимание, что теперь мы не создаем экземпляр по умолчанию `settings = Settings()`. @@ -134,7 +134,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p Теперь мы создаем зависимость, которая возвращает новый `config.Settings()`. -{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} +{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *} /// tip | Совет @@ -146,13 +146,13 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p Затем мы можем запросить ее в *функции-обработчике пути* как зависимость и использовать там, где нужно. -{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} +{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *} ### Настройки и тестирование { #settings-and-testing } Далее будет очень просто предоставить другой объект настроек во время тестирования, создав переопределение зависимости для `get_settings`: -{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} +{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *} В переопределении зависимости мы задаем новое значение `admin_email` при создании нового объекта `Settings`, а затем возвращаем этот новый объект. @@ -193,7 +193,7 @@ APP_NAME="ChimichangApp" Затем обновите ваш `config.py` так: -{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} +{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *} /// tip | Совет @@ -226,7 +226,7 @@ def get_settings(): Но так как мы используем декоратор `@lru_cache` сверху, объект `Settings` будет создан только один раз — при первом вызове. ✔️ -{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} +{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *} Затем при любых последующих вызовах `get_settings()` в зависимостях для следующих запросов, вместо выполнения внутреннего кода `get_settings()` и создания нового объекта `Settings`, будет возвращаться тот же объект, что был возвращен при первом вызове, снова и снова. diff --git a/docs/ru/docs/advanced/sub-applications.md b/docs/ru/docs/advanced/sub-applications.md index fa5a683f4..4fd5649ce 100644 --- a/docs/ru/docs/advanced/sub-applications.md +++ b/docs/ru/docs/advanced/sub-applications.md @@ -10,7 +10,7 @@ Сначала создайте основное, верхнего уровня, приложение **FastAPI** и его *операции пути*: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *} ### Подприложение { #sub-application } @@ -18,7 +18,7 @@ Это подприложение — обычное стандартное приложение FastAPI, но именно оно будет «смонтировано»: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *} ### Смонтируйте подприложение { #mount-the-sub-application } @@ -26,7 +26,7 @@ В этом случае оно будет смонтировано по пути `/subapi`: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *} ### Проверьте автоматическую документацию API { #check-the-automatic-api-docs } diff --git a/docs/ru/docs/advanced/templates.md b/docs/ru/docs/advanced/templates.md index 460e2e466..68adcb515 100644 --- a/docs/ru/docs/advanced/templates.md +++ b/docs/ru/docs/advanced/templates.md @@ -27,7 +27,7 @@ $ pip install jinja2 - Объявите параметр `Request` в *операции пути*, которая будет возвращать шаблон. - Используйте созданный `templates`, чтобы отрендерить и вернуть `TemplateResponse`; передайте имя шаблона, объект `request` и словарь «context» с парами ключ-значение для использования внутри шаблона Jinja2. -{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *} +{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *} /// note | Примечание diff --git a/docs/ru/docs/advanced/testing-events.md b/docs/ru/docs/advanced/testing-events.md index 82caea845..452342cdd 100644 --- a/docs/ru/docs/advanced/testing-events.md +++ b/docs/ru/docs/advanced/testing-events.md @@ -2,11 +2,11 @@ Если вам нужно, чтобы `lifespan` выполнялся в ваших тестах, вы можете использовать `TestClient` вместе с оператором `with`: -{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *} +{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *} Вы можете узнать больше подробностей в статье [Запуск lifespan в тестах на официальном сайте документации Starlette.](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) Для устаревших событий `startup` и `shutdown` вы можете использовать `TestClient` следующим образом: -{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *} +{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *} diff --git a/docs/ru/docs/advanced/testing-websockets.md b/docs/ru/docs/advanced/testing-websockets.md index b6626679e..f6fa6a04b 100644 --- a/docs/ru/docs/advanced/testing-websockets.md +++ b/docs/ru/docs/advanced/testing-websockets.md @@ -4,7 +4,7 @@ Для этого используйте `TestClient` с менеджером контекста `with`, подключаясь к WebSocket: -{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *} +{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *} /// note | Примечание diff --git a/docs/ru/docs/advanced/using-request-directly.md b/docs/ru/docs/advanced/using-request-directly.md index cdf500c0e..0c091cded 100644 --- a/docs/ru/docs/advanced/using-request-directly.md +++ b/docs/ru/docs/advanced/using-request-directly.md @@ -29,7 +29,7 @@ Для этого нужно обратиться к запросу напрямую. -{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *} +{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *} Если объявить параметр *функции-обработчика пути* с типом `Request`, **FastAPI** поймёт, что нужно передать объект `Request` в этот параметр. diff --git a/docs/ru/docs/advanced/websockets.md b/docs/ru/docs/advanced/websockets.md index fa5e4738e..446cc2505 100644 --- a/docs/ru/docs/advanced/websockets.md +++ b/docs/ru/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets Для примера нам нужен наиболее простой способ, который позволит сосредоточиться на серверной части веб‑сокетов и получить рабочий код: -{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} ## Создание `websocket` { #create-a-websocket } Создайте `websocket` в своем **FastAPI** приложении: -{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} /// note | Технические детали @@ -58,7 +58,7 @@ $ pip install websockets Через эндпоинт веб-сокета вы можете получать и отправлять сообщения. -{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} Вы можете получать и отправлять двоичные, текстовые и JSON данные. @@ -154,7 +154,7 @@ $ fastapi dev main.py Если веб-сокет соединение закрыто, то `await websocket.receive_text()` вызовет исключение `WebSocketDisconnect`, которое можно поймать и обработать как в этом примере: -{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} +{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} Чтобы воспроизвести пример: diff --git a/docs/ru/docs/advanced/wsgi.md b/docs/ru/docs/advanced/wsgi.md index 41d3a169c..aa630c228 100644 --- a/docs/ru/docs/advanced/wsgi.md +++ b/docs/ru/docs/advanced/wsgi.md @@ -18,7 +18,7 @@ После этого смонтируйте его на путь. -{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} +{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *} /// note | Примечание diff --git a/docs/ru/docs/alternatives.md b/docs/ru/docs/alternatives.md index 17b54aad2..1f713c3f3 100644 --- a/docs/ru/docs/alternatives.md +++ b/docs/ru/docs/alternatives.md @@ -137,7 +137,7 @@ def read_url(): ### Marshmallow { #marshmallow } -Одна из основных возможностей, нужных системам API, — «сериализация» данных, то есть преобразование данных из кода (Python) во что-то, что можно отправить по сети. Например, преобразование объекта с данными из базы в JSON-объект. Преобразование объектов `datetime` в строки и т. п. +Одна из основных возможностей, нужных системам API, — «сериализация» данных, то есть преобразование данных из кода (Python) во что-то, что можно отправить по сети. Например, преобразование объекта с данными из базы в JSON-объект. Преобразование объектов `datetime` в строки и т. п. Ещё одна важная возможность, востребованная API, — валидация данных: убеждаться, что данные валидны с учётом заданных параметров. Например, что какое-то поле — `int`, а не произвольная строка. Это особенно полезно для входящих данных. @@ -145,7 +145,7 @@ def read_url(): Именно для этих возможностей и был создан Marshmallow. Это отличная библиотека, я много ей пользовался раньше. -Но она появилась до того, как в Python появились аннотации типов. Поэтому для определения каждой схемы нужно использовать специальные утилиты и классы, предоставляемые Marshmallow. +Но она появилась до того, как в Python появились аннотации типов. Поэтому для определения каждой схемы нужно использовать специальные утилиты и классы, предоставляемые Marshmallow. /// check | Вдохновило **FastAPI** на @@ -155,7 +155,7 @@ def read_url(): ### Webargs { #webargs } -Ещё одна важная возможность для API — парсинг данных из входящих HTTP-запросов. +Ещё одна важная возможность для API — парсинг данных из входящих HTTP-запросов. Webargs — это инструмент, созданный для этого поверх нескольких фреймворков, включая Flask. @@ -245,7 +245,7 @@ Flask-apispec был создан теми же разработчиками, ч В нём встроена система внедрения зависимостей, вдохновлённая Angular 2. Требуется предварительная регистрация «инжектируемых» компонентов (как и во всех известных мне системах внедрения зависимостей), что добавляет многословности и повторяемости кода. -Поскольку параметры описываются с помощью типов TypeScript (аналог аннотаций типов в Python), поддержка редактора весьма хороша. +Поскольку параметры описываются с помощью типов TypeScript (аналог аннотаций типов в Python), поддержка редактора кода весьма хороша. Но так как данные о типах TypeScript не сохраняются после компиляции в JavaScript, он не может полагаться на типы для одновременного определения валидации, сериализации и документации. Из‑за этого и некоторых проектных решений для получения валидации, сериализации и автоматической генерации схем приходится добавлять декораторы во многих местах. В итоге это становится довольно многословным. @@ -359,7 +359,7 @@ Hug вдохновил **FastAPI** объявлять параметр `response В нём были автоматические валидация данных, сериализация данных и генерация схемы OpenAPI на основе тех же аннотаций типов в нескольких местах. -Определение схемы тела запроса не использовало те же аннотации типов Python, как в Pydantic, — это было ближе к Marshmallow, поэтому поддержка редактора была бы хуже, но всё равно APIStar оставался лучшим доступным вариантом. +Определение схемы тела запроса не использовало те же аннотации типов Python, как в Pydantic, — это было ближе к Marshmallow, поэтому поддержка редактора кода была бы хуже, но всё равно APIStar оставался лучшим доступным вариантом. На тот момент у него были лучшие показатели в бенчмарках (его превосходил только Starlette). @@ -419,7 +419,7 @@ Pydantic — это библиотека для определения вали ### Starlette { #starlette } -Starlette — это лёгкий ASGI фреймворк/набор инструментов, идеально подходящий для создания высокопроизводительных asyncio‑сервисов. +Starlette — это лёгкий ASGI фреймворк/набор инструментов, идеально подходящий для создания высокопроизводительных asyncio‑сервисов. Он очень простой и интуитивный. Спроектирован так, чтобы его было легко расширять, и чтобы компоненты были модульными. diff --git a/docs/ru/docs/async.md b/docs/ru/docs/async.md index 15d4e108a..bff32aaf4 100644 --- a/docs/ru/docs/async.md +++ b/docs/ru/docs/async.md @@ -4,7 +4,7 @@ ## Нет времени? { #in-a-hurry } -TL;DR: +TL;DR: Если вы используете сторонние библиотеки, которые нужно вызывать с `await`, например: @@ -68,13 +68,13 @@ def results(): Асинхронный код значит, что в языке 💬 есть способ сказать компьютеру/программе 🤖, что в некоторый момент кода ему 🤖 придётся подождать, пока *что-то ещё* где-то в другом месте завершится. Назовём это *что-то ещё* «медленный файл» 📝. -И пока мы ждём завершения работы с «медленныи файлом» 📝, компьютер может заняться другой работой. +И пока мы ждём завершения работы с «медленным файлом» 📝, компьютер может заняться другой работой. Затем компьютер/программа 🤖 будет возвращаться каждый раз, когда появится возможность (пока снова где-то идёт ожидание), или когда 🤖 завершит всю текущую работу. И он 🤖 проверит, не завершилась ли какая-либо из задач, которых он ждал, и сделает то, что нужно. Далее он 🤖 возьмёт первую завершившуюся задачу (скажем, наш «медленный файл» 📝) и продолжит делать с ней то, что требуется. -Это «ожидание чего-то ещё» обычно относится к операциям I/O, которые относительно «медленные» (по сравнению со скоростью процессора и оперативной памяти), например ожидание: +Это «ожидание чего-то ещё» обычно относится к операциям I/O, которые относительно «медленные» (по сравнению со скоростью процессора и оперативной памяти), например ожидание: * отправки данных клиентом по сети * получения клиентом данных, отправленных вашей программой по сети @@ -85,7 +85,7 @@ def results(): * возврата результатов запроса к базе данных * и т.д. -Поскольку основное время выполнения уходит на ожидание операций I/O, их называют операциями, «ограниченными вводом-выводом» (I/O bound). +Поскольку основное время выполнения уходит на ожидание операций I/O, их называют операциями, «ограниченными вводом-выводом» (I/O bound). Это называется «асинхронным», потому что компьютеру/программе не нужно «синхронизироваться» с медленной задачей, простаивая и выжидая точный момент её завершения, чтобы забрать результат и продолжить работу. @@ -277,7 +277,7 @@ def results(): В этом сценарии каждый уборщик (включая вас) был бы процессором, выполняющим свою часть работы. -И так как основное время выполнения уходит на реальную работу (а не ожидание), а работу в компьютере выполняет CPU, такие задачи называют «ограниченными процессором» (CPU bound). +И так как основное время выполнения уходит на реальную работу (а не ожидание), а работу в компьютере выполняет CPU, такие задачи называют «ограниченными процессором» (CPU bound). --- @@ -417,7 +417,7 @@ Starlette (и **FastAPI**) основаны на I/O. +Если вы пришли из другого async-фреймворка, который работает иначе, и привыкли объявлять тривиальные *функции-обработчики пути*, выполняющие только вычисления, через простой `def` ради крошечной выгоды в производительности (около 100 наносекунд), обратите внимание: в **FastAPI** эффект будет противоположным. В таких случаях лучше использовать `async def`, если только ваши *функции-обработчики пути* не используют код, выполняющий блокирующий I/O. Тем не менее, в обоих случаях велика вероятность, что **FastAPI** [всё равно будет быстрее](index.md#performance){.internal-link target=_blank} (или как минимум сопоставим) с вашим предыдущим фреймворком. diff --git a/docs/ru/docs/benchmarks.md b/docs/ru/docs/benchmarks.md index 612b39f70..c8cacae5f 100644 --- a/docs/ru/docs/benchmarks.md +++ b/docs/ru/docs/benchmarks.md @@ -16,19 +16,19 @@ * **Uvicorn**: ASGI-сервер * **Starlette**: (использует Uvicorn) веб-микрофреймворк - * **FastAPI**: (использует Starlette) API-микрофреймворк с рядом дополнительных возможностей для создания API, включая валидацию данных и т. п. + * **FastAPI**: (использует Starlette) API-микрофреймворк с рядом дополнительных возможностей для создания API, включая валидацию данных и т.п. * **Uvicorn**: * Будет иметь наилучшую производительность, так как помимо самого сервера у него немного дополнительного кода. * Вы не будете писать приложение непосредственно на Uvicorn. Это означало бы, что Ваш код должен включать как минимум весь код, предоставляемый Starlette (или **FastAPI**). И если Вы так сделаете, то в конечном итоге Ваше приложение будет иметь те же накладные расходы, что и при использовании фреймворка, минимизирующего код Вашего приложения и Ваши ошибки. - * Если Вы сравниваете Uvicorn, сравнивайте его с Daphne, Hypercorn, uWSGI и т. д. — серверами приложений. + * Если Вы сравниваете Uvicorn, сравнивайте его с Daphne, Hypercorn, uWSGI и т.д. — серверами приложений. * **Starlette**: * Будет на следующем месте по производительности после Uvicorn. Фактически Starlette запускается под управлением Uvicorn, поэтому он может быть только «медленнее» Uvicorn из‑за выполнения большего объёма кода. - * Зато он предоставляет Вам инструменты для создания простых веб‑приложений с маршрутизацией по путям и т. п. - * Если Вы сравниваете Starlette, сравнивайте его с Sanic, Flask, Django и т. д. — веб‑фреймворками (или микрофреймворками). + * Зато он предоставляет Вам инструменты для создания простых веб‑приложений с маршрутизацией по путям и т.п. + * Если Вы сравниваете Starlette, сравнивайте его с Sanic, Flask, Django и т.д. — веб‑фреймворками (или микрофреймворками). * **FastAPI**: * Точно так же, как Starlette использует Uvicorn и не может быть быстрее него, **FastAPI** использует Starlette, поэтому не может быть быстрее его. * FastAPI предоставляет больше возможностей поверх Starlette — те, которые почти всегда нужны при создании API, такие как валидация и сериализация данных. В довесок Вы ещё и получаете автоматическую документацию (автоматическая документация даже не увеличивает накладные расходы при работе приложения, так как она создаётся при запуске). - * Если бы Вы не использовали FastAPI, а использовали Starlette напрямую (или другой инструмент вроде Sanic, Flask, Responder и т. д.), Вам пришлось бы самостоятельно реализовать валидацию и сериализацию данных. То есть, в итоге, Ваше приложение имело бы такие же накладные расходы, как если бы оно было создано с использованием FastAPI. И во многих случаях валидация и сериализация данных представляют собой самый большой объём кода, написанного в приложениях. + * Если бы Вы не использовали FastAPI, а использовали Starlette напрямую (или другой инструмент вроде Sanic, Flask, Responder и т.д.), Вам пришлось бы самостоятельно реализовать валидацию и сериализацию данных. То есть, в итоге, Ваше приложение имело бы такие же накладные расходы, как если бы оно было создано с использованием FastAPI. И во многих случаях валидация и сериализация данных представляют собой самый большой объём кода, написанного в приложениях. * Таким образом, используя FastAPI, Вы экономите время разработки, уменьшаете количество ошибок, строк кода и, вероятно, получите ту же производительность (или лучше), как и если бы не использовали его (поскольку Вам пришлось бы реализовать все его возможности в своём коде). * Если Вы сравниваете FastAPI, сравнивайте его с фреймворком веб‑приложений (или набором инструментов), который обеспечивает валидацию данных, сериализацию и документацию, такими как Flask-apispec, NestJS, Molten и им подобные. Фреймворки с интегрированной автоматической валидацией данных, сериализацией и документацией. diff --git a/docs/ru/docs/deployment/concepts.md b/docs/ru/docs/deployment/concepts.md index 207d1604d..173dbb962 100644 --- a/docs/ru/docs/deployment/concepts.md +++ b/docs/ru/docs/deployment/concepts.md @@ -27,13 +27,13 @@ В [предыдущей главе про HTTPS](https.md){.internal-link target=_blank} мы разобрались, как HTTPS обеспечивает шифрование для вашего API. -Также мы увидели, что HTTPS обычно обеспечивает компонент, **внешний** по отношению к серверу вашего приложения — **TLS Termination Proxy**. +Также мы увидели, что HTTPS обычно обеспечивает компонент, **внешний** по отношению к серверу вашего приложения — **прокси-сервер TSL-терминации**. И должен быть компонент, отвечающий за **обновление HTTPS‑сертификатов** — это может быть тот же самый компонент или отдельный. ### Примеры инструментов для HTTPS { #example-tools-for-https } -Некоторые инструменты, которые можно использовать как TLS Termination Proxy: +Некоторые инструменты, которые можно использовать как прокси-сервер TSL-терминации: * Traefik * Автоматически обновляет сертификаты ✨ @@ -47,7 +47,7 @@ * С внешним компонентом (например, cert-manager) для обновления сертификатов * Обрабатывается внутри облачного провайдера как часть его услуг (см. ниже 👇) -Другой вариант — использовать **облачный сервис**, который возьмёт на себя больше задач, включая настройку HTTPS. Там могут быть ограничения или дополнительная стоимость и т.п., но в таком случае вам не придётся самим настраивать TLS Termination Proxy. +Другой вариант — использовать **облачный сервис**, который возьмёт на себя больше задач, включая настройку HTTPS. Там могут быть ограничения или дополнительная стоимость и т.п., но в таком случае вам не придётся самим настраивать прокси-сервер TSL-терминации. В следующих главах я покажу конкретные примеры. @@ -214,7 +214,7 @@ Процесс‑менеджер, вероятно, будет тем, кто слушает **порт** на IP. И он будет передавать всю коммуникацию воркер‑процессам. -Эти воркеры будут запускать ваше приложение, выполнять основные вычисления для получения **запроса** и возврата **ответа**, и загружать всё, что вы кладёте в переменные, в RAM. +Эти воркеры будут запускать ваше приложение, выполнять основные вычисления для получения **HTTP‑запроса** и возврата **HTTP‑ответа**, и загружать всё, что вы кладёте в переменные, в RAM. @@ -289,7 +289,7 @@ Ваш сервер(а) — это **ресурс**, который ваши программы могут потреблять или **использовать**: время вычислений на CPU и доступную оперативную память (RAM). -Какую долю системных ресурсов вы хотите потреблять/использовать? Можно подумать «немного», но на практике вы, скорее всего, захотите потреблять **максимум без падений**. +Какую долю системных ресурсов вы хотите потреблять/использовать? Можно подумать «немного», но на практике вы, вероятно, захотите потреблять **максимум без падений**. Если вы платите за 3 сервера, но используете лишь малую часть их RAM и CPU, вы, вероятно, **тратите деньги впустую** 💸 и **электроэнергию серверов** 🌎 и т.п. diff --git a/docs/ru/docs/deployment/docker.md b/docs/ru/docs/deployment/docker.md index 9e8562be7..791057fe5 100644 --- a/docs/ru/docs/deployment/docker.md +++ b/docs/ru/docs/deployment/docker.md @@ -14,7 +14,7 @@ Предпросмотр Dockerfile 👀 ```Dockerfile -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -166,7 +166,7 @@ def read_item(item_id: int, q: str | None = None): ```{ .dockerfile .annotate } # (1)! -FROM python:3.9 +FROM python:3.14 # (2)! WORKDIR /code @@ -273,9 +273,9 @@ CMD fastapi run app/main.py --port 80 └── requirements.txt ``` -#### За прокси-сервером TLS терминации { #behind-a-tls-termination-proxy } +#### За прокси-сервером TSL-терминации { #behind-a-tls-termination-proxy } -Если вы запускаете контейнер за прокси-сервером завершения TLS (балансировщиком нагрузки), таким как Nginx или Traefik, добавьте опцию `--proxy-headers`. Это сообщит Uvicorn (через FastAPI CLI), что приложение работает за HTTPS и можно доверять соответствующим заголовкам. +Если вы запускаете контейнер за прокси-сервером TSL-терминации (балансировщиком нагрузки), таким как Nginx или Traefik, добавьте опцию `--proxy-headers`. Это сообщит Uvicorn (через FastAPI CLI), что приложение работает за HTTPS и можно доверять соответствующим заголовкам. ```Dockerfile CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] @@ -390,7 +390,7 @@ $ docker run -d --name mycontainer -p 80:80 myimage Тогда в `Dockerfile` нужно изменить пути копирования: ```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -454,7 +454,7 @@ CMD ["fastapi", "run", "main.py", "--port", "80"] ## Репликация — количество процессов { #replication-number-of-processes } -Если у вас есть кластер машин с **Kubernetes**, Docker Swarm Mode, Nomad или другой похожей системой для управления распределёнными контейнерами на нескольких машинах, скорее всего вы будете **управлять репликацией** на **уровне кластера**, а не использовать **менеджер процессов** (например, Uvicorn с воркерами) в каждом контейнере. +Если у вас есть кластер машин с **Kubernetes**, Docker Swarm Mode, Nomad или другой похожей системой для управления распределёнными контейнерами на нескольких машинах, скорее всего вы будете **управлять репликацией** на **уровне кластера**, а не использовать **менеджер процессов** (например, Uvicorn с воркерами) в каждом контейнере. Одна из таких систем управления распределёнными контейнерами, как Kubernetes, обычно имеет встроенный способ управлять **репликацией контейнеров**, поддерживая **балансировку нагрузки** для входящих запросов — всё это на **уровне кластера**. @@ -462,17 +462,17 @@ CMD ["fastapi", "run", "main.py", "--port", "80"] ### Балансировщик нагрузки { #load-balancer } -При использовании контейнеров обычно есть компонент, **слушающий главный порт**. Это может быть другой контейнер — **прокси завершения TLS** для обработки **HTTPS** или похожий инструмент. +При использовании контейнеров обычно есть компонент, **слушающий главный порт**. Это может быть другой контейнер — **прокси-сервер TSL-терминации** для обработки **HTTPS** или похожий инструмент. Поскольку этот компонент принимает **нагрузку** запросов и распределяет её между воркерами **сбалансированно**, его часто называют **балансировщиком нагрузки**. /// tip | Подсказка -Тот же компонент **прокси завершения TLS**, который обрабатывает HTTPS, скорее всего также будет **балансировщиком нагрузки**. +Тот же компонент **прокси-сервер TSL-терминации**, который обрабатывает HTTPS, скорее всего также будет **балансировщиком нагрузки**. /// -При работе с контейнерами система, которую вы используете для запуска и управления ими, уже имеет внутренние средства для передачи **сетевого взаимодействия** (например, HTTP-запросов) от **балансировщика нагрузки** (который также может быть **прокси завершения TLS**) к контейнеру(-ам) с вашим приложением. +При работе с контейнерами система, которую вы используете для запуска и управления ими, уже имеет внутренние средства для передачи **сетевого взаимодействия** (например, HTTP-запросов) от **балансировщика нагрузки** (который также может быть **прокси-сервером TSL-терминации**) к контейнеру(-ам) с вашим приложением. ### Один балансировщик — несколько контейнеров-воркеров { #one-load-balancer-multiple-worker-containers } @@ -499,7 +499,7 @@ CMD ["fastapi", "run", "main.py", "--port", "80"] В таких случаях вы можете использовать опцию командной строки `--workers`, чтобы указать нужное количество воркеров: ```{ .dockerfile .annotate } -FROM python:3.9 +FROM python:3.14 WORKDIR /code diff --git a/docs/ru/docs/deployment/https.md b/docs/ru/docs/deployment/https.md index 05a03255e..ffeccfd7d 100644 --- a/docs/ru/docs/deployment/https.md +++ b/docs/ru/docs/deployment/https.md @@ -65,7 +65,7 @@ Чаще всего всё начинается с **приобретения** **имени домена**. Затем вы настраиваете его на DNS‑сервере (возможно, у того же облачного провайдера). -Скорее всего, вы получите облачный сервер (виртуальную машину) или что-то подобное, и у него будет постоянный **публичный IP-адрес**. +Скорее всего, вы получите облачный сервер (виртуальную машину) или что-то подобное, и у него будет постоянный **публичный IP-адрес**. На DNS‑сервере(ах) вы настроите запись («`A record`» - запись типа A), указывающую, что **ваш домен** должен указывать на публичный **IP‑адрес вашего сервера**. diff --git a/docs/ru/docs/deployment/index.md b/docs/ru/docs/deployment/index.md index ffb77641d..7e735593b 100644 --- a/docs/ru/docs/deployment/index.md +++ b/docs/ru/docs/deployment/index.md @@ -18,6 +18,6 @@ Например, мы, команда, стоящая за FastAPI, создали **FastAPI Cloud**, чтобы сделать развёртывание приложений FastAPI в облаке как можно более простым и прямолинейным, с тем же удобством для разработчика, что и при работе с FastAPI. -В этом блоке я покажу вам некоторые из основных концепций, которые вы, вероятно, должны иметь в виду при развертывании приложения **FastAPI** (хотя большинство из них применимо к любому другому типу веб-приложений). +Я покажу вам некоторые из основных концепций, которые вы, вероятно, должны иметь в виду при развертывании приложения **FastAPI** (хотя большинство из них применимо к любому другому типу веб-приложений). В последующих разделах вы узнаете больше деталей и методов, необходимых для этого. ✨ diff --git a/docs/ru/docs/deployment/versions.md b/docs/ru/docs/deployment/versions.md index 58d5aa110..4195d689f 100644 --- a/docs/ru/docs/deployment/versions.md +++ b/docs/ru/docs/deployment/versions.md @@ -1,12 +1,12 @@ # О версиях FastAPI { #about-fastapi-versions } -**FastAPI** уже используется в продакшене во многих приложениях и системах. Покрытие тестами поддерживается на уровне 100%. Но его разработка всё ещё движется быстрыми темпами. +**FastAPI** уже используется в продакшн во многих приложениях и системах. Покрытие тестами поддерживается на уровне 100%. Но его разработка всё ещё движется быстрыми темпами. Часто добавляются новые функции, регулярно исправляются баги, код продолжает постоянно совершенствоваться. По указанным причинам текущие версии до сих пор `0.x.x`. Это говорит о том, что каждая версия может содержать обратно несовместимые изменения, следуя Семантическому версионированию. -Уже сейчас вы можете создавать приложения в продакшене, используя **FastAPI** (и скорее всего так и делаете), главное убедиться в том, что вы используете версию, которая корректно работает с вашим кодом. +Уже сейчас вы можете создавать приложения в продакшн, используя **FastAPI** (и скорее всего так и делаете), главное убедиться в том, что вы используете версию, которая корректно работает с вашим кодом. ## Закрепите вашу версию `fastapi` { #pin-your-fastapi-version } diff --git a/docs/ru/docs/environment-variables.md b/docs/ru/docs/environment-variables.md index 6291b79d2..759127420 100644 --- a/docs/ru/docs/environment-variables.md +++ b/docs/ru/docs/environment-variables.md @@ -161,7 +161,7 @@ Hello World from Python Эти переменные окружения могут работать только с **текстовыми строками**, поскольку они являются внешними по отношению к Python и должны быть совместимы с другими программами и остальной системой (и даже с различными операционными системами, такими как Linux, Windows, macOS). -Это означает, что **любое значение**, считанное в Python из переменной окружения, **будет `str`**, и любое преобразование к другому типу или любая проверка должны быть выполнены в коде. +Это означает, что **любое значение**, считанное в Python из переменной окружения, **будет `str`**, и любое преобразование к другому типу или любая валидация должны быть выполнены в коде. Подробнее об использовании переменных окружения для работы с **настройками приложения** вы узнаете в [Расширенное руководство пользователя - Настройки и переменные среды](./advanced/settings.md){.internal-link target=_blank}. diff --git a/docs/ru/docs/fastapi-cli.md b/docs/ru/docs/fastapi-cli.md index 72cf55e7b..a46e0053e 100644 --- a/docs/ru/docs/fastapi-cli.md +++ b/docs/ru/docs/fastapi-cli.md @@ -52,7 +52,7 @@ FastAPI CLI берет путь к вашей Python-программе (нап Для работы в режиме продакшн вместо `fastapi dev` нужно использовать `fastapi run`. 🚀 -Внутри **FastAPI CLI** используется Uvicorn, высокопроизводительный, готовый к работе в продакшне ASGI-сервер. 😎 +Внутри **FastAPI CLI** используется Uvicorn, высокопроизводительный, готовый к работе в продакшн ASGI-сервер. 😎 ## `fastapi dev` { #fastapi-dev } diff --git a/docs/ru/docs/features.md b/docs/ru/docs/features.md index 703ff951e..0bc3dbb2d 100644 --- a/docs/ru/docs/features.md +++ b/docs/ru/docs/features.md @@ -6,7 +6,7 @@ ### Основано на открытых стандартах { #based-on-open-standards } -* OpenAPI для создания API, включая объявления операций пути, параметров, тел запросов, безопасности и т. д. +* OpenAPI для создания API, включая объявления операций пути, параметров, тел запросов, безопасности и т.д. * Автоматическая документация моделей данных с помощью JSON Schema (так как сама спецификация OpenAPI основана на JSON Schema). * Разработан вокруг этих стандартов, после тщательного их изучения. Это не дополнительная надстройка поверх. * Это также позволяет использовать автоматическую **генерацию клиентского кода** на многих языках. @@ -107,7 +107,7 @@ FastAPI имеет продуманные значения **по умолчан * Объекты JSON (`dict`). * Массив JSON (`list`) с определёнными типами элементов. * Строковые (`str`) поля с ограничением минимальной и максимальной длины. - * Числа (`int`, `float`) с минимальными и максимальными значениями и т. п. + * Числа (`int`, `float`) с минимальными и максимальными значениями и т.п. * Проверка для более экзотических типов, таких как: * URL. @@ -126,24 +126,24 @@ FastAPI имеет продуманные значения **по умолчан * HTTP Basic. * **OAuth2** (также с **токенами JWT**). Ознакомьтесь с руководством [OAuth2 с JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. * Ключи API в: - * Заголовках. + * HTTP-заголовках. * Параметрах запросов. * Cookies и т.п. Вдобавок все функции безопасности от Starlette (включая **сессионные cookies**). -Все инструменты и компоненты спроектированы для многократного использования и легко интегрируются с вашими системами, хранилищами данных, реляционными и NoSQL базами данных и т. д. +Все инструменты и компоненты спроектированы для многократного использования и легко интегрируются с вашими системами, хранилищами данных, реляционными и NoSQL базами данных и т.д. ### Внедрение зависимостей { #dependency-injection } -FastAPI включает в себя чрезвычайно простую в использовании, но чрезвычайно мощную систему Внедрения зависимостей. +FastAPI включает в себя чрезвычайно простую в использовании, но чрезвычайно мощную систему Внедрения зависимостей. * Даже зависимости могут иметь зависимости, создавая иерархию или **«граф» зависимостей**. * Всё **автоматически обрабатывается** фреймворком. * Все зависимости могут запрашивать данные из запросов и **дополнять операции пути** ограничениями и автоматической документацией. * **Автоматическая проверка** даже для параметров *операций пути*, определённых в зависимостях. -* Поддержка сложных систем аутентификации пользователей, **соединений с базами данных** и т. д. -* **Никаких компромиссов** с базами данных, интерфейсами и т. д. Но при этом — лёгкая интеграция со всеми ними. +* Поддержка сложных систем аутентификации пользователей, **соединений с базами данных** и т.д. +* **Никаких компромиссов** с базами данных, интерфейсами и т.д. Но при этом — лёгкая интеграция со всеми ними. ### Нет ограничений на "Плагины" { #unlimited-plug-ins } @@ -153,8 +153,8 @@ FastAPI включает в себя чрезвычайно простую в и ### Проверен { #tested } -* 100% покрытие тестами. -* 100% аннотирование типов в кодовой базе. +* 100% покрытие тестами. +* 100% аннотирование типов в кодовой базе. * Используется в продакшн‑приложениях. ## Возможности Starlette { #starlette-features } @@ -179,7 +179,7 @@ FastAPI включает в себя чрезвычайно простую в и **FastAPI** полностью совместим с (и основан на) Pydantic. Поэтому любой дополнительный код Pydantic, который у вас есть, также будет работать. -Включая внешние библиотеки, также основанные на Pydantic, такие как ORM’ы, ODM’ы для баз данных. +Включая внешние библиотеки, также основанные на Pydantic, такие как ORM’ы, ODM’ы для баз данных. Это также означает, что во многих случаях вы можете передавать тот же объект, который получили из запроса, **непосредственно в базу данных**, так как всё проверяется автоматически. @@ -190,10 +190,10 @@ FastAPI включает в себя чрезвычайно простую в и * **Никакой нервотрёпки**: * Не нужно изучать новые схемы в микроязыках. * Если вы знаете типы в Python, вы знаете, как использовать Pydantic. -* Прекрасно сочетается с вашим **IDE/linter/мозгом**: +* Прекрасно сочетается с вашим **IDE/линтер/мозгом**: * Потому что структуры данных pydantic — это всего лишь экземпляры классов, определённых вами; автозавершение, проверка кода, mypy и ваша интуиция — всё будет работать с вашими валидированными данными. * Валидация **сложных структур**: - * Использование иерархических моделей Pydantic; `List`, `Dict` и т. п. из модуля `typing` (входит в стандартную библиотеку Python). + * Использование иерархических моделей Pydantic; `List`, `Dict` и т.п. из модуля `typing` (входит в стандартную библиотеку Python). * Валидаторы позволяют чётко и легко определять, проверять и документировать сложные схемы данных в виде JSON Schema. * У вас могут быть глубоко **вложенные объекты JSON**, и все они будут проверены и аннотированы. * **Расширяемость**: diff --git a/docs/ru/docs/history-design-future.md b/docs/ru/docs/history-design-future.md index 9cdd53376..e2395fe8b 100644 --- a/docs/ru/docs/history-design-future.md +++ b/docs/ru/docs/history-design-future.md @@ -30,7 +30,7 @@ ## Исследования { #investigation } -Благодаря опыту использования существующих альтернатив, мы с коллегами изучили их основные идеи и скомбинировали собранные знания наилучшим образом. +Используя все существовавшие ранее альтернативы, я получил возможность у каждой из них чему-то научиться, позаимствовать идеи и объединить их наилучшим образом для себя и для команд разработчиков, с которыми я работал. Например, стало ясно, что необходимо брать за основу стандартные аннотации типов Python. @@ -44,13 +44,13 @@ Я проверил несколько идей на самых популярных редакторах кода: PyCharm, VS Code, редакторы на базе Jedi. -Данные по редакторам я взял из опроса Python-разработчиков, который охватывает около 80% пользователей. +Согласно последнему опросу Python-разработчиков, который охватывает около 80% пользователей. Это означает, что **FastAPI** был специально проверен на редакторах, используемых 80% Python-разработчиками. И поскольку большинство других редакторов, как правило, работают аналогичным образом, все его преимущества должны работать практически для всех редакторов. Таким образом, я смог найти наилучшие способы сократить дублирование кода, обеспечить повсеместное автозавершение, проверку типов и ошибок и т.д. -И все это, чтобы все пользователи могли получать наилучший опыт разработки. +И все это, чтобы все разработчики могли получать наилучший опыт разработки. ## Зависимости { #requirements } @@ -58,7 +58,7 @@ По моим предложениям был изменён код этого фреймворка, чтобы сделать его полностью совместимым с JSON Schema, поддержать различные способы определения ограничений и улучшить поддержку в редакторах кода (проверки типов, автозавершение) на основе тестов в нескольких редакторах. -В то же время, я принимал участие в разработке **Starlette**, ещё один из основных компонентов FastAPI. +Во время разработки я также внес вклад в **Starlette**, другую ключевую зависимость. ## Разработка { #development } diff --git a/docs/ru/docs/how-to/authentication-error-status-code.md b/docs/ru/docs/how-to/authentication-error-status-code.md index 5675cecc5..596563c54 100644 --- a/docs/ru/docs/how-to/authentication-error-status-code.md +++ b/docs/ru/docs/how-to/authentication-error-status-code.md @@ -8,7 +8,7 @@ Например, вы можете создать подкласс `HTTPBearer`, который будет возвращать ошибку `403 Forbidden` вместо стандартной `401 Unauthorized`: -{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} /// tip | Совет diff --git a/docs/ru/docs/how-to/conditional-openapi.md b/docs/ru/docs/how-to/conditional-openapi.md index d0845b91e..6efa30608 100644 --- a/docs/ru/docs/how-to/conditional-openapi.md +++ b/docs/ru/docs/how-to/conditional-openapi.md @@ -4,9 +4,9 @@ ## О безопасности, API и документации { #about-security-apis-and-docs } -Скрытие пользовательских интерфейсов документации в продакшн *не должно* быть способом защиты вашего API. +Скрытие пользовательских интерфейсов документации в продакшн не должно быть способом защиты вашего API. -Это не добавляет дополнительной безопасности вашему API, *операции пути* (обработчики пути) всё равно будут доступны по своим путям. +Это не добавляет дополнительной безопасности вашему API, операции пути (обработчики пути) всё равно будут доступны по своим путям. Если в вашем коде есть уязвимость, она всё равно останется. @@ -29,7 +29,7 @@ Например: -{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} Здесь мы объявляем настройку `openapi_url` с тем же значением по умолчанию — `"/openapi.json"`. diff --git a/docs/ru/docs/how-to/configure-swagger-ui.md b/docs/ru/docs/how-to/configure-swagger-ui.md index b3b1c1ba6..f4f2a0e54 100644 --- a/docs/ru/docs/how-to/configure-swagger-ui.md +++ b/docs/ru/docs/how-to/configure-swagger-ui.md @@ -18,7 +18,7 @@ FastAPI преобразует эти настройки в **JSON**, чтобы Но вы можете отключить её, установив `syntaxHighlight` в `False`: -{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} …и после этого Swagger UI больше не будет показывать подсветку синтаксиса: @@ -28,7 +28,7 @@ FastAPI преобразует эти настройки в **JSON**, чтобы Аналогично вы можете задать тему подсветки синтаксиса с ключом "syntaxHighlight.theme" (обратите внимание, что посередине стоит точка): -{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} Эта настройка изменит цветовую тему подсветки синтаксиса: @@ -46,7 +46,7 @@ FastAPI включает некоторые параметры конфигур Например, чтобы отключить `deepLinking`, можно передать такие настройки в `swagger_ui_parameters`: -{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} ## Другие параметры Swagger UI { #other-swagger-ui-parameters } diff --git a/docs/ru/docs/how-to/custom-docs-ui-assets.md b/docs/ru/docs/how-to/custom-docs-ui-assets.md index f524911e6..e3c31b32c 100644 --- a/docs/ru/docs/how-to/custom-docs-ui-assets.md +++ b/docs/ru/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ Чтобы отключить её, установите их URL в значение `None` при создании вашего приложения `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} ### Подключить пользовательскую документацию { #include-the-custom-docs } @@ -34,7 +34,7 @@ Аналогично и для ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} /// tip | Совет @@ -50,7 +50,7 @@ Swagger UI сделает это за вас «за кулисами», но д Чтобы убедиться, что всё работает, создайте *операцию пути*: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} ### Тестирование { #test-it } @@ -118,7 +118,7 @@ Swagger UI сделает это за вас «за кулисами», но д * Импортируйте `StaticFiles`. * Смонтируйте экземпляр `StaticFiles()` в определённый путь. -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} ### Протестируйте статические файлы { #test-the-static-files } @@ -144,7 +144,7 @@ Swagger UI сделает это за вас «за кулисами», но д Чтобы отключить её, установите их URL в значение `None` при создании вашего приложения `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} ### Подключить пользовательскую документацию со статическими файлами { #include-the-custom-docs-for-static-files } @@ -160,7 +160,7 @@ Swagger UI сделает это за вас «за кулисами», но д Аналогично и для ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} /// tip | Совет @@ -176,7 +176,7 @@ Swagger UI сделает это за вас «за кулисами», но д Чтобы убедиться, что всё работает, создайте *операцию пути*: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} ### Тестирование UI со статическими файлами { #test-static-files-ui } diff --git a/docs/ru/docs/how-to/extending-openapi.md b/docs/ru/docs/how-to/extending-openapi.md index 1d69cbdb3..197a1790a 100644 --- a/docs/ru/docs/how-to/extending-openapi.md +++ b/docs/ru/docs/how-to/extending-openapi.md @@ -24,7 +24,7 @@ * `version`: Версия вашего API, например `2.5.0`. * `openapi_version`: Версия используемой спецификации OpenAPI. По умолчанию — последняя: `3.1.0`. * `summary`: Краткое описание API. -* `description`: Описание вашего API; может включать Markdown и будет отображается в документации. +* `description`: Описание вашего API; может включать Markdown и будет отображаться в документации. * `routes`: Список маршрутов — это каждая зарегистрированная *операция пути*. Берутся из `app.routes`. /// info | Информация @@ -43,19 +43,19 @@ Сначала напишите приложение **FastAPI** как обычно: -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[1,4,7:9] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *} ### Сгенерируйте схему OpenAPI { #generate-the-openapi-schema } Затем используйте ту же вспомогательную функцию для генерации схемы OpenAPI внутри функции `custom_openapi()`: -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *} ### Измените схему OpenAPI { #modify-the-openapi-schema } Теперь можно добавить расширение ReDoc, добавив кастомный `x-logo` в «объект» `info` в схеме OpenAPI: -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *} ### Кэшируйте схему OpenAPI { #cache-the-openapi-schema } @@ -65,13 +65,13 @@ Она будет создана один раз, а затем тот же кэшированный вариант будет использоваться для последующих запросов. -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *} ### Переопределите метод { #override-the-method } Теперь вы можете заменить метод `.openapi()` на вашу новую функцию. -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *} ### Проверьте { #check-it } diff --git a/docs/ru/docs/how-to/graphql.md b/docs/ru/docs/how-to/graphql.md index 50c321e7d..9e28d6182 100644 --- a/docs/ru/docs/how-to/graphql.md +++ b/docs/ru/docs/how-to/graphql.md @@ -35,7 +35,7 @@ Вот небольшой пример того, как можно интегрировать Strawberry с FastAPI: -{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *} +{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *} Подробнее о Strawberry можно узнать в документации Strawberry. diff --git a/docs/ru/docs/how-to/index.md b/docs/ru/docs/how-to/index.md index 228c125dd..75e7fff26 100644 --- a/docs/ru/docs/how-to/index.md +++ b/docs/ru/docs/how-to/index.md @@ -1,13 +1,13 @@ # Как сделать — Рецепты { #how-to-recipes } -Здесь вы найдете разные рецепты и руководства «как сделать» по различным темам. +Здесь вы найдете разные рецепты и руководства «как сделать» по **различным темам**. -Большинство из этих идей более-менее независимы, и в большинстве случаев вам стоит изучать их только если они напрямую относятся к вашему проекту. +Большинство из этих идей более-менее **независимы**, и в большинстве случаев вам стоит изучать их только если они напрямую относятся к **вашему проекту**. Если что-то кажется интересным и полезным для вашего проекта, смело изучайте; в противном случае, вероятно, можно просто пропустить. /// tip | Совет -Если вы хотите изучить FastAPI структурированно (рекомендуется), вместо этого читайте [Учебник — Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} по главам. +Если вы хотите **изучить FastAPI** структурированно (рекомендуется), вместо этого читайте [Учебник — Руководство пользователя](../tutorial/index.md){.internal-link target=_blank} по главам. /// diff --git a/docs/ru/docs/index.md b/docs/ru/docs/index.md index b1a0c9a2e..877014a08 100644 --- a/docs/ru/docs/index.md +++ b/docs/ru/docs/index.md @@ -40,7 +40,7 @@ FastAPI — это современный, быстрый (высокопрои * **Скорость**: Очень высокая производительность, на уровне **NodeJS** и **Go** (благодаря Starlette и Pydantic). [Один из самых быстрых доступных фреймворков Python](#performance). * **Быстрота разработки**: Увеличьте скорость разработки фич примерно на 200–300%. * * **Меньше ошибок**: Сократите примерно на 40% количество ошибок, вызванных человеком (разработчиком). * -* **Интуитивность**: Отличная поддержка редактора кода. Автозавершение везде. Меньше времени на отладку. +* **Интуитивность**: Отличная поддержка редактора кода. Автозавершение везде. Меньше времени на отладку. * **Простота**: Разработан так, чтобы его было легко использовать и осваивать. Меньше времени на чтение документации. * **Краткость**: Минимизируйте дублирование кода. Несколько возможностей из каждого объявления параметров. Меньше ошибок. * **Надежность**: Получите код, готовый к продакшн. С автоматической интерактивной документацией. @@ -127,7 +127,7 @@ FastAPI — это современный, быстрый (высокопрои -Если вы создаёте приложение CLI для использования в терминале вместо веб-API, посмотрите **Typer**. +Если вы создаёте приложение CLI для использования в терминале вместо веб-API, посмотрите **Typer**. **Typer** — младший брат FastAPI. И он задуман как **FastAPI для CLI**. ⌨️ 🚀 @@ -368,7 +368,7 @@ item: Item * Валидацию данных: * Автоматические и понятные ошибки, когда данные некорректны. * Валидацию даже для глубоко вложенных объектов JSON. -* Преобразование входных данных: из сети в данные и типы Python. Чтение из: +* Преобразование входных данных: из сети в данные и типы Python. Чтение из: * JSON. * Параметров пути. * Параметров запроса. @@ -376,7 +376,7 @@ item: Item * HTTP-заголовков. * Форм. * Файлов. -* Преобразование выходных данных: из данных и типов Python в данные сети (например, JSON): +* Преобразование выходных данных: из данных и типов Python в данные сети (например, JSON): * Преобразование типов Python (`str`, `int`, `float`, `bool`, `list` и т.д.). * Объекты `datetime`. * Объекты `UUID`. @@ -439,7 +439,7 @@ item: Item * Объявление **параметров** из других источников: **HTTP-заголовки**, **cookies**, **поля формы** и **файлы**. * Как задать **ограничения валидации** вроде `maximum_length` или `regex`. -* Очень мощную и простую в использовании систему **внедрения зависимостей**. +* Очень мощную и простую в использовании систему **внедрения зависимостей**. * Безопасность и аутентификацию, включая поддержку **OAuth2** с **JWT токенами** и **HTTP Basic** аутентификацию. * Более продвинутые (но столь же простые) приёмы объявления **глубоко вложенных JSON-моделей** (спасибо Pydantic). * Интеграцию **GraphQL** с Strawberry и другими библиотеками. @@ -524,7 +524,7 @@ FastAPI зависит от Pydantic и Starlette. * httpx — обязателен, если вы хотите использовать `TestClient`. * jinja2 — обязателен, если вы хотите использовать конфигурацию шаблонов по умолчанию. -* python-multipart - обязателен, если вы хотите поддерживать «парсинг» форм через `request.form()`. +* python-multipart - обязателен, если вы хотите поддерживать «парсинг» форм через `request.form()`. Используется FastAPI: diff --git a/docs/ru/docs/project-generation.md b/docs/ru/docs/project-generation.md index dbedf76fe..8155457e3 100644 --- a/docs/ru/docs/project-generation.md +++ b/docs/ru/docs/project-generation.md @@ -18,7 +18,7 @@ - 🤖 Автоматически сгенерированный фронтенд‑клиент. - 🧪 [Playwright](https://playwright.dev) для End‑to‑End тестирования. - 🦇 Поддержка тёмной темы. -- 🐋 [Docker Compose](https://www.docker.com) для разработки и продакшна. +- 🐋 [Docker Compose](https://www.docker.com) для разработки и продакшн. - 🔒 Безопасное хэширование паролей по умолчанию. - 🔑 Аутентификация по JWT‑токенам. - 📫 Восстановление пароля по электронной почте. diff --git a/docs/ru/docs/python-types.md b/docs/ru/docs/python-types.md index ae4a1e2b7..95153c388 100644 --- a/docs/ru/docs/python-types.md +++ b/docs/ru/docs/python-types.md @@ -2,7 +2,7 @@ Python поддерживает необязательные «подсказки типов» (их также называют «аннотациями типов»). -Эти **«подсказки типов»** или аннотации — это специальный синтаксис, позволяющий объявлять тип переменной. +Эти **«подсказки типов»** или аннотации — это специальный синтаксис, позволяющий объявлять тип переменной. Объявляя типы для ваших переменных, редакторы кода и инструменты смогут лучше вас поддерживать. @@ -22,7 +22,7 @@ Python поддерживает необязательные «подсказк Давайте начнем с простого примера: -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} Вызов этой программы выводит: @@ -34,9 +34,9 @@ John Doe * Принимает `first_name` и `last_name`. * Преобразует первую букву каждого значения в верхний регистр с помощью `title()`. -* Соединяет их пробелом посередине. +* Соединяет их пробелом посередине. -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### Отредактируем пример { #edit-it } @@ -78,7 +78,7 @@ John Doe Это и есть «подсказки типов»: -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} Это не то же самое, что объявление значений по умолчанию, как, например: @@ -106,7 +106,7 @@ John Doe Посмотрите на эту функцию — у неё уже есть подсказки типов: -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} Так как редактор кода знает типы переменных, вы получаете не только автозавершение, но и проверки ошибок: @@ -114,7 +114,7 @@ John Doe Теперь вы знаете, что нужно исправить — преобразовать `age` в строку с помощью `str(age)`: -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} ## Объявление типов { #declaring-types } @@ -133,29 +133,32 @@ John Doe * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} -### Generic-типы с параметрами типов { #generic-types-with-type-parameters } +### Модуль `typing` { #typing-module } -Есть структуры данных, которые могут содержать другие значения, например, `dict`, `list`, `set` и `tuple`. И внутренние значения тоже могут иметь свой тип. +Для некоторых дополнительных сценариев может понадобиться импортировать что-то из стандартного модуля `typing`. Например, когда вы хотите объявить, что что-то имеет «любой тип», можно использовать `Any` из `typing`: -Такие типы, которые содержат внутренние типы, называют «**generic**»-типами. И их можно объявлять, в том числе с указанием внутренних типов. +```python +from typing import Any -Чтобы объявлять эти типы и их внутренние типы, вы можете использовать стандартный модуль Python `typing`. Он существует специально для поддержки подсказок типов. -#### Новые версии Python { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -Синтаксис с использованием `typing` **совместим** со всеми версиями, от Python 3.6 до самых новых, включая Python 3.9, Python 3.10 и т.д. +### Generic-типы { #generic-types } -По мере развития Python **новые версии** получают улучшенную поддержку этих аннотаций типов, и во многих случаях вам даже не нужно импортировать и использовать модуль `typing`, чтобы объявлять аннотации типов. +Некоторые типы могут принимать «параметры типов» в квадратных скобках, чтобы определить их внутренние типы. Например, «список строк» объявляется как `list[str]`. -Если вы можете выбрать более свежую версию Python для проекта, вы получите дополнительную простоту. +Такие типы, которые принимают параметры типов, называются **Generic-типами** или **Generics**. -Во всей документации есть примеры, совместимые с каждой версией Python (когда есть различия). +Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри): -Например, «**Python 3.6+**» означает совместимость с Python 3.6 и выше (включая 3.7, 3.8, 3.9, 3.10 и т.д.). А «**Python 3.9+**» — совместимость с Python 3.9 и выше (включая 3.10 и т.п.). - -Если вы можете использовать **последние версии Python**, используйте примеры для самой новой версии — у них будет **самый лучший и простой синтаксис**, например, «**Python 3.10+**». +* `list` +* `tuple` +* `set` +* `dict` #### List { #list } @@ -167,7 +170,7 @@ John Doe Так как список — это тип, содержащий внутренние типы, укажите их в квадратных скобках: -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} /// info | Информация @@ -193,7 +196,7 @@ John Doe Аналогично вы бы объявили `tuple` и `set`: -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} Это означает: @@ -208,7 +211,7 @@ John Doe Второй параметр типа — для значений `dict`: -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} Это означает: @@ -220,44 +223,20 @@ John Doe Вы можете объявить, что переменная может быть **одним из нескольких типов**, например, `int` или `str`. -В Python 3.6 и выше (включая Python 3.10) вы можете использовать тип `Union` из `typing` и перечислить в квадратных скобках все допустимые типы. +Чтобы это определить, используйте вертикальную черту (`|`) для разделения обоих типов. -В Python 3.10 также появился **новый синтаксис**, где допустимые типы можно указать через вертикальную черту (`|`). - -//// tab | Python 3.10+ +Это называется «объединение» (union), потому что переменная может быть чем угодно из объединения этих двух множеств типов. ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -В обоих случаях это означает, что `item` может быть `int` или `str`. +Это означает, что `item` может быть `int` или `str`. #### Возможно `None` { #possibly-none } Вы можете объявить, что значение может иметь определённый тип, например `str`, но также может быть и `None`. -В Python 3.6 и выше (включая Python 3.10) это можно объявить, импортировав и используя `Optional` из модуля `typing`. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -Использование `Optional[str]` вместо просто `str` позволит редактору кода помочь вам обнаружить ошибки, когда вы предполагаете, что значение всегда `str`, хотя на самом деле оно может быть и `None`. - -`Optional[Something]` — это на самом деле сокращение для `Union[Something, None]`, они эквивалентны. - -Это также означает, что в Python 3.10 вы можете использовать `Something | None`: - //// tab | Python 3.10+ ```Python hl_lines="1" @@ -266,96 +245,7 @@ John Doe //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ альтернативный вариант - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### Использовать `Union` или `Optional` { #using-union-or-optional } - -Если вы используете версию Python ниже 3.10, вот совет с моей весьма **субъективной** точки зрения: - -* 🚨 Избегайте использования `Optional[SomeType]` -* Вместо этого ✨ **используйте `Union[SomeType, None]`** ✨. - -Оба варианта эквивалентны и внутри одинаковы, но я бы рекомендовал `Union` вместо `Optional`, потому что слово «**optional**» («необязательный») может навести на мысль, что значение необязательное, хотя на самом деле оно означает «может быть `None`», даже если параметр не является необязательным и всё ещё обязателен. - -Мне кажется, `Union[SomeType, None]` более явно выражает смысл. - -Речь только о словах и названиях. Но эти слова могут влиять на то, как вы и ваши коллеги думаете о коде. - -В качестве примера возьмём эту функцию: - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -Параметр `name` определён как `Optional[str]`, но он **не необязательный** — вы не можете вызвать функцию без этого параметра: - -```Python -say_hi() # О нет, это вызывает ошибку! 😱 -``` - -Параметр `name` всё ещё **обязателен** (не *optional*), потому что у него нет значения по умолчанию. При этом `name` принимает `None` как значение: - -```Python -say_hi(name=None) # Это работает, None допустим 🎉 -``` - -Хорошая новость: как только вы перейдёте на Python 3.10, об этом можно не переживать — вы сможете просто использовать `|` для объединения типов: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -И тогда вам не придётся задумываться о названиях вроде `Optional` и `Union`. 😎 - -#### Generic-типы { #generic-types } - -Типы, которые принимают параметры типов в квадратных скобках, называются **Generic-типами** или **Generics**, например: - -//// tab | Python 3.10+ - -Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри): - -* `list` -* `tuple` -* `set` -* `dict` - -И, как и в предыдущих версиях Python, из модуля `typing`: - -* `Union` -* `Optional` -* ...и другие. - -В Python 3.10, как альтернативу generics `Union` и `Optional`, можно использовать вертикальную черту (`|`) для объявления объединений типов — это гораздо лучше и проще. - -//// - -//// tab | Python 3.9+ - -Вы можете использовать те же встроенные типы как generics (с квадратными скобками и типами внутри): - -* `list` -* `tuple` -* `set` -* `dict` - -И generics из модуля `typing`: - -* `Union` -* `Optional` -* ...и другие. - -//// +Использование `str | None` вместо просто `str` позволит редактору кода помочь вам обнаружить ошибки, когда вы предполагаете, что значение всегда `str`, хотя на самом деле оно может быть и `None`. ### Классы как типы { #classes-as-types } @@ -363,11 +253,11 @@ say_hi(name=None) # Это работает, None допустим 🎉 Допустим, у вас есть класс `Person` с именем: -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} Тогда вы можете объявить переменную типа `Person`: -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} И снова вы получите полную поддержку редактора кода: @@ -401,21 +291,15 @@ say_hi(name=None) # Это работает, None допустим 🎉 **FastAPI** целиком основан на Pydantic. -Вы увидите намного больше всего этого на практике в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}. - -/// tip | Совет - -У Pydantic есть особое поведение, когда вы используете `Optional` или `Union[Something, None]` без значения по умолчанию. Подробнее читайте в документации Pydantic: Required Optional fields. - -/// +Вы увидите намного больше всего этого на практике в [Учебник - Руководство пользователя](tutorial/index.md){.internal-link target=_blank}. ## Подсказки типов с аннотациями метаданных { #type-hints-with-metadata-annotations } -В Python также есть возможность добавлять **дополнительные метаданные** к подсказкам типов с помощью `Annotated`. +В Python также есть возможность добавлять **дополнительные метаданные** к подсказкам типов с помощью `Annotated`. -Начиная с Python 3.9, `Annotated` входит в стандартную библиотеку, поэтому вы можете импортировать его из `typing`. +Вы можете импортировать `Annotated` из `typing`. -{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} Сам Python ничего не делает с `Annotated`. А для редакторов кода и других инструментов тип по-прежнему `str`. @@ -453,7 +337,7 @@ say_hi(name=None) # Это работает, None допустим 🎉 * **Документирования** API с использованием OpenAPI: * что затем используется пользовательскими интерфейсами автоматической интерактивной документации. -Всё это может звучать абстрактно. Не волнуйтесь. Вы увидите всё это в действии в [Руководстве пользователя](tutorial/index.md){.internal-link target=_blank}. +Всё это может звучать абстрактно. Не волнуйтесь. Вы увидите всё это в действии в [Учебник - Руководство пользователя](tutorial/index.md){.internal-link target=_blank}. Важно то, что, используя стандартные типы Python в одном месте (вместо добавления дополнительных классов, декораторов и т.д.), **FastAPI** сделает за вас большую часть работы. diff --git a/docs/ru/docs/tutorial/background-tasks.md b/docs/ru/docs/tutorial/background-tasks.md index 8d7b7442f..9fa7a8502 100644 --- a/docs/ru/docs/tutorial/background-tasks.md +++ b/docs/ru/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ Сначала импортируйте `BackgroundTasks` и объявите параметр в вашей функции‑обработчике пути с типом `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} **FastAPI** создаст объект типа `BackgroundTasks` для вас и передаст его через этот параметр. @@ -31,13 +31,13 @@ Так как операция записи не использует `async` и `await`, мы определим функцию как обычную `def`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} ## Добавление фоновой задачи { #add-the-background-task } Внутри вашей функции‑обработчика пути передайте функцию задачи объекту фоновых задач методом `.add_task()`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} `.add_task()` принимает следующие аргументы: @@ -47,7 +47,7 @@ ## Встраивание зависимостей { #dependency-injection } -Использование `BackgroundTasks` также работает с системой встраивания зависимостей, вы можете объявить параметр типа `BackgroundTasks` на нескольких уровнях: в функции‑обработчике пути, в зависимости (dependable), в подзависимости и т. д. +Использование `BackgroundTasks` также работает с системой встраивания зависимостей, вы можете объявить параметр типа `BackgroundTasks` на нескольких уровнях: в функции‑обработчике пути, в зависимости (dependable), в подзависимости и т.д. **FastAPI** знает, что делать в каждом случае и как переиспользовать один и тот же объект, так чтобы все фоновые задачи были объединены и затем выполнены в фоне: @@ -73,7 +73,7 @@ ## Предостережение { #caveat } -Если вам нужно выполнять тяжелые вычисления в фоне, и при этом они не обязательно должны запускаться тем же процессом (например, вам не нужно делиться памятью, переменными и т. п.), вам могут подойти более мощные инструменты, такие как Celery. +Если вам нужно выполнять тяжелые вычисления в фоне, и при этом они не обязательно должны запускаться тем же процессом (например, вам не нужно делиться памятью, переменными и т.п.), вам могут подойти более мощные инструменты, такие как Celery. Они обычно требуют более сложной конфигурации, менеджера очереди сообщений/заданий (например, RabbitMQ или Redis), но позволяют запускать фоновые задачи в нескольких процессах и, что особенно важно, на нескольких серверах. diff --git a/docs/ru/docs/tutorial/bigger-applications.md b/docs/ru/docs/tutorial/bigger-applications.md index 76304523c..3fb36d5a2 100644 --- a/docs/ru/docs/tutorial/bigger-applications.md +++ b/docs/ru/docs/tutorial/bigger-applications.md @@ -85,7 +85,7 @@ from app.routers import items Точно так же, как и в случае с классом `FastAPI`, вам нужно импортировать и создать его «экземпляр»: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} ### *Операции пути* с `APIRouter` { #path-operations-with-apirouter } @@ -93,7 +93,7 @@ from app.routers import items Используйте его так же, как вы использовали бы класс `FastAPI`: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} Вы можете думать об `APIRouter` как об «мини-классе `FastAPI`». @@ -117,7 +117,7 @@ from app.routers import items Теперь мы воспользуемся простой зависимостью, чтобы прочитать кастомный HTTP-заголовок `X-Token`: -{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | Подсказка @@ -149,7 +149,7 @@ from app.routers import items Таким образом, вместо того чтобы добавлять всё это в каждую *операцию пути*, мы можем добавить это в `APIRouter`. -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} Так как путь каждой *операции пути* должен начинаться с `/`, как здесь: @@ -208,7 +208,7 @@ async def read_item(item_id: str): Поэтому мы используем относительный импорт с `..` для зависимостей: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} #### Как работает относительный импорт { #how-relative-imports-work } @@ -279,7 +279,7 @@ from ...dependencies import get_token_header Но мы всё равно можем добавить _ещё_ `tags`, которые будут применяться к конкретной *операции пути*, а также дополнительные `responses`, специфичные для этой *операции пути*: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | Подсказка @@ -305,13 +305,13 @@ from ...dependencies import get_token_header И мы даже можем объявить [глобальные зависимости](dependencies/global-dependencies.md){.internal-link target=_blank}, которые будут объединены с зависимостями для каждого `APIRouter`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} ### Импорт `APIRouter` { #import-the-apirouter } Теперь мы импортируем другие подмодули, содержащие `APIRouter`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} Так как файлы `app/routers/users.py` и `app/routers/items.py` являются подмодулями, входящими в один и тот же Python-пакет `app`, мы можем использовать одну точку `.` для импорта через «относительные импорты». @@ -374,13 +374,13 @@ from .routers.users import router Поэтому, чтобы иметь возможность использовать обе в одном файле, мы импортируем подмодули напрямую: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} ### Подключение `APIRouter` для `users` и `items` { #include-the-apirouters-for-users-and-items } Теперь давайте подключим `router` из подмодулей `users` и `items`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} /// info | Примечание @@ -420,13 +420,13 @@ from .routers.users import router Для этого примера всё будет очень просто. Но допустим, что поскольку он используется совместно с другими проектами в организации, мы не можем модифицировать его и добавить `prefix`, `dependencies`, `tags` и т.д. непосредственно в `APIRouter`: -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} Но мы всё равно хотим задать пользовательский `prefix` при подключении `APIRouter`, чтобы все его *операции пути* начинались с `/admin`, хотим защитить его с помощью `dependencies`, которые у нас уже есть для этого проекта, и хотим включить `tags` и `responses`. Мы можем объявить всё это, не изменяя исходный `APIRouter`, передав эти параметры в `app.include_router()`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} Таким образом исходный `APIRouter` не будет модифицирован, и мы сможем использовать файл `app/internal/admin.py` сразу в нескольких проектах организации. @@ -447,7 +447,7 @@ from .routers.users import router Здесь мы делаем это... просто чтобы показать, что можем 🤷: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} и это будет работать корректно вместе со всеми другими *операциями пути*, добавленными через `app.include_router()`. diff --git a/docs/ru/docs/tutorial/body-multiple-params.md b/docs/ru/docs/tutorial/body-multiple-params.md index 9d9400494..ddd9c6fdd 100644 --- a/docs/ru/docs/tutorial/body-multiple-params.md +++ b/docs/ru/docs/tutorial/body-multiple-params.md @@ -52,7 +52,7 @@ } ``` -/// note | Внимание +/// note | Заметка Обратите внимание, что хотя параметр `item` был объявлен таким же способом, как и раньше, теперь предполагается, что он находится внутри тела с ключом `item`. @@ -104,12 +104,6 @@ q: str | None = None ``` -Или в Python 3.9: - -```Python -q: Union[str, None] = None -``` - Например: {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/ru/docs/tutorial/body-nested-models.md b/docs/ru/docs/tutorial/body-nested-models.md index 4c914b97f..6610b209c 100644 --- a/docs/ru/docs/tutorial/body-nested-models.md +++ b/docs/ru/docs/tutorial/body-nested-models.md @@ -163,7 +163,7 @@ images: list[Image] например так: -{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} ## Поддержка редактора кода везде { #editor-support-everywhere } @@ -193,7 +193,7 @@ images: list[Image] В этом случае вы принимаете любой `dict`, пока у него есть ключи типа `int` со значениями типа `float`: -{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} /// tip | Совет diff --git a/docs/ru/docs/tutorial/body.md b/docs/ru/docs/tutorial/body.md index 537d7ebc9..3e55607da 100644 --- a/docs/ru/docs/tutorial/body.md +++ b/docs/ru/docs/tutorial/body.md @@ -72,7 +72,7 @@ * Проведёт валидацию данных. * Если данные некорректны, вернёт понятную и наглядную ошибку, указывающую, где именно и что было некорректно. * Передаст полученные данные в параметр `item`. - * Поскольку внутри функции вы объявили его с типом `Item`, у вас будет поддержка со стороны редактора кода (автозавершение и т. п.) для всех атрибутов и их типов. + * Поскольку внутри функции вы объявили его с типом `Item`, у вас будет поддержка со стороны редактора кода (автозавершение и т.п.) для всех атрибутов и их типов. * Сгенерирует определения JSON Schema для вашей модели; вы можете использовать их и в других местах, если это имеет смысл для вашего проекта. * Эти схемы будут частью сгенерированной схемы OpenAPI и будут использоваться автоматической документацией UIs. @@ -148,14 +148,14 @@ JSON Schema ваших моделей будет частью сгенериро Параметры функции будут распознаны следующим образом: * Если параметр также объявлен в **пути**, он будет использоваться как path-параметр. -* Если параметр имеет **скалярный тип** (например, `int`, `float`, `str`, `bool` и т. п.), он будет интерпретирован как параметр **запроса**. +* Если параметр имеет **скалярный тип** (например, `int`, `float`, `str`, `bool` и т.п.), он будет интерпретирован как параметр **запроса**. * Если параметр объявлен как тип **модели Pydantic**, он будет интерпретирован как **тело** запроса. /// note | Заметка FastAPI понимает, что значение `q` не является обязательным из-за значения по умолчанию `= None`. -Аннотации типов `str | None` (Python 3.10+) или `Union` в `Union[str, None]` (Python 3.9+) не используются FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`. +Аннотация типов `str | None` не используется FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`. Но добавление аннотаций типов позволит вашему редактору кода лучше вас поддерживать и обнаруживать ошибки. diff --git a/docs/ru/docs/tutorial/cookie-param-models.md b/docs/ru/docs/tutorial/cookie-param-models.md index 182813afd..9b34cf030 100644 --- a/docs/ru/docs/tutorial/cookie-param-models.md +++ b/docs/ru/docs/tutorial/cookie-param-models.md @@ -46,7 +46,7 @@ В некоторых случаях (не особо часто встречающихся) вам может понадобиться **ограничить** cookies, которые вы хотите получать. -Теперь ваш API сам решает, принимать ли cookies. 🤪🍪 +Теперь у вашего API есть возможность контролировать своё согласие на использование cookie. 🤪🍪 Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) любые дополнительные (`extra`) поля: @@ -54,9 +54,9 @@ Если клиент попробует отправить **дополнительные cookies**, то в ответ он получит **ошибку**. -Бедные баннеры cookies, они всеми силами пытаются получить ваше согласие — и всё ради того, чтобы API его отклонил. 🍪 +Бедные баннеры cookies, они всеми силами пытаются получить ваше согласие — и всё ради того, чтобы API его отклонил. 🍪 -Например, если клиент попытается отправить cookie `santa_tracker` со значением `good-list-please`, то в ответ он получит **ошибку**, сообщающую ему, что cookie `santa_tracker` не разрешён: +Например, если клиент попытается отправить cookie `santa_tracker` со значением `good-list-please`, то в ответ он получит **ошибку**, сообщающую ему, что `santa_tracker` cookie не разрешён: ```json { @@ -73,4 +73,4 @@ ## Заключение { #summary } -Вы можете использовать **Pydantic-модели** для объявления **cookies** в **FastAPI**. 😎 +Вы можете использовать **Pydantic-модели** для объявления **cookies** в **FastAPI**. 😎 diff --git a/docs/ru/docs/tutorial/cookie-params.md b/docs/ru/docs/tutorial/cookie-params.md index 2d2eff8d7..8dad3873e 100644 --- a/docs/ru/docs/tutorial/cookie-params.md +++ b/docs/ru/docs/tutorial/cookie-params.md @@ -32,11 +32,11 @@ /// info | Дополнительная информация -Имейте в виду, что, поскольку браузеры обрабатывают cookies особым образом и «за кулисами», они не позволяют JavaScript просто так получать к ним доступ. +Имейте в виду, что, поскольку **браузеры обрабатывают cookies** особым образом и «за кулисами», они **не** позволяют **JavaScript** просто так получать к ним доступ. -Если вы откроете интерфейс документации API на `/docs`, вы сможете увидеть документацию по cookies для ваших операций пути. +Если вы откроете **интерфейс документации API** на `/docs`, вы сможете увидеть **документацию** по cookies для ваших *операции пути*. -Но даже если вы заполните данные и нажмёте «Execute», поскольку UI документации работает с JavaScript, cookies отправлены не будут, и вы увидите сообщение об ошибке, как будто вы не указали никаких значений. +Но даже если вы **заполните данные** и нажмёте «Execute», поскольку UI документации работает с **JavaScript**, cookies отправлены не будут, и вы увидите сообщение об **ошибке**, как будто вы не указали никаких значений. /// diff --git a/docs/ru/docs/tutorial/cors.md b/docs/ru/docs/tutorial/cors.md index d09a31e2c..feaa15968 100644 --- a/docs/ru/docs/tutorial/cors.md +++ b/docs/ru/docs/tutorial/cors.md @@ -46,7 +46,7 @@ * Отдельных HTTP-методов (`POST`, `PUT`) или всех вместе, используя `"*"`. * Отдельных HTTP-заголовков или всех вместе, используя `"*"`. -{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} `CORSMiddleware` использует "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте. @@ -77,7 +77,7 @@ ## Больше информации { #more-info } -Для получения более подробной информации о CORS обратитесь к документации CORS от Mozilla. +Для получения более подробной информации о CORS обратитесь к документации CORS от Mozilla. /// note | Технические детали diff --git a/docs/ru/docs/tutorial/debugging.md b/docs/ru/docs/tutorial/debugging.md index 51955835e..483fe8086 100644 --- a/docs/ru/docs/tutorial/debugging.md +++ b/docs/ru/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ В вашем FastAPI приложении, импортируйте и вызовите `uvicorn` напрямую: -{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} ### Описание `__name__ == "__main__"` { #about-name-main } @@ -42,7 +42,7 @@ $ python myapp.py то встроенная переменная `__name__`, автоматически создаваемая Python в вашем файле, будет иметь значение строкового типа `"__main__"`. -Тогда выполнится условие и эта часть кода: +Тогда эта часть кода: ```Python uvicorn.run(app, host="0.0.0.0", port=8000) @@ -59,7 +59,7 @@ $ python myapp.py ```Python from myapp import app -# Some more code +# Еще немного кода ``` то автоматическая создаваемая внутри файла `myapp.py` переменная `__name__` будет иметь значение отличающееся от `"__main__"`. @@ -99,7 +99,7 @@ from myapp import app --- -Если используете Pycharm, вы можете выполнить следующие шаги: +Если используете PyCharm, вы можете выполнить следующие шаги: * Открыть "Run" меню. * Выбрать опцию "Debug...". diff --git a/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md index a38e885d4..9a3171e9f 100644 --- a/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md @@ -4,7 +4,7 @@ ## `dict` из предыдущего примера { #a-dict-from-the-previous-example } -В предыдущем примере мы возвращали `dict` из нашей зависимости: +В предыдущем примере мы возвращали `dict` из нашей зависимости («dependable»): {* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *} @@ -67,7 +67,7 @@ fluffy = Cat(name="Mr Fluffy") Это относится и к вызываемым объектам без параметров. Работа с ними происходит точно так же, как и для *функций-обработчиков пути* без параметров. -Теперь мы можем изменить зависимость `common_parameters`, указанную выше, на класс `CommonQueryParams`: +Теперь мы можем изменить зависимость («dependable») `common_parameters`, указанную выше, на класс `CommonQueryParams`: {* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *} @@ -101,7 +101,7 @@ fluffy = Cat(name="Mr Fluffy") Обратите внимание, что в приведенном выше коде мы два раза пишем `CommonQueryParams`: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ без Annotated /// tip | Подсказка @@ -137,7 +137,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) В этом случае первый `CommonQueryParams`, в: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ без Annotated /// tip | Подсказка @@ -163,7 +163,7 @@ commons: CommonQueryParams ... На самом деле можно написать просто: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ без Annotated /// tip | Подсказка @@ -197,7 +197,7 @@ commons = Depends(CommonQueryParams) Но вы видите, что здесь мы имеем некоторое повторение кода, дважды написав `CommonQueryParams`: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ без Annotated /// tip | Подсказка @@ -225,7 +225,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) Вместо того чтобы писать: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ без Annotated /// tip | Подсказка @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...следует написать: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ без Annotated /// tip | Подсказка diff --git a/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index ef5664448..4cfc4e699 100644 --- a/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/ru/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -14,7 +14,7 @@ Это должен быть `list` состоящий из `Depends()`: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} Зависимости из dependencies выполнятся так же, как и обычные зависимости. Но их значения (если они были) не будут переданы в *функцию операции пути*. @@ -30,7 +30,7 @@ /// info | Примечание -В этом примере мы используем выдуманные пользовательские заголовки `X-Key` и `X-Token`. +В этом примере мы используем выдуманные пользовательские HTTP-заголовки `X-Key` и `X-Token`. Но в реальных проектах, при внедрении системы безопасности, вы получите больше пользы используя интегрированные [средства защиты (следующая глава)](../security/index.md){.internal-link target=_blank}. @@ -42,15 +42,15 @@ ### Требования к зависимостям { #dependency-requirements } -Они могут объявлять требования к запросу (например заголовки) или другие подзависимости: +Они могут объявлять требования к запросу (например HTTP-заголовки) или другие подзависимости: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} ### Вызов исключений { #raise-exceptions } Зависимости из dependencies могут вызывать исключения с помощью `raise`, как и обычные зависимости: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} ### Возвращаемые значения { #return-values } @@ -58,7 +58,7 @@ Таким образом, вы можете переиспользовать обычную зависимость (возвращающую значение), которую вы уже используете где-то в другом месте, и хотя значение не будет использоваться, зависимость будет выполнена: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} ## Зависимости для группы *операций путей* { #dependencies-for-a-group-of-path-operations } diff --git a/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md index dc202db61..03a7c083c 100644 --- a/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,6 +1,6 @@ # Зависимости с yield { #dependencies-with-yield } -FastAPI поддерживает зависимости, которые выполняют некоторые дополнительные шаги после завершения. +FastAPI поддерживает зависимости, которые выполняют некоторые дополнительные шаги после завершения. Для этого используйте `yield` вместо `return`, а дополнительные шаги (код) напишите после него. @@ -29,15 +29,15 @@ FastAPI поддерживает зависимости, которые выпо Перед созданием ответа будет выполнен только код до и включая оператор `yield`: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[2:4] *} Значение, полученное из `yield`, внедряется в *операции пути* и другие зависимости: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[4] *} Код, следующий за оператором `yield`, выполняется после ответа: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[5:6] *} /// tip | Подсказка @@ -57,7 +57,7 @@ FastAPI поддерживает зависимости, которые выпо Точно так же можно использовать `finally`, чтобы убедиться, что обязательные шаги при выходе выполнены независимо от того, было ли исключение или нет. -{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[3,5] *} ## Подзависимости с `yield` { #sub-dependencies-with-yield } @@ -67,7 +67,7 @@ FastAPI поддерживает зависимости, которые выпо Например, `dependency_c` может зависеть от `dependency_b`, а `dependency_b` — от `dependency_a`: -{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[6,14,22] *} И все они могут использовать `yield`. @@ -75,7 +75,7 @@ FastAPI поддерживает зависимости, которые выпо И, в свою очередь, `dependency_b` нуждается в том, чтобы значение из `dependency_a` (здесь `dep_a`) было доступно для её кода выхода. -{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *} +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[18:19,26:27] *} Точно так же можно иметь часть зависимостей с `yield`, часть — с `return`, и какие-то из них могут зависеть друг от друга. @@ -109,7 +109,7 @@ FastAPI поддерживает зависимости, которые выпо /// -{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *} +{* ../../docs_src/dependencies/tutorial008b_an_py310.py hl[18:22,31] *} Если вы хотите перехватывать исключения и формировать на их основе пользовательский ответ, создайте [Пользовательский обработчик исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. @@ -117,7 +117,7 @@ FastAPI поддерживает зависимости, которые выпо Если вы ловите исключение с помощью `except` в зависимости с `yield` и не вызываете его снова (или не вызываете новое исключение), FastAPI не сможет заметить, что было исключение — так же, как это происходит в обычном Python: -{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} +{* ../../docs_src/dependencies/tutorial008c_an_py310.py hl[15:16] *} В этом случае клиент получит *HTTP 500 Internal Server Error*, как и должно быть, поскольку мы не вызываем `HTTPException` или что-то подобное, но на сервере **не будет никаких логов** или других указаний на то, какая была ошибка. 😱 @@ -127,7 +127,7 @@ FastAPI поддерживает зависимости, которые выпо Вы можете повторно вызвать то же самое исключение с помощью `raise`: -{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *} +{* ../../docs_src/dependencies/tutorial008d_an_py310.py hl[17] *} Теперь клиент получит тот же *HTTP 500 Internal Server Error*, но на сервере в логах будет наше пользовательское `InternalError`. 😎 @@ -190,7 +190,7 @@ participant tasks as Background tasks Но если вы знаете, что не будете использовать зависимость после возврата из *функции-обработчика пути*, вы можете использовать `Depends(scope="function")`, чтобы сообщить FastAPI, что он должен закрыть зависимость после возврата из *функции-обработчика пути*, но **до того**, как **ответ будет отправлен**. -{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *} +{* ../../docs_src/dependencies/tutorial008e_an_py310.py hl[12,16] *} `Depends()` принимает параметр `scope`, который может быть: @@ -269,7 +269,7 @@ with open("./somefile.txt") as f: Их также можно использовать внутри зависимостей **FastAPI** с `yield`, применяя операторы `with` или `async with` внутри функции зависимости: -{* ../../docs_src/dependencies/tutorial010_py39.py hl[1:9,13] *} +{* ../../docs_src/dependencies/tutorial010_py310.py hl[1:9,13] *} /// tip | Подсказка diff --git a/docs/ru/docs/tutorial/dependencies/global-dependencies.md b/docs/ru/docs/tutorial/dependencies/global-dependencies.md index 2347c6dd8..f488322a9 100644 --- a/docs/ru/docs/tutorial/dependencies/global-dependencies.md +++ b/docs/ru/docs/tutorial/dependencies/global-dependencies.md @@ -6,10 +6,10 @@ В этом случае они будут применяться ко всем *операциям пути* в приложении: -{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *} +{* ../../docs_src/dependencies/tutorial012_an_py310.py hl[17] *} Все способы [добавления `dependencies` (зависимостей) в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} по-прежнему применимы, но в данном случае зависимости применяются ко всем *операциям пути* приложения. ## Зависимости для групп *операций пути* { #dependencies-for-groups-of-path-operations } -Позднее, читая о том, как структурировать более крупные [приложения, содержащие много файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}, вы узнаете, как объявить один параметр `dependencies` для целой группы *операций пути*. +Позднее, читая о том, как структурировать более крупные приложения ([приложения, содержащие много файлов](../../tutorial/bigger-applications.md){.internal-link target=_blank}), возможно, состоящие из нескольких файлов, вы узнаете, как объявить один параметр `dependencies` для целой группы *операций пути*. diff --git a/docs/ru/docs/tutorial/dependencies/index.md b/docs/ru/docs/tutorial/dependencies/index.md index 98b0d59c6..29f735ab6 100644 --- a/docs/ru/docs/tutorial/dependencies/index.md +++ b/docs/ru/docs/tutorial/dependencies/index.md @@ -1,6 +1,6 @@ # Зависимости { #dependencies } -**FastAPI** имеет очень мощную, но интуитивную систему **Инъекция зависимостей**. +**FastAPI** имеет очень мощную, но интуитивную систему **Инъекция зависимостей**. Она спроектирована так, чтобы быть очень простой в использовании и облегчать любому разработчику интеграцию других компонентов с **FastAPI**. diff --git a/docs/ru/docs/tutorial/dependencies/sub-dependencies.md b/docs/ru/docs/tutorial/dependencies/sub-dependencies.md index da31a6682..3c71defd8 100644 --- a/docs/ru/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/ru/docs/tutorial/dependencies/sub-dependencies.md @@ -58,11 +58,11 @@ query_extractor --> query_or_cookie_extractor --> read_query Если одна из ваших зависимостей объявлена несколько раз для одной и той же *функции операции пути*, например, несколько зависимостей имеют общую подзависимость, **FastAPI** будет знать, что вызывать эту подзависимость нужно только один раз за запрос. -При этом возвращаемое значение будет сохранено в "кэш" и будет передано всем "зависимым" функциям, которые нуждаются в нем внутри этого конкретного запроса, вместо того, чтобы вызывать зависимость несколько раз для одного и того же запроса. +При этом возвращаемое значение будет сохранено в «кэш» и будет передано всем "зависимым" функциям, которые нуждаются в нем внутри этого конкретного запроса, вместо того, чтобы вызывать зависимость несколько раз для одного и того же запроса. В расширенном сценарии, когда вы знаете, что вам нужно, чтобы зависимость вызывалась на каждом шаге (возможно, несколько раз) в одном и том же запросе, вместо использования "кэшированного" значения, вы можете установить параметр `use_cache=False` при использовании `Depends`: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python hl_lines="1" async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): @@ -71,7 +71,7 @@ async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_ca //// -//// tab | Python 3.9+ без Annotated +//// tab | Python 3.10+ без Annotated /// tip | Подсказка diff --git a/docs/ru/docs/tutorial/encoder.md b/docs/ru/docs/tutorial/encoder.md index 16981f79d..28e2a49c0 100644 --- a/docs/ru/docs/tutorial/encoder.md +++ b/docs/ru/docs/tutorial/encoder.md @@ -10,9 +10,9 @@ Представим, что у вас есть база данных `fake_db`, которая принимает только JSON-совместимые данные. -Например, он не принимает объекты `datetime`, так как они не совместимы с JSON. +Например, она не принимает объекты `datetime`, так как они не совместимы с JSON. -В таком случае объект `datetime` следует преобразовать в строку соответствующую формату ISO. +В таком случае объект `datetime` следует преобразовать в строку, соответствующую формату ISO. Точно так же эта база данных не может принять Pydantic-модель (объект с атрибутами), а только `dict`. diff --git a/docs/ru/docs/tutorial/extra-models.md b/docs/ru/docs/tutorial/extra-models.md index 03156f2b4..f9b63ca70 100644 --- a/docs/ru/docs/tutorial/extra-models.md +++ b/docs/ru/docs/tutorial/extra-models.md @@ -190,9 +190,9 @@ some_variable: PlaneItem | CarItem Таким же образом вы можете объявлять HTTP-ответы, возвращающие списки объектов. -Для этого используйте стандартный `typing.List` в Python (или просто `list` в Python 3.9 и выше): +Для этого используйте стандартный `list` в Python: -{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} +{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *} ## Ответ с произвольным `dict` { #response-with-arbitrary-dict } @@ -200,9 +200,9 @@ some_variable: PlaneItem | CarItem Это полезно, если вы заранее не знаете корректных названий полей/атрибутов (которые будут нужны при использовании Pydantic-модели). -В этом случае вы можете использовать `typing.Dict` (или просто `dict` в Python 3.9 и выше): +В этом случае вы можете использовать `dict`: -{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} +{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *} ## Резюме { #recap } diff --git a/docs/ru/docs/tutorial/first-steps.md b/docs/ru/docs/tutorial/first-steps.md index 798c03d51..cee264ff4 100644 --- a/docs/ru/docs/tutorial/first-steps.md +++ b/docs/ru/docs/tutorial/first-steps.md @@ -2,7 +2,7 @@ Самый простой файл FastAPI может выглядеть так: -{* ../../docs_src/first_steps/tutorial001_py39.py *} +{* ../../docs_src/first_steps/tutorial001_py310.py *} Скопируйте это в файл `main.py`. @@ -183,7 +183,7 @@ Deploying to FastAPI Cloud... ### Шаг 1: импортируйте `FastAPI` { #step-1-import-fastapi } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[1] *} `FastAPI` — это класс на Python, который предоставляет всю функциональность для вашего API. @@ -197,7 +197,7 @@ Deploying to FastAPI Cloud... ### Шаг 2: создайте экземпляр `FastAPI` { #step-2-create-a-fastapi-instance } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[3] *} Здесь переменная `app` будет экземпляром класса `FastAPI`. @@ -266,12 +266,12 @@ https://example.com/items/foo #### Определите *декоратор операции пути (path operation decorator)* { #define-a-path-operation-decorator } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[6] *} `@app.get("/")` сообщает **FastAPI**, что функция прямо под ним отвечает за обработку запросов, поступающих: * по пути `/` -* с использованием get операции +* с использованием get операции /// info | Информация о `@decorator` @@ -320,7 +320,7 @@ https://example.com/items/foo * **операция**: `get`. * **функция**: функция ниже «декоратора» (ниже `@app.get("/")`). -{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[7] *} Это функция на Python. @@ -332,7 +332,7 @@ https://example.com/items/foo Вы также можете определить её как обычную функцию вместо `async def`: -{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py310.py hl[7] *} /// note | Примечание @@ -342,7 +342,7 @@ https://example.com/items/foo ### Шаг 5: верните содержимое { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[8] *} Вы можете вернуть `dict`, `list`, отдельные значения `str`, `int` и т.д. diff --git a/docs/ru/docs/tutorial/handling-errors.md b/docs/ru/docs/tutorial/handling-errors.md index 2e00d7075..fbd82cf28 100644 --- a/docs/ru/docs/tutorial/handling-errors.md +++ b/docs/ru/docs/tutorial/handling-errors.md @@ -11,7 +11,7 @@ * Элемент, к которому клиент пытался получить доступ, не существует. * и т.д. -В таких случаях обычно возвращается **HTTP-код статуса ответа** в диапазоне **400** (от 400 до 499). +В таких случаях обычно возвращают **HTTP статус-код** в диапазоне **400** (от 400 до 499). Они похожи на двухсотые HTTP статус-коды (от 200 до 299), которые означают, что запрос обработан успешно. @@ -25,7 +25,7 @@ ### Импортируйте `HTTPException` { #import-httpexception } -{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *} +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[1] *} ### Вызовите `HTTPException` в своем коде { #raise-an-httpexception-in-your-code } @@ -39,7 +39,7 @@ В данном примере, когда клиент запрашивает элемент по несуществующему ID, возникает исключение со статус-кодом `404`: -{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *} +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[11] *} ### Возвращаемый ответ { #the-resulting-response } @@ -71,13 +71,13 @@ ## Добавление пользовательских заголовков { #add-custom-headers } -В некоторых ситуациях полезно иметь возможность добавлять пользовательские заголовки к ошибке HTTP. Например, для некоторых типов безопасности. +В некоторых ситуациях полезно иметь возможность добавлять пользовательские HTTP-заголовки к ошибке HTTP. Например, для некоторых типов безопасности. Скорее всего, вам не потребуется использовать его непосредственно в коде. Но в случае, если это необходимо для продвинутого сценария, можно добавить пользовательские заголовки: -{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial002_py310.py hl[14] *} ## Установка пользовательских обработчиков исключений { #install-custom-exception-handlers } @@ -89,7 +89,7 @@ Можно добавить собственный обработчик исключений с помощью `@app.exception_handler()`: -{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *} +{* ../../docs_src/handling_errors/tutorial003_py310.py hl[5:7,13:18,24] *} Здесь, если запросить `/unicorns/yolo`, то *операция пути* вызовет `UnicornException`. @@ -127,7 +127,7 @@ Обработчик исключения получит объект `Request` и исключение. -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[2,14:19] *} Теперь, если перейти к `/items/foo`, то вместо стандартной JSON-ошибки с: @@ -159,7 +159,7 @@ Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to pa Например, для этих ошибок можно вернуть обычный текстовый ответ вместо JSON: -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[3:4,9:11,25] *} /// note | Технические детали @@ -183,7 +183,7 @@ Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to pa Вы можете использовать его при разработке приложения для регистрации тела и его отладки, возврата пользователю и т.д. -{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial005_py310.py hl[14] *} Теперь попробуйте отправить недействительный элемент, например: @@ -239,6 +239,6 @@ from starlette.exceptions import HTTPException as StarletteHTTPException Если вы хотите использовать исключение вместе с теми же обработчиками исключений по умолчанию из **FastAPI**, вы можете импортировать и повторно использовать обработчики исключений по умолчанию из `fastapi.exception_handlers`: -{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *} +{* ../../docs_src/handling_errors/tutorial006_py310.py hl[2:5,15,21] *} В этом примере вы просто `выводите в терминал` ошибку с очень выразительным сообщением, но идея вам понятна. Вы можете использовать исключение, а затем просто повторно использовать стандартные обработчики исключений. diff --git a/docs/ru/docs/tutorial/metadata.md b/docs/ru/docs/tutorial/metadata.md index e4fe5fb54..221655aa5 100644 --- a/docs/ru/docs/tutorial/metadata.md +++ b/docs/ru/docs/tutorial/metadata.md @@ -18,7 +18,7 @@ Вы можете задать их следующим образом: -{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} +{* ../../docs_src/metadata/tutorial001_py310.py hl[3:16, 19:32] *} /// tip | Подсказка @@ -36,7 +36,7 @@ К примеру: -{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} +{* ../../docs_src/metadata/tutorial001_1_py310.py hl[31] *} ## Метаданные для тегов { #metadata-for-tags } @@ -58,7 +58,7 @@ Создайте метаданные для ваших тегов и передайте их в параметре `openapi_tags`: -{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[3:16,18] *} Помните, что вы можете использовать Markdown внутри описания, к примеру "login" будет отображен жирным шрифтом (**login**) и "fancy" будет отображаться курсивом (_fancy_). @@ -72,7 +72,7 @@ Используйте параметр `tags` с вашими *операциями пути* (и `APIRouter`ами), чтобы присвоить им различные теги: -{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[21,26] *} /// info | Дополнительная информация @@ -100,7 +100,7 @@ К примеру, чтобы задать её отображение по адресу `/api/v1/openapi.json`: -{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py310.py hl[3] *} Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые её используют. @@ -117,4 +117,4 @@ К примеру, чтобы задать отображение Swagger UI по адресу `/documentation` и отключить ReDoc: -{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial003_py310.py hl[3] *} diff --git a/docs/ru/docs/tutorial/middleware.md b/docs/ru/docs/tutorial/middleware.md index a83d3c011..734545cd8 100644 --- a/docs/ru/docs/tutorial/middleware.md +++ b/docs/ru/docs/tutorial/middleware.md @@ -1,10 +1,8 @@ # Middleware (Промежуточный слой) { #middleware } -Вы можете добавить промежуточный слой (middleware) в **FastAPI** приложение. - -"Middleware" это функция, которая выполняется с каждым запросом до его обработки какой-либо конкретной *операцией пути*. -А также с каждым ответом перед его возвращением. +Вы можете добавить middleware (промежуточный слой) в **FastAPI** приложение. +"Middleware" - это функция, которая выполняется с каждым **запросом** до его обработки какой-либо конкретной *операцией пути*. А также с каждым **ответом** перед его возвращением. * Она принимает каждый поступающий **запрос**. * Может что-то сделать с этим **запросом** или выполнить любой нужный код. @@ -23,23 +21,23 @@ ## Создание middleware { #create-a-middleware } -Для создания middleware используйте декоратор `@app.middleware("http")`. +Для создания middleware используйте декоратор `@app.middleware("http")` поверх функции. Функция middleware получает: -* `request` (объект запроса). +* `request`. * Функцию `call_next`, которая получает `request` в качестве параметра. * Эта функция передаёт `request` соответствующей *операции пути*. - * Затем она возвращает ответ `response`, сгенерированный *операцией пути*. -* Также имеется возможность видоизменить `response`, перед тем как его вернуть. + * Затем она возвращает `response`, сгенерированный соответствующей *операцией пути*. +* Также имеется возможность видоизменить `response` перед тем как его вернуть. -{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[8:9,11,14] *} -/// tip | Примечание +/// tip | Совет -Имейте в виду, что можно добавлять свои собственные заголовки при помощи префикса 'X-'. +Имейте в виду, что можно добавлять проприетарные HTTP-заголовки с префиксом `X-`. -Если же вы хотите добавить собственные заголовки, которые клиент сможет увидеть в браузере, то вам потребуется добавить их в настройки CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, см. документацию Starlette's CORS docs. +Но если вы хотите, чтобы клиент в браузере мог видеть ваши пользовательские заголовки, необходимо добавить их в настройки CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}), используя параметр `expose_headers`, описанный в документации по CORS Starlette. /// @@ -53,17 +51,17 @@ ### До и после `response` { #before-and-after-the-response } -Вы можете добавить код, использующий `request` до передачи его какой-либо *операции пути*. +Вы можете добавить код, использующий `request`, до передачи его какой-либо *операции пути*. А также после формирования `response`, до того, как вы его вернёте. Например, вы можете добавить собственный заголовок `X-Process-Time`, содержащий время в секундах, необходимое для обработки запроса и генерации ответа: -{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[10,12:13] *} -/// tip | Примечание +/// tip | Совет -Мы используем `time.perf_counter()` вместо `time.time()` для обеспечения большей точности наших примеров. 🤓 +Мы используем `time.perf_counter()` вместо `time.time()` для обеспечения большей точности в таких случаях. 🤓 /// @@ -94,4 +92,4 @@ app.add_middleware(MiddlewareB) О других middleware вы можете узнать больше в разделе [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}. -В следующем разделе вы можете прочитать, как настроить CORS с помощью middleware. +В следующем разделе вы можете прочитать, как настроить CORS с помощью middleware. diff --git a/docs/ru/docs/tutorial/path-operation-configuration.md b/docs/ru/docs/tutorial/path-operation-configuration.md index 112a1efca..31531c67f 100644 --- a/docs/ru/docs/tutorial/path-operation-configuration.md +++ b/docs/ru/docs/tutorial/path-operation-configuration.md @@ -4,7 +4,7 @@ /// warning | Внимание -Помните, что эти параметры передаются непосредственно *декоратору операций пути*, а не вашей *функции-обработчику операций пути*. +Помните, что эти параметры передаются непосредственно *декоратору операций пути*, а не вашей *функции-обработчику пути*. /// @@ -46,7 +46,7 @@ **FastAPI** поддерживает это так же, как и в случае с обычными строками: -{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *} +{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *} ## Краткое и развёрнутое содержание { #summary-and-description } @@ -56,7 +56,7 @@ ## Описание из строк документации { #description-from-docstring } -Так как описания обычно длинные и содержат много строк, вы можете объявить описание *операции пути* в функции строки документации и **FastAPI** прочитает её отсюда. +Так как описания обычно длинные и содержат много строк, вы можете объявить описание *операции пути* в строке документации функции, и **FastAPI** прочитает её оттуда. Вы можете использовать Markdown в строке документации, и он будет интерпретирован и отображён корректно (с учетом отступа в строке документации). @@ -90,9 +90,9 @@ OpenAPI указывает, что каждой *операции пути* не ## Обозначение *операции пути* как устаревшей { #deprecate-a-path-operation } -Если вам необходимо пометить *операцию пути* как устаревшую, при этом не удаляя её, передайте параметр `deprecated`: +Если вам необходимо пометить *операцию пути* как устаревшую, при этом не удаляя её, передайте параметр `deprecated`: -{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} Он будет четко помечен как устаревший в интерактивной документации: diff --git a/docs/ru/docs/tutorial/path-params-numeric-validations.md b/docs/ru/docs/tutorial/path-params-numeric-validations.md index f0fe78805..6c1148b60 100644 --- a/docs/ru/docs/tutorial/path-params-numeric-validations.md +++ b/docs/ru/docs/tutorial/path-params-numeric-validations.md @@ -44,7 +44,7 @@ Path-параметр всегда является обязательным, п И если вам больше ничего не нужно указывать для этого параметра, то нет необходимости использовать `Query`. -Но вам по-прежнему нужно использовать `Path` для path-параметра `item_id`. И если по какой-либо причине вы не хотите использовать `Annotated`, то могут возникнуть небольшие сложности. +Но вам по-прежнему нужно использовать `Path` для path-параметра `item_id`. И по какой-либо причине вы не хотите использовать `Annotated`. Если вы поместите параметр со значением по умолчанию перед другим параметром, у которого нет значения по умолчанию, то Python укажет на ошибку. @@ -54,11 +54,11 @@ Path-параметр всегда является обязательным, п Поэтому вы можете определить функцию так: -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете значения по умолчанию параметров функции для `Query()` или `Path()`. -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## Задайте нужный вам порядок параметров, полезные приёмы { #order-the-parameters-as-you-need-tricks } @@ -83,13 +83,13 @@ Path-параметр всегда является обязательным, п Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как kwargs, даже если у них нет значений по умолчанию. -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### Лучше с `Annotated` { #better-with-annotated } Имейте в виду, что если вы используете `Annotated`, то, поскольку вы не используете значений по умолчанию для параметров функции, у вас не возникнет подобной проблемы и вам, вероятно, не придётся использовать `*`. -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## Валидация числовых данных: больше или равно { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ Python не будет ничего делать с `*`, но он будет з В этом примере при указании `ge=1`, параметр `item_id` должен быть целым числом "`g`reater than or `e`qual" — больше или равно `1`. -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## Валидация числовых данных: больше и меньше или равно { #number-validations-greater-than-and-less-than-or-equal } @@ -106,7 +106,7 @@ Python не будет ничего делать с `*`, но он будет з * `gt`: больше (`g`reater `t`han) * `le`: меньше или равно (`l`ess than or `e`qual) -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## Валидация числовых данных: числа с плавающей точкой, больше и меньше { #number-validations-floats-greater-than-and-less-than } @@ -118,7 +118,7 @@ Python не будет ничего делать с `*`, но он будет з То же самое справедливо и для lt. -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## Резюме { #recap } diff --git a/docs/ru/docs/tutorial/path-params.md b/docs/ru/docs/tutorial/path-params.md index 83a7ed3ff..729569748 100644 --- a/docs/ru/docs/tutorial/path-params.md +++ b/docs/ru/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Вы можете определить "параметры" или "переменные" пути, используя синтаксис форматированных строк Python: -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} Значение параметра пути `item_id` будет передано в функцию в качестве аргумента `item_id`. @@ -16,7 +16,7 @@ Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python: -{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params/tutorial002_py310.py hl[7] *} Здесь, `item_id` объявлен типом `int`. @@ -26,7 +26,7 @@ /// -## Преобразование данных { #data-conversion } +## Преобразование данных { #data-conversion } Если запустите этот пример и перейдёте по адресу: http://127.0.0.1:8000/items/3, то увидите ответ: @@ -38,7 +38,7 @@ Обратите внимание на значение `3`, которое получила (и вернула) функция. Это целочисленный Python `int`, а не строка `"3"`. -Используя такое объявление типов, **FastAPI** выполняет автоматический "парсинг" запросов. +Используя такое объявление типов, **FastAPI** выполняет автоматический HTTP-запрос "парсинг". /// @@ -118,13 +118,13 @@ Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/users/me` был объявлен раньше, чем путь для `/users/{user_id}`: -{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *} Иначе путь для `/users/{user_id}` также будет соответствовать `/users/me`, "подразумевая", что он получает параметр `user_id` со значением `"me"`. Аналогично, вы не можете переопределить операцию с путем: -{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *} Первый будет выполняться всегда, так как путь совпадает первым. @@ -140,11 +140,11 @@ Затем создайте атрибуты класса с фиксированными допустимыми значениями: -{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *} /// tip | Подсказка -Если интересно, то "AlexNet", "ResNet" и "LeNet" - это названия моделей Машинного обучения. +Если интересно, то "AlexNet", "ResNet" и "LeNet" - это названия моделей Машинного обучения. /// @@ -152,7 +152,7 @@ Определите *параметр пути*, используя в аннотации типа класс перечисления (`ModelName`), созданный ранее: -{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[16] *} ### Проверьте документацию { #check-the-docs } @@ -168,13 +168,13 @@ Вы можете сравнить это значение с *элементом перечисления* класса `ModelName`: -{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[17] *} #### Получение *значения перечисления* { #get-the-enumeration-value } Можно получить фактическое значение (в данном случае - `str`) с помощью `model_name.value` или в общем случае `your_enum_member.value`: -{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[20] *} /// tip | Подсказка @@ -188,8 +188,8 @@ Они будут преобразованы в соответствующие значения (в данном случае - строки) перед их возвратом клиенту: -{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} -Вы отправите клиенту такой JSON-ответ: +{* ../../docs_src/path_params/tutorial005_py310.py hl[18,21,23] *} +На стороне клиента вы получите такой JSON-ответ: ```JSON { @@ -226,7 +226,7 @@ OpenAPI не поддерживает способов объявления *п Можете использовать так: -{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py310.py hl[6] *} /// tip | Подсказка @@ -241,7 +241,7 @@ OpenAPI не поддерживает способов объявления *п Используя **FastAPI** вместе со стандартными объявлениями типов Python (короткими и интуитивно понятными), вы получаете: * Поддержку редактора кода (проверку ошибок, автозавершение и т.п.) -* "Парсинг" данных +* "Парсинг" данных * Валидацию данных * Аннотации API и автоматическую документацию diff --git a/docs/ru/docs/tutorial/query-param-models.md b/docs/ru/docs/tutorial/query-param-models.md index 5ad7f1d99..7f29740ac 100644 --- a/docs/ru/docs/tutorial/query-param-models.md +++ b/docs/ru/docs/tutorial/query-param-models.md @@ -6,7 +6,7 @@ /// note | Заметка -Этот функционал доступен с версии `0.115.0`. 🤓 +Это поддерживается начиная с версии FastAPI `0.115.0`. 🤓 /// diff --git a/docs/ru/docs/tutorial/query-params-str-validations.md b/docs/ru/docs/tutorial/query-params-str-validations.md index 2bc2fb22c..43cbcad03 100644 --- a/docs/ru/docs/tutorial/query-params-str-validations.md +++ b/docs/ru/docs/tutorial/query-params-str-validations.md @@ -47,40 +47,16 @@ FastAPI поймёт, что значение `q` не обязательно, У нас была такая аннотация типа: -//// tab | Python 3.10+ - ```Python q: str | None = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Union[str, None] = None -``` - -//// - Мы «обернём» это в `Annotated`, и получится: -//// tab | Python 3.10+ - ```Python q: Annotated[str | None] = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Annotated[Union[str, None]] = None -``` - -//// - Обе версии означают одно и то же: `q` — параметр, который может быть `str` или `None`, и по умолчанию равен `None`. А теперь к самому интересному. 🎉 @@ -109,7 +85,7 @@ q: Annotated[Union[str, None]] = None ## Альтернатива (устаревшее): `Query` как значение по умолчанию { #alternative-old-query-as-the-default-value } -В предыдущих версиях FastAPI (до 0.95.0) требовалось использовать `Query` как значение по умолчанию для параметра вместо помещения его в `Annotated`. Скорее всего вы ещё встретите такой код, поэтому поясню. +В предыдущих версиях FastAPI (до 0.95.0) требовалось использовать `Query` как значение по умолчанию для параметра вместо помещения его в `Annotated`. Скорее всего вы ещё встретите такой код, поэтому поясню. /// tip | Подсказка @@ -191,7 +167,7 @@ q: str = Query(default="rick") ## Регулярные выражения { #add-regular-expressions } -Вы можете определить регулярное выражение `pattern`, которому должен соответствовать параметр: +Вы можете определить регулярное выражение `pattern`, которому должен соответствовать параметр: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -211,7 +187,7 @@ q: str = Query(default="rick") Допустим, вы хотите объявить, что query-параметр `q` должен иметь `min_length` равный `3` и значение по умолчанию `"fixedquery"`: -{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} /// note | Примечание @@ -241,7 +217,7 @@ q: Annotated[str | None, Query(min_length=3)] = None Поэтому, когда вам нужно объявить значение как обязательное при использовании `Query`, просто не указывайте значение по умолчанию: -{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} ### Обязательный, но может быть `None` { #required-can-be-none } @@ -292,7 +268,7 @@ http://localhost:8000/items/?q=foo&q=bar Можно также определить значение по умолчанию как `list`, если ничего не передано: -{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} Если вы перейдёте по адресу: @@ -315,7 +291,7 @@ http://localhost:8000/items/ Можно использовать `list` напрямую вместо `list[str]`: -{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} /// note | Примечание @@ -371,7 +347,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems Предположим, этот параметр вам больше не нравится. -Его нужно оставить на какое‑то время, так как клиенты его используют, но вы хотите, чтобы в документации он явно отображался как устаревший. +Его нужно оставить на какое‑то время, так как клиенты его используют, но вы хотите, чтобы в документации он явно отображался как устаревший. Тогда передайте параметр `deprecated=True` в `Query`: @@ -401,7 +377,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems /// -Например, эта кастомная проверка убеждается, что ID элемента начинается с `isbn-` для номера книги ISBN или с `imdb-` для ID URL фильма на IMDB: +Например, эта кастомная проверка убеждается, что ID элемента начинается с `isbn-` для номера книги ISBN или с `imdb-` для ID URL фильма на IMDB: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -435,7 +411,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems #### Случайный элемент { #a-random-item } -С помощью `data.items()` мы получаем итерируемый объект с кортежами, содержащими ключ и значение для каждого элемента словаря. +С помощью `data.items()` мы получаем итерируемый объект с кортежами, содержащими ключ и значение для каждого элемента словаря. Мы превращаем этот итерируемый объект в обычный `list` через `list(data.items())`. diff --git a/docs/ru/docs/tutorial/query-params.md b/docs/ru/docs/tutorial/query-params.md index be1c0e46e..cbacb129c 100644 --- a/docs/ru/docs/tutorial/query-params.md +++ b/docs/ru/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры. -{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} Query-параметры представляют из себя набор пар ключ-значение, которые идут после знака `?` в URL-адресе, разделенные символами `&`. @@ -24,7 +24,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10 Все те же правила, которые применяются к path-параметрам, также применяются и query-параметрам: * Поддержка от редактора кода (очевидно) -* "Парсинг" данных +* "Парсинг" данных * Проверка на соответствие данных (Валидация) * Автоматическая документация @@ -121,13 +121,13 @@ http://127.0.0.1:8000/items/foo?short=yes ## Обязательные query-параметры { #required-query-parameters } -Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе, мы пока что познакомились только с path-параметрами), то он не является обязательным. +Когда вы объявляете значение по умолчанию для параметра, который не является path-параметром (в этом разделе мы пока что рассмотрели только query-параметры), то он не является обязательным. Если вы не хотите задавать конкретное значение, но хотите сделать параметр необязательным, вы можете установить значение по умолчанию равным `None`. Но если вы хотите сделать query-параметр обязательным, вы можете просто не указывать значение по умолчанию: -{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} Здесь параметр запроса `needy` является обязательным параметром с типом данных `str`. diff --git a/docs/ru/docs/tutorial/request-files.md b/docs/ru/docs/tutorial/request-files.md index 9cfbd53df..41922333f 100644 --- a/docs/ru/docs/tutorial/request-files.md +++ b/docs/ru/docs/tutorial/request-files.md @@ -20,13 +20,13 @@ $ pip install python-multipart Импортируйте `File` и `UploadFile` из модуля `fastapi`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} ## Определите параметры `File` { #define-file-parameters } Создайте параметры `File` так же, как вы это делаете для `Body` или `Form`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} /// info | Дополнительная информация @@ -54,7 +54,7 @@ $ pip install python-multipart Определите параметр файла с типом `UploadFile`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} Использование `UploadFile` имеет ряд преимуществ перед `bytes`: @@ -122,7 +122,7 @@ contents = myfile.file.read() Но когда форма включает файлы, она кодируется как multipart/form-data. Если вы используете `File`, **FastAPI** будет знать, что ему нужно получить файлы из нужной части тела. -Если вы хотите узнать больше об этих кодировках и полях форм, перейдите по ссылке MDN web docs for POST. +Если вы хотите узнать больше об этих кодировках и полях форм, перейдите по ссылке MDN web docs for POST. /// @@ -144,7 +144,7 @@ contents = myfile.file.read() Вы также можете использовать `File()` вместе с `UploadFile`, например, для установки дополнительных метаданных: -{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} ## Загрузка нескольких файлов { #multiple-file-uploads } @@ -154,7 +154,7 @@ contents = myfile.file.read() Для этого необходимо объявить список `bytes` или `UploadFile`: -{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} Вы получите, как и было объявлено, список `list` из `bytes` или `UploadFile`. @@ -170,7 +170,7 @@ contents = myfile.file.read() Так же, как и раньше, вы можете использовать `File()` для задания дополнительных параметров, даже для `UploadFile`: -{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} ## Резюме { #recap } diff --git a/docs/ru/docs/tutorial/request-form-models.md b/docs/ru/docs/tutorial/request-form-models.md index f8c58356c..f4411a27b 100644 --- a/docs/ru/docs/tutorial/request-form-models.md +++ b/docs/ru/docs/tutorial/request-form-models.md @@ -24,7 +24,7 @@ $ pip install python-multipart Вам просто нужно объявить **Pydantic-модель** с полями, которые вы хотите получить как **поля формы**, а затем объявить параметр как `Form`: -{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} **FastAPI** **извлечёт** данные для **каждого поля** из **данных формы** в запросе и выдаст вам объявленную Pydantic-модель. @@ -48,7 +48,7 @@ $ pip install python-multipart Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) все дополнительные (`extra`) поля: -{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} Если клиент попробует отправить дополнительные данные, то в ответ он получит **ошибку**. diff --git a/docs/ru/docs/tutorial/request-forms-and-files.md b/docs/ru/docs/tutorial/request-forms-and-files.md index 691dc75ba..10836d74f 100644 --- a/docs/ru/docs/tutorial/request-forms-and-files.md +++ b/docs/ru/docs/tutorial/request-forms-and-files.md @@ -16,13 +16,13 @@ $ pip install python-multipart ## Импортируйте `File` и `Form` { #import-file-and-form } -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} ## Определите параметры `File` и `Form` { #define-file-and-form-parameters } Создайте параметры файла и формы таким же образом, как для `Body` или `Query`: -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} Файлы и поля формы будут загружены в виде данных формы, и вы получите файлы и поля формы. diff --git a/docs/ru/docs/tutorial/request-forms.md b/docs/ru/docs/tutorial/request-forms.md index e257652b6..01f71ac2f 100644 --- a/docs/ru/docs/tutorial/request-forms.md +++ b/docs/ru/docs/tutorial/request-forms.md @@ -18,17 +18,17 @@ $ pip install python-multipart Импортируйте `Form` из `fastapi`: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} ## Определение параметров `Form` { #define-form-parameters } Создайте параметры формы так же, как это делается для `Body` или `Query`: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} -Например, в одном из способов использования спецификации OAuth2 (называемом «потоком пароля») требуется отправить `username` и `password` в виде полей формы. +Например, в одном из способов использования спецификации OAuth2 (называемом «password flow» - аутентификация по паролю) требуется отправить `username` и `password` в виде полей формы. -spec требует, чтобы поля были строго названы `username` и `password` и отправлялись как поля формы, а не JSON. +спецификация требует, чтобы поля были строго названы `username` и `password` и отправлялись как поля формы, а не JSON. С помощью `Form` вы можете объявить те же настройки, что и с `Body` (и `Query`, `Path`, `Cookie`), включая валидацию, примеры, псевдоним (например, `user-name` вместо `username`) и т.д. @@ -56,7 +56,7 @@ $ pip install python-multipart Но когда форма содержит файлы, она кодируется как `multipart/form-data`. О работе с файлами вы прочтёте в следующей главе. -Если вы хотите узнать больше про эти кодировки и поля формы, обратитесь к MDN веб-документации для `POST`. +Если вы хотите узнать больше про эти кодировки и поля формы, обратитесь к MDN веб-документации для `POST`. /// diff --git a/docs/ru/docs/tutorial/response-model.md b/docs/ru/docs/tutorial/response-model.md index 22a811cd5..cd99ce28c 100644 --- a/docs/ru/docs/tutorial/response-model.md +++ b/docs/ru/docs/tutorial/response-model.md @@ -183,7 +183,7 @@ FastAPI делает несколько вещей внутри вместе с Самый распространённый случай — [возвращать Response напрямую, как описано далее в разделах документации для продвинутых](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} Этот простой случай обрабатывается FastAPI автоматически, потому что аннотация возвращаемого типа — это класс (или подкласс) `Response`. @@ -193,7 +193,7 @@ FastAPI делает несколько вещей внутри вместе с Вы также можете использовать подкласс `Response` в аннотации типа: -{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} Это тоже сработает, так как `RedirectResponse` — подкласс `Response`, и FastAPI автоматически обработает этот простой случай. @@ -201,7 +201,7 @@ FastAPI делает несколько вещей внутри вместе с Но когда вы возвращаете произвольный объект, не являющийся валидным типом Pydantic (например, объект базы данных), и аннотируете его таким образом в функции, FastAPI попытается создать модель ответа Pydantic из этой аннотации типа и потерпит неудачу. -То же произойдёт, если у вас будет что-то вроде union разных типов, где один или несколько не являются валидными типами Pydantic, например, это приведёт к ошибке 💥: +То же произойдёт, если у вас будет что-то вроде union разных типов, где один или несколько не являются валидными типами Pydantic, например, это приведёт к ошибке 💥: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} diff --git a/docs/ru/docs/tutorial/response-status-code.md b/docs/ru/docs/tutorial/response-status-code.md index 30f642b64..13d982e80 100644 --- a/docs/ru/docs/tutorial/response-status-code.md +++ b/docs/ru/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ * `@app.delete()` * и других. -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} /// note | Примечание @@ -66,7 +66,7 @@ FastAPI знает об этом и создаст документацию Open /// tip | Подсказка -Чтобы узнать больше о HTTP кодах статуса и о том, для чего каждый из них предназначен, ознакомьтесь с MDN документацией об HTTP статус-кодах. +Чтобы узнать больше о HTTP кодах статуса и о том, для чего каждый из них предназначен, ознакомьтесь с MDN документацией об HTTP статус-кодах. /// @@ -74,7 +74,7 @@ FastAPI знает об этом и создаст документацию Open Рассмотрим предыдущий пример еще раз: -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} `201` – это код статуса "Создано". @@ -82,7 +82,7 @@ FastAPI знает об этом и создаст документацию Open Для удобства вы можете использовать переменные из `fastapi.status`. -{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} Они содержат те же числовые значения, но позволяют использовать автозавершение редактора кода для выбора кода статуса: @@ -90,7 +90,7 @@ FastAPI знает об этом и создаст документацию Open /// note | Технические детали -Вы также можете использовать `from starlette import status` вместо `from fastapi import status`. +Вы также можете использовать `from starlette import status`. **FastAPI** позволяет использовать как `starlette.status`, так и `fastapi.status` исключительно для удобства разработчиков. Но поставляется fastapi.status непосредственно из Starlette. diff --git a/docs/ru/docs/tutorial/schema-extra-example.md b/docs/ru/docs/tutorial/schema-extra-example.md index e4a97c880..c7381aae2 100644 --- a/docs/ru/docs/tutorial/schema-extra-example.md +++ b/docs/ru/docs/tutorial/schema-extra-example.md @@ -74,7 +74,7 @@ OpenAPI 3.1.0 (используется начиная с FastAPI 0.99.0) доб Когда вы делаете это, примеры становятся частью внутренней **JSON Schema** для данных тела запроса. -Тем не менее, на момент написания этого Swagger UI, инструмент, отвечающий за отображение UI документации, не поддерживает показ нескольких примеров для данных в **JSON Schema**. Но ниже есть обходной путь. +Тем не менее, на момент написания этого Swagger UI, инструмент, отвечающий за отображение UI документации, не поддерживает показ нескольких примеров для данных в **JSON Schema**. Но ниже есть обходной путь. ### Специфические для OpenAPI `examples` { #openapi-specific-examples } diff --git a/docs/ru/docs/tutorial/security/first-steps.md b/docs/ru/docs/tutorial/security/first-steps.md index 983e85e66..9b9673b84 100644 --- a/docs/ru/docs/tutorial/security/first-steps.md +++ b/docs/ru/docs/tutorial/security/first-steps.md @@ -20,7 +20,7 @@ Скопируйте пример в файл `main.py`: -{* ../../docs_src/security/tutorial001_an_py39.py *} +{* ../../docs_src/security/tutorial001_an_py310.py *} ## Запуск { #run-it } @@ -132,7 +132,7 @@ OAuth2 был спроектирован так, чтобы бэкенд или При создании экземпляра класса `OAuth2PasswordBearer` мы передаем параметр `tokenUrl`. Этот параметр содержит URL, который клиент (фронтенд, работающий в браузере пользователя) будет использовать для отправки `username` и `password`, чтобы получить токен. -{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} /// tip | Подсказка @@ -170,7 +170,7 @@ oauth2_scheme(some, parameters) Теперь вы можете передать `oauth2_scheme` как зависимость с `Depends`. -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} Эта зависимость предоставит `str`, который будет присвоен параметру `token` *функции-обработчика пути*. diff --git a/docs/ru/docs/tutorial/security/get-current-user.md b/docs/ru/docs/tutorial/security/get-current-user.md index c6bc07cc1..8388b672c 100644 --- a/docs/ru/docs/tutorial/security/get-current-user.md +++ b/docs/ru/docs/tutorial/security/get-current-user.md @@ -2,7 +2,7 @@ В предыдущей главе система безопасности (основанная на системе внедрения зависимостей) передавала *функции-обработчику пути* `token` типа `str`: -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} Но это всё ещё не слишком полезно. diff --git a/docs/ru/docs/tutorial/security/index.md b/docs/ru/docs/tutorial/security/index.md index ebac013b6..bd8da824b 100644 --- a/docs/ru/docs/tutorial/security/index.md +++ b/docs/ru/docs/tutorial/security/index.md @@ -1,56 +1,56 @@ -# Настройка авторизации { #security } +# Безопасность { #security } Существует множество способов обеспечения безопасности, аутентификации и авторизации. -Обычно эта тема является достаточно сложной и трудной. +Обычно эта тема является достаточно сложной и «трудной». -Во многих фреймворках и системах только работа с определением доступов к приложению и аутентификацией требует значительных затрат усилий и написания множества кода (во многих случаях его объём может составлять более 50% от всего написанного кода). +Во многих фреймворках и системах только работа с безопасностью и аутентификацией требует значительных затрат усилий и написания множества кода (во многих случаях его объём может составлять 50% или более от всего написанного кода). -**FastAPI** предоставляет несколько инструментов, которые помогут вам настроить **Авторизацию** легко, быстро, стандартным способом, без необходимости изучать все её тонкости. +**FastAPI** предоставляет несколько инструментов, которые помогут вам работать с **безопасностью** легко, быстро, стандартным способом, без необходимости изучать и разбираться во всех спецификациях по безопасности. -Но сначала давайте рассмотрим некоторые небольшие концепции. +Но сначала давайте рассмотрим несколько небольших концепций. -## Куда-то торопишься? { #in-a-hurry } +## Нет времени? { #in-a-hurry } -Если вам не нужна информация о каких-либо из следующих терминов и вам просто нужно добавить защиту с аутентификацией на основе логина и пароля *прямо сейчас*, переходите к следующим главам. +Если вам не важны какие-либо из этих терминов и вам просто нужно добавить защиту с аутентификацией на основе имени пользователя и пароля прямо сейчас, переходите к следующим главам. ## OAuth2 { #oauth2 } -OAuth2 - это протокол, который определяет несколько способов обработки аутентификации и авторизации. +OAuth2 - это спецификация, которая определяет несколько способов обработки аутентификации и авторизации. -Он довольно обширен и охватывает несколько сложных вариантов использования. +Это довольно обширная спецификация, охватывающая несколько сложных вариантов использования. -OAuth2 включает в себя способы аутентификации с использованием "третьей стороны". +Она включает способы аутентификации с использованием «третьей стороны». -Это то, что используют под собой все кнопки "вход с помощью Facebook, Google, X (Twitter), GitHub" на страницах авторизации. +Именно это используется во всех системах с кнопками «войти с помощью Facebook, Google, X (Twitter), GitHub». ### OAuth 1 { #oauth-1 } -Ранее использовался протокол OAuth 1, который сильно отличается от OAuth2 и является более сложным, поскольку он включал прямые описания того, как шифровать сообщение. +Ранее использовался OAuth 1, который сильно отличается от OAuth2 и является более сложным, поскольку он включал прямые спецификации того, как шифровать обмен данными. В настоящее время он не очень популярен и не используется. -OAuth2 не указывает, как шифровать сообщение, он ожидает, что ваше приложение будет обслуживаться по протоколу HTTPS. +OAuth2 не указывает, как шифровать обмен данными, он ожидает, что ваше приложение будет обслуживаться по протоколу HTTPS. /// tip | Подсказка -В разделе **Развертывание** вы увидите как настроить протокол HTTPS бесплатно, используя Traefik и Let's Encrypt. +В разделе о **развертывании** вы увидите, как настроить HTTPS бесплатно, используя Traefik и Let's Encrypt. /// ## OpenID Connect { #openid-connect } -OpenID Connect - это еще один протокол, основанный на **OAuth2**. +OpenID Connect — это ещё одна спецификация, основанная на **OAuth2**. -Он просто расширяет OAuth2, уточняя некоторые вещи, не имеющие однозначного определения в OAuth2, в попытке сделать его более совместимым. +Она просто расширяет OAuth2, уточняя некоторые вещи, которые относительно неоднозначны в OAuth2, стараясь сделать его более совместимым. -Например, для входа в Google используется OpenID Connect (который под собой использует OAuth2). +Например, для входа в Google используется OpenID Connect (который под капотом использует OAuth2). Но вход в Facebook не поддерживает OpenID Connect. У него есть собственная вариация OAuth2. -### OpenID (не "OpenID Connect") { #openid-not-openid-connect } +### OpenID (не «OpenID Connect») { #openid-not-openid-connect } -Также ранее использовался стандарт "OpenID", который пытался решить ту же проблему, что и **OpenID Connect**, но не был основан на OAuth2. +Также ранее использовалась спецификация «OpenID», которая пыталась решить ту же задачу, что и **OpenID Connect**, но не была основана на OAuth2. Таким образом, это была полноценная дополнительная система. @@ -62,45 +62,45 @@ OpenAPI (ранее известный как Swagger) - это открытая **FastAPI** основан на **OpenAPI**. -Это то, что делает возможным наличие множества автоматических интерактивных интерфейсов документирования, сгенерированного кода и т.д. +Это то, что делает возможными несколько автоматических интерактивных интерфейсов документации, генерацию кода и т.д. -В OpenAPI есть способ использовать несколько "схем" безопасности. +В OpenAPI есть способ определить несколько «схем» безопасности. -Таким образом, вы можете воспользоваться преимуществами Всех этих стандартных инструментов, включая интерактивные системы документирования. +Используя их, вы можете воспользоваться преимуществами всех этих инструментов, основанных на стандартах, включая интерактивные системы документирования. -OpenAPI может использовать следующие схемы авторизации: +OpenAPI определяет следующие схемы безопасности: -* `apiKey`: уникальный идентификатор для приложения, который может быть получен из: - * Параметров запроса. - * Заголовка. - * Cookies. -* `http`: стандартные системы аутентификации по протоколу HTTP, включая: - * `bearer`: заголовок `Authorization` со значением `Bearer {уникальный токен}`. Это унаследовано от OAuth2. - * Базовая аутентификация по протоколу HTTP. +* `apiKey`: специфичный для приложения ключ, который может поступать из: + * параметра запроса. + * HTTP-заголовка. + * cookie. +* `http`: стандартные системы аутентификации по HTTP, включая: + * `bearer`: HTTP-заголовок `Authorization` со значением `Bearer ` плюс токен. Это унаследовано от OAuth2. + * Базовая аутентификация по HTTP. * HTTP Digest и т.д. -* `oauth2`: все способы обеспечения безопасности OAuth2 называемые "потоки" (англ. "flows"). - * Некоторые из этих "потоков" подходят для реализации аутентификации через сторонний сервис использующий OAuth 2.0 (например, Google, Facebook, X (Twitter), GitHub и т.д.): +* `oauth2`: все способы OAuth2 для обеспечения безопасности (называются «потоками»). + * Несколько из этих «потоков» подходят для построения провайдера аутентификации OAuth 2.0 (например, Google, Facebook, X (Twitter), GitHub и т.д.): * `implicit` * `clientCredentials` * `authorizationCode` - * Но есть один конкретный "поток", который может быть идеально использован для обработки аутентификации непосредственно в том же приложении: - * `password`: в некоторых следующих главах будут рассмотрены примеры этого. -* `openIdConnect`: способ определить, как автоматически обнаруживать данные аутентификации OAuth2. - * Это автоматическое обнаружение определено в спецификации OpenID Connect. + * Но есть один конкретный «поток», который можно идеально использовать для обработки аутентификации непосредственно в этом же приложении: + * `password`: в некоторых следующих главах будут приведены примеры. +* `openIdConnect`: имеет способ определить, как автоматически обнаруживать данные аутентификации OAuth2. + * Именно это автоматическое обнаружение определено в спецификации OpenID Connect. /// tip | Подсказка -Интеграция сторонних сервисов для аутентификации/авторизации таких как Google, Facebook, X (Twitter), GitHub и т.д. осуществляется достаточно легко. +Интеграция сторонних провайдеров аутентификации/авторизации, таких как Google, Facebook, X (Twitter), GitHub и т.д., также возможна и относительно проста. -Самой сложной проблемой является создание такого провайдера аутентификации/авторизации, но **FastAPI** предоставляет вам инструменты, позволяющие легко это сделать, выполняя при этом всю тяжелую работу за вас. +Самой сложной задачей является создание такого провайдера аутентификации/авторизации, но **FastAPI** предоставляет вам инструменты, позволяющие легко это сделать, выполняя при этом всю тяжёлую работу за вас. /// -## Преимущества **FastAPI** { #fastapi-utilities } +## Инструменты **FastAPI** { #fastapi-utilities } -Fast API предоставляет несколько инструментов для каждой из этих схем безопасности в модуле `fastapi.security`, которые упрощают использование этих механизмов безопасности. +FastAPI предоставляет несколько инструментов для каждой из этих схем безопасности в модуле `fastapi.security`, которые упрощают использование этих механизмов безопасности. -В следующих главах вы увидите, как обезопасить свой API, используя инструменты, предоставляемые **FastAPI**. +В следующих главах вы увидите, как добавить безопасность в ваш API, используя инструменты, предоставляемые **FastAPI**. -И вы также увидите, как он автоматически интегрируется в систему интерактивной документации. +И вы также увидите, как это автоматически интегрируется в систему интерактивной документации. diff --git a/docs/ru/docs/tutorial/security/oauth2-jwt.md b/docs/ru/docs/tutorial/security/oauth2-jwt.md index 803491f53..7838b07df 100644 --- a/docs/ru/docs/tutorial/security/oauth2-jwt.md +++ b/docs/ru/docs/tutorial/security/oauth2-jwt.md @@ -1,10 +1,10 @@ # OAuth2 с паролем (и хешированием), Bearer с JWT-токенами { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens } -Теперь, когда у нас определен процесс обеспечения безопасности, давайте сделаем приложение действительно безопасным, используя токены JWT и безопасное хеширование паролей. +Теперь, когда у нас определен процесс обеспечения безопасности, давайте сделаем приложение действительно безопасным, используя токены JWT и безопасное хеширование паролей. Этот код можно реально использовать в своем приложении, сохранять хэши паролей в базе данных и т.д. -Мы продолжим разбираться, начиная с того места, на котором остановились в предыдущей главе. +Мы продолжим разбираться, начиная с того места, на котором остановились в предыдущей главе, и расширим его. ## Про JWT { #about-jwt } @@ -43,9 +43,11 @@ $ pip install pyjwt /// info | Дополнительная информация + Если вы планируете использовать алгоритмы цифровой подписи, такие как RSA или ECDSA, вам следует установить зависимость библиотеки криптографии `pyjwt[crypto]`. Подробнее об этом можно прочитать в документации по установке PyJWT. + /// ## Хеширование паролей { #password-hashing } @@ -83,11 +85,13 @@ $ pip install "pwdlib[argon2]" /// tip | Подсказка + С помощью `pwdlib` можно даже настроить его на чтение паролей, созданных **Django**, плагином безопасности **Flask** или многими другими библиотеками. Таким образом, вы сможете, например, совместно использовать одни и те же данные из приложения Django в базе данных с приложением FastAPI. Или постепенно мигрировать Django-приложение, используя ту же базу данных. При этом пользователи смогут одновременно входить в систему как из приложения Django, так и из приложения **FastAPI**. + /// ## Хеширование и проверка паролей { #hash-and-verify-the-passwords } @@ -97,11 +101,13 @@ $ pip install "pwdlib[argon2]" Создайте экземпляр PasswordHash с рекомендованными настройками — он будет использоваться для хэширования и проверки паролей. /// tip | Подсказка + pwdlib также поддерживает алгоритм хеширования bcrypt, но не включает устаревшие алгоритмы — для работы с устаревшими хэшами рекомендуется использовать библиотеку passlib. Например, вы можете использовать ее для чтения и проверки паролей, сгенерированных другой системой (например, Django), но хэшировать все новые пароли другим алгоритмом, например Argon2 или Bcrypt. И при этом быть совместимым со всеми этими системами. + /// Создайте служебную функцию для хэширования пароля, поступающего от пользователя. @@ -110,10 +116,16 @@ pwdlib также поддерживает алгоритм хешировани И еще одну — для аутентификации и возврата пользователя. -{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +Когда `authenticate_user` вызывается с именем пользователя, которого нет в базе данных, мы все равно запускаем `verify_password` с использованием фиктивного хэша. + +Это гарантирует, что эндпоинт отвечает примерно за одно и то же время вне зависимости от того, существует имя пользователя или нет, предотвращая тайминговые атаки (атака по времени), с помощью которых можно было бы перечислять существующие имена пользователей. /// note | Технические детали + Если проверить новую (фальшивую) базу данных `fake_users_db`, то можно увидеть, как теперь выглядит хэшированный пароль: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`. + /// ## Работа с JWT токенами { #handle-jwt-tokens } @@ -144,7 +156,7 @@ $ openssl rand -hex 32 Создайте служебную функцию для генерации нового токена доступа. -{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} ## Обновление зависимостей { #update-the-dependencies } @@ -154,7 +166,7 @@ $ openssl rand -hex 32 Если токен недействителен, то сразу же верните HTTP-ошибку. -{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} ## Обновление *операции пути* `/token` { #update-the-token-path-operation } @@ -162,7 +174,7 @@ $ openssl rand -hex 32 Создайте реальный токен доступа JWT и верните его -{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} ### Технические подробности о JWT ключе `sub` { #technical-details-about-the-jwt-subject-sub } @@ -202,7 +214,9 @@ Username: `johndoe` Password: `secret` /// check | Проверка + Обратите внимание, что нигде в коде не используется открытый текст пароля "`secret`", мы используем только его хэшированную версию. + /// @@ -225,7 +239,9 @@ Password: `secret` /// note | Техническая информация + Обратите внимание на HTTP-заголовок `Authorization`, значение которого начинается с `Bearer `. + /// ## Продвинутое использование `scopes` { #advanced-usage-with-scopes } diff --git a/docs/ru/docs/tutorial/security/simple-oauth2.md b/docs/ru/docs/tutorial/security/simple-oauth2.md index 36ff32c8e..4b86a4013 100644 --- a/docs/ru/docs/tutorial/security/simple-oauth2.md +++ b/docs/ru/docs/tutorial/security/simple-oauth2.md @@ -236,7 +236,7 @@ UserInDB( -Если щёлкнуть на значке замка и выйти из системы, а затем попытаться выполнить ту же операцию ещё раз, будет выдана ошибка HTTP 401: +Если щёлкнуть на значке замка и выйти из системы, а затем попробовать выполнить ту же операцию ещё раз, будет выдана ошибка HTTP 401: ```JSON { diff --git a/docs/ru/docs/tutorial/sql-databases.md b/docs/ru/docs/tutorial/sql-databases.md index 1d0346533..ed67739cc 100644 --- a/docs/ru/docs/tutorial/sql-databases.md +++ b/docs/ru/docs/tutorial/sql-databases.md @@ -8,7 +8,7 @@ /// tip | Подсказка -Вы можете использовать любую другую библиотеку для работы с SQL или NoSQL базами данных (иногда их называют "ORMs"), FastAPI ничего не навязывает. 😎 +Вы можете использовать любую другую библиотеку для работы с SQL или NoSQL базами данных (иногда их называют "ORMs"), FastAPI ничего не навязывает. 😎 /// @@ -344,7 +344,7 @@ $ fastapi dev main.py -Если вы перейдёте в UI API `/docs`, вы увидите, что он обновился: теперь при создании героя он не ожидает получить `id` от клиента и т. д. +Если вы перейдёте в UI API `/docs`, вы увидите, что он обновился: теперь при создании героя он не ожидает получить `id` от клиента и т.д.
@@ -354,4 +354,4 @@ $ fastapi dev main.py Вы можете использовать **SQLModel** для взаимодействия с SQL базой данных и упростить код с помощью *моделей данных* и *моделей-таблиц*. -Гораздо больше вы можете узнать в документации **SQLModel**, там есть более подробный мини-туториал по использованию SQLModel с **FastAPI**. 🚀 +Гораздо больше вы можете узнать в документации **SQLModel**, там есть более подробное мини-руководство по использованию SQLModel с **FastAPI**. 🚀 diff --git a/docs/ru/docs/tutorial/static-files.md b/docs/ru/docs/tutorial/static-files.md index f40cfe9b0..3b0cab831 100644 --- a/docs/ru/docs/tutorial/static-files.md +++ b/docs/ru/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ * Импортируйте `StaticFiles`. * "Примонтируйте" экземпляр `StaticFiles()` к определённому пути. -{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} /// note | Технические детали diff --git a/docs/ru/docs/tutorial/testing.md b/docs/ru/docs/tutorial/testing.md index ab58429c5..6dd2fe579 100644 --- a/docs/ru/docs/tutorial/testing.md +++ b/docs/ru/docs/tutorial/testing.md @@ -30,7 +30,7 @@ $ pip install httpx Напишите простое утверждение с `assert` дабы проверить истинность Python-выражения (это тоже стандарт `pytest`). -{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} /// tip | Подсказка @@ -76,7 +76,7 @@ $ pip install httpx В файле `main.py` находится Ваше приложение **FastAPI**: -{* ../../docs_src/app_testing/app_a_py39/main.py *} +{* ../../docs_src/app_testing/app_a_py310/main.py *} ### Файл тестов { #testing-file } @@ -92,7 +92,7 @@ $ pip install httpx Так как оба файла находятся в одной директории, для импорта объекта приложения из файла `main` в файл `test_main` Вы можете использовать относительный импорт: -{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} ...и писать дальше тесты, как и раньше. @@ -119,7 +119,7 @@ $ pip install httpx Ещё есть операция `POST`, и она может вернуть несколько ошибок. -Обе *операции пути* требуют наличия в запросе заголовка `X-Token`. +Обе *операции пути* требуют наличия в запросе HTTP-заголовка `X-Token`. {* ../../docs_src/app_testing/app_b_an_py310/main.py *} @@ -139,7 +139,7 @@ $ pip install httpx * Передаёте *path*-параметры или *query*-параметры, вписав их непосредственно в строку URL. * Передаёте JSON в теле запроса, передав Python-объект (например: `dict`) через именованный параметр `json`. * Если же Вам необходимо отправить *форму с данными* вместо JSON, то используйте параметр `data` вместо `json`. -* Для передачи *заголовков*, передайте объект `dict` через параметр `headers`. +* Для передачи *HTTP-заголовков*, передайте объект `dict` через параметр `headers`. * Для передачи *cookies* также передайте `dict`, но через параметр `cookies`. Для получения дополнительной информации о передаче данных на бэкенд с помощью `httpx` или `TestClient` ознакомьтесь с документацией HTTPX. diff --git a/docs/ru/docs/virtual-environments.md b/docs/ru/docs/virtual-environments.md index 43136298a..f931cc3c8 100644 --- a/docs/ru/docs/virtual-environments.md +++ b/docs/ru/docs/virtual-environments.md @@ -53,7 +53,7 @@ $ cd awesome-project ## Создание виртуального окружения { #create-a-virtual-environment } -Когда вы начинаете работать над Python‑проектом **впервые**, создайте виртуальное окружение **внутри вашего проекта**. +Когда вы начинаете работать над Python‑проектом **впервые**, создайте виртуальное окружение **внутри вашего проекта**. /// tip | Подсказка @@ -166,7 +166,7 @@ $ source .venv/Scripts/activate Каждый раз, когда вы устанавливаете **новый пакет** в это окружение, **активируйте** окружение снова. -Это гарантирует, что если вы используете **программу терминала (CLI)**, установленную этим пакетом, вы будете использовать именно ту, что из вашего виртуального окружения, а не какую‑то глобально установленную, возможно другой версии, чем вам нужна. +Это гарантирует, что если вы используете **программу терминала (CLI)**, установленную этим пакетом, вы будете использовать именно ту, что из вашего виртуального окружения, а не какую‑то глобально установленную, возможно другой версии, чем вам нужна. /// From 6fa573ce0bc16fe445f93db413d20146dd9ff35d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Feb 2026 19:58:08 +0000 Subject: [PATCH 113/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d8bc3a26f..dfa4eb489 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -11,6 +11,10 @@ hide: * 📝 Update docs for JWT to prevent timing attacks. PR [#14908](https://github.com/fastapi/fastapi/pull/14908) by [@tiangolo](https://github.com/tiangolo). +### Translations + +* 🌐 Update translations for ru (update-outdated). PR [#14909](https://github.com/fastapi/fastapi/pull/14909) by [@YuriiMotov](https://github.com/YuriiMotov). + ## 0.129.0 ### Breaking Changes From db328270aaf74bc512dca9e02dc463fab92ccd97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 07:56:30 +0100 Subject: [PATCH 114/195] =?UTF-8?q?=E2=AC=86=20Bump=20pillow=20from=2012.1?= =?UTF-8?q?.0=20to=2012.1.1=20(#14899)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [pillow](https://github.com/python-pillow/Pillow) from 12.1.0 to 12.1.1. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/12.1.0...12.1.1) --- updated-dependencies: - dependency-name: pillow dependency-version: 12.1.1 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 184 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/uv.lock b/uv.lock index 736b0e145..78026fa67 100644 --- a/uv.lock +++ b/uv.lock @@ -3511,100 +3511,100 @@ wheels = [ [[package]] name = "pillow" -version = "12.1.0" +version = "12.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" }, - { url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" }, - { url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" }, - { url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" }, - { url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" }, - { url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" }, - { url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" }, - { url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, - { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, - { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, - { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, - { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, - { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, - { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, - { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, - { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, - { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, - { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, - { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, - { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, - { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, - { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" }, - { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" }, - { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" }, - { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" }, - { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" }, - { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" }, - { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" }, - { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" }, - { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" }, - { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" }, - { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" }, - { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" }, - { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" }, - { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" }, - { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" }, - { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" }, - { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" }, - { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" }, - { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" }, - { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" }, - { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" }, - { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" }, - { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" }, - { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" }, - { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" }, - { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" }, - { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" }, - { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" }, - { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" }, - { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" }, - { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" }, - { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" }, - { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" }, - { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" }, - { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" }, - { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" }, - { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, - { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, - { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, - { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, - { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, - { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, + { url = "https://files.pythonhosted.org/packages/1d/30/5bd3d794762481f8c8ae9c80e7b76ecea73b916959eb587521358ef0b2f9/pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0", size = 5304099, upload-time = "2026-02-11T04:20:06.13Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c1/aab9e8f3eeb4490180e357955e15c2ef74b31f64790ff356c06fb6cf6d84/pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713", size = 4657880, upload-time = "2026-02-11T04:20:09.291Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0a/9879e30d56815ad529d3985aeff5af4964202425c27261a6ada10f7cbf53/pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b", size = 6222587, upload-time = "2026-02-11T04:20:10.82Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5f/a1b72ff7139e4f89014e8d451442c74a774d5c43cd938fb0a9f878576b37/pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b", size = 8027678, upload-time = "2026-02-11T04:20:12.455Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c2/c7cb187dac79a3d22c3ebeae727abee01e077c8c7d930791dc592f335153/pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4", size = 6335777, upload-time = "2026-02-11T04:20:14.441Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7b/f9b09a7804ec7336effb96c26d37c29d27225783dc1501b7d62dcef6ae25/pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4", size = 7027140, upload-time = "2026-02-11T04:20:16.387Z" }, + { url = "https://files.pythonhosted.org/packages/98/b2/2fa3c391550bd421b10849d1a2144c44abcd966daadd2f7c12e19ea988c4/pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e", size = 6449855, upload-time = "2026-02-11T04:20:18.554Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/9caf4b5b950c669263c39e96c78c0d74a342c71c4f43fd031bb5cb7ceac9/pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff", size = 7151329, upload-time = "2026-02-11T04:20:20.646Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f8/4b24841f582704da675ca535935bccb32b00a6da1226820845fac4a71136/pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40", size = 6325574, upload-time = "2026-02-11T04:20:22.43Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/9f6b01c0881d7036063aa6612ef04c0e2cad96be21325a1e92d0203f8e91/pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23", size = 7032347, upload-time = "2026-02-11T04:20:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/79/13/c7922edded3dcdaf10c59297540b72785620abc0538872c819915746757d/pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9", size = 2453457, upload-time = "2026-02-11T04:20:25.392Z" }, + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, ] [[package]] From 7c4b134464f1a795c799e766583a5886c4a33315 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Feb 2026 06:56:53 +0000 Subject: [PATCH 115/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index dfa4eb489..6eadcc162 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,10 @@ hide: * 🌐 Update translations for ru (update-outdated). PR [#14909](https://github.com/fastapi/fastapi/pull/14909) by [@YuriiMotov](https://github.com/YuriiMotov). +### Internal + +* ⬆ Bump pillow from 12.1.0 to 12.1.1. PR [#14899](https://github.com/fastapi/fastapi/pull/14899) by [@dependabot[bot]](https://github.com/apps/dependabot). + ## 0.129.0 ### Breaking Changes From b93c964d7fc6ab44015e9a0da4af98c101417a83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 08:04:40 +0100 Subject: [PATCH 116/195] =?UTF-8?q?=E2=AC=86=20Bump=20cryptography=20from?= =?UTF-8?q?=2046.0.4=20to=2046.0.5=20(#14892)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.4 to 46.0.5. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/46.0.4...46.0.5) --- updated-dependencies: - dependency-name: cryptography dependency-version: 46.0.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- uv.lock | 100 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/uv.lock b/uv.lock index 78026fa67..aa8c558c7 100644 --- a/uv.lock +++ b/uv.lock @@ -843,62 +843,62 @@ wheels = [ [[package]] name = "cryptography" -version = "46.0.4" +version = "46.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/19/f748958276519adf6a0c1e79e7b8860b4830dda55ccdf29f2719b5fc499c/cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59", size = 749301, upload-time = "2026-01-28T00:24:37.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/99/157aae7949a5f30d51fcb1a9851e8ebd5c74bf99b5285d8bb4b8b9ee641e/cryptography-46.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:281526e865ed4166009e235afadf3a4c4cba6056f99336a99efba65336fd5485", size = 7173686, upload-time = "2026-01-28T00:23:07.515Z" }, - { url = "https://files.pythonhosted.org/packages/87/91/874b8910903159043b5c6a123b7e79c4559ddd1896e38967567942635778/cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc", size = 4275871, upload-time = "2026-01-28T00:23:09.439Z" }, - { url = "https://files.pythonhosted.org/packages/c0/35/690e809be77896111f5b195ede56e4b4ed0435b428c2f2b6d35046fbb5e8/cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0", size = 4423124, upload-time = "2026-01-28T00:23:11.529Z" }, - { url = "https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa", size = 4277090, upload-time = "2026-01-28T00:23:13.123Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d8/4bb7aec442a9049827aa34cee1aa83803e528fa55da9a9d45d01d1bb933e/cryptography-46.0.4-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5aa3e463596b0087b3da0dbe2b2487e9fc261d25da85754e30e3b40637d61f81", size = 4947652, upload-time = "2026-01-28T00:23:14.554Z" }, - { url = "https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255", size = 4455157, upload-time = "2026-01-28T00:23:16.443Z" }, - { url = "https://files.pythonhosted.org/packages/0a/05/19d849cf4096448779d2dcc9bb27d097457dac36f7273ffa875a93b5884c/cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e", size = 3981078, upload-time = "2026-01-28T00:23:17.838Z" }, - { url = "https://files.pythonhosted.org/packages/e6/89/f7bac81d66ba7cde867a743ea5b37537b32b5c633c473002b26a226f703f/cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c", size = 4276213, upload-time = "2026-01-28T00:23:19.257Z" }, - { url = "https://files.pythonhosted.org/packages/da/9f/7133e41f24edd827020ad21b068736e792bc68eecf66d93c924ad4719fb3/cryptography-46.0.4-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:eeeb2e33d8dbcccc34d64651f00a98cb41b2dc69cef866771a5717e6734dfa32", size = 4912190, upload-time = "2026-01-28T00:23:21.244Z" }, - { url = "https://files.pythonhosted.org/packages/a6/f7/6d43cbaddf6f65b24816e4af187d211f0bc536a29961f69faedc48501d8e/cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616", size = 4454641, upload-time = "2026-01-28T00:23:22.866Z" }, - { url = "https://files.pythonhosted.org/packages/9e/4f/ebd0473ad656a0ac912a16bd07db0f5d85184924e14fc88feecae2492834/cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0", size = 4405159, upload-time = "2026-01-28T00:23:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/d1/f7/7923886f32dc47e27adeff8246e976d77258fd2aa3efdd1754e4e323bf49/cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0", size = 4666059, upload-time = "2026-01-28T00:23:26.766Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a7/0fca0fd3591dffc297278a61813d7f661a14243dd60f499a7a5b48acb52a/cryptography-46.0.4-cp311-abi3-win32.whl", hash = "sha256:82a62483daf20b8134f6e92898da70d04d0ef9a75829d732ea1018678185f4f5", size = 3026378, upload-time = "2026-01-28T00:23:28.317Z" }, - { url = "https://files.pythonhosted.org/packages/2d/12/652c84b6f9873f0909374864a57b003686c642ea48c84d6c7e2c515e6da5/cryptography-46.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:6225d3ebe26a55dbc8ead5ad1265c0403552a63336499564675b29eb3184c09b", size = 3478614, upload-time = "2026-01-28T00:23:30.275Z" }, - { url = "https://files.pythonhosted.org/packages/b9/27/542b029f293a5cce59349d799d4d8484b3b1654a7b9a0585c266e974a488/cryptography-46.0.4-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:485e2b65d25ec0d901bca7bcae0f53b00133bf3173916d8e421f6fddde103908", size = 7116417, upload-time = "2026-01-28T00:23:31.958Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f5/559c25b77f40b6bf828eabaf988efb8b0e17b573545edb503368ca0a2a03/cryptography-46.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:078e5f06bd2fa5aea5a324f2a09f914b1484f1d0c2a4d6a8a28c74e72f65f2da", size = 4264508, upload-time = "2026-01-28T00:23:34.264Z" }, - { url = "https://files.pythonhosted.org/packages/49/a1/551fa162d33074b660dc35c9bc3616fefa21a0e8c1edd27b92559902e408/cryptography-46.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dce1e4f068f03008da7fa51cc7abc6ddc5e5de3e3d1550334eaf8393982a5829", size = 4409080, upload-time = "2026-01-28T00:23:35.793Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/4d8d129a755f5d6df1bbee69ea2f35ebfa954fa1847690d1db2e8bca46a5/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2067461c80271f422ee7bdbe79b9b4be54a5162e90345f86a23445a0cf3fd8a2", size = 4270039, upload-time = "2026-01-28T00:23:37.263Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f5/ed3fcddd0a5e39321e595e144615399e47e7c153a1fb8c4862aec3151ff9/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:c92010b58a51196a5f41c3795190203ac52edfd5dc3ff99149b4659eba9d2085", size = 4926748, upload-time = "2026-01-28T00:23:38.884Z" }, - { url = "https://files.pythonhosted.org/packages/43/ae/9f03d5f0c0c00e85ecb34f06d3b79599f20630e4db91b8a6e56e8f83d410/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:829c2b12bbc5428ab02d6b7f7e9bbfd53e33efd6672d21341f2177470171ad8b", size = 4442307, upload-time = "2026-01-28T00:23:40.56Z" }, - { url = "https://files.pythonhosted.org/packages/8b/22/e0f9f2dae8040695103369cf2283ef9ac8abe4d51f68710bec2afd232609/cryptography-46.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:62217ba44bf81b30abaeda1488686a04a702a261e26f87db51ff61d9d3510abd", size = 3959253, upload-time = "2026-01-28T00:23:42.827Z" }, - { url = "https://files.pythonhosted.org/packages/01/5b/6a43fcccc51dae4d101ac7d378a8724d1ba3de628a24e11bf2f4f43cba4d/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:9c2da296c8d3415b93e6053f5a728649a87a48ce084a9aaf51d6e46c87c7f2d2", size = 4269372, upload-time = "2026-01-28T00:23:44.655Z" }, - { url = "https://files.pythonhosted.org/packages/17/b7/0f6b8c1dd0779df2b526e78978ff00462355e31c0a6f6cff8a3e99889c90/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9b34d8ba84454641a6bf4d6762d15847ecbd85c1316c0a7984e6e4e9f748ec2e", size = 4891908, upload-time = "2026-01-28T00:23:46.48Z" }, - { url = "https://files.pythonhosted.org/packages/83/17/259409b8349aa10535358807a472c6a695cf84f106022268d31cea2b6c97/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:df4a817fa7138dd0c96c8c8c20f04b8aaa1fac3bbf610913dcad8ea82e1bfd3f", size = 4441254, upload-time = "2026-01-28T00:23:48.403Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fe/e4a1b0c989b00cee5ffa0764401767e2d1cf59f45530963b894129fd5dce/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b1de0ebf7587f28f9190b9cb526e901bf448c9e6a99655d2b07fff60e8212a82", size = 4396520, upload-time = "2026-01-28T00:23:50.26Z" }, - { url = "https://files.pythonhosted.org/packages/b3/81/ba8fd9657d27076eb40d6a2f941b23429a3c3d2f56f5a921d6b936a27bc9/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9b4d17bc7bd7cdd98e3af40b441feaea4c68225e2eb2341026c84511ad246c0c", size = 4651479, upload-time = "2026-01-28T00:23:51.674Z" }, - { url = "https://files.pythonhosted.org/packages/00/03/0de4ed43c71c31e4fe954edd50b9d28d658fef56555eba7641696370a8e2/cryptography-46.0.4-cp314-cp314t-win32.whl", hash = "sha256:c411f16275b0dea722d76544a61d6421e2cc829ad76eec79280dbdc9ddf50061", size = 3001986, upload-time = "2026-01-28T00:23:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/5c/70/81830b59df7682917d7a10f833c4dab2a5574cd664e86d18139f2b421329/cryptography-46.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:728fedc529efc1439eb6107b677f7f7558adab4553ef8669f0d02d42d7b959a7", size = 3468288, upload-time = "2026-01-28T00:23:55.09Z" }, - { url = "https://files.pythonhosted.org/packages/56/f7/f648fdbb61d0d45902d3f374217451385edc7e7768d1b03ff1d0e5ffc17b/cryptography-46.0.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9556ba711f7c23f77b151d5798f3ac44a13455cc68db7697a1096e6d0563cab", size = 7169583, upload-time = "2026-01-28T00:23:56.558Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cc/8f3224cbb2a928de7298d6ed4790f5ebc48114e02bdc9559196bfb12435d/cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef", size = 4275419, upload-time = "2026-01-28T00:23:58.364Z" }, - { url = "https://files.pythonhosted.org/packages/17/43/4a18faa7a872d00e4264855134ba82d23546c850a70ff209e04ee200e76f/cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d", size = 4419058, upload-time = "2026-01-28T00:23:59.867Z" }, - { url = "https://files.pythonhosted.org/packages/ee/64/6651969409821d791ba12346a124f55e1b76f66a819254ae840a965d4b9c/cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973", size = 4278151, upload-time = "2026-01-28T00:24:01.731Z" }, - { url = "https://files.pythonhosted.org/packages/20/0b/a7fce65ee08c3c02f7a8310cc090a732344066b990ac63a9dfd0a655d321/cryptography-46.0.4-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a90e43e3ef65e6dcf969dfe3bb40cbf5aef0d523dff95bfa24256be172a845f4", size = 4939441, upload-time = "2026-01-28T00:24:03.175Z" }, - { url = "https://files.pythonhosted.org/packages/db/a7/20c5701e2cd3e1dfd7a19d2290c522a5f435dd30957d431dcb531d0f1413/cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af", size = 4451617, upload-time = "2026-01-28T00:24:05.403Z" }, - { url = "https://files.pythonhosted.org/packages/00/dc/3e16030ea9aa47b63af6524c354933b4fb0e352257c792c4deeb0edae367/cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263", size = 3977774, upload-time = "2026-01-28T00:24:06.851Z" }, - { url = "https://files.pythonhosted.org/packages/42/c8/ad93f14118252717b465880368721c963975ac4b941b7ef88f3c56bf2897/cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095", size = 4277008, upload-time = "2026-01-28T00:24:08.926Z" }, - { url = "https://files.pythonhosted.org/packages/00/cf/89c99698151c00a4631fbfcfcf459d308213ac29e321b0ff44ceeeac82f1/cryptography-46.0.4-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d5a45ddc256f492ce42a4e35879c5e5528c09cd9ad12420828c972951d8e016b", size = 4903339, upload-time = "2026-01-28T00:24:12.009Z" }, - { url = "https://files.pythonhosted.org/packages/03/c3/c90a2cb358de4ac9309b26acf49b2a100957e1ff5cc1e98e6c4996576710/cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019", size = 4451216, upload-time = "2026-01-28T00:24:13.975Z" }, - { url = "https://files.pythonhosted.org/packages/96/2c/8d7f4171388a10208671e181ca43cdc0e596d8259ebacbbcfbd16de593da/cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4", size = 4404299, upload-time = "2026-01-28T00:24:16.169Z" }, - { url = "https://files.pythonhosted.org/packages/e9/23/cbb2036e450980f65c6e0a173b73a56ff3bccd8998965dea5cc9ddd424a5/cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b", size = 4664837, upload-time = "2026-01-28T00:24:17.629Z" }, - { url = "https://files.pythonhosted.org/packages/0a/21/f7433d18fe6d5845329cbdc597e30caf983229c7a245bcf54afecc555938/cryptography-46.0.4-cp38-abi3-win32.whl", hash = "sha256:0563655cb3c6d05fb2afe693340bc050c30f9f34e15763361cf08e94749401fc", size = 3009779, upload-time = "2026-01-28T00:24:20.198Z" }, - { url = "https://files.pythonhosted.org/packages/3a/6a/bd2e7caa2facffedf172a45c1a02e551e6d7d4828658c9a245516a598d94/cryptography-46.0.4-cp38-abi3-win_amd64.whl", hash = "sha256:fa0900b9ef9c49728887d1576fd8d9e7e3ea872fa9b25ef9b64888adc434e976", size = 3466633, upload-time = "2026-01-28T00:24:21.851Z" }, - { url = "https://files.pythonhosted.org/packages/59/e0/f9c6c53e1f2a1c2507f00f2faba00f01d2f334b35b0fbfe5286715da2184/cryptography-46.0.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:766330cce7416c92b5e90c3bb71b1b79521760cdcfc3a6a1a182d4c9fab23d2b", size = 3476316, upload-time = "2026-01-28T00:24:24.144Z" }, - { url = "https://files.pythonhosted.org/packages/27/7a/f8d2d13227a9a1a9fe9c7442b057efecffa41f1e3c51d8622f26b9edbe8f/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c236a44acfb610e70f6b3e1c3ca20ff24459659231ef2f8c48e879e2d32b73da", size = 4216693, upload-time = "2026-01-28T00:24:25.758Z" }, - { url = "https://files.pythonhosted.org/packages/c5/de/3787054e8f7972658370198753835d9d680f6cd4a39df9f877b57f0dd69c/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8a15fb869670efa8f83cbffbc8753c1abf236883225aed74cd179b720ac9ec80", size = 4382765, upload-time = "2026-01-28T00:24:27.577Z" }, - { url = "https://files.pythonhosted.org/packages/8a/5f/60e0afb019973ba6a0b322e86b3d61edf487a4f5597618a430a2a15f2d22/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:fdc3daab53b212472f1524d070735b2f0c214239df131903bae1d598016fa822", size = 4216066, upload-time = "2026-01-28T00:24:29.056Z" }, - { url = "https://files.pythonhosted.org/packages/81/8e/bf4a0de294f147fee66f879d9bae6f8e8d61515558e3d12785dd90eca0be/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:44cc0675b27cadb71bdbb96099cca1fa051cd11d2ade09e5cd3a2edb929ed947", size = 4382025, upload-time = "2026-01-28T00:24:30.681Z" }, - { url = "https://files.pythonhosted.org/packages/79/f4/9ceb90cfd6a3847069b0b0b353fd3075dc69b49defc70182d8af0c4ca390/cryptography-46.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be8c01a7d5a55f9a47d1888162b76c8f49d62b234d88f0ff91a9fbebe32ffbc3", size = 3406043, upload-time = "2026-01-28T00:24:32.236Z" }, + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, + { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, + { url = "https://files.pythonhosted.org/packages/eb/dd/2d9fdb07cebdf3d51179730afb7d5e576153c6744c3ff8fded23030c204e/cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c", size = 3476964, upload-time = "2026-02-10T19:18:20.687Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6f/6cc6cc9955caa6eaf83660b0da2b077c7fe8ff9950a3c5e45d605038d439/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", size = 4218321, upload-time = "2026-02-10T19:18:22.349Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5d/c4da701939eeee699566a6c1367427ab91a8b7088cc2328c09dbee940415/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", size = 4381786, upload-time = "2026-02-10T19:18:24.529Z" }, + { url = "https://files.pythonhosted.org/packages/ac/97/a538654732974a94ff96c1db621fa464f455c02d4bb7d2652f4edc21d600/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", size = 4217990, upload-time = "2026-02-10T19:18:25.957Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/7e500d2dd3ba891197b9efd2da5454b74336d64a7cc419aa7327ab74e5f6/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", size = 4381252, upload-time = "2026-02-10T19:18:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" }, ] [[package]] From fdbbf7490835c83b8e8c1d881e1989acc8cb4fda Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Feb 2026 07:05:05 +0000 Subject: [PATCH 117/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 6eadcc162..5eec22e0b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -17,6 +17,7 @@ hide: ### Internal +* ⬆ Bump cryptography from 46.0.4 to 46.0.5. PR [#14892](https://github.com/fastapi/fastapi/pull/14892) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump pillow from 12.1.0 to 12.1.1. PR [#14899](https://github.com/fastapi/fastapi/pull/14899) by [@dependabot[bot]](https://github.com/apps/dependabot). ## 0.129.0 From ea8db708f10e3e966062eeafdcfc95e83b1003d5 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:41:38 +0100 Subject: [PATCH 118/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20tr=20(update-all)=20(#14913)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all * 🎨 Auto format --------- Co-authored-by: github-actions[bot] --- docs/tr/docs/_llm-test.md | 46 +-- docs/tr/docs/advanced/additional-responses.md | 4 +- .../tr/docs/advanced/advanced-dependencies.md | 8 +- .../tr/docs/advanced/advanced-python-types.md | 61 +++ docs/tr/docs/advanced/async-tests.md | 8 +- docs/tr/docs/advanced/behind-a-proxy.md | 20 +- docs/tr/docs/advanced/custom-response.md | 32 +- docs/tr/docs/advanced/dataclasses.md | 2 +- docs/tr/docs/advanced/events.md | 16 +- docs/tr/docs/advanced/generate-clients.md | 8 +- docs/tr/docs/advanced/index.md | 4 +- docs/tr/docs/advanced/middleware.md | 6 +- docs/tr/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 20 +- .../advanced/response-change-status-code.md | 2 +- docs/tr/docs/advanced/response-cookies.md | 6 +- docs/tr/docs/advanced/response-directly.md | 6 +- docs/tr/docs/advanced/response-headers.md | 4 +- .../docs/advanced/security/http-basic-auth.md | 8 +- docs/tr/docs/advanced/security/index.md | 4 +- .../docs/advanced/security/oauth2-scopes.md | 4 +- docs/tr/docs/advanced/settings.md | 20 +- docs/tr/docs/advanced/sub-applications.md | 8 +- docs/tr/docs/advanced/templates.md | 6 +- docs/tr/docs/advanced/testing-dependencies.md | 1 + docs/tr/docs/advanced/testing-events.md | 6 +- docs/tr/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/tr/docs/advanced/websockets.md | 8 +- docs/tr/docs/advanced/wsgi.md | 2 +- docs/tr/docs/alternatives.md | 8 +- docs/tr/docs/async.md | 379 ++++++++++-------- docs/tr/docs/deployment/concepts.md | 30 +- docs/tr/docs/deployment/docker.md | 10 +- docs/tr/docs/deployment/https.md | 2 +- docs/tr/docs/deployment/index.md | 6 +- docs/tr/docs/deployment/manually.md | 4 +- docs/tr/docs/environment-variables.md | 4 +- docs/tr/docs/features.md | 195 +++++---- docs/tr/docs/history-design-future.md | 2 +- .../authentication-error-status-code.md | 2 +- docs/tr/docs/how-to/conditional-openapi.md | 6 +- docs/tr/docs/how-to/configure-swagger-ui.md | 8 +- docs/tr/docs/how-to/custom-docs-ui-assets.md | 14 +- docs/tr/docs/how-to/extending-openapi.md | 24 +- docs/tr/docs/how-to/general.md | 2 +- docs/tr/docs/how-to/graphql.md | 2 +- docs/tr/docs/how-to/index.md | 2 +- ...migrate-from-pydantic-v1-to-pydantic-v2.md | 2 +- docs/tr/docs/index.md | 14 +- docs/tr/docs/python-types.md | 198 ++------- docs/tr/docs/translation-banner.md | 2 +- docs/tr/docs/tutorial/background-tasks.md | 6 +- docs/tr/docs/tutorial/bigger-applications.md | 26 +- docs/tr/docs/tutorial/body-multiple-params.md | 6 - docs/tr/docs/tutorial/body-nested-models.md | 6 +- docs/tr/docs/tutorial/body.md | 2 +- docs/tr/docs/tutorial/cookie-param-models.md | 20 +- docs/tr/docs/tutorial/cookie-params.md | 12 +- docs/tr/docs/tutorial/cors.md | 4 +- docs/tr/docs/tutorial/debugging.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +- ...pendencies-in-path-operation-decorators.md | 8 +- .../dependencies/dependencies-with-yield.md | 27 +- .../dependencies/global-dependencies.md | 2 +- docs/tr/docs/tutorial/dependencies/index.md | 2 +- .../tutorial/dependencies/sub-dependencies.md | 6 +- docs/tr/docs/tutorial/extra-data-types.md | 16 +- docs/tr/docs/tutorial/extra-models.md | 14 +- docs/tr/docs/tutorial/first-steps.md | 16 +- docs/tr/docs/tutorial/handling-errors.md | 16 +- docs/tr/docs/tutorial/index.md | 2 +- docs/tr/docs/tutorial/metadata.md | 12 +- docs/tr/docs/tutorial/middleware.md | 6 +- .../tutorial/path-operation-configuration.md | 10 +- .../path-params-numeric-validations.md | 20 +- docs/tr/docs/tutorial/path-params.md | 28 +- .../tutorial/query-params-str-validations.md | 51 +-- docs/tr/docs/tutorial/query-params.md | 8 +- docs/tr/docs/tutorial/request-files.md | 14 +- docs/tr/docs/tutorial/request-form-models.md | 4 +- .../docs/tutorial/request-forms-and-files.md | 4 +- docs/tr/docs/tutorial/request-forms.md | 10 +- docs/tr/docs/tutorial/response-model.md | 8 +- docs/tr/docs/tutorial/response-status-code.md | 8 +- docs/tr/docs/tutorial/schema-extra-example.md | 2 +- docs/tr/docs/tutorial/security/first-steps.md | 6 +- .../tutorial/security/get-current-user.md | 2 +- docs/tr/docs/tutorial/security/oauth2-jwt.md | 12 +- docs/tr/docs/tutorial/sql-databases.md | 30 +- docs/tr/docs/tutorial/static-files.md | 4 +- docs/tr/docs/tutorial/testing.md | 16 +- docs/tr/docs/virtual-environments.md | 2 +- 93 files changed, 833 insertions(+), 883 deletions(-) create mode 100644 docs/tr/docs/advanced/advanced-python-types.md diff --git a/docs/tr/docs/_llm-test.md b/docs/tr/docs/_llm-test.md index 0ca218e6e..0ba5cca75 100644 --- a/docs/tr/docs/_llm-test.md +++ b/docs/tr/docs/_llm-test.md @@ -27,7 +27,7 @@ Bu bir code snippet: `foo`. Bu da başka bir code snippet: `bar`. Bir tane daha: Code snippet'lerin içeriği olduğu gibi bırakılmalıdır. -`script/translate.py` içindeki genel prompt'ta `### Content of code snippets` bölümüne bakın. +`scripts/translate.py` içindeki genel prompt'ta `### Content of code snippets` bölümüne bakın. //// @@ -116,7 +116,7 @@ works(foo="bar") # This works 🎉 Code block'ların içindeki code değiştirilmemelidir; tek istisna yorumlardır (comments). -`script/translate.py` içindeki genel prompt'ta `### Content of code blocks` bölümüne bakın. +`scripts/translate.py` içindeki genel prompt'ta `### Content of code blocks` bölümüne bakın. //// @@ -125,31 +125,31 @@ Code block'ların içindeki code değiştirilmemelidir; tek istisna yorumlardır //// tab | Test /// info | Bilgi -Some text +Bazı metin /// /// note | Not -Some text +Bazı metin /// /// note | Teknik Detaylar -Some text +Bazı metin /// /// check | Ek bilgi -Some text +Bazı metin /// /// tip | İpucu -Some text +Bazı metin /// /// warning | Uyarı -Some text +Bazı metin /// /// danger | Tehlike -Some text +Bazı metin /// //// @@ -158,7 +158,7 @@ Some text Sekmelerin ve `Info`/`Note`/`Warning`/vb. blokların başlığı, dikey çizgiden (`|`) sonra çeviri olarak eklenmelidir. -`script/translate.py` içindeki genel prompt'ta `### Special blocks` ve `### Tab blocks` bölümlerine bakın. +`scripts/translate.py` içindeki genel prompt'ta `### Special blocks` ve `### Tab blocks` bölümlerine bakın. //// @@ -170,10 +170,10 @@ Link metni çevrilmelidir, link adresi değişmeden kalmalıdır: * [Yukarıdaki başlığa link](#code-snippets) * [Internal link](index.md#installation){.internal-link target=_blank} -* External link -* Link to a style -* Link to a script -* Link to an image +* Harici link +* Bir stile bağlantı +* Bir betiğe bağlantı +* Bir görsele bağlantı Link metni çevrilmelidir, link adresi çeviriye işaret etmelidir: @@ -185,7 +185,7 @@ Link metni çevrilmelidir, link adresi çeviriye işaret etmelidir: Link'ler çevrilmelidir, ancak adresleri değişmeden kalmalıdır. Bir istisna, FastAPI dokümantasyonunun sayfalarına verilen mutlak link'lerdir. Bu durumda link, çeviriye işaret etmelidir. -`script/translate.py` içindeki genel prompt'ta `### Links` bölümüne bakın. +`scripts/translate.py` içindeki genel prompt'ta `### Links` bölümüne bakın. //// @@ -199,14 +199,9 @@ Burada HTML "abbr" öğeleriyle sarılmış bazı şeyler var (bazıları uydurm * GTD * lt -* XWT +* XWT * PSGI -### abbr bir açıklama verir { #the-abbr-gives-an-explanation } - -* cluster -* Deep Learning - ### abbr tam bir ifade ve bir açıklama verir { #the-abbr-gives-a-full-phrase-and-an-explanation } * MDN @@ -220,10 +215,15 @@ Burada HTML "abbr" öğeleriyle sarılmış bazı şeyler var (bazıları uydurm Çeviriler, LLM'nin kaldırmaması gereken kendi "abbr" öğelerini ekleyebilir. Örneğin İngilizce kelimeleri açıklamak için. -`script/translate.py` içindeki genel prompt'ta `### HTML abbr elements` bölümüne bakın. +`scripts/translate.py` içindeki genel prompt'ta `### HTML abbr elements` bölümüne bakın. //// +## HTML "dfn" öğeleri { #html-dfn-elements } + +* küme +* Derin Öğrenme + ## Başlıklar { #headings } //// tab | Test @@ -246,7 +246,7 @@ Tekrar merhaba. Başlıklarla ilgili tek katı kural, LLM'nin süslü parantezler içindeki hash kısmını değiştirmemesidir; böylece link'ler bozulmaz. -`script/translate.py` içindeki genel prompt'ta `### Headings` bölümüne bakın. +`scripts/translate.py` içindeki genel prompt'ta `### Headings` bölümüne bakın. Dile özel bazı talimatlar için örneğin `docs/de/llm-prompt.md` içindeki `### Headings` bölümüne bakın. diff --git a/docs/tr/docs/advanced/additional-responses.md b/docs/tr/docs/advanced/additional-responses.md index c8e372775..3a066af40 100644 --- a/docs/tr/docs/advanced/additional-responses.md +++ b/docs/tr/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ Bu response `dict`'lerinin her birinde, `response_model`'e benzer şekilde bir P Örneğin, `404` status code'u ve `Message` Pydantic model'i ile başka bir response tanımlamak için şunu yazabilirsiniz: -{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} /// note | Not @@ -203,7 +203,7 @@ Varsayılan `200` status code'unu (ya da gerekiyorsa özel bir tane) kullanarak Ayrıca `response_model`'inizi kullanan, ancak özel bir `example` içeren `200` status code'lu bir response da tanımlayabilirsiniz: -{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} Bunların hepsi OpenAPI'nize birleştirilerek dahil edilir ve API dokümanlarında gösterilir: diff --git a/docs/tr/docs/advanced/advanced-dependencies.md b/docs/tr/docs/advanced/advanced-dependencies.md index 8afb544bd..8be92a6ac 100644 --- a/docs/tr/docs/advanced/advanced-dependencies.md +++ b/docs/tr/docs/advanced/advanced-dependencies.md @@ -18,7 +18,7 @@ Class'ın kendisini değil (zaten callable'dır), o class'ın bir instance'ını Bunu yapmak için `__call__` adında bir method tanımlarız: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} Bu durumda, ek parametreleri ve alt-dependency'leri kontrol etmek için **FastAPI**'nin kullanacağı şey bu `__call__` olacaktır; ayrıca daha sonra *path operation function* içindeki parametreye bir değer geçmek için çağrılacak olan da budur. @@ -26,7 +26,7 @@ Bu durumda, ek parametreleri ve alt-dependency'leri kontrol etmek için **FastAP Ve şimdi, dependency'yi "parametreleştirmek" için kullanacağımız instance parametrelerini tanımlamak üzere `__init__` kullanabiliriz: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} Bu durumda **FastAPI**, `__init__` ile asla uğraşmaz veya onu önemsemez; onu doğrudan kendi kodumuzda kullanırız. @@ -34,7 +34,7 @@ Bu durumda **FastAPI**, `__init__` ile asla uğraşmaz veya onu önemsemez; onu Bu class'tan bir instance'ı şöyle oluşturabiliriz: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} Böylece dependency'mizi "parametreleştirmiş" oluruz; artık içinde `"bar"` vardır ve bu değer `checker.fixed_content` attribute'u olarak durur. @@ -50,7 +50,7 @@ checker(q="somequery") ...ve bunun döndürdüğü her şeyi, *path operation function* içinde `fixed_content_included` parametresine dependency değeri olarak geçirir: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} /// tip | İpucu diff --git a/docs/tr/docs/advanced/advanced-python-types.md b/docs/tr/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..91fe1d5e0 --- /dev/null +++ b/docs/tr/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# Gelişmiş Python Tipleri { #advanced-python-types } + +Python tipleriyle çalışırken işinize yarayabilecek bazı ek fikirler. + +## `Union` veya `Optional` Kullanımı { #using-union-or-optional } + +Kodunuz herhangi bir nedenle `|` kullanamıyorsa — örneğin bir tip açıklamasında (type annotation) değil de `response_model=` gibi bir yerdeyse — dikey çizgi (`|`) yerine `typing` içindeki `Union`'ı kullanabilirsiniz. + +Örneğin, bir şeyin `str` ya da `None` olabileceğini şöyle belirtebilirsiniz: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +`typing`, bir şeyin `None` olabileceğini belirtmek için `Optional` ile bir kısayol da sunar. + +Benim oldukça öznel bakış açıma göre küçük bir ipucu: + +- 🚨 `Optional[SomeType]` kullanmaktan kaçının +- Bunun yerine ✨ **`Union[SomeType, None]` kullanın** ✨. + +İkisi de eşdeğer ve temelde aynıdır; ancak "**optional**" kelimesi değerin isteğe bağlı olduğunu ima eder. Oysa aslında " `None` olabilir" demektir; değer isteğe bağlı olmasa ve hâlâ zorunlu olsa bile. + +Bence `Union[SomeType, None]` ne demek istediğini daha açık anlatır. + +Burada mesele sadece kelimeler ve isimler. Ancak bu kelimeler sizin ve ekip arkadaşlarınızın koda bakışını etkileyebilir. + +Örnek olarak şu fonksiyona bakalım: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +`name` parametresi `Optional[str]` olarak tanımlıdır; ancak isteğe bağlı değildir, parametre olmadan fonksiyonu çağıramazsınız: + +```Python +say_hi() # Ah hayır, bu hata fırlatır! 😱 +``` + +`name` parametresi varsayılan bir değeri olmadığı için hâlâ zorunludur (yani *optional* değildir). Yine de `name`, değer olarak `None` kabul eder: + +```Python +say_hi(name=None) # Bu çalışır, None geçerlidir 🎉 +``` + +İyi haber şu ki, çoğu durumda tip birliklerini (union) tanımlamak için doğrudan `|` kullanabilirsiniz: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +Dolayısıyla, normalde `Optional` ve `Union` gibi isimler için endişelenmenize gerek yok. 😎 diff --git a/docs/tr/docs/advanced/async-tests.md b/docs/tr/docs/advanced/async-tests.md index 82349bbec..1e5b37a3d 100644 --- a/docs/tr/docs/advanced/async-tests.md +++ b/docs/tr/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ Basit bir örnek için, [Bigger Applications](../tutorial/bigger-applications.md `main.py` dosyası şöyle olur: -{* ../../docs_src/async_tests/app_a_py39/main.py *} +{* ../../docs_src/async_tests/app_a_py310/main.py *} `test_main.py` dosyasında `main.py` için testler yer alır, artık şöyle görünebilir: -{* ../../docs_src/async_tests/app_a_py39/test_main.py *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} ## Çalıştırma { #run-it } @@ -56,7 +56,7 @@ $ pytest `@pytest.mark.anyio` marker'ı, pytest'e bu test fonksiyonunun asenkron olarak çağrılması gerektiğini söyler: -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} /// tip | İpucu @@ -66,7 +66,7 @@ Test fonksiyonu artık `TestClient` kullanırken eskiden olduğu gibi sadece `de Ardından app ile bir `AsyncClient` oluşturup `await` kullanarak ona async request'ler gönderebiliriz. -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} Bu, şu kullanıma denktir: diff --git a/docs/tr/docs/advanced/behind-a-proxy.md b/docs/tr/docs/advanced/behind-a-proxy.md index e70b16960..5cf9a534d 100644 --- a/docs/tr/docs/advanced/behind-a-proxy.md +++ b/docs/tr/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" Örneğin `/items/` adında bir *path operation* tanımladığınızı düşünelim: -{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} Client `/items`'a gitmeye çalışırsa, varsayılan olarak `/items/`'a redirect edilir. @@ -115,7 +115,7 @@ Bu durumda, orijinal `/app` path'i aslında `/api/v1/app` altında servis edilir Kodunuzun tamamı sadece `/app` varmış gibi yazılmış olsa bile. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} Proxy, request'i app server'a (muhtemelen FastAPI CLI üzerinden Uvicorn) iletmeden önce **path prefix**'i anlık olarak **"kırpar"** (strip). Böylece uygulamanız hâlâ `/app` altında servis ediliyormuş gibi davranır ve tüm kodunuzu `/api/v1` prefix'ini içerecek şekilde güncellemeniz gerekmez. @@ -149,14 +149,14 @@ Docs UI'nin, bu API `server`'ının (proxy arkasında) `/api/v1` altında bulund ```JSON hl_lines="4-8" { "openapi": "3.1.0", - // More stuff here + // Burada daha fazla şey var "servers": [ { "url": "/api/v1" } ], "paths": { - // More stuff here + // Burada daha fazla şey var } } ``` @@ -193,7 +193,7 @@ Uygulamanızın her request için kullandığı mevcut `root_path` değerini ala Burada sadece göstermek için bunu mesaja dahil ediyoruz. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} Ardından Uvicorn'u şu şekilde başlatırsanız: @@ -220,7 +220,7 @@ Response şöyle bir şey olur: Alternatif olarak, `--root-path` gibi bir komut satırı seçeneği (veya muadili) sağlayamıyorsanız, FastAPI uygulamanızı oluştururken `root_path` parametresini ayarlayabilirsiniz: -{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} `FastAPI`'ye `root_path` vermek, Uvicorn veya Hypercorn'a `--root-path` komut satırı seçeneğini vermekle eşdeğerdir. @@ -400,14 +400,14 @@ Ancak başka alternatif `servers` da sağlayabilirsiniz; örneğin *aynı* docs Örneğin: -{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} Şöyle bir OpenAPI şeması üretir: ```JSON hl_lines="5-7" { "openapi": "3.1.0", - // More stuff here + // Burada daha fazla şey var "servers": [ { "url": "/api/v1" @@ -422,7 +422,7 @@ Ancak başka alternatif `servers` da sağlayabilirsiniz; örneğin *aynı* docs } ], "paths": { - // More stuff here + // Burada daha fazla şey var } } ``` @@ -455,7 +455,7 @@ OpenAPI spesifikasyonunda `servers` özelliği opsiyoneldir. **FastAPI**'nin `root_path` kullanarak otomatik bir server eklemesini istemiyorsanız, `root_path_in_servers=False` parametresini kullanabilirsiniz: -{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} Böylece OpenAPI şemasına dahil etmez. diff --git a/docs/tr/docs/advanced/custom-response.md b/docs/tr/docs/advanced/custom-response.md index c5148f428..218a5de5c 100644 --- a/docs/tr/docs/advanced/custom-response.md +++ b/docs/tr/docs/advanced/custom-response.md @@ -30,7 +30,7 @@ Büyük response'larda, doğrudan bir `Response` döndürmek bir dictionary dön Ancak döndürdüğünüz içeriğin **JSON ile serialize edilebilir** olduğundan eminseniz, onu doğrudan response class’ına verebilir ve FastAPI’nin response class’ına vermeden önce dönüş içeriğinizi `jsonable_encoder` içinden geçirirken oluşturacağı ek yükten kaçınabilirsiniz. -{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} /// info | Bilgi @@ -55,7 +55,7 @@ Ve OpenAPI’de de bu şekilde dokümante edilir. * `HTMLResponse` import edin. * *path operation decorator*’ınızın `response_class` parametresi olarak `HTMLResponse` verin. -{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} /// info | Bilgi @@ -73,7 +73,7 @@ Ve OpenAPI’de de bu şekilde dokümante edilir. Yukarıdaki örneğin aynısı, bu sefer bir `HTMLResponse` döndürerek, şöyle görünebilir: -{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} /// warning | Uyarı @@ -97,7 +97,7 @@ Bu durumda `response_class` sadece OpenAPI *path operation*’ını dokümante e Örneğin şöyle bir şey olabilir: -{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} Bu örnekte `generate_html_response()` fonksiyonu, HTML’i bir `str` olarak döndürmek yerine zaten bir `Response` üretip döndürmektedir. @@ -136,7 +136,7 @@ Bunu doğrudan döndürebilirsiniz. FastAPI (aslında Starlette) otomatik olarak bir Content-Length header’ı ekler. Ayrıca `media_type`’a göre bir Content-Type header’ı ekler ve text türleri için sona bir charset ekler. -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ### `HTMLResponse` { #htmlresponse } @@ -146,7 +146,7 @@ Yukarıda okuduğunuz gibi, bir miktar text veya bytes alır ve HTML response d Bir miktar text veya bytes alır ve düz metin response döndürür. -{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} ### `JSONResponse` { #jsonresponse } @@ -180,7 +180,7 @@ Bunun için `ujson` kurulmalıdır; örneğin `pip install ujson`. /// -{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} /// tip | İpucu @@ -194,13 +194,13 @@ HTTP redirect döndürür. Varsayılan olarak 307 status code (Temporary Redirec `RedirectResponse`’u doğrudan döndürebilirsiniz: -{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} --- Veya `response_class` parametresi içinde kullanabilirsiniz: -{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} Bunu yaparsanız, *path operation* function’ınızdan doğrudan URL döndürebilirsiniz. @@ -210,13 +210,13 @@ Bu durumda kullanılan `status_code`, `RedirectResponse` için varsayılan olan Ayrıca `status_code` parametresini `response_class` parametresiyle birlikte kullanabilirsiniz: -{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} ### `StreamingResponse` { #streamingresponse } Bir async generator veya normal generator/iterator alır ve response body’yi stream eder. -{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} #### `StreamingResponse`’u file-like objelerle kullanma { #using-streamingresponse-with-file-like-objects } @@ -226,7 +226,7 @@ Böylece önce hepsini memory’ye okumak zorunda kalmazsınız; bu generator fu Buna cloud storage ile etkileşime giren, video işleyen ve benzeri birçok kütüphane dahildir. -{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} 1. Bu generator function’dır. İçinde `yield` ifadeleri olduğu için "generator function" denir. 2. Bir `with` bloğu kullanarak, generator function bittiğinde file-like objenin kapandığından emin oluruz. Yani response göndermeyi bitirdikten sonra kapanır. @@ -255,11 +255,11 @@ Diğer response türlerine göre instantiate ederken farklı argümanlar alır: File response'ları uygun `Content-Length`, `Last-Modified` ve `ETag` header’larını içerir. -{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} `response_class` parametresini de kullanabilirsiniz: -{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} Bu durumda *path operation* function’ınızdan doğrudan dosya path'ini döndürebilirsiniz. @@ -273,7 +273,7 @@ Diyelim ki girintili ve biçimlendirilmiş JSON döndürmek istiyorsunuz; bunun Bir `CustomORJSONResponse` oluşturabilirsiniz. Burada yapmanız gereken temel şey, content’i `bytes` olarak döndüren bir `Response.render(content)` metodu yazmaktır: -{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} Artık şunu döndürmek yerine: @@ -299,7 +299,7 @@ Bunu tanımlayan parametre `default_response_class`’tır. Aşağıdaki örnekte **FastAPI**, tüm *path operations* için varsayılan olarak `JSONResponse` yerine `ORJSONResponse` kullanır. -{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} /// tip | İpucu diff --git a/docs/tr/docs/advanced/dataclasses.md b/docs/tr/docs/advanced/dataclasses.md index 263976007..ed70a6a94 100644 --- a/docs/tr/docs/advanced/dataclasses.md +++ b/docs/tr/docs/advanced/dataclasses.md @@ -64,7 +64,7 @@ Bu durumda standart `dataclasses` yerine, drop-in replacement olan `pydantic.dat 6. Burada `items` içeren bir dictionary döndürüyoruz; `items` bir dataclass listesi. - FastAPI, veriyi JSON'a serializing etmeyi yine başarır. + FastAPI, veriyi JSON'a serileştirmeyi yine başarır. 7. Burada `response_model`, `Author` dataclass'larından oluşan bir listenin type annotation'ını kullanıyor. diff --git a/docs/tr/docs/advanced/events.md b/docs/tr/docs/advanced/events.md index 257b952f9..cef8b42a5 100644 --- a/docs/tr/docs/advanced/events.md +++ b/docs/tr/docs/advanced/events.md @@ -30,7 +30,7 @@ Bu *startup* ve *shutdown* mantığını, `FastAPI` uygulamasının `lifespan` p Aşağıdaki gibi `yield` kullanan async bir `lifespan()` fonksiyonu oluşturuyoruz: -{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} Burada, `yield` öncesinde (sahte) model fonksiyonunu machine learning modellerini içeren dictionary’e koyarak, modeli yükleme gibi maliyetli bir *startup* işlemini simüle ediyoruz. Bu kod, *startup* sırasında, uygulama **request almaya başlamadan önce** çalıştırılır. @@ -48,7 +48,7 @@ Belki yeni bir sürüm başlatmanız gerekiyordur, ya da çalıştırmaktan sık Dikkat edilmesi gereken ilk şey, `yield` içeren async bir fonksiyon tanımlıyor olmamız. Bu, `yield` kullanan Dependencies’e oldukça benzer. -{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} Fonksiyonun `yield`’den önceki kısmı, uygulama başlamadan **önce** çalışır. @@ -60,7 +60,7 @@ Bakarsanız, fonksiyon `@asynccontextmanager` ile dekore edilmiş. Bu da fonksiyonu "**async context manager**" denen şeye dönüştürür. -{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} Python’da **context manager**, `with` ifadesi içinde kullanabildiğiniz bir yapıdır. Örneğin `open()` bir context manager olarak kullanılabilir: @@ -82,7 +82,7 @@ Yukarıdaki kod örneğimizde bunu doğrudan kullanmıyoruz; bunun yerine FastAP `FastAPI` uygulamasının `lifespan` parametresi bir **async context manager** alır; dolayısıyla oluşturduğumuz yeni `lifespan` async context manager’ını buraya geçebiliriz. -{* ../../docs_src/events/tutorial003_py39.py hl[22] *} +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} ## Alternatif Events (kullanımdan kaldırıldı) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ Bu fonksiyonlar `async def` ile veya normal `def` ile tanımlanabilir. Uygulama başlamadan önce çalıştırılacak bir fonksiyon eklemek için, `"startup"` event’i ile tanımlayın: -{* ../../docs_src/events/tutorial001_py39.py hl[8] *} +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} Bu durumda `startup` event handler fonksiyonu, "database" öğesini (sadece bir `dict`) bazı değerlerle başlatır. @@ -116,7 +116,7 @@ Ve tüm `startup` event handler’ları tamamlanmadan uygulamanız request almay Uygulama kapanırken çalıştırılacak bir fonksiyon eklemek için, `"shutdown"` event’i ile tanımlayın: -{* ../../docs_src/events/tutorial002_py39.py hl[6] *} +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} Burada `shutdown` event handler fonksiyonu, `log.txt` dosyasına `"Application shutdown"` satırını yazar. @@ -150,11 +150,11 @@ Bu nedenle artık bunun yerine, yukarıda açıklandığı gibi `lifespan` kulla Meraklı nerd’ler için küçük bir teknik detay. 🤓 -Altta, ASGI teknik spesifikasyonunda bu, Lifespan Protocol’ün bir parçasıdır ve `startup` ile `shutdown` adında event’ler tanımlar. +Altta, ASGI teknik spesifikasyonunda bu, Lifespan Protokolü’nün bir parçasıdır ve `startup` ile `shutdown` adında event’ler tanımlar. /// info | Bilgi -Starlette `lifespan` handler’ları hakkında daha fazlasını Starlette's Lifespan docs içinde okuyabilirsiniz. +Starlette `lifespan` handler’ları hakkında daha fazlasını Starlette Lifespan dokümanları içinde okuyabilirsiniz. Ayrıca kodunuzun başka bölgelerinde de kullanılabilecek lifespan state’i nasıl yöneteceğinizi de kapsar. diff --git a/docs/tr/docs/advanced/generate-clients.md b/docs/tr/docs/advanced/generate-clients.md index af278f2fe..f3d6038a8 100644 --- a/docs/tr/docs/advanced/generate-clients.md +++ b/docs/tr/docs/advanced/generate-clients.md @@ -40,7 +40,7 @@ Bu çözümlerin bazıları açık kaynak olabilir veya ücretsiz katman sunabil Basit bir FastAPI uygulamasıyla başlayalım: -{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} *Path operation*'ların, request payload ve response payload için kullandıkları modelleri `Item` ve `ResponseMessage` modelleriyle tanımladıklarına dikkat edin. @@ -98,7 +98,7 @@ Birçok durumda FastAPI uygulamanız daha büyük olacaktır ve farklı *path op Örneğin **items** için bir bölüm, **users** için başka bir bölüm olabilir ve bunları tag'lerle ayırabilirsiniz: -{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} ### Tag'lerle TypeScript Client Üretme { #generate-a-typescript-client-with-tags } @@ -145,7 +145,7 @@ Bu fonksiyonu özelleştirebilirsiniz. Bir `APIRoute` alır ve string döndürü Sonrasında bu özel fonksiyonu `generate_unique_id_function` parametresiyle **FastAPI**'ye geçebilirsiniz: -{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} ### Özel Operation ID'lerle TypeScript Client Üretme { #generate-a-typescript-client-with-custom-operation-ids } @@ -167,7 +167,7 @@ Ancak üretilen client için, client'ları üretmeden hemen önce OpenAPI operat OpenAPI JSON'u `openapi.json` diye bir dosyaya indirip, şu tarz bir script ile **öndeki tag'i kaldırabiliriz**: -{* ../../docs_src/generate_clients/tutorial004_py39.py *} +{* ../../docs_src/generate_clients/tutorial004_py310.py *} //// tab | Node.js diff --git a/docs/tr/docs/advanced/index.md b/docs/tr/docs/advanced/index.md index 3995109e2..ec43da6b3 100644 --- a/docs/tr/docs/advanced/index.md +++ b/docs/tr/docs/advanced/index.md @@ -2,7 +2,7 @@ ## Ek Özellikler { #additional-features } -Ana [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank} sayfası, **FastAPI**'ın tüm temel özelliklerini tanımanız için yeterli olmalıdır. +Ana [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank} sayfası, **FastAPI**'nin tüm temel özelliklerini tanımanız için yeterli olmalıdır. Sonraki bölümlerde diğer seçenekleri, konfigürasyonları ve ek özellikleri göreceksiniz. @@ -16,6 +16,6 @@ Ve kullanım amacınıza bağlı olarak, çözüm bunlardan birinde olabilir. ## Önce Tutorial'ı Okuyun { #read-the-tutorial-first } -Ana [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank} sayfasındaki bilgilerle **FastAPI**'nın çoğu özelliğini yine de kullanabilirsiniz. +Ana [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank} sayfasındaki bilgilerle **FastAPI**'nin çoğu özelliğini yine de kullanabilirsiniz. Ve sonraki bölümler, onu zaten okuduğunuzu ve bu temel fikirleri bildiğinizi varsayar. diff --git a/docs/tr/docs/advanced/middleware.md b/docs/tr/docs/advanced/middleware.md index a22644a09..7c1fb54f6 100644 --- a/docs/tr/docs/advanced/middleware.md +++ b/docs/tr/docs/advanced/middleware.md @@ -57,13 +57,13 @@ Gelen tüm request'lerin `https` veya `wss` olmasını zorunlu kılar. `http` veya `ws` olarak gelen herhangi bir request, bunun yerine güvenli şemaya redirect edilir. -{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} ## `TrustedHostMiddleware` { #trustedhostmiddleware } HTTP Host Header saldırılarına karşı korunmak için, gelen tüm request'lerde `Host` header'ının doğru ayarlanmış olmasını zorunlu kılar. -{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} Aşağıdaki argümanlar desteklenir: @@ -78,7 +78,7 @@ Gelen bir request doğru şekilde doğrulanmazsa `400` response gönderilir. Middleware hem standart hem de streaming response'ları ele alır. -{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} Aşağıdaki argümanlar desteklenir: diff --git a/docs/tr/docs/advanced/openapi-webhooks.md b/docs/tr/docs/advanced/openapi-webhooks.md index dd9e9bbe7..68b7e6f8d 100644 --- a/docs/tr/docs/advanced/openapi-webhooks.md +++ b/docs/tr/docs/advanced/openapi-webhooks.md @@ -32,7 +32,7 @@ Webhook'lar OpenAPI 3.1.0 ve üzeri sürümlerde mevcuttur; FastAPI `0.99.0` ve Bir **FastAPI** uygulaması oluşturduğunuzda, *webhook*'ları tanımlamak için kullanabileceğiniz bir `webhooks` attribute'u vardır; *path operation* tanımlar gibi, örneğin `@app.webhooks.post()` ile. -{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} Tanımladığınız webhook'lar **OpenAPI** şemasında ve otomatik **docs UI**'da yer alır. diff --git a/docs/tr/docs/advanced/path-operation-advanced-configuration.md b/docs/tr/docs/advanced/path-operation-advanced-configuration.md index e326842d6..ad2c46095 100644 --- a/docs/tr/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/tr/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ OpenAPI konusunda "uzman" değilseniz, muhtemelen buna ihtiyacınız yok. Bunun her operation için benzersiz olduğundan emin olmanız gerekir. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} ### operationId olarak *path operation function* adını kullanma { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ API’lerinizin function adlarını `operationId` olarak kullanmak istiyorsanız Bunu, tüm *path operation*’ları ekledikten sonra yapmalısınız. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} /// tip | İpucu @@ -40,7 +40,7 @@ Farklı modüllerde (Python dosyalarında) olsalar bile. Bir *path operation*’ı üretilen OpenAPI şemasından (dolayısıyla otomatik dokümantasyon sistemlerinden) hariç tutmak için `include_in_schema` parametresini kullanın ve `False` yapın: -{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} ## Docstring’den İleri Düzey Açıklama { #advanced-description-from-docstring } @@ -68,7 +68,7 @@ Uygulamanızda bir *path operation* tanımladığınızda, **FastAPI** OpenAPI /// note | Teknik Detaylar -OpenAPI spesifikasyonunda buna Operation Object denir. +OpenAPI spesifikasyonunda buna Operation Nesnesi denir. /// @@ -90,9 +90,9 @@ Bir *path operation* için OpenAPI şemasını `openapi_extra` parametresiyle ge ### OpenAPI Extensions { #openapi-extensions } -Örneğin bu `openapi_extra`, [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) tanımlamak için faydalı olabilir: +Örneğin bu `openapi_extra`, [OpenAPI Uzantıları](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) tanımlamak için faydalı olabilir: -{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *} Otomatik API dokümanlarını açtığınızda, extension’ınız ilgili *path operation*’ın en altında görünür. @@ -139,9 +139,9 @@ Böylece otomatik üretilen şemaya ek veri ekleyebilirsiniz. Bunu `openapi_extra` ile yapabilirsiniz: -{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *} -Bu örnekte herhangi bir Pydantic model tanımlamadık. Hatta request body JSON olarak parsed bile edilmiyor; doğrudan `bytes` olarak okunuyor ve `magic_data_reader()` fonksiyonu bunu bir şekilde parse etmekten sorumlu oluyor. +Bu örnekte herhangi bir Pydantic model tanımlamadık. Hatta request body JSON olarak ayrıştırılmıyor; doğrudan `bytes` olarak okunuyor ve `magic_data_reader()` fonksiyonu bunu bir şekilde parse etmekten sorumlu oluyor. Buna rağmen, request body için beklenen şemayı tanımlayabiliriz. @@ -153,7 +153,7 @@ Ve bunu, request içindeki veri tipi JSON olmasa bile yapabilirsiniz. Örneğin bu uygulamada, FastAPI’nin Pydantic modellerinden JSON Schema çıkarmaya yönelik entegre işlevselliğini ve JSON için otomatik doğrulamayı kullanmıyoruz. Hatta request content type’ını JSON değil, YAML olarak tanımlıyoruz: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *} Buna rağmen, varsayılan entegre işlevselliği kullanmasak da, YAML olarak almak istediğimiz veri için JSON Schema’yı manuel üretmek üzere bir Pydantic model kullanmaya devam ediyoruz. @@ -161,7 +161,7 @@ Ardından request’i doğrudan kullanıp body’yi `bytes` olarak çıkarıyoru Sonrasında kodumuzda bu YAML içeriğini doğrudan parse ediyor, ardından YAML içeriğini doğrulamak için yine aynı Pydantic modeli kullanıyoruz: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *} /// tip | İpucu diff --git a/docs/tr/docs/advanced/response-change-status-code.md b/docs/tr/docs/advanced/response-change-status-code.md index 239c0dddd..cc86cc0e3 100644 --- a/docs/tr/docs/advanced/response-change-status-code.md +++ b/docs/tr/docs/advanced/response-change-status-code.md @@ -20,7 +20,7 @@ Bu tür durumlarda bir `Response` parametresi kullanabilirsiniz. Ardından bu *geçici (temporal)* `Response` nesnesi üzerinde `status_code` değerini ayarlayabilirsiniz. -{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *} Sonrasında, normalde yaptığınız gibi ihtiyacınız olan herhangi bir nesneyi döndürebilirsiniz (`dict`, bir veritabanı modeli, vb.). diff --git a/docs/tr/docs/advanced/response-cookies.md b/docs/tr/docs/advanced/response-cookies.md index d00bfc4cd..526d6d3c6 100644 --- a/docs/tr/docs/advanced/response-cookies.md +++ b/docs/tr/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ Ardından bu *geçici* response nesnesi üzerinde cookie'leri set edebilirsiniz. -{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *} Sonrasında normalde yaptığınız gibi ihtiyaç duyduğunuz herhangi bir nesneyi döndürebilirsiniz (bir `dict`, bir veritabanı modeli vb.). @@ -24,9 +24,9 @@ Bunu yapmak için, [Doğrudan Response Döndürme](response-directly.md){.intern Sonra bunun içinde Cookie'leri set edin ve response'u döndürün: -{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *} -/// tip | İpucu +/// tip `Response` parametresini kullanmak yerine doğrudan bir response döndürürseniz, FastAPI onu olduğu gibi (doğrudan) döndürür. diff --git a/docs/tr/docs/advanced/response-directly.md b/docs/tr/docs/advanced/response-directly.md index 332f1224f..cadefbdde 100644 --- a/docs/tr/docs/advanced/response-directly.md +++ b/docs/tr/docs/advanced/response-directly.md @@ -2,7 +2,7 @@ **FastAPI** ile bir *path operation* oluşturduğunuzda, normalde ondan herhangi bir veri döndürebilirsiniz: bir `dict`, bir `list`, bir Pydantic model, bir veritabanı modeli vb. -Varsayılan olarak **FastAPI**, döndürdüğünüz bu değeri [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} bölümünde anlatılan `jsonable_encoder` ile otomatik olarak JSON'a çevirir. +Varsayılan olarak **FastAPI**, döndürdüğünüz bu değeri [JSON Uyumlu Encoder](../tutorial/encoder.md){.internal-link target=_blank} bölümünde anlatılan `jsonable_encoder` ile otomatik olarak JSON'a çevirir. Ardından perde arkasında, JSON-uyumlu bu veriyi (ör. bir `dict`) client'a response göndermek için kullanılacak bir `JSONResponse` içine yerleştirir. @@ -54,12 +54,12 @@ Diyelim ki Marshmallow { #marshmallow } -API sistemlerinin ihtiyaç duyduğu temel özelliklerden biri, koddan (Python) veriyi alıp ağ üzerinden gönderilebilecek bir şeye dönüştürmek, yani veri “dönüşüm”üdür. Örneğin, bir veritabanından gelen verileri içeren bir objeyi JSON objesine dönüştürmek, `datetime` objelerini string’e çevirmek vb. +API sistemlerinin ihtiyaç duyduğu temel özelliklerden biri, koddan (Python) veriyi alıp ağ üzerinden gönderilebilecek bir şeye dönüştürmek, yani veri “dönüşüm”üdür. Örneğin, bir veritabanından gelen verileri içeren bir objeyi JSON objesine dönüştürmek, `datetime` objelerini string’e çevirmek vb. API’ların ihtiyaç duyduğu bir diğer önemli özellik, veri doğrulamadır; belirli parametreler göz önüne alındığında verinin geçerli olduğundan emin olmak. Örneğin, bir alanın `int` olması ve rastgele bir metin olmaması. Bu özellikle dışarıdan gelen veriler için kullanışlıdır. @@ -145,7 +145,7 @@ Bir veri doğrulama sistemi olmadan, tüm bu kontrolleri kod içinde el ile yapm Marshmallow, bu özellikleri sağlamak için inşa edildi. Harika bir kütüphanedir ve geçmişte çok kullandım. -Ancak Python tip belirteçlerinden önce yazılmıştır. Dolayısıyla her şemayı tanımlamak için Marshmallow’un sağladığı belirli yardımcılar ve sınıflar kullanılır. +Ancak Python tip belirteçlerinden önce yazılmıştır. Dolayısıyla her şemayı tanımlamak için Marshmallow’un sağladığı belirli yardımcılar ve sınıflar kullanılır. /// check | **FastAPI**'a ilham olan @@ -155,7 +155,7 @@ Kodla, veri tiplerini ve doğrulamayı otomatik sağlayan “şemalar” tanıml ### Webargs { #webargs } -API’ların ihtiyaç duyduğu bir diğer büyük özellik, gelen isteklerden veriyi ayrıştırmadır. +API’ların ihtiyaç duyduğu bir diğer büyük özellik, gelen isteklerden veriyi ayrıştırmadır. Webargs, Flask dahil birkaç framework’ün üzerinde bunu sağlamak için geliştirilmiş bir araçtır. @@ -417,7 +417,7 @@ Tüm veri doğrulama, veri dönüşümü ve JSON Schema tabanlı otomatik model ### Starlette { #starlette } -Starlette, yüksek performanslı asyncio servisleri oluşturmak için ideal, hafif bir ASGI framework’ü/araç takımıdır. +Starlette, yüksek performanslı asyncio servisleri oluşturmak için ideal, hafif bir ASGI framework’ü/araç takımıdır. Çok basit ve sezgiseldir. Kolayca genişletilebilir ve modüler bileşenlere sahip olacak şekilde tasarlanmıştır. diff --git a/docs/tr/docs/async.md b/docs/tr/docs/async.md index 88fd763fc..c788d60a8 100644 --- a/docs/tr/docs/async.md +++ b/docs/tr/docs/async.md @@ -1,18 +1,18 @@ -# Concurrency ve async / await +# Eşzamanlılık ve async / await { #concurrency-and-async-await } -*path operasyon fonksiyonu* için `async def `sözdizimi, asenkron kod, eşzamanlılık ve paralellik hakkında bazı ayrıntılar. +*path operasyon fonksiyonları* için `async def` sözdizimi hakkında detaylar ve asenkron kod, eşzamanlılık (concurrency) ve paralellik üzerine arka plan bilgisi. -## Aceleniz mi var? +## Aceleniz mi var? { #in-a-hurry } -TL;DR: +TL;DR: -Eğer `await` ile çağrılması gerektiğini belirten üçüncü taraf kütüphaneleri kullanıyorsanız, örneğin: +Eğer `await` ile çağırmanız gerektiğini söyleyen üçüncü taraf kütüphaneler kullanıyorsanız, örneğin: ```Python results = await some_library() ``` -O zaman *path operasyon fonksiyonunu* `async def` ile tanımlayın örneğin: +O zaman *path operasyon fonksiyonlarınızı* aşağıdaki gibi `async def` ile tanımlayın: ```Python hl_lines="2" @app.get('/') @@ -23,13 +23,13 @@ async def read_results(): /// note | Not -Sadece `async def` ile tanımlanan fonksiyonlar içinde `await` kullanabilirsiniz. +`await` yalnızca `async def` ile oluşturulan fonksiyonların içinde kullanılabilir. /// --- -Eğer bir veritabanı, bir API, dosya sistemi vb. ile iletişim kuran bir üçüncü taraf bir kütüphane kullanıyorsanız ve `await` kullanımını desteklemiyorsa, (bu şu anda çoğu veritabanı kütüphanesi için geçerli bir durumdur), o zaman *path operasyon fonksiyonunuzu* `def` kullanarak normal bir şekilde tanımlayın, örneğin: +Eğer bir veritabanı, bir API, dosya sistemi vb. ile iletişim kuran ve `await` desteği olmayan bir üçüncü taraf kütüphane kullanıyorsanız (bu şu anda çoğu veritabanı kütüphanesi için geçerlidir), o zaman *path operasyon fonksiyonlarınızı* normal olarak `def` ile tanımlayın: ```Python hl_lines="2" @app.get('/') @@ -40,279 +40,307 @@ def results(): --- -Eğer uygulamanız (bir şekilde) başka bir şeyle iletişim kurmak ve onun cevap vermesini beklemek zorunda değilse, `async def` kullanın. +Uygulamanız (bir şekilde) başka bir şeyle iletişim kurmak ve onun yanıtını beklemek zorunda değilse, içinde `await` kullanmanız gerekmese bile `async def` kullanın. --- -Sadece bilmiyorsanız, normal `def` kullanın. +Emin değilseniz, normal `def` kullanın. --- -**Not**: *path operasyon fonksiyonlarınızda* `def` ve `async def`'i ihtiyaç duyduğunuz gibi karıştırabilir ve her birini sizin için en iyi seçeneği kullanarak tanımlayabilirsiniz. FastAPI onlarla doğru olanı yapacaktır. +Not: *path operasyon fonksiyonlarınızda* `def` ve `async def`'i ihtiyacınız kadar karıştırabilirsiniz, her birini sizin için en iyi seçenekle tanımlayın. FastAPI onlar için doğru olanı yapacaktır. -Her neyse, yukarıdaki durumlardan herhangi birinde, FastAPI yine de asenkron olarak çalışacak ve son derece hızlı olacaktır. +Yukarıdaki durumların herhangi birinde FastAPI yine de asenkron olarak çalışır ve son derece hızlıdır. -Ancak yukarıdaki adımları takip ederek, bazı performans optimizasyonları yapılabilecektir. +Ancak yukarıdaki adımları izleyerek bazı performans optimizasyonları mümkün olur. -## Teknik Detaylar +## Teknik Detaylar { #technical-details } -Python'un modern versiyonlarında **`async` ve `await`** sözdizimi ile **"coroutines"** kullanan **"asenkron kod"** desteğine sahiptir. +Python’un modern sürümleri, **`async` ve `await`** sözdizimiyle, **"coroutines"** denilen bir yapıyı kullanarak **"asenkron kod"** desteğine sahiptir. -Bu ifadeyi aşağıdaki bölümlerde daha da ayrıntılı açıklayalım: +Aşağıdaki bölümlerde bu ifadeyi parça parça ele alalım: -* **Asenkron kod** +* **Asenkron Kod** * **`async` ve `await`** -* **Coroutines** +* **Coroutine'ler** -## Asenkron kod +## Asenkron Kod { #asynchronous-code } -Asenkron kod programlama dilinin 💬 bilgisayara / programa 🤖 kodun bir noktasında, *başka bir kodun* bir yerde bitmesini 🤖 beklemesi gerektiğini söylemenin bir yoludur. Bu *başka koda* "slow-file" denir 📝. +Asenkron kod, dilin 💬 bilgisayara / programa 🤖 kodun bir noktasında, bir yerde *başka bir şeyin* bitmesini beklemesi gerektiğini söylemesinin bir yoludur. Diyelim ki bu *başka şeye* "slow-file" 📝 diyoruz. -Böylece, bu süreçte bilgisayar "slow-file" 📝 tamamlanırken gidip başka işler yapabilir. +Bu sırada bilgisayar, "slow-file" 📝 biterken gidip başka işler yapabilir. -Sonra bilgisayar / program 🤖 her fırsatı olduğunda o noktada yaptığı tüm işleri 🤖 bitirene kadar geri dönücek. Ve 🤖 yapması gerekeni yaparak, beklediği görevlerden herhangi birinin bitip bitmediğini görecek. +Sonra bilgisayar / program 🤖, ya tekrar beklediği için ya da o anda elindeki tüm işleri bitirdiğinde fırsat buldukça geri gelir. Ve beklediği görevlerden herhangi biri bittiyse, yapılması gerekenleri yapar. -Ardından, 🤖 bitirmek için ilk görevi alır ("slow-file" 📝) ve onunla ne yapması gerekiyorsa onu devam ettirir. +Ardından, 🤖 ilk biten görevi alır (örneğin bizim "slow-file" 📝) ve onunla yapması gerekenlere devam eder. -Bu "başka bir şey için bekle" normalde, aşağıdakileri beklemek gibi (işlemcinin ve RAM belleğinin hızına kıyasla) nispeten "yavaş" olan I/O işlemlerine atıfta bulunur: +Bu "başka bir şeyi beklemek" genelde işlemci ve RAM hızına kıyasla nispeten "yavaş" olan I/O işlemlerine atıfta bulunur, örneğin şunları beklemek gibi: -* istemci tarafından ağ üzerinden veri göndermek -* ağ üzerinden istemciye gönderilen veriler -* sistem tarafından okunacak ve programınıza verilecek bir dosya içeriği -* programınızın diske yazılmak üzere sisteme verdiği dosya içerikleri +* istemciden verinin ağ üzerinden gelmesi +* programınızın gönderdiği verinin ağ üzerinden istemciye ulaşması +* diskteki bir dosyanın içeriğinin sistem tarafından okunup programınıza verilmesi +* programınızın sisteme verdiği içeriğin diske yazılması * uzak bir API işlemi -* bir veritabanı bitirme işlemi -* sonuçları döndürmek için bir veritabanı sorgusu +* bir veritabanı işleminin bitmesi +* bir veritabanı sorgusunun sonuç döndürmesi * vb. -Yürütme süresi çoğunlukla I/O işlemleri beklenerek tüketildiğinden bunlara "I/O bağlantılı" işlemler denir. +Çalışma süresi çoğunlukla I/O işlemlerini beklemekle geçtiğinden, bunlara "I/O bound" işlemler denir. -Buna "asenkron" denir, çünkü bilgisayar/program yavaş görevle "senkronize" olmak zorunda değildir, görevin tam olarak biteceği anı bekler, hiçbir şey yapmadan, görev sonucunu alabilmek ve çalışmaya devam edebilmek için . +"Bunun" asenkron" denmesinin sebebi, bilgisayarın / programın yavaş görevle "senkronize" olmak, görev tam bittiği anda orada olup görev sonucunu almak ve işe devam etmek için hiçbir şey yapmadan beklemek zorunda olmamasıdır. -Bunun yerine, "asenkron" bir sistem olarak, bir kez bittiğinde, bilgisayarın / programın yapması gerekeni bitirmesi için biraz (birkaç mikrosaniye) sırada bekleyebilir ve ardından sonuçları almak için geri gelebilir ve onlarla çalışmaya devam edebilir. +Bunun yerine "asenkron" bir sistem olarak, görev bittiğinde, bilgisayarın / programın o sırada yaptığı işi bitirmesi için biraz (birkaç mikrosaniye) sırada bekleyebilir ve sonra sonuçları almak üzere geri dönüp onlarla çalışmaya devam edebilir. -"Senkron" ("asenkron"un aksine) için genellikle "sıralı" terimini de kullanırlar, çünkü bilgisayar/program, bu adımlar beklemeyi içerse bile, farklı bir göreve geçmeden önce tüm adımları sırayla izler. +"Senkron" (asenkronun tersi) için genelde "sıralı" terimi de kullanılır; çünkü bilgisayar / program, farklı bir göreve geçmeden önce tüm adımları sırayla izler, bu adımlar beklemeyi içerse bile. +### Eşzamanlılık ve Burgerler { #concurrency-and-burgers } -### Eşzamanlılık (Concurrency) ve Burgerler +Yukarıda anlatılan **asenkron** kod fikrine bazen **"eşzamanlılık"** (concurrency) da denir. **"Paralellik"**ten (parallelism) farklıdır. +**Eşzamanlılık** ve **paralellik**, "aynı anda az çok birden fazla şeyin olması" ile ilgilidir. -Yukarıda açıklanan bu **asenkron** kod fikrine bazen **"eşzamanlılık"** da denir. **"Paralellikten"** farklıdır. +Ama *eşzamanlılık* ve *paralellik* arasındaki ayrıntılar oldukça farklıdır. -**Eşzamanlılık** ve **paralellik**, "aynı anda az ya da çok olan farklı işler" ile ilgilidir. +Farkı görmek için burgerlerle ilgili şu hikayeyi hayal edin: -Ancak *eşzamanlılık* ve *paralellik* arasındaki ayrıntılar oldukça farklıdır. +### Eşzamanlı Burgerler { #concurrent-burgers } +Aşkınla fast food almaya gidiyorsun, kasiyer senden önceki insanların siparişlerini alırken sıraya giriyorsun. 😍 -Farkı görmek için burgerlerle ilgili aşağıdaki hikayeyi hayal edin: + -### Eşzamanlı Burgerler +Sonra sıra size geliyor, sen ve aşkın için 2 çok havalı burger sipariş ediyorsun. 🍔🍔 - + -Aşkınla beraber 😍 dışarı hamburger yemeye çıktınız 🍔, kasiyer 💁 öndeki insanlardan sipariş alırken siz sıraya girdiniz. +Kasiyer, mutfaktaki aşçıya burgerlerini hazırlamaları gerektiğini söylüyor (o an önceki müşterilerin burgerlerini hazırlıyor olsalar bile). -Sıra sizde ve sen aşkın 😍 ve kendin için 2 çılgın hamburger 🍔 söylüyorsun. + -Ödemeyi yaptın 💸. +Ödeme yapıyorsun. 💸 -Kasiyer 💁 mutfakdaki aşçıya 👨‍🍳 hamburgerleri 🍔 hazırlaması gerektiğini söyler ve aşçı bunu bilir (o an önceki müşterilerin siparişlerini hazırlıyor olsa bile). +Kasiyer sana sıra numaranı veriyor. -Kasiyer 💁 size bir sıra numarası verir. + -Beklerken askınla 😍 bir masaya oturur ve uzun bir süre konuşursunuz(Burgerleriniz çok çılgın olduğundan ve hazırlanması biraz zaman alıyor ✨🍔✨). +Beklerken aşkınla bir masa seçip oturuyorsunuz, uzun uzun sohbet ediyorsunuz (burgerler baya havalı ve hazırlanması biraz zaman alıyor). -Hamburgeri beklerkenki zamanı 🍔, aşkının ne kadar zeki ve tatlı olduğuna hayran kalarak harcayabilirsin ✨😍✨. +Masada aşkınla otururken, burgerleri beklerken, o zamanı aşkının ne kadar harika, tatlı ve zeki olduğuna hayran kalarak geçirebilirsin ✨😍✨. -Aşkınla 😍 konuşurken arada sıranın size gelip gelmediğini kontrol ediyorsun. + -Nihayet sıra size geldi. Tezgaha gidip hamburgerleri 🍔kapıp masaya geri dönüyorsun. +Bekler ve sohbet ederken, ara ara tezgâhtaki numaraya bakıp sıranın size gelip gelmediğini kontrol ediyorsun. -Aşkınla hamburgerlerinizi yiyor 🍔 ve iyi vakit geçiriyorsunuz ✨. +Bir noktada, nihayet sıra size geliyor. Tezgâha gidiyor, burgerleri alıp masaya dönüyorsun. + + + +Aşkınla burgerleri yiyip güzel vakit geçiriyorsunuz. ✨ + + + +/// info | Bilgi + +Harika çizimler: Ketrina Thompson. 🎨 + +/// --- -Bu hikayedeki bilgisayar / program 🤖 olduğunuzu hayal edin. +Bu hikâyede bilgisayar / program 🤖 olduğunu hayal et. -Sırada beklerken boştasın 😴, sıranı beklerken herhangi bir "üretim" yapmıyorsun. Ama bu sıra hızlı çünkü kasiyer sadece siparişleri alıyor (onları hazırlamıyor), burada bir sıknıtı yok. +Sıradayken sadece boştasın 😴, sıranı bekliyorsun, çok "üretken" bir şey yapmıyorsun. Ama sorun yok, çünkü kasiyer sadece sipariş alıyor (hazırlamıyor), bu yüzden sıra hızlı ilerliyor. -Sonra sıra size geldiğinde gerçekten "üretken" işler yapabilirsiniz 🤓, menüyü oku, ne istediğine larar ver, aşkının seçimini al 😍, öde 💸, doğru kartı çıkart, ödemeyi kontrol et, faturayı kontrol et, siparişin doğru olup olmadığını kontrol et, vb. +Sıra sana geldiğinde gerçekten "üretken" işler yapıyorsun: menüyü işliyorsun, ne istediğine karar veriyorsun, aşkının seçimini alıyorsun, ödüyorsun, doğru para ya da kartı verdiğini kontrol ediyorsun, doğru ücretlendirildiğini kontrol ediyorsun, sipariş kalemlerinin doğru olduğunu kontrol ediyorsun, vb. -Ama hamburgerler 🍔 hazır olmamasına rağmen Kasiyer 💁 ile işiniz "duraklıyor" ⏸, çünkü hamburgerlerin hazır olmasını bekliyoruz 🕙. +Ama sonra, burgerlerin hâlâ gelmemiş olsa da, kasiyerle olan işin "duraklatılıyor" ⏸, çünkü burgerlerin hazır olmasını 🕙 beklemen gerekiyor. -Ama tezgahtan uzaklaşıp sıranız gelene kadarmasanıza dönebilir 🔀 ve dikkatinizi aşkınıza 😍 verebilirsiniz vr bunun üzerine "çalışabilirsiniz" ⏯ 🤓. Artık "üretken" birşey yapıyorsunuz 🤓, sevgilinle 😍 flört eder gibi. +Fakat tezgâhtan uzaklaşıp masada sıra numaranla oturduğun için, dikkatinizi 🔀 aşkına çevirebilir, onunla "çalışmaya" ⏯ 🤓 odaklanabilirsin. Yani yine çok "üretken" bir şey yapıyorsun, aşkınla flört etmek gibi 😍. -Kasiyer 💁 "Hamburgerler hazır !" 🍔 dediğinde ve görüntülenen numara sizin numaranız olduğunda hemen koşup hamburgerlerinizi almaya çalışmıyorsunuz. Biliyorsunuzki kimse sizin hamburgerlerinizi 🍔 çalmayacak çünkü sıra sizin. +Ardından kasiyer 💁, tezgâh ekranına numaranı koyarak "burgerleri bitirdim" diyor; ama numara seninki olduğunda çılgınca sıçramıyorsun. Sıra numaran sende, herkesin kendi numarası var; kimse burgerlerini çalamaz. -Yani Aşkınızın😍 hikayeyi bitirmesini bekliyorsunuz (çalışmayı bitir ⏯ / görev işleniyor.. 🤓), nazikçe gülümseyin ve hamburger yemeye gittiğinizi söyleyin ⏸. +Bu yüzden aşkının hikâyeyi bitirmesini (mevcut işi ⏯ / işlenen görevi 🤓 bitirmesini) bekliyor, nazikçe gülümsüyor ve burgerleri almaya gittiğini söylüyorsun ⏸. -Ardından tezgaha 🔀, şimdi biten ilk göreve ⏯ gidin, Hamburgerleri 🍔 alın, teşekkür edin ve masaya götürün. sayacın bu adımı tamamlanır ⏹. Bu da yeni bir görev olan "hamburgerleri ye" 🔀 ⏯ görevini başlatırken "hamburgerleri al" ⏹ görevini bitirir. +Sonra tezgâha 🔀 gidip artık bitmiş olan ilk göreve ⏯ dönüyor, burgerleri alıyor, teşekkür ediyor ve masaya getiriyorsun. Tezgâhla etkileşimin bu adımı / görevi böylece bitiyor ⏹. Bu da yeni bir görev olan "burgerleri yemek" 🔀 ⏯ görevini oluşturuyor, ama "burgerleri almak" görevi tamamlandı ⏹. -### Parallel Hamburgerler +### Paralel Burgerler { #parallel-burgers } -Şimdi bunların "Eşzamanlı Hamburger" değil, "Paralel Hamburger" olduğunu düşünelim. +Şimdi bunların "Eşzamanlı Burgerler" değil, "Paralel Burgerler" olduğunu hayal edelim. -Hamburger 🍔 almak için 😍 aşkınla Paralel fast food'a gidiyorsun. +Aşkınla paralel fast food almaya gidiyorsun. -Birden fazla kasiyer varken (varsayalım 8) sıraya girdiniz👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳 ve sıranız gelene kadar bekliyorsunuz. +Aynı anda aşçı da olan birden fazla (8 diyelim) kasiyerin, senden önceki insanların siparişlerini aldığı bir sırada bekliyorsun. -Sizden önceki herkez ayrılmadan önce hamburgerlerinin 🍔 hazır olmasını bekliyor 🕙. Çünkü kasiyerlerin her biri bir hamburger hazırlanmadan önce bir sonraki siparişe geçmiiyor. +Senden önceki herkes, tezgâhtan ayrılmadan önce burgerlerinin hazırlanmasını bekliyor; çünkü 8 kasiyerin her biri bir sonraki siparişe geçmeden önce burgeri hemen gidip hazırlıyor. -Sonunda senin sıran, aşkın 😍 ve kendin için 2 hamburger 🍔 siparişi verdiniz. + -Ödemeyi yaptınız 💸. +Sonunda sıra size geliyor, sen ve aşkın için 2 çok havalı burger siparişi veriyorsun. -Kasiyer mutfağa gider 👨‍🍳. +Ödüyorsun 💸. -Sırada bekliyorsunuz 🕙, kimse sizin burgerinizi 🍔 almaya çalışmıyor çünkü sıra sizin. + -Sen ve aşkın 😍 sıranızı korumak ve hamburgerleri almakla o kadar meşgulsünüz ki birbirinize vakit 🕙 ayıramıyorsunuz 😞. +Kasiyer mutfağa gidiyor. -İşte bu "senkron" çalışmadır. Kasiyer/aşçı 👨‍🍳ile senkron hareket ediyorsunuz. Bu yüzden beklemek 🕙 ve kasiyer/aşçı burgeri 🍔bitirip size getirdiğinde orda olmak zorundasınız yoksa başka biri alabilir. +Tezgâhın önünde ayakta 🕙 bekliyorsun; sıra numarası olmadığından, burgerlerini senden önce kimsenin almaması için orada durman gerekiyor. -Sonra kasiyeri/aşçı 👨‍🍳 nihayet hamburgerlerinizle 🍔, uzun bir süre sonra 🕙 tezgaha geri geliyor. + -Burgerlerinizi 🍔 al ve aşkınla masanıza doğru ilerle 😍. +Sen ve aşkın, kimsenin önünüze geçip burgerler gelince almaması için meşgul olduğunuzdan, aşkına dikkatini veremiyorsun. 😞 -Sadece burgerini yiyorsun 🍔 ve bitti ⏹. +Bu "senkron" bir iştir; kasiyer/aşçı 👨‍🍳 ile "senkronize"sin. 🕙 Beklemen ve kasiyer/aşçı 👨‍🍳 burgerleri bitirip sana verdiği anda tam orada olman gerekir; yoksa bir başkası alabilir. -Bekleyerek çok fazla zaman geçtiğinden 🕙 konuşmaya çok fazla vakit kalmadı 😞. + + +Sonra kasiyer/aşçı 👨‍🍳, uzun süre tezgâhın önünde 🕙 bekledikten sonra nihayet burgerlerinle geri geliyor. + + + +Burgerleri alıyor ve aşkınla masaya gidiyorsun. + +Sadece yiyorsunuz ve iş bitiyor. ⏹ + + + +Vaktin çoğu tezgâhın önünde 🕙 beklemekle geçtiğinden, pek konuşma ya da flört olmadı. 😞 + +/// info | Bilgi + +Harika çizimler: Ketrina Thompson. 🎨 + +/// --- -Paralel burger senaryosunda ise, siz iki işlemcili birer robotsunuz 🤖 (sen ve sevgilin 😍), Beklıyorsunuz 🕙 hem konuşarak güzel vakit geçirirken ⏯ hem de sıranızı bekliyorsunuz 🕙. +Bu paralel burger senaryosunda, ikiniz (sen ve aşkın) iki işlemcili bir bilgisayar / programsınız 🤖; ikiniz de uzun süre tezgâhta "bekleme" işine 🕙 dikkat ⏯ ayırıyorsunuz. -Mağazada ise 8 işlemci bulunuyor (Kasiyer/aşçı) 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳. Eşzamanlı burgerde yalnızca 2 kişi olabiliyordu (bir kasiyer ve bir aşçı) 💁 👨‍🍳. +Fast food dükkânında 8 işlemci var (kasiyer/aşçılar). Eşzamanlı burger dükkânında yalnızca 2 kişi olabilir (bir kasiyer ve bir aşçı). -Ama yine de bu en iyisi değil 😞. +Ama yine de nihai deneyim pek iyi değil. 😞 --- -Bu hikaye burgerler 🍔 için paralel. +Bu, burgerler için paralel karşılık gelen hikâye olurdu. 🍔 -Bir gerçek hayat örneği verelim. Bir banka hayal edin. +Daha "gerçek hayat" bir örnek için, bir banka hayal edin. -Bankaların çoğunda birkaç kasiyer 👨‍💼👨‍💼👨‍💼👨‍💼 ve uzun bir sıra var 🕙🕙🕙🕙🕙🕙🕙🕙. +Yakın zamana kadar, bankaların çoğunda birden çok gişe memuru 👨‍💼👨‍💼👨‍💼👨‍💼 ve uzun bir sıra 🕙🕙🕙🕙🕙🕙🕙🕙 vardı. -Tüm işi sırayla bir müşteri ile yapan tüm kasiyerler 👨‍💼⏯. +Tüm gişe memurları bir müşteriyle tüm işi yapar, sonra sıradakiyle 👨‍💼⏯. -Ve uzun süre kuyrukta beklemek 🕙 zorundasın yoksa sıranı kaybedersin. +Ve sıranı kaybetmemek için uzun süre 🕙 kuyrukta beklemen gerekir. -Muhtemelen ayak işlerı yaparken sevgilini 😍 bankaya 🏦 getirmezsin. +Muhtemelen, bankada 🏦 işlerini hallederken aşkını 😍 yanında götürmek istemezsin. -### Burger Sonucu +### Burger Sonucu { #burger-conclusion } -Bu "aşkınla fast food burgerleri" senaryosunda, çok fazla bekleme olduğu için 🕙, eşzamanlı bir sisteme sahip olmak çok daha mantıklı ⏸🔀⏯. +"Fast food burgerleri ve aşkın" senaryosunda, çok fazla bekleme 🕙 olduğundan, eşzamanlı bir sistem ⏸🔀⏯ çok daha mantıklıdır. -Web uygulamalarının çoğu için durum böyledir. +Bu, çoğu web uygulaması için de geçerlidir. -Pek çok kullanıcı var, ama sunucunuz pek de iyi olmayan bir bağlantı ile istek atmalarını bekliyor. +Çok fazla kullanıcı vardır; ancak sunucunuz, iyi olmayan bağlantılarından gelen istekleri 🕙 bekler. -Ve sonra yanıtların geri gelmesi için tekrar 🕙 bekliyor +Ve sonra yanıtların geri gelmesini yine 🕙 bekler. -Bu "bekleme" 🕙 mikrosaniye cinsinden ölçülür, yine de, hepsini toplarsak çok fazla bekleme var. +Bu "beklemeler" 🕙 mikrosaniyelerle ölçülür; ama hepsi toplandığında sonuçta oldukça fazla bekleme olur. -Bu nedenle, web API'leri için asenkron ⏸🔀⏯ kod kullanmak çok daha mantıklı. +Bu yüzden web API’leri için asenkron ⏸🔀⏯ kod kullanmak çok mantıklıdır. -Mevcut popüler Python frameworklerinin çoğu (Flask ve Django gibi), Python'daki yeni asenkron özellikler mevcut olmadan önce yazıldı. Bu nedenle, dağıtılma biçimleri paralel yürütmeyi ve yenisi kadar güçlü olmayan eski bir eşzamansız yürütme biçimini destekler. +Bu tür asenkronluk, NodeJS’i popüler yapan şeydir (NodeJS paralel olmasa bile) ve Go dilinin gücüdür. -Asenkron web (ASGI) özelliği, WebSockets için destek eklemek için Django'ya eklenmiş olsa da. +Ve **FastAPI** ile elde ettiğiniz performans seviyesi de budur. -Asenkron çalışabilme NodeJS in popüler olmasının sebebi (paralel olamasa bile) ve Go dilini güçlü yapan özelliktir. +Ayrıca, aynı anda hem paralellik hem de asenkronluk kullanabildiğiniz için, test edilen çoğu NodeJS framework’ünden daha yüksek ve C’ye daha yakın derlenen bir dil olan Go ile başa baş performans elde edersiniz (hepsi Starlette sayesinde). -Ve bu **FastAPI** ile elde ettiğiniz performans düzeyiyle aynıdır. +### Eşzamanlılık paralellikten daha mı iyi? { #is-concurrency-better-than-parallelism } -Aynı anda paralellik ve asenkronluğa sahip olabildiğiniz için, test edilen NodeJS çerçevelerinin çoğundan daha yüksek performans elde edersiniz ve C'ye daha yakın derlenmiş bir dil olan Go ile eşit bir performans elde edersiniz (bütün teşekkürler Starlette'e ). +Hayır! Hikâyenin özü bu değil. -### Eşzamanlılık paralellikten daha mı iyi? +Eşzamanlılık paralellikten farklıdır. Ve çok fazla bekleme içeren **belirli** senaryolarda daha iyidir. Bu nedenle, genellikle web uygulaması geliştirme için paralellikten çok daha iyidir. Ama her şey için değil. -Hayır! Hikayenin ahlakı bu değil. +Bunu dengelemek için, şu kısa hikâyeyi hayal edin: -Eşzamanlılık paralellikten farklıdır. Ve çok fazla bekleme içeren **belirli** senaryolarda daha iyidir. Bu nedenle, genellikle web uygulamaları için paralellikten çok daha iyidir. Ama her şey için değil. +> Büyük, kirli bir evi temizlemen gerekiyor. -Yanı, bunu aklınızda oturtmak için aşağıdaki kısa hikayeyi hayal edin: - -> Büyük, kirli bir evi temizlemelisin. - -*Evet, tüm hikaye bu*. +*Evet, tüm hikâye bu kadar*. --- -Beklemek yok 🕙. Hiçbir yerde. Sadece evin birden fazla yerinde yapılacak fazlasıyla iş var. +Hiçbir yerde 🕙 bekleme yok; sadece evin birden fazla yerinde yapılacak çok iş var. -You could have turns as in the burgers example, first the living room, then the kitchen, but as you are not waiting 🕙 for anything, just cleaning and cleaning, the turns wouldn't affect anything. -Hamburger örneğindeki gibi dönüşleriniz olabilir, önce oturma odası, sonra mutfak, ama hiçbir şey için 🕙 beklemediğinizden, sadece temizlik, temizlik ve temizlik, dönüşler hiçbir şeyi etkilemez. +Hamburger örneğindeki gibi dönüşlerle ilerleyebilirsin, önce salon, sonra mutfak; ama hiçbir şey 🕙 beklemediğin için, sadece temizlik yaptığından, dönüşlerin hiçbir etkisi olmaz. -Sıralı veya sırasız (eşzamanlılık) bitirmek aynı zaman alır ve aynı miktarda işi yaparsınız. +Dönüşlerle ya da dönüşsüz (eşzamanlılık) bitirmek aynı zaman alır ve aynı miktarda iş yapmış olursun. -Ama bu durumda, 8 eski kasiyer/aşçı - yeni temizlikçiyi getirebilseydiniz 👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳👩‍🍳👨‍🍳 ve her birini (artı siz) evin bir bölgesini temizlemek için görevlendirseydiniz, ekstra yardımla tüm işleri **paralel** olarak yapabilir ve çok daha erken bitirebilirdiniz. +Ama bu durumda, 8 eski kasiyer/aşçı—yeni temizlikçiyi getirip her birine (artı sana) evin bir bölümünü versen, fazladan yardımla tüm işleri **paralel** yaparak çok daha çabuk bitirebilirdin. -Bu senaryoda, temizlikçilerin her biri (siz dahil) birer işlemci olacak ve üzerine düşeni yapacaktır. +Bu senaryoda, her bir temizlikçi (sen dâhil) birer işlemci olur ve kendi iş payını yapar. -Yürütme süresinin çoğu (beklemek yerine) iş yapıldığından ve bilgisayardaki iş bir CPU tarafından yapıldığından, bu sorunlara "CPU bound" diyorlar". +Ve yürütme süresinin çoğu gerçek işten (bekleme yerine) oluştuğu ve bilgisayardaki işi bir CPU yaptığı için, bu sorunlara "CPU bound" denir. --- -CPU'ya bağlı işlemlerin yaygın örnekleri, karmaşık matematik işlemleri gerektiren işlerdir. +CPU’ya bağlı işlemlerin yaygın örnekleri, karmaşık matematiksel işlem gerektiren iş yükleridir. Örneğin: * **Ses** veya **görüntü işleme**. -* **Bilgisayar görüsü**: bir görüntü milyonlarca pikselden oluşur, her pikselin 3 değeri / rengi vardır, bu pikseller üzerinde aynı anda bir şeyler hesaplamayı gerektiren işleme. -* **Makine Öğrenimi**: Çok sayıda "matris" ve "vektör" çarpımı gerektirir. Sayıları olan ve hepsini aynı anda çarpan büyük bir elektronik tablo düşünün. -* **Derin Öğrenme**: Bu, Makine Öğreniminin bir alt alanıdır, dolayısıyla aynısı geçerlidir. Sadece çarpılacak tek bir sayı tablosu değil, büyük bir sayı kümesi vardır ve çoğu durumda bu modelleri oluşturmak ve/veya kullanmak için özel işlemciler kullanırsınız. +* **Bilgisayar görüsü**: bir görüntü milyonlarca pikselden oluşur, her pikselin 3 değeri / rengi vardır; işleme genellikle bu pikseller üzerinde aynı anda bir şeyler hesaplamayı gerektirir. +* **Makine Öğrenimi**: genellikle çok sayıda "matris" ve "vektör" çarpımı gerekir. Sayılar içeren devasa bir elektronik tabloyu ve hepsini aynı anda çarpmayı düşünün. +* **Derin Öğrenme**: Makine Öğreniminin bir alt alanıdır, dolayısıyla aynısı geçerlidir. Sadece çarpılacak tek bir sayı tablosu değil, kocaman bir sayı kümesi vardır ve çoğu durumda bu modelleri kurmak ve/veya kullanmak için özel işlemciler kullanırsınız. -### Eşzamanlılık + Paralellik: Web + Makine Öğrenimi +### Eşzamanlılık + Paralellik: Web + Makine Öğrenimi { #concurrency-parallelism-web-machine-learning } -**FastAPI** ile web geliştirme için çok yaygın olan eşzamanlılıktan yararlanabilirsiniz (NodeJS'in aynı çekiciliği). +**FastAPI** ile web geliştirmede çok yaygın olan eşzamanlılıktan (NodeJS’in başlıca cazibesiyle aynı) yararlanabilirsiniz. -Ancak, Makine Öğrenimi sistemlerindekile gibi **CPU'ya bağlı** iş yükleri için paralellik ve çoklu işlemenin (birden çok işlemin paralel olarak çalışması) avantajlarından da yararlanabilirsiniz. +Ama ayrıca **CPU’ya bağlı** iş yükleri (Makine Öğrenimi sistemlerindeki gibi) için paralellik ve çoklu işlemden (paralel çalışan birden çok işlem) de yararlanabilirsiniz. -Buna ek olarak Python'un **Veri Bilimi**, Makine Öğrenimi ve özellikle Derin Öğrenme için ana dil olduğu gerçeği, FastAPI'yi Veri Bilimi / Makine Öğrenimi web API'leri ve uygulamaları için çok iyi bir seçenek haline getirir. +Buna ek olarak Python’un **Veri Bilimi**, Makine Öğrenimi ve özellikle Derin Öğrenme için ana dil olması, FastAPI’yi Veri Bilimi / Makine Öğrenimi web API’leri ve uygulamaları için çok iyi bir seçenek yapar. -Production'da nasıl oldugunu görmek için şu bölüme bakın [Deployment](deployment/index.md){.internal-link target=_blank}. +Production’da bu paralelliği nasıl sağlayacağınızı görmek için [Deployment](deployment/index.md){.internal-link target=_blank} bölümüne bakın. -## `async` ve `await` +## `async` ve `await` { #async-and-await } -Python'un modern sürümleri, asenkron kodu tanımlamanın çok sezgisel bir yoluna sahiptir. Bu, normal "sequentıal" (sıralı) kod gibi görünmesini ve doğru anlarda sizin için "awaıt" ile bekleme yapmasını sağlar. +Python’un modern sürümleri, asenkron kodu tanımlamak için oldukça sezgisel bir yol sunar. Bu sayede kod normal "sıralı" kod gibi görünür ve doğru anlarda sizin yerinize "beklemeyi" yapar. -Sonuçları vermeden önce beklemeyi gerektirecek ve yeni Python özelliklerini destekleyen bir işlem olduğunda aşağıdaki gibi kodlayabilirsiniz: +Sonuçları vermeden önce bekleme gerektiren ve bu yeni Python özelliklerini destekleyen bir işlem olduğunda, şöyle kodlayabilirsiniz: ```Python burgers = await get_burgers(2) ``` -Buradaki `await` anahtari Python'a, sonuçları `burgers` degiskenine atamadan önce `get_burgers(2)` kodunun işini bitirmesini 🕙 beklemesi gerektiğini söyler. Bununla Python, bu ara zamanda başka bir şey 🔀 ⏯ yapabileceğini bilecektir (başka bir istek almak gibi). +Buradaki kilit nokta `await`. Python’a, sonuçları `burgers` değişkenine koymadan önce `get_burgers(2)` çalışmasının bitmesini 🕙 beklemesi ⏸ gerektiğini söyler. Böylece Python, bu arada başka bir şey 🔀 ⏯ yapabileceğini bilir (ör. başka bir request almak gibi). - `await`kodunun çalışması için, eşzamansızlığı destekleyen bir fonksiyonun içinde olması gerekir. Bunu da yapmak için fonksiyonu `async def` ile tanımlamamız yeterlidir: +`await`’in çalışabilmesi için, bu asenkronluğu destekleyen bir fonksiyonun içinde olması gerekir. Bunu yapmak için fonksiyonu `async def` ile tanımlayın: ```Python hl_lines="1" async def get_burgers(number: int): - # burgerleri oluşturmak için asenkron birkaç iş + # Burgerleri yaratmak için bazı asenkron işler yap return burgers ``` ...`def` yerine: ```Python hl_lines="2" -# bu kod asenkron değil +# Bu asenkron değildir def get_sequential_burgers(number: int): - # burgerleri oluşturmak için senkron bırkaç iş + # Burgerleri yaratmak için bazı sıralı işler yap return burgers ``` -`async def` ile Python, bu fonksıyonun içinde, `await` ifadelerinin farkında olması gerektiğini ve çalışma zamanı gelmeden önce bu işlevin yürütülmesini "duraklatabileceğini" ve başka bir şey yapabileceğini 🔀 bilir. +`async def` ile Python, bu fonksiyonun içinde `await` ifadelerinin olabileceğini bilir ve bu fonksiyonun yürütülmesini "duraklatıp" ⏸ başka bir şey yapabileceğini 🔀, sonra geri dönebileceğini anlar. -`async def` fonksiyonunu çağırmak istediğinizde, onu "awaıt" ıle kullanmanız gerekir. Yani, bu işe yaramaz: +`async def` fonksiyonunu çağırmak istediğinizde, onu "await" etmeniz gerekir. Yani şu çalışmaz: ```Python -# Bu işe yaramaz, çünkü get_burgers, şu şekilde tanımlandı: async def +# Bu çalışmaz, çünkü get_burgers şöyle tanımlandı: async def burgers = get_burgers(2) ``` --- -Bu nedenle, size onu `await` ile çağırabileceğinizi söyleyen bir kitaplık kullanıyorsanız, onu `async def` ile tanımlanan *path fonksiyonu* içerisinde kullanmanız gerekir, örneğin: +Dolayısıyla, `await` ile çağrılabileceğini söyleyen bir kütüphane kullanıyorsanız, onu kullanan *path operasyon fonksiyonunu* `async def` ile oluşturmanız gerekir, örneğin: ```Python hl_lines="2-3" @app.get('/burgers') @@ -321,87 +349,96 @@ async def read_burgers(): return burgers ``` -### Daha fazla teknik detay +### Daha teknik detaylar { #more-technical-details } -`await` in yalnızca `async def` ile tanımlanan fonksıyonların içinde kullanılabileceğini fark etmişsinizdir. +`await`’in yalnızca `async def` ile tanımlanan fonksiyonların içinde kullanılabildiğini fark etmiş olabilirsiniz. -Ama aynı zamanda, `async def` ile tanımlanan fonksiyonların "await" ile beklenmesi gerekir. Bu nedenle, "`async def` içeren fonksiyonlar yalnızca "`async def` ile tanımlanan fonksiyonların içinde çağrılabilir. +Aynı zamanda, `async def` ile tanımlanan fonksiyonların da "await" edilmesi gerekir. Yani `async def` ile tanımlanan fonksiyonlar yalnızca `async def` ile tanımlanan fonksiyonların içinde çağrılabilir. +Peki, tavuk-yumurta meselesi: ilk `async` fonksiyon nasıl çağrılır? -Yani yumurta mı tavukdan, tavuk mu yumurtadan gibi ilk `async` fonksiyonu nasıl çağırılır? +**FastAPI** ile çalışıyorsanız bunu dert etmenize gerek yok; çünkü o "ilk" fonksiyon sizin *path operasyon fonksiyonunuz* olacaktır ve FastAPI doğru olanı yapmasını bilir. -**FastAPI** ile çalışıyorsanız bunun için endişelenmenize gerek yok, çünkü bu "ilk" fonksiyon sizin *path fonksiyonunuz* olacak ve FastAPI doğru olanı nasıl yapacağını bilecek. +Ama FastAPI olmadan da `async` / `await` kullanmak isterseniz, bunu da yapabilirsiniz. -Ancak FastAPI olmadan `async` / `await` kullanmak istiyorsanız, resmi Python belgelerini kontrol edin. +### Kendi async kodunuzu yazın { #write-your-own-async-code } -### Asenkron kodun diğer biçimleri +Starlette (ve **FastAPI**) AnyIO üzerine kuruludur; bu sayede Python standart kütüphanesindeki asyncio ve Trio ile uyumludur. -Bu `async` ve `await` kullanimi oldukça yenidir. +Özellikle, kendi kodunuzda daha gelişmiş desenler gerektiren ileri seviye eşzamanlılık kullanım senaryoları için doğrudan AnyIO kullanabilirsiniz. -Ancak asenkron kodla çalışmayı çok daha kolay hale getirir. +Hatta FastAPI kullanmıyor olsanız bile, yüksek uyumluluk ve avantajları (ör. *structured concurrency*) için AnyIO ile kendi async uygulamalarınızı yazabilirsiniz. -Aynı sözdizimi (hemen hemen aynı) son zamanlarda JavaScript'in modern sürümlerine de dahil edildi (Tarayıcı ve NodeJS'de). +AnyIO’nun üzerine, tür açıklamalarını biraz iyileştirmek ve daha iyi **otomatik tamamlama**, **satır içi hatalar** vb. elde etmek için ince bir katman olarak başka bir kütüphane daha oluşturdum. Ayrıca **kendi async kodunuzu** anlamanıza ve yazmanıza yardımcı olacak dostça bir giriş ve eğitim içerir: Asyncer. Özellikle **async kodu normal** (bloklayan/senkron) **kodla birleştirmeniz** gerektiğinde faydalı olacaktır. -Ancak bundan önce, asenkron kodu işlemek oldukça karmaşık ve zordu. +### Asenkron kodun diğer biçimleri { #other-forms-of-asynchronous-code } -Python'un önceki sürümlerinde, threadlerı veya Gevent kullanıyor olabilirdin. Ancak kodu anlamak, hata ayıklamak ve düşünmek çok daha karmaşık olurdu. +`async` ve `await` kullanma tarzı, dilde nispeten yenidir. -NodeJS / Browser JavaScript'in önceki sürümlerinde, "callback" kullanırdınız. Bu da "callbacks cehennemine" yol açar. +Ama asenkron kodla çalışmayı çok daha kolaylaştırır. -## Coroutine'ler +Aynı (ya da neredeyse aynı) sözdizimi yakın zamanda modern JavaScript sürümlerine (Tarayıcı ve NodeJS) de eklendi. -**Coroutine**, bir `async def` fonksiyonu tarafından döndürülen değer için çok süslü bir terimdir. Python bunun bir fonksiyon gibi bir noktada başlayıp biteceğini bilir, ancak içinde bir `await` olduğunda dahili olarak da duraklatılabilir ⏸. +Bundan önce, asenkron kodu ele almak oldukça daha karmaşık ve zordu. -Ancak, `async` ve `await` ile asenkron kod kullanmanın tüm bu işlevselliği, çoğu zaman "Coroutine" kullanmak olarak adlandırılır. Go'nun ana özelliği olan "Goroutines" ile karşılaştırılabilir. +Python’un önceki sürümlerinde thread’ler veya Gevent kullanabilirdiniz. Ama kodu anlamak, hata ayıklamak ve üzerine düşünmek çok daha zordu. -## Sonuç +NodeJS / Tarayıcı JavaScript’in önceki sürümlerinde "callback" kullanırdınız. Bu da "callback cehennemi"ne yol açardı. -Aynı ifadeyi yukarıdan görelim: +## Coroutine'ler { #coroutines } -> Python'ın modern sürümleri, **"async" ve "await"** sözdizimi ile birlikte **"coroutines"** adlı bir özelliği kullanan **"asenkron kod"** desteğine sahiptir. +**Coroutine**, bir `async def` fonksiyonunun döndürdüğü şeye verilen süslü isimdir. Python bunun bir fonksiyona benzer bir şey olduğunu, bir noktada başlayıp biteceğini bilir; ama içinde bir `await` olduğunda dahili olarak duraklatılabileceğini ⏸ de bilir. -Şimdi daha mantıklı gelmeli. ✨ +`async` ve `await` ile asenkron kod kullanmanın bu işlevselliği çoğu zaman "coroutine" kullanmak olarak özetlenir. Go’nun ana kilit özelliği olan "Goroutines" ile karşılaştırılabilir. -FastAPI'ye (Starlette aracılığıyla) güç veren ve bu kadar etkileyici bir performansa sahip olmasını sağlayan şey budur. +## Sonuç { #conclusion } -## Çok Teknik Detaylar +Yukarıdaki cümleyi tekrar görelim: -/// warning +> Python’un modern sürümleri, **`async` ve `await`** sözdizimiyle, **"coroutines"** denilen bir yapıyı kullanarak **"asenkron kod"** desteğine sahiptir. -Muhtemelen burayı atlayabilirsiniz. +Artık daha anlamlı gelmeli. ✨ -Bunlar, **FastAPI**'nin altta nasıl çalıştığına dair çok teknik ayrıntılardır. +Bunların hepsi, FastAPI’ye (Starlette aracılığıyla) güç verir ve böylesine etkileyici bir performansa sahip olmasını sağlar. -Biraz teknik bilginiz varsa (co-routines, threads, blocking, vb)ve FastAPI'nin "async def" ile normal "def" arasındaki farkı nasıl işlediğini merak ediyorsanız, devam edin. +## Çok Teknik Detaylar { #very-technical-details } + +/// warning | Uyarı + +Büyük ihtimalle burayı atlayabilirsiniz. + +Bunlar, **FastAPI**’nin altında nasıl çalıştığına dair oldukça teknik ayrıntılardır. + +Coroutine’ler, thread’ler, blocking vb. hakkında teknik bilginiz varsa ve FastAPI’nin `async def` ile normal `def` arasındaki farkı nasıl ele aldığını merak ediyorsanız, devam edin. /// -### Path fonksiyonu +### Path Operasyon Fonksiyonları { #path-operation-functions } -"async def" yerine normal "def" ile bir *yol işlem işlevi* bildirdiğinizde, doğrudan çağrılmak yerine (sunucuyu bloke edeceğinden) daha sonra beklenen harici bir iş parçacığı havuzunda çalıştırılır. +Bir *path operasyon fonksiyonunu* `async def` yerine normal `def` ile tanımladığınızda, (sunucuyu bloklayacağından) doğrudan çağrılmak yerine, harici bir thread pool’da çalıştırılır ve ardından beklenir. -Yukarıda açıklanan şekilde çalışmayan başka bir asenkron framework'den geliyorsanız ve küçük bir performans kazancı (yaklaşık 100 nanosaniye) için "def" ile *path fonksiyonu* tanımlamaya alışkınsanız, **FastAPI**'de tam tersi olacağını unutmayın. Bu durumlarda, *path fonksiyonu* G/Ç engelleyen durum oluşturmadıkça "async def" kullanmak daha iyidir. +Yukarıda açıklanan şekilde çalışmayan başka bir async framework’ten geliyorsanız ve ufak bir performans kazancı (yaklaşık 100 nanosaniye) için yalnızca hesaplama yapan basit *path operasyon fonksiyonlarını* düz `def` ile tanımlamaya alışkınsanız, **FastAPI**’de etkinin tam tersi olacağını unutmayın. Bu durumlarda, *path operasyon fonksiyonlarınız* bloklayan I/O yapan kod kullanmadıkça `async def` kullanmak daha iyidir. -Yine de, her iki durumda da, **FastAPI**'nin önceki frameworkden [hala daha hızlı](index.md#performans){.internal-link target=_blank} (veya en azından karşılaştırılabilir) olma olasılığı vardır. +Yine de her iki durumda da, **FastAPI**’nin önceki framework’ünüzden [hala daha hızlı](index.md#performance){.internal-link target=_blank} (ya da en azından karşılaştırılabilir) olması muhtemeldir. -### Bagımlılıklar +### Bağımlılıklar { #dependencies } -Aynısı bağımlılıklar için de geçerlidir. Bir bağımlılık, "async def" yerine standart bir "def" işleviyse, harici iş parçacığı havuzunda çalıştırılır. +Aynısı [bağımlılıklar](tutorial/dependencies/index.md){.internal-link target=_blank} için de geçerlidir. Bir bağımlılık `async def` yerine standart bir `def` fonksiyonuysa, harici thread pool’da çalıştırılır. -### Alt-bağımlıklar +### Alt-bağımlılıklar { #sub-dependencies } -Birbirini gerektiren (fonksiyonlarin parametreleri olarak) birden fazla bağımlılık ve alt bağımlılıklarınız olabilir, bazıları 'async def' ve bazıları normal 'def' ile oluşturulabilir. Yine de normal 'def' ile oluşturulanlar, "await" kulanilmadan harici bir iş parçacığında (iş parçacığı havuzundan) çağrılır. +Birbirini gerektiren birden çok bağımlılık ve [alt-bağımlılık](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} olabilir (fonksiyon tanımlarının parametreleri olarak). Bazıları `async def` ile, bazıları normal `def` ile oluşturulmuş olabilir. Yine de çalışır ve normal `def` ile oluşturulanlar "await" edilmek yerine harici bir thread’de (thread pool’dan) çağrılır. -### Diğer yardımcı fonksiyonlar +### Diğer yardımcı fonksiyonlar { #other-utility-functions } -Doğrudan çağırdığınız diğer herhangi bir yardımcı fonksiyonu, normal "def" veya "async def" ile tanimlayabilirsiniz. FastAPI onu çağırma şeklinizi etkilemez. +Doğrudan çağırdığınız diğer yardımcı fonksiyonları normal `def` veya `async def` ile tanımlayabilirsiniz ve FastAPI onları çağırma biçiminizi etkilemez. -Bu, FastAPI'nin sizin için çağırdığı fonksiyonlarin tam tersidir: *path fonksiyonu* ve bağımlılıklar. +Bu, FastAPI’nin sizin için çağırdığı fonksiyonların tersidir: *path operasyon fonksiyonları* ve bağımlılıklar. -Yardımcı program fonksiyonunuz 'def' ile normal bir işlevse, bir iş parçacığı havuzunda değil doğrudan (kodunuzda yazdığınız gibi) çağrılır, işlev 'async def' ile oluşturulmuşsa çağırıldığı yerde 'await' ile beklemelisiniz. +Yardımcı fonksiyonunuz `def` ile tanımlı normal bir fonksiyonsa, bir thread pool’da değil doğrudan (kodunuzda yazdığınız gibi) çağrılır; fonksiyon `async def` ile tanımlıysa kodunuzda çağırırken onu `await` etmelisiniz. --- -Yeniden, bunlar, onları aramaya geldiğinizde muhtemelen işinize yarayacak çok teknik ayrıntılardır. +Yine, bunlar muhtemelen özellikle aradığınızda işinize yarayacak çok teknik ayrıntılardır. -Aksi takdirde, yukarıdaki bölümdeki yönergeleri iyi bilmelisiniz: Aceleniz mi var?. +Aksi hâlde, yukarıdaki bölümdeki yönergeler yeterlidir: Aceleniz mi var?. diff --git a/docs/tr/docs/deployment/concepts.md b/docs/tr/docs/deployment/concepts.md index d0f568146..f5ee988c9 100644 --- a/docs/tr/docs/deployment/concepts.md +++ b/docs/tr/docs/deployment/concepts.md @@ -13,7 +13,7 @@ Bir **FastAPI** uygulamasını (hatta genel olarak herhangi bir web API'yi) depl Bunların **deployment**'ları nasıl etkilediğine bakalım. -Nihai hedef, **API client**'larınıza **güvenli** bir şekilde hizmet verebilmek, **kesintileri** önlemek ve **hesaplama kaynaklarını** (ör. uzak server'lar/sanal makineler) olabildiğince verimli kullanmaktır. +Nihai hedef, **API client**'larınıza **güvenli** bir şekilde hizmet verebilmek, **kesintileri** önlemek ve **hesaplama kaynaklarını** (ör. uzak server'lar/sanal makineler) olabildiğince verimli kullanmaktır. 🚀 Burada bu **kavramlar** hakkında biraz daha bilgi vereceğim. Böylece, çok farklı ortamlarda—hatta bugün var olmayan **gelecekteki** ortamlarda bile—API'nizi nasıl deploy edeceğinize karar verirken ihtiyaç duyacağınız **sezgiyi** kazanmış olursunuz. @@ -21,7 +21,7 @@ Bu kavramları dikkate alarak, **kendi API**'leriniz için en iyi deployment yak Sonraki bölümlerde, FastAPI uygulamalarını deploy etmek için daha **somut tarifler** (recipes) paylaşacağım. -Ama şimdilik, bu önemli **kavramsal fikirleri** inceleyelim. Bu kavramlar diğer tüm web API türleri için de geçerlidir. +Ama şimdilik, bu önemli **kavramsal fikirleri** inceleyelim. Bu kavramlar diğer tüm web API türleri için de geçerlidir. 💡 ## Güvenlik - HTTPS { #security-https } @@ -36,16 +36,16 @@ Ve **HTTPS sertifikalarını yenilemekten** sorumlu bir şey olmalıdır; bu ayn TLS Termination Proxy olarak kullanabileceğiniz bazı araçlar: * Traefik - * Sertifika yenilemelerini otomatik yönetir + * Sertifika yenilemelerini otomatik yönetir ✨ * Caddy - * Sertifika yenilemelerini otomatik yönetir + * Sertifika yenilemelerini otomatik yönetir ✨ * Nginx * Sertifika yenilemeleri için Certbot gibi harici bir bileşenle * HAProxy * Sertifika yenilemeleri için Certbot gibi harici bir bileşenle * Nginx gibi bir Ingress Controller ile Kubernetes * Sertifika yenilemeleri için cert-manager gibi harici bir bileşenle -* Bir cloud provider tarafından servislerinin parçası olarak içeride yönetilmesi (aşağıyı okuyun) +* Bir cloud provider tarafından servislerinin parçası olarak içeride yönetilmesi (aşağıyı okuyun 👇) Bir diğer seçenek de, HTTPS kurulumunu da dahil olmak üzere işin daha büyük kısmını yapan bir **cloud service** kullanmaktır. Bunun bazı kısıtları olabilir veya daha pahalı olabilir vb. Ancak bu durumda TLS Termination Proxy'yi kendiniz kurmak zorunda kalmazsınız. @@ -100,7 +100,7 @@ Bu yöntem çalışır ve **geliştirme sırasında** faydalıdır. Ancak server'a olan bağlantınız koparsa, **çalışan process** muhtemelen ölür. -Ve server yeniden başlatılırsa (örneğin update'lerden sonra ya da cloud provider'ın migration'larından sonra) bunu muhtemelen **fark etmezsiniz**. Dolayısıyla process'i manuel yeniden başlatmanız gerektiğini de bilmezsiniz. Sonuçta API'niz ölü kalır. +Ve server yeniden başlatılırsa (örneğin update'lerden sonra ya da cloud provider'ın migration'larından sonra) bunu muhtemelen **fark etmezsiniz**. Dolayısıyla process'i manuel yeniden başlatmanız gerektiğini de bilmezsiniz. Sonuçta API'niz ölü kalır. 😱 ### Startup'ta Otomatik Çalıştırma { #run-automatically-on-startup } @@ -131,19 +131,19 @@ Uygulamanızın startup'ta çalıştığından emin olmaya benzer şekilde, hata ### Hata Yaparız { #we-make-mistakes } -Biz insanlar sürekli **hata** yaparız. Yazılımın neredeyse *her zaman* farklı yerlerinde gizli **bug**'lar vardır. +Biz insanlar sürekli **hata** yaparız. Yazılımın neredeyse *her zaman* farklı yerlerinde gizli **bug**'lar vardır. 🐛 -Ve biz geliştiriciler bu bug'ları buldukça ve yeni özellikler ekledikçe code'u iyileştiririz (muhtemelen yeni bug'lar da ekleyerek). +Ve biz geliştiriciler bu bug'ları buldukça ve yeni özellikler ekledikçe code'u iyileştiririz (muhtemelen yeni bug'lar da ekleyerek 😅). ### Küçük Hatalar Otomatik Yönetilir { #small-errors-automatically-handled } -FastAPI ile web API geliştirirken, code'umuzda bir hata olursa FastAPI genellikle bunu hatayı tetikleyen tek request ile sınırlar. +FastAPI ile web API geliştirirken, code'umuzda bir hata olursa FastAPI genellikle bunu hatayı tetikleyen tek request ile sınırlar. 🛡 Client o request için **500 Internal Server Error** alır; ancak uygulama tamamen çöküp durmak yerine sonraki request'ler için çalışmaya devam eder. ### Daha Büyük Hatalar - Çökmeler { #bigger-errors-crashes } -Yine de bazı durumlarda, yazdığımız bir code **tüm uygulamayı çökertip** Uvicorn ve Python'ın crash olmasına neden olabilir. +Yine de bazı durumlarda, yazdığımız bir code **tüm uygulamayı çökertip** Uvicorn ve Python'ın crash olmasına neden olabilir. 💥 Böyle bir durumda, tek bir noktadaki hata yüzünden uygulamanın ölü kalmasını istemezsiniz; bozuk olmayan *path operations* en azından çalışmaya devam etsin istersiniz. @@ -206,7 +206,7 @@ Ve birden fazla process normalde **belleği paylaşmaz**. Yani her çalışan pr Örneğin code'unuz **1 GB** boyutunda bir Machine Learning modelini yüklüyorsa, API'niz tek process ile çalışırken en az 1 GB RAM tüketir. **4 process** (4 worker) başlatırsanız her biri 1 GB RAM tüketir. Yani toplamda API'niz **4 GB RAM** tüketir. -Uzak server'ınız veya sanal makineniz yalnızca 3 GB RAM'e sahipse, 4 GB'tan fazla RAM yüklemeye çalışmak sorun çıkarır. +Uzak server'ınız veya sanal makineniz yalnızca 3 GB RAM'e sahipse, 4 GB'tan fazla RAM yüklemeye çalışmak sorun çıkarır. 🚨 ### Birden Fazla Process - Bir Örnek { #multiple-processes-an-example } @@ -265,7 +265,7 @@ Elbette bazı durumlarda ön adımları birden fazla kez çalıştırmak sorun d Ayrıca, kurulumunuza bağlı olarak bazı durumlarda uygulamanızı başlatmadan önce **hiç ön adıma ihtiyaç duymayabilirsiniz**. -Bu durumda bunların hiçbirini düşünmeniz gerekmez. +Bu durumda bunların hiçbirini düşünmeniz gerekmez. 🤷 /// @@ -291,7 +291,7 @@ Server(lar)ınız bir **kaynaktır**. Programlarınızla CPU'lardaki hesaplama z Sistem kaynaklarının ne kadarını tüketmek/kullanmak istersiniz? "Az" demek kolaydır; ancak pratikte hedef genellikle **çökmeden mümkün olduğunca fazla** kullanmaktır. -3 server için para ödüyor ama onların RAM ve CPU'sunun yalnızca küçük bir kısmını kullanıyorsanız, muhtemelen **para israf ediyorsunuz** ve muhtemelen **elektrik tüketimini** de gereksiz yere artırıyorsunuz vb. +3 server için para ödüyor ama onların RAM ve CPU'sunun yalnızca küçük bir kısmını kullanıyorsanız, muhtemelen **para israf ediyorsunuz** 💸 ve muhtemelen **elektrik tüketimini** de gereksiz yere artırıyorsunuz 🌎 vb. Bu durumda 2 server ile devam edip onların kaynaklarını (CPU, bellek, disk, ağ bant genişliği vb.) daha yüksek oranlarda kullanmak daha iyi olabilir. @@ -316,6 +316,6 @@ Uygulamanızı nasıl deploy edeceğinize karar verirken aklınızda tutmanız g * Bellek * Başlatmadan önceki adımlar -Bu fikirleri ve nasıl uygulayacağınızı anlamak, deployment'larınızı yapılandırırken ve ince ayar yaparken ihtiyaç duyacağınız sezgiyi kazanmanızı sağlamalıdır. +Bu fikirleri ve nasıl uygulayacağınızı anlamak, deployment'larınızı yapılandırırken ve ince ayar yaparken ihtiyaç duyacağınız sezgiyi kazanmanızı sağlamalıdır. 🤓 -Sonraki bölümlerde, izleyebileceğiniz stratejilere dair daha somut örnekler paylaşacağım. +Sonraki bölümlerde, izleyebileceğiniz stratejilere dair daha somut örnekler paylaşacağım. 🚀 diff --git a/docs/tr/docs/deployment/docker.md b/docs/tr/docs/deployment/docker.md index 6c8f74c77..6d38a2ce1 100644 --- a/docs/tr/docs/deployment/docker.md +++ b/docs/tr/docs/deployment/docker.md @@ -14,7 +14,7 @@ Aceleniz var ve bunları zaten biliyor musunuz? Aşağıdaki [`Dockerfile`'a atl Dockerfile Önizleme 👀 ```Dockerfile -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -166,7 +166,7 @@ def read_item(item_id: int, q: str | None = None): ```{ .dockerfile .annotate } # (1)! -FROM python:3.9 +FROM python:3.14 # (2)! WORKDIR /code @@ -390,7 +390,7 @@ FastAPI uygulamanız tek bir dosyaysa; örneğin `./app` dizini olmadan sadece ` Bu durumda `Dockerfile` içinde dosyayı kopyaladığınız path'leri buna göre değiştirmeniz yeterlidir: ```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -454,7 +454,7 @@ Container kullanmadan, uygulamaları startup'ta çalıştırmak ve restart mekan ## Replication - Process Sayısı { #replication-number-of-processes } -Kubernetes, Docker Swarm Mode, Nomad veya benzeri, birden fazla makinede dağıtık container'ları yöneten karmaşık bir sistemle kurulmuş bir cluster'ınız varsa, replication'ı her container içinde bir **process manager** (ör. worker'lı Uvicorn) kullanarak yönetmek yerine, muhtemelen **cluster seviyesinde** ele almak istersiniz. +Eğer bir küme (cluster) olarak yapılandırılmış makineler grubunuz varsa ve bunları **Kubernetes**, Docker Swarm Mode, Nomad veya benzeri, birden çok makinede dağıtık container'ları yöneten karmaşık bir sistemle yönetiyorsanız, replication'ı her container içinde bir **process manager** (ör. worker'lı Uvicorn) kullanarak yönetmek yerine, muhtemelen **küme seviyesinde (cluster level)** ele almak istersiniz. Kubernetes gibi dağıtık container yönetim sistemleri, gelen request'ler için **load balancing** desteği sunarken aynı zamanda **container replication**'ını yönetmek için entegre mekanizmalara sahiptir. Hepsi **cluster seviyesinde**. @@ -499,7 +499,7 @@ Elbette bazı **özel durumlarda** bir container içinde birden fazla **Uvicorn Bu durumlarda çalıştırmak istediğiniz worker sayısını `--workers` komut satırı seçeneğiyle ayarlayabilirsiniz: ```{ .dockerfile .annotate } -FROM python:3.9 +FROM python:3.14 WORKDIR /code diff --git a/docs/tr/docs/deployment/https.md b/docs/tr/docs/deployment/https.md index bb70883aa..de9716520 100644 --- a/docs/tr/docs/deployment/https.md +++ b/docs/tr/docs/deployment/https.md @@ -65,7 +65,7 @@ Burada, bir HTTPS API’nin adım adım nasıl görünebileceğine dair, özelli Muhtemelen her şey, bir **domain adı** **temin etmenizle** başlar. Sonra bunu bir DNS server’ında (muhtemelen aynı cloud provider’ınızda) yapılandırırsınız. -Muhtemelen bir cloud server (virtual machine) ya da benzeri bir şey alırsınız ve bunun fixed bir **public IP adresi** olur. +Muhtemelen bir cloud server (virtual machine) ya da benzeri bir şey alırsınız ve bunun sabit bir **public IP adresi** olur. DNS server(lar)ında, bir kaydı ("`A record`") **domain**’inizi server’ınızın **public IP adresine** yönlendirecek şekilde yapılandırırsınız. diff --git a/docs/tr/docs/deployment/index.md b/docs/tr/docs/deployment/index.md index 055d99929..c9efcef99 100644 --- a/docs/tr/docs/deployment/index.md +++ b/docs/tr/docs/deployment/index.md @@ -1,8 +1,8 @@ -# Deployment { #deployment } +# Dağıtım { #deployment } **FastAPI** uygulamasını deploy etmek nispeten kolaydır. -## Deployment Ne Anlama Gelir? { #what-does-deployment-mean } +## Dağıtım Ne Anlama Gelir? { #what-does-deployment-mean } Bir uygulamayı **deploy** etmek, onu **kullanıcılara erişilebilir hale getirmek** için gerekli adımları gerçekleştirmek anlamına gelir. @@ -10,7 +10,7 @@ Bir **web API** için bu süreç normalde uygulamayı **uzak bir makineye** yerl Bu, kodu sürekli olarak değiştirdiğiniz, bozup düzelttiğiniz, geliştirme sunucusunu durdurup yeniden başlattığınız vb. **geliştirme** aşamalarının tam tersidir. -## Deployment Stratejileri { #deployment-strategies } +## Dağıtım Stratejileri { #deployment-strategies } Kullanım durumunuza ve kullandığınız araçlara bağlı olarak bunu yapmanın birkaç yolu vardır. diff --git a/docs/tr/docs/deployment/manually.md b/docs/tr/docs/deployment/manually.md index 561ba8677..8c7fd990e 100644 --- a/docs/tr/docs/deployment/manually.md +++ b/docs/tr/docs/deployment/manually.md @@ -46,7 +46,7 @@ Bu, çoğu durumda işinizi görür. 😎 Şimdi biraz daha detaya inelim. -FastAPI, Python web framework'leri ve sunucularını inşa etmek için kullanılan ASGI adlı bir standardı kullanır. FastAPI bir ASGI web framework'üdür. +FastAPI, Python web framework'leri ve sunucularını inşa etmek için kullanılan ASGI adlı bir standardı kullanır. FastAPI bir ASGI web framework'üdür. Uzak bir sunucu makinesinde **FastAPI** uygulamasını (veya herhangi bir ASGI uygulamasını) çalıştırmak için gereken ana şey, **Uvicorn** gibi bir ASGI server programıdır. `fastapi` komutuyla varsayılan olarak gelen de budur. @@ -74,7 +74,7 @@ FastAPI'yi kurduğunuzda, production sunucusu olarak Uvicorn da beraberinde geli Ancak bir ASGI server'ı manuel olarak da kurabilirsiniz. -Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, etkinleştirdiğinizden emin olun; ardından server uygulamasını kurabilirsiniz. +Bir [sanal ortam](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, etkinleştirdiğinizden emin olun; ardından server uygulamasını kurabilirsiniz. Örneğin Uvicorn'u kurmak için: diff --git a/docs/tr/docs/environment-variables.md b/docs/tr/docs/environment-variables.md index e4f769a16..8b546c77a 100644 --- a/docs/tr/docs/environment-variables.md +++ b/docs/tr/docs/environment-variables.md @@ -153,7 +153,7 @@ Hello World from Python /// tip | İpucu -Bu konuyla ilgili daha fazlasını The Twelve-Factor App: Config bölümünde okuyabilirsiniz. +Bu konuyla ilgili daha fazlasını Twelve-Factor Uygulaması: Config bölümünde okuyabilirsiniz. /// @@ -291,7 +291,7 @@ Bu bilgiler, [Virtual Environments](virtual-environments.md){.internal-link targ Buraya kadar **ortam değişkenleri**nin ne olduğuna ve Python’da nasıl kullanılacağına dair temel bir fikir edinmiş olmalısınız. -Ayrıca Wikipedia for Environment Variable sayfasından daha fazlasını da okuyabilirsiniz. +Ayrıca Ortam Değişkeni için Wikipedia sayfasından daha fazlasını da okuyabilirsiniz. Çoğu zaman ortam değişkenlerinin hemen nasıl işe yarayacağı ilk bakışta çok net olmayabilir. Ancak geliştirme yaparken birçok farklı senaryoda tekrar tekrar karşınıza çıkarlar; bu yüzden bunları bilmek faydalıdır. diff --git a/docs/tr/docs/features.md b/docs/tr/docs/features.md index 86085c5e9..5c651e491 100644 --- a/docs/tr/docs/features.md +++ b/docs/tr/docs/features.md @@ -1,59 +1,55 @@ -# Özelikler +# Özellikler { #features } -## FastAPI özellikleri +## FastAPI Özellikleri { #fastapi-features } -**FastAPI** sana bunları sağlıyor +**FastAPI** size şunları sağlar: -### Açık standartları temel alır +### Açık Standartlara Dayalı { #based-on-open-standards } -* API oluşturma işlemlerinde OpenAPI buna path operasyonları parametreleri, body talebi, güvenlik gibi şeyler dahil olmak üzere deklare bunların deklare edilmesi. -* Otomatik olarak data modelinin JSON Schema ile beraber dokümante edilmesi (OpenAPI'n kendisi zaten JSON Schema'ya dayanıyor). -* Titiz bir çalışmanın sonucunda yukarıdaki standartlara uygun bir framework oluşturduk. Standartları pastanın üzerine sonradan eklenmiş bir çilek olarak görmedik. -* Ayrıca bu bir çok dilde kullanılabilecek **client code generator** kullanımına da izin veriyor. +* API oluşturmada OpenAPI, buna path operasyonları, parametreler, request body'leri, güvenlik vb. deklarasyonları dahildir. +* JSON Schema ile otomatik veri modeli dokümantasyonu (OpenAPI zaten JSON Schema'ya dayanır). +* Bu standartlar etrafında, titiz bir çalışmanın ardından tasarlandı; sonradan eklenmiş bir katman değil. +* Bu sayede birçok dilde otomatik **client code generation** da kullanılabilir. -### Otomatik dokümantasyon +### Otomatik Dokümantasyon { #automatic-docs } +Etkileşimli API dokümantasyonu ve keşif için web arayüzleri. Framework OpenAPI’ye dayandığından, birden fazla seçenek vardır; varsayılan olarak 2’si dahildir. -OpenAPI standartlarına dayalı olan bir framework olarak, geliştiricilerin birden çok seçeneği var, varsayılan olarak gelen 2 farklı interaktif API dokümantasyonu ve web kullanıcı arayüzü var. - - -* Swagger UI interaktif olarak API'ınızı tarayıcı üzerinden çağırıp test edebilmenize olanak sağlıyor. +* Swagger UI ile etkileşimli keşif; API’nizi tarayıcıdan doğrudan çağırıp test edin. ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* ReDoc ile beraber alternatif API dokümantasyonu. +* ReDoc ile alternatif API dokümantasyonu. ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Sadece modern Python +### Sadece Modern Python { #just-modern-python } -Tamamiyle standartlar **Python 3.8**'nın type hintlerine dayanıyor (Pydantic'in sayesinde). Yeni bir syntax öğrenmene gerek yok. Sadece modern Python. +Her şey standart **Python type** deklarasyonlarına dayanır (Pydantic sayesinde). Öğrenilecek yeni bir söz dizimi yok. Sadece standart, modern Python. +Python type’larını nasıl kullanacağınıza dair 2 dakikalık bir hatırlatmaya ihtiyacınız varsa (FastAPI kullanmasanız bile) kısa eğitime göz atın: [Python Types](python-types.md){.internal-link target=_blank}. -Eğer Python type hintlerini bilmiyorsan veya bir hatırlatmaya ihtiyacın var ise(FastAPI kullanmasan bile) şu iki dakikalık küçük bilgilendirici içeriğe bir göz at: [Python Types](python-types.md){.internal-link target=_blank}. - -Standart Python'u typelarını belirterek yazıyorsun: +Türleriyle standart Python yazarsınız: ```Python -from typing import List, Dict from datetime import date from pydantic import BaseModel -# Değişkeni str olarak belirt -# ve o fonksiyon için harika bir editör desteği al +# Bir değişkeni str olarak belirt +# ve fonksiyon içinde editör desteği al def main(user_id: str): return user_id -# Pydantic modeli +# Bir Pydantic modeli class User(BaseModel): id: int name: str joined: date ``` -Sonrasında bu şekilde kullanabilirsin +Sonra şöyle kullanabilirsiniz: ```Python my_user: User = User(id=3, name="John Doe", joined="2018-07-19") @@ -67,25 +63,26 @@ second_user_data = { my_second_user: User = User(**second_user_data) ``` + /// info -`**second_user_data` şu anlama geliyor: +`**second_user_data` şu anlama gelir: -Key-Value çiftini direkt olarak `second_user_data` dictionarysine kaydet , yaptığın şey buna eşit olacak: `User(id=4, name="Mary", joined="2018-11-30")` +`second_user_data` dict’indeki anahtar ve değerleri doğrudan anahtar-değer argümanları olarak geç; şu ifadeye eşdeğerdir: `User(id=4, name="Mary", joined="2018-11-30")` /// -### Editor desteği +### Editör Desteği { #editor-support } -Bütün framework kullanılması kolay ve sezgileri güçlü olması için tasarlandı, verilen bütün kararlar geliştiricilere en iyi geliştirme deneyimini yaşatmak üzere, bir çok editör üzerinde test edildi. +Tüm framework, kullanımı kolay ve sezgisel olacak şekilde tasarlandı; en iyi geliştirme deneyimini sağlamak için geliştirmeye başlamadan önce bile alınan kararlar birden çok editörde test edildi. -Son yapılan Python geliştiricileri anketinde, açık ara en çok kullanılan özellik "oto-tamamlama" idi.. +Python geliştirici anketlerinde açıkça görülüyor ki en çok kullanılan özelliklerden biri "otomatik tamamlama". -Bütün **FastAPI** frameworkü oto-tamamlama açısından geliştiriciyi tatmin etmek üzerine tasarlandı. Otomatik tamamlama her yerde çalışıyor. +Tüm **FastAPI** bunun tatmin edilmesi üzerine kuruldu. Otomatik tamamlama her yerde çalışır. -Dokümantasyona tekrardan çok nadir olarak geleceksin. +Dokümana geri dönmeniz nadiren gerekecek. -Editörün sana nasıl yardım ettiğine bir bak: +Editörünüz şöyle yardımcı olabilir: * Visual Studio Code ile: @@ -95,115 +92,111 @@ Editörün sana nasıl yardım ettiğine bir bak: ![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) +Daha önce imkânsız olduğunu düşünebileceğiniz yerlerde bile tamamlama alırsınız. Örneğin, bir request’ten gelen (iç içe de olabilir) JSON body içindeki `price` anahtarı için. -Daha önceden düşünüp en imkansız diyebileceğin durumlarda bile otomatik tamamlama alacaksın, örnek olarak `price` JSON body içerisinde (nested bir JSON body de olabilirdi.) direkt olarak istekten geliyor, bu durumda bile oto-tammalama sağlıyor. +Artık anahtar adlarını yanlış yazmak, dokümana gidip gelmek ya da sonunda `username` mi `user_name` mi kullandığınızı bulmak için sayfayı yukarı aşağı kaydırmak yok. -Artık key isimlerini yanlış yazma, dokümantasyona dönüp deliler gibi yukarı aşağı sayfada gezmek ve en sonunda `username` mi yoksa `user_name` mi kullandım gibi sorular yok. +### Kısa { #short } -### Kısa +Her şey için mantıklı **varsayılanlar** ve her yerde isteğe bağlı yapılandırmalar vardır. Tüm parametreler, ihtiyacınızı karşılayacak şekilde ince ayar yapılarak tanımlamak istediğiniz API’yi oluşturabilir. -Her şey için mantıklı bir **varsayılanı** var. Parametrelerini opsiyonel olarak tanımlayıp API'nı istediğin gibi modifiye edebilirsin. +Ancak varsayılan hâliyle hepsi **“hemen çalışır”**. -Hepsi varsayılan olarak **çalışıyor**. +### Doğrulama { #validation } +* Çoğu (veya hepsi?) Python **veri tipi** için doğrulama, şunlar dâhil: + * JSON nesneleri (`dict`). + * Eleman tipleri tanımlanan JSON dizileri (`list`). + * Minimum ve maksimum uzunlukları tanımlanan String (`str`) alanları. + * Min ve max değerleri olan sayılar (`int`, `float`) vb. -### Doğrulama - -* Neredeyse bütün (ya da hepsi?) Python **data typeları** için doğrulama, kapsadıkları: - * JSON objeleri (`dict`). - * JSON array (`list`) item type'ı belirtirken. - * String (`str`) parametresi, minimum ve maksimum uzunluk gibi sınırlandırmalar yaparken. - * Numaralar (`int`, `float`) maksimum ve minimum gibi sınırlandırmalar yaparken. - -* Bunlar gibi en egzotik typelarla bile doğrulama yapabiliyorsunuz.: +* Daha “egzotik” tipler için doğrulama: * URL. * Email. * UUID. * ...ve diğerleri. -Bütün doğrulama olayları çok güçlü bir kütüphane sayesinde yapılıyor, **Pydantic**. +Tüm doğrulama köklü ve sağlam **Pydantic** tarafından yapılır. -### Güvenlik ve kimlik doğrulama +### Güvenlik ve Kimlik Doğrulama { #security-and-authentication } -Güvenlik ve doğrulama database ve data modellerinden taviz vermeden entegre edilebilir durumda. +Güvenlik ve kimlik doğrulama entegredir. Veritabanları veya veri modelleriyle ilgili hiçbir taviz yoktur. -Bütün güvenlik şemaları OpenAPI'da tanımlanmış durumda, kapsadıkları: +OpenAPI’da tanımlanan tüm güvenlik şemaları, şunlar dâhil: * HTTP Basic. -* **OAuth2** (ve **JWT tokenleriyle** beraber). Bu öğretici içeriğe göz atabilirsin [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. +* **OAuth2** (ayrıca **JWT token**’larla). Şu eğitime göz atın: [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. * API anahtarları: - * Headerlar. - * Query parametreleri. - * Cookies, vs. + * Header’larda. + * Query parametrelerinde. + * Cookie’lerde vb. -Bütün güvenlik özellikleri Starlette'den geliyor (**session cookies'de** dahil olmak üzere). +Buna ek olarak Starlette’in tüm güvenlik özellikleri (**session cookies** dâhil). -Bütün hepsi tekrardan kullanılabilir aletler ve bileşenler olarak, kolayca sistemlerinize, data depolarınıza, ilişkisel ve NoSQL databaselerinize entegre edebileceğiniz şekilde yapıldı. +Tümü, sistemleriniz, veri depolarınız, ilişkisel ve NoSQL veritabanlarınız vb. ile kolayca entegre edilebilen, yeniden kullanılabilir araçlar ve bileşenler olarak inşa edilmiştir. -### Dependency injection +### Dependency Injection { #dependency-injection } -FastAPI'ın inanılmaz derecede kullanımı kolay, fakat inanılmaz derecede güçlü Dependency Injection sistemi var. +FastAPI, son derece kolay kullanımlı ama son derece güçlü bir Dependency Injection sistemine sahiptir. -* Dependencylerin bile dependencies'i olabiliyor, FastAPI bunun için **graph of "dependency"** yaratıyor. -* Hepsi **otomatik olarak** FastAPI tarafından hallediliyor. -* Bütün zorunlulukların gelen datalara bağlı olarak farklı gereksinimleri olabiliyor, ilave path operasyonlarının kısıtlamaları ve otomatik dokümantasyonu da ayrıca yapılıyor . -* Path operasyonu parametreleri içerisinde belirtilen gereksinimler için bile **Otomatik doğrulama** yapılabiliyor. -* Kompleks kimlik doğrulama sistemleri için destek, **database bağlantıları**, vs. -* **Taviz yok** hiçbir şeyden taviz vermeden, database frontend vs. Bütün hepsinin kolayca entegre edilebiliyor. +* Bağımlılıkların da kendi bağımlılıkları olabilir; böylece bir hiyerarşi veya **bağımlılıklar "grafı"** oluşur. +* Tüm süreç framework tarafından **otomatik olarak yönetilir**. +* Tüm bağımlılıklar, request’lerden veri talep edebilir ve *path operation* kısıtlarını ve otomatik dokümantasyonu **zenginleştirebilir**. +* Bağımlılıklarda tanımlanan *path operation* parametreleri için bile **otomatik doğrulama**. +* Karmaşık kullanıcı kimlik doğrulama sistemleri, **veritabanı bağlantıları** vb. için destek. +* Veritabanları, frontend’ler vb. ile **taviz yok**; ancak hepsiyle kolay entegrasyon. -### Sınırsız "plug-inler" +### Sınırsız "Plug-in" { #unlimited-plug-ins } -Başka bir deyişle, plug-inlere ihtiyacımız yok, import edip direkt olarak kullanmaya başlayabiliriz. +Başka bir deyişle, onlara gerek yok; ihtiyaç duyduğunuz kodu import edin ve kullanın. -Bütün entegrasyonlar kullanımı kolay olmak üzere (zorunluluklar ile beraber) tasarlandı, sen bir "plug-in" yaratıp 2 satır kod ile, *path operasyonlarında* kullandığımız syntax ve aynı yapı ile koduna entregre edebilirsin. +Her entegrasyon (bağımlılıklar ile) o kadar basit olacak şekilde tasarlanmıştır ki, uygulamanız için, *path operations* ile kullandığınız aynı yapı ve söz dizimiyle sadece 2 satırda bir “plug-in” yazabilirsiniz. +### Test Edildi { #tested } -### Test edildi +* %100 test kapsayıcılığı. +* %100 type annotated kod tabanı. +* Üretimde kullanılan uygulamalarda kullanılıyor. -* 100% test coverage. -* 100% typeları belirtilmiş codebase. -* FastAPI ile yapılan bir çok proje insanlar tarafından kullanılıyor. +## Starlette Özellikleri { #starlette-features } -## Starlette özellikleri +**FastAPI**, Starlette ile tamamen uyumludur (ve onun üzerine kuruludur). Dolayısıyla elinizdeki ek Starlette kodları da çalışır. -**FastAPI**, Starlette ile tamamiyle uyumlu ve üzerine kurulu. Yani FastAPI üzerine ekleme yapacağınız herhangi bir Starlette kodu da çalışacaktır. +`FastAPI` aslında `Starlette`’in bir alt sınıfıdır. Starlette’i zaten biliyor veya kullanıyorsanız, işlevlerin çoğu aynı şekilde çalışır. -`FastAPI` aslında `Starlette`'nin bir sub-class'ı. Eğer Starlette'nin nasıl kullanılacağını biliyor isen, çoğu işlevini aynı şekilde yapıyor. +**FastAPI** ile **Starlette**’in tüm özelliklerini elde edersiniz (FastAPI, steroid basılmış Starlette gibidir): -**FastAPI** ile beraber **Starlette**'nin bütün özelliklerine de sahip olacaksınız (FastAPI aslında Starlette'nin steroid basmış hali): - -* Gerçekten etkileyici bir performansa sahip.Python'un ise en hızlı frameworklerinden bir tanesi, **NodeJS** ve **Go** ile ise eşdeğer performansa sahip.. +* Cidden etkileyici performans. Mevcut en hızlı Python frameworklerinden biridir; **NodeJS** ve **Go** ile aynı seviyededir. * **WebSocket** desteği. -* **GraphQL** desteği. -* Kullanım halinde arka plan işlevleri. -* Başlatma ve kapatma eventleri(startup and shutdown). -* Test sunucusu HTTPX üzerine kurulu. -* **CORS**, GZip, Static dosyalar, Streaming responseları. -* **Session and Cookie** desteği. -* 100% test kapsayıcılığı. -* 100% typeları belirtilmiş codebase. +* Süreç içi arka plan görevleri. +* Başlatma ve kapatma olayları. +* HTTPX üzerine kurulu test istemcisi. +* **CORS**, GZip, Static Files, Streaming response’lar. +* **Session** ve **Cookie** desteği. +* %100 test kapsayıcılığı. +* %100 type annotated kod tabanı. -## Pydantic özellikleri +## Pydantic Özellikleri { #pydantic-features } -**FastAPI** ile Pydantic tamamiyle uyumlu ve üzerine kurulu. Yani FastAPI üzerine ekleme yapacağınız herhangi bir Pydantic kodu da çalışacaktır. +**FastAPI**, Pydantic ile tamamen uyumludur (ve onun üzerine kuruludur). Dolayısıyla elinizdeki ek Pydantic kodları da çalışır. -Bunlara Pydantic üzerine kurulu ORM databaseler ve , ODM kütüphaneler de dahil olmak üzere. +Pydantic’e dayanan harici kütüphaneler de dâhildir; veritabanları için ORM’ler, ODM’ler gibi. -Bu ayrıca şu anlama da geliyor, bir çok durumda requestten gelen objeyi **direkt olarak database**'e her şeyi otomatik olarak doğrulanmış bir biçimde aktarabilirisin. +Bu aynı zamanda, birçok durumda request’ten aldığınız nesneyi **doğrudan veritabanına** iletebileceğiniz anlamına gelir; zira her şey otomatik olarak doğrulanır. -Aynı şekilde, databaseden gelen objeyi de **direkt olarak isteğe** de tamamiyle doğrulanmış bir biçimde gönderebilirsiniz. +Tersi yönde de geçerlidir; birçok durumda veritabanından aldığınız nesneyi **doğrudan client**’a gönderebilirsiniz. -**FastAPI** ile beraber **Pydantic**'in bütün özelliklerine sahip olacaksınız (FastAPI data kontrolünü Pydantic'in üzerine kurduğu için): +**FastAPI** ile **Pydantic**’in tüm özelliklerini elde edersiniz (FastAPI, tüm veri işlemede Pydantic’e dayanır): * **Kafa karıştırmaz**: - * Farklı bir syntax öğrenmenize gerek kalmaz, - * Eğer Python typelarını nasıl kullanacağını biliyorsan Pydantic kullanmayı da biliyorsundur. -* Kullandığın geliştirme araçları ile iyi çalışır **IDE/linter/brain**: - * Pydantic'in veri yapıları aslında sadece senin tanımladığın classlar; Bu yüzden doğrulanmış dataların ile otomatik tamamlama, linting ve mypy'ı kullanarak sorunsuz bir şekilde çalışabilirsin -* **En kompleks** yapıları bile doğrula: - * Hiyerarşik Pydantic modellerinin kullanımı ile beraber, Python `typing`’s `List` and `Dict`, vs gibi şeyleri doğrula. - * Doğrulayıcılar en kompleks data şemalarının bile temiz ve kolay bir şekilde tanımlanmasına izin veriyor, ve hepsi JSON şeması olarak dokümante ediliyor - * Pydantic, JSON objen ne kadar derin (nested) olursa olsun doğrulamasını ve gösterimini yapıyor + * Öğrenmeniz gereken yeni bir şema tanımlama mikro-dili yok. + * Python type’larını biliyorsanız Pydantic’i nasıl kullanacağınızı da biliyorsunuz. +* **IDE/linter/beyin**’inizle iyi anlaşır: + * Pydantic veri yapıları, sizin tanımladığınız sınıfların örnekleridir; bu nedenle doğrulanmış verilerinizle otomatik tamamlama, linting ve mypy sorunsuz çalışır, sezgileriniz de yol gösterir. +* **Karmaşık yapıları** doğrulayın: + * Hiyerarşik Pydantic modelleri, Python `typing`’in `List` ve `Dict`’i vb. kullanımı. + * Doğrulayıcılar (validators), karmaşık veri şemalarının net ve kolay şekilde tanımlanmasını, kontrol edilmesini ve JSON Schema olarak dokümante edilmesini sağlar. + * Derinlemesine iç içe **JSON** nesnelerine sahip olabilir, hepsinin doğrulanmasını ve anotasyonlanmasını sağlayabilirsiniz. * **Genişletilebilir**: - * Pydantic özelleştirilmiş data tiplerinin tanımlanmasının yapılmasına izin veriyor ayrıca validator decoratorü ile senin doğrulamaları genişletip, kendi doğrulayıcılarını yazmana izin veriyor. -* 100% test kapsayıcılığı. + * Pydantic, özel veri tiplerinin tanımlanmasına izin verir; ayrıca validator decorator’üyle bir modeldeki metodlarla doğrulamayı genişletebilirsiniz. +* %100 test kapsayıcılığı. diff --git a/docs/tr/docs/history-design-future.md b/docs/tr/docs/history-design-future.md index 764a51957..d70e45104 100644 --- a/docs/tr/docs/history-design-future.md +++ b/docs/tr/docs/history-design-future.md @@ -44,7 +44,7 @@ Sonrasında, (**FastAPI** kullanan bir geliştirici olarak) sahip olmak istediğ Çeşitli fikirleri en popüler Python editörlerinde test ettim: PyCharm, VS Code, Jedi tabanlı editörler. -Bu test, en son Python Developer Survey'ine göre, kullanıcıların yaklaşık %80'inin kullandığı editörleri kapsıyor. +Bu test, en son Python Geliştirici Anketi'ine göre, kullanıcıların yaklaşık %80'inin kullandığı editörleri kapsıyor. Bu da demek oluyor ki **FastAPI**, Python geliştiricilerinin %80'inin kullandığı editörlerle test edildi. Ve diğer editörlerin çoğu benzer şekilde çalıştığından, avantajları neredeyse tüm editörlerde çalışacaktır. diff --git a/docs/tr/docs/how-to/authentication-error-status-code.md b/docs/tr/docs/how-to/authentication-error-status-code.md index 579673624..a22bef1cb 100644 --- a/docs/tr/docs/how-to/authentication-error-status-code.md +++ b/docs/tr/docs/how-to/authentication-error-status-code.md @@ -8,7 +8,7 @@ Ancak herhangi bir nedenle client'larınız eski davranışa bağlıysa, securit Örneğin, varsayılan `401 Unauthorized` hatası yerine `403 Forbidden` hatası döndüren bir `HTTPBearer` alt sınıfı oluşturabilirsiniz: -{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} /// tip | İpucu diff --git a/docs/tr/docs/how-to/conditional-openapi.md b/docs/tr/docs/how-to/conditional-openapi.md index 9562637c4..32b799fa7 100644 --- a/docs/tr/docs/how-to/conditional-openapi.md +++ b/docs/tr/docs/how-to/conditional-openapi.md @@ -4,9 +4,9 @@ Gerekirse, ayarlar ve environment variable'ları kullanarak OpenAPI'yi ortama g ## Güvenlik, API'ler ve dokümantasyon hakkında { #about-security-apis-and-docs } -Production ortamında dokümantasyon arayüzlerini gizlemek, API'nizi korumanın yolu *olmamalıdır*. +Production ortamında dokümantasyon arayüzlerini gizlemek, API'nizi korumanın yolu olmamalıdır. -Bu, API'nize ekstra bir güvenlik katmanı eklemez; *path operation*'lar bulundukları yerde yine erişilebilir olacaktır. +Bu, API'nize ekstra bir güvenlik katmanı eklemez; path operation'lar bulundukları yerde yine erişilebilir olacaktır. Kodunuzda bir güvenlik açığı varsa, o açık yine var olmaya devam eder. @@ -29,7 +29,7 @@ Yine de, bazı ortamlarda (örn. production) veya environment variable'lardan ge Örneğin: -{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} Burada `openapi_url` ayarını, varsayılanı `"/openapi.json"` olacak şekilde tanımlıyoruz. diff --git a/docs/tr/docs/how-to/configure-swagger-ui.md b/docs/tr/docs/how-to/configure-swagger-ui.md index 6c051a121..54e27d5dc 100644 --- a/docs/tr/docs/how-to/configure-swagger-ui.md +++ b/docs/tr/docs/how-to/configure-swagger-ui.md @@ -18,7 +18,7 @@ Ayarları değiştirmeden bırakırsanız, syntax highlighting varsayılan olara Ancak `syntaxHighlight` değerini `False` yaparak devre dışı bırakabilirsiniz: -{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} ...ve ardından Swagger UI artık syntax highlighting'i göstermeyecektir: @@ -28,7 +28,7 @@ Ancak `syntaxHighlight` değerini `False` yaparak devre dışı bırakabilirsini Aynı şekilde, `"syntaxHighlight.theme"` anahtarıyla (ortasında bir nokta olduğuna dikkat edin) syntax highlighting temasını ayarlayabilirsiniz: -{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} Bu yapılandırma, syntax highlighting renk temasını değiştirir: @@ -46,13 +46,13 @@ FastAPI, çoğu kullanım senaryosu için uygun bazı varsayılan yapılandırma Örneğin `deepLinking`'i devre dışı bırakmak için `swagger_ui_parameters`'a şu ayarları geçebilirsiniz: -{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} ## Diğer Swagger UI Parametreleri { #other-swagger-ui-parameters } Kullanabileceğiniz diğer tüm olası yapılandırmaları görmek için, resmi Swagger UI parametreleri dokümantasyonunu okuyun. -## Yalnızca JavaScript ayarları { #javascript-only-settings } +## Yalnızca JavaScript Ayarları { #javascript-only-settings } Swagger UI ayrıca bazı yapılandırmaların **yalnızca JavaScript** nesneleri olmasına izin verir (örneğin JavaScript fonksiyonları). diff --git a/docs/tr/docs/how-to/custom-docs-ui-assets.md b/docs/tr/docs/how-to/custom-docs-ui-assets.md index bdd2d0244..4a843b40f 100644 --- a/docs/tr/docs/how-to/custom-docs-ui-assets.md +++ b/docs/tr/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ Bu, örneğin bazı URL'leri kısıtlayan bir ülkede yaşıyorsanız faydalı o Bunları devre dışı bırakmak için `FastAPI` uygulamanızı oluştururken URL'lerini `None` olarak ayarlayın: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} ### Özel dokümanları ekleyin { #include-the-custom-docs } @@ -34,7 +34,7 @@ Dokümanlar için HTML sayfalarını üretmek üzere FastAPI'nin dahili fonksiyo ReDoc için de benzer şekilde... -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} /// tip | İpucu @@ -50,7 +50,7 @@ Swagger UI bunu arka planda sizin için yönetir, ancak bu "redirect" yardımcı Şimdi her şeyin çalıştığını test edebilmek için bir *path operation* oluşturun: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} ### Test edin { #test-it } @@ -118,7 +118,7 @@ Bundan sonra dosya yapınız şöyle görünebilir: * `StaticFiles` içe aktarın. * Belirli bir path'te bir `StaticFiles()` instance'ını "mount" edin. -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} ### Statik dosyaları test edin { #test-the-static-files } @@ -144,7 +144,7 @@ Bu, uygulamanızdan statik dosyaları servis edebildiğinizi ve dokümanlar içi Bunları devre dışı bırakmak için `FastAPI` uygulamanızı oluştururken URL'lerini `None` olarak ayarlayın: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} ### Statik dosyalar için özel dokümanları ekleyin { #include-the-custom-docs-for-static-files } @@ -160,7 +160,7 @@ Yine FastAPI'nin dahili fonksiyonlarını kullanarak dokümanlar için HTML sayf ReDoc için de benzer şekilde... -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} /// tip | İpucu @@ -176,7 +176,7 @@ Swagger UI bunu arka planda sizin için yönetir, ancak bu "redirect" yardımcı Şimdi her şeyin çalıştığını test edebilmek için bir *path operation* oluşturun: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} ### Statik Dosyalar UI'ını Test Edin { #test-static-files-ui } diff --git a/docs/tr/docs/how-to/extending-openapi.md b/docs/tr/docs/how-to/extending-openapi.md index 99691946c..9998f0bac 100644 --- a/docs/tr/docs/how-to/extending-openapi.md +++ b/docs/tr/docs/how-to/extending-openapi.md @@ -4,7 +4,7 @@ Oluşturulan OpenAPI şemasını değiştirmeniz gereken bazı durumlar olabilir Bu bölümde bunun nasıl yapılacağını göreceksiniz. -## Normal süreç { #the-normal-process } +## Normal Süreç { #the-normal-process } Normal (varsayılan) süreç şöyledir. @@ -33,7 +33,7 @@ Ve `get_openapi()` fonksiyonu şu parametreleri alır: /// -## Varsayılanları ezme { #overriding-the-defaults } +## Varsayılanları Ezme { #overriding-the-defaults } Yukarıdaki bilgileri kullanarak aynı yardımcı fonksiyonla OpenAPI şemasını üretebilir ve ihtiyacınız olan her parçayı override edebilirsiniz. @@ -43,21 +43,21 @@ Yukarıdaki bilgileri kullanarak aynı yardımcı fonksiyonla OpenAPI şemasın Önce, tüm **FastAPI** uygulamanızı her zamanki gibi yazın: -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[1,4,7:9] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *} -### OpenAPI şemasını üretme { #generate-the-openapi-schema } +### OpenAPI Şemasını Üretme { #generate-the-openapi-schema } Ardından, bir `custom_openapi()` fonksiyonunun içinde aynı yardımcı fonksiyonu kullanarak OpenAPI şemasını üretin: -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *} -### OpenAPI şemasını değiştirme { #modify-the-openapi-schema } +### OpenAPI Şemasını Değiştirme { #modify-the-openapi-schema } Artık OpenAPI şemasındaki `info` "object"'ine özel bir `x-logo` ekleyerek ReDoc extension'ını ekleyebilirsiniz: -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *} -### OpenAPI şemasını cache'leme { #cache-the-openapi-schema } +### OpenAPI Şemasını Cache'leme { #cache-the-openapi-schema } Ürettiğiniz şemayı saklamak için `.openapi_schema` özelliğini bir "cache" gibi kullanabilirsiniz. @@ -65,15 +65,15 @@ Böylece bir kullanıcı API docs'larınızı her açtığında uygulamanız şe Şema yalnızca bir kez üretilecektir; sonraki request'ler için de aynı cache'lenmiş şema kullanılacaktır. -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *} -### Metodu override etme { #override-the-method } +### Metodu Override Etme { #override-the-method } Şimdi `.openapi()` metodunu yeni fonksiyonunuzla değiştirebilirsiniz. -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *} -### Kontrol edin { #check-it } +### Kontrol Edin { #check-it } http://127.0.0.1:8000/redoc adresine gittiğinizde, özel logonuzu kullandığınızı göreceksiniz (bu örnekte **FastAPI**'nin logosu): diff --git a/docs/tr/docs/how-to/general.md b/docs/tr/docs/how-to/general.md index e3154921a..ad1ed3fbc 100644 --- a/docs/tr/docs/how-to/general.md +++ b/docs/tr/docs/how-to/general.md @@ -14,7 +14,7 @@ Döndürmeniz gerekenden daha fazla veri döndürmediğinizden emin olmak için, *path operation*'larınıza özet ve açıklama eklemek ve bunları dokümantasyon arayüzünde göstermek için, [Tutorial - Path Operation Configurations - Summary and Description](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank} dokümantasyonunu okuyun. -## Dokümantasyon Yanıt Açıklaması - OpenAPI { #documentation-response-description-openapi } +## Dokümantasyon Response Açıklaması - OpenAPI { #documentation-response-description-openapi } Dokümantasyon arayüzünde gösterilen response açıklamasını tanımlamak için, [Tutorial - Path Operation Configurations - Response description](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank} dokümantasyonunu okuyun. diff --git a/docs/tr/docs/how-to/graphql.md b/docs/tr/docs/how-to/graphql.md index fbf018874..99123cf2a 100644 --- a/docs/tr/docs/how-to/graphql.md +++ b/docs/tr/docs/how-to/graphql.md @@ -35,7 +35,7 @@ Kullanım senaryonuza göre farklı bir kütüphaneyi tercih edebilirsiniz; anca Strawberry'yi FastAPI ile nasıl entegre edebileceğinize dair küçük bir ön izleme: -{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *} +{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *} Strawberry hakkında daha fazlasını Strawberry dokümantasyonunda öğrenebilirsiniz. diff --git a/docs/tr/docs/how-to/index.md b/docs/tr/docs/how-to/index.md index 5ec2e0268..928911c0e 100644 --- a/docs/tr/docs/how-to/index.md +++ b/docs/tr/docs/how-to/index.md @@ -8,6 +8,6 @@ Projeniz için ilginç ve yararlı görünen bir şey varsa devam edin ve incele /// tip | İpucu -**FastAPI**'ı yapılandırılmış bir şekilde (önerilir) **öğrenmek** istiyorsanız bunun yerine [Öğretici - Kullanıcı Rehberi](../tutorial/index.md){.internal-link target=_blank}'ni bölüm bölüm okuyun. +**FastAPI**'yi yapılandırılmış bir şekilde (önerilir) **öğrenmek** istiyorsanız bunun yerine [Öğretici - Kullanıcı Rehberi](../tutorial/index.md){.internal-link target=_blank}'ni bölüm bölüm okuyun. /// diff --git a/docs/tr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/tr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md index 275ac5fd1..6d732c1df 100644 --- a/docs/tr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md +++ b/docs/tr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -88,7 +88,7 @@ graph TB style V2Field fill:#f9fff3 ``` -...ancak aynı uygulamada Pydantic v1 ve v2 kullanarak **ayrı (separated)** modeller tanımlayabilirsiniz. +...ancak aynı uygulamada Pydantic v1 ve v2 kullanarak **ayrı** modeller tanımlayabilirsiniz. ```mermaid graph TB diff --git a/docs/tr/docs/index.md b/docs/tr/docs/index.md index 16c425f5d..f240a9691 100644 --- a/docs/tr/docs/index.md +++ b/docs/tr/docs/index.md @@ -40,7 +40,7 @@ Temel özellikleri şunlardır: * **Hızlı**: Çok yüksek performanslı, **NodeJS** ve **Go** ile eşit düzeyde (Starlette ve Pydantic sayesinde). [Mevcut en hızlı Python framework'lerinden biri](#performance). * **Kodlaması Hızlı**: Özellik geliştirme hızını yaklaşık %200 ile %300 aralığında artırır. * * **Daha az hata**: İnsan (geliştirici) kaynaklı hataları yaklaşık %40 azaltır. * -* **Sezgisel**: Harika bir editör desteği. Her yerde Completion. Hata ayıklamaya daha az zaman. +* **Sezgisel**: Harika bir editör desteği. Her yerde Tamamlama. Hata ayıklamaya daha az zaman. * **Kolay**: Kullanımı ve öğrenmesi kolay olacak şekilde tasarlandı. Doküman okumaya daha az zaman. * **Kısa**: Kod tekrarını minimize eder. Her parametre tanımından birden fazla özellik. Daha az hata. * **Sağlam**: Production'a hazır kod elde edersiniz. Otomatik etkileşimli dokümantasyon ile birlikte. @@ -127,7 +127,7 @@ Temel özellikleri şunlardır: -Web API yerine terminalde kullanılacak bir CLI uygulaması geliştiriyorsanız **Typer**'a göz atın. +Web API yerine terminalde kullanılacak bir CLI uygulaması geliştiriyorsanız **Typer**'a göz atın. **Typer**, FastAPI'ın küçük kardeşi. Ve hedefi CLI'ların **FastAPI'ı** olmak. ⌨️ 🚀 @@ -368,7 +368,7 @@ item: Item * Verinin doğrulanması: * Veri geçersiz olduğunda otomatik ve anlaşılır hatalar. * Çok derin iç içe JSON nesneleri için bile doğrulama. -* Girdi verisinin Dönüşümü: network'ten gelen veriyi Python verisine ve type'larına çevirir. Şunlardan okur: +* Girdi verisinin Dönüşümü: network'ten gelen veriyi Python verisine ve type'larına çevirir. Şunlardan okur: * JSON. * Path parameter'lar. * Query parameter'lar. @@ -376,7 +376,7 @@ item: Item * Header'lar. * Form'lar. * File'lar. -* Çıktı verisinin Dönüşümü: Python verisini ve type'larını network verisine çevirir (JSON olarak): +* Çıktı verisinin Dönüşümü: Python verisini ve type'larını network verisine çevirir (JSON olarak): * Python type'larını dönüştürür (`str`, `int`, `float`, `bool`, `list`, vb.). * `datetime` nesneleri. * `UUID` nesneleri. @@ -439,7 +439,7 @@ Daha fazla özellik içeren daha kapsamlı bir örnek için Dependency Injection** sistemi. +* Çok güçlü ve kullanımı kolay bir **Bağımlılık Enjeksiyonu** sistemi. * **JWT tokens** ve **HTTP Basic** auth ile **OAuth2** desteği dahil güvenlik ve kimlik doğrulama. * **Çok derin iç içe JSON modelleri** tanımlamak için daha ileri (ama aynı derecede kolay) teknikler (Pydantic sayesinde). * Strawberry ve diğer kütüphaneler ile **GraphQL** entegrasyonu. @@ -524,7 +524,7 @@ Starlette tarafından kullanılanlar: * httpx - `TestClient` kullanmak istiyorsanız gereklidir. * jinja2 - varsayılan template yapılandırmasını kullanmak istiyorsanız gereklidir. -* python-multipart - `request.form()` ile, form "parsing" desteği istiyorsanız gereklidir. +* python-multipart - `request.form()` ile, form "ayrıştırma" desteği istiyorsanız gereklidir. FastAPI tarafından kullanılanlar: @@ -534,7 +534,7 @@ FastAPI tarafından kullanılanlar: ### `standard` Bağımlılıkları Olmadan { #without-standard-dependencies } -`standard` opsiyonel bağımlılıklarını dahil etmek istemiyorsanız, `pip install "fastapi[standard]"` yerine `pip install fastapi` ile kurabilirsiniz. +`standard` opsiyonel bağımlılıklarını dahil etmek istemiyorsanız, `pip install fastapi` ile kurabilirsiniz. ### `fastapi-cloud-cli` Olmadan { #without-fastapi-cloud-cli } diff --git a/docs/tr/docs/python-types.md b/docs/tr/docs/python-types.md index 01a3efe98..9477e4fab 100644 --- a/docs/tr/docs/python-types.md +++ b/docs/tr/docs/python-types.md @@ -2,9 +2,9 @@ Python, isteğe bağlı "type hints" (diğer adıyla "type annotations") desteğine sahiptir. -Bu **"type hints"** veya annotations, bir değişkenin type'ını bildirmeye yarayan özel bir sözdizimidir. +Bu **"type hints"** veya annotations, bir değişkenin tip'ini bildirmeye yarayan özel bir sözdizimidir. -Değişkenleriniz için type bildirerek, editörler ve araçlar size daha iyi destek sağlayabilir. +Değişkenleriniz için tip bildirerek, editörler ve araçlar size daha iyi destek sağlayabilir. Bu, Python type hints hakkında sadece **hızlı bir eğitim / bilgi tazeleme** dokümanıdır. **FastAPI** ile kullanmak için gereken minimum bilgiyi kapsar... ki aslında bu çok azdır. @@ -22,7 +22,7 @@ Eğer bir Python uzmanıysanız ve type hints hakkında her şeyi zaten biliyors Basit bir örnekle başlayalım: -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} Bu programı çalıştırınca şu çıktıyı alırsınız: @@ -34,9 +34,9 @@ Fonksiyon şunları yapar: * `first_name` ve `last_name` değerlerini alır. * `title()` ile her birinin ilk harfini büyük harfe çevirir. -* Ortada bir boşluk olacak şekilde Concatenates eder. +* Ortada bir boşluk olacak şekilde Birleştirir. -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### Düzenleyelim { #edit-it } @@ -78,7 +78,7 @@ Bu kadar. Bunlar "type hints": -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} Bu, aşağıdaki gibi default değerler bildirmekle aynı şey değildir: @@ -106,7 +106,7 @@ Bununla birlikte, seçenekleri görerek kaydırabilirsiniz; ta ki "tanıdık gel Şu fonksiyona bakın, zaten type hints içeriyor: -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} Editör değişkenlerin tiplerini bildiği için, sadece completion değil, aynı zamanda hata kontrolleri de alırsınız: @@ -114,9 +114,9 @@ Editör değişkenlerin tiplerini bildiği için, sadece completion değil, ayn Artık bunu düzeltmeniz gerektiğini, `age`'i `str(age)` ile string'e çevirmeniz gerektiğini biliyorsunuz: -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} -## Tipleri bildirmek { #declaring-types } +## Tipleri Bildirmek { #declaring-types } Type hints bildirmek için ana yeri az önce gördünüz: fonksiyon parametreleri. @@ -133,29 +133,32 @@ Sadece `str` değil, tüm standart Python tiplerini bildirebilirsiniz. * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} -### Tip parametreleri ile Generic tipler { #generic-types-with-type-parameters } +### `typing` modülü { #typing-module } -`dict`, `list`, `set` ve `tuple` gibi, başka değerler içerebilen bazı veri yapıları vardır. Ve iç değerlerin kendi tipi de olabilir. +Bazı ek kullanım durumları için standart kütüphanedeki `typing` modülünden bazı şeyleri import etmeniz gerekebilir. Örneğin bir şeyin "herhangi bir tip" olabileceğini bildirmek istediğinizde, `typing` içindeki `Any`'yi kullanabilirsiniz: -İç tipleri olan bu tiplere "**generic**" tipler denir. Ve bunları, iç tipleriyle birlikte bildirmek mümkündür. +```python +from typing import Any -Bu tipleri ve iç tipleri bildirmek için standart Python modülü `typing`'i kullanabilirsiniz. Bu modül, özellikle bu type hints desteği için vardır. -#### Python'un daha yeni sürümleri { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -`typing` kullanan sözdizimi, Python 3.6'dan en yeni sürümlere kadar (Python 3.9, Python 3.10, vb. dahil) tüm sürümlerle **uyumludur**. +### Generic tipler { #generic-types } -Python geliştikçe, **daha yeni sürümler** bu type annotations için daha iyi destekle gelir ve çoğu durumda type annotations bildirmek için `typing` modülünü import edip kullanmanız bile gerekmez. +Bazı tipler, köşeli parantez içinde "type parameters" alarak iç tiplerini tanımlayabilir; örneğin "string listesi" `list[str]` olarak bildirilir. -Projeniz için daha yeni bir Python sürümü seçebiliyorsanız, bu ek sadelikten yararlanabilirsiniz. +Bu şekilde type parameter alabilen tiplere **Generic types** veya **Generics** denir. -Tüm dokümanlarda her Python sürümüyle uyumlu örnekler vardır (fark olduğunda). +Aynı builtin tipleri generics olarak kullanabilirsiniz (köşeli parantez ve içinde tiplerle): -Örneğin "**Python 3.6+**", Python 3.6 veya üstüyle (3.7, 3.8, 3.9, 3.10, vb. dahil) uyumludur. "**Python 3.9+**" ise Python 3.9 veya üstüyle (3.10 vb. dahil) uyumludur. - -Eğer **Python'un en güncel sürümlerini** kullanabiliyorsanız, en güncel sürüme ait örnekleri kullanın; bunlar **en iyi ve en basit sözdizimine** sahip olur, örneğin "**Python 3.10+**". +* `list` +* `tuple` +* `set` +* `dict` #### List { #list } @@ -163,11 +166,11 @@ Eğer **Python'un en güncel sürümlerini** kullanabiliyorsanız, en güncel s Değişkeni, aynı iki nokta (`:`) sözdizimiyle bildirin. -Type olarak `list` yazın. +Tip olarak `list` yazın. `list`, bazı iç tipleri barındıran bir tip olduğundan, bunları köşeli parantez içine yazarsınız: -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} /// info | Bilgi @@ -193,7 +196,7 @@ Ve yine de editör bunun bir `str` olduğunu bilir ve buna göre destek sağlar. `tuple`'ları ve `set`'leri bildirmek için de aynısını yaparsınız: -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} Bu şu anlama gelir: @@ -208,7 +211,7 @@ Bir `dict` tanımlamak için, virgülle ayrılmış 2 type parameter verirsiniz. İkinci type parameter, `dict`'in value'ları içindir: -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} Bu şu anlama gelir: @@ -220,44 +223,20 @@ Bu şu anlama gelir: Bir değişkenin **birkaç tipten herhangi biri** olabileceğini bildirebilirsiniz; örneğin bir `int` veya bir `str`. -Python 3.6 ve üzeri sürümlerde (Python 3.10 dahil), `typing` içinden `Union` tipini kullanabilir ve köşeli parantez içine kabul edilecek olası tipleri yazabilirsiniz. +Bunu tanımlamak için, her iki tipi ayırmak üzere dikey çizgi (`|`) kullanırsınız. -Python 3.10'da ayrıca, olası tipleri vertical bar (`|`) ile ayırabildiğiniz **yeni bir sözdizimi** de vardır. - -//// tab | Python 3.10+ +Buna "union" denir, çünkü değişken bu iki tip kümesinin birleşimindeki herhangi bir şey olabilir. ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -Her iki durumda da bu, `item`'ın `int` veya `str` olabileceği anlamına gelir. +Bu, `item`'ın `int` veya `str` olabileceği anlamına gelir. #### Muhtemelen `None` { #possibly-none } Bir değerin `str` gibi bir tipi olabileceğini ama aynı zamanda `None` da olabileceğini bildirebilirsiniz. -Python 3.6 ve üzeri sürümlerde (Python 3.10 dahil), `typing` modülünden `Optional` import edip kullanarak bunu bildirebilirsiniz. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -Sadece `str` yerine `Optional[str]` kullanmak, aslında değer `None` olabilecekken her zaman `str` olduğunu varsaydığınız hataları editörün yakalamanıza yardımcı olmasını sağlar. - -`Optional[Something]`, aslında `Union[Something, None]` için bir kısayoldur; eşdeğerdirler. - -Bu aynı zamanda Python 3.10'da `Something | None` kullanabileceğiniz anlamına gelir: - //// tab | Python 3.10+ ```Python hl_lines="1" @@ -266,96 +245,7 @@ Bu aynı zamanda Python 3.10'da `Something | None` kullanabileceğiniz anlamına //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ alternatif - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### `Union` veya `Optional` kullanmak { #using-union-or-optional } - -Python sürümünüz 3.10'un altındaysa, benim oldukça **öznel** bakış açıma göre küçük bir ipucu: - -* 🚨 `Optional[SomeType]` kullanmaktan kaçının -* Bunun yerine ✨ **`Union[SomeType, None]` kullanın** ✨. - -İkisi eşdeğerdir ve altta aynı şeydir; ama ben `Optional` yerine `Union` önermeyi tercih ederim. Çünkü "**optional**" kelimesi değerin optional olduğunu ima ediyor gibi durur; ama gerçekte anlamı "değer `None` olabilir"dir. Değer optional olmasa ve hâlâ required olsa bile. - -Bence `Union[SomeType, None]` ne anlama geldiğini daha açık şekilde ifade ediyor. - -Bu, tamamen kelimeler ve isimlendirmelerle ilgili. Ancak bu kelimeler, sizin ve ekip arkadaşlarınızın kod hakkında nasıl düşündüğünü etkileyebilir. - -Örnek olarak şu fonksiyonu ele alalım: - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -`name` parametresi `Optional[str]` olarak tanımlanmış, ama **optional değil**; parametre olmadan fonksiyonu çağıramazsınız: - -```Python -say_hi() # Oh, no, this throws an error! 😱 -``` - -`name` parametresi **hâlâ required**'dır (*optional* değildir) çünkü bir default değeri yoktur. Yine de `name`, değer olarak `None` kabul eder: - -```Python -say_hi(name=None) # This works, None is valid 🎉 -``` - -İyi haber şu ki, Python 3.10'a geçtiğinizde bununla uğraşmanız gerekmeyecek; çünkü tiplerin union'larını tanımlamak için doğrudan `|` kullanabileceksiniz: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -Ve böylece `Optional` ve `Union` gibi isimlerle de uğraşmanız gerekmeyecek. 😎 - -#### Generic tipler { #generic-types } - -Köşeli parantez içinde type parameter alan bu tiplere **Generic types** veya **Generics** denir, örneğin: - -//// tab | Python 3.10+ - -Aynı builtin tipleri generics olarak kullanabilirsiniz (köşeli parantez ve içindeki tiplerle): - -* `list` -* `tuple` -* `set` -* `dict` - -Ve önceki Python sürümlerinde olduğu gibi `typing` modülünden: - -* `Union` -* `Optional` -* ...and others. - -Python 3.10'da, `Union` ve `Optional` generics'lerini kullanmaya alternatif olarak, tip union'larını bildirmek için vertical bar (`|`) kullanabilirsiniz; bu çok daha iyi ve daha basittir. - -//// - -//// tab | Python 3.9+ - -Aynı builtin tipleri generics olarak kullanabilirsiniz (köşeli parantez ve içindeki tiplerle): - -* `list` -* `tuple` -* `set` -* `dict` - -Ve `typing` modülünden gelen generics: - -* `Union` -* `Optional` -* ...and others. - -//// +Sadece `str` yerine `str | None` kullanmak, aslında değer `None` olabilecekken her zaman `str` olduğunu varsaydığınız hataları editörün yakalamanıza yardımcı olur. ### Tip olarak sınıflar { #classes-as-types } @@ -363,11 +253,11 @@ Bir sınıfı da bir değişkenin tipi olarak bildirebilirsiniz. Örneğin, adı olan bir `Person` sınıfınız olsun: -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} Sonra bir değişkeni `Person` tipinde olacak şekilde bildirebilirsiniz: -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} Ve sonra, yine tüm editör desteğini alırsınız: @@ -401,21 +291,15 @@ Daha fazlasını öğrenmek için Required Optional fields bölümünde okuyabilirsiniz. - -/// +Bunların pratikte nasıl çalıştığını [Eğitim - Kullanım Kılavuzu](tutorial/index.md){.internal-link target=_blank} içinde çok daha fazla göreceksiniz. ## Metadata Annotations ile Type Hints { #type-hints-with-metadata-annotations } -Python'da ayrıca, `Annotated` kullanarak bu type hints içine **ek metadata** koymayı sağlayan bir özellik de vardır. +Python'da ayrıca, `Annotated` kullanarak bu type hints içine **ek üstveri** koymayı sağlayan bir özellik de vardır. -Python 3.9'dan itibaren `Annotated`, standart kütüphanenin bir parçasıdır; bu yüzden `typing` içinden import edebilirsiniz. +`Annotated`'ı `typing` içinden import edebilirsiniz. -{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} Python'un kendisi bu `Annotated` ile bir şey yapmaz. Editörler ve diğer araçlar için tip hâlâ `str`'dir. @@ -446,14 +330,14 @@ Ayrıca kodunuzun pek çok başka Python aracı ve kütüphanesiyle çok uyumlu ...ve **FastAPI** aynı bildirimleri şunlar için de kullanır: -* **Gereksinimleri tanımlamak**: request path parameters, query parameters, headers, bodies, dependencies, vb. +* **Gereksinimleri tanımlamak**: request path parameters, query parameters, headers, bodies, bağımlılıklar (dependencies), vb. * **Veriyi dönüştürmek**: request'ten gerekli tipe. * **Veriyi doğrulamak**: her request'ten gelen veriyi: * Veri geçersiz olduğunda client'a dönen **otomatik hatalar** üretmek. * OpenAPI kullanarak API'yi **dokümante etmek**: * bu, daha sonra otomatik etkileşimli dokümantasyon kullanıcı arayüzleri tarafından kullanılır. -Bunların hepsi kulağa soyut gelebilir. Merak etmeyin. Tüm bunları [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank} içinde çalışırken göreceksiniz. +Bunların hepsi kulağa soyut gelebilir. Merak etmeyin. Tüm bunları [Eğitim - Kullanım Kılavuzu](tutorial/index.md){.internal-link target=_blank} içinde çalışırken göreceksiniz. Önemli olan, standart Python tiplerini tek bir yerde kullanarak (daha fazla sınıf, decorator vb. eklemek yerine), **FastAPI**'nin sizin için işin büyük kısmını yapmasıdır. diff --git a/docs/tr/docs/translation-banner.md b/docs/tr/docs/translation-banner.md index b52578f71..7491e61b8 100644 --- a/docs/tr/docs/translation-banner.md +++ b/docs/tr/docs/translation-banner.md @@ -4,7 +4,7 @@ Bu çeviri, insanlar tarafından yönlendirilen bir yapay zekâ ile oluşturuldu Orijinal anlamın yanlış anlaşılması ya da kulağa doğal gelmeme gibi hatalar içerebilir. 🤖 -[Yapay zekâyı daha iyi yönlendirmemize yardımcı olarak](https://fastapi.tiangolo.com/tr/contributing/#translations) bu çeviriyi iyileştirebilirsiniz. +[Yapay zekâ LLM'ini daha iyi yönlendirmemize yardımcı olarak](https://fastapi.tiangolo.com/tr/contributing/#translations) bu çeviriyi iyileştirebilirsiniz. [İngilizce sürüm](ENGLISH_VERSION_URL) diff --git a/docs/tr/docs/tutorial/background-tasks.md b/docs/tr/docs/tutorial/background-tasks.md index 4cb67d822..a6dfbe5ea 100644 --- a/docs/tr/docs/tutorial/background-tasks.md +++ b/docs/tr/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ Bu, request’ten sonra yapılması gereken; ancak client’ın response’u alm Önce `BackgroundTasks`’i import edin ve *path operation function*’ınızda `BackgroundTasks` tip bildirimi olan bir parametre tanımlayın: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} **FastAPI**, sizin için `BackgroundTasks` tipinde bir obje oluşturur ve onu ilgili parametre olarak geçirir. @@ -31,13 +31,13 @@ Bu örnekte görev fonksiyonu bir dosyaya yazacaktır (email göndermeyi simüle Ve yazma işlemi `async` ve `await` kullanmadığı için fonksiyonu normal `def` ile tanımlarız: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} ## Arka Plan Görevini Ekleyin { #add-the-background-task } *Path operation function*’ınızın içinde, görev fonksiyonunuzu `.add_task()` metodu ile *background tasks* objesine ekleyin: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} `.add_task()` şu argümanları alır: diff --git a/docs/tr/docs/tutorial/bigger-applications.md b/docs/tr/docs/tutorial/bigger-applications.md index d8a4b8208..9dbaae601 100644 --- a/docs/tr/docs/tutorial/bigger-applications.md +++ b/docs/tr/docs/tutorial/bigger-applications.md @@ -85,7 +85,7 @@ Bu module için *path operation*’ları `APIRouter` kullanarak oluşturabilirsi `FastAPI` class’ında yaptığınız gibi import edip bir "instance" oluşturursunuz: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} ### `APIRouter` ile *Path Operations* { #path-operations-with-apirouter } @@ -93,7 +93,7 @@ Sonra bunu kullanarak *path operation*’larınızı tanımlarsınız. `FastAPI` class’ını nasıl kullanıyorsanız aynı şekilde kullanın: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} `APIRouter`’ı "mini bir `FastAPI`" class’ı gibi düşünebilirsiniz. @@ -117,7 +117,7 @@ Bu yüzden onları ayrı bir `dependencies` module’üne koyuyoruz (`app/depend Şimdi, özel bir `X-Token` header'ını okumak için basit bir dependency kullanalım: -{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | İpucu @@ -149,7 +149,7 @@ Bu module’deki tüm *path operation*’ların şu ortak özelliklere sahip old Dolayısıyla bunları her *path operation*’a tek tek eklemek yerine `APIRouter`’a ekleyebiliriz. -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} Her *path operation*’ın path’i aşağıdaki gibi `/` ile başlamak zorunda olduğundan: @@ -208,7 +208,7 @@ Dependency function’ını ise `app.dependencies` module’ünden, yani `app/de Bu yüzden dependency’ler için `..` ile relative import kullanıyoruz: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} #### Relative Import Nasıl Çalışır { #how-relative-imports-work } @@ -279,7 +279,7 @@ Artık nasıl çalıştığını bildiğinize göre, uygulamalarınız ne kadar Ama yine de belirli bir *path operation*’a uygulanacak _ek_ `tags` tanımlayabilir, ayrıca o *path operation*’a özel `responses` ekleyebiliriz: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | İpucu @@ -305,13 +305,13 @@ Normal şekilde bir `FastAPI` class’ı oluşturursunuz. Hatta her `APIRouter` için olan dependency’lerle birleştirilecek [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank} bile tanımlayabilirsiniz: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} ### `APIRouter` Import Edin { #import-the-apirouter } Şimdi `APIRouter` içeren diğer submodule’leri import ediyoruz: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} `app/routers/users.py` ve `app/routers/items.py` dosyaları aynı Python package’i olan `app`’in parçası olan submodule’ler olduğu için, onları "relative import" ile tek bir nokta `.` kullanarak import edebiliriz. @@ -374,13 +374,13 @@ from .routers.users import router Bu yüzden ikisini de aynı dosyada kullanabilmek için submodule’leri doğrudan import ediyoruz: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} ### `users` ve `items` için `APIRouter`’ları Dahil Edin { #include-the-apirouters-for-users-and-items } Şimdi `users` ve `items` submodule’lerindeki `router`’ları dahil edelim: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} /// info | Bilgi @@ -420,13 +420,13 @@ Bu dosyada, kurumunuzun birden fazla proje arasında paylaştığı bazı admin Bu örnekte çok basit olacak. Ancak kurum içinde başka projelerle paylaşıldığı için, bunu değiştirip `prefix`, `dependencies`, `tags` vs. doğrudan `APIRouter`’a ekleyemediğimizi varsayalım: -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} Yine de bu `APIRouter`’ı dahil ederken özel bir `prefix` ayarlamak istiyoruz ki tüm *path operation*’ları `/admin` ile başlasın; ayrıca bu projede hâlihazırda kullandığımız `dependencies` ile güvene almak, `tags` ve `responses` eklemek istiyoruz. Orijinal `APIRouter`’ı değiştirmeden, bu parametreleri `app.include_router()`’a vererek hepsini tanımlayabiliriz: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} Böylece orijinal `APIRouter` değişmeden kalır; yani aynı `app/internal/admin.py` dosyasını kurum içindeki diğer projelerle de paylaşmaya devam edebiliriz. @@ -447,7 +447,7 @@ Dolayısıyla örneğin diğer projeler aynı `APIRouter`’ı farklı bir authe Burada bunu yapıyoruz... sadece yapabildiğimizi göstermek için 🤷: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} ve `app.include_router()` ile eklenen diğer tüm *path operation*’larla birlikte doğru şekilde çalışır. diff --git a/docs/tr/docs/tutorial/body-multiple-params.md b/docs/tr/docs/tutorial/body-multiple-params.md index 29970ca40..4cd381b86 100644 --- a/docs/tr/docs/tutorial/body-multiple-params.md +++ b/docs/tr/docs/tutorial/body-multiple-params.md @@ -106,12 +106,6 @@ Varsayılan olarak tekil değerler query parametresi olarak yorumlandığı içi q: str | None = None ``` -Ya da Python 3.9'da: - -```Python -q: Union[str, None] = None -``` - Örneğin: {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/tr/docs/tutorial/body-nested-models.md b/docs/tr/docs/tutorial/body-nested-models.md index b4ffef3f1..b661d175d 100644 --- a/docs/tr/docs/tutorial/body-nested-models.md +++ b/docs/tr/docs/tutorial/body-nested-models.md @@ -95,7 +95,7 @@ Yine, sadece bu tanımı yaparak **FastAPI** ile şunları elde edersiniz: `str`, `int`, `float` vb. normal tekil tiplerin yanında, `str`’den türeyen daha karmaşık tekil tipleri de kullanabilirsiniz. -Tüm seçenekleri görmek için Pydantic Type Overview sayfasına göz atın. Sonraki bölümde bazı örnekleri göreceksiniz. +Tüm seçenekleri görmek için Pydantic Türlerine Genel Bakış sayfasına göz atın. Sonraki bölümde bazı örnekleri göreceksiniz. Örneğin `Image` modelinde bir `url` alanımız olduğuna göre, bunu `str` yerine Pydantic’in `HttpUrl` tipinden bir instance olacak şekilde tanımlayabiliriz: @@ -163,7 +163,7 @@ images: list[Image] şu örnekte olduğu gibi: -{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} ## Her yerde editör desteği { #editor-support-everywhere } @@ -193,7 +193,7 @@ Burada göreceğimiz şey de bu. Bu durumda, `int` key’lere ve `float` value’lara sahip olduğu sürece herhangi bir `dict` kabul edersiniz: -{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} /// tip | İpucu diff --git a/docs/tr/docs/tutorial/body.md b/docs/tr/docs/tutorial/body.md index 0557ef737..47fee6701 100644 --- a/docs/tr/docs/tutorial/body.md +++ b/docs/tr/docs/tutorial/body.md @@ -155,7 +155,7 @@ Fonksiyon parametreleri şu şekilde tanınır: FastAPI, `q` değerinin zorunlu olmadığını `= None` default değerinden anlayacaktır. -`str | None` (Python 3.10+) veya `Union[str, None]` (Python 3.9+) içindeki `Union`, FastAPI tarafından bu değerin zorunlu olmadığını belirlemek için kullanılmaz; FastAPI bunun zorunlu olmadığını `= None` default değeri olduğu için bilir. +`str | None`, FastAPI tarafından bu değerin zorunlu olmadığını belirlemek için kullanılmaz; FastAPI bunun zorunlu olmadığını `= None` default değeri olduğu için bilir. Ancak type annotation'larını eklemek, editor'ünüzün size daha iyi destek vermesini ve hataları yakalamasını sağlar. diff --git a/docs/tr/docs/tutorial/cookie-param-models.md b/docs/tr/docs/tutorial/cookie-param-models.md index a5bf51560..0fa399c6a 100644 --- a/docs/tr/docs/tutorial/cookie-param-models.md +++ b/docs/tr/docs/tutorial/cookie-param-models.md @@ -1,18 +1,18 @@ -# Cookie Parameter Models { #cookie-parameter-models } +# Cookie Parametre Modelleri { #cookie-parameter-models } -Birbirleriyle ilişkili bir **cookie** grubunuz varsa, bunları tanımlamak için bir **Pydantic model** oluşturabilirsiniz. +Birbirleriyle ilişkili bir **cookie** grubunuz varsa, bunları tanımlamak için bir **Pydantic model** oluşturabilirsiniz. 🍪 -Bu sayede **model'i yeniden kullanabilir**, **birden fazla yerde** tekrar tekrar kullanabilir ve tüm parametreler için validation ve metadata'yı tek seferde tanımlayabilirsiniz. +Bu sayede **model'i yeniden kullanabilir**, **birden fazla yerde** tekrar tekrar kullanabilir ve tüm parametreler için validation ve metadata'yı tek seferde tanımlayabilirsiniz. 😎 /// note | Not -Bu özellik FastAPI `0.115.0` sürümünden beri desteklenmektedir. +This is supported since FastAPI version `0.115.0`. 🤓 /// /// tip | İpucu -Aynı teknik `Query`, `Cookie` ve `Header` için de geçerlidir. +Aynı teknik `Query`, `Cookie` ve `Header` için de geçerlidir. 😎 /// @@ -42,11 +42,11 @@ Ancak verileri **doldurup** "Execute" düğmesine tıklasanız bile, docs UI **J /// -## Fazladan Cookies'leri Yasaklayın { #forbid-extra-cookies } +## Fazladan Cookie'leri Yasaklayın { #forbid-extra-cookies } Bazı özel kullanım senaryolarında (muhtemelen çok yaygın değildir) almak istediğiniz cookie'leri **kısıtlamak** isteyebilirsiniz. -API'niz artık kendi cookie consent'ını kontrol etme gücüne sahip. +API'niz artık kendi cookie onayı'nı kontrol etme gücüne sahip. 🤪🍪 Pydantic'in model configuration'ını kullanarak `extra` olan herhangi bir field'ı `forbid` edebilirsiniz: @@ -54,9 +54,9 @@ Pydantic'in model configuration'ını kullanarak `extra` olan herhangi bir field Bir client **fazladan cookie** göndermeye çalışırsa, bir **error** response alır. -Onayınızı almak için bunca çaba harcayan zavallı cookie banner'ları... API'nin bunu reddetmesi için. +Onayınızı almak için bunca çaba harcayan zavallı cookie banner'ları... API'nin bunu reddetmesi için. 🍪 -Örneğin client, değeri `good-list-please` olan bir `santa_tracker` cookie'si göndermeye çalışırsa, client `santa_tracker` cookie is not allowed diyen bir **error** response alır: +Örneğin client, değeri `good-list-please` olan bir `santa_tracker` cookie'si göndermeye çalışırsa, client `santa_tracker` cookie'ye izin verilmiyor diyen bir **error** response alır: ```json { @@ -73,4 +73,4 @@ Onayınızı almak için bunca çaba harcayan zavallı cookie banner'ları... **cookies** tanımlamak için **Pydantic model**'lerini kullanabilirsiniz. 😎 +**FastAPI**'de **cookie** tanımlamak için **Pydantic model**'lerini kullanabilirsiniz. 😎 diff --git a/docs/tr/docs/tutorial/cookie-params.md b/docs/tr/docs/tutorial/cookie-params.md index 18eedab7f..28b57fd7e 100644 --- a/docs/tr/docs/tutorial/cookie-params.md +++ b/docs/tr/docs/tutorial/cookie-params.md @@ -1,4 +1,4 @@ -# Çerez (Cookie) Parametreleri { #cookie-parameters } +# Cookie (Çerez) Parametreleri { #cookie-parameters } `Query` ve `Path` parametrelerini tanımladığınız şekilde Cookie parametreleri tanımlayabilirsiniz. @@ -26,20 +26,20 @@ Ancak `fastapi`'dan `Query`, `Path`, `Cookie` ve diğerlerini import ettiğinizd /// info | Bilgi -Çerezleri tanımlamak için `Cookie` kullanmanız gerekir, aksi halde parametreler query parametreleri olarak yorumlanır. +Cookie'leri tanımlamak için `Cookie` kullanmanız gerekir, aksi halde parametreler query parametreleri olarak yorumlanır. /// /// info | Bilgi -**Tarayıcılar çerezleri** özel şekillerde ve arka planda işlediği için, **JavaScript**'in onlara dokunmasına kolayca izin **vermezler**. +**Tarayıcılar cookie'leri** özel şekillerde ve arka planda işlediği için, **JavaScript**'in onlara dokunmasına kolayca izin **vermezler**. -`/docs` adresindeki **API docs UI**'a giderseniz, *path operation*'larınız için çerezlerin **dokümantasyonunu** görebilirsiniz. +`/docs` adresindeki **API docs UI**'a giderseniz, *path operation*'larınız için cookie'lerin **dokümantasyonunu** görebilirsiniz. -Ancak **veriyi doldurup** "Execute" düğmesine tıklasanız bile, docs UI **JavaScript** ile çalıştığı için çerezler gönderilmez ve herhangi bir değer yazmamışsınız gibi bir **hata** mesajı görürsünüz. +Ancak **veriyi doldurup** "Execute" düğmesine tıklasanız bile, docs UI **JavaScript** ile çalıştığı için cookie'ler gönderilmez ve herhangi bir değer yazmamışsınız gibi bir **hata** mesajı görürsünüz. /// ## Özet { #recap } -`Query` ve `Path` ile aynı ortak deseni kullanarak, çerezleri `Cookie` ile tanımlayın. +`Query` ve `Path` ile aynı ortak deseni kullanarak, cookie'leri `Cookie` ile tanımlayın. diff --git a/docs/tr/docs/tutorial/cors.md b/docs/tr/docs/tutorial/cors.md index aae560022..c3c853fdd 100644 --- a/docs/tr/docs/tutorial/cors.md +++ b/docs/tr/docs/tutorial/cors.md @@ -46,7 +46,7 @@ Ayrıca backend’in şunlara izin verip vermediğini de belirtebilirsiniz: * Belirli HTTP method’ları (`POST`, `PUT`) veya wildcard `"*"` ile hepsini. * Belirli HTTP header’ları veya wildcard `"*"` ile hepsini. -{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} `CORSMiddleware` implementasyonu tarafından kullanılan varsayılan parametreler kısıtlayıcıdır; bu nedenle tarayıcıların Cross-Domain bağlamında kullanmasına izin vermek için belirli origin’leri, method’ları veya header’ları açıkça etkinleştirmeniz gerekir. @@ -78,7 +78,7 @@ Bu durumda middleware gelen request’i intercept eder ve uygun CORS header’la ## Daha Fazla Bilgi { #more-info } -CORS hakkında daha fazla bilgi için Mozilla CORS dokümantasyonuna bakın. +CORS hakkında daha fazla bilgi için Mozilla CORS dokümantasyonuna bakın. /// note | Teknik Detaylar diff --git a/docs/tr/docs/tutorial/debugging.md b/docs/tr/docs/tutorial/debugging.md index 54d5c9252..839277b6d 100644 --- a/docs/tr/docs/tutorial/debugging.md +++ b/docs/tr/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ Visual Studio Code veya PyCharm gibi editörünüzde debugger'ı bağlayabilirsi FastAPI uygulamanızda `uvicorn`'ı import edip doğrudan çalıştırın: -{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} ### `__name__ == "__main__"` Hakkında { #about-name-main } diff --git a/docs/tr/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/tr/docs/tutorial/dependencies/classes-as-dependencies.md index 9ee57cb29..989e93b40 100644 --- a/docs/tr/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/tr/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ Artık bu class'ı kullanarak dependency'nizi tanımlayabilirsiniz. Yukarıdaki kodda `CommonQueryParams`'ı iki kez yazdığımıza dikkat edin: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ Annotated Olmadan +//// tab | Python 3.10+ Annotated Olmadan /// tip | İpucu @@ -137,7 +137,7 @@ FastAPI tanımlanan parametreleri buradan çıkarır ve aslında çağıracağı Bu durumda, şuradaki ilk `CommonQueryParams`: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.9+ Annotated Olmadan +//// tab | Python 3.10+ Annotated Olmadan /// tip | İpucu @@ -163,7 +163,7 @@ commons: CommonQueryParams ... Hatta şunu bile yazabilirsiniz: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ Annotated Olmadan +//// tab | Python 3.10+ Annotated Olmadan /// tip | İpucu @@ -197,7 +197,7 @@ Ancak type'ı belirtmeniz önerilir; böylece editor'ünüz `commons` parametres Ama burada bir miktar kod tekrarımız olduğunu görüyorsunuz; `CommonQueryParams`'ı iki kez yazıyoruz: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ Annotated Olmadan +//// tab | Python 3.10+ Annotated Olmadan /// tip | İpucu @@ -225,7 +225,7 @@ Bu özel durumlarda şunu yapabilirsiniz: Şunu yazmak yerine: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ Annotated Olmadan +//// tab | Python 3.10+ Annotated Olmadan /// tip | İpucu @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...şunu yazarsınız: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.9+ Annotated Olmadan +//// tab | Python 3.10+ Annotated Olmadan /// tip | İpucu diff --git a/docs/tr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/tr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 4903aec4a..cbb636342 100644 --- a/docs/tr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/tr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -14,7 +14,7 @@ Bu gibi durumlarda, `Depends` ile bir *path operation function* parametresi tan Bu, `Depends()` öğelerinden oluşan bir `list` olmalıdır: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} Bu dependency'ler normal dependency'lerle aynı şekilde çalıştırılır/çözülür. Ancak (eğer bir değer döndürüyorlarsa) bu değer *path operation function*'ınıza aktarılmaz. @@ -44,13 +44,13 @@ Normalde kullandığınız aynı dependency *function*'larını burada da kullan Request gereksinimleri (header'lar gibi) veya başka alt dependency'ler tanımlayabilirler: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} ### Exception Fırlatmak { #raise-exceptions } Bu dependency'ler, normal dependency'lerde olduğu gibi `raise` ile exception fırlatabilir: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} ### Return Değerleri { #return-values } @@ -58,7 +58,7 @@ Ayrıca değer döndürebilirler ya da döndürmeyebilirler; dönen değer kulla Yani başka bir yerde zaten kullandığınız, değer döndüren normal bir dependency'yi tekrar kullanabilirsiniz; değer kullanılmasa bile dependency çalıştırılacaktır: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} ## Bir *Path Operation* Grubu İçin Dependency'ler { #dependencies-for-a-group-of-path-operations } diff --git a/docs/tr/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/tr/docs/tutorial/dependencies/dependencies-with-yield.md index bd025f799..445110ae0 100644 --- a/docs/tr/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/tr/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,12 +1,12 @@ # `yield` ile Dependency'ler { #dependencies-with-yield } -FastAPI, işini bitirdikten sonra ek adımlar çalıştıran dependency'leri destekler. +FastAPI, işini bitirdikten sonra ek adımlar çalıştıran dependency'leri destekler. Bunu yapmak için `return` yerine `yield` kullanın ve ek adımları (kodu) `yield` satırından sonra yazın. /// tip | İpucu -Her dependency için yalnızca **bir kez** `yield` kullandığınızdan emin olun. +Her dependency için yalnızca bir kez `yield` kullandığınızdan emin olun. /// @@ -29,15 +29,15 @@ Hatta FastAPI bu iki decorator'ı içeride (internally) kullanır. Response oluşturulmadan önce yalnızca `yield` satırına kadar olan (ve `yield` dahil) kod çalıştırılır: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[2:4] *} `yield` edilen değer, *path operation*'lara ve diğer dependency'lere enjekte edilen (injected) değerdir: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[4] *} Response'dan sonra `yield` satırını takip eden kod çalıştırılır: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[5:6] *} /// tip | İpucu @@ -57,7 +57,7 @@ Dolayısıyla `except SomeException` ile dependency içinde o spesifik exception Aynı şekilde, exception olsun ya da olmasın çıkış (exit) adımlarının çalıştırılmasını garanti etmek için `finally` kullanabilirsiniz. -{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[3,5] *} ## `yield` ile Alt Dependency'ler { #sub-dependencies-with-yield } @@ -67,7 +67,7 @@ Her boyutta ve şekilde alt dependency'ler ve alt dependency "ağaçları" (tree Örneğin, `dependency_c`, `dependency_b`'ye; `dependency_b` de `dependency_a`'ya bağlı olabilir: -{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[6,14,22] *} Ve hepsi `yield` kullanabilir. @@ -75,7 +75,7 @@ Bu durumda `dependency_c`, exit code'unu çalıştırabilmek için `dependency_b Aynı şekilde `dependency_b` de exit code'u için `dependency_a`'dan gelen değerin (burada `dep_a`) erişilebilir olmasına ihtiyaç duyar. -{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *} +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[18:19,26:27] *} Benzer şekilde, bazı dependency'ler `yield`, bazıları `return` kullanabilir ve bunların bazıları diğerlerine bağlı olabilir. @@ -109,7 +109,7 @@ Ama ihtiyaç duyarsanız diye burada. 🤓 /// -{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *} +{* ../../docs_src/dependencies/tutorial008b_an_py310.py hl[18:22,31] *} Exception yakalayıp buna göre özel bir response oluşturmak istiyorsanız bir [Custom Exception Handler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} oluşturun. @@ -117,7 +117,7 @@ Exception yakalayıp buna göre özel bir response oluşturmak istiyorsanız bir `yield` kullanan bir dependency içinde `except` ile bir exception yakalar ve bunu tekrar fırlatmazsanız (ya da yeni bir exception fırlatmazsanız), FastAPI normal Python'da olduğu gibi bir exception olduğunu fark edemez: -{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} +{* ../../docs_src/dependencies/tutorial008c_an_py310.py hl[15:16] *} Bu durumda client, biz `HTTPException` veya benzeri bir şey fırlatmadığımız için olması gerektiği gibi *HTTP 500 Internal Server Error* response'u görür; ancak server **hiç log üretmez** ve hatanın ne olduğuna dair başka bir işaret de olmaz. 😱 @@ -127,7 +127,7 @@ Bu durumda client, biz `HTTPException` veya benzeri bir şey fırlatmadığımı Aynı exception'ı `raise` ile tekrar fırlatabilirsiniz: -{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *} +{* ../../docs_src/dependencies/tutorial008d_an_py310.py hl[17] *} Artık client yine aynı *HTTP 500 Internal Server Error* response'unu alır, ama server log'larda bizim özel `InternalError`'ımızı görür. 😎 @@ -190,7 +190,7 @@ Normalde `yield` kullanan dependency'lerin exit code'u, client'a response gönde Ama *path operation function*'dan döndükten sonra dependency'yi kullanmayacağınızı biliyorsanız, `Depends(scope="function")` kullanarak FastAPI'ye dependency'yi *path operation function* döndükten sonra kapatmasını, ancak **response gönderilmeden önce** kapatmasını söyleyebilirsiniz. -{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *} +{* ../../docs_src/dependencies/tutorial008e_an_py310.py hl[12,16] *} `Depends()` şu değerleri alabilen bir `scope` parametresi alır: @@ -234,7 +234,6 @@ participant operation as Path Operation `yield` kullanan dependency'ler, zaman içinde farklı kullanım senaryolarını kapsamak ve bazı sorunları düzeltmek için gelişti. FastAPI'nin farklı sürümlerinde nelerin değiştiğini görmek isterseniz, advanced guide'da şu bölümü okuyabilirsiniz: [Advanced Dependencies - Dependencies with `yield`, `HTTPException`, `except` and Background Tasks](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}. - ## Context Managers { #context-managers } ### "Context Managers" Nedir? { #what-are-context-managers } @@ -269,7 +268,7 @@ Python'da Context Manager'ları, Dependency Injection** sistemine sahiptir. +**FastAPI**, çok güçlü ama aynı zamanda sezgisel bir **Bağımlılık Enjeksiyonu** sistemine sahiptir. Kullanımı çok basit olacak şekilde tasarlanmıştır ve herhangi bir geliştiricinin diğer bileşenleri **FastAPI** ile entegre etmesini kolaylaştırır. diff --git a/docs/tr/docs/tutorial/dependencies/sub-dependencies.md b/docs/tr/docs/tutorial/dependencies/sub-dependencies.md index 184db839b..ab196d829 100644 --- a/docs/tr/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/tr/docs/tutorial/dependencies/sub-dependencies.md @@ -58,11 +58,11 @@ query_extractor --> query_or_cookie_extractor --> read_query Bağımlılıklarınızdan biri aynı *path operation* için birden fazla kez tanımlanırsa (örneğin birden fazla bağımlılığın ortak bir alt bağımlılığı varsa), **FastAPI** o alt bağımlılığı request başına yalnızca bir kez çağıracağını bilir. -Dönen değeri bir "cache" içinde saklar ve aynı request içinde buna ihtiyaç duyan tüm "dependant"lara aktarır; böylece aynı request için bağımlılığı tekrar tekrar çağırmaz. +Dönen değeri bir "önbellek" içinde saklar ve aynı request içinde buna ihtiyaç duyan tüm "dependant"lara aktarır; böylece aynı request için bağımlılığı tekrar tekrar çağırmaz. Daha ileri bir senaryoda, "cached" değeri kullanmak yerine aynı request içinde her adımda (muhtemelen birden fazla kez) bağımlılığın çağrılması gerektiğini biliyorsanız, `Depends` kullanırken `use_cache=False` parametresini ayarlayabilirsiniz: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python hl_lines="1" async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): @@ -71,7 +71,7 @@ async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_ca //// -//// tab | Python 3.9+ Annotated olmayan +//// tab | Python 3.10+ Annotated olmayan /// tip | İpucu diff --git a/docs/tr/docs/tutorial/extra-data-types.md b/docs/tr/docs/tutorial/extra-data-types.md index 464d3a82a..534efefc4 100644 --- a/docs/tr/docs/tutorial/extra-data-types.md +++ b/docs/tr/docs/tutorial/extra-data-types.md @@ -23,32 +23,32 @@ Kullanabileceğiniz ek veri tiplerinden bazıları şunlardır: * `UUID`: * Birçok veritabanı ve sistemde ID olarak yaygın kullanılan, standart bir "Universally Unique Identifier". - * request ve response'larda `str` olarak temsil edilir. + * request'lerde ve response'larda `str` olarak temsil edilir. * `datetime.datetime`: * Python `datetime.datetime`. - * request ve response'larda ISO 8601 formatında bir `str` olarak temsil edilir, örn: `2008-09-15T15:53:00+05:00`. + * request'lerde ve response'larda ISO 8601 formatında bir `str` olarak temsil edilir, örn: `2008-09-15T15:53:00+05:00`. * `datetime.date`: * Python `datetime.date`. - * request ve response'larda ISO 8601 formatında bir `str` olarak temsil edilir, örn: `2008-09-15`. + * request'lerde ve response'larda ISO 8601 formatında bir `str` olarak temsil edilir, örn: `2008-09-15`. * `datetime.time`: * Python `datetime.time`. - * request ve response'larda ISO 8601 formatında bir `str` olarak temsil edilir, örn: `14:23:55.003`. + * request'lerde ve response'larda ISO 8601 formatında bir `str` olarak temsil edilir, örn: `14:23:55.003`. * `datetime.timedelta`: * Python `datetime.timedelta`. - * request ve response'larda toplam saniye sayısını ifade eden bir `float` olarak temsil edilir. + * request'lerde ve response'larda toplam saniye sayısını ifade eden bir `float` olarak temsil edilir. * Pydantic, bunu ayrıca bir "ISO 8601 time diff encoding" olarak temsil etmeye de izin verir, daha fazla bilgi için dokümanlara bakın. * `frozenset`: - * request ve response'larda, `set` ile aynı şekilde ele alınır: + * request'lerde ve response'larda, `set` ile aynı şekilde ele alınır: * request'lerde bir list okunur, tekrarlar kaldırılır ve `set`'e dönüştürülür. * response'larda `set`, `list`'e dönüştürülür. * Üretilen schema, `set` değerlerinin benzersiz olduğunu belirtir (JSON Schema'nın `uniqueItems` özelliğini kullanarak). * `bytes`: * Standart Python `bytes`. - * request ve response'larda `str` gibi ele alınır. + * request'lerde ve response'larda `str` gibi ele alınır. * Üretilen schema, bunun `binary` "format"ına sahip bir `str` olduğunu belirtir. * `Decimal`: * Standart Python `Decimal`. - * request ve response'larda `float` ile aynı şekilde işlenir. + * request'lerde ve response'larda `float` ile aynı şekilde işlenir. * Geçerli tüm Pydantic veri tiplerini burada görebilirsiniz: Pydantic data types. ## Örnek { #example } diff --git a/docs/tr/docs/tutorial/extra-models.md b/docs/tr/docs/tutorial/extra-models.md index 9aae28e05..f2bcf7a01 100644 --- a/docs/tr/docs/tutorial/extra-models.md +++ b/docs/tr/docs/tutorial/extra-models.md @@ -8,7 +8,7 @@ Bu durum özellikle kullanıcı modellerinde sık görülür, çünkü: * **output modeli** `password` içermemelidir. * **database modeli** büyük ihtimalle hash'lenmiş bir `password` tutmalıdır. -/// danger +/// danger | Tehlike Kullanıcının düz metin (plaintext) `password`'ünü asla saklamayın. Her zaman sonradan doğrulayabileceğiniz "güvenli bir hash" saklayın. @@ -132,7 +132,7 @@ UserInDB( ) ``` -/// warning +/// warning | Uyarı Ek destek fonksiyonları olan `fake_password_hasher` ve `fake_save_user` sadece verinin olası bir akışını göstermek içindir; elbette gerçek bir güvenlik sağlamazlar. @@ -164,7 +164,7 @@ OpenAPI'de bu `anyOf` ile tanımlanır. Bunu yapmak için standart Python type hint'i olan `typing.Union`'ı kullanın: -/// note +/// note | Not Bir `Union` tanımlarken en spesifik type'ı önce, daha az spesifik olanı sonra ekleyin. Aşağıdaki örnekte daha spesifik olan `PlaneItem`, `Union[PlaneItem, CarItem]` içinde `CarItem`'dan önce gelir. @@ -190,9 +190,9 @@ Ancak bunu `response_model=PlaneItem | CarItem` atamasına koyarsak hata alırı Aynı şekilde, nesne listesi döndüren response'ları da tanımlayabilirsiniz. -Bunun için standart Python `typing.List`'i (ya da Python 3.9 ve üzeri için sadece `list`) kullanın: +Bunun için standart Python `list`'i kullanın: -{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} +{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *} ## Rastgele `dict` ile Response { #response-with-arbitrary-dict } @@ -200,9 +200,9 @@ Bir Pydantic modeli kullanmadan, sadece key ve value type'larını belirterek d Bu, geçerli field/attribute adlarını (Pydantic modeli için gerekli olurdu) önceden bilmiyorsanız kullanışlıdır. -Bu durumda `typing.Dict` (ya da Python 3.9 ve üzeri için sadece `dict`) kullanabilirsiniz: +Bu durumda `dict` kullanabilirsiniz: -{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} +{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *} ## Özet { #recap } diff --git a/docs/tr/docs/tutorial/first-steps.md b/docs/tr/docs/tutorial/first-steps.md index 332f5c559..4b645778f 100644 --- a/docs/tr/docs/tutorial/first-steps.md +++ b/docs/tr/docs/tutorial/first-steps.md @@ -2,7 +2,7 @@ En sade FastAPI dosyası şu şekilde görünür: -{* ../../docs_src/first_steps/tutorial001_py39.py *} +{* ../../docs_src/first_steps/tutorial001_py310.py *} Yukarıdakini `main.py` adlı bir dosyaya kopyalayın. @@ -183,7 +183,7 @@ Bu kadar! Artık uygulamanıza o URL üzerinden erişebilirsiniz. ✨ ### Adım 1: `FastAPI` import edin { #step-1-import-fastapi } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[1] *} `FastAPI`, API'nız için tüm işlevselliği sağlayan bir Python class'ıdır. @@ -197,7 +197,7 @@ Bu kadar! Artık uygulamanıza o URL üzerinden erişebilirsiniz. ✨ ### Adım 2: bir `FastAPI` "instance"ı oluşturun { #step-2-create-a-fastapi-instance } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[3] *} Burada `app` değişkeni `FastAPI` class'ının bir "instance"ı olacaktır. @@ -266,12 +266,12 @@ Biz de bunlara "**operation**" diyeceğiz. #### Bir *path operation decorator* tanımlayın { #define-a-path-operation-decorator } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[6] *} `@app.get("/")`, **FastAPI**'a hemen altındaki fonksiyonun şuraya giden request'leri ele almakla sorumlu olduğunu söyler: * path `/` -* get operation kullanarak +* get operation kullanarak /// info | `@decorator` Bilgisi @@ -320,7 +320,7 @@ Bu bizim "**path operation function**"ımız: * **operation**: `get`. * **function**: "decorator"ün altındaki fonksiyondur (`@app.get("/")`'in altındaki). -{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[7] *} Bu bir Python fonksiyonudur. @@ -332,7 +332,7 @@ Bu durumda, bu bir `async` fonksiyondur. Bunu `async def` yerine normal bir fonksiyon olarak da tanımlayabilirsiniz: -{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py310.py hl[7] *} /// note | Not @@ -342,7 +342,7 @@ Eğer farkı bilmiyorsanız, [Async: *"Aceleniz mi var?"*](../async.md#in-a-hurr ### Adım 5: içeriği döndürün { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[8] *} Bir `dict`, `list`, `str`, `int` vb. tekil değerler döndürebilirsiniz. diff --git a/docs/tr/docs/tutorial/handling-errors.md b/docs/tr/docs/tutorial/handling-errors.md index a4d693792..a74e1a76a 100644 --- a/docs/tr/docs/tutorial/handling-errors.md +++ b/docs/tr/docs/tutorial/handling-errors.md @@ -25,7 +25,7 @@ Client’a hata içeren HTTP response’ları döndürmek için `HTTPException` ### `HTTPException`’ı Import Etme { #import-httpexception } -{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *} +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[1] *} ### Kodunuzda Bir `HTTPException` Raise Etme { #raise-an-httpexception-in-your-code } @@ -39,7 +39,7 @@ Bir değer döndürmek yerine exception raise etmenin faydası, Dependencies ve Bu örnekte, client var olmayan bir ID ile bir item istediğinde, `404` status code’u ile bir exception raise edelim: -{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *} +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[11] *} ### Ortaya Çıkan Response { #the-resulting-response } @@ -77,7 +77,7 @@ Muhtemelen bunu doğrudan kendi kodunuzda kullanmanız gerekmeyecek. Ama ileri seviye bir senaryo için ihtiyaç duyarsanız, özel header’lar ekleyebilirsiniz: -{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial002_py310.py hl[14] *} ## Özel Exception Handler’ları Kurmak { #install-custom-exception-handlers } @@ -89,7 +89,7 @@ Ve bu exception’ı FastAPI ile global olarak handle etmek istiyorsunuz. `@app.exception_handler()` ile özel bir exception handler ekleyebilirsiniz: -{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *} +{* ../../docs_src/handling_errors/tutorial003_py310.py hl[5:7,13:18,24] *} Burada `/unicorns/yolo` için request atarsanız, *path operation* bir `UnicornException` `raise` eder. @@ -127,7 +127,7 @@ Override etmek için `RequestValidationError`’ı import edin ve exception hand Exception handler, bir `Request` ve exception’ı alır. -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[2,14:19] *} Artık `/items/foo`’ya giderseniz, şu varsayılan JSON hatası yerine: @@ -159,7 +159,7 @@ Benzer şekilde `HTTPException` handler’ını da override edebilirsiniz. Örneğin bu hatalar için JSON yerine plain text response döndürmek isteyebilirsiniz: -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[3:4,9:11,25] *} /// note | Teknik Detaylar @@ -183,7 +183,7 @@ Ancak bu, eğer sadece string’e çevirip bu bilgiyi doğrudan response olarak Uygulamanızı geliştirirken body’yi log’lamak, debug etmek, kullanıcıya döndürmek vb. için bunu kullanabilirsiniz. -{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial005_py310.py hl[14] *} Şimdi şu gibi geçersiz bir item göndermeyi deneyin: @@ -239,6 +239,6 @@ from starlette.exceptions import HTTPException as StarletteHTTPException Exception’ı, **FastAPI**’nin aynı varsayılan exception handler’larıyla birlikte kullanmak isterseniz, varsayılan exception handler’ları `fastapi.exception_handlers` içinden import edip yeniden kullanabilirsiniz: -{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *} +{* ../../docs_src/handling_errors/tutorial006_py310.py hl[2:5,15,21] *} Bu örnekte sadece oldukça açıklayıcı bir mesajla hatayı yazdırıyorsunuz; ama fikir anlaşılıyor. Exception’ı kullanıp ardından varsayılan exception handler’ları olduğu gibi yeniden kullanabilirsiniz. diff --git a/docs/tr/docs/tutorial/index.md b/docs/tr/docs/tutorial/index.md index f672c9e20..6047e83ca 100644 --- a/docs/tr/docs/tutorial/index.md +++ b/docs/tr/docs/tutorial/index.md @@ -62,7 +62,7 @@ Editörünüzde kullanmak FastAPI'nin avantajlarını gerçekten gösterir: ne k İlk adım FastAPI'yi kurmaktır. -Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan emin olun, etkinleştirin ve ardından **FastAPI'yi kurun**: +Bir [sanal ortam](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan emin olun, etkinleştirin ve ardından **FastAPI'yi kurun**:
diff --git a/docs/tr/docs/tutorial/metadata.md b/docs/tr/docs/tutorial/metadata.md index dacd68cf5..0cc5adee4 100644 --- a/docs/tr/docs/tutorial/metadata.md +++ b/docs/tr/docs/tutorial/metadata.md @@ -18,7 +18,7 @@ OpenAPI spesifikasyonunda ve otomatik API doküman arayüzlerinde kullanılan ş Şu şekilde ayarlayabilirsiniz: -{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} +{* ../../docs_src/metadata/tutorial001_py310.py hl[3:16, 19:32] *} /// tip | İpucu @@ -36,7 +36,7 @@ OpenAPI 3.1.0 ve FastAPI 0.99.0 sürümünden itibaren, `license_info` içinde ` Örneğin: -{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} +{* ../../docs_src/metadata/tutorial001_1_py310.py hl[31] *} ## Tag'ler için Metadata { #metadata-for-tags } @@ -58,7 +58,7 @@ Her sözlük şunları içerebilir: Tag'leriniz için metadata oluşturup `openapi_tags` parametresine geçin: -{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[3:16,18] *} Açıklamaların içinde Markdown kullanabileceğinizi unutmayın; örneğin "login" kalın (**login**) ve "fancy" italik (_fancy_) olarak gösterilecektir. @@ -72,7 +72,7 @@ Kullandığınız tüm tag'ler için metadata eklemek zorunda değilsiniz. *path operation*'larınızı (ve `APIRouter`'ları) farklı tag'lere atamak için `tags` parametresini kullanın: -{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[21,26] *} /// info | Bilgi @@ -100,7 +100,7 @@ Ancak bunu `openapi_url` parametresiyle yapılandırabilirsiniz. Örneğin `/api/v1/openapi.json` adresinden sunulacak şekilde ayarlamak için: -{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py310.py hl[3] *} OpenAPI şemasını tamamen kapatmak isterseniz `openapi_url=None` ayarlayabilirsiniz; bu, onu kullanan dokümantasyon arayüzlerini de devre dışı bırakır. @@ -117,4 +117,4 @@ Dahil gelen iki dokümantasyon arayüzünü yapılandırabilirsiniz: Örneğin Swagger UI'yi `/documentation` adresinden sunup ReDoc'u kapatmak için: -{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial003_py310.py hl[3] *} diff --git a/docs/tr/docs/tutorial/middleware.md b/docs/tr/docs/tutorial/middleware.md index 68222265e..7ee0ef249 100644 --- a/docs/tr/docs/tutorial/middleware.md +++ b/docs/tr/docs/tutorial/middleware.md @@ -31,7 +31,7 @@ Middleware fonksiyonu şunları alır: * Ardından ilgili *path operation* tarafından üretilen `response`'u döndürür. * Sonrasında `response`'u döndürmeden önce ayrıca değiştirebilirsiniz. -{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[8:9,11,14] *} /// tip | İpucu @@ -57,7 +57,7 @@ Ayrıca `response` üretildikten sonra, geri döndürmeden önce de kod çalış Örneğin, request'i işleyip response üretmenin kaç saniye sürdüğünü içeren `X-Process-Time` adlı özel bir header ekleyebilirsiniz: -{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[10,12:13] *} /// tip | İpucu @@ -92,4 +92,4 @@ Bu stack davranışı, middleware'lerin öngörülebilir ve kontrol edilebilir b Diğer middleware'ler hakkında daha fazlasını daha sonra [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank} bölümünde okuyabilirsiniz. -Bir sonraki bölümde, middleware ile CORS'un nasıl ele alınacağını göreceksiniz. +Bir sonraki bölümde, middleware ile CORS'un nasıl ele alınacağını göreceksiniz. diff --git a/docs/tr/docs/tutorial/path-operation-configuration.md b/docs/tr/docs/tutorial/path-operation-configuration.md index 3fe24dc0a..c65d3696f 100644 --- a/docs/tr/docs/tutorial/path-operation-configuration.md +++ b/docs/tr/docs/tutorial/path-operation-configuration.md @@ -46,7 +46,7 @@ Bu durumlarda tag’leri bir `Enum` içinde tutmak mantıklı olabilir. **FastAPI** bunu düz string’lerde olduğu gibi aynı şekilde destekler: -{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *} +{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *} ## Özet ve açıklama { #summary-and-description } @@ -54,9 +54,9 @@ Bir `summary` ve `description` ekleyebilirsiniz: {* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} -## Docstring’den Description { #description-from-docstring } +## Docstring’den Açıklama { #description-from-docstring } -Açıklamalar genelde uzun olur ve birden fazla satıra yayılır; bu yüzden *path operation* açıklamasını, fonksiyonun içinde docstring olarak tanımlayabilirsiniz; **FastAPI** de onu buradan okur. +Açıklamalar genelde uzun olur ve birden fazla satıra yayılır; bu yüzden *path operation* açıklamasını, fonksiyonun içinde docstring olarak tanımlayabilirsiniz; **FastAPI** de onu buradan okur. Docstring içinde Markdown yazabilirsiniz; doğru şekilde yorumlanır ve gösterilir (docstring girintisi dikkate alınarak). @@ -90,9 +90,9 @@ Bu yüzden siz sağlamazsanız, **FastAPI** otomatik olarak "Successful response ## Bir *path operation*’ı Deprecate Etmek { #deprecate-a-path-operation } -Bir *path operation*’ı kaldırmadan, deprecated olarak işaretlemeniz gerekiyorsa `deprecated` parametresini verin: +Bir *path operation*’ı kaldırmadan, deprecated olarak işaretlemeniz gerekiyorsa `deprecated` parametresini verin: -{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} Interactive docs’ta deprecated olduğu net şekilde işaretlenecektir: diff --git a/docs/tr/docs/tutorial/path-params-numeric-validations.md b/docs/tr/docs/tutorial/path-params-numeric-validations.md index e0118d71d..63506a1f2 100644 --- a/docs/tr/docs/tutorial/path-params-numeric-validations.md +++ b/docs/tr/docs/tutorial/path-params-numeric-validations.md @@ -54,11 +54,11 @@ Bu **FastAPI** için önemli değildir. FastAPI parametreleri isimlerine, tipler Dolayısıyla fonksiyonunuzu şöyle tanımlayabilirsiniz: -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} Ancak şunu unutmayın: `Annotated` kullanırsanız bu problem olmaz; çünkü `Query()` veya `Path()` için fonksiyon parametresi default değerlerini kullanmıyorsunuz. -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## Parametreleri İhtiyacınıza Göre Sıralayın: Küçük Hileler { #order-the-parameters-as-you-need-tricks } @@ -81,15 +81,15 @@ Ancak şunu unutmayın: `Annotated` kullanırsanız bu problem olmaz; çünkü ` Fonksiyonun ilk parametresi olarak `*` geçin. -Python bu `*` ile bir şey yapmaz; ama bundan sonraki tüm parametrelerin keyword argument (anahtar-değer çiftleri) olarak çağrılması gerektiğini bilir; buna kwargs da denir. Default değerleri olmasa bile. +Python bu `*` ile bir şey yapmaz; ama bundan sonraki tüm parametrelerin keyword argument (anahtar-değer çiftleri) olarak çağrılması gerektiğini bilir; buna kwargs da denir. Default değerleri olmasa bile. -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### `Annotated` ile Daha İyi { #better-with-annotated } Şunu da unutmayın: `Annotated` kullanırsanız, fonksiyon parametresi default değerlerini kullanmadığınız için bu sorun ortaya çıkmaz ve muhtemelen `*` kullanmanız da gerekmez. -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## Sayı Doğrulamaları: Büyük Eşit { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ Python bu `*` ile bir şey yapmaz; ama bundan sonraki tüm parametrelerin keywor Burada `ge=1` ile, `item_id` değerinin `1`'den "`g`reater than or `e`qual" olacak şekilde bir tam sayı olması gerekir. -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## Sayı Doğrulamaları: Büyük ve Küçük Eşit { #number-validations-greater-than-and-less-than-or-equal } @@ -106,19 +106,19 @@ Aynısı şunlar için de geçerlidir: * `gt`: `g`reater `t`han * `le`: `l`ess than or `e`qual -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## Sayı Doğrulamaları: `float` Değerler, Büyük ve Küçük { #number-validations-floats-greater-than-and-less-than } Sayı doğrulamaları `float` değerler için de çalışır. -Burada gt tanımlayabilmek (sadece ge değil) önemli hale gelir. Çünkü örneğin bir değerin `0`'dan büyük olmasını isteyebilirsiniz; `1`'den küçük olsa bile. +Burada gt tanımlayabilmek (sadece ge değil) önemli hale gelir. Çünkü örneğin bir değerin `0`'dan büyük olmasını isteyebilirsiniz; `1`'den küçük olsa bile. Bu durumda `0.5` geçerli bir değer olur. Ancak `0.0` veya `0` geçerli olmaz. -Aynısı lt için de geçerlidir. +Aynısı lt için de geçerlidir. -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## Özet { #recap } diff --git a/docs/tr/docs/tutorial/path-params.md b/docs/tr/docs/tutorial/path-params.md index db676f1ee..cbcec05d4 100644 --- a/docs/tr/docs/tutorial/path-params.md +++ b/docs/tr/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Python string biçimlemede kullanılan sözdizimiyle path "parametreleri"ni veya "değişkenleri"ni tanımlayabilirsiniz: -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} Path parametresi `item_id`'nin değeri, fonksiyonunuza `item_id` argümanı olarak aktarılacaktır. @@ -16,7 +16,7 @@ Yani, bu örneği çalıştırıp conversion { #data-conversion } +## Veri dönüştürme { #data-conversion } Bu örneği çalıştırıp tarayıcınızda http://127.0.0.1:8000/items/3 adresini açarsanız, şöyle bir response görürsünüz: @@ -38,7 +38,7 @@ Bu örneği çalıştırıp tarayıcınızda "parsing" sağlar. +Yani, bu tip tanımıyla birlikte **FastAPI** size otomatik request "ayrıştırma" sağlar. /// @@ -118,13 +118,13 @@ Sonra belirli bir kullanıcı hakkında, kullanıcı ID'si ile veri almak için *Path operation*'lar sırayla değerlendirildiği için, `/users/me` için olan path'in `/users/{user_id}` olandan önce tanımlandığından emin olmanız gerekir: -{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *} Aksi halde, `/users/{user_id}` için olan path, `"me"` değerini `user_id` parametresi olarak aldığını "düşünerek" `/users/me` için de eşleşir. Benzer şekilde, bir path operation'ı yeniden tanımlayamazsınız: -{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *} Path önce eşleştiği için her zaman ilk olan kullanılır. @@ -140,11 +140,11 @@ Bir *path operation*'ınız *path parameter* alıyorsa ama olası geçerli *path Sonra, kullanılabilir geçerli değerler olacak sabit değerli class attribute'ları oluşturun: -{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *} /// tip | İpucu -Merak ediyorsanız: "AlexNet", "ResNet" ve "LeNet", Makine Öğrenmesi modellerinin sadece isimleridir. +Merak ediyorsanız: "AlexNet", "ResNet" ve "LeNet", Makine Öğrenmesi modellerinin sadece isimleridir. /// @@ -152,7 +152,7 @@ Merak ediyorsanız: "AlexNet", "ResNet" ve "LeNet", Makine Öğrenmesi parsing" +* Veri "ayrıştırma" * Veri doğrulama * API annotation ve otomatik dokümantasyon diff --git a/docs/tr/docs/tutorial/query-params-str-validations.md b/docs/tr/docs/tutorial/query-params-str-validations.md index 18f0249e5..3bdbd7aae 100644 --- a/docs/tr/docs/tutorial/query-params-str-validations.md +++ b/docs/tr/docs/tutorial/query-params-str-validations.md @@ -16,7 +16,7 @@ FastAPI, `q`’nun zorunlu olmadığını `= None` varsayılan değerinden anlar /// -## Ek doğrulama { #additional-validation } +## Ek Doğrulama { #additional-validation } `q` opsiyonel olsa bile, verildiği durumda **uzunluğunun 50 karakteri geçmemesini** zorlayacağız. @@ -47,40 +47,16 @@ Daha eski bir sürüm kullanıyorsanız `Annotated` kullanmaya çalışırken ha Şu tip anotasyonuna sahiptik: -//// tab | Python 3.10+ - ```Python q: str | None = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Union[str, None] = None -``` - -//// - Şimdi bunu `Annotated` ile saracağız; şöyle olacak: -//// tab | Python 3.10+ - ```Python q: Annotated[str | None] = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Annotated[Union[str, None]] = None -``` - -//// - Bu iki sürüm de aynı anlama gelir: `q`, `str` veya `None` olabilen bir parametredir ve varsayılan olarak `None`’dır. Şimdi işin eğlenceli kısmına geçelim. 🎉 @@ -109,7 +85,7 @@ FastAPI artık şunları yapacak: ## Alternatif (eski): Varsayılan değer olarak `Query` { #alternative-old-query-as-the-default-value } -FastAPI’nin önceki sürümlerinde (0.95.0 öncesi) `Query`’yi `Annotated` içine koymak yerine, parametrenizin varsayılan değeri olarak kullanmanız gerekiyordu. Etrafta bu şekilde yazılmış kod görme ihtimaliniz yüksek; bu yüzden açıklayalım. +FastAPI’nin önceki sürümlerinde ( 0.95.0 öncesi) `Query`’yi `Annotated` içine koymak yerine, parametrenizin varsayılan değeri olarak kullanmanız gerekiyordu. Etrafta bu şekilde yazılmış kod görme ihtimaliniz yüksek; bu yüzden açıklayalım. /// tip | İpucu @@ -131,6 +107,7 @@ q: str | None = Query(default=None) ...parametreyi `None` varsayılan değeriyle opsiyonel yapar; şununla aynı: + ```Python q: str | None = None ``` @@ -179,7 +156,7 @@ Fonksiyon parametrelerindeki varsayılan değer stiline göre **`Annotated` kull Aynı fonksiyonu FastAPI olmadan **başka yerlerde** de **çağırabilirsiniz** ve **beklendiği gibi çalışır**. Eğer **zorunlu** bir parametre varsa (varsayılan değer yoksa) editörünüz hata ile bunu belirtir; ayrıca gerekli parametreyi vermeden çalıştırırsanız **Python** da şikayet eder. -`Annotated` kullanmayıp bunun yerine **(eski) varsayılan değer stilini** kullanırsanız, o fonksiyonu FastAPI olmadan **başka yerlerde** çağırdığınızda doğru çalışması için argümanları geçmeniz gerektiğini **hatırlamak** zorunda kalırsınız; yoksa değerler beklediğinizden farklı olur (ör. `str` yerine `QueryInfo` veya benzeri). Üstelik editörünüz de şikayet etmez ve Python da fonksiyonu çalıştırırken şikayet etmez; ancak içerideki operasyonlar hata verince ortaya çıkar. +`Annotated` kullanmayıp bunun yerine **(eski) varsayılan değer stilini** kullanırsanız, o fonksiyonu FastAPI olmadan **başka yerlerde** çağırdığınızda doğru çalışması için argümanları geçmeniz gerektiğini **hatırlamak** zorunda kalırsınız; yoksa değerler beklediğinizden farklı olur (ör. `QueryInfo` veya benzeri). Üstelik editörünüz de şikayet etmez ve Python da fonksiyonu çalıştırırken şikayet etmez; ancak içerideki operasyonlar hata verince ortaya çıkar. `Annotated` birden fazla metadata anotasyonu alabildiği için, artık aynı fonksiyonu Typer gibi başka araçlarla da kullanabilirsiniz. 🚀 @@ -191,7 +168,7 @@ Aynı fonksiyonu FastAPI olmadan **başka yerlerde** de **çağırabilirsiniz** ## Regular expression ekleyin { #add-regular-expressions } -Parametrenin eşleşmesi gereken bir `pattern` regular expression tanımlayabilirsiniz: +Parametrenin eşleşmesi gereken bir `pattern` düzenli ifade tanımlayabilirsiniz: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -211,7 +188,7 @@ Elbette `None` dışında varsayılan değerler de kullanabilirsiniz. Örneğin `q` query parametresi için `min_length` değerini `3` yapmak ve varsayılan değer olarak `"fixedquery"` vermek istediğinizi düşünelim: -{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} /// note | Not @@ -233,7 +210,7 @@ q: str q: str | None = None ``` -Ancak biz artık `Query` ile tanımlıyoruz; örneğin şöyle: +Acak biz artık `Query` ile tanımlıyoruz; örneğin şöyle: ```Python q: Annotated[str | None, Query(min_length=3)] = None @@ -241,7 +218,7 @@ q: Annotated[str | None, Query(min_length=3)] = None Dolayısıyla `Query` kullanırken bir değeri zorunlu yapmak istediğinizde, varsayılan değer tanımlamamanız yeterlidir: -{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} ### Zorunlu ama `None` olabilir { #required-can-be-none } @@ -292,7 +269,7 @@ Etkileşimli API dokümanları da buna göre güncellenir ve birden fazla değer Hiç değer verilmezse varsayılan bir `list` de tanımlayabilirsiniz: -{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} Şu adrese giderseniz: @@ -315,7 +292,7 @@ http://localhost:8000/items/ `list[str]` yerine doğrudan `list` de kullanabilirsiniz: -{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} /// note | Not @@ -371,7 +348,7 @@ O zaman bir `alias` tanımlayabilirsiniz; bu alias, parametre değerini bulmak i Diyelim ki artık bu parametreyi istemiyorsunuz. -Bazı client’lar hâlâ kullandığı için bir süre tutmanız gerekiyor, ama dokümanların bunu açıkça deprecated olarak göstermesini istiyorsunuz. +Bazı client’lar hâlâ kullandığı için bir süre tutmanız gerekiyor, ama dokümanların bunu açıkça deprecated olarak göstermesini istiyorsunuz. O zaman `Query`’ye `deprecated=True` parametresini geçin: @@ -401,7 +378,7 @@ Pydantic’te ISBN kitap numarası için `isbn-` ile veya IMDB film URL ID’si için `imdb-` ile başladığını kontrol eder: +Örneğin bu custom validator, bir item ID’sinin ISBN kitap numarası için `isbn-` ile veya IMDB film URL ID’si için `imdb-` ile başladığını kontrol eder: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -435,9 +412,9 @@ Fark ettiniz mi? `value.startswith()` ile bir string, tuple alabilir ve tuple i #### Rastgele Bir Item { #a-random-item } -`data.items()` ile, her dictionary öğesi için key ve value içeren tuple’lardan oluşan bir iterable object elde ederiz. +`data.items()` ile, her dictionary öğesi için key ve value içeren tuple’lardan oluşan bir yinelemeli nesne elde ederiz. -Bu iterable object’i `list(data.items())` ile düzgün bir `list`’e çeviririz. +Bu yinelemeli nesneyi `list(data.items())` ile düzgün bir `list`’e çeviririz. Ardından `random.choice()` ile list’ten **rastgele bir değer** alırız; yani `(id, name)` içeren bir tuple elde ederiz. Şuna benzer: `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. diff --git a/docs/tr/docs/tutorial/query-params.md b/docs/tr/docs/tutorial/query-params.md index 89cfa3fb3..4a9f688ca 100644 --- a/docs/tr/docs/tutorial/query-params.md +++ b/docs/tr/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ Fonksiyonda path parametrelerinin parçası olmayan diğer parametreleri tanımladığınızda, bunlar otomatik olarak "query" parametreleri olarak yorumlanır. -{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} Query, bir URL'de `?` işaretinden sonra gelen ve `&` karakterleriyle ayrılan anahtar-değer çiftlerinin kümesidir. @@ -24,7 +24,7 @@ Ancak, bunları Python tipleriyle (yukarıdaki örnekte `int` olarak) tanımlad Path parametreleri için geçerli olan aynı süreç query parametreleri için de geçerlidir: * Editör desteği (tabii ki) -* Veri "parsing" +* Veri "ayrıştırma" * Veri doğrulama * Otomatik dokümantasyon @@ -46,7 +46,7 @@ http://127.0.0.1:8000/items/ http://127.0.0.1:8000/items/?skip=0&limit=10 ``` -Ancak örneğin şuraya giderseniz: +Namunak örneğin şuraya giderseniz: ``` http://127.0.0.1:8000/items/?skip=20 @@ -128,7 +128,7 @@ Belirli bir değer eklemek istemiyor ama sadece opsiyonel olmasını istiyorsan Ancak bir query parametresini zorunlu yapmak istediğinizde, herhangi bir varsayılan değer tanımlamamanız yeterlidir: -{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} Burada query parametresi `needy`, `str` tipinde zorunlu bir query parametresidir. diff --git a/docs/tr/docs/tutorial/request-files.md b/docs/tr/docs/tutorial/request-files.md index 0bbc557e0..8ebef15e2 100644 --- a/docs/tr/docs/tutorial/request-files.md +++ b/docs/tr/docs/tutorial/request-files.md @@ -20,13 +20,13 @@ Bunun nedeni, upload edilen dosyaların "form data" olarak gönderilmesidir. `fastapi` içinden `File` ve `UploadFile` import edin: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} ## `File` Parametrelerini Tanımlayın { #define-file-parameters } `Body` veya `Form` için yaptığınız gibi dosya parametreleri oluşturun: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} /// info | Bilgi @@ -54,7 +54,7 @@ Ancak bazı durumlarda `UploadFile` kullanmak size fayda sağlayabilir. Tipi `UploadFile` olan bir dosya parametresi tanımlayın: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} `UploadFile` kullanmanın `bytes`’a göre birkaç avantajı vardır: @@ -121,7 +121,7 @@ Formlardan gelen veri, dosya içermiyorsa normalde "media type" olarak `applicat Ancak form dosya içeriyorsa `multipart/form-data` olarak encode edilir. `File` kullanırsanız, **FastAPI** dosyaları body’nin doğru kısmından alması gerektiğini bilir. -Bu encoding’ler ve form alanları hakkında daha fazla okumak isterseniz MDN web dokümanlarındaki POST sayfasına bakın. +Bu encoding’ler ve form alanları hakkında daha fazla okumak isterseniz MDN web dokümanlarındaki POST sayfasına bakın. /// @@ -143,7 +143,7 @@ Standart type annotation’ları kullanıp varsayılan değeri `None` yaparak bi Ek metadata ayarlamak için `UploadFile` ile birlikte `File()` da kullanabilirsiniz. Örneğin: -{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} ## Birden Fazla Dosya Upload { #multiple-file-uploads } @@ -153,7 +153,7 @@ Bu dosyalar, "form data" ile gönderilen aynı "form field" ile ilişkilendirili Bunu kullanmak için `bytes` veya `UploadFile` listesini tanımlayın: -{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} Tanımladığınız gibi, `bytes` veya `UploadFile`’lardan oluşan bir `list` alırsınız. @@ -169,7 +169,7 @@ Tanımladığınız gibi, `bytes` veya `UploadFile`’lardan oluşan bir `list` Daha önce olduğu gibi, `UploadFile` için bile ek parametreler ayarlamak amacıyla `File()` kullanabilirsiniz: -{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} ## Özet { #recap } diff --git a/docs/tr/docs/tutorial/request-form-models.md b/docs/tr/docs/tutorial/request-form-models.md index c35f956fc..75693190f 100644 --- a/docs/tr/docs/tutorial/request-form-models.md +++ b/docs/tr/docs/tutorial/request-form-models.md @@ -24,7 +24,7 @@ Bu özellik FastAPI `0.113.0` sürümünden itibaren desteklenmektedir. 🤓 Sadece, **form field** olarak almak istediğiniz alanlarla bir **Pydantic model** tanımlayın ve ardından parametreyi `Form` olarak bildirin: -{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} **FastAPI**, request içindeki **form data**'dan **her bir field** için veriyi **çıkarır** ve size tanımladığınız Pydantic model'ini verir. @@ -48,7 +48,7 @@ Bu özellik FastAPI `0.114.0` sürümünden itibaren desteklenmektedir. 🤓 Herhangi bir `extra` field'ı `forbid` etmek için Pydantic'in model konfigürasyonunu kullanabilirsiniz: -{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} Bir client fazladan veri göndermeye çalışırsa, bir **error** response alır. diff --git a/docs/tr/docs/tutorial/request-forms-and-files.md b/docs/tr/docs/tutorial/request-forms-and-files.md index 86d26b498..69926af5e 100644 --- a/docs/tr/docs/tutorial/request-forms-and-files.md +++ b/docs/tr/docs/tutorial/request-forms-and-files.md @@ -16,13 +16,13 @@ $ pip install python-multipart ## `File` ve `Form` Import Edin { #import-file-and-form } -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} ## `File` ve `Form` Parametrelerini Tanımlayın { #define-file-and-form-parameters } Dosya ve form parametrelerini, `Body` veya `Query` için yaptığınız şekilde oluşturun: -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} Dosyalar ve form alanları form data olarak upload edilir ve siz de dosyaları ve form alanlarını alırsınız. diff --git a/docs/tr/docs/tutorial/request-forms.md b/docs/tr/docs/tutorial/request-forms.md index 4608a6b79..1a5303b3e 100644 --- a/docs/tr/docs/tutorial/request-forms.md +++ b/docs/tr/docs/tutorial/request-forms.md @@ -18,17 +18,17 @@ $ pip install python-multipart `Form`'u `fastapi`'den import edin: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} ## `Form` Parametrelerini Tanımlayın { #define-form-parameters } Form parametrelerini `Body` veya `Query` için yaptığınız gibi oluşturun: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} Örneğin OAuth2 spesifikasyonunun kullanılabileceği ("password flow" olarak adlandırılan) yollardan birinde, form alanları olarak bir `username` ve `password` göndermek zorunludur. -spec, alanların adının tam olarak `username` ve `password` olmasını ve JSON değil form alanları olarak gönderilmesini gerektirir. +Spesifikasyon, alanların adının tam olarak `username` ve `password` olmasını ve JSON değil form alanları olarak gönderilmesini gerektirir. `Form` ile `Body` (ve `Query`, `Path`, `Cookie`) ile yaptığınız aynı konfigürasyonları tanımlayabilirsiniz; validasyon, örnekler, alias (örn. `username` yerine `user-name`) vb. dahil. @@ -56,13 +56,13 @@ Formlardan gelen veri normalde "media type" `application/x-www-form-urlencoded` Ancak form dosyalar içerdiğinde `multipart/form-data` olarak encode edilir. Dosyaları ele almayı bir sonraki bölümde okuyacaksınız. -Bu encoding'ler ve form alanları hakkında daha fazla okumak isterseniz, MDN web docs for POST sayfasına gidin. +Bu encoding'ler ve form alanları hakkında daha fazla okumak isterseniz, MDN web docs for POST sayfasına gidin. /// /// warning | Uyarı -Bir *path operation* içinde birden fazla `Form` parametresi tanımlayabilirsiniz, ancak JSON olarak almayı beklediğiniz `Body` alanlarını da ayrıca tanımlayamazsınız; çünkü bu durumda request'in body'si `application/json` yerine `application/x-www-form-urlencoded` ile encode edilmiş olur. +Bir *path operation* içinde birden fazla `Form` parametresi tanımlayabilirsiniz, ancak JSON olarak almayı beklediğiniz `Body` alanlarını da ayrıca tanımlayamazsınız; çünkü bu durumda request'in body'si `application/x-www-form-urlencoded` ile encode edilmiş olur. Bu **FastAPI**'ın bir kısıtlaması değildir, HTTP protokolünün bir parçasıdır. diff --git a/docs/tr/docs/tutorial/response-model.md b/docs/tr/docs/tutorial/response-model.md index f1d1f7d15..2bf04bd9e 100644 --- a/docs/tr/docs/tutorial/response-model.md +++ b/docs/tr/docs/tutorial/response-model.md @@ -97,7 +97,7 @@ Artık bir browser password ile user oluşturduğunda, API response içinde ayn Bu örnekte sorun olmayabilir; çünkü password’ü gönderen kullanıcı zaten aynı kişi. -Ancak aynı modeli başka bir *path operation* için kullanırsak, kullanıcının password’lerini her client’a gönderiyor olabiliriz. +Namun ancak aynı modeli başka bir *path operation* için kullanırsak, kullanıcının password’lerini her client’a gönderiyor olabiliriz. /// danger @@ -183,7 +183,7 @@ Bazı durumlarda Pydantic field olarak geçerli olmayan bir şey döndürebilir En yaygın durum, [ileri seviye dokümanlarda daha sonra anlatıldığı gibi doğrudan bir Response döndürmektir](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} Bu basit durum FastAPI tarafından otomatik olarak ele alınır; çünkü dönüş tipi annotation’ı `Response` class’ıdır (veya onun bir subclass’ı). @@ -193,7 +193,7 @@ Araçlar da memnun olur; çünkü hem `RedirectResponse` hem `JSONResponse`, `Re Type annotation içinde `Response`’un bir subclass’ını da kullanabilirsiniz: -{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} Bu da çalışır; çünkü `RedirectResponse`, `Response`’un subclass’ıdır ve FastAPI bu basit durumu otomatik olarak yönetir. @@ -201,7 +201,7 @@ Bu da çalışır; çünkü `RedirectResponse`, `Response`’un subclass’ıdı Ancak geçerli bir Pydantic tipi olmayan başka rastgele bir obje (ör. bir veritabanı objesi) döndürür ve fonksiyonu da öyle annotate ederseniz, FastAPI bu type annotation’dan bir Pydantic response model oluşturmaya çalışır ve başarısız olur. -Aynı şey, farklı tipler arasında bir union kullandığınızda ve bu tiplerden biri veya birkaçı geçerli bir Pydantic tipi değilse de olur; örneğin şu kullanım patlar 💥: +Aynı şey, farklı tipler arasında bir birleşim kullandığınızda ve bu tiplerden biri veya birkaçı geçerli bir Pydantic tipi değilse de olur; örneğin şu kullanım patlar 💥: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} diff --git a/docs/tr/docs/tutorial/response-status-code.md b/docs/tr/docs/tutorial/response-status-code.md index 57ae7bde3..d2270a334 100644 --- a/docs/tr/docs/tutorial/response-status-code.md +++ b/docs/tr/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ Bir response model tanımlayabildiğiniz gibi, herhangi bir *path operation* iç * `@app.delete()` * vb. -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} /// note | Not @@ -66,7 +66,7 @@ Kısaca: /// tip | İpucu -Her bir status code hakkında daha fazla bilgi almak ve hangi kodun ne için kullanıldığını görmek için HTTP status code'lar hakkında MDN dokümantasyonuna göz atın. +Her bir status code hakkında daha fazla bilgi almak ve hangi kodun ne için kullanıldığını görmek için MDN dokümantasyonu: HTTP status code'lar hakkında göz atın. /// @@ -74,7 +74,7 @@ Her bir status code hakkında daha fazla bilgi almak ve hangi kodun ne için kul Önceki örneğe tekrar bakalım: -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} `201`, "Created" için kullanılan status code'dur. @@ -82,7 +82,7 @@ Ancak bu kodların her birinin ne anlama geldiğini ezberlemek zorunda değilsin `fastapi.status` içindeki kolaylık değişkenlerini (convenience variables) kullanabilirsiniz. -{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} Bunlar sadece kolaylık sağlar; aynı sayıyı taşırlar. Ancak bu şekilde editörün autocomplete özelliğiyle kolayca bulabilirsiniz: diff --git a/docs/tr/docs/tutorial/schema-extra-example.md b/docs/tr/docs/tutorial/schema-extra-example.md index a91dda892..7e29b2309 100644 --- a/docs/tr/docs/tutorial/schema-extra-example.md +++ b/docs/tr/docs/tutorial/schema-extra-example.md @@ -74,7 +74,7 @@ Elbette birden fazla `examples` da geçebilirsiniz: Bunu yaptığınızda, örnekler bu body verisi için dahili **JSON Schema**’nın bir parçası olur. -Buna rağmen, time of writing this, doküman arayüzünü gösteren araç olan Swagger UI, **JSON Schema** içindeki veriler için birden fazla örneği göstermeyi desteklemiyor. Ancak aşağıda bir çözüm yolu var. +Buna rağmen, bu yazı yazılırken, doküman arayüzünü gösteren araç olan Swagger UI, **JSON Schema** içindeki veriler için birden fazla örneği göstermeyi desteklemiyor. Ancak aşağıda bir çözüm yolu var. ### OpenAPI’ye özel `examples` { #openapi-specific-examples } diff --git a/docs/tr/docs/tutorial/security/first-steps.md b/docs/tr/docs/tutorial/security/first-steps.md index 7e0a70a02..0f72d0c83 100644 --- a/docs/tr/docs/tutorial/security/first-steps.md +++ b/docs/tr/docs/tutorial/security/first-steps.md @@ -20,7 +20,7 @@ Güvenliği yönetmek için **FastAPI**’nin sunduğu araçları kullanalım. Örneği `main.py` adlı bir dosyaya kopyalayın: -{* ../../docs_src/security/tutorial001_an_py39.py *} +{* ../../docs_src/security/tutorial001_an_py310.py *} ## Çalıştırın { #run-it } @@ -132,7 +132,7 @@ Bu durumda bile **FastAPI**, onu oluşturabilmeniz için gereken araçları suna `OAuth2PasswordBearer` sınıfının bir instance’ını oluştururken `tokenUrl` parametresini veririz. Bu parametre, client’ın (kullanıcının browser’ında çalışan frontend’in) token almak için `username` ve `password` göndereceği URL’yi içerir. -{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} /// tip | İpucu @@ -170,7 +170,7 @@ Dolayısıyla `Depends` ile kullanılabilir. Artık `Depends` ile bir dependency olarak `oauth2_scheme`’i geçebilirsiniz. -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} Bu dependency, *path operation function* içindeki `token` parametresine atanacak bir `str` sağlar. diff --git a/docs/tr/docs/tutorial/security/get-current-user.md b/docs/tr/docs/tutorial/security/get-current-user.md index 9f56c7628..cc7f7a51b 100644 --- a/docs/tr/docs/tutorial/security/get-current-user.md +++ b/docs/tr/docs/tutorial/security/get-current-user.md @@ -2,7 +2,7 @@ Önceki bölümde güvenlik sistemi (dependency injection sistemine dayanır) *path operation function*'a `str` olarak bir `token` veriyordu: -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} Ancak bu hâlâ pek kullanışlı değil. diff --git a/docs/tr/docs/tutorial/security/oauth2-jwt.md b/docs/tr/docs/tutorial/security/oauth2-jwt.md index 716761157..ad991d322 100644 --- a/docs/tr/docs/tutorial/security/oauth2-jwt.md +++ b/docs/tr/docs/tutorial/security/oauth2-jwt.md @@ -116,7 +116,11 @@ Sonra, alınan password'ün kayıttaki hash ile eşleşip eşleşmediğini doğr Bir tane de kullanıcıyı authenticate edip geri döndüren bir yardımcı fonksiyon ekleyelim. -{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +`authenticate_user`, veritabanında var olmayan bir username ile çağrıldığında, yine de sahte (dummy) bir hash'e karşı `verify_password` çalıştırıyoruz. + +Bu, username geçerli olsun ya da olmasın endpoint'in yaklaşık aynı sürede yanıt vermesini sağlar; böylece mevcut username'leri saymaya yarayabilecek zamanlama saldırılarını (timing attacks) engeller. /// note | Not @@ -152,7 +156,7 @@ Response için token endpoint'inde kullanılacak bir Pydantic Model tanımlayın Yeni bir access token üretmek için bir yardımcı fonksiyon oluşturun. -{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} ## Dependency'leri güncelleme { #update-the-dependencies } @@ -162,7 +166,7 @@ Gelen token'ı decode edin, doğrulayın ve mevcut kullanıcıyı döndürün. Token geçersizse, hemen bir HTTP hatası döndürün. -{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} ## `/token` *path operation*'ını güncelleme { #update-the-token-path-operation } @@ -170,7 +174,7 @@ Token'ın süre sonu için bir `timedelta` oluşturun. Gerçek bir JWT access token üretip döndürün. -{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} ### JWT "subject" `sub` Hakkında Teknik Detaylar { #technical-details-about-the-jwt-subject-sub } diff --git a/docs/tr/docs/tutorial/sql-databases.md b/docs/tr/docs/tutorial/sql-databases.md index e1638cb04..0a02b47ae 100644 --- a/docs/tr/docs/tutorial/sql-databases.md +++ b/docs/tr/docs/tutorial/sql-databases.md @@ -8,7 +8,7 @@ Burada "ORMs" olarak adlandırılır). FastAPI sizi hiçbir şeye zorlamaz. +İstediğiniz başka bir SQL veya NoSQL veritabanı kütüphanesini kullanabilirsiniz (bazı durumlarda "ORMs" olarak adlandırılır). FastAPI sizi hiçbir şeye zorlamaz. 😎 /// @@ -49,7 +49,7 @@ $ pip install sqlmodel Önce, tek bir **SQLModel** modeliyle uygulamanın en basit ilk sürümünü oluşturacağız. -Aşağıda, **birden fazla model** kullanarak güvenliği ve esnekliği artırıp geliştireceğiz. +Aşağıda, **birden fazla model** kullanarak güvenliği ve esnekliği artırıp geliştireceğiz. 🤓 ### Modelleri Oluşturma { #create-models } @@ -93,7 +93,7 @@ Sonra `SQLModel.metadata.create_all(engine)` kullanan bir fonksiyon ekleyerek t Bir **`Session`**, **nesneleri memory’de** tutar ve verideki gerekli değişiklikleri takip eder; ardından veritabanıyla iletişim kurmak için **`engine` kullanır**. -`yield` ile, her request için yeni bir `Session` sağlayacak bir FastAPI **dependency** oluşturacağız. Bu da her request’te tek session kullanmamızı garanti eder. +`yield` ile, her request için yeni bir `Session` sağlayacak bir FastAPI **dependency** oluşturacağız. Bu da her request’te tek session kullanmamızı garanti eder. 🤓 Ardından bu dependency’yi kullanacak kodun geri kalanını sadeleştirmek için `Annotated` ile `SessionDep` dependency’sini oluştururuz. @@ -107,7 +107,7 @@ Uygulama başlarken veritabanı table’larını oluşturacağız. Burada bir uygulama startup event’inde table’ları oluşturuyoruz. -Production’da büyük ihtimalle uygulamayı başlatmadan önce çalışan bir migration script’i kullanırsınız. +Production’da büyük ihtimalle uygulamayı başlatmadan önce çalışan bir migration script’i kullanırsınız. 🤓 /// tip | İpucu @@ -169,19 +169,19 @@ Sonra `/docs` arayüzüne gidin; **FastAPI**’nin API’yi **dokümante etmek** Şimdi bu uygulamayı biraz **refactor** edelim ve **güvenliği** ile **esnekliği** artıralım. -Önceki uygulamaya bakarsanız, UI’da şu ana kadar client’ın oluşturulacak `Hero`’nun `id` değerini belirlemesine izin verdiğini görebilirsiniz. +Önceki uygulamaya bakarsanız, UI’da şu ana kadar client’ın oluşturulacak `Hero`’nun `id` değerini belirlemesine izin verdiğini görebilirsiniz. 😱 Buna izin vermemeliyiz; DB’de zaten atanmış bir `id`’yi ezebilirler. `id` belirlemek **client** tarafından değil, **backend** veya **veritabanı** tarafından yapılmalıdır. -Ayrıca hero için bir `secret_name` oluşturuyoruz ama şimdiye kadar her yerde geri döndürüyoruz; bu pek de **secret** sayılmaz... +Ayrıca hero için bir `secret_name` oluşturuyoruz ama şimdiye kadar her yerde geri döndürüyoruz; bu pek de **secret** sayılmaz... 😅 -Bunları birkaç **ek model** ekleyerek düzelteceğiz. SQLModel’in parlayacağı yer de burası. +Bunları birkaç **ek model** ekleyerek düzelteceğiz. SQLModel’in parlayacağı yer de burası. ✨ ### Birden Fazla Model Oluşturma { #create-multiple-models } **SQLModel**’de, `table=True` olan herhangi bir model sınıfı bir **table model**’dir. -`table=True` olmayan her model sınıfı ise bir **data model**’dir; bunlar aslında sadece Pydantic modelleridir (bazı küçük ek özelliklerle). +`table=True` olmayan her model sınıfı ise bir **data model**’dir; bunlar aslında sadece Pydantic modelleridir (bazı küçük ek özelliklerle). 🤓 SQLModel ile **inheritance** kullanarak her durumda tüm alanları tekrar tekrar yazmaktan **kaçınabiliriz**. @@ -216,7 +216,7 @@ Sonraki adımda `HeroPublic` modelini oluştururuz; bu model API client’ların `HeroBase` ile aynı alanlara sahiptir; dolayısıyla `secret_name` içermez. -Sonunda kahramanlarımızın kimliği korunmuş oldu! +Sonunda kahramanlarımızın kimliği korunmuş oldu! 🥷 Ayrıca `id: int` alanını yeniden tanımlar. Bunu yaparak API client’larıyla bir **contract** (sözleşme) oluşturmuş oluruz; böylece `id` alanının her zaman var olacağını ve `int` olacağını (asla `None` olmayacağını) bilirler. @@ -224,7 +224,7 @@ Ayrıca `id: int` alanını yeniden tanımlar. Bunu yaparak API client’larıyl Return model’in bir değerin her zaman mevcut olduğunu ve her zaman `int` olduğunu (`None` değil) garanti etmesi API client’ları için çok faydalıdır; bu kesinlik sayesinde daha basit kod yazabilirler. -Ayrıca **otomatik üretilen client**’ların arayüzleri de daha basit olur; böylece API’nizle çalışan geliştiriciler için süreç çok daha rahat olur. +Ayrıca **otomatik üretilen client**’ların arayüzleri de daha basit olur; böylece API’nizle çalışan geliştiriciler için süreç çok daha rahat olur. 😎 /// @@ -262,13 +262,13 @@ Ayrıca password değerlerini saklamadan önce **hash** etmelisiniz; **asla plai #### `HeroUpdate` - hero güncellemek için *data model* { #heroupdate-the-data-model-to-update-a-hero } -Uygulamanın önceki sürümünde bir hero’yu **güncellemenin** bir yolu yoktu; ancak artık **birden fazla model** ile bunu yapabiliriz. +Uygulamanın önceki sürümünde bir hero’yu **güncellemenin** bir yolu yoktu; ancak artık **birden fazla model** ile bunu yapabiliriz. 🎉 `HeroUpdate` *data model* biraz özeldir: yeni bir hero oluşturmak için gereken alanların **tamamına** sahiptir, ancak tüm alanlar **opsiyoneldir** (hepsinin bir default değeri vardır). Bu sayede hero güncellerken sadece güncellemek istediğiniz alanları gönderebilirsiniz. Tüm **alanlar aslında değiştiği** için (tip artık `None` içeriyor ve default değerleri `None` oluyor), onları **yeniden tanımlamamız** gerekir. -Aslında `HeroBase`’ten miras almamız gerekmiyor; çünkü tüm alanları yeniden tanımlıyoruz. Tutarlılık için miras almayı bırakıyorum ama bu gerekli değil. Daha çok kişisel tercih meselesi. +Aslında `HeroBase`’ten miras almamız gerekmiyor; çünkü tüm alanları yeniden tanımlıyoruz. Tutarlılık için miras almayı bırakıyorum ama bu gerekli değil. Daha çok kişisel tercih meselesi. 🤷 `HeroUpdate` alanları: @@ -316,7 +316,7 @@ Tek bir hero’yu **okuyabiliriz**: Bir hero’yu **güncelleyebiliriz**. Bunun için HTTP `PATCH` operasyonu kullanırız. -Kodda, client’ın gönderdiği tüm verilerle bir `dict` alırız; **yalnızca client’ın gönderdiği veriler**, yani sadece default değer oldukları için orada bulunan değerler hariç. Bunu yapmak için `exclude_unset=True` kullanırız. Asıl numara bu. +Kodda, client’ın gönderdiği tüm verilerle bir `dict` alırız; **yalnızca client’ın gönderdiği veriler**, yani sadece default değer oldukları için orada bulunan değerler hariç. Bunu yapmak için `exclude_unset=True` kullanırız. Asıl numara bu. 🪄 Sonra `hero_db.sqlmodel_update(hero_data)` ile `hero_db`’yi `hero_data` içindeki verilerle güncelleriz. @@ -326,7 +326,7 @@ Sonra `hero_db.sqlmodel_update(hero_data)` ile `hero_db`’yi `hero_data` içind Bir hero’yu **silmek** büyük ölçüde aynı kalıyor. -Bu örnekte her şeyi refactor etme isteğimizi bastıracağız. +Bu örnekte her şeyi refactor etme isteğimizi bastıracağız. 😅 {* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} @@ -354,4 +354,4 @@ $ fastapi dev main.py Bir SQL veritabanıyla etkileşim kurmak için **SQLModel** kullanabilir ve *data model* ile *table model* yaklaşımıyla kodu sadeleştirebilirsiniz. -**SQLModel** dokümantasyonunda çok daha fazlasını öğrenebilirsiniz; **FastAPI** ile SQLModel kullanımı için daha uzun bir mini tutorial da bulunuyor. +**SQLModel** dokümantasyonunda çok daha fazlasını öğrenebilirsiniz; **FastAPI** ile SQLModel kullanımı için daha uzun bir mini tutorial da bulunuyor. 🚀 diff --git a/docs/tr/docs/tutorial/static-files.md b/docs/tr/docs/tutorial/static-files.md index d30b4389d..e49b14bd8 100644 --- a/docs/tr/docs/tutorial/static-files.md +++ b/docs/tr/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ * `StaticFiles`'ı import edin. * Belirli bir path'te bir `StaticFiles()` örneğini "mount" edin. -{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} /// note | Teknik Detaylar @@ -23,7 +23,7 @@ Bu, bir `APIRouter` kullanmaktan farklıdır; çünkü mount edilen uygulama tamamen bağımsızdır. Ana uygulamanızın OpenAPI ve docs'ları, mount edilen uygulamadan hiçbir şey içermez, vb. -Bununla ilgili daha fazla bilgiyi [Advanced User Guide](../advanced/index.md){.internal-link target=_blank} içinde okuyabilirsiniz. +Bununla ilgili daha fazla bilgiyi [Gelişmiş Kullanıcı Kılavuzu](../advanced/index.md){.internal-link target=_blank} içinde okuyabilirsiniz. ## Detaylar { #details } diff --git a/docs/tr/docs/tutorial/testing.md b/docs/tr/docs/tutorial/testing.md index 887156606..18e0dd96c 100644 --- a/docs/tr/docs/tutorial/testing.md +++ b/docs/tr/docs/tutorial/testing.md @@ -12,7 +12,7 @@ Bununla birlikte **FastAPI** ile `httpx`'i kurun. -Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu aktifleştirdiğinizden ve sonra kurulumu yaptığınızdan emin olun; örneğin: +Bir [Sanal Ortam](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu aktifleştirdiğinizden ve sonra kurulumu yaptığınızdan emin olun; örneğin: ```console $ pip install httpx @@ -30,7 +30,7 @@ Adı `test_` ile başlayan fonksiyonlar oluşturun (bu, `pytest`'in standart kon Kontrol etmeniz gereken şeyler için standart Python ifadeleriyle basit `assert` satırları yazın (bu da `pytest` standardıdır). -{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} /// tip | İpucu @@ -52,7 +52,7 @@ Bu sayede `pytest`'i ek bir karmaşıklık olmadan doğrudan kullanabilirsiniz. /// tip | İpucu -FastAPI uygulamanıza request göndermenin dışında testlerinizde `async` fonksiyonlar çağırmak istiyorsanız (örn. asenkron veritabanı fonksiyonları), ileri seviye bölümdeki [Async Tests](../advanced/async-tests.md){.internal-link target=_blank} dokümanına göz atın. +FastAPI uygulamanıza request göndermenin dışında testlerinizde `async` fonksiyonlar çağırmak istiyorsanız (örn. asenkron veritabanı fonksiyonları), ileri seviye bölümdeki [Asenkron Testler](../advanced/async-tests.md){.internal-link target=_blank} dokümanına göz atın. /// @@ -64,7 +64,7 @@ Ayrıca **FastAPI** uygulamanız birden fazla dosya/modül vb. ile de oluşturul ### **FastAPI** Uygulama Dosyası { #fastapi-app-file } -[Bigger Applications](bigger-applications.md){.internal-link target=_blank}'te anlatılan şekilde bir dosya yapınız olduğunu varsayalım: +[Daha Büyük Uygulamalar](bigger-applications.md){.internal-link target=_blank}'te anlatılan şekilde bir dosya yapınız olduğunu varsayalım: ``` . @@ -75,7 +75,7 @@ Ayrıca **FastAPI** uygulamanız birden fazla dosya/modül vb. ile de oluşturul `main.py` dosyasında **FastAPI** uygulamanız bulunuyor olsun: -{* ../../docs_src/app_testing/app_a_py39/main.py *} +{* ../../docs_src/app_testing/app_a_py310/main.py *} ### Test Dosyası { #testing-file } @@ -91,7 +91,7 @@ Sonra testlerinizin olduğu bir `test_main.py` dosyanız olabilir. Bu dosya ayn Bu dosya aynı package içinde olduğu için, `main` modülünden (`main.py`) `app` nesnesini import etmek üzere relative import kullanabilirsiniz: -{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} ...ve test kodunu da öncekiyle aynı şekilde yazabilirsiniz. @@ -145,7 +145,7 @@ Backend'e veri geçme hakkında daha fazla bilgi için (`httpx` veya `TestClient `TestClient`'ın Pydantic model'lerini değil, JSON'a dönüştürülebilen verileri aldığını unutmayın. -Testinizde bir Pydantic model'iniz varsa ve test sırasında verisini uygulamaya göndermek istiyorsanız, [JSON Compatible Encoder](encoder.md){.internal-link target=_blank} içinde açıklanan `jsonable_encoder`'ı kullanabilirsiniz. +Testinizde bir Pydantic model'iniz varsa ve test sırasında verisini uygulamaya göndermek istiyorsanız, [JSON Uyumlu Encoder](encoder.md){.internal-link target=_blank} içinde açıklanan `jsonable_encoder`'ı kullanabilirsiniz. /// @@ -153,7 +153,7 @@ Testinizde bir Pydantic model'iniz varsa ve test sırasında verisini uygulamaya Bundan sonra yapmanız gereken tek şey `pytest`'i kurmaktır. -Bir [virtual environment](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu aktifleştirdiğinizden ve sonra kurulumu yaptığınızdan emin olun; örneğin: +Bir [Sanal Ortam](../virtual-environments.md){.internal-link target=_blank} oluşturduğunuzdan, onu aktifleştirdiğinizden ve sonra kurulumu yaptığınızdan emin olun; örneğin:
diff --git a/docs/tr/docs/virtual-environments.md b/docs/tr/docs/virtual-environments.md index cf7fab778..668a3d5a9 100644 --- a/docs/tr/docs/virtual-environments.md +++ b/docs/tr/docs/virtual-environments.md @@ -53,7 +53,7 @@ $ cd awesome-project ## Virtual Environment Oluşturun { #create-a-virtual-environment } -Bir Python projesi üzerinde **ilk kez** çalışmaya başladığınızda, projenizin içinde **virtual environment** oluşturun. +Bir Python projesi üzerinde **ilk kez** çalışmaya başladığınızda, **virtual environment**'i projenizin içinde oluşturun. /// tip | İpucu From 685cc0de17a1f5af0c7ad0936f066aef5a64cd37 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:42:01 +0100 Subject: [PATCH 119/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20uk=20(update-all)=20(#14914)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all * 🎨 Auto format --------- Co-authored-by: github-actions[bot] --- docs/uk/docs/alternatives.md | 66 +++--- docs/uk/docs/features.md | 10 +- docs/uk/docs/index.md | 12 +- docs/uk/docs/learn/index.md | 2 +- docs/uk/docs/python-types.md | 202 ++++-------------- docs/uk/docs/tutorial/background-tasks.md | 26 +-- docs/uk/docs/tutorial/body-multiple-params.md | 9 +- docs/uk/docs/tutorial/body-nested-models.md | 6 +- docs/uk/docs/tutorial/body.md | 14 +- docs/uk/docs/tutorial/cookie-param-models.md | 22 +- docs/uk/docs/tutorial/cookie-params.md | 16 +- docs/uk/docs/tutorial/cors.md | 31 ++- docs/uk/docs/tutorial/debugging.md | 2 +- docs/uk/docs/tutorial/first-steps.md | 22 +- docs/uk/docs/tutorial/handling-errors.md | 16 +- docs/uk/docs/tutorial/header-param-models.md | 12 +- docs/uk/docs/tutorial/header-params.md | 2 +- docs/uk/docs/tutorial/index.md | 8 +- docs/uk/docs/tutorial/metadata.md | 12 +- docs/uk/docs/tutorial/middleware.md | 10 +- .../path-params-numeric-validations.md | 20 +- docs/uk/docs/tutorial/path-params.md | 38 ++-- docs/uk/docs/tutorial/query-param-models.md | 22 +- .../tutorial/query-params-str-validations.md | 85 +++----- docs/uk/docs/tutorial/query-params.md | 10 +- docs/uk/docs/tutorial/request-files.md | 14 +- docs/uk/docs/tutorial/request-form-models.md | 4 +- .../docs/tutorial/request-forms-and-files.md | 8 +- docs/uk/docs/tutorial/request-forms.md | 18 +- docs/uk/docs/tutorial/response-model.md | 8 +- docs/uk/docs/tutorial/response-status-code.md | 8 +- docs/uk/docs/tutorial/schema-extra-example.md | 4 +- docs/uk/docs/tutorial/security/index.md | 6 +- docs/uk/docs/tutorial/static-files.md | 2 +- docs/uk/docs/tutorial/testing.md | 8 +- 35 files changed, 303 insertions(+), 452 deletions(-) diff --git a/docs/uk/docs/alternatives.md b/docs/uk/docs/alternatives.md index d44ca794f..5dbf8a96b 100644 --- a/docs/uk/docs/alternatives.md +++ b/docs/uk/docs/alternatives.md @@ -58,9 +58,9 @@ Flask — це «мікрофреймворк», він не включає ін /// check | Надихнуло **FastAPI** на -Бути мікрофреймоворком. Зробити легким комбінування та поєднання необхідних інструментів та частин. +Бути мікрофреймворком. Зробити легким комбінування та поєднання необхідних інструментів та частин. - Мати просту та легку у використанні систему маршрутизації. +Мати просту та легку у використанні систему маршрутизації. /// @@ -100,9 +100,9 @@ def read_url(): /// check | Надихнуло **FastAPI** на -* Майте простий та інтуїтивно зрозумілий API. -* Використовуйте імена (операції) методів HTTP безпосередньо, простим та інтуїтивно зрозумілим способом. -* Розумні параметри за замовчуванням, але потужні налаштування. +* Мати простий та інтуїтивно зрозумілий API. +* Використовувати імена (операції) методів HTTP безпосередньо, простим та інтуїтивно зрозумілим способом. +* Мати розумні параметри за замовчуванням, але потужні налаштування. /// @@ -122,12 +122,12 @@ def read_url(): Прийняти і використовувати відкритий стандарт для специфікацій API замість спеціальної схеми. - Інтегрувати інструменти інтерфейсу на основі стандартів: +Інтегрувати інструменти інтерфейсу на основі стандартів: * Інтерфейс Swagger * ReDoc - Ці два було обрано через те, що вони досить популярні та стабільні, але, виконавши швидкий пошук, ви можете знайти десятки додаткових альтернативних інтерфейсів для OpenAPI (які можна використовувати з **FastAPI**). +Ці два було обрано через те, що вони досить популярні та стабільні, але, виконавши швидкий пошук, ви можете знайти десятки додаткових альтернативних інтерфейсів для OpenAPI (які можна використовувати з **FastAPI**). /// @@ -137,7 +137,7 @@ def read_url(): ### Marshmallow { #marshmallow } -Однією з головних функцій, необхідних для систем API, є "серіалізація", яка бере дані з коду (Python) і перетворює їх на щось, що можна надіслати через мережу. Наприклад, перетворення об’єкта, що містить дані з бази даних, на об’єкт JSON. Перетворення об’єктів `datetime` на строки тощо. +Однією з головних функцій, необхідних для систем API, є "серіалізація", яка бере дані з коду (Python) і перетворює їх на щось, що можна надіслати через мережу. Наприклад, перетворення об’єкта, що містить дані з бази даних, на об’єкт JSON. Перетворення об’єктів `datetime` на строки тощо. Іншою важливою функцією, необхідною для API, є перевірка даних, яка забезпечує дійсність даних за певними параметрами. Наприклад, що деяке поле є `int`, а не деяка випадкова строка. Це особливо корисно для вхідних даних. @@ -145,7 +145,7 @@ def read_url(): Marshmallow створено для забезпечення цих функцій. Це чудова бібліотека, і я часто нею користувався раніше. -Але він був створений до того, як існували підказки типу Python. Отже, щоб визначити кожну схему, вам потрібно використовувати спеціальні утиліти та класи, надані Marshmallow. +Але він був створений до того, як існували підказки типу Python. Отже, щоб визначити кожну схему, вам потрібно використовувати спеціальні утиліти та класи, надані Marshmallow. /// check | Надихнуло **FastAPI** на @@ -155,7 +155,7 @@ Marshmallow створено для забезпечення цих функці ### Webargs { #webargs } -Іншою важливою функцією, необхідною для API, є аналіз даних із вхідних запитів. +Іншою важливою функцією, необхідною для API, є аналіз даних із вхідних запитів. Webargs — це інструмент, створений, щоб забезпечити це поверх кількох фреймворків, включаючи Flask. @@ -217,7 +217,7 @@ APISpec був створений тими ж розробниками Marshmall Ця комбінація Flask, Flask-apispec із Marshmallow і Webargs була моїм улюбленим бекенд-стеком до створення **FastAPI**. -Їі використання призвело до створення кількох генераторів повного стека Flask. Це основний стек, який я (та кілька зовнішніх команд) використовував досі: +Її використання призвело до створення кількох генераторів повного стека Flask. Це основний стек, який я (та кілька зовнішніх команд) використовував досі: * https://github.com/tiangolo/full-stack * https://github.com/tiangolo/full-stack-flask-couchbase @@ -255,7 +255,7 @@ Flask-apispec був створений тими ж розробниками Mar Використовувати типи Python, щоб мати чудову підтримку редактора. - Мати потужну систему впровадження залежностей. Знайдіть спосіб звести до мінімуму повторення коду. +Мати потужну систему впровадження залежностей. Знайдіть спосіб звести до мінімуму повторення коду. /// @@ -267,7 +267,7 @@ Flask-apispec був створений тими ж розробниками Mar Він використовував `uvloop` замість стандартного циклу Python `asyncio`. Ось що зробило його таким швидким. - Це явно надихнуло Uvicorn і Starlette, які зараз швидші за Sanic у відкритих тестах. +Це явно надихнуло Uvicorn і Starlette, які зараз швидші за Sanic у відкритих тестах. /// @@ -275,7 +275,7 @@ Flask-apispec був створений тими ж розробниками Mar Знайти спосіб отримати божевільну продуктивність. - Ось чому **FastAPI** базується на Starlette, оскільки це найшвидша доступна структура (перевірена тестами сторонніх розробників). +Ось чому **FastAPI** базується на Starlette, оскільки це найшвидша доступна структура (перевірена тестами сторонніх розробників). /// @@ -291,9 +291,9 @@ Falcon — ще один високопродуктивний фреймворк Знайти способи отримати чудову продуктивність. - Разом із Hug (оскільки Hug базується на Falcon) надихнув **FastAPI** оголосити параметр `response` у функціях. +Разом із Hug (оскільки Hug базується на Falcon) надихнув **FastAPI** оголосити параметр `response` у функціях. - Хоча у FastAPI це необов’язково, і використовується в основному для встановлення заголовків, файлів cookie та альтернативних кодів стану. +Хоча у FastAPI це необов’язково, і використовується в основному для встановлення заголовків, файлів cookie та альтернативних кодів статусу. /// @@ -317,7 +317,7 @@ Falcon — ще один високопродуктивний фреймворк Визначити додаткові перевірки для типів даних, використовуючи значення "за замовчуванням" атрибутів моделі. Це покращує підтримку редактора, а раніше вона була недоступна в Pydantic. - Це фактично надихнуло оновити частини Pydantic, щоб підтримувати той самий стиль оголошення перевірки (всі ці функції вже доступні в Pydantic). +Це фактично надихнуло оновити частини Pydantic, щоб підтримувати той самий стиль оголошення перевірки (всі ці функції вже доступні в Pydantic). /// @@ -341,13 +341,13 @@ Hug створив Тімоті Крослі, той самий творець < /// -/// check | Надихнуло **FastAPI** на +/// check | Ідеї, що надихнули **FastAPI** Hug надихнув частину APIStar і був одним із найбільш перспективних інструментів, поряд із APIStar. - Hug надихнув **FastAPI** на використання підказок типу Python для оголошення параметрів і автоматичного створення схеми, що визначає API. +Hug надихнув **FastAPI** на використання підказок типу Python для оголошення параметрів і автоматичного створення схеми, що визначає API. - Hug надихнув **FastAPI** оголосити параметр `response` у функціях для встановлення заголовків і файлів cookie. +Hug надихнув **FastAPI** оголосити параметр `response` у функціях для встановлення заголовків і файлів cookie. /// @@ -389,13 +389,13 @@ APIStar створив Том Крісті. Той самий хлопець, я Існувати. - Ідею оголошення кількох речей (перевірки даних, серіалізації та документації) за допомогою тих самих типів Python, які в той же час забезпечували чудову підтримку редактора, я вважав геніальною ідеєю. +Ідею оголошення кількох речей (перевірки даних, серіалізації та документації) за допомогою тих самих типів Python, які в той же час забезпечували чудову підтримку редактора, я вважав геніальною ідеєю. - І після тривалого пошуку подібної структури та тестування багатьох різних альтернатив, APIStar став найкращим доступним варіантом. +І після тривалого пошуку подібної структури та тестування багатьох різних альтернатив, APIStar став найкращим доступним варіантом. - Потім APIStar перестав існувати як сервер, і було створено Starlette, який став новою кращою основою для такої системи. Це стало останнім джерелом натхнення для створення **FastAPI**. +Потім APIStar перестав існувати як сервер, і було створено Starlette, який став новою кращою основою для такої системи. Це стало останнім джерелом натхнення для створення **FastAPI**. - Я вважаю **FastAPI** «духовним спадкоємцем» APIStar, удосконалюючи та розширюючи функції, систему типізації та інші частини на основі досвіду, отриманого від усіх цих попередніх інструментів. +Я вважаю **FastAPI** «духовним спадкоємцем» APIStar, удосконалюючи та розширюючи функції, систему типізації та інші частини на основі досвіду, отриманого від усіх цих попередніх інструментів. /// @@ -403,7 +403,7 @@ APIStar створив Том Крісті. Той самий хлопець, я ### Pydantic { #pydantic } -Pydantic — це бібліотека для визначення перевірки даних, серіалізації та документації (за допомогою схеми JSON) на основі підказок типу Python. +Pydantic — це бібліотека для визначення перевірки даних, серіалізації та документації (за допомогою Схеми JSON) на основі підказок типу Python. Це робить його надзвичайно інтуїтивним. @@ -411,15 +411,15 @@ Pydantic — це бібліотека для визначення переві /// check | **FastAPI** використовує його для -Виконання перевірки всіх даних, серіалізації даних і автоматичної документацію моделі (на основі схеми JSON). +Виконання перевірки всіх даних, серіалізації даних і автоматичної документації моделі (на основі Схеми JSON). - Потім **FastAPI** бере ці дані схеми JSON і розміщує їх у OpenAPI, окремо від усіх інших речей, які він робить. +Потім **FastAPI** бере ці дані Схеми JSON і розміщує їх у OpenAPI, окремо від усіх інших речей, які він робить. /// ### Starlette { #starlette } -Starlette — це легкий фреймворк/набір інструментів ASGI, який ідеально підходить для створення високопродуктивних asyncio сервісів. +Starlette — це легкий фреймворк/набір інструментів ASGI, який ідеально підходить для створення високопродуктивних asyncio сервісів. Він дуже простий та інтуїтивно зрозумілий. Його розроблено таким чином, щоб його можна було легко розширювати та мати модульні компоненти. @@ -448,7 +448,7 @@ Starlette надає всі основні функції веб-мікрофр ASGI — це новий «стандарт», який розробляється членами основної команди Django. Це ще не «стандарт Python» (PEP), хоча вони в процесі цього. - Тим не менш, він уже використовується як «стандарт» кількома інструментами. Це значно покращує сумісність, оскільки ви можете переключити Uvicorn на будь-який інший сервер ASGI (наприклад, Daphne або Hypercorn), або ви можете додати інструменти, сумісні з ASGI, як-от `python-socketio`. +Тим не менш, він уже використовується як «стандарт» кількома інструментами. Це значно покращує сумісність, оскільки ви можете переключити Uvicorn на будь-який інший сервер ASGI (наприклад, Daphne або Hypercorn), або ви можете додати інструменти, сумісні з ASGI, як-от `python-socketio`. /// @@ -456,9 +456,9 @@ ASGI — це новий «стандарт», який розробляєтьс Керування всіма основними веб-частинами. Додавання функцій зверху. - Сам клас `FastAPI` безпосередньо успадковує клас `Starlette`. +Сам клас `FastAPI` безпосередньо успадковує клас `Starlette`. - Отже, усе, що ви можете робити зі Starlette, ви можете робити це безпосередньо за допомогою **FastAPI**, оскільки це, по суті, Starlette на стероїдах. +Отже, усе, що ви можете робити зі Starlette, ви можете робити це безпосередньо за допомогою **FastAPI**, оскільки це, по суті, Starlette на стероїдах. /// @@ -474,9 +474,9 @@ Uvicorn — це блискавичний сервер ASGI, побудован Основний веб-сервер для запуску програм **FastAPI**. - Ви також можете використати параметр командного рядка `--workers`, щоб мати асинхронний багатопроцесний сервер. +Ви також можете використати параметр командного рядка `--workers`, щоб мати асинхронний багатопроцесний сервер. - Додаткову інформацію див. у розділі [Розгортання](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 d8233115f..db044bf94 100644 --- a/docs/uk/docs/features.md +++ b/docs/uk/docs/features.md @@ -6,7 +6,7 @@ ### На основі відкритих стандартів { #based-on-open-standards } -* OpenAPI для створення API, включаючи оголошення шляхів, операцій, параметрів, тіл запитів, безпеки тощо. +* OpenAPI для створення API, включаючи оголошення шляхів, операцій, параметрів, тіл запитів, безпеки тощо. * Автоматична документація моделей даних за допомогою JSON Schema (оскільки OpenAPI базується саме на JSON Schema). * Розроблено на основі цих стандартів після ретельного аналізу, а не як додатковий рівень поверх основної архітектури. * Це також дає змогу використовувати автоматичну **генерацію клієнтського коду** багатьма мовами. @@ -136,7 +136,7 @@ FastAPI має розумні **налаштування за замовчува ### Впровадження залежностей { #dependency-injection } -FastAPI містить надзвичайно просту у використанні, але надзвичайно потужну систему Dependency Injection. +FastAPI містить надзвичайно просту у використанні, але надзвичайно потужну систему Впровадження залежностей. * Навіть залежності можуть мати власні залежності, утворюючи ієрархію або **«граф» залежностей**. * Усе **автоматично обробляється** фреймворком. @@ -153,8 +153,8 @@ FastAPI містить надзвичайно просту у використа ### Протестовано { #tested } -* 100% покриття тестами. -* 100% анотована типами кодова база. +* 100% покриття тестами. +* 100% анотована типами кодова база. * Використовується в production-застосунках. ## Можливості Starlette { #starlette-features } @@ -190,7 +190,7 @@ FastAPI містить надзвичайно просту у використа * **Ніякої плутанини**: * Не потрібно вчити нову мікромову для визначення схем. * Якщо ви знаєте типи Python, ви знаєте, як використовувати Pydantic. -* Легко працює з вашим **IDE/linter/мозком**: +* Легко працює з вашим **IDE/linter/мозком**: * Оскільки структури даних pydantic є просто екземплярами класів, які ви визначаєте; автодоповнення, лінтинг, mypy і ваша інтуїція повинні добре працювати з вашими перевіреними даними. * Валідує **складні структури**: * Використання ієрархічних моделей Pydantic, Python `typing`’s `List` і `Dict` тощо. diff --git a/docs/uk/docs/index.md b/docs/uk/docs/index.md index 4f089c4e1..0a6788502 100644 --- a/docs/uk/docs/index.md +++ b/docs/uk/docs/index.md @@ -40,7 +40,7 @@ FastAPI - це сучасний, швидкий (високопродуктив * **Швидкий**: дуже висока продуктивність, на рівні з **NodeJS** та **Go** (завдяки Starlette та Pydantic). [Один із найшвидших Python-фреймворків](#performance). * **Швидке написання коду**: пришвидшує розробку функціоналу приблизно на 200%–300%. * * **Менше помилок**: зменшує приблизно на 40% кількість помилок, спричинених людиною (розробником). * -* **Інтуїтивний**: чудова підтримка редакторами коду. Автодоповнення всюди. Менше часу на налагодження. +* **Інтуїтивний**: чудова підтримка редакторами коду. Автодоповнення всюди. Менше часу на налагодження. * **Простий**: спроєктований так, щоб бути простим у використанні та вивченні. Менше часу на читання документації. * **Короткий**: мінімізує дублювання коду. Кілька можливостей з кожного оголошення параметра. Менше помилок. * **Надійний**: ви отримуєте код, готовий до продакшину. З автоматичною інтерактивною документацією. @@ -127,7 +127,7 @@ FastAPI - це сучасний, швидкий (високопродуктив -Якщо ви створюєте застосунок CLI для використання в терміналі замість веб-API, зверніть увагу на **Typer**. +Якщо ви створюєте застосунок CLI для використання в терміналі замість веб-API, зверніть увагу на **Typer**. **Typer** - молодший брат FastAPI. І його задумано як **FastAPI для CLI**. ⌨️ 🚀 @@ -368,7 +368,7 @@ item: Item * Валідацію даних: * Автоматичні та зрозумілі помилки, коли дані некоректні. * Валідацію навіть для глибоко вкладених JSON-обʼєктів. -* Перетворення вхідних даних: з мережі до даних і типів Python. Читання з: +* Перетворення вхідних даних: з мережі до даних і типів Python. Читання з: * JSON. * Параметрів шляху. * Параметрів запиту. @@ -376,7 +376,7 @@ item: Item * Headers. * Forms. * Files. -* Перетворення вихідних даних: перетворення з даних і типів Python у мережеві дані (як JSON): +* Перетворення вихідних даних: перетворення з даних і типів Python у мережеві дані (як JSON): * Перетворення типів Python (`str`, `int`, `float`, `bool`, `list`, тощо). * Обʼєктів `datetime`. * Обʼєктів `UUID`. @@ -439,7 +439,7 @@ item: Item * Оголошення **параметрів** з інших різних місць, як-от: **headers**, **cookies**, **form fields** та **files**. * Як встановлювати **обмеження валідації** як `maximum_length` або `regex`. -* Дуже потужну і просту у використанні систему **Dependency Injection**. +* Дуже потужну і просту у використанні систему **Впровадження залежностей**. * Безпеку та автентифікацію, включно з підтримкою **OAuth2** з **JWT tokens** та **HTTP Basic** auth. * Досконаліші (але однаково прості) техніки для оголошення **глибоко вкладених моделей JSON** (завдяки Pydantic). * Інтеграцію **GraphQL** з Strawberry та іншими бібліотеками. @@ -524,7 +524,7 @@ FastAPI залежить від Pydantic і Starlette. * httpx - потрібно, якщо ви хочете використовувати `TestClient`. * jinja2 - потрібно, якщо ви хочете використовувати конфігурацію шаблонів за замовчуванням. -* python-multipart - потрібно, якщо ви хочете підтримувати «parsing» форм за допомогою `request.form()`. +* python-multipart - потрібно, якщо ви хочете підтримувати форми з «парсингом» через `request.form()`. Використовується FastAPI: diff --git a/docs/uk/docs/learn/index.md b/docs/uk/docs/learn/index.md index 6e28d414a..ad468fea1 100644 --- a/docs/uk/docs/learn/index.md +++ b/docs/uk/docs/learn/index.md @@ -1,5 +1,5 @@ # Навчання { #learn } -У цьому розділі надані вступні розділи та навчальні матеріали для вивчення **FastAPI**. +У цьому розділі надані вступні розділи та навчальні посібники для вивчення **FastAPI**. Це можна розглядати як **книгу**, **курс**, або **офіційний** та рекомендований спосіб освоїти FastAPI. 😎 diff --git a/docs/uk/docs/python-types.md b/docs/uk/docs/python-types.md index a82d13a28..deeeb2f9c 100644 --- a/docs/uk/docs/python-types.md +++ b/docs/uk/docs/python-types.md @@ -2,11 +2,11 @@ Python підтримує додаткові «підказки типів» (також звані «анотаціями типів»). -Ці **«підказки типів»** або анотації — це спеціальний синтаксис, що дозволяє оголошувати тип змінної. +Ці **«підказки типів»** або анотації — це спеціальний синтаксис, що дозволяє оголошувати тип змінної. За допомогою оголошення типів для ваших змінних редактори та інструменти можуть надати вам кращу підтримку. -Це лише **швидкий туторіал / нагадування** про підказки типів у Python. Він покриває лише мінімум, необхідний щоб використовувати їх з **FastAPI**... що насправді дуже мало. +Це лише **швидкий навчальний посібник / нагадування** про підказки типів у Python. Він покриває лише мінімум, необхідний щоб використовувати їх з **FastAPI**... що насправді дуже мало. **FastAPI** повністю базується на цих підказках типів, вони дають йому багато переваг і користі. @@ -22,7 +22,7 @@ Python підтримує додаткові «підказки типів» (т Давайте почнемо з простого прикладу: -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} Виклик цієї програми виводить: @@ -34,9 +34,9 @@ John Doe * Бере `first_name` та `last_name`. * Перетворює першу літеру кожного з них у верхній регістр за допомогою `title()`. -* Конкатенує їх разом із пробілом по середині. +* Конкатенує їх разом із пробілом по середині. -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### Редагуйте це { #edit-it } @@ -78,7 +78,7 @@ John Doe Це «підказки типів»: -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} Це не те саме, що оголошення значень за замовчуванням, як це було б з: @@ -106,15 +106,15 @@ John Doe Перевірте цю функцію, вона вже має підказки типів: -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} Оскільки редактор знає типи змінних, ви не тільки отримаєте автозаповнення, ви також отримаєте перевірку помилок: -Тепер ви знаєте, щоб виправити це, вам потрібно перетворити `age` у рядок за допомогою `str(age)`: +Тепер ви знаєте, щоб виправити це, вам потрібно перетворити `age` у строку за допомогою `str(age)`: -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} ## Оголошення типів { #declaring-types } @@ -133,29 +133,32 @@ John Doe * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} -### Generic-типи з параметрами типів { #generic-types-with-type-parameters } +### Модуль `typing` { #typing-module } -Існують деякі структури даних, які можуть містити інші значення, наприклад `dict`, `list`, `set` та `tuple`. І внутрішні значення також можуть мати свій тип. +Для деяких додаткових випадків використання може знадобитися імпортувати елементи зі стандартної бібліотеки, модуля `typing`. Наприклад, коли ви хочете оголосити, що щось має «будь-який тип», ви можете використати `Any` з `typing`: -Ці типи, які мають внутрішні типи, називаються «**generic**» типами. І оголосити їх можна навіть із внутрішніми типами. +```python +from typing import Any -Щоб оголосити ці типи та внутрішні типи, ви можете використовувати стандартний модуль Python `typing`. Він існує спеціально для підтримки цих підказок типів. -#### Новіші версії Python { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -Синтаксис із використанням `typing` **сумісний** з усіма версіями, від Python 3.6 до останніх, включаючи Python 3.9, Python 3.10 тощо. +### Generic типи { #generic-types } -У міру розвитку Python **новіші версії** мають покращену підтримку цих анотацій типів і в багатьох випадках вам навіть не потрібно буде імпортувати та використовувати модуль `typing` для оголошення анотацій типів. +Деякі типи можуть приймати «параметри типів» у квадратних дужках, щоб визначити їх внутрішні типи. Наприклад, «list строк» буде оголошений як `list[str]`. -Якщо ви можете вибрати новішу версію Python для свого проекту, ви зможете скористатися цією додатковою простотою. +Ці типи, які можуть приймати параметри типів, називаються **generic типами** або **generics**. -У всій документації є приклади, сумісні з кожною версією Python (коли є різниця). +Ви можете використовувати ті самі вбудовані типи як generics (з квадратними дужками та типами всередині): -Наприклад, «**Python 3.6+**» означає, що це сумісно з Python 3.6 або вище (включно з 3.7, 3.8, 3.9, 3.10 тощо). А «**Python 3.9+**» означає, що це сумісно з Python 3.9 або вище (включаючи 3.10 тощо). - -Якщо ви можете використовувати **останні версії Python**, використовуйте приклади для останньої версії — вони матимуть **найкращий і найпростіший синтаксис**, наприклад, «**Python 3.10+**». +* `list` +* `tuple` +* `set` +* `dict` #### List { #list } @@ -167,7 +170,7 @@ John Doe Оскільки список є типом, який містить деякі внутрішні типи, ви поміщаєте їх у квадратні дужки: -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} /// info | Інформація @@ -193,7 +196,7 @@ John Doe Ви повинні зробити те ж саме, щоб оголосити `tuple` і `set`: -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} Це означає: @@ -208,56 +211,32 @@ John Doe Другий параметр типу для значень у `dict`: -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} Це означає: * Змінна `prices` — це `dict`: - * Ключі цього `dict` мають тип `str` (скажімо, назва кожного елементу). - * Значення цього `dict` мають тип `float` (скажімо, ціна кожного елементу). + * Ключі цього `dict` мають тип `str` (скажімо, назва кожного предмета). + * Значення цього `dict` мають тип `float` (скажімо, ціна кожного предмета). #### Union { #union } Ви можете оголосити, що змінна може бути будь-яким із **кількох типів**, наприклад `int` або `str`. -У Python 3.6 і вище (включаючи Python 3.10) ви можете використовувати тип `Union` з `typing` і вставляти в квадратні дужки можливі типи, які можна прийняти. +Щоб визначити це, використовуйте вертикальну риску (`|`), щоб розділити обидва типи. -У Python 3.10 також є **новий синтаксис**, у якому ви можете розділити можливі типи за допомогою вертикальної смуги (`|`). - -//// tab | Python 3.10+ +Це називається «union», тому що змінна може бути чимось із об’єднання цих двох множин типів. ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -В обох випадках це означає, що `item` може бути `int` або `str`. +Це означає, що `item` може бути `int` або `str`. #### Можливо `None` { #possibly-none } Ви можете оголосити, що значення може мати тип, наприклад `str`, але також може бути `None`. -У Python 3.6 і вище (включаючи Python 3.10) ви можете оголосити його, імпортувавши та використовуючи `Optional` з модуля `typing`. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -Використання `Optional[str]` замість просто `str` дозволить редактору допомогти вам виявити помилки, коли ви могли б вважати, що значенням завжди є `str`, хоча насправді воно також може бути `None`. - -`Optional[Something]` насправді є скороченням для `Union[Something, None]`, вони еквівалентні. - -Це також означає, що в Python 3.10 ви можете використовувати `Something | None`: - //// tab | Python 3.10+ ```Python hl_lines="1" @@ -266,96 +245,7 @@ John Doe //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ alternative - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### Використання `Union` або `Optional` { #using-union-or-optional } - -Якщо ви використовуєте версію Python нижче 3.10, ось порада з моєї дуже **суб’єктивної** точки зору: - -* 🚨 Уникайте використання `Optional[SomeType]` -* Натомість ✨ **використовуйте `Union[SomeType, None]`** ✨. - -Обидва варіанти еквівалентні й «під капотом» це одне й те саме, але я рекомендую `Union` замість `Optional`, тому що слово «**optional**» може створювати враження, ніби значення є необов’язковим, хоча насправді це означає «воно може бути `None`», навіть якщо воно не є необов’язковим і все одно є обов’язковим. - -Я вважаю, що `Union[SomeType, None]` більш явно показує, що саме мається на увазі. - -Це лише про слова й назви. Але ці слова можуть впливати на те, як ви та ваша команда думаєте про код. - -Як приклад, розгляньмо цю функцію: - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -Параметр `name` визначено як `Optional[str]`, але він **не є необов’язковим**, ви не можете викликати функцію без параметра: - -```Python -say_hi() # Ой, ні, це викликає помилку! 😱 -``` - -Параметр `name` **все ще є обов’язковим** (не *optional*), тому що він не має значення за замовчуванням. Водночас `name` приймає `None` як значення: - -```Python -say_hi(name=None) # Це працює, None є валідним 🎉 -``` - -Добра новина: щойно ви перейдете на Python 3.10, вам не доведеться про це хвилюватися, адже ви зможете просто використовувати `|` для визначення об’єднань типів: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -І тоді вам не доведеться хвилюватися про назви на кшталт `Optional` і `Union`. 😎 - -#### Generic типи { #generic-types } - -Ці типи, які приймають параметри типу у квадратних дужках, називаються **Generic types** or **Generics**, наприклад: - -//// tab | Python 3.10+ - -Ви можете використовувати ті самі вбудовані типи як generic (з квадратними дужками та типами всередині): - -* `list` -* `tuple` -* `set` -* `dict` - -І так само, як і в попередніх версіях Python, з модуля `typing`: - -* `Union` -* `Optional` -* ...та інші. - -У Python 3.10 як альтернативу використанню generic `Union` та `Optional` ви можете використовувати вертикальну смугу (`|`) для оголошення об’єднань типів — це значно краще й простіше. - -//// - -//// tab | Python 3.9+ - -Ви можете використовувати ті самі вбудовані типи як generic (з квадратними дужками та типами всередині): - -* `list` -* `tuple` -* `set` -* `dict` - -І generic з модуля `typing`: - -* `Union` -* `Optional` -* ...та інші. - -//// +Використання `str | None` замість просто `str` дозволить редактору допомогти вам виявити помилки, коли ви могли б вважати, що значенням завжди є `str`, хоча насправді воно також може бути `None`. ### Класи як типи { #classes-as-types } @@ -363,11 +253,11 @@ say_hi(name=None) # Це працює, None є валідним 🎉 Скажімо, у вас є клас `Person` з імʼям: -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} Потім ви можете оголосити змінну типу `Person`: -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} І знову ж таки, ви отримуєте всю підтримку редактора: @@ -401,21 +291,15 @@ say_hi(name=None) # Це працює, None є валідним 🎉 **FastAPI** повністю базується на Pydantic. -Ви побачите набагато більше цього всього на практиці в [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}. - -/// tip | Порада - -Pydantic має спеціальну поведінку, коли ви використовуєте `Optional` або `Union[Something, None]` без значення за замовчуванням; детальніше про це можна прочитати в документації Pydantic про Required Optional fields. - -/// +Ви побачите набагато більше цього всього на практиці в [Навчальний посібник - Посібник користувача](tutorial/index.md){.internal-link target=_blank}. ## Підказки типів з анотаціями метаданих { #type-hints-with-metadata-annotations } -У Python також є можливість додавати **додаткові метадані** до цих підказок типів за допомогою `Annotated`. +У Python також є можливість додавати **додаткові метадані** до цих підказок типів за допомогою `Annotated`. -Починаючи з Python 3.9, `Annotated` є частиною стандартної бібліотеки, тож ви можете імпортувати його з `typing`. +Ви можете імпортувати `Annotated` з `typing`. -{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} Сам Python нічого не робить із цим `Annotated`. А для редакторів та інших інструментів тип усе ще є `str`. @@ -435,7 +319,7 @@ Pydantic має спеціальну поведінку, коли ви вико /// -## Анотації типів у **FastAPI** { #type-hints-in-fastapi } +## Підказки типів у **FastAPI** { #type-hints-in-fastapi } **FastAPI** використовує ці підказки типів для виконання кількох речей. @@ -453,12 +337,12 @@ Pydantic має спеціальну поведінку, коли ви вико * **Документування** API за допомогою OpenAPI: * який потім використовується для автоматичної інтерактивної документації користувальницьких інтерфейсів. -Все це може здатися абстрактним. Не хвилюйтеся. Ви побачите все це в дії в [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}. +Все це може здатися абстрактним. Не хвилюйтеся. Ви побачите все це в дії в [Навчальний посібник - Посібник користувача](tutorial/index.md){.internal-link target=_blank}. Важливо те, що за допомогою стандартних типів Python в одному місці (замість того, щоб додавати більше класів, декораторів тощо), **FastAPI** зробить багато роботи за вас. /// info | Інформація -Якщо ви вже пройшли весь туторіал і повернулися, щоб дізнатися більше про типи, ось хороший ресурс: «шпаргалка» від `mypy`. +Якщо ви вже пройшли весь навчальний посібник і повернулися, щоб дізнатися більше про типи, ось хороший ресурс: «шпаргалка» від `mypy`. /// diff --git a/docs/uk/docs/tutorial/background-tasks.md b/docs/uk/docs/tutorial/background-tasks.md index 6d7804195..71266a8b1 100644 --- a/docs/uk/docs/tutorial/background-tasks.md +++ b/docs/uk/docs/tutorial/background-tasks.md @@ -1,6 +1,6 @@ # Фонові задачі { #background-tasks } -Ви можете створювати фонові задачі, які будуть виконуватися *після* повернення відповіді. +Ви можете створювати фонові задачі, які будуть виконуватися після повернення відповіді. Це корисно для операцій, які потрібно виконати після обробки запиту, але клієнту не обов’язково чекати завершення цієї операції перед отриманням відповіді. @@ -13,9 +13,9 @@ ## Використання `BackgroundTasks` { #using-backgroundtasks } -Спочатку імпортуйте `BackgroundTasks` і оголосіть параметр у вашій *функції операції шляху* з анотацією типу `BackgroundTasks`: +Спочатку імпортуйте `BackgroundTasks` і оголосіть параметр у вашій функції операції шляху з анотацією типу `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} **FastAPI** створить для вас об’єкт типу `BackgroundTasks` і передасть його як цей параметр. @@ -31,13 +31,13 @@ І оскільки операція запису не використовує `async` та `await`, ми визначаємо функцію як звичайну `def`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} ## Додавання фонової задачі { #add-the-background-task } -Усередині вашої *функції операції шляху*, передайте функцію задачі в об'єкт *background tasks*, використовуючи метод `.add_task()`: +Усередині вашої функції операції шляху, передайте функцію задачі в об'єкт background tasks, використовуючи метод `.add_task()`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} `.add_task()` приймає аргументи: @@ -47,17 +47,17 @@ ## Впровадження залежностей { #dependency-injection } -Використання `BackgroundTasks` також працює з системою впровадження залежностей. Ви можете оголосити параметр типу `BackgroundTasks` на різних рівнях: у *функції операції шляху*, у залежності (dependable), у підзалежності тощо. +Використання `BackgroundTasks` також працює з системою впровадження залежностей. Ви можете оголосити параметр типу `BackgroundTasks` на різних рівнях: у функції операції шляху, у залежності (залежний), у підзалежності тощо. -**FastAPI** знає, як діяти в кожному випадку і як повторно використовувати один і той самий об'єкт, щоб усі фонові задачі були об’єднані та виконувалися у фоновому режимі після завершення основного запиту: +**FastAPI** знає, як діяти в кожному випадку і як повторно використовувати один і той самий об'єкт, щоб усі фонові задачі були об’єднані та виконувалися у фоновому режимі після завершення основного запиту: {* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *} -У цьому прикладі повідомлення будуть записані у файл `log.txt` *після* того, як відповідь буде надіслана. +У цьому прикладі повідомлення будуть записані у файл `log.txt` після того, як відповідь буде надіслана. -Якщо у запиті був переданий query, він буде записаний у лог у фоновій задачі. +Якщо у запиті був переданий параметр запиту, він буде записаний у лог у фоновій задачі. -А потім інша фонова задача, згенерована у *функції операції шляху*, запише повідомлення з використанням path параметра `email`. +А потім інша фонова задача, згенерована у функції операції шляху, запише повідомлення з використанням параметра шляху `email`. ## Технічні деталі { #technical-details } @@ -65,7 +65,7 @@ Він імпортується/включається безпосередньо у FastAPI, щоб ви могли імпортувати його з `fastapi` і випадково не імпортували альтернативний `BackgroundTask` (без `s` в кінці) з `starlette.background`. -Якщо використовувати лише `BackgroundTasks` (а не `BackgroundTask`), то його можна передавати як параметр у *функції операції шляху*, і **FastAPI** подбає про все інше, так само як і про використання об'єкта `Request`. +Якщо використовувати лише `BackgroundTasks` (а не `BackgroundTask`), то його можна передавати як параметр у функції операції шляху, і **FastAPI** подбає про все інше, так само як і про використання об'єкта `Request`. Також можна використовувати `BackgroundTask` окремо в FastAPI, але для цього вам доведеться створити об'єкт у коді та повернути Starlette `Response`, включаючи його. @@ -81,4 +81,4 @@ ## Підсумок { #recap } -Імпортуйте та використовуйте `BackgroundTasks` як параметри у *функціях операції шляху* та залежностях, щоб додавати фонові задачі. +Імпортуйте та використовуйте `BackgroundTasks` як параметри у функціях операції шляху та залежностях, щоб додавати фонові задачі. diff --git a/docs/uk/docs/tutorial/body-multiple-params.md b/docs/uk/docs/tutorial/body-multiple-params.md index f541beea7..a0db2b186 100644 --- a/docs/uk/docs/tutorial/body-multiple-params.md +++ b/docs/uk/docs/tutorial/body-multiple-params.md @@ -18,7 +18,7 @@ ## Декілька параметрів тіла { #multiple-body-parameters } -У попередньому прикладі *операції шляху* очікували б JSON-тіло з атрибутами `Item`, наприклад: +У попередньому прикладi *операції шляху* очікували б JSON-тіло з атрибутами `Item`, наприклад: ```JSON { @@ -106,13 +106,6 @@ q: str | None = None ``` -Або в Python 3.9: - -```Python -q: Union[str, None] = None -``` - - Наприклад: {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/uk/docs/tutorial/body-nested-models.md b/docs/uk/docs/tutorial/body-nested-models.md index 6d0669358..a56ae484d 100644 --- a/docs/uk/docs/tutorial/body-nested-models.md +++ b/docs/uk/docs/tutorial/body-nested-models.md @@ -164,7 +164,7 @@ images: list[Image] наприклад: -{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} ## Підтримка в редакторі всюди { #editor-support-everywhere } @@ -194,7 +194,7 @@ images: list[Image] У цьому випадку ви можете приймати будь-який `dict`, якщо його ключі — це `int`, а значення — `float`: -{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} /// tip | Порада @@ -216,6 +216,6 @@ images: list[Image] * Підтримка в редакторі (автодоповнення всюди!) * Конвертація даних (парсинг/сериалізація) -* Валідацію даних +* Валідація даних * Документація схем * Автоматичне створення документації diff --git a/docs/uk/docs/tutorial/body.md b/docs/uk/docs/tutorial/body.md index ca1f308ab..615f0274c 100644 --- a/docs/uk/docs/tutorial/body.md +++ b/docs/uk/docs/tutorial/body.md @@ -8,7 +8,7 @@ Щоб оголосити тіло **запиту**, ви використовуєте Pydantic моделі з усією їх потужністю та перевагами. -/// info | Інформація +/// info Щоб надіслати дані, ви повинні використовувати один із: `POST` (більш поширений), `PUT`, `DELETE` або `PATCH`. @@ -73,8 +73,8 @@ * Якщо дані недійсні, він поверне гарну та чітку помилку, вказуючи, де саме і які дані були неправильними. * Надавати отримані дані у параметрі `item`. * Оскільки ви оголосили його у функції як тип `Item`, ви також матимете всю підтримку редактора (автозаповнення, тощо) для всіх атрибутів та їх типів. -* Генерувати JSON Schema визначення для вашої моделі, ви також можете використовувати їх де завгодно, якщо це має сенс для вашого проекту. -* Ці схеми будуть частиною згенерованої схеми OpenAPI і використовуватимуться автоматичною документацією UIs. +* Генерувати визначення Схеми JSON для вашої моделі, ви також можете використовувати їх де завгодно, якщо це має сенс для вашого проекту. +* Ці схеми будуть частиною згенерованої схеми OpenAPI і використовуватимуться автоматичною документацією UIs. ## Автоматична документація { #automatic-docs } @@ -108,7 +108,7 @@ -/// tip | Порада +/// tip Якщо ви використовуєте PyCharm як ваш редактор, ви можете використати Pydantic PyCharm Plugin. @@ -151,11 +151,11 @@ * Якщо параметр має **сингулярний тип** (наприклад, `int`, `float`, `str`, `bool` тощо), він буде інтерпретуватися як параметр **запиту**. * Якщо параметр оголошується як тип **Pydantic моделі**, він інтерпретується як **тіло** **запиту**. -/// note | Примітка +/// note FastAPI буде знати, що значення `q` не є обов'язковим через значення за замовчуванням `= None`. -`str | None` (Python 3.10+) або `Union` у `Union[str, None]` (Python 3.9+) FastAPI не використовує, щоб визначити, що значення не є обов’язковим — він знатиме, що воно не є обов’язковим, тому що має значення за замовчуванням `= None`. +`str | None` FastAPI не використовує, щоб визначити, що значення не є обов’язковим - він знатиме, що воно не є обов’язковим, тому що має значення за замовчуванням `= None`. Але додавання анотацій типів дозволить вашому редактору надати вам кращу підтримку та виявляти помилки. @@ -163,4 +163,4 @@ FastAPI буде знати, що значення `q` не є обов'язко ## Без Pydantic { #without-pydantic } -Якщо ви не хочете використовувати моделі Pydantic, ви також можете використовувати параметри **Body**. Перегляньте документацію для [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. +Якщо ви не хочете використовувати моделі Pydantic, ви також можете використовувати параметри **Body**. Перегляньте документацію для [Тіло - Кілька параметрів: Окремі значення в тілі](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. diff --git a/docs/uk/docs/tutorial/cookie-param-models.md b/docs/uk/docs/tutorial/cookie-param-models.md index 3c6407716..dab57c536 100644 --- a/docs/uk/docs/tutorial/cookie-param-models.md +++ b/docs/uk/docs/tutorial/cookie-param-models.md @@ -1,8 +1,8 @@ # Моделі параметрів Cookie { #cookie-parameter-models } -Якщо у Вас є група **cookies**, які пов'язані між собою, Ви можете створити **Pydantic-модель**, щоб оголосити їх. 🍪 +Якщо у вас є група **cookies**, які пов'язані між собою, ви можете створити **Pydantic-модель**, щоб оголосити їх. 🍪 -Це дозволить Вам повторно **використовувати модель** у **різних місцях**, а також оголосити валідацію та метадані для всіх параметрів одночасно. 😎 +Це дозволить вам повторно **використовувати модель** у **різних місцях**, а також оголосити валідацію та метадані для всіх параметрів одночасно. 😎 /// note | Примітка @@ -18,11 +18,11 @@ ## Cookie з Pydantic-моделлю { #cookies-with-a-pydantic-model } -Оголосіть **cookie**-параметри, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть параметр як `Cookie`: +Оголосіть **cookie**-параметри, які вам потрібні, у **Pydantic-моделі**, а потім оголосіть параметр як `Cookie`: {* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} -**FastAPI** буде **витягувати** дані для **кожного поля** з **cookies**, отриманих у запиті, і передавати Вам Pydantic-модель, яку Ви визначили. +**FastAPI** буде **витягувати** дані для **кожного поля** з **cookies**, отриманих у запиті, і передавати вам Pydantic-модель, яку ви визначили. ## Перевірка у документації { #check-the-docs } @@ -36,17 +36,17 @@ Майте на увазі, що оскільки **браузери обробляють cookies** особливим чином і «за лаштунками», вони **не** дозволяють **JavaScript** легко з ними працювати. -Якщо Ви зайдете до **інтерфейсу документації API** за адресою `/docs`, Ви зможете побачити **документацію** для cookies у Ваших *операціях шляху*. +Якщо ви зайдете до **інтерфейсу документації API** за адресою `/docs`, ви зможете побачити **документацію** для cookies у ваших *операціях шляху*. -Але навіть якщо Ви заповните дані й натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, cookies не будуть відправлені, і Ви побачите **помилку**, ніби Ви не ввели жодних значень. +Але навіть якщо ви заповните дані й натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, cookies не будуть відправлені, і ви побачите **помилку**, ніби ви не ввели жодних значень. /// ## Заборона додаткових cookie { #forbid-extra-cookies } -У деяких спеціальних випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** cookies, які хочете отримувати. +У деяких спеціальних випадках (ймовірно, не дуже поширених) ви можете захотіти **обмежити** cookies, які хочете отримувати. -Ваша API тепер має можливість контролювати власну згоду на cookie. 🤪🍪 +Ваш API тепер має можливість контролювати власну згоду на cookies. 🤪🍪 Ви можете використовувати налаштування моделі Pydantic, щоб `forbid` будь-які `extra` поля: @@ -54,9 +54,9 @@ Якщо клієнт спробує надіслати якісь **додаткові cookies**, він отримає відповідь з **помилкою**. -Бідні банери cookie, які так старанно намагаються отримати Вашу згоду, щоб API її відхилила. 🍪 +Бідні банери cookie, які так старанно намагаються отримати вашу згоду, щоб API її відхилила. 🍪 -Наприклад, якщо клієнт спробує надіслати cookie `santa_tracker` зі значенням `good-list-please`, він отримає відповідь з помилкою, яка повідомить, що `santa_tracker` cookie не дозволено: +Наприклад, якщо клієнт спробує надіслати cookie `santa_tracker` зі значенням `good-list-please`, він отримає відповідь з помилкою, яка повідомить, що `santa_tracker` cookie не дозволено: ```json { @@ -73,4 +73,4 @@ ## Підсумок { #summary } -Ви можете використовувати **Pydantic-моделі** для оголошення **cookies** у **FastAPI**. 😎 +Ви можете використовувати **Pydantic-моделі** для оголошення **cookies** у **FastAPI**. 😎 diff --git a/docs/uk/docs/tutorial/cookie-params.md b/docs/uk/docs/tutorial/cookie-params.md index 8a5b44e8a..3a2e6fa24 100644 --- a/docs/uk/docs/tutorial/cookie-params.md +++ b/docs/uk/docs/tutorial/cookie-params.md @@ -1,6 +1,6 @@ -# Параметри Cookie { #cookie-parameters } +# Параметри кукі { #cookie-parameters } -Ви можете визначати параметри Cookie таким же чином, як визначаються параметри `Query` і `Path`. +Ви можете визначати параметри кукі таким же чином, як визначаються параметри `Query` і `Path`. ## Імпорт `Cookie` { #import-cookie } @@ -10,7 +10,7 @@ ## Визначення параметрів `Cookie` { #declare-cookie-parameters } -Потім визначте параметри cookie, використовуючи таку ж конструкцію як для `Path` і `Query`. +Потім визначте параметри кукі, використовуючи таку ж конструкцію як для `Path` і `Query`. Ви можете визначити значення за замовчуванням, а також усі додаткові параметри валідації чи анотації: @@ -26,20 +26,20 @@ /// info -Для визначення cookies ви маєте використовувати `Cookie`, тому що в іншому випадку параметри будуть інтерпритовані, як параметри запиту. +Для визначення кукі ви маєте використовувати `Cookie`, тому що в іншому випадку параметри будуть інтерпретовані як параметри запиту. /// /// info -Майте на увазі, що оскільки **браузери обробляють cookies** спеціальним чином і за лаштунками, вони **не** дозволяють **JavaScript** легко взаємодіяти з ними. +Майте на увазі, що оскільки **браузери обробляють кукі** спеціальним чином і за лаштунками, вони **не** дозволяють **JavaScript** легко взаємодіяти з ними. -Якщо ви перейдете до **інтерфейсу документації API** за адресою `/docs`, ви зможете побачити **документацію** для cookies для ваших *операцій шляху*. +Якщо ви перейдете до **інтерфейсу документації API** за адресою `/docs`, ви зможете побачити **документацію** для кукі для ваших *операцій шляху*. -Але навіть якщо ви **заповните дані** і натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, cookies не буде надіслано, і ви побачите повідомлення про **помилку**, ніби ви не ввели жодних значень. +Але навіть якщо ви **заповните дані** і натиснете "Execute", оскільки інтерфейс документації працює з **JavaScript**, кукі не буде надіслано, і ви побачите повідомлення про **помилку**, ніби ви не ввели жодних значень. /// ## Підсумки { #recap } -Визначайте cookies за допомогою `Cookie`, використовуючи той же спільний шаблон, що і `Query` та `Path`. +Визначайте кукі за допомогою `Cookie`, використовуючи той же спільний шаблон, що і `Query` та `Path`. diff --git a/docs/uk/docs/tutorial/cors.md b/docs/uk/docs/tutorial/cors.md index f3ed8a7d9..5c959cef1 100644 --- a/docs/uk/docs/tutorial/cors.md +++ b/docs/uk/docs/tutorial/cors.md @@ -1,6 +1,6 @@ # CORS (Обмін ресурсами між різними джерелами) { #cors-cross-origin-resource-sharing } -CORS або "Cross-Origin Resource Sharing" є ситуація, коли фронтенд, що працює в браузері, містить JavaScript-код, який взаємодіє з бекендом, розташованим в іншому "джерелі" (origin). +CORS або «Cross-Origin Resource Sharing» є ситуація, коли фронтенд, що працює в браузері, містить JavaScript-код, який взаємодіє з бекендом, розташованим в іншому «джерелі» (origin). ## Джерело (Origin) { #origin } @@ -13,50 +13,49 @@ * `https://localhost` * `http://localhost:8080` -Навіть якщо вони всі містять `localhost`, вони мають різні протоколи або порти, що робить їх окремими "джерелами". +Навіть якщо вони всі містять `localhost`, вони мають різні протоколи або порти, що робить їх окремими «джерелами». ## Кроки { #steps } -Припустимо, що Ваш фронтенд працює в браузері на `http://localhost:8080`, а його JavaScript намагається відправити запит до бекенду, який працює на `http://localhost` (Оскільки ми не вказуємо порт, браузер за замовчуванням припускає порт `80`). +Припустимо, що ваш фронтенд працює в браузері на `http://localhost:8080`, а його JavaScript намагається відправити запит до бекенду, який працює на `http://localhost` (Оскільки ми не вказуємо порт, браузер за замовчуванням припускає порт `80`). Потім браузер надішле HTTP-запит `OPTIONS` до бекенду на порту `:80`, і якщо бекенд надішле відповідні заголовки, що дозволяють комунікацію з цього іншого джерела (`http://localhost:8080`), тоді браузер на порту `:8080` дозволить JavaScript у фронтенді надіслати свій запит до бекенду на порту `:80`. -Щоб досягти цього, бекенд на порту `:80` повинен мати список "дозволених джерел". +Щоб досягти цього, бекенд на порту `:80` повинен мати список «дозволених джерел». У цьому випадку список має містити `http://localhost:8080`, щоб фронтенд на порту `:8080` працював коректно. -## Символьне підставляння { #wildcards } +## Дикі карти { #wildcards } -Можна також оголосити список як `"*"` ("символьне підставляння"), що означає дозвіл для всіх джерел. +Можна також оголосити список як `"*"` (дика карта), що означає дозвіл для всіх джерел. -Однак це дозволить лише певні типи комунікації, виключаючи все, що пов'язане з обліковими даними: Cookies, заголовки авторизації, такі як ті, що використовуються з Bearer Tokens, тощо. +Однак це дозволить лише певні типи комунікації, виключаючи все, що пов'язане з обліковими даними: Cookies, заголовки авторизації, як-от ті, що використовуються з токенами носія, тощо. Тому для коректної роботи краще явно вказувати дозволені джерела. ## Використання `CORSMiddleware` { #use-corsmiddleware } -Ви можете налаштувати це у Вашому додатку **FastAPI** за допомогою `CORSMiddleware`. +Ви можете налаштувати це у вашому додатку **FastAPI** за допомогою `CORSMiddleware`. * Імпортуйте `CORSMiddleware`. * Створіть список дозволених джерел (у вигляді рядків). -* Додайте його як "middleware" у Ваш додаток **FastAPI**. +* Додайте його як «проміжне програмне забезпечення» у ваш додаток **FastAPI**. - -Також можна вказати, чи дозволяє Ваш бекенд: +Також можна вказати, чи дозволяє ваш бекенд: * Облікові дані (заголовки авторизації, Cookies, тощо). * Конкретні HTTP-методи (`POST`, `PUT`) або всі за допомогою `"*"` * Конкретні HTTP-заголовки або всі за допомогою `"*"`. -{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} -Параметри за замовчуванням у реалізації `CORSMiddleware` є досить обмеженими, тому Вам потрібно явно увімкнути конкретні джерела, методи або заголовки, щоб браузерам було дозволено використовувати їх у міждоменному контексті. +Параметри за замовчуванням у реалізації `CORSMiddleware` є досить обмеженими, тому вам потрібно явно увімкнути конкретні джерела, методи або заголовки, щоб браузерам було дозволено використовувати їх у міждоменному контексті. Підтримуються такі аргументи: -* `allow_origins` - Список джерел, яким дозволено здійснювати міждоменні запити. Наприклад `['https://example.org', 'https://www.example.org']`. Ви можете використовувати `['*']`, щоб дозволити будь-яке джерело. +* `allow_origins` - Список джерел, яким дозволено здійснювати міждоменні запити. Наприклад `['https://example.org', 'https://www.example.org']`. Ви можете використовувати `['*']`, щоб дозволити будь-яке джерело. * `allow_origin_regex` - Рядок регулярного виразу для відповідності джерелам, яким дозволено здійснювати міждоменні запити. Наприклад, `'https://.*\.example\.org'`. * `allow_methods` - Список HTTP-методів, дозволених для міждоменних запитів. За замовчуванням `['GET']`. Ви можете використовувати `['*']`, щоб дозволити всі стандартні методи. * `allow_headers` - Список HTTP-заголовків запиту, які підтримуються для міждоменних запитів. За замовчуванням `[]`. Ви можете використовувати `['*']`, щоб дозволити всі заголовки. Заголовки `Accept`, `Accept-Language`, `Content-Language` і `Content-Type` завжди дозволені для простих CORS-запитів. @@ -67,7 +66,7 @@ * `expose_headers` - Вказує, які заголовки відповіді повинні бути доступні для браузера. За замовчуванням `[]`. * `max_age` - Встановлює максимальний час (у секундах) для кешування CORS-відповідей у браузерах. За замовчуванням `600`. -Цей middleware обробляє два типи HTTP-запитів... +Це проміжне програмне забезпечення обробляє два типи HTTP-запитів... ### Попередні CORS-запити { #cors-preflight-requests } @@ -81,7 +80,7 @@ ## Додаткова інформація { #more-info } -Більше про CORS можна дізнатися в документації Mozilla про CORS. +Більше про CORS можна дізнатися в документації Mozilla про CORS. /// note | Технічні деталі diff --git a/docs/uk/docs/tutorial/debugging.md b/docs/uk/docs/tutorial/debugging.md index 0db418dcc..679018cc2 100644 --- a/docs/uk/docs/tutorial/debugging.md +++ b/docs/uk/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ У вашому FastAPI-додатку імпортуйте та запустіть `uvicorn` безпосередньо: -{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} ### Про `__name__ == "__main__"` { #about-name-main } diff --git a/docs/uk/docs/tutorial/first-steps.md b/docs/uk/docs/tutorial/first-steps.md index 5f3750010..f03a120a0 100644 --- a/docs/uk/docs/tutorial/first-steps.md +++ b/docs/uk/docs/tutorial/first-steps.md @@ -2,7 +2,7 @@ Найпростіший файл FastAPI може виглядати так: -{* ../../docs_src/first_steps/tutorial001_py39.py *} +{* ../../docs_src/first_steps/tutorial001_py310.py *} Скопіюйте це до файлу `main.py`. @@ -183,7 +183,7 @@ Deploying to FastAPI Cloud... ### Крок 1: імпортуємо `FastAPI` { #step-1-import-fastapi } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[1] *} `FastAPI` — це клас у Python, який надає всю функціональність для вашого API. @@ -197,7 +197,7 @@ Deploying to FastAPI Cloud... ### Крок 2: створюємо «екземпляр» `FastAPI` { #step-2-create-a-fastapi-instance } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[3] *} Тут змінна `app` буде «екземпляром» класу `FastAPI`. @@ -221,7 +221,7 @@ https://example.com/items/foo /items/foo ``` -/// info | Інформація +/// info «Шлях» також зазвичай називають «ендпоінтом» або «маршрутом». @@ -266,12 +266,12 @@ https://example.com/items/foo #### Визначте *декоратор операції шляху* { #define-a-path-operation-decorator } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[6] *} Декоратор `@app.get("/")` повідомляє **FastAPI**, що функція одразу нижче відповідає за обробку запитів, які надходять до: * шляху `/` -* використовуючи get операцію +* використовуючи get операція /// info | `@decorator` Інформація @@ -300,7 +300,7 @@ https://example.com/items/foo * `@app.patch()` * `@app.trace()` -/// tip | Порада +/// tip Ви можете використовувати кожну операцію (HTTP-метод) як забажаєте. @@ -320,7 +320,7 @@ https://example.com/items/foo * **операція**: це `get`. * **функція**: це функція нижче «декоратора» (нижче `@app.get("/")`). -{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[7] *} Це функція Python. @@ -332,9 +332,9 @@ https://example.com/items/foo Ви також можете визначити її як звичайну функцію замість `async def`: -{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py310.py hl[7] *} -/// note | Примітка +/// note Якщо ви не знаєте різницю, подивіться [Асинхронність: *«Поспішаєте?»*](../async.md#in-a-hurry){.internal-link target=_blank}. @@ -342,7 +342,7 @@ https://example.com/items/foo ### Крок 5: поверніть вміст { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[8] *} Ви можете повернути `dict`, `list`, а також окремі значення `str`, `int` тощо. diff --git a/docs/uk/docs/tutorial/handling-errors.md b/docs/uk/docs/tutorial/handling-errors.md index 53b8b12f6..28a83127f 100644 --- a/docs/uk/docs/tutorial/handling-errors.md +++ b/docs/uk/docs/tutorial/handling-errors.md @@ -25,7 +25,7 @@ ### Імпорт `HTTPException` { #import-httpexception } -{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *} +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[1] *} ### Згенеруйте `HTTPException` у своєму коді { #raise-an-httpexception-in-your-code } @@ -39,7 +39,7 @@ У цьому прикладі, коли клієнт запитує елемент за ID, якого не існує, згенеруйте виключення зі статус-кодом `404`: -{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *} +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[11] *} ### Отримана відповідь { #the-resulting-response } @@ -77,7 +77,7 @@ Але якщо вам знадобиться це для складного сценарію, ви можете додати власні заголовки: -{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial002_py310.py hl[14] *} ## Встановлення власних обробників виключень { #install-custom-exception-handlers } @@ -89,7 +89,7 @@ Ви можете додати власний обробник виключень за допомогою `@app.exception_handler()`: -{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *} +{* ../../docs_src/handling_errors/tutorial003_py310.py hl[5:7,13:18,24] *} Тут, якщо ви звернетеся до `/unicorns/yolo`, *операція шляху* згенерує (`raise`) `UnicornException`. @@ -127,7 +127,7 @@ Обробник виключень отримає `Request` і саме виключення. -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[2,14:19] *} Тепер, якщо ви перейдете за посиланням `/items/foo`, замість того, щоб отримати стандартну JSON-помилку: @@ -159,7 +159,7 @@ Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to pa Наприклад, ви можете захотіти повернути відповідь у вигляді простого тексту замість JSON для цих помилок: -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[3:4,9:11,25] *} /// note | Технічні деталі @@ -183,7 +183,7 @@ Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to pa Ви можете використовувати це під час розробки свого додатка, щоб логувати тіло запиту та налагоджувати його, повертати користувачеві тощо. -{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial005_py310.py hl[14] *} Тепер спробуйте надіслати некоректний елемент, наприклад: @@ -239,6 +239,6 @@ from starlette.exceptions import HTTPException as StarletteHTTPException Якщо ви хочете використовувати виключення разом із такими ж обробниками виключень за замовчуванням, як у **FastAPI**, ви можете імпортувати та повторно використати обробники виключень за замовчуванням із `fastapi.exception_handlers`: -{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *} +{* ../../docs_src/handling_errors/tutorial006_py310.py hl[2:5,15,21] *} У цьому прикладі ви просто друкуєте помилку з дуже виразним повідомленням, але ви зрозуміли основну ідею. Ви можете використовувати виключення, а потім просто повторно використати обробники виключень за замовчуванням. diff --git a/docs/uk/docs/tutorial/header-param-models.md b/docs/uk/docs/tutorial/header-param-models.md index c080c19f0..dd734eaee 100644 --- a/docs/uk/docs/tutorial/header-param-models.md +++ b/docs/uk/docs/tutorial/header-param-models.md @@ -1,8 +1,8 @@ # Моделі параметрів заголовків { #header-parameter-models } -Якщо у Вас є група пов’язаних **параметрів заголовків**, Ви можете створити **Pydantic модель** для їх оголошення. +Якщо у вас є група пов’язаних **параметрів заголовків**, ви можете створити **Pydantic модель** для їх оголошення. -Це дозволить Вам повторно **використовувати модель** в **різних місцях**, а також оголосити валідації та метадані для всіх параметрів одночасно. 😎 +Це дозволить вам повторно **використовувати модель** в **різних місцях**, а також оголосити валідації та метадані для всіх параметрів одночасно. 😎 /// note | Примітка @@ -16,7 +16,7 @@ {* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *} -**FastAPI** буде **витягувати** дані для **кожного поля** з **заголовків** у запиті та передавати їх у створену Вами Pydantic модель. +**FastAPI** буде **витягувати** дані для **кожного поля** з **заголовків** у запиті та передавати їх у створену вами Pydantic модель. ## Перевірка в документації { #check-the-docs } @@ -28,7 +28,7 @@ ## Заборонити додаткові заголовки { #forbid-extra-headers } -У деяких особливих випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** заголовки, які хочете отримати. +У деяких особливих випадках (ймовірно, не дуже поширених) ви можете захотіти **обмежити** заголовки, які хочете отримати. Ви можете використати конфігурацію моделі Pydantic, щоб `заборонити` будь-які `додаткові` поля: @@ -55,9 +55,9 @@ Так само, як і зі звичайними параметрами заголовків, коли у назвах параметрів є символи підкреслення, вони **автоматично перетворюються на дефіси**. -Наприклад, якщо у коді у Вас є параметр заголовка `save_data`, очікуваний HTTP-заголовок буде `save-data`, і він так само відображатиметься в документації. +Наприклад, якщо у коді у вас є параметр заголовка `save_data`, очікуваний HTTP-заголовок буде `save-data`, і він так само відображатиметься в документації. -Якщо з якоїсь причини Вам потрібно вимкнути це автоматичне перетворення, Ви також можете зробити це для Pydantic моделей для параметрів заголовків. +Якщо з якоїсь причини вам потрібно вимкнути це автоматичне перетворення, ви також можете зробити це для Pydantic моделей для параметрів заголовків. {* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *} diff --git a/docs/uk/docs/tutorial/header-params.md b/docs/uk/docs/tutorial/header-params.md index f5a4ea18d..9e564d27a 100644 --- a/docs/uk/docs/tutorial/header-params.md +++ b/docs/uk/docs/tutorial/header-params.md @@ -50,7 +50,7 @@ /// warning | Попередження -Перед тим як встановити `convert_underscores` в `False`, пам’ятайте, що деякі HTTP-проксі та сервери забороняють використання заголовків із підкресленнями. +Перед тим як встановити `convert_underscores` в `False`, пам’ятайте, що деякі HTTP-представники та сервери забороняють використання заголовків із підкресленнями. /// diff --git a/docs/uk/docs/tutorial/index.md b/docs/uk/docs/tutorial/index.md index 6848090ec..ed53ac772 100644 --- a/docs/uk/docs/tutorial/index.md +++ b/docs/uk/docs/tutorial/index.md @@ -84,12 +84,12 @@ $ pip install "fastapi[standard]" /// -## Розширений посібник користувача { #advanced-user-guide } +## Просунутий посібник користувача { #advanced-user-guide } -Існує також **Розширений посібник користувача**, який ви зможете прочитати пізніше після цього **Туторіал - Посібник користувача**. +Існує також **Просунутий посібник користувача**, який ви зможете прочитати пізніше після цього **Туторіал - Посібник користувача**. -**Розширений посібник користувача** засновано на цьому, використовує ті самі концепції та навчає вас деяким додатковим функціям. +**Просунутий посібник користувача** засновано на цьому, використовує ті самі концепції та навчає вас деяким додатковим функціям. Але вам слід спочатку прочитати **Туторіал - Посібник користувача** (те, що ви зараз читаєте). -Він розроблений таким чином, що ви можете створити повну програму лише за допомогою **Туторіал - Посібник користувача**, а потім розширити її різними способами, залежно від ваших потреб, використовуючи деякі з додаткових ідей з **Розширеного посібника користувача**. +Він розроблений таким чином, що ви можете створити повну програму лише за допомогою **Туторіал - Посібник користувача**, а потім розширити її різними способами, залежно від ваших потреб, використовуючи деякі з додаткових ідей з **Просунутого посібника користувача**. diff --git a/docs/uk/docs/tutorial/metadata.md b/docs/uk/docs/tutorial/metadata.md index cf1704562..ebe8dc40e 100644 --- a/docs/uk/docs/tutorial/metadata.md +++ b/docs/uk/docs/tutorial/metadata.md @@ -18,7 +18,7 @@ Ви можете налаштувати їх наступним чином: -{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} +{* ../../docs_src/metadata/tutorial001_py310.py hl[3:16, 19:32] *} /// tip | Порада @@ -36,7 +36,7 @@ Наприклад: -{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} +{* ../../docs_src/metadata/tutorial001_1_py310.py hl[31] *} ## Метадані для тегів { #metadata-for-tags } @@ -58,7 +58,7 @@ Створіть метадані для своїх тегів і передайте їх у параметр `openapi_tags`: -{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[3:16,18] *} Зверніть увагу, що в описах можна використовувати Markdown, наприклад, "login" буде показано жирним шрифтом (**login**), а "fancy" буде показано курсивом (_fancy_). @@ -72,7 +72,7 @@ Використовуйте параметр `tags` зі своїми *операціями шляху* (і `APIRouter`s), щоб призначити їх до різних тегів: -{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[21,26] *} /// info | Інформація @@ -100,7 +100,7 @@ Наприклад, щоб налаштувати його на `/api/v1/openapi.json`: -{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py310.py hl[3] *} Якщо Ви хочете повністю вимкнути схему OpenAPI, Ви можете встановити `openapi_url=None`, це також вимкне інтерфейси документації, які її використовують. @@ -117,4 +117,4 @@ Наприклад, щоб налаштувати Swagger UI на `/documentation` і вимкнути ReDoc: -{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial003_py310.py hl[3] *} diff --git a/docs/uk/docs/tutorial/middleware.md b/docs/uk/docs/tutorial/middleware.md index 2d1580e49..961fb179e 100644 --- a/docs/uk/docs/tutorial/middleware.md +++ b/docs/uk/docs/tutorial/middleware.md @@ -31,9 +31,9 @@ * Потім вона поверне `response`, згенеровану відповідною *операцією шляху*. * Потім ви можете додатково змінити `response` перед тим, як повернути її. -{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[8:9,11,14] *} -/// tip | Порада +/// tip Пам’ятайте, що власні пропрієтарні заголовки можна додавати використовуючи префікс `X-`. @@ -57,9 +57,9 @@ Наприклад, ви можете додати власний заголовок `X-Process-Time`, який міститиме час у секундах, який витратився на обробку запиту та генерацію відповіді: -{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[10,12:13] *} -/// tip | Порада +/// tip Тут ми використовуємо `time.perf_counter()` замість `time.time()` оскільки він може бути більш точним для таких випадків. 🤓 @@ -92,4 +92,4 @@ app.add_middleware(MiddlewareB) Ви можете пізніше прочитати більше про інші middlewares в [Advanced User Guide: Advanced Middleware](../advanced/middleware.md){.internal-link target=_blank}. -Ви дізнаєтесь, як обробляти CORS за допомогою middleware в наступному розділі. +Ви дізнаєтесь, як обробляти CORS за допомогою middleware в наступному розділі. diff --git a/docs/uk/docs/tutorial/path-params-numeric-validations.md b/docs/uk/docs/tutorial/path-params-numeric-validations.md index f6aa92019..9458436fd 100644 --- a/docs/uk/docs/tutorial/path-params-numeric-validations.md +++ b/docs/uk/docs/tutorial/path-params-numeric-validations.md @@ -54,11 +54,11 @@ Python видасть помилку, якщо розмістити значен Тому ви можете оголосити вашу функцію так: -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} Але майте на увазі, що якщо ви використовуєте `Annotated`, цієї проблеми не буде, це не матиме значення, оскільки ви не використовуєте значення за замовчуванням параметрів функції для `Query()` або `Path()`. -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## Упорядковуйте параметри, як вам потрібно: хитрощі { #order-the-parameters-as-you-need-tricks } @@ -81,15 +81,15 @@ Python видасть помилку, якщо розмістити значен Передайте `*` як перший параметр функції. -Python нічого не зробить із цією `*`, але розпізнає, що всі наступні параметри слід викликати як аргументи за ключовим словом (пари ключ-значення), також відомі як kwargs. Навіть якщо вони не мають значення за замовчуванням. +Python нічого не зробить із цією `*`, але розпізнає, що всі наступні параметри слід викликати як аргументи за ключовим словом (пари ключ-значення), також відомі як kwargs. Навіть якщо вони не мають значення за замовчуванням. -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### Краще з `Annotated` { #better-with-annotated } Майте на увазі, що якщо ви використовуєте `Annotated`, оскільки ви не використовуєте значення за замовчуванням параметрів функції, цієї проблеми не буде, і, ймовірно, вам не потрібно буде використовувати `*`. -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## Валідація числових даних: більше або дорівнює { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ Python нічого не зробить із цією `*`, але розпізн Тут, завдяки `ge=1`, `item_id` має бути цілим числом, яке "`g`reater than or `e`qual" (більше або дорівнює) `1`. -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## Валідація числових даних: більше ніж і менше або дорівнює { #number-validations-greater-than-and-less-than-or-equal } @@ -106,19 +106,19 @@ Python нічого не зробить із цією `*`, але розпізн * `gt`: `g`reater `t`han * `le`: `l`ess than or `e`qual -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## Валідація числових даних: float, більше ніж і менше ніж { #number-validations-floats-greater-than-and-less-than } Валідація чисел також працює для значень типу `float`. -Ось де стає важливо мати можливість оголошувати gt, а не тільки ge. Це дозволяє, наприклад, вимагати, щоб значення було більше `0`, навіть якщо воно менше `1`. +Ось де стає важливо мати можливість оголошувати gt, а не тільки ge. Це дозволяє, наприклад, вимагати, щоб значення було більше `0`, навіть якщо воно менше `1`. Таким чином, значення `0.5` буде допустимим. Але `0.0` або `0` — ні. -Те саме стосується lt. +Те саме стосується lt. -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## Підсумок { #recap } diff --git a/docs/uk/docs/tutorial/path-params.md b/docs/uk/docs/tutorial/path-params.md index 059890549..17b99cf39 100644 --- a/docs/uk/docs/tutorial/path-params.md +++ b/docs/uk/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Ви можете оголосити «параметри» або «змінні» шляху, використовуючи той самий синтаксис, що й у форматованих рядках Python: -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} Значення параметра шляху `item_id` буде передано у вашу функцію як аргумент `item_id`. @@ -16,17 +16,17 @@ Ви можете оголосити тип параметра шляху у функції, використовуючи стандартні анотації типів Python: -{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params/tutorial002_py310.py hl[7] *} У цьому випадку `item_id` оголошено як `int`. -/// check | Примітка +/// check | Перевірте Це дасть вам підтримку редактора всередині функції з перевірками помилок, автодоповненням тощо. /// -## Перетворення даних { #data-conversion } +## Перетворення даних { #data-conversion } Якщо ви запустите цей приклад і відкриєте у браузері http://127.0.0.1:8000/items/3, то побачите відповідь: @@ -34,11 +34,11 @@ {"item_id":3} ``` -/// check | Примітка +/// check | Перевірте Зверніть увагу, що значення, яке отримала (і повернула) ваша функція, — це `3`, як Python `int`, а не рядок `"3"`. -Отже, з таким оголошенням типу **FastAPI** надає вам автоматичний «parsing» запиту. +Отже, з таким оголошенням типу **FastAPI** надає вам автоматичний запит «парсинг». /// @@ -66,7 +66,7 @@ Та сама помилка з’явиться, якщо ви передасте `float` замість `int`, як у: http://127.0.0.1:8000/items/4.2 -/// check | Примітка +/// check | Перевірте Отже, з тим самим оголошенням типу в Python **FastAPI** надає вам валідацію даних. @@ -82,7 +82,7 @@ -/// check | Примітка +/// check | Перевірте Знову ж таки, лише з тим самим оголошенням типу в Python **FastAPI** надає вам автоматичну, інтерактивну документацію (з інтеграцією Swagger UI). @@ -118,19 +118,19 @@ Оскільки *операції шляху* оцінюються по черзі, вам потрібно переконатися, що шлях для `/users/me` оголошено перед шляхом для `/users/{user_id}`: -{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *} Інакше шлях для `/users/{user_id}` також відповідатиме `/users/me`, «вважаючи», що отримує параметр `user_id` зі значенням `"me"`. Так само ви не можете перевизначити операцію шляху: -{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *} Завжди використовуватиметься перша, оскільки шлях збігається першим. ## Попередньо визначені значення { #predefined-values } -Якщо у вас є *операція шляху*, яка отримує *параметр шляху*, але ви хочете, щоб можливі коректні значення *параметра шляху* були попередньо визначені, ви можете використати стандартний Python `Enum`. +Якщо у вас є *операція шляху*, яка отримує *параметр шляху*, але ви хочете, щоб можливі коректні значення *параметра шляху* були попередньо визначені, ви можете використати стандартний Python `Enum`. ### Створіть клас `Enum` { #create-an-enum-class } @@ -140,11 +140,11 @@ Після цього створіть атрибути класу з фіксованими значеннями, які будуть доступними коректними значеннями: -{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *} /// tip | Порада -Якщо вам цікаво, «AlexNet», «ResNet» та «LeNet» — це просто назви Machine Learning models. +Якщо вам цікаво, «AlexNet», «ResNet» та «LeNet» — це просто назви моделей машинного навчання моделі. /// @@ -152,7 +152,7 @@ Потім створіть *параметр шляху* з анотацією типу, використовуючи створений вами клас enum (`ModelName`): -{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[16] *} ### Перевірте документацію { #check-the-docs } @@ -168,13 +168,13 @@ Ви можете порівнювати його з *елементом перелічування* у створеному вами enum `ModelName`: -{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[17] *} #### Отримайте *значення перелічування* { #get-the-enumeration-value } Ви можете отримати фактичне значення (у цьому випадку це `str`), використовуючи `model_name.value`, або загалом `your_enum_member.value`: -{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[20] *} /// tip | Порада @@ -188,7 +188,7 @@ Вони будуть перетворені на відповідні значення (у цьому випадку рядки) перед поверненням клієнту: -{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[18,21,23] *} На стороні клієнта ви отримаєте відповідь у форматі JSON, наприклад: @@ -227,7 +227,7 @@ OpenAPI не підтримує спосіб оголошення *параме Отже, ви можете використати його так: -{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py310.py hl[6] *} /// tip | Порада @@ -242,7 +242,7 @@ OpenAPI не підтримує спосіб оголошення *параме З **FastAPI**, використовуючи короткі, інтуїтивно зрозумілі та стандартні оголошення типів Python, ви отримуєте: * Підтримку редактора: перевірка помилок, автодоповнення тощо. -* Перетворення даних «parsing» +* Перетворення даних «парсинг» * Валідацію даних * Анотацію API та автоматичну документацію diff --git a/docs/uk/docs/tutorial/query-param-models.md b/docs/uk/docs/tutorial/query-param-models.md index a28bf6c27..5affc8a6c 100644 --- a/docs/uk/docs/tutorial/query-param-models.md +++ b/docs/uk/docs/tutorial/query-param-models.md @@ -1,6 +1,6 @@ # Моделі параметрів запиту { #query-parameter-models } -Якщо у Вас є група **query параметрів**, які пов’язані між собою, Ви можете створити **Pydantic-модель** для їх оголошення. +Якщо у Вас є група **параметрів запиту**, які пов’язані між собою, Ви можете створити **Pydantic-модель** для їх оголошення. Це дозволить Вам **повторно використовувати модель** у **різних місцях**, а також оголошувати перевірки та метадані для всіх параметрів одночасно. 😎 @@ -10,13 +10,13 @@ /// -## Query параметри з Pydantic-моделлю { #query-parameters-with-a-pydantic-model } +## Параметри запиту з Pydantic-моделлю { #query-parameters-with-a-pydantic-model } -Оголосіть **query параметри**, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть цей параметр як `Query`: +Оголосіть **параметри запиту**, які Вам потрібні, у **Pydantic-моделі**, а потім оголосіть цей параметр як `Query`: {* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} -**FastAPI** буде **витягувати** дані для **кожного поля** з **query параметрів** у запиті та передавати їх у визначену вами Pydantic-модель. +**FastAPI** буде **витягувати** дані для **кожного поля** з **параметрів запиту** у запиті та передавати їх у визначену вами Pydantic-модель. ## Перевірте документацію { #check-the-docs } @@ -26,23 +26,23 @@
-## Заборона зайвих Query параметрів { #forbid-extra-query-parameters } +## Заборона зайвих параметрів запиту { #forbid-extra-query-parameters } -У деяких особливих випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** query параметри, які дозволено отримувати. +У деяких особливих випадках (ймовірно, не дуже поширених) Ви можете захотіти **обмежити** параметри запиту, які дозволено отримувати. Ви можете використати конфігурацію моделі Pydantic, щоб заборонити (`forbid`) будь-які зайві (`extra`) поля: {* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} -Якщо клієнт спробує надіслати **зайві** дані у **query параметрах**, він отримає **помилку** відповідь. +Якщо клієнт спробує надіслати **зайві** дані у **параметрах запиту**, він отримає **відповідь з помилкою**. -Наприклад, якщо клієнт спробує надіслати query параметр `tool` зі значенням `plumbus`, як у цьому запиті: +Наприклад, якщо клієнт спробує надіслати параметр запиту `tool` зі значенням `plumbus`, як у цьому запиті: ```http https://example.com/items/?limit=10&tool=plumbus ``` -Він отримає відповідь з **помилкою**, яка повідомить, що query параметр `tool ` не дозволено: +Він отримає відповідь з **помилкою**, яка повідомить, що параметр запиту `tool` не дозволено: ```json { @@ -59,10 +59,10 @@ https://example.com/items/?limit=10&tool=plumbus ## Підсумок { #summary } -Ви можете використовувати **Pydantic-моделі** для оголошення **query параметрів** у **FastAPI**. 😎 +Ви можете використовувати **Pydantic-моделі** для оголошення **параметрів запиту** у **FastAPI**. 😎 /// tip | Порада -Спойлер: Ви також можете використовувати Pydantic-моделі для оголошення cookie та заголовків, але про це Ви дізнаєтеся пізніше в цьому посібнику. 🤫 +Спойлер: Ви також можете використовувати Pydantic-моделі для оголошення кукі та заголовків, але про це Ви дізнаєтеся пізніше в цьому посібнику. 🤫 /// diff --git a/docs/uk/docs/tutorial/query-params-str-validations.md b/docs/uk/docs/tutorial/query-params-str-validations.md index 414987880..706dc670a 100644 --- a/docs/uk/docs/tutorial/query-params-str-validations.md +++ b/docs/uk/docs/tutorial/query-params-str-validations.md @@ -18,7 +18,7 @@ FastAPI знатиме, що значення `q` не є обов’язков ## Додаткова валідація { #additional-validation } -Ми хочемо, щоб навіть якщо `q` є необов’язковим, коли його передають, **його довжина не перевищувала 50 символів**. +Ми хочемо, щоб навіть якщо `q` є необов’язковим, коли його передають, його довжина не перевищувала 50 символів. ### Імпорт `Query` та `Annotated` { #import-query-and-annotated } @@ -47,40 +47,16 @@ FastAPI додав підтримку `Annotated` (і почав рекомен Раніше ми мали таку анотацію типу: -//// tab | Python 3.10+ - ```Python q: str | None = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Union[str, None] = None -``` - -//// - Тепер ми загорнемо її у `Annotated`, і отримаємо: -//// tab | Python 3.10+ - ```Python q: Annotated[str | None] = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Annotated[Union[str, None]] = None -``` - -//// - Обидві ці версії означають одне й те саме: `q` — це параметр, який може бути `str` або `None`, і за замовчуванням має значення `None`. А тепер переходимо до цікавого! 🎉 @@ -93,23 +69,23 @@ q: Annotated[Union[str, None]] = None Зверніть увагу, що значення за замовчуванням усе ще `None`, тому параметр залишається необов'язковим. -Але тепер, додавши `Query(max_length=50)` всередину `Annotated`, ми повідомляємо FastAPI, що хочемо **додаткову валідацію** для цього значення: ми хочемо, щоб воно мало максимум 50 символів. 😎 +Але тепер, додавши `Query(max_length=50)` всередину `Annotated`, ми повідомляємо FastAPI, що хочемо додаткову валідацію для цього значення: ми хочемо, щоб воно мало максимум 50 символів. 😎 /// tip | Порада -Тут ми використовуємо `Query()`, оскільки це **query параметр**. Далі ми розглянемо інші варіанти, як-от `Path()`, `Body()`, `Header()` та `Cookie()`, які приймають ті самі аргументи, що й `Query()`. +Тут ми використовуємо `Query()`, оскільки це query параметр. Далі ми розглянемо інші варіанти, як-от `Path()`, `Body()`, `Header()` та `Cookie()`, які приймають ті самі аргументи, що й `Query()`. /// Тепер FastAPI: -* **Перевірить** дані, щоб переконатися, що їхня максимальна довжина — 50 символів -* Покажe **чітку помилку** клієнту, якщо дані недійсні -* **Задокументує** параметр в OpenAPI-схемі *операції шляху* (що відобразиться в **автоматично згенерованій документації**) +* Перевірить дані, щоб переконатися, що їхня максимальна довжина — 50 символів +* Покажe чітку помилку клієнту, якщо дані недійсні +* Задокументує параметр в OpenAPI-схемі операції шляху (що відобразиться в автоматично згенерованій документації) ## Альтернативний (застарілий) метод: `Query` як значення за замовчуванням { #alternative-old-query-as-the-default-value } -У попередніх версіях FastAPI (до 0.95.0) потрібно було використовувати `Query` як значення за замовчуванням параметра, замість того, щоб додавати його в `Annotated`. Є висока ймовірність, що ви зустрінете код із таким підходом, тож я поясню його. +У попередніх версіях FastAPI (до 0.95.0) потрібно було використовувати `Query` як значення за замовчуванням параметра, замість того, щоб додавати його в `Annotated`. Є висока ймовірність, що ви зустрінете код із таким підходом, тож я поясню його. /// tip | Порада @@ -131,7 +107,6 @@ q: str | None = Query(default=None) ...робить параметр необов’язковим зі значенням за замовчуванням `None`, що еквівалентно: - ```Python q: str | None = None ``` @@ -144,7 +119,7 @@ q: str | None = None q: str | None = Query(default=None, max_length=50) ``` -Це забезпечить валідацію даних, виведе зрозумілу помилку у разі недійсних даних і задокументує параметр у схемі OpenAPI *операції шляху*. +Це забезпечить валідацію даних, виведе зрозумілу помилку у разі недійсних даних і задокументує параметр у схемі OpenAPI операції шляху. ### `Query` як значення за замовчуванням або всередині `Annotated` { #query-as-the-default-value-or-in-annotated } @@ -174,13 +149,13 @@ q: str = Query(default="rick") ### Переваги використання `Annotated` { #advantages-of-annotated } -**Використання `Annotated` є рекомендованим** замість задання значення за замовчуванням у параметрах функції, оскільки воно **краще** з кількох причин. 🤓 +Використання `Annotated` є рекомендованим замість задання значення за замовчуванням у параметрах функції, оскільки воно краще з кількох причин. 🤓 -Значення **за замовчуванням** параметра **функції** є **фактичним значенням за замовчуванням**, що є більш інтуїтивним у Python загалом. 😌 +Значення за замовчуванням параметра функції є фактичним значенням за замовчуванням, що є більш інтуїтивним у Python загалом. 😌 -Ви можете **викликати** ту саму функцію **в інших місцях** без FastAPI, і вона **працюватиме очікувано**. Якщо параметр є **обов’язковим** (без значення за замовчуванням), ваш **редактор** повідомить про помилку, а **Python** також видасть помилку, якщо ви виконаєте функцію без передавання цього параметра. +Ви можете викликати ту саму функцію в інших місцях без FastAPI, і вона працюватиме очікувано. Якщо параметр є обов’язковим (без значення за замовчуванням), ваш редактор повідомить про помилку, а Python також видасть помилку, якщо ви виконаєте функцію без передавання цього параметра. -Якщо ви не використовуєте `Annotated`, а використовуєте **(старий) стиль значень за замовчуванням**, то при виклику цієї функції без FastAPI **в інших місцях**, потрібно **пам’ятати** передати їй аргументи, щоб вона працювала коректно, інакше значення будуть відрізнятися від очікуваних (наприклад, ви отримаєте `QueryInfo` або щось подібне замість `str`). І ваш редактор не повідомить про помилку, і Python не скаржитиметься під час запуску цієї функції — лише коли операції всередині завершаться помилкою. +Якщо ви не використовуєте `Annotated`, а використовуєте (старий) стиль значень за замовчуванням, то при виклику цієї функції без FastAPI в інших місцях потрібно пам’ятати передати їй аргументи, щоб вона працювала коректно, інакше значення будуть відрізнятися від очікуваних (наприклад, ви отримаєте `QueryInfo` або щось подібне замість `str`). І ваш редактор не повідомить про помилку, і Python не скаржитиметься під час запуску цієї функції — лише коли операції всередині завершаться помилкою. Оскільки `Annotated` може містити кілька анотацій метаданих, тепер ви навіть можете використовувати ту саму функцію з іншими інструментами, такими як Typer. 🚀 @@ -192,7 +167,7 @@ q: str = Query(default="rick") ## Додавання регулярних виразів { #add-regular-expressions } -Ви можете визначити regular expression `pattern`, якому має відповідати параметр: +Ви можете визначити регулярний вираз `pattern`, якому має відповідати параметр: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -212,7 +187,7 @@ q: str = Query(default="rick") Припустімо, що ви хочете оголосити query параметр `q` з `min_length` `3` і значенням за замовчуванням `"fixedquery"`: -{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} /// note | Примітка @@ -242,7 +217,7 @@ q: Annotated[str | None, Query(min_length=3)] = None Тому, якщо вам потрібно оголосити значення як обов’язкове під час використання `Query`, просто не вказуйте значення за замовчуванням: -{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} ### Обов’язковий, може бути `None` { #required-can-be-none } @@ -266,7 +241,7 @@ q: Annotated[str | None, Query(min_length=3)] = None http://localhost:8000/items/?q=foo&q=bar ``` -ви отримаєте кілька значень `q` *query параметрів* (`foo` і `bar`) у вигляді Python `list` у вашій *функції операції шляху*, у *параметрі функції* `q`. +ви отримаєте кілька значень `q` query параметрів (`foo` і `bar`) у вигляді Python `list` у вашій функції операції шляху, у параметрі функції `q`. Отже, відповідь на цей URL буде: @@ -293,7 +268,7 @@ http://localhost:8000/items/?q=foo&q=bar Ви також можете визначити значення за замовчуванням `list`, якщо жодне значення не було передане: -{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} Якщо ви перейдете за посиланням: @@ -316,7 +291,7 @@ http://localhost:8000/items/ Ви також можете використовувати `list` напряму замість `list[str]`: -{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} /// note | Примітка @@ -372,7 +347,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems Припустімо, що вам більше не подобається цей параметр. -Вам потрібно залишити його на деякий час, оскільки ним користуються клієнти, але ви хочете, щоб документація чітко показувала, що він є deprecated. +Вам потрібно залишити його на деякий час, оскільки ним користуються клієнти, але ви хочете, щоб документація чітко показувала, що він є застарілий. Тоді передайте параметр `deprecated=True` до `Query`: @@ -390,9 +365,9 @@ http://127.0.0.1:8000/items/?item-query=foobaritems ## Кастомна валідація { #custom-validation } -Можуть бути випадки, коли вам потрібно провести **кастомну валідацію**, яку не можна реалізувати за допомогою параметрів, показаних вище. +Можуть бути випадки, коли вам потрібно провести кастомну валідацію, яку не можна реалізувати за допомогою параметрів, показаних вище. -У таких випадках ви можете використати **кастомну функцію-валідатор**, яка буде застосована після звичайної валідації (наприклад, після перевірки, що значення є типом `str`). +У таких випадках ви можете використати кастомну функцію-валідатор, яка буде застосована після звичайної валідації (наприклад, після перевірки, що значення є типом `str`). Це можна досягти за допомогою Pydantic's `AfterValidator` в середині `Annotated`. @@ -402,7 +377,7 @@ Pydantic також має ISBN або з `imdb-` для ID URL фільму на IMDB: +Наприклад, цей кастомний валідатор перевіряє, чи починається ID елемента з `isbn-` для номера книги ISBN або з `imdb-` для ID URL фільму на IMDB: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -414,15 +389,15 @@ Pydantic також має iterable object із кортежами, що містять ключ і значення для кожного елемента словника. +За допомогою `data.items()` ми отримуємо ітерабельний об'єкт із кортежами, що містять ключ і значення для кожного елемента словника. -Ми перетворюємо цей iterable object у звичайний `list` за допомогою `list(data.items())`. +Ми перетворюємо цей ітерабельний об'єкт у звичайний `list` за допомогою `list(data.items())`. -Потім, використовуючи `random.choice()`, ми можемо отримати **випадкове значення** зі списку, тобто отримуємо кортеж із `(id, name)`. Це може бути щось на зразок `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. +Потім, використовуючи `random.choice()`, ми можемо отримати випадкове значення зі списку, тобто отримуємо кортеж із `(id, name)`. Це може бути щось на зразок `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. -Далі ми **присвоюємо ці два значення** кортежу змінним `id` і `name`. +Далі ми присвоюємо ці два значення кортежу змінним `id` і `name`. Тож, якщо користувач не вказав ID елемента, він все одно отримає випадкову рекомендацію. -...ми робимо все це в **одному простому рядку**. 🤯 Хіба ви не любите Python? 🐍 +...ми робимо все це в одному простому рядку. 🤯 Хіба ви не любите Python? 🐍 {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} diff --git a/docs/uk/docs/tutorial/query-params.md b/docs/uk/docs/tutorial/query-params.md index a9068aa8f..4888f4c46 100644 --- a/docs/uk/docs/tutorial/query-params.md +++ b/docs/uk/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ Коли ви оголошуєте інші параметри функції, які не є частиною параметрів шляху, вони автоматично інтерпретуються як параметри «query». -{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} Query — це набір пар ключ-значення, що йдуть після символу `?` в URL, розділені символами `&`. @@ -24,7 +24,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10 Увесь той самий процес, який застосовується до параметрів шляху, також застосовується до параметрів query: * Підтримка в редакторі (очевидно) -* «parsing» даних +* «парсинг» даних * Валідація даних * Автоматична документація @@ -65,7 +65,7 @@ http://127.0.0.1:8000/items/?skip=20 У цьому випадку параметр функції `q` буде необов’язковим і за замовчуванням матиме значення `None`. -/// check | Примітка +/// check | Перевірте Також зверніть увагу, що **FastAPI** достатньо розумний, щоб визначити, що параметр шляху `item_id` є параметром шляху, а `q` — ні, отже, це параметр query. @@ -128,7 +128,7 @@ http://127.0.0.1:8000/items/foo?short=yes Але якщо ви хочете зробити параметр query обов’язковим, просто не вказуйте для нього значення за замовчуванням: -{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} Тут параметр query `needy` — обов’язковий параметр query типу `str`. @@ -183,6 +183,6 @@ http://127.0.0.1:8000/items/foo-item?needy=sooooneedy /// tip | Порада -Ви також можете використовувати `Enum` так само, як і з [Path Parameters](path-params.md#predefined-values){.internal-link target=_blank}. +Ви також можете використовувати `Enum` так само, як і з [Параметри шляху](path-params.md#predefined-values){.internal-link target=_blank}. /// diff --git a/docs/uk/docs/tutorial/request-files.md b/docs/uk/docs/tutorial/request-files.md index a6ff70dc0..8e64b12c3 100644 --- a/docs/uk/docs/tutorial/request-files.md +++ b/docs/uk/docs/tutorial/request-files.md @@ -20,13 +20,13 @@ $ pip install python-multipart Імпортуйте `File` та `UploadFile` з `fastapi`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} ## Визначення параметрів `File` { #define-file-parameters } Створіть параметри файлів так само як ви б створювали `Body` або `Form`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} /// info | Інформація @@ -54,7 +54,7 @@ $ pip install python-multipart Визначте параметр файлу з типом `UploadFile`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} Використання `UploadFile` має кілька переваг перед `bytes`: @@ -121,7 +121,7 @@ contents = myfile.file.read() Але якщо форма містить файли, вона кодується як `multipart/form-data`. Якщо ви використовуєте `File`, **FastAPI** знатиме, що потрібно отримати файли з правильної частини тіла. -Якщо ви хочете дізнатися більше про ці типи кодування та формові поля, ознайомтеся з MDN web docs для POST. +Якщо ви хочете дізнатися більше про ці типи кодування та формові поля, ознайомтеся з MDN web docs для POST. /// @@ -143,7 +143,7 @@ contents = myfile.file.read() Ви також можете використовувати `File()` разом із `UploadFile`, наприклад, щоб встановити додаткові метадані: -{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} ## Завантаження кількох файлів { #multiple-file-uploads } @@ -153,7 +153,7 @@ contents = myfile.file.read() Щоб це реалізувати, потрібно оголосити список `bytes` або `UploadFile`: -{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} Ви отримаєте, як і було оголошено, `list` із `bytes` або `UploadFile`. @@ -169,7 +169,7 @@ contents = myfile.file.read() Так само як і раніше, ви можете використовувати `File()`, щоб встановити додаткові параметри навіть для `UploadFile`: -{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} ## Підсумок { #recap } diff --git a/docs/uk/docs/tutorial/request-form-models.md b/docs/uk/docs/tutorial/request-form-models.md index 1bfd368d6..86510be58 100644 --- a/docs/uk/docs/tutorial/request-form-models.md +++ b/docs/uk/docs/tutorial/request-form-models.md @@ -24,7 +24,7 @@ $ pip install python-multipart Вам просто потрібно оголосити **Pydantic-модель** з полями, які ви хочете отримати як **поля форми**, а потім оголосити параметр як `Form`: -{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} **FastAPI** **витягне** дані для **кожного поля** з **формових даних** у запиті та надасть вам Pydantic-модель, яку ви визначили. @@ -48,7 +48,7 @@ $ pip install python-multipart Ви можете використати конфігурацію Pydantic-моделі, щоб заборонити `forbid` будь-які додаткові `extra` поля: -{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} Якщо клієнт спробує надіслати додаткові дані, він отримає **відповідь з помилкою**. diff --git a/docs/uk/docs/tutorial/request-forms-and-files.md b/docs/uk/docs/tutorial/request-forms-and-files.md index e809bee22..817769b71 100644 --- a/docs/uk/docs/tutorial/request-forms-and-files.md +++ b/docs/uk/docs/tutorial/request-forms-and-files.md @@ -6,7 +6,7 @@ Щоб отримувати завантажені файли та/або дані форми, спочатку встановіть `python-multipart`. -Переконайтеся, що Ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили бібліотеку, наприклад: +Переконайтеся, що ви створили [віртуальне середовище](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили бібліотеку, наприклад: ```console $ pip install python-multipart @@ -16,15 +16,15 @@ $ pip install python-multipart ## Імпорт `File` та `Form` { #import-file-and-form } -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} ## Оголошення параметрів `File` та `Form` { #define-file-and-form-parameters } Створіть параметри файлів та форми так само як і для `Body` або `Query`: -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} -Файли та поля форми будуть завантажені як формові дані, і Ви отримаєте файли та поля форми. +Файли та поля форми будуть завантажені як формові дані, і ви отримаєте файли та поля форми. Ви також можете оголосити деякі файли як `bytes`, а деякі як `UploadFile`. diff --git a/docs/uk/docs/tutorial/request-forms.md b/docs/uk/docs/tutorial/request-forms.md index 2a22ad922..7f0c6e9bb 100644 --- a/docs/uk/docs/tutorial/request-forms.md +++ b/docs/uk/docs/tutorial/request-forms.md @@ -1,6 +1,6 @@ # Дані форми { #form-data } -Якщо вам потрібно отримувати поля форми замість JSON, ви можете використовувати `Form`. +Коли вам потрібно отримувати поля форми замість JSON, ви можете використовувати `Form`. /// info | Інформація @@ -18,17 +18,17 @@ $ pip install python-multipart Імпортуйте `Form` з `fastapi`: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} ## Оголошення параметрів `Form` { #define-form-parameters } Створюйте параметри форми так само як ви б створювали `Body` або `Query`: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} -Наприклад, один зі способів використання специфікації OAuth2 (так званий "password flow") вимагає надсилати `username` та `password` як поля форми. +Наприклад, один зі способів використання специфікації OAuth2 (так званий «password flow») вимагає надсилати `username` та `password` як поля форми. -spec вимагає, щоб ці поля мали точні назви `username` і `password` та надсилалися у вигляді полів форми, а не JSON. +специфікація вимагає, щоб ці поля мали точні назви `username` і `password` та надсилалися у вигляді полів форми, а не JSON. З `Form` ви можете оголошувати ті ж конфігурації, що і з `Body` (та `Query`, `Path`, `Cookie`), включаючи валідацію, приклади, псевдоніми (наприклад, `user-name` замість `username`) тощо. @@ -44,19 +44,19 @@ $ pip install python-multipart /// -## Про "поля форми" { #about-form-fields } +## Про «поля форми» { #about-form-fields } -HTML-форми (`
`) надсилають дані на сервер у "спеціальному" кодуванні, яке відрізняється від JSON. +HTML-форми (`
`) надсилають дані на сервер у «спеціальному» кодуванні, яке відрізняється від JSON. **FastAPI** подбає про те, щоб зчитати ці дані з правильного місця, а не з JSON. /// note | Технічні деталі -Дані з форм зазвичай кодуються за допомогою "типу медіа" `application/x-www-form-urlencoded`. +Дані з форм зазвичай кодуються за допомогою «типу медіа» `application/x-www-form-urlencoded`. Але якщо форма містить файли, вона кодується як `multipart/form-data`. Ви дізнаєтеся про обробку файлів у наступному розділі. -Якщо ви хочете дізнатися більше про ці кодування та поля форм, зверніться до MDN вебдокументації для POST. +Якщо ви хочете дізнатися більше про ці кодування та поля форм, зверніться до MDN вебдокументації для POST. /// diff --git a/docs/uk/docs/tutorial/response-model.md b/docs/uk/docs/tutorial/response-model.md index 2fcad9438..fcf765c9d 100644 --- a/docs/uk/docs/tutorial/response-model.md +++ b/docs/uk/docs/tutorial/response-model.md @@ -81,7 +81,7 @@ FastAPI використовуватиме цей `response_model` для вик $ pip install email-validator ``` -or with: +або так: ```console $ pip install "pydantic[email]" @@ -183,7 +183,7 @@ FastAPI виконує кілька внутрішніх операцій з Pyd Найпоширенішим випадком буде [повернення Response напряму, як пояснюється пізніше у розширеній документації](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} Цей простий випадок автоматично обробляється FastAPI, тому що анотація типу повернення — це клас (або підклас) `Response`. @@ -193,7 +193,7 @@ FastAPI виконує кілька внутрішніх операцій з Pyd Ви також можете використати підклас `Response` в анотації типу: -{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} Це теж працюватиме, бо `RedirectResponse` — підклас `Response`, і FastAPI автоматично обробить цей простий випадок. @@ -201,7 +201,7 @@ FastAPI виконує кілька внутрішніх операцій з Pyd Але коли ви повертаєте якийсь інший довільний об’єкт, що не є валідним типом Pydantic (наприклад, об’єкт бази даних), і анотуєте його так у функції, FastAPI спробує створити модель відповіді Pydantic на основі цієї анотації типу і це завершиться помилкою. -Те саме станеться, якщо ви використаєте union між різними типами, де один або більше не є валідними типами Pydantic, наприклад, це завершиться помилкою 💥: +Те саме станеться, якщо у вас буде об'єднання між різними типами, де один або більше не є валідними типами Pydantic, наприклад, це завершиться помилкою 💥: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} diff --git a/docs/uk/docs/tutorial/response-status-code.md b/docs/uk/docs/tutorial/response-status-code.md index 5a08ee46b..c9ceb8f50 100644 --- a/docs/uk/docs/tutorial/response-status-code.md +++ b/docs/uk/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ * `@app.delete()` * тощо. -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} /// note | Примітка @@ -66,7 +66,7 @@ FastAPI знає про це і створить документацію OpenAP /// tip | Порада -Щоб дізнатися більше про кожен код статусу і для чого призначений кожен із них, перегляньте документацію MDN про HTTP коди статусу. +Щоб дізнатися більше про кожен код статусу і для чого призначений кожен із них, перегляньте документацію MDN про HTTP коди статусу. /// @@ -74,7 +74,7 @@ FastAPI знає про це і створить документацію OpenAP Розглянемо попередній приклад ще раз: -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} `201` — це код статусу для «Created». @@ -82,7 +82,7 @@ FastAPI знає про це і створить документацію OpenAP Ви можете використовувати зручні змінні з `fastapi.status`. -{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} Вони — просто для зручності, містять те саме число, але так ви можете скористатися автозаповненням редактора, щоб знайти їх: diff --git a/docs/uk/docs/tutorial/schema-extra-example.md b/docs/uk/docs/tutorial/schema-extra-example.md index 54608c2ab..38ce0eb30 100644 --- a/docs/uk/docs/tutorial/schema-extra-example.md +++ b/docs/uk/docs/tutorial/schema-extra-example.md @@ -40,7 +40,7 @@ OpenAPI 3.1.0 (який використовується починаючи з F {* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} -## `examples` у JSON-схемі — OpenAPI { #examples-in-json-schema-openapi } +## `examples` у JSON-схемі - OpenAPI { #examples-in-json-schema-openapi } При використанні будь-кого з наступного: @@ -74,7 +74,7 @@ OpenAPI 3.1.0 (який використовується починаючи з F Коли Ви це робите, приклади будуть частиною внутрішньої **JSON-схеми** для цих даних тіла. -Втім, на момент написання цього (час написання цього), Swagger UI — інструмент, який відповідає за відображення UI документації — не підтримує показ кількох прикладів для даних у **JSON-схемі**. Але нижче можна прочитати про обхідний шлях. +Втім, на час написання цього, Swagger UI — інструмент, який відповідає за відображення UI документації — не підтримує показ кількох прикладів для даних у **JSON-схемі**. Але нижче можна прочитати про обхідний шлях. ### Специфічні для OpenAPI `examples` { #openapi-specific-examples } diff --git a/docs/uk/docs/tutorial/security/index.md b/docs/uk/docs/tutorial/security/index.md index f1fb25178..de09c728b 100644 --- a/docs/uk/docs/tutorial/security/index.md +++ b/docs/uk/docs/tutorial/security/index.md @@ -73,11 +73,11 @@ OpenAPI визначає такі схеми безпеки: * `apiKey`: специфічний для застосунку ключ, який може передаватися через: * Параметр запиту. * Заголовок. - * Cookie. + * Кукі. * `http`: стандартні системи HTTP-автентифікації, включаючи: * `bearer`: заголовок `Authorization` зі значенням `Bearer ` та токеном. Це успадковано з OAuth2. - * HTTP Basic автентифікацію. - * HTTP Digest тощо. + * базову автентифікацію HTTP. + * HTTP дайджест тощо. * `oauth2`: усі способи обробки безпеки за допомогою OAuth2 (так звані «потоки»). * Декілька з цих потоків підходять для створення провайдера автентифікації OAuth 2.0 (наприклад, Google, Facebook, X (Twitter), GitHub тощо): * `implicit` diff --git a/docs/uk/docs/tutorial/static-files.md b/docs/uk/docs/tutorial/static-files.md index 32ca1311d..7f45973df 100644 --- a/docs/uk/docs/tutorial/static-files.md +++ b/docs/uk/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ * Імпортуйте `StaticFiles`. * «Під'єднати» екземпляр `StaticFiles()` з вказанням необхідного шляху. -{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} /// note | Технічні деталі diff --git a/docs/uk/docs/tutorial/testing.md b/docs/uk/docs/tutorial/testing.md index 462592829..ff32e9fb6 100644 --- a/docs/uk/docs/tutorial/testing.md +++ b/docs/uk/docs/tutorial/testing.md @@ -30,7 +30,7 @@ $ pip install httpx Записуйте прості `assert`-вирази зі стандартними виразами Python, які потрібно перевірити (це також стандарт для `pytest`). -{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} /// tip | Порада @@ -76,7 +76,7 @@ $ pip install httpx У файлі `main.py` знаходиться ваш застосунок **FastAPI**: -{* ../../docs_src/app_testing/app_a_py39/main.py *} +{* ../../docs_src/app_testing/app_a_py310/main.py *} ### Файл тестування { #testing-file } @@ -90,9 +90,9 @@ $ pip install httpx │   └── test_main.py ``` -Оскільки цей файл знаходиться в тому ж пакеті, ви можете використовувати відносний імпорт, щоб імпортувати об'єкт `app` із модуля `main` (`main.py`): +Оскільки цей файл знаходиться в тому ж пакеті, ви можете використовувати відносний імпорт, щоб імпортувати об'єкт `app` із модуля `main` (`main.py`): -{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} ...і написати код для тестів так само як і раніше. From 54e29fbad85d7875800b86205ad47981f41b4faa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Feb 2026 12:42:05 +0000 Subject: [PATCH 120/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5eec22e0b..4f4492f7b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for tr (update-all). PR [#14913](https://github.com/fastapi/fastapi/pull/14913) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for ru (update-outdated). PR [#14909](https://github.com/fastapi/fastapi/pull/14909) by [@YuriiMotov](https://github.com/YuriiMotov). ### Internal From 149ef6264e822490dfbf2986abbbcb7f6645ac35 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Feb 2026 12:42:31 +0000 Subject: [PATCH 121/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4f4492f7b..ae5bef77f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for uk (update-all). PR [#14914](https://github.com/fastapi/fastapi/pull/14914) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for tr (update-all). PR [#14913](https://github.com/fastapi/fastapi/pull/14913) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for ru (update-outdated). PR [#14909](https://github.com/fastapi/fastapi/pull/14909) by [@YuriiMotov](https://github.com/YuriiMotov). From eada361fa2c15b4f5f178062d89291440d651139 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:37:57 +0100 Subject: [PATCH 122/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20zh=20(update-all)=20(#14917)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all * 🎨 Auto format * Add missing * 🎨 Auto format --------- Co-authored-by: github-actions[bot] --- docs/zh/docs/_llm-test.md | 755 ++++++++++++++++++ docs/zh/docs/about/index.md | 3 + docs/zh/docs/advanced/additional-responses.md | 4 +- .../docs/advanced/additional-status-codes.md | 4 +- .../zh/docs/advanced/advanced-dependencies.md | 8 +- .../zh/docs/advanced/advanced-python-types.md | 61 ++ docs/zh/docs/advanced/async-tests.md | 10 +- docs/zh/docs/advanced/behind-a-proxy.md | 12 +- docs/zh/docs/advanced/custom-response.md | 34 +- docs/zh/docs/advanced/dataclasses.md | 2 +- docs/zh/docs/advanced/events.md | 12 +- docs/zh/docs/advanced/generate-clients.md | 10 +- docs/zh/docs/advanced/middleware.md | 8 +- docs/zh/docs/advanced/openapi-callbacks.md | 2 +- docs/zh/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 16 +- .../advanced/response-change-status-code.md | 6 +- docs/zh/docs/advanced/response-cookies.md | 6 +- docs/zh/docs/advanced/response-directly.md | 4 +- docs/zh/docs/advanced/response-headers.md | 6 +- .../docs/advanced/security/http-basic-auth.md | 6 +- docs/zh/docs/advanced/settings.md | 26 +- docs/zh/docs/advanced/sub-applications.md | 6 +- docs/zh/docs/advanced/templates.md | 2 +- docs/zh/docs/advanced/testing-dependencies.md | 1 - docs/zh/docs/advanced/testing-events.md | 4 +- docs/zh/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/zh/docs/advanced/websockets.md | 8 +- docs/zh/docs/advanced/wsgi.md | 2 +- docs/zh/docs/alternatives.md | 482 +++++++++++ docs/zh/docs/async.md | 4 +- docs/zh/docs/benchmarks.md | 2 +- docs/zh/docs/deployment/concepts.md | 2 +- docs/zh/docs/deployment/docker.md | 10 +- docs/zh/docs/deployment/fastapicloud.md | 65 ++ docs/zh/docs/deployment/https.md | 8 +- docs/zh/docs/deployment/manually.md | 6 +- docs/zh/docs/features.md | 12 +- docs/zh/docs/help-fastapi.md | 2 +- .../authentication-error-status-code.md | 17 + docs/zh/docs/how-to/conditional-openapi.md | 56 ++ docs/zh/docs/how-to/configure-swagger-ui.md | 6 +- docs/zh/docs/how-to/custom-docs-ui-assets.md | 185 +++++ .../docs/how-to/custom-request-and-route.md | 109 +++ docs/zh/docs/how-to/extending-openapi.md | 80 ++ docs/zh/docs/how-to/general.md | 2 +- docs/zh/docs/how-to/graphql.md | 60 ++ ...migrate-from-pydantic-v1-to-pydantic-v2.md | 135 ++++ .../docs/how-to/separate-openapi-schemas.md | 102 +++ docs/zh/docs/how-to/testing-database.md | 7 + docs/zh/docs/index.md | 10 +- docs/zh/docs/python-types.md | 188 +---- docs/zh/docs/resources/index.md | 3 + docs/zh/docs/translation-banner.md | 11 + docs/zh/docs/tutorial/background-tasks.md | 8 +- docs/zh/docs/tutorial/bigger-applications.md | 28 +- docs/zh/docs/tutorial/body-multiple-params.md | 6 - docs/zh/docs/tutorial/body-nested-models.md | 4 +- docs/zh/docs/tutorial/body.md | 2 +- docs/zh/docs/tutorial/cookie-param-models.md | 10 +- docs/zh/docs/tutorial/cors.md | 4 +- docs/zh/docs/tutorial/debugging.md | 4 +- .../dependencies/classes-as-dependencies.md | 28 +- ...pendencies-in-path-operation-decorators.md | 12 +- .../dependencies/dependencies-with-yield.md | 24 +- .../dependencies/global-dependencies.md | 2 +- docs/zh/docs/tutorial/dependencies/index.md | 6 +- .../tutorial/dependencies/sub-dependencies.md | 6 +- docs/zh/docs/tutorial/encoder.md | 4 +- docs/zh/docs/tutorial/extra-models.md | 14 +- docs/zh/docs/tutorial/first-steps.md | 18 +- docs/zh/docs/tutorial/handling-errors.md | 16 +- docs/zh/docs/tutorial/metadata.md | 12 +- docs/zh/docs/tutorial/middleware.md | 4 +- .../tutorial/path-operation-configuration.md | 12 +- .../path-params-numeric-validations.md | 14 +- docs/zh/docs/tutorial/path-params.md | 30 +- .../tutorial/query-params-str-validations.md | 42 +- docs/zh/docs/tutorial/query-params.md | 13 +- docs/zh/docs/tutorial/request-files.md | 22 +- docs/zh/docs/tutorial/request-form-models.md | 4 +- .../docs/tutorial/request-forms-and-files.md | 6 +- docs/zh/docs/tutorial/request-forms.md | 6 +- docs/zh/docs/tutorial/response-model.md | 6 +- docs/zh/docs/tutorial/response-status-code.md | 6 +- docs/zh/docs/tutorial/schema-extra-example.md | 2 +- docs/zh/docs/tutorial/security/first-steps.md | 6 +- .../tutorial/security/get-current-user.md | 2 +- docs/zh/docs/tutorial/security/index.md | 2 +- docs/zh/docs/tutorial/security/oauth2-jwt.md | 12 +- .../docs/tutorial/security/simple-oauth2.md | 12 +- docs/zh/docs/tutorial/sql-databases.md | 2 +- docs/zh/docs/tutorial/static-files.md | 4 +- docs/zh/docs/tutorial/testing.md | 6 +- docs/zh/docs/virtual-environments.md | 10 +- 96 files changed, 2504 insertions(+), 515 deletions(-) create mode 100644 docs/zh/docs/_llm-test.md create mode 100644 docs/zh/docs/about/index.md create mode 100644 docs/zh/docs/advanced/advanced-python-types.md create mode 100644 docs/zh/docs/alternatives.md create mode 100644 docs/zh/docs/deployment/fastapicloud.md create mode 100644 docs/zh/docs/how-to/authentication-error-status-code.md create mode 100644 docs/zh/docs/how-to/conditional-openapi.md create mode 100644 docs/zh/docs/how-to/custom-docs-ui-assets.md create mode 100644 docs/zh/docs/how-to/custom-request-and-route.md create mode 100644 docs/zh/docs/how-to/extending-openapi.md create mode 100644 docs/zh/docs/how-to/graphql.md create mode 100644 docs/zh/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md create mode 100644 docs/zh/docs/how-to/separate-openapi-schemas.md create mode 100644 docs/zh/docs/how-to/testing-database.md create mode 100644 docs/zh/docs/resources/index.md create mode 100644 docs/zh/docs/translation-banner.md diff --git a/docs/zh/docs/_llm-test.md b/docs/zh/docs/_llm-test.md new file mode 100644 index 000000000..05c512e99 --- /dev/null +++ b/docs/zh/docs/_llm-test.md @@ -0,0 +1,755 @@ +# LLM 测试文件 { #llm-test-file } + +本文用于测试用于翻译文档的 LLM 是否理解 `scripts/translate.py` 中的 `general_prompt` 以及 `docs/{language code}/llm-prompt.md` 中的语言特定提示。语言特定提示会追加到 `general_prompt` 之后。 + +这里添加的测试会被所有语言特定提示的设计者看到。 + +用法如下: + +* 准备语言特定提示——`docs/{language code}/llm-prompt.md`。 +* 将本文重新翻译为你的目标语言(例如使用 `translate.py` 的 `translate-page` 命令)。这会在 `docs/{language code}/docs/_llm-test.md` 下创建翻译。 +* 检查翻译是否正确。 +* 如有需要,改进你的语言特定提示、通用提示,或英文文档。 +* 然后手动修正翻译中剩余的问题,确保这是一个优秀的译文。 +* 重新翻译,在已有的优秀译文基础上进行。理想情况是 LLM 不再对译文做任何更改。这意味着通用提示和你的语言特定提示已经尽可能完善(有时它仍会做一些看似随机的改动,原因是LLM 不是确定性算法)。 + +测试如下: + +## 代码片段 { #code-snippets } + +//// tab | 测试 + +这是一个代码片段:`foo`。这是另一个代码片段:`bar`。还有一个:`baz quux`。 + +//// + +//// tab | 信息 + +代码片段的内容应保持不变。 + +参见 `scripts/translate.py` 中通用提示的 `### Content of code snippets` 部分。 + +//// + +## 引号 { #quotes } + +//// tab | 测试 + +昨天,我的朋友写道:"如果你把 incorrectly 拼对了,你就把它拼错了"。我回答:"没错,但 'incorrectly' 错的不是 '"incorrectly"'"。 + +/// note | 注意 + +LLM 很可能会把这段翻错。我们只关心在重新翻译时它是否能保持修正后的译文。 + +/// + +//// + +//// tab | 信息 + +提示词设计者可以选择是否将中性引号转换为排版引号。也可以保持不变。 + +例如参见 `docs/de/llm-prompt.md` 中的 `### Quotes` 部分。 + +//// + +## 代码片段中的引号 { #quotes-in-code-snippets } + +//// tab | 测试 + +`pip install "foo[bar]"` + +代码片段中的字符串字面量示例:`"this"`,`'that'`。 + +一个较难的字符串字面量示例:`f"I like {'oranges' if orange else "apples"}"` + +硬核:`Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` + +//// + +//// tab | 信息 + +... 但是,代码片段内的引号必须保持不变。 + +//// + +## 代码块 { #code-blocks } + +//// tab | 测试 + +一个 Bash 代码示例... + +```bash +# 向宇宙打印问候 +echo "Hello universe" +``` + +...以及一个控制台代码示例... + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +...以及另一个控制台代码示例... + +```console +// 创建目录 "code" +$ mkdir code +// 切换到该目录 +$ cd code +``` + +...以及一个 Python 代码示例... + +```Python +wont_work() # 这不会起作用 😱 +works(foo="bar") # 这可行 🎉 +``` + +...就这些。 + +//// + +//// tab | 信息 + +代码块中的代码不应被修改,注释除外。 + +参见 `scripts/translate.py` 中通用提示的 `### Content of code blocks` 部分。 + +//// + +## 选项卡与彩色提示框 { #tabs-and-colored-boxes } + +//// tab | 测试 + +/// info | 信息 +Some text +/// + +/// note | 注意 +Some text +/// + +/// note | 技术细节 +Some text +/// + +/// check | 检查 +Some text +/// + +/// tip | 提示 +Some text +/// + +/// warning | 警告 +Some text +/// + +/// danger | 危险 +Some text +/// + +//// + +//// tab | 信息 + +选项卡以及 `Info`/`Note`/`Warning`/等提示块,应在竖线(`|`)后添加其标题的翻译。 + +参见 `scripts/translate.py` 中通用提示的 `### Special blocks` 与 `### Tab blocks` 部分。 + +//// + +## Web 与内部链接 { #web-and-internal-links } + +//// tab | 测试 + +链接文本应被翻译,链接地址应保持不变: + +* [链接到上面的标题](#code-snippets) +* [内部链接](index.md#installation){.internal-link target=_blank} +* 外部链接 +* 样式链接 +* 脚本链接 +* 图片链接 + +链接文本应被翻译,且链接地址应指向对应的译文页面: + +* FastAPI 链接 + +//// + +//// tab | 信息 + +链接的文本应被翻译,但地址保持不变。唯一的例外是指向 FastAPI 文档页面的绝对链接,此时应指向对应语言的译文。 + +参见 `scripts/translate.py` 中通用提示的 `### Links` 部分。 + +//// + +## HTML "abbr" 元素 { #html-abbr-elements } + +//// tab | 测试 + +这里有一些包裹在 HTML "abbr" 元素中的内容(有些是虚构的): + +### abbr 提供了完整短语 { #the-abbr-gives-a-full-phrase } + +* GTD +* lt +* XWT +* PSGI + +### abbr 提供了完整短语与解释 { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* MDN +* I/O. + +//// + +//// tab | 信息 + +"abbr" 元素的 "title" 属性需要按照特定规则进行翻译。 + +译文可以自行添加 "abbr" 元素以解释英语单词,LLM 不应删除这些元素。 + +参见 `scripts/translate.py` 中通用提示的 `### HTML abbr elements` 部分。 + +//// + +## HTML "dfn" 元素 { #html-dfn-elements } + +* 集群 +* 深度学习 + +## 标题 { #headings } + +//// tab | 测试 + +### 开发 Web 应用——教程 { #develop-a-webapp-a-tutorial } + +Hello. + +### 类型提示与注解 { #type-hints-and-annotations } + +Hello again. + +### 超类与子类 { #super-and-subclasses } + +Hello again. + +//// + +//// tab | 信息 + +关于标题的唯一硬性规则是:LLM 必须保持花括号内的哈希部分不变,以确保链接不会失效。 + +参见 `scripts/translate.py` 中通用提示的 `### Headings` 部分。 + +语言特定的说明可参见例如 `docs/de/llm-prompt.md` 中的 `### Headings` 部分。 + +//// + +## 文档中使用的术语 { #terms-used-in-the-docs } + +//// tab | 测试 + +* you +* your + +* e.g. +* etc. + +* `foo` as an `int` +* `bar` as a `str` +* `baz` as a `list` + +* the Tutorial - User guide +* the Advanced User Guide +* the SQLModel docs +* the API docs +* the automatic docs + +* Data Science +* Deep Learning +* Machine Learning +* Dependency Injection +* HTTP Basic authentication +* HTTP Digest +* ISO format +* the JSON Schema standard +* the JSON schema +* the schema definition +* Password Flow +* Mobile + +* deprecated +* designed +* invalid +* on the fly +* standard +* default +* case-sensitive +* case-insensitive + +* to serve the application +* to serve the page + +* the app +* the application + +* the request +* the response +* the error response + +* the path operation +* the path operation decorator +* the path operation function + +* the body +* the request body +* the response body +* the JSON body +* the form body +* the file body +* the function body + +* the parameter +* the body parameter +* the path parameter +* the query parameter +* the cookie parameter +* the header parameter +* the form parameter +* the function parameter + +* the event +* the startup event +* the startup of the server +* the shutdown event +* the lifespan event + +* the handler +* the event handler +* the exception handler +* to handle + +* the model +* the Pydantic model +* the data model +* the database model +* the form model +* the model object + +* the class +* the base class +* the parent class +* the subclass +* the child class +* the sibling class +* the class method + +* the header +* the headers +* the authorization header +* the `Authorization` header +* the forwarded header + +* the dependency injection system +* the dependency +* the dependable +* the dependant + +* I/O bound +* CPU bound +* concurrency +* parallelism +* multiprocessing + +* the env var +* the environment variable +* the `PATH` +* the `PATH` variable + +* the authentication +* the authentication provider +* the authorization +* the authorization form +* the authorization provider +* the user authenticates +* the system authenticates the user + +* the CLI +* the command line interface + +* the server +* the client + +* the cloud provider +* the cloud service + +* the development +* the development stages + +* the dict +* the dictionary +* the enumeration +* the enum +* the enum member + +* the encoder +* the decoder +* to encode +* to decode + +* the exception +* to raise + +* the expression +* the statement + +* the frontend +* the backend + +* the GitHub discussion +* the GitHub issue + +* the performance +* the performance optimization + +* the return type +* the return value + +* the security +* the security scheme + +* the task +* the background task +* the task function + +* the template +* the template engine + +* the type annotation +* the type hint + +* the server worker +* the Uvicorn worker +* the Gunicorn Worker +* the worker process +* the worker class +* the workload + +* the deployment +* to deploy + +* the SDK +* the software development kit + +* the `APIRouter` +* the `requirements.txt` +* the Bearer Token +* the breaking change +* the bug +* the button +* the callable +* the code +* the commit +* the context manager +* the coroutine +* the database session +* the disk +* the domain +* the engine +* the fake X +* the HTTP GET method +* the item +* the library +* the lifespan +* the lock +* the middleware +* the mobile application +* the module +* the mounting +* the network +* the origin +* the override +* the payload +* the processor +* the property +* the proxy +* the pull request +* the query +* the RAM +* the remote machine +* the status code +* the string +* the tag +* the web framework +* the wildcard +* to return +* to validate + +//// + +//// tab | 信息 + +这是一份不完整且非规范性的(主要是)技术术语清单,取自文档中常见的词汇。它可能有助于提示词设计者判断哪些术语需要对 LLM 提供额外指引。例如当它总是把一个好的译法改回次优译法,或在你的语言中对某个术语的词形变化有困难时。 + +参见例如 `docs/de/llm-prompt.md` 中的 `### List of English terms and their preferred German translations` 部分。 + +//// + +//// + +翻译(术语)对照: + +//// tab | 测试(译文) + +* 你 +* 你的 + +* 例如 +* 等等 + +* 将 `foo` 作为 `int` +* 将 `bar` 作为 `str` +* 将 `baz` 作为 `list` + +* 教程 - 用户指南 +* 高级用户指南 +* SQLModel 文档 +* API 文档 +* 自动文档 + +* 数据科学 +* 深度学习 +* 机器学习 +* 依赖注入 +* HTTP 基本认证 +* HTTP 摘要认证 +* ISO 格式 +* JSON Schema 标准 +* JSON 模式 +* 模式定义 +* 密码流 +* 移动端 + +* 已弃用 +* 设计的 +* 无效 +* 即时 +* 标准的 +* 默认的 +* 区分大小写 +* 不区分大小写 + +* 为应用提供服务 +* 为页面提供服务 + +* 应用 +* 应用程序 + +* 请求 +* 响应 +* 错误响应 + +* 路径操作 +* 路径操作装饰器 +* 路径操作函数 + +* 主体 +* 请求体 +* 响应体 +* JSON 体 +* 表单体 +* 文件体 +* 函数体 + +* 参数 +* 请求体参数 +* 路径参数 +* 查询参数 +* Cookie 参数 +* Header 参数 +* 表单参数 +* 函数参数 + +* 事件 +* 启动事件 +* 服务器的启动 +* 关闭事件 +* 生命周期事件 + +* 处理器 +* 事件处理器 +* 异常处理器 +* 处理 + +* 模型 +* Pydantic 模型 +* 数据模型 +* 数据库模型 +* 表单模型 +* 模型对象 + +* 类 +* 基类 +* 父类 +* 子类 +* 子类 +* 兄弟类 +* 类方法 + +* 请求头 +* 请求头 +* 授权头 +* `Authorization` 头 +* 转发头 + +* 依赖注入系统 +* 依赖 +* 可依赖对象 +* 依赖项 + +* I/O 受限 +* CPU 受限 +* 并发 +* 并行 +* 多进程 + +* 环境变量 +* 环境变量 +* `PATH` +* `PATH` 变量 + +* 认证 +* 认证提供方 +* 授权 +* 授权表单 +* 授权提供方 +* 用户进行认证 +* 系统对用户进行认证 + +* CLI +* 命令行界面 + +* 服务器 +* 客户端 + +* 云服务提供商 +* 云服务 + +* 开发 +* 开发阶段 + +* dict +* 字典 +* 枚举 +* 枚举 +* 枚举成员 + +* 编码器 +* 解码器 +* 编码 +* 解码 + +* 异常 +* 抛出 + +* 表达式 +* 语句 + +* 前端 +* 后端 + +* GitHub 讨论 +* GitHub Issue + +* 性能 +* 性能优化 + +* 返回类型 +* 返回值 + +* 安全 +* 安全方案 + +* 任务 +* 后台任务 +* 任务函数 + +* 模板 +* 模板引擎 + +* 类型注解 +* 类型提示 + +* 服务器 worker +* Uvicorn worker +* Gunicorn worker +* worker 进程 +* worker 类 +* 工作负载 + +* 部署 +* 部署 + +* SDK +* 软件开发工具包 + +* `APIRouter` +* `requirements.txt` +* Bearer Token +* 破坏性变更 +* Bug +* 按钮 +* 可调用对象 +* 代码 +* 提交 +* 上下文管理器 +* 协程 +* 数据库会话 +* 磁盘 +* 域名 +* 引擎 +* 假 X +* HTTP GET 方法 +* 项 +* 库 +* 生命周期 +* 锁 +* 中间件 +* 移动应用 +* 模块 +* 挂载 +* 网络 +* 源 +* 覆盖 +* 负载 +* 处理器 +* 属性 +* 代理 +* Pull Request +* 查询 +* RAM +* 远程机器 +* 状态码 +* 字符串 +* 标签 +* Web 框架 +* 通配符 +* 返回 +* 校验 + +//// + +//// tab | 信息(译文) + +此清单是不完整且非规范性的,列出(主要是)文档中出现的技术术语。它有助于提示词设计者确定哪些术语需要额外的指引。例如当 LLM 总是把更好的译法改回次优译法,或在你的语言中难以正确变形时。 + +也可参见 `docs/de/llm-prompt.md` 中的 `### List of English terms and their preferred German translations` 部分。 + +//// diff --git a/docs/zh/docs/about/index.md b/docs/zh/docs/about/index.md new file mode 100644 index 000000000..9b73533f7 --- /dev/null +++ b/docs/zh/docs/about/index.md @@ -0,0 +1,3 @@ +# 关于 { #about } + +关于 FastAPI、其设计、灵感等。🤓 diff --git a/docs/zh/docs/advanced/additional-responses.md b/docs/zh/docs/advanced/additional-responses.md index bc197280a..aa3d22d1c 100644 --- a/docs/zh/docs/advanced/additional-responses.md +++ b/docs/zh/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ 例如,要声明另一个状态码为 `404` 且具有 Pydantic 模型 `Message` 的响应,你可以这样写: -{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} /// note | 注意 @@ -203,7 +203,7 @@ 以及一个状态码为 `200` 的响应,它使用你的 `response_model`,但包含自定义的 `example`: -{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} 所有这些都会被合并并包含到你的 OpenAPI 中,并显示在 API 文档里: diff --git a/docs/zh/docs/advanced/additional-status-codes.md b/docs/zh/docs/advanced/additional-status-codes.md index 23ceab4e8..7eeffaf53 100644 --- a/docs/zh/docs/advanced/additional-status-codes.md +++ b/docs/zh/docs/advanced/additional-status-codes.md @@ -16,7 +16,7 @@ {* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} -/// warning +/// warning | 警告 当你直接返回一个像上面例子中的 `Response` 对象时,它会直接返回。 @@ -26,7 +26,7 @@ /// -/// note | 技术细节 +/// note | 注意 你也可以使用 `from starlette.responses import JSONResponse`。  diff --git a/docs/zh/docs/advanced/advanced-dependencies.md b/docs/zh/docs/advanced/advanced-dependencies.md index 3efca8944..a547e8881 100644 --- a/docs/zh/docs/advanced/advanced-dependencies.md +++ b/docs/zh/docs/advanced/advanced-dependencies.md @@ -18,7 +18,7 @@ 为此,声明一个 `__call__` 方法: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} 在这种情况下,**FastAPI** 会使用这个 `__call__` 来检查附加参数和子依赖,并且稍后会调用它,把返回值传递给你的*路径操作函数*中的参数。 @@ -26,7 +26,7 @@ 现在,我们可以用 `__init__` 声明实例的参数,用来“参数化”这个依赖项: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} 在本例中,**FastAPI** 不会接触或关心 `__init__`,我们会在自己的代码中直接使用它。 @@ -34,7 +34,7 @@ 我们可以这样创建该类的实例: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} 这样就把依赖项“参数化”了,现在它内部带有属性 `checker.fixed_content` 的值 `"bar"`。 @@ -50,7 +50,7 @@ checker(q="somequery") ...并将其返回值作为依赖项的值,传给我们的*路径操作函数*中的参数 `fixed_content_included`: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} /// tip | 提示 diff --git a/docs/zh/docs/advanced/advanced-python-types.md b/docs/zh/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..bbc9302e8 --- /dev/null +++ b/docs/zh/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# 高级 Python 类型 { #advanced-python-types } + +这里有一些在使用 Python 类型时可能有用的额外想法。 + +## 使用 `Union` 或 `Optional` { #using-union-or-optional } + +如果你的代码因为某些原因不能使用 `|`,例如它不是在类型注解里,而是在 `response_model=` 之类的参数中,那么你可以使用 `typing` 中的 `Union` 来代替竖线(`|`)。 + +例如,你可以声明某个值可以是 `str` 或 `None`: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +`typing` 也提供了一个声明“可能为 `None`”的快捷方式:`Optional`。 + +从我非常主观的角度给个小建议: + +- 🚨 避免使用 `Optional[SomeType]` +- 改用 ✨`Union[SomeType, None]`✨。 + +两者是等价的,底层其实也是一样的。但我更推荐使用 `Union` 而不是 `Optional`,因为单词“optional”(可选)看起来会暗示该值是可选的,而它真正的含义是“它可以是 `None`”,即使它并不是可选的,仍然是必填的。 + +我认为 `Union[SomeType, None]` 更能明确表达其含义。 + +这只是关于词语和命名的问题,但这些词语会影响你和你的队友如何看待代码。 + +举个例子,看这段函数: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +参数 `name` 被定义为 `Optional[str]`,但它并不是“可选”的,你不能不传这个参数就调用函数: + +```Python +say_hi() # 哎呀,这会报错!😱 +``` + +参数 `name` 仍然是必填的(不是“可选”),因为它没有默认值。不过,`name` 接受 `None` 作为取值: + +```Python +say_hi(name=None) # 这样可以,None 是有效的 🎉 +``` + +好消息是,在大多数情况下,你可以直接使用 `|` 来定义类型联合: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +因此,通常你不必为像 `Optional` 和 `Union` 这样的名字而操心。😎 diff --git a/docs/zh/docs/advanced/async-tests.md b/docs/zh/docs/advanced/async-tests.md index 6803358d2..16b8a8c81 100644 --- a/docs/zh/docs/advanced/async-tests.md +++ b/docs/zh/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ 文件 `main.py` 将包含: -{* ../../docs_src/async_tests/app_a_py39/main.py *} +{* ../../docs_src/async_tests/app_a_py310/main.py *} 文件 `test_main.py` 将包含针对 `main.py` 的测试,现在它可能看起来如下: -{* ../../docs_src/async_tests/app_a_py39/test_main.py *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} ## 运行测试 { #run-it } @@ -56,7 +56,7 @@ $ pytest 这个标记 `@pytest.mark.anyio` 会告诉 pytest 该测试函数应该被异步调用: -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} /// tip | 提示 @@ -66,7 +66,7 @@ $ pytest 我们现在可以使用应用程序创建一个 `AsyncClient` ,并使用 `await` 向其发送异步请求。 -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} 这相当于: @@ -74,7 +74,7 @@ $ pytest response = client.get('/') ``` -我们曾经通过它向 `TestClient` 发出请求。 +...我们曾经通过它向 `TestClient` 发出请求。 /// tip | 提示 diff --git a/docs/zh/docs/advanced/behind-a-proxy.md b/docs/zh/docs/advanced/behind-a-proxy.md index b44b0b5ac..3ccc65f29 100644 --- a/docs/zh/docs/advanced/behind-a-proxy.md +++ b/docs/zh/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" 例如,假设你定义了一个*路径操作* `/items/`: -{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} 如果客户端尝试访问 `/items`,默认会被重定向到 `/items/`。 @@ -115,7 +115,7 @@ sequenceDiagram 即使你的所有代码都假设只有 `/app`。 -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} 代理会在将请求传递给应用服务器(可能是通过 FastAPI CLI 运行的 Uvicorn)之前,实时**“移除”**这个**路径前缀**,让你的应用认为它是在 `/app` 被服务,这样你就不需要更新所有代码去包含 `/api/v1` 前缀。 @@ -193,7 +193,7 @@ ASGI 规范为这种用例定义了 `root_path`。 这里我们把它包含在响应消息中仅用于演示。 -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} 然后,如果你这样启动 Uvicorn: @@ -220,7 +220,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 或者,如果你无法提供类似 `--root-path` 的命令行选项,你可以在创建 FastAPI 应用时设置参数 `root_path`: -{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} 把 `root_path` 传给 `FastAPI` 等同于把命令行选项 `--root-path` 传给 Uvicorn 或 Hypercorn。 @@ -400,7 +400,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 例如: -{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} 会生成如下的 OpenAPI 模式: @@ -455,7 +455,7 @@ OpenAPI 规范中的 `servers` 属性是可选的。 如果你不希望 **FastAPI** 包含一个使用 `root_path` 的自动服务器,可以使用参数 `root_path_in_servers=False`: -{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} 这样它就不会被包含到 OpenAPI 模式中。 diff --git a/docs/zh/docs/advanced/custom-response.md b/docs/zh/docs/advanced/custom-response.md index f5bec5fdc..7c19b73fb 100644 --- a/docs/zh/docs/advanced/custom-response.md +++ b/docs/zh/docs/advanced/custom-response.md @@ -30,7 +30,7 @@ 但如果你确定你返回的内容是「可以用 JSON 序列化」的,你可以将它直接传给响应类,从而避免在传给响应类之前先通过 `jsonable_encoder` 带来的额外开销。 -{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} /// info | 信息 @@ -55,7 +55,7 @@ * 导入 `HTMLResponse`。 * 将 `HTMLResponse` 作为你的 *路径操作* 的 `response_class` 参数传入。 -{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} /// info | 信息 @@ -73,7 +73,7 @@ 和上面一样的例子,返回一个 `HTMLResponse` 看起来可能是这样: -{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} /// warning | 警告 @@ -97,7 +97,7 @@ 比如像这样: -{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} 在这个例子中,函数 `generate_html_response()` 已经生成并返回 `Response` 对象而不是在 `str` 中返回 HTML。 @@ -136,7 +136,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它还将包含一个基于 `media_type` 的 Content-Type 头,并为文本类型附加一个字符集。 -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ### `HTMLResponse` { #htmlresponse } @@ -146,7 +146,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 接受文本或字节并返回纯文本响应。 -{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} ### `JSONResponse` { #jsonresponse } @@ -180,7 +180,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 /// -{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} /// tip | 提示 @@ -194,13 +194,13 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 你可以直接返回一个 `RedirectResponse`: -{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} --- 或者你可以把它用于 `response_class` 参数: -{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} 如果你这么做,那么你可以在 *路径操作* 函数中直接返回 URL。 @@ -210,13 +210,13 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 你也可以将 `status_code` 参数和 `response_class` 参数结合使用: -{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} ### `StreamingResponse` { #streamingresponse } 采用异步生成器或普通生成器/迭代器,然后流式传输响应主体。 -{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} #### 对类似文件的对象使用 `StreamingResponse` { #using-streamingresponse-with-file-like-objects } @@ -226,7 +226,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 这也包括许多与云存储、视频处理等交互的库。 -{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} 1. 这是生成器函数。之所以是「生成器函数」,是因为它内部包含 `yield` 语句。 2. 通过使用 `with` 代码块,我们可以确保在生成器函数完成后关闭类文件对象。因此,在它完成发送响应之后会被关闭。 @@ -255,11 +255,11 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 文件响应将包含适当的 `Content-Length`、`Last-Modified` 和 `ETag` 响应头。 -{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} 你也可以使用 `response_class` 参数: -{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} 在这种情况下,你可以在 *路径操作* 函数中直接返回文件路径。 @@ -273,7 +273,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 你可以创建一个 `CustomORJSONResponse`。你需要做的主要事情是实现一个返回 `bytes` 的 `Response.render(content)` 方法: -{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} 现在,不再是返回: @@ -281,7 +281,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 {"message": "Hello World"} ``` -…这个响应将返回: +...这个响应将返回: ```json { @@ -299,7 +299,7 @@ FastAPI(实际上是 Starlette)将自动包含 Content-Length 的头。它 在下面的示例中,**FastAPI** 会在所有 *路径操作* 中默认使用 `ORJSONResponse`,而不是 `JSONResponse`。 -{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} /// tip | 提示 diff --git a/docs/zh/docs/advanced/dataclasses.md b/docs/zh/docs/advanced/dataclasses.md index d62aef8f3..f552d779f 100644 --- a/docs/zh/docs/advanced/dataclasses.md +++ b/docs/zh/docs/advanced/dataclasses.md @@ -59,7 +59,7 @@ FastAPI 基于 **Pydantic** 构建,我已经向你展示过如何使用 Pydant 在本例中,它是一个 `Item` 数据类列表。 6. 这里我们返回一个字典,里面的 `items` 是一个数据类列表。 - FastAPI 仍然能够将数据序列化为 JSON。 + FastAPI 仍然能够将数据序列化为 JSON。 7. 这里的 `response_model` 使用了 “`Author` 数据类列表” 的类型注解。 同样,你可以将 `dataclasses` 与标准类型注解组合使用。 diff --git a/docs/zh/docs/advanced/events.md b/docs/zh/docs/advanced/events.md index 7b49931a4..71ad1ae38 100644 --- a/docs/zh/docs/advanced/events.md +++ b/docs/zh/docs/advanced/events.md @@ -30,7 +30,7 @@ 我们使用 `yield` 创建了一个异步函数 `lifespan()` 像这样: -{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} 在这里,我们在 `yield` 之前将(虚拟的)模型函数放入机器学习模型的字典中,以此模拟加载模型的耗时**启动**操作。这段代码将在应用程序**开始处理请求之前**执行,即**启动**期间。 @@ -48,7 +48,7 @@ 首先要注意的是,我们定义了一个带有 `yield` 的异步函数。这与带有 `yield` 的依赖项非常相似。 -{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} 这个函数在 `yield` 之前的部分,会在应用启动前执行。 @@ -60,7 +60,7 @@ 它将函数转化为所谓的“**异步上下文管理器**”。 -{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} 在 Python 中,**上下文管理器**是一个你可以在 `with` 语句中使用的东西,例如,`open()` 可以作为上下文管理器使用。 @@ -82,7 +82,7 @@ async with lifespan(app): `FastAPI` 的 `lifespan` 参数接受一个**异步上下文管理器**,所以我们可以把我们新定义的异步上下文管理器 `lifespan` 传给它。 -{* ../../docs_src/events/tutorial003_py39.py hl[22] *} +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} ## 替代事件(弃用) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ async with lifespan(app): 使用事件 `"startup"` 声明一个在应用启动前运行的函数: -{* ../../docs_src/events/tutorial001_py39.py hl[8] *} +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} 本例中,`startup` 事件处理器函数为项目“数据库”(只是一个 `dict`)提供了一些初始值。 @@ -116,7 +116,7 @@ async with lifespan(app): 使用事件 `"shutdown"` 声明一个在应用关闭时运行的函数: -{* ../../docs_src/events/tutorial002_py39.py hl[6] *} +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} 此处,`shutdown` 事件处理器函数会向文件 `log.txt` 写入一行文本 `"Application shutdown"`。 diff --git a/docs/zh/docs/advanced/generate-clients.md b/docs/zh/docs/advanced/generate-clients.md index 48a4ba07a..e8a3b2055 100644 --- a/docs/zh/docs/advanced/generate-clients.md +++ b/docs/zh/docs/advanced/generate-clients.md @@ -40,7 +40,7 @@ FastAPI 会自动生成 **OpenAPI 3.1** 规范,因此你使用的任何工具 先从一个简单的 FastAPI 应用开始: -{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} 请注意,这些*路径操作*使用 `Item` 和 `ResponseMessage` 模型来定义它们的请求载荷和响应载荷。 @@ -98,7 +98,7 @@ npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client 例如,你可以有一个 **items** 相关的部分和另一个 **users** 相关的部分,它们可以用标签来分隔: -{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} ### 生成带标签的 TypeScript 客户端 { #generate-a-typescript-client-with-tags } @@ -121,7 +121,7 @@ npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) ``` -……这是因为客户端生成器会把每个*路径操作*的 OpenAPI 内部**操作 ID(operation ID)**用作方法名的一部分。 +...这是因为客户端生成器会把每个*路径操作*的 OpenAPI 内部**操作 ID(operation ID)**用作方法名的一部分。 OpenAPI 要求每个操作 ID 在所有*路径操作*中都是唯一的,因此 FastAPI 会使用**函数名**、**路径**和**HTTP 方法/操作**来生成操作 ID,以确保其唯一性。 @@ -145,7 +145,7 @@ FastAPI 为每个*路径操作*使用一个**唯一 ID**,它既用于**操作 然后你可以把这个自定义函数通过 `generate_unique_id_function` 参数传给 **FastAPI**: -{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} ### 使用自定义操作 ID 生成 TypeScript 客户端 { #generate-a-typescript-client-with-custom-operation-ids } @@ -167,7 +167,7 @@ FastAPI 为每个*路径操作*使用一个**唯一 ID**,它既用于**操作 我们可以把 OpenAPI JSON 下载到 `openapi.json` 文件中,然后用如下脚本**移除这个标签前缀**: -{* ../../docs_src/generate_clients/tutorial004_py39.py *} +{* ../../docs_src/generate_clients/tutorial004_py310.py *} //// tab | Node.js diff --git a/docs/zh/docs/advanced/middleware.md b/docs/zh/docs/advanced/middleware.md index 108bbbb5c..de4a3fcb1 100644 --- a/docs/zh/docs/advanced/middleware.md +++ b/docs/zh/docs/advanced/middleware.md @@ -43,7 +43,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") **FastAPI** 为常见用例提供了一些中间件,下面介绍怎么使用这些中间件。 -/// note | 注意 +/// note | 技术细节 以下几个示例中也可以使用 `from starlette.middleware.something import SomethingMiddleware`。 @@ -57,13 +57,13 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") 任何传向 `http` 或 `ws` 的请求都会被重定向至安全方案。 -{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} ## `TrustedHostMiddleware` { #trustedhostmiddleware } 强制所有传入请求都必须正确设置 `Host` 请求头,以防 HTTP 主机头攻击。 -{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} 支持以下参数: @@ -78,7 +78,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") 中间件会处理标准响应与流响应。 -{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} 支持以下参数: diff --git a/docs/zh/docs/advanced/openapi-callbacks.md b/docs/zh/docs/advanced/openapi-callbacks.md index 6e8df68ae..cc9f5c28e 100644 --- a/docs/zh/docs/advanced/openapi-callbacks.md +++ b/docs/zh/docs/advanced/openapi-callbacks.md @@ -179,7 +179,7 @@ JSON 请求体包含如下内容: ### 查看文档 { #check-the-docs } -现在,使用 Uvicorn 启动应用,打开 http://127.0.0.1:8000/docs。 +现在,启动应用并打开 http://127.0.0.1:8000/docs。 就能看到文档的*路径操作*已经包含了**回调**的内容以及*外部 API*: diff --git a/docs/zh/docs/advanced/openapi-webhooks.md b/docs/zh/docs/advanced/openapi-webhooks.md index 9e64ed4e3..d23fbcf88 100644 --- a/docs/zh/docs/advanced/openapi-webhooks.md +++ b/docs/zh/docs/advanced/openapi-webhooks.md @@ -32,7 +32,7 @@ 当您创建一个 **FastAPI** 应用程序时,有一个 `webhooks` 属性可以用来定义网络钩子,方式与您定义*路径操作*的时候相同,例如使用 `@app.webhooks.post()` 。 -{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} 您定义的网络钩子将被包含在 `OpenAPI` 的架构中,并出现在自动生成的**文档 UI** 中。 diff --git a/docs/zh/docs/advanced/path-operation-advanced-configuration.md b/docs/zh/docs/advanced/path-operation-advanced-configuration.md index 6c9928ffc..588d4f09c 100644 --- a/docs/zh/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/zh/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ 务必确保每个操作的 `operation_id` 都是唯一的。 -{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} ### 使用 *路径操作函数* 的函数名作为 operationId { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ 你应该在添加了所有 *路径操作* 之后执行此操作。 -{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} /// tip @@ -40,7 +40,7 @@ 使用参数 `include_in_schema` 并将其设置为 `False`,来从生成的 OpenAPI 方案中排除一个 *路径操作*(这样一来,就从自动化文档系统中排除掉了): -{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} ## 来自 docstring 的高级描述 { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ 例如,这个 `openapi_extra` 可用于声明 [OpenAPI 扩展](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): -{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *} 当你打开自动 API 文档时,你的扩展会显示在该 *路径操作* 的底部。 @@ -139,9 +139,9 @@ 你可以用 `openapi_extra` 来做到: -{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *} -在这个示例中,我们没有声明任何 Pydantic 模型。事实上,请求体甚至没有被 解析 为 JSON,而是直接以 `bytes` 读取,并由函数 `magic_data_reader()` 以某种方式负责解析。 +在这个示例中,我们没有声明任何 Pydantic 模型。事实上,请求体甚至没有被 解析 为 JSON,而是直接以 `bytes` 读取,并由函数 `magic_data_reader()` 以某种方式负责解析。 尽管如此,我们仍然可以声明请求体的预期方案。 @@ -153,7 +153,7 @@ 例如,在这个应用中我们不使用 FastAPI 集成的从 Pydantic 模型提取 JSON Schema 的功能,也不使用对 JSON 的自动校验。实际上,我们将请求的内容类型声明为 YAML,而不是 JSON: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *} 尽管我们没有使用默认的集成功能,我们仍然使用 Pydantic 模型手动生成我们想以 YAML 接收的数据的 JSON Schema。 @@ -161,7 +161,7 @@ 接着在我们的代码中,我们直接解析该 YAML 内容,然后再次使用同一个 Pydantic 模型来验证该 YAML 内容: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *} /// tip diff --git a/docs/zh/docs/advanced/response-change-status-code.md b/docs/zh/docs/advanced/response-change-status-code.md index cdcd39f50..0b004bf4e 100644 --- a/docs/zh/docs/advanced/response-change-status-code.md +++ b/docs/zh/docs/advanced/response-change-status-code.md @@ -20,9 +20,11 @@ 然后你可以在这个*临时*响应对象中设置`status_code`。 -{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *} -然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 +然后你可以像平常一样返回任何你需要的对象(例如一个`dict`或者一个数据库模型)。 + +如果你声明了一个`response_model`,它仍然会被用来过滤和转换你返回的对象。 **FastAPI**将使用这个*临时*响应来提取状态码(也包括cookies和头部),并将它们放入包含你返回的值的最终响应中,该响应由任何`response_model`过滤。 diff --git a/docs/zh/docs/advanced/response-cookies.md b/docs/zh/docs/advanced/response-cookies.md index cc311a270..c618cd0f0 100644 --- a/docs/zh/docs/advanced/response-cookies.md +++ b/docs/zh/docs/advanced/response-cookies.md @@ -4,7 +4,7 @@ 你可以在 *路径操作函数* 中定义一个类型为 `Response` 的参数,这样你就可以在这个临时响应对象中设置cookie了。 -{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *} 而且你还可以根据你的需要响应不同的对象,比如常用的 `dict`,数据库model等。 @@ -22,9 +22,9 @@ 然后设置Cookies,并返回: -{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *} -/// tip +/// tip | 提示 需要注意,如果你直接反馈一个response对象,而不是使用`Response`入参,FastAPI则会直接反馈你封装的response对象。 diff --git a/docs/zh/docs/advanced/response-directly.md b/docs/zh/docs/advanced/response-directly.md index 8a9cf6ab8..a97992d24 100644 --- a/docs/zh/docs/advanced/response-directly.md +++ b/docs/zh/docs/advanced/response-directly.md @@ -36,7 +36,7 @@ {* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} -/// note | 注意 +/// note | 技术细节 你也可以使用 `from starlette.responses import JSONResponse`。 @@ -54,7 +54,7 @@ 你可以把你的 XML 内容放到一个字符串中,放到一个 `Response` 中,然后返回: -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ## 说明 { #notes } diff --git a/docs/zh/docs/advanced/response-headers.md b/docs/zh/docs/advanced/response-headers.md index fa02f53be..01bde56d2 100644 --- a/docs/zh/docs/advanced/response-headers.md +++ b/docs/zh/docs/advanced/response-headers.md @@ -6,13 +6,13 @@ 然后你可以在这个*临时*响应对象中设置头部。 -{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *} +{* ../../docs_src/response_headers/tutorial002_py310.py hl[1, 7:8] *} 然后你可以像平常一样返回任何你需要的对象(例如一个 `dict` 或者一个数据库模型)。 如果你声明了一个 `response_model`,它仍然会被用来过滤和转换你返回的对象。 -**FastAPI** 将使用这个临时响应来提取头部(也包括 cookies 和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何 `response_model` 过滤。 +**FastAPI** 将使用这个*临时*响应来提取头部(也包括 cookies 和状态码),并将它们放入包含你返回的值的最终响应中,该响应由任何 `response_model` 过滤。 你也可以在依赖项中声明 `Response` 参数,并在其中设置头部(和 cookies)。 @@ -22,7 +22,7 @@ 按照[直接返回响应](response-directly.md){.internal-link target=_blank}中所述创建响应,并将头部作为附加参数传递: -{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *} +{* ../../docs_src/response_headers/tutorial001_py310.py hl[10:12] *} /// note | 技术细节 diff --git a/docs/zh/docs/advanced/security/http-basic-auth.md b/docs/zh/docs/advanced/security/http-basic-auth.md index 55479d8e3..9128a4975 100644 --- a/docs/zh/docs/advanced/security/http-basic-auth.md +++ b/docs/zh/docs/advanced/security/http-basic-auth.md @@ -20,7 +20,7 @@ HTTP 基础授权让浏览器显示内置的用户名与密码提示。 * 返回类型为 `HTTPBasicCredentials` 的对象: * 包含发送的 `username` 与 `password` -{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} +{* ../../docs_src/security/tutorial006_an_py310.py hl[4,8,12] *} 第一次打开 URL(或在 API 文档中点击 **Execute** 按钮)时,浏览器要求输入用户名与密码: @@ -40,7 +40,7 @@ HTTP 基础授权让浏览器显示内置的用户名与密码提示。 然后我们可以使用 `secrets.compare_digest()` 来确保 `credentials.username` 是 `"stanleyjobson"`,且 `credentials.password` 是`"swordfish"`。 -{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} +{* ../../docs_src/security/tutorial007_an_py310.py hl[1,12:24] *} 这类似于: @@ -104,4 +104,4 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": 检测到凭证不正确后,返回 `HTTPException` 及状态码 401(与无凭证时返回的内容一样),并添加响应头 `WWW-Authenticate`,让浏览器再次显示登录提示: -{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} +{* ../../docs_src/security/tutorial007_an_py310.py hl[26:30] *} diff --git a/docs/zh/docs/advanced/settings.md b/docs/zh/docs/advanced/settings.md index adf264491..b4def73eb 100644 --- a/docs/zh/docs/advanced/settings.md +++ b/docs/zh/docs/advanced/settings.md @@ -54,7 +54,7 @@ $ pip install "fastapi[all]" 你可以使用与 Pydantic 模型相同的验证功能和工具,例如不同的数据类型,以及使用 `Field()` 进行附加验证。 -{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} +{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *} /// tip | 提示 @@ -70,7 +70,7 @@ $ pip install "fastapi[all]" 然后你可以在应用中使用新的 `settings` 对象: -{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *} +{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *} ### 运行服务器 { #run-the-server } @@ -100,19 +100,19 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p ## 在另一个模块中放置设置 { #settings-in-another-module } -你可以把这些设置放在另一个模块文件中,就像你在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 +你可以把这些设置放在另一个模块文件中,就像你在[更大的应用 - 多个文件](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 例如,可以有一个 `config.py` 文件: -{* ../../docs_src/settings/app01_py39/config.py *} +{* ../../docs_src/settings/app01_py310/config.py *} 然后在 `main.py` 文件中使用它: -{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *} +{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *} /// tip | 提示 -你还需要一个 `__init__.py` 文件,就像你在[Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 +你还需要一个 `__init__.py` 文件,就像你在[更大的应用 - 多个文件](../tutorial/bigger-applications.md){.internal-link target=_blank}中看到的那样。 /// @@ -126,7 +126,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p 延续上一个示例,你的 `config.py` 文件可能如下所示: -{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} +{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *} 注意,现在我们不再创建默认实例 `settings = Settings()`。 @@ -134,7 +134,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p 现在我们创建一个依赖项,返回一个新的 `config.Settings()`。 -{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} +{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *} /// tip | 提示 @@ -144,15 +144,15 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p /// -然后我们可以在“路径操作函数”中将其作为依赖项引入,并在需要的任何地方使用它。 +然后我们可以在路径操作函数中将其作为依赖项引入,并在需要的任何地方使用它。 -{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} +{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *} ### 设置与测试 { #settings-and-testing } 接着,在测试期间,通过为 `get_settings` 创建依赖项覆盖,就可以很容易地提供一个不同的设置对象: -{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} +{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *} 在依赖项覆盖中,我们在创建新的 `Settings` 对象时为 `admin_email` 设置了一个新值,然后返回该新对象。 @@ -193,7 +193,7 @@ APP_NAME="ChimichangApp" 然后更新 `config.py`: -{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} +{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *} /// tip | 提示 @@ -226,7 +226,7 @@ def get_settings(): 但由于我们在顶部使用了 `@lru_cache` 装饰器,`Settings` 对象只会在第一次调用时创建一次。 ✔️ -{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} +{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *} 接着,对于后续请求中依赖项里对 `get_settings()` 的任何调用,它不会再次执行 `get_settings()` 的内部代码并创建新的 `Settings` 对象,而是会一遍又一遍地返回第一次调用时返回的那个相同对象。 diff --git a/docs/zh/docs/advanced/sub-applications.md b/docs/zh/docs/advanced/sub-applications.md index fe1fcd121..3e61610a3 100644 --- a/docs/zh/docs/advanced/sub-applications.md +++ b/docs/zh/docs/advanced/sub-applications.md @@ -10,7 +10,7 @@ 首先,创建主(顶层)**FastAPI** 应用及其*路径操作*: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *} ### 子应用 { #sub-application } @@ -18,7 +18,7 @@ 子应用只是另一个标准 FastAPI 应用,但这个应用是被**挂载**的应用: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *} ### 挂载子应用 { #mount-the-sub-application } @@ -26,7 +26,7 @@ 本例的子应用挂载在 `/subapi` 路径下: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *} ### 查看自动 API 文档 { #check-the-automatic-api-docs } diff --git a/docs/zh/docs/advanced/templates.md b/docs/zh/docs/advanced/templates.md index f2e5c21cf..37575aff2 100644 --- a/docs/zh/docs/advanced/templates.md +++ b/docs/zh/docs/advanced/templates.md @@ -27,7 +27,7 @@ $ pip install jinja2 * 在返回模板的*路径操作*中声明 `Request` 参数 * 使用 `templates` 渲染并返回 `TemplateResponse`,传递模板的名称、request 对象以及一个包含多个键值对(用于 Jinja2 模板)的 "context" 字典。 -{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *} +{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *} /// note diff --git a/docs/zh/docs/advanced/testing-dependencies.md b/docs/zh/docs/advanced/testing-dependencies.md index db0b39483..3291cfc25 100644 --- a/docs/zh/docs/advanced/testing-dependencies.md +++ b/docs/zh/docs/advanced/testing-dependencies.md @@ -46,7 +46,6 @@ FastAPI 可以覆盖这些位置的依赖项。 app.dependency_overrides = {} ``` - /// tip | 提示 如果只在某些测试时覆盖依赖项,您可以在测试开始时(在测试函数内)设置覆盖依赖项,并在结束时(在测试函数结尾)重置覆盖依赖项。 diff --git a/docs/zh/docs/advanced/testing-events.md b/docs/zh/docs/advanced/testing-events.md index 221984e90..90cbbda13 100644 --- a/docs/zh/docs/advanced/testing-events.md +++ b/docs/zh/docs/advanced/testing-events.md @@ -2,10 +2,10 @@ 当你需要在测试中运行 `lifespan` 时,可以将 `TestClient` 与 `with` 语句一起使用: -{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *} +{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *} 你可以在[官方 Starlette 文档站点的“在测试中运行 lifespan”](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)阅读更多细节。 对于已弃用的 `startup` 和 `shutdown` 事件,可以按如下方式使用 `TestClient`: -{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *} +{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *} diff --git a/docs/zh/docs/advanced/testing-websockets.md b/docs/zh/docs/advanced/testing-websockets.md index 64e1d3005..e435e41e2 100644 --- a/docs/zh/docs/advanced/testing-websockets.md +++ b/docs/zh/docs/advanced/testing-websockets.md @@ -4,7 +4,7 @@ 为此,在 `with` 语句中使用 `TestClient` 连接到 WebSocket: -{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *} +{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *} /// note | 注意 diff --git a/docs/zh/docs/advanced/using-request-directly.md b/docs/zh/docs/advanced/using-request-directly.md index 64ba8da1b..8cfad4203 100644 --- a/docs/zh/docs/advanced/using-request-directly.md +++ b/docs/zh/docs/advanced/using-request-directly.md @@ -29,7 +29,7 @@ 此时,需要直接访问请求。 -{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *} +{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *} 把*路径操作函数*的参数类型声明为 `Request`,**FastAPI** 就能把 `Request` 传递到参数里。 diff --git a/docs/zh/docs/advanced/websockets.md b/docs/zh/docs/advanced/websockets.md index 4486a2e69..513e1aaec 100644 --- a/docs/zh/docs/advanced/websockets.md +++ b/docs/zh/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets 但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式: -{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} ## 创建 `websocket` { #create-a-websocket } 在您的 **FastAPI** 应用程序中,创建一个 `websocket`: -{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} /// note | 技术细节 @@ -58,7 +58,7 @@ $ pip install websockets 在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。 -{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} 您可以接收和发送二进制、文本和 JSON 数据。 @@ -154,7 +154,7 @@ $ fastapi dev main.py 当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。 -{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} +{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} 尝试以下操作: diff --git a/docs/zh/docs/advanced/wsgi.md b/docs/zh/docs/advanced/wsgi.md index 73fcb3219..487fbf8dd 100644 --- a/docs/zh/docs/advanced/wsgi.md +++ b/docs/zh/docs/advanced/wsgi.md @@ -18,7 +18,7 @@ 之后将其挂载到某一个路径下。 -{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} +{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *} /// note | 注意 diff --git a/docs/zh/docs/alternatives.md b/docs/zh/docs/alternatives.md new file mode 100644 index 000000000..8a552c91d --- /dev/null +++ b/docs/zh/docs/alternatives.md @@ -0,0 +1,482 @@ +# 替代方案、灵感与对比 { #alternatives-inspiration-and-comparisons } + +是什么启发了 **FastAPI**,它与替代方案的比较,以及它从中学到的东西。 + +## 介绍 { #intro } + +没有前人的工作,就不会有 **FastAPI**。 + +在它诞生之前,已经有许多工具为其提供了灵感。 + +我曾经多年避免创建一个新框架。起初,我尝试用许多不同的框架、插件和工具来解决 **FastAPI** 所覆盖的全部功能。 + +但在某个时刻,除了创造一个能提供所有这些功能的东西之外,别无选择;它要吸收以往工具的最佳理念,并以尽可能好的方式组合起来,利用之前都不存在的语言特性(Python 3.6+ 类型提示)。 + +## 先前的工具 { #previous-tools } + +### Django { #django } + +它是最流行且被广泛信任的 Python 框架。被用于构建 Instagram 等系统。 + +它与关系型数据库(如 MySQL、PostgreSQL)耦合相对紧密,因此若要以 NoSQL 数据库(如 Couchbase、MongoDB、Cassandra 等)作为主要存储引擎并不容易。 + +它最初用于在后端生成 HTML,而不是创建由现代前端(如 React、Vue.js、Angular)或与之通信的其他系统(如 IoT 设备)使用的 API。 + +### Django REST Framework { #django-rest-framework } + +Django REST framework 作为一个灵活工具箱而创建,用于在底层使用 Django 构建 Web API,从而增强其 API 能力。 + +它被包括 Mozilla、Red Hat、Eventbrite 在内的许多公司使用。 + +它是最早的“自动 API 文档”的范例之一,这正是启发“寻找” **FastAPI** 的最初想法之一。 + +/// note | 注意 + +Django REST Framework 由 Tom Christie 创建。他也是 Starlette 和 Uvicorn 的作者,**FastAPI** 就是基于它们构建的。 + +/// + +/// check | 启发 **FastAPI**: + +提供自动化的 API 文档 Web 界面。 + +/// + +### Flask { #flask } + +Flask 是一个“微框架”,它不包含数据库集成,也没有像 Django 那样的许多默认内建功能。 + +这种简单与灵活使得可以将 NoSQL 数据库作为主要的数据存储系统。 + +由于非常简单,它相对直观易学,尽管文档在某些部分略显偏技术。 + +它也常用于不一定需要数据库、用户管理,或任何 Django 预构建功能的应用;当然,许多这类功能可以通过插件添加。 + +这种组件解耦、可按需扩展的“微框架”特性,是我想保留的关键点。 + +鉴于 Flask 的简洁,它似乎非常适合构建 API。接下来要找的,就是 Flask 版的 “Django REST Framework”。 + +/// check | 启发 **FastAPI**: + +- 成为微框架,便于按需组合所需的工具与组件。 +- 提供简单易用的路由系统。 + +/// + +### Requests { #requests } + +**FastAPI** 实际上不是 **Requests** 的替代品。它们的作用范围完全不同。 + +在 FastAPI 应用程序内部使用 Requests 其实非常常见。 + +尽管如此,FastAPI 依然从 Requests 中获得了不少灵感。 + +**Requests** 是一个用于与 API 交互(作为客户端)的库,而 **FastAPI** 是一个用于构建 API(作为服务端)的库。 + +它们处在某种意义上的“对立端”,彼此互补。 + +Requests 设计非常简单直观,易于使用,且有合理的默认值。同时它也非常强大、可定制。 + +这就是为什么,正如其官网所说: + +> Requests 是有史以来下载量最高的 Python 包之一 + +它的用法非常简单。例如,进行一次 `GET` 请求,你会这样写: + +```Python +response = requests.get("http://example.com/some/url") +``` + +对应地,FastAPI 的 API 路径操作可能看起来是这样的: + +```Python hl_lines="1" +@app.get("/some/url") +def read_url(): + return {"message": "Hello World"} +``` + +可以看到 `requests.get(...)` 与 `@app.get(...)` 的相似之处。 + +/// check | 启发 **FastAPI**: + +- 提供简单直观的 API。 +- 直接、自然地使用 HTTP 方法名(操作)。 +- 具备合理默认值,同时支持强大定制能力。 + +/// + +### Swagger / OpenAPI { #swagger-openapi } + +我想从 Django REST Framework 得到的主要特性之一是自动 API 文档。 + +随后我发现有一个用于用 JSON(或 YAML,JSON 的扩展)来描述 API 的标准,称为 Swagger。 + +并且已经有了用于 Swagger API 的 Web 用户界面。因此,只要能为 API 生成 Swagger 文档,就能自动使用这个 Web 界面。 + +后来,Swagger 交由 Linux 基金会管理,并更名为 OpenAPI。 + +因此,在谈到 2.0 版本时人们常说 “Swagger”,而 3+ 版本则称为 “OpenAPI”。 + +/// check | 启发 **FastAPI**: + +采用并使用开放的 API 规范标准,而非自定义模式。 + +并集成基于标准的用户界面工具: + +- Swagger UI +- ReDoc + +选择这两者是因为它们相当流行且稳定;但稍作搜索,你就能找到数十种 OpenAPI 的替代用户界面(都可以与 **FastAPI** 搭配使用)。 + +/// + +### Flask REST 框架 { #flask-rest-frameworks } + +有若干基于 Flask 的 REST 框架,但在投入时间精力深入调研后,我发现许多已停止维护或被弃用,并存在多处未解决问题,不太适合采用。 + +### Marshmallow { #marshmallow } + +API 系统所需的主要特性之一是数据“序列化”,即将代码(Python)中的数据转换为可通过网络发送的形式。例如,将包含数据库数据的对象转换为 JSON 对象、将 `datetime` 对象转换为字符串等。 + +API 的另一个重要特性是数据校验,确保数据在给定约束下是有效的。例如,某个字段必须是 `int` 而不是任意字符串。这对传入数据尤其有用。 + +没有数据校验系统的话,你就得在代码里手写所有检查。 + +这些正是 Marshmallow 要提供的功能。它是个很棒的库,我之前大量使用过。 + +但它诞生于 Python 类型提示出现之前。因此,定义每个模式都需要使用 Marshmallow 提供的特定工具和类。 + +/// check | 启发 **FastAPI**: + +使用代码定义“模式”,自动提供数据类型与校验。 + +/// + +### Webargs { #webargs } + +API 的另一个重要需求是从传入请求中解析数据。 + +Webargs 是一个在多个框架(包括 Flask)之上提供该功能的工具。 + +它在底层使用 Marshmallow 进行数据校验,并且由相同的开发者创建。 + +在拥有 **FastAPI** 之前,我也大量使用过它,这是个很棒的工具。 + +/// info | 信息 + +Webargs 由与 Marshmallow 相同的开发者创建。 + +/// + +/// check | 启发 **FastAPI**: + +对传入请求数据进行自动校验。 + +/// + +### APISpec { #apispec } + +Marshmallow 与 Webargs 通过插件提供了校验、解析与序列化。 + +但文档仍然缺失,于是出现了 APISpec。 + +它为许多框架提供插件(Starlette 也有插件)。 + +它的工作方式是:你在处理路由的每个函数的文档字符串里,用 YAML 格式编写模式定义。 + +然后它会生成 OpenAPI 模式。 + +这正是它在 Flask、Starlette、Responder 等框架里的工作方式。 + +但这样我们又回到了在 Python 字符串中维护一套“微语法”(一大段 YAML)的问题上。 + +编辑器很难为此提供帮助;而且如果我们修改了参数或 Marshmallow 模式,却忘了同步更新那个 YAML 文档字符串,生成的模式就会过时。 + +/// info | 信息 + +APISpec 由与 Marshmallow 相同的开发者创建。 + +/// + +/// check | 启发 **FastAPI**: + +支持开放的 API 标准 OpenAPI。 + +/// + +### Flask-apispec { #flask-apispec } + +这是一个 Flask 插件,将 Webargs、Marshmallow 与 APISpec 结合在一起。 + +它利用 Webargs 与 Marshmallow 的信息,通过 APISpec 自动生成 OpenAPI 模式。 + +这是个很棒却被低估的工具;它理应比许多 Flask 插件更流行。或许是因为它的文档过于简洁与抽象。 + +这解决了在 Python 文档字符串里书写 YAML(另一套语法)的问题。 + +在构建 **FastAPI** 之前,Flask + Flask-apispec + Marshmallow + Webargs 的组合是我最喜欢的后端技术栈。 + +使用它促成了若干 Flask 全栈脚手架的诞生。以下是我(以及若干外部团队)至今使用的主要技术栈: + +* https://github.com/tiangolo/full-stack +* https://github.com/tiangolo/full-stack-flask-couchbase +* https://github.com/tiangolo/full-stack-flask-couchdb + +这些全栈脚手架也成为了[**FastAPI** 项目脚手架](project-generation.md){.internal-link target=_blank}的基础。 + +/// info | 信息 + +Flask-apispec 由与 Marshmallow 相同的开发者创建。 + +/// + +/// check | 启发 **FastAPI**: + +从定义序列化与校验的同一份代码自动生成 OpenAPI 模式。 + +/// + +### NestJS(以及 Angular) { #nestjs-and-angular } + +这甚至不是 Python。NestJS 是一个 JavaScript(TypeScript)的 NodeJS 框架,受 Angular 启发。 + +它实现了与 Flask-apispec 有些类似的效果。 + +它集成了受 Angular 2 启发的依赖注入系统。与我所知的其他依赖注入系统一样,需要预先注册“可注入项”,因此会增加冗长与重复。 + +由于参数用 TypeScript 类型描述(类似 Python 类型提示),编辑器支持相当好。 + +但由于 TypeScript 的类型在编译为 JavaScript 后不会保留,无法只依赖这些类型同时定义校验、序列化与文档。受此以及一些设计决策影响,为了获得校验、序列化与自动 schema 生成,需要在许多位置添加装饰器,因此代码会相当冗长。 + +它对嵌套模型的支持并不好。如果请求的 JSON 体是包含嵌套 JSON 对象的 JSON 对象,则无法被正确文档化和校验。 + +/// check | 启发 **FastAPI**: + +使用 Python 类型以获得出色的编辑器支持。 + +拥有强大的依赖注入系统,并设法尽量减少代码重复。 + +/// + +### Sanic { #sanic } + +它是最早的一批基于 `asyncio` 的极速 Python 框架之一,且做得与 Flask 很相似。 + +/// note | 技术细节 + +它使用了 `uvloop` 来替代 Python 默认的 `asyncio` 循环。这正是它如此之快的原因。 + +它显然启发了 Uvicorn 和 Starlette;在公开的基准测试中,它们目前比 Sanic 更快。 + +/// + +/// check | 启发 **FastAPI**: + +找到实现疯狂性能的路径。 + +这就是 **FastAPI** 基于 Starlette 的原因,因为它是目前可用的最快框架(由第三方基准测试验证)。 + +/// + +### Falcon { #falcon } + +Falcon 是另一个高性能 Python 框架,它被设计为精简且可作为 Hug 等其他框架的基础。 + +它设计为接收两个参数的函数:一个“request”和一个“response”。然后从 request 中“读取”,向 response 中“写入”。由于这种设计,无法用标准的 Python 类型提示将请求参数和请求体声明为函数形参。 + +因此,数据校验、序列化与文档要么需要手写完成,无法自动化;要么需要在 Falcon 之上实现一个框架,例如 Hug。其他受 Falcon 设计启发、采用“一个 request 对象 + 一个 response 对象作为参数”的框架也有同样的区别。 + +/// check | 启发 **FastAPI**: + +寻找获得卓越性能的方法。 + +与 Hug(Hug 基于 Falcon)一起,启发 **FastAPI** 在函数中声明一个 `response` 参数。尽管在 FastAPI 中它是可选的,主要用于设置 headers、cookies 和可选的状态码。 + +/// + +### Molten { #molten } + +我在构建 **FastAPI** 的早期阶段发现了 Molten。它有不少相似的想法: + +* 基于 Python 类型提示。 +* 从这些类型获得校验与文档。 +* 依赖注入系统。 + +它没有使用像 Pydantic 这样的第三方数据校验、序列化与文档库,而是有自己的实现。因此这些数据类型定义不太容易在其他地方复用。 + +它需要稍微冗长一些的配置。并且由于基于 WSGI(而非 ASGI),它并未设计为充分利用 Uvicorn、Starlette、Sanic 等工具所提供的高性能。 + +其依赖注入系统需要预先注册依赖,且依赖根据声明的类型来解析。因此无法为同一类型声明多于一个“组件”。 + +路由在一个地方集中声明,使用在其他地方声明的函数(而不是使用可以直接放在处理端点函数之上的装饰器)。这更接近 Django 的做法,而不是 Flask(和 Starlette)。它在代码中割裂了相对紧耦合的内容。 + +/// check | 启发 **FastAPI**: + +通过模型属性的“默认值”为数据类型定义额外校验。这提升了编辑器支持,而这在当时的 Pydantic 中尚不可用。 + +这实际上促成了对 Pydantic 的部分更新,以支持这种校验声明风格(这些功能现已在 Pydantic 中可用)。 + +/// + +### Hug { #hug } + +Hug 是最早使用 Python 类型提示来声明 API 参数类型的框架之一。这一绝妙想法也启发了其他工具。 + +它在声明中使用自定义类型而不是标准的 Python 类型,但这依然是巨大的进步。 + +它也是最早生成一个自定义 JSON 模式来声明整个 API 的框架之一。 + +它并不基于 OpenAPI 与 JSON Schema 这类标准。因此与其他工具(如 Swagger UI)的集成并非一帆风顺。但它仍是非常有创新性的想法。 + +它有一个有趣且少见的特性:使用同一框架,可以同时创建 API 与 CLI。 + +由于基于同步 Python Web 框架的上一代标准(WSGI),它无法处理 WebSocket 等,尽管它的性能仍然很高。 + +/// info | 信息 + +Hug 由 Timothy Crosley 创建,他也是 `isort` 的作者,这是一个能自动排序 Python 文件中导入的优秀工具。 + +/// + +/// check | 启发 **FastAPI** 的想法: + +Hug 启发了 APIStar 的部分设计,也是我当时最看好的工具之一,与 APIStar 并列。 + +Hug 促使 **FastAPI** 使用 Python 类型提示来声明参数,并自动生成定义整个 API 的模式。 + +Hug 启发 **FastAPI** 在函数中声明 `response` 参数,用于设置 headers 与 cookies。 + +/// + +### APIStar (<= 0.5) { #apistar-0-5 } + +就在决定动手构建 **FastAPI** 之前,我找到了 **APIStar** 服务器。它几乎具备我想要的一切,设计也很出色。 + +在我见过的框架中,它是最早使用 Python 类型提示来声明参数和请求的实现之一(早于 NestJS 与 Molten)。我与 Hug 几乎同时发现了它。但 APIStar 使用了 OpenAPI 标准。 + +它基于相同的类型提示,在多处自动进行数据校验、序列化并生成 OpenAPI 模式。 + +请求体模式定义并未使用与 Pydantic 相同的 Python 类型提示,它更接近 Marshmallow,因此编辑器支持不如 Pydantic 好,但即便如此,APIStar 仍是当时可用的最佳选择。 + +它在当时拥有最好的性能基准(仅被 Starlette 超越)。 + +起初它没有自动 API 文档 Web 界面,但我知道我可以把 Swagger UI 加进去。 + +它有一个依赖注入系统。与上文提到的其他工具一样,需要预先注册组件。但这依然是很棒的特性。 + +我从未在完整项目中使用过它,因为它没有安全集成,因此我无法用它替代基于 Flask-apispec 的全栈脚手架所具备的全部功能。我曾把“提交一个增加该功能的 PR”放在了待办里。 + +但随后,项目的重心发生了变化。 + +它不再是一个 API Web 框架,因为作者需要专注于 Starlette。 + +现在 APIStar 是一组用于校验 OpenAPI 规范的工具,而不是 Web 框架。 + +/// info | 信息 + +APIStar 由 Tom Christie 创建。他还创建了: + +* Django REST Framework +* Starlette(**FastAPI** 基于其之上) +* Uvicorn(被 Starlette 与 **FastAPI** 使用) + +/// + +/// check | 启发 **FastAPI**: + +诞生。 + +用同一套 Python 类型同时声明多件事(数据校验、序列化与文档),并且还能提供出色的编辑器支持——我认为这是个极其巧妙的想法。 + +在长时间寻找与测试多种替代之后,APIStar 是当时最好的选择。 + +随后 APIStar 不再作为服务器存在,而 Starlette 出现,成为实现该体系的更佳基础。这成为构建 **FastAPI** 的最终灵感来源。 + +我把 **FastAPI** 视为 APIStar 的“精神续作”,并在此基础上,结合前述工具的经验,改进并增强了功能、类型系统及其他各方面。 + +/// + +## **FastAPI** 所使用的组件 { #used-by-fastapi } + +### Pydantic { #pydantic } + +Pydantic 是一个基于 Python 类型提示来定义数据校验、序列化与文档(使用 JSON Schema)的库。 + +这使得它极其直观。 + +它可与 Marshmallow 类比。尽管在基准测试中它比 Marshmallow 更快。并且由于同样基于 Python 类型提示,编辑器支持优秀。 + +/// check | **FastAPI** 用它来: + +处理所有数据校验、数据序列化与自动模型文档(基于 JSON Schema)。 + +随后 **FastAPI** 会把这些 JSON Schema 数据纳入 OpenAPI(以及完成其他所有工作)。 + +/// + +### Starlette { #starlette } + +Starlette 是一个轻量级的 ASGI 框架/工具集,非常适合构建高性能的 asyncio 服务。 + +它非常简单直观。被设计为易于扩展,且具有模块化组件。 + +它具备: + +* 性能极其出色。 +* 支持 WebSocket。 +* 进程内后台任务。 +* 启动与停止事件。 +* 基于 HTTPX 的测试客户端。 +* CORS、GZip、静态文件、流式响应。 +* 会话与 Cookie 支持。 +* 100% 测试覆盖率。 +* 100% 类型注解的代码库。 +* 极少的强依赖。 + +Starlette 目前是测试中最快的 Python 框架。仅次于 Uvicorn,它不是框架,而是服务器。 + +Starlette 提供了 Web 微框架的全部基础能力。 + +但它不提供自动的数据校验、序列化或文档。 + +这正是 **FastAPI** 在其之上增加的主要内容之一,全部基于 Python 类型提示(通过 Pydantic)。此外还有依赖注入系统、安全工具、OpenAPI 模式生成等。 + +/// note | 技术细节 + +ASGI 是由 Django 核心团队成员推动的新“标准”。它尚不是正式的“Python 标准”(PEP),尽管正朝此方向推进。 + +尽管如此,已有多种工具将其作为“标准”使用。这极大提升了互操作性:你可以把 Uvicorn 换成其他 ASGI 服务器(如 Daphne 或 Hypercorn),或添加 ASGI 兼容的工具,如 `python-socketio`。 + +/// + +/// check | **FastAPI** 用它来: + +处理所有核心 Web 部分,并在其之上扩展功能。 + +`FastAPI` 类本身直接继承自 `Starlette`。 + +因此,凡是你能用 Starlette 完成的事,也能直接用 **FastAPI** 完成;可以把它看作“加速版”的 Starlette。 + +/// + +### Uvicorn { #uvicorn } + +Uvicorn 是一个基于 uvloop 与 httptools 构建的极速 ASGI 服务器。 + +它不是 Web 框架,而是服务器。例如它不提供按路径路由的工具——这是 Starlette(或 **FastAPI**)这类框架在其之上提供的功能。 + +它是 Starlette 与 **FastAPI** 推荐的服务器。 + +/// check | **FastAPI** 推荐将其作为: + +运行 **FastAPI** 应用的主要 Web 服务器。 + +你也可以使用 `--workers` 命令行选项以获得异步的多进程服务器。 + +更多细节见[部署](deployment/index.md){.internal-link target=_blank}一节。 + +/// + +## 基准与速度 { #benchmarks-and-speed } + +要理解、比较并查看 Uvicorn、Starlette 与 FastAPI 之间的差异,请查看[基准](benchmarks.md){.internal-link target=_blank}一节。 diff --git a/docs/zh/docs/async.md b/docs/zh/docs/async.md index c94c90787..36d875f51 100644 --- a/docs/zh/docs/async.md +++ b/docs/zh/docs/async.md @@ -298,7 +298,7 @@ CPU 密集型操作的常见示例是需要复杂的数学处理。 这一点,再加上 Python 是**数据科学**、机器学习(尤其是深度学习)的主要语言这一简单事实,使得 **FastAPI** 与数据科学/机器学习 Web API 和应用程序(以及其他许多应用程序)非常匹配。 -了解如何在生产环境中实现这种并行性,可查看此文 [Deployment](deployment/index.md){.internal-link target=_blank}。 +了解如何在生产环境中实现这种并行性,可查看此文 [部署](deployment/index.md){.internal-link target=_blank}。 ## `async` 和 `await` { #async-and-await } @@ -369,7 +369,7 @@ Starlette (和 **FastAPI**) 是基于 AnyIO 编写自己的异步程序,使其拥有较高的兼容性并获得一些好处(例如, 结构化并发)。 -我(指原作者 —— 译者注)基于 AnyIO 新建了一个库,作为一个轻量级的封装层,用来优化类型注解,同时提供了更好的**自动补全**、**内联错误提示**等功能。这个库还附带了一个友好的入门指南和教程,能帮助你**理解**并编写**自己的异步代码**:Asyncer。如果你有**结合使用异步代码和常规**(阻塞/同步)代码的需求,这个库会特别有用。 +我基于 AnyIO 新建了一个库,作为一个轻量级的封装层,用来优化类型注解,同时提供了更好的**自动补全**、**内联错误提示**等功能。这个库还附带了一个友好的入门指南和教程,能帮助你**理解**并编写**自己的异步代码**:Asyncer。如果你有**结合使用异步代码和常规**(阻塞/同步)代码的需求,这个库会特别有用。 ### 其他形式的异步代码 { #other-forms-of-asynchronous-code } diff --git a/docs/zh/docs/benchmarks.md b/docs/zh/docs/benchmarks.md index 1a4b4a3de..a6e706dfa 100644 --- a/docs/zh/docs/benchmarks.md +++ b/docs/zh/docs/benchmarks.md @@ -23,7 +23,7 @@ * 你不会直接在 Uvicorn 中编写应用程序。这意味着你的代码至少必须包含 Starlette(或 **FastAPI**)提供的代码。如果你这样做了(即直接在 Uvicorn 中编写应用程序),最终的应用程序会和使用了框架并且最小化了应用代码和 bug 的情况具有相同的性能损耗。 * 如果你要对比 Uvicorn,请将其与 Daphne,Hypercorn,uWSGI 等应用服务器进行比较。 * **Starlette**: - * 在 Uvicorn 后使用 Starlette,性能会略有下降。实际上,Starlette 使用 Uvicorn 运行。因此,由于必须执行更多的代码,它只会比 Uvicorn 更慢。 + * 性能仅次于 Uvicorn。实际上,Starlette 使用 Uvicorn 运行。因此,由于必须执行更多的代码,它只会比 Uvicorn 更慢。 * 但它为你提供了构建简单的网络程序的工具,并具有基于路径的路由等功能。 * 如果想对比与 Starlette 对标的开发框架,请将其与 Sanic,Flask,Django 等网络框架(或微框架)进行比较。 * **FastAPI**: diff --git a/docs/zh/docs/deployment/concepts.md b/docs/zh/docs/deployment/concepts.md index 66d32629c..76e967d7d 100644 --- a/docs/zh/docs/deployment/concepts.md +++ b/docs/zh/docs/deployment/concepts.md @@ -190,7 +190,7 @@ ### 工作进程和端口 { #worker-processes-and-ports } -还记得文档 [About HTTPS](https.md){.internal-link target=_blank} 中只有一个进程可以侦听服务器中的端口和 IP 地址的一种组合吗? +还记得文档 [关于 HTTPS](https.md){.internal-link target=_blank} 中只有一个进程可以侦听服务器中的端口和 IP 地址的一种组合吗? 现在仍然是对的。 diff --git a/docs/zh/docs/deployment/docker.md b/docs/zh/docs/deployment/docker.md index 3d0c19903..4e7410587 100644 --- a/docs/zh/docs/deployment/docker.md +++ b/docs/zh/docs/deployment/docker.md @@ -14,7 +14,7 @@ Dockerfile 预览 👀 ```Dockerfile -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -166,7 +166,7 @@ def read_item(item_id: int, q: str | None = None): ```{ .dockerfile .annotate } # (1)! -FROM python:3.9 +FROM python:3.14 # (2)! WORKDIR /code @@ -390,7 +390,7 @@ $ docker run -d --name mycontainer -p 80:80 myimage 然后你只需要在 `Dockerfile` 中修改相应路径来复制该文件: ```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -454,7 +454,7 @@ Traefik 与 Docker、Kubernetes 等都有集成,因此为容器设置和配置 ## 复制 - 进程数 { #replication-number-of-processes } -如果你有一个由 **Kubernetes**、Docker Swarm Mode、Nomad 或其他类似的复杂系统管理的、在多台机器上运行的分布式容器集群,那么你很可能会希望在**集群层面**来**处理复制**,而不是在每个容器中使用**进程管理**(比如让 Uvicorn 运行多个 workers)。 +如果你有一个由 **Kubernetes**、Docker Swarm Mode、Nomad 或其他类似的复杂系统管理的、在多台机器上运行的分布式容器集群,那么你很可能会希望在**集群层面**来**处理复制**,而不是在每个容器中使用**进程管理**(比如让 Uvicorn 运行多个 workers)。 像 Kubernetes 这样的分布式容器管理系统通常都有某种内置方式来处理**容器复制**,同时对传入请求进行**负载均衡**。这一切都在**集群层面**完成。 @@ -499,7 +499,7 @@ Traefik 与 Docker、Kubernetes 等都有集成,因此为容器设置和配置 在这些情况下,你可以使用 `--workers` 命令行选项来设置要运行的 worker 数量: ```{ .dockerfile .annotate } -FROM python:3.9 +FROM python:3.14 WORKDIR /code diff --git a/docs/zh/docs/deployment/fastapicloud.md b/docs/zh/docs/deployment/fastapicloud.md new file mode 100644 index 000000000..0239a1512 --- /dev/null +++ b/docs/zh/docs/deployment/fastapicloud.md @@ -0,0 +1,65 @@ +# FastAPI Cloud { #fastapi-cloud } + +你可以用**一条命令**将你的 FastAPI 应用部署到 FastAPI Cloud,如果还没有,去加入候补名单吧。🚀 + +## 登录 { #login } + +请确保你已有 **FastAPI Cloud** 账号(我们已从候补名单向你发出邀请 😉)。 + +然后登录: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +## 部署 { #deploy } + +现在用**一条命令**部署你的应用: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +就这样!现在你可以通过该 URL 访问你的应用。✨ + +## 关于 FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** 由 **FastAPI** 背后的作者与团队打造。 + +它让你以最小的投入完成 API 的**构建**、**部署**与**访问**。 + +它把使用 FastAPI 构建应用时的**开发者体验**,同样带到将应用**部署**到云上的过程。🎉 + +它还会替你处理部署应用时大多数需要的事项,例如: + +* HTTPS +* 副本、基于请求的自动伸缩 +* 等等 + +FastAPI Cloud 是 *FastAPI and friends* 开源项目的主要赞助方与资金提供者。✨ + +## 部署到其他云服务商 { #deploy-to-other-cloud-providers } + +FastAPI 是开源的,并基于标准。你可以将 FastAPI 应用部署到你选择的任意云服务商。 + +按照你所选云服务商的指南部署 FastAPI 应用即可。🤓 + +## 部署到你自己的服务器 { #deploy-your-own-server } + +在后面的**部署**指南中,我也会讲解所有细节,帮助你理解幕后发生了什么、需要做什么,以及如何自行部署 FastAPI 应用,包括部署到你自己的服务器。🤓 diff --git a/docs/zh/docs/deployment/https.md b/docs/zh/docs/deployment/https.md index 4f60b7bca..591707f6d 100644 --- a/docs/zh/docs/deployment/https.md +++ b/docs/zh/docs/deployment/https.md @@ -65,13 +65,13 @@ 第一步我们要先**获取**一些**域名(Domain Name)**。 然后可以在 DNS 服务器(可能是你的同一家云服务商提供的)中配置它。 -你可能拥有一个云服务器(虚拟机)或类似的东西,并且它会有一个固定 **公共IP地址**。 +你可能拥有一个云服务器(虚拟机)或类似的东西,并且它会有一个固定 **公共IP地址**。 在 DNS 服务器中,你可以配置一条记录(“A 记录”)以将 **你的域名** 指向你服务器的公共 **IP 地址**。 这个操作一般只需要在最开始执行一次。 -/// tip +/// tip | 提示 域名这部分发生在 HTTPS 之前,由于这一切都依赖于域名和 IP 地址,所以先在这里提一下。 @@ -121,7 +121,7 @@ TLS 终止代理可以访问一个或多个 **TLS 证书**(HTTPS 证书)。 这就是 **HTTPS**,它只是 **安全 TLS 连接** 内的普通 **HTTP**,而不是纯粹的(未加密的)TCP 连接。 -/// tip +/// tip | 提示 请注意,通信加密发生在 **TCP 层**,而不是 HTTP 层。 @@ -217,7 +217,7 @@ TLS 终止代理将使用协商好的加密算法**解密请求**,并将**( 这在需要正确处理重定向等场景时很有用。 -/// tip +/// tip | 提示 你可以在文档中了解更多:[在代理之后 - 启用代理转发请求头](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} diff --git a/docs/zh/docs/deployment/manually.md b/docs/zh/docs/deployment/manually.md index 6f2ad27b2..f519c1e87 100644 --- a/docs/zh/docs/deployment/manually.md +++ b/docs/zh/docs/deployment/manually.md @@ -90,7 +90,7 @@ $ pip install "uvicorn[standard]" 类似的流程也适用于任何其他 ASGI 服务器程序。 -/// tip +/// tip | 提示 通过添加 `standard` 选项,Uvicorn 将安装并使用一些推荐的额外依赖项。 @@ -114,7 +114,7 @@ $ uvicorn main:app --host 0.0.0.0 --port 80
-/// note +/// note | 注意 命令 `uvicorn main:app` 的含义如下: @@ -131,7 +131,7 @@ from main import app 每种 ASGI 服务器程序通常都会有类似的命令,您可以在它们的官方文档中找到更多信息。 -/// warning +/// warning | 警告 Uvicorn 和其他服务器支持 `--reload` 选项,该选项在开发过程中非常有用。 diff --git a/docs/zh/docs/features.md b/docs/zh/docs/features.md index 7d7aa19c0..1414f7c6f 100644 --- a/docs/zh/docs/features.md +++ b/docs/zh/docs/features.md @@ -6,7 +6,7 @@ ### 基于开放标准 { #based-on-open-standards } -* 用于创建 API 的 OpenAPI,包含对路径 操作、参数、请求体、安全等的声明。 +* 用于创建 API 的 OpenAPI,包含对路径 操作、参数、请求体、安全等的声明。 * 使用 JSON Schema 自动生成数据模型文档(因为 OpenAPI 本身就是基于 JSON Schema 的)。 * 经过了缜密的研究后围绕这些标准而设计。并非狗尾续貂。 * 这也允许了在很多语言中自动**生成客户端代码**。 @@ -137,7 +137,7 @@ OpenAPI 中定义的安全模式,包括: ### 依赖注入 { #dependency-injection } -FastAPI 有一个使用非常简单,但是非常强大的依赖注入系统。 +FastAPI 有一个使用非常简单,但是非常强大的依赖注入系统。 * 甚至依赖也可以有依赖,创建一个层级或者**“图”依赖**。 * 所有**自动化处理**都由框架完成。 @@ -154,8 +154,8 @@ FastAPI 有一个使用非常简单,但是非常强大的IDE/linter/brain** 适配: +* 和你 **IDE/linter/brain** 适配: * 因为 pydantic 数据结构仅仅是你定义的类的实例;自动补全,linting,mypy 以及你的直觉应该可以和你验证的数据一起正常工作。 * 验证**复杂结构**: * 使用分层的 Pydantic 模型,Python `typing` 的 `List` 和 `Dict` 等等。 diff --git a/docs/zh/docs/help-fastapi.md b/docs/zh/docs/help-fastapi.md index 5a5157b5d..1be60d9d5 100644 --- a/docs/zh/docs/help-fastapi.md +++ b/docs/zh/docs/help-fastapi.md @@ -59,7 +59,7 @@ ## 发推谈谈 **FastAPI** { #tweet-about-fastapi } -Tweet about **FastAPI**,告诉我和大家你为什么喜欢它。🎉 +发推谈谈 **FastAPI**,告诉我和大家你为什么喜欢它。🎉 我很高兴听到 **FastAPI** 的使用情况、你喜欢它的哪些点、你在哪个项目/公司使用它,等等。 diff --git a/docs/zh/docs/how-to/authentication-error-status-code.md b/docs/zh/docs/how-to/authentication-error-status-code.md new file mode 100644 index 000000000..466b90e15 --- /dev/null +++ b/docs/zh/docs/how-to/authentication-error-status-code.md @@ -0,0 +1,17 @@ +# 使用旧的 403 认证错误状态码 { #use-old-403-authentication-error-status-codes } + +在 FastAPI `0.122.0` 版本之前,当内置的安全工具在认证失败后向客户端返回错误时,会使用 HTTP 状态码 `403 Forbidden`。 + +从 FastAPI `0.122.0` 版本开始,它们改用更合适的 HTTP 状态码 `401 Unauthorized`,并在响应中返回合理的 `WWW-Authenticate` 头,遵循 HTTP 规范,RFC 7235RFC 9110。 + +但如果由于某些原因你的客户端依赖旧行为,你可以在你的安全类中重写方法 `make_not_authenticated_error` 来回退到旧行为。 + +例如,你可以创建一个 `HTTPBearer` 的子类,使其返回 `403 Forbidden` 错误,而不是默认的 `401 Unauthorized` 错误: + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} + +/// tip | 提示 + +注意该函数返回的是异常实例,而不是直接抛出它。抛出操作由其余的内部代码完成。 + +/// diff --git a/docs/zh/docs/how-to/conditional-openapi.md b/docs/zh/docs/how-to/conditional-openapi.md new file mode 100644 index 000000000..d26b5f3bd --- /dev/null +++ b/docs/zh/docs/how-to/conditional-openapi.md @@ -0,0 +1,56 @@ +# 按条件配置 OpenAPI { #conditional-openapi } + +如果需要,你可以使用设置和环境变量,按环境有条件地配置 OpenAPI,甚至完全禁用它。 + +## 关于安全、API 和文档 { #about-security-apis-and-docs } + +在生产环境隐藏文档界面并不应该成为保护 API 的方式。 + +这并不会给你的 API 增加任何额外的安全性,*路径操作* 仍然会在原来的位置可用。 + +如果你的代码里有安全漏洞,它仍然存在。 + +隐藏文档只会让理解如何与 API 交互变得更困难,也可能让你在生产环境中调试更困难。这大体上可以被视为一种 通过隐藏实现安全 的做法。 + +如果你想保护你的 API,有很多更好的措施,例如: + +- 确保为请求体和响应定义完善的 Pydantic 模型。 +- 使用依赖配置所需的权限和角色。 +- 绝不要存储明文密码,只存储密码哈希。 +- 实现并使用成熟的密码学工具,比如 pwdlib 和 JWT 令牌等。 +- 在需要的地方使用 OAuth2 作用域添加更细粒度的权限控制。 +- ...等。 + +尽管如此,你可能确实有非常特定的用例,需要在某些环境(例如生产环境)禁用 API 文档,或根据环境变量的配置来决定。 + +## 基于设置和环境变量的条件式 OpenAPI { #conditional-openapi-from-settings-and-env-vars } + +你可以很容易地使用相同的 Pydantic 设置来配置生成的 OpenAPI 和文档 UI。 + +例如: + +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} + +这里我们声明了设置项 `openapi_url`,其默认值同样是 `"/openapi.json"`。 + +然后在创建 `FastAPI` 应用时使用它。 + +接着,你可以通过把环境变量 `OPENAPI_URL` 设为空字符串来禁用 OpenAPI(包括文档 UI),例如: + +
+ +```console +$ OPENAPI_URL= uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +然后如果你访问 `/openapi.json`、`/docs` 或 `/redoc`,就会得到一个 `404 Not Found` 错误,例如: + +```JSON +{ + "detail": "Not Found" +} +``` diff --git a/docs/zh/docs/how-to/configure-swagger-ui.md b/docs/zh/docs/how-to/configure-swagger-ui.md index 104baff4b..bf2624657 100644 --- a/docs/zh/docs/how-to/configure-swagger-ui.md +++ b/docs/zh/docs/how-to/configure-swagger-ui.md @@ -18,7 +18,7 @@ FastAPI会将这些配置转换为 **JSON**,使其与 JavaScript 兼容,因 但是你可以通过设置 `syntaxHighlight` 为 `False` 来禁用 Swagger UI 中的语法高亮: -{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} ...在此之后,Swagger UI 将不会高亮代码: @@ -28,7 +28,7 @@ FastAPI会将这些配置转换为 **JSON**,使其与 JavaScript 兼容,因 同样地,你也可以通过设置键 `"syntaxHighlight.theme"` 来设置语法高亮主题(注意中间有一个点): -{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} 这个配置会改变语法高亮主题: @@ -46,7 +46,7 @@ FastAPI 包含了一些默认配置参数,适用于大多数用例。 比如,如果要禁用 `deepLinking`,你可以像这样传递设置到 `swagger_ui_parameters` 中: -{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} ## 其他 Swagger UI 参数 { #other-swagger-ui-parameters } diff --git a/docs/zh/docs/how-to/custom-docs-ui-assets.md b/docs/zh/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 000000000..9e6e5a66b --- /dev/null +++ b/docs/zh/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,185 @@ +# 自托管自定义文档 UI 静态资源 { #custom-docs-ui-static-assets-self-hosting } + +API 文档使用 Swagger UI 和 ReDoc,它们各自需要一些 JavaScript 和 CSS 文件。 + +默认情况下,这些文件从一个 CDN 提供。 + +不过你可以自定义:可以指定特定的 CDN,或自行提供这些文件。 + +## 为 JavaScript 和 CSS 自定义 CDN { #custom-cdn-for-javascript-and-css } + +假设你想使用不同的 CDN,例如使用 `https://unpkg.com/`。 + +如果你所在的国家/地区屏蔽了某些 URL,这会很有用。 + +### 关闭自动文档 { #disable-the-automatic-docs } + +第一步是关闭自动文档,因为默认它们会使用默认的 CDN。 + +要关闭它们,在创建 `FastAPI` 应用时将其 URL 设为 `None`: + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} + +### 包含自定义文档 { #include-the-custom-docs } + +现在你可以为自定义文档创建*路径操作*。 + +你可以复用 FastAPI 的内部函数来创建文档的 HTML 页面,并传入所需参数: + +- `openapi_url`:文档 HTML 页面获取你的 API 的 OpenAPI 模式的 URL。这里可以使用 `app.openapi_url` 属性。 +- `title`:你的 API 标题。 +- `oauth2_redirect_url`:这里可以使用 `app.swagger_ui_oauth2_redirect_url` 来使用默认值。 +- `swagger_js_url`:你的 Swagger UI 文档 HTML 获取**JavaScript** 文件的 URL。这里是自定义的 CDN URL。 +- `swagger_css_url`:你的 Swagger UI 文档 HTML 获取**CSS** 文件的 URL。这里是自定义的 CDN URL。 + +ReDoc 也类似... + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} + +/// tip | 提示 + +`swagger_ui_redirect` 的*路径操作*是在你使用 OAuth2 时的一个辅助。 + +如果你把 API 与某个 OAuth2 提供方集成,你就可以完成认证并带着获取到的凭据回到 API 文档里。然后使用真实的 OAuth2 认证与之交互。 + +Swagger UI 会在幕后为你处理这些,但它需要这个“重定向”辅助路径。 + +/// + +### 创建一个路径操作进行测试 { #create-a-path-operation-to-test-it } + +现在,为了测试一切是否正常,创建一个*路径操作*: + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} + +### 测试 { #test-it } + +现在,你应该可以访问 http://127.0.0.1:8000/docs,并刷新页面,页面会从新的 CDN 加载这些资源。 + +## 为文档自托管 JavaScript 和 CSS { #self-hosting-javascript-and-css-for-docs } + +如果你需要在离线、无法访问互联网或仅在局域网内时,应用仍能工作,那么自托管 JavaScript 和 CSS 会很有用。 + +这里你将看到如何在同一个 FastAPI 应用中自行提供这些文件,并配置文档使用它们。 + +### 项目文件结构 { #project-file-structure } + +假设你的项目文件结构如下: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +现在创建一个目录来存放这些静态文件。 + +你的新文件结构可能如下: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### 下载文件 { #download-the-files } + +下载文档需要的静态文件,并将它们放到 `static/` 目录中。 + +你通常可以右键点击每个链接,选择类似“将链接另存为...”的选项。 + +Swagger UI 使用以下文件: + +- `swagger-ui-bundle.js` +- `swagger-ui.css` + +而 ReDoc 使用以下文件: + +- `redoc.standalone.js` + +之后,你的文件结构可能如下: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### 提供静态文件 { #serve-the-static-files } + +- 导入 `StaticFiles`。 +- 在特定路径上“挂载”一个 `StaticFiles()` 实例。 + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} + +### 测试静态文件 { #test-the-static-files } + +启动你的应用,并访问 http://127.0.0.1:8000/static/redoc.standalone.js。 + +你应该会看到一个非常长的 **ReDoc** 的 JavaScript 文件。 + +它可能以如下内容开头: + +```JavaScript +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): +... +``` + +这就确认了你的应用能够提供静态文件,并且你把文档所需的静态文件放在了正确的位置。 + +现在我们可以配置应用,让文档使用这些静态文件。 + +### 为静态文件关闭自动文档 { #disable-the-automatic-docs-for-static-files } + +和使用自定义 CDN 一样,第一步是关闭自动文档,因为默认情况下它们会使用 CDN。 + +要关闭它们,在创建 `FastAPI` 应用时将其 URL 设为 `None`: + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} + +### 为静态文件包含自定义文档 { #include-the-custom-docs-for-static-files } + +同样地,现在你可以为自定义文档创建*路径操作*。 + +你可以再次复用 FastAPI 的内部函数来创建文档的 HTML 页面,并传入所需参数: + +- `openapi_url`:文档 HTML 页面获取你的 API 的 OpenAPI 模式的 URL。这里可以使用 `app.openapi_url` 属性。 +- `title`:你的 API 标题。 +- `oauth2_redirect_url`:这里可以使用 `app.swagger_ui_oauth2_redirect_url` 来使用默认值。 +- `swagger_js_url`:你的 Swagger UI 文档 HTML 获取**JavaScript** 文件的 URL。**这是现在由你的应用自己提供的那个**。 +- `swagger_css_url`:你的 Swagger UI 文档 HTML 获取**CSS** 文件的 URL。**这是现在由你的应用自己提供的那个**。 + +ReDoc 也类似... + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} + +/// tip | 提示 + +`swagger_ui_redirect` 的*路径操作*是在你使用 OAuth2 时的一个辅助。 + +如果你把 API 与某个 OAuth2 提供方集成,你就可以完成认证并带着获取到的凭据回到 API 文档里。然后使用真实的 OAuth2 认证与之交互。 + +Swagger UI 会在幕后为你处理这些,但它需要这个“重定向”辅助路径。 + +/// + +### 创建一个路径操作测试静态文件 { #create-a-path-operation-to-test-static-files } + +现在,为了测试一切是否正常,创建一个*路径操作*: + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} + +### 测试静态文件 UI { #test-static-files-ui } + +现在,你可以断开 WiFi,访问 http://127.0.0.1:8000/docs,并刷新页面。 + +即使没有互联网,你也能看到 API 的文档并与之交互。 diff --git a/docs/zh/docs/how-to/custom-request-and-route.md b/docs/zh/docs/how-to/custom-request-and-route.md new file mode 100644 index 000000000..8b365987c --- /dev/null +++ b/docs/zh/docs/how-to/custom-request-and-route.md @@ -0,0 +1,109 @@ +# 自定义 Request 和 APIRoute 类 { #custom-request-and-apiroute-class } + +在某些情况下,你可能想要重写 `Request` 和 `APIRoute` 类使用的逻辑。 + +尤其是,当你本来会把这些逻辑放到中间件里时,这是一个不错的替代方案。 + +例如,如果你想在应用处理之前读取或操作请求体。 + +/// danger | 危险 + +这是一个“高级”特性。 + +如果你刚开始使用 **FastAPI**,可以先跳过本节。 + +/// + +## 使用场景 { #use-cases } + +一些使用场景包括: + +* 将非 JSON 的请求体转换为 JSON(例如 `msgpack`)。 +* 解压缩使用 gzip 压缩的请求体。 +* 自动记录所有请求体日志。 + +## 处理自定义请求体编码 { #handling-custom-request-body-encodings } + +来看如何用自定义的 `Request` 子类来解压 gzip 请求。 + +以及一个 `APIRoute` 子类来使用该自定义请求类。 + +### 创建自定义 `GzipRequest` 类 { #create-a-custom-gziprequest-class } + +/// tip | 提示 + +这是一个演示工作原理的示例。如果你需要 Gzip 支持,可以直接使用提供的 [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}。 + +/// + +首先,我们创建一个 `GzipRequest` 类,它会重写 `Request.body()` 方法:当请求头中存在相应标记时对请求体进行解压。 + +如果请求头中没有 `gzip`,则不会尝试解压。 + +这样,同一个路由类即可同时处理 gzip 压缩和未压缩的请求。 + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} + +### 创建自定义 `GzipRoute` 类 { #create-a-custom-gziproute-class } + +接着,我们创建 `fastapi.routing.APIRoute` 的自定义子类来使用 `GzipRequest`。 + +这次,我们会重写 `APIRoute.get_route_handler()` 方法。 + +该方法返回一个函数,这个函数负责接收请求并返回响应。 + +这里我们用它把原始请求包装为 `GzipRequest`。 + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} + +/// note | 技术细节 + +`Request` 拥有 `request.scope` 属性,它就是一个 Python `dict`,包含与请求相关的元数据。 + +`Request` 还包含 `request.receive`,它是一个用于“接收”请求体的函数。 + +`scope` 字典和 `receive` 函数都是 ASGI 规范的一部分。 + +创建一个新的 `Request` 实例需要这两样:`scope` 和 `receive`。 + +想了解更多关于 `Request` 的信息,请查看 Starlette 的 Request 文档。 + +/// + +由 `GzipRequest.get_route_handler` 返回的函数唯一不同之处是把 `Request` 转换为 `GzipRequest`。 + +这样,在传给我们的路径操作之前,`GzipRequest` 会(在需要时)负责解压数据。 + +之后,其余处理逻辑完全相同。 + +但由于我们修改了 `GzipRequest.body`,在 **FastAPI** 需要读取时,请求体会被自动解压。 + +## 在异常处理器中访问请求体 { #accessing-the-request-body-in-an-exception-handler } + +/// tip | 提示 + +要解决类似问题,使用 `RequestValidationError` 的自定义处理器中的 `body` 往往更简单([处理错误](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank})。 + +但本示例同样有效,并展示了如何与内部组件交互。 + +/// + +我们也可以用相同的方法在异常处理器中访问请求体。 + +所需仅是在 `try`/`except` 块中处理请求: + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} + +如果发生异常,`Request` 实例仍在作用域内,因此我们可以在处理错误时读取并使用请求体: + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} + +## 在路由器中自定义 `APIRoute` 类 { #custom-apiroute-class-in-a-router } + +你也可以设置 `APIRouter` 的 `route_class` 参数: + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} + +在此示例中,`router` 下的路径操作将使用自定义的 `TimedRoute` 类,响应中会多一个 `X-Response-Time` 头,包含生成响应所用的时间: + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} diff --git a/docs/zh/docs/how-to/extending-openapi.md b/docs/zh/docs/how-to/extending-openapi.md new file mode 100644 index 000000000..ad8a1d698 --- /dev/null +++ b/docs/zh/docs/how-to/extending-openapi.md @@ -0,0 +1,80 @@ +# 扩展 OpenAPI { #extending-openapi } + +在某些情况下,你可能需要修改生成的 OpenAPI 架构(schema)。 + +本节将介绍如何实现。 + +## 常规流程 { #the-normal-process } + +常规(默认)流程如下。 + +`FastAPI` 应用(实例)有一个 `.openapi()` 方法,预期返回 OpenAPI 架构。 + +在创建应用对象时,会注册一个用于 `/openapi.json`(或你在 `openapi_url` 中设置的路径)的路径操作。 + +它只会返回一个 JSON 响应,内容是应用 `.openapi()` 方法的结果。 + +默认情况下,`.openapi()` 方法会检查属性 `.openapi_schema` 是否已有内容,若有则直接返回。 + +如果没有,则使用 `fastapi.openapi.utils.get_openapi` 工具函数生成。 + +该 `get_openapi()` 函数接收以下参数: + +- `title`:OpenAPI 标题,显示在文档中。 +- `version`:你的 API 版本,例如 `2.5.0`。 +- `openapi_version`:使用的 OpenAPI 规范版本。默认是最新的 `3.1.0`。 +- `summary`:API 的简短摘要。 +- `description`:API 的描述,可包含 Markdown,并会展示在文档中。 +- `routes`:路由列表,即已注册的每个路径操作。来自 `app.routes`。 + +/// info | 信息 + +参数 `summary` 仅在 OpenAPI 3.1.0 及更高版本中可用,FastAPI 0.99.0 及以上版本支持。 + +/// + +## 覆盖默认值 { #overriding-the-defaults } + +基于以上信息,你可以用同一个工具函数生成 OpenAPI 架构,并按需覆盖其中的各个部分。 + +例如,让我们添加 ReDoc 的 OpenAPI 扩展以包含自定义 Logo。 + +### 常规 **FastAPI** { #normal-fastapi } + +首先,像平常一样编写你的 **FastAPI** 应用: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *} + +### 生成 OpenAPI 架构 { #generate-the-openapi-schema } + +然后,在一个 `custom_openapi()` 函数中使用同一个工具函数生成 OpenAPI 架构: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *} + +### 修改 OpenAPI 架构 { #modify-the-openapi-schema } + +现在你可以添加 ReDoc 扩展,在 OpenAPI 架构的 `info` “对象”中加入自定义 `x-logo`: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *} + +### 缓存 OpenAPI 架构 { #cache-the-openapi-schema } + +你可以把 `.openapi_schema` 属性当作“缓存”,用来存储已生成的架构。 + +这样一来,用户每次打开 API 文档时,应用就不必重新生成架构。 + +它只会生成一次,后续请求都会使用同一份缓存的架构。 + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *} + +### 覆盖方法 { #override-the-method } + +现在你可以用你的新函数替换 `.openapi()` 方法。 + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *} + +### 验证 { #check-it } + +当你访问 http://127.0.0.1:8000/redoc 时,你会看到已使用你的自定义 Logo(本例中为 **FastAPI** 的 Logo): + + diff --git a/docs/zh/docs/how-to/general.md b/docs/zh/docs/how-to/general.md index e75ad6c79..2c9f78179 100644 --- a/docs/zh/docs/how-to/general.md +++ b/docs/zh/docs/how-to/general.md @@ -36,4 +36,4 @@ ## OpenAPI 文档 URL { #openapi-docs-urls } -要更改用于自动生成文档的 URL,请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}。 +要更改自动生成的文档用户界面所使用的 URL,请阅读 [教程 - 元数据和文档 URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}。 diff --git a/docs/zh/docs/how-to/graphql.md b/docs/zh/docs/how-to/graphql.md new file mode 100644 index 000000000..5384f1513 --- /dev/null +++ b/docs/zh/docs/how-to/graphql.md @@ -0,0 +1,60 @@ +# GraphQL { #graphql } + +由于 **FastAPI** 基于 **ASGI** 标准,因此很容易集成任何也兼容 ASGI 的 **GraphQL** 库。 + +你可以在同一个应用中将常规的 FastAPI 路径操作与 GraphQL 结合使用。 + +/// tip | 提示 + +**GraphQL** 解决一些非常特定的用例。 + +与常见的 **Web API** 相比,它有各自的**优点**和**缺点**。 + +请确保评估在你的用例中,这些**好处**是否足以弥补这些**缺点**。 🤓 + +/// + +## GraphQL 库 { #graphql-libraries } + +以下是一些支持 **ASGI** 的 **GraphQL** 库。你可以将它们与 **FastAPI** 一起使用: + +* Strawberry 🍓 + * 提供 面向 FastAPI 的文档 +* Ariadne + * 提供 面向 FastAPI 的文档 +* Tartiflette + * 提供用于 ASGI 集成的 Tartiflette ASGI +* Graphene + * 可配合 starlette-graphene3 使用 + +## 使用 Strawberry 的 GraphQL { #graphql-with-strawberry } + +如果你需要或想要使用 **GraphQL**,**Strawberry** 是**推荐**的库,因为它的设计与 **FastAPI** 最为接近,全部基于**类型注解**。 + +根据你的用例,你可能会更喜欢其他库,但如果你问我,我大概率会建议你先试试 **Strawberry**。 + +下面是一个将 Strawberry 与 FastAPI 集成的小预览: + +{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *} + +你可以在 Strawberry 文档中了解更多信息。 + +还有关于 将 Strawberry 与 FastAPI 结合使用的文档。 + +## Starlette 中较早的 `GraphQLApp` { #older-graphqlapp-from-starlette } + +早期版本的 Starlette 包含一个 `GraphQLApp` 类,用于与 Graphene 集成。 + +它已在 Starlette 中被弃用,但如果你的代码使用了它,你可以轻松**迁移**到 starlette-graphene3,它覆盖相同的用例,且接口**几乎完全一致**。 + +/// tip | 提示 + +如果你需要 GraphQL,我仍然建议看看 Strawberry,因为它基于类型注解而不是自定义类和类型。 + +/// + +## 了解更多 { #learn-more } + +你可以在 GraphQL 官方文档中了解更多关于 **GraphQL** 的内容。 + +你也可以通过上面的链接阅读各个库的更多信息。 diff --git a/docs/zh/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/zh/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md new file mode 100644 index 000000000..2e5445200 --- /dev/null +++ b/docs/zh/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -0,0 +1,135 @@ +# 从 Pydantic v1 迁移到 Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 } + +如果你有一个较旧的 FastAPI 应用,可能在使用 Pydantic v1。 + +FastAPI 0.100.0 同时支持 Pydantic v1 和 v2,会使用你已安装的任一版本。 + +FastAPI 0.119.0 引入了在 Pydantic v2 内部以 `pydantic.v1` 形式对 Pydantic v1 的部分支持,以便于迁移到 v2。 + +FastAPI 0.126.0 移除了对 Pydantic v1 的支持,但在一段时间内仍支持 `pydantic.v1`。 + +/// warning | 警告 + +从 Python 3.14 开始,Pydantic 团队不再为最新的 Python 版本提供 Pydantic v1 的支持。 + +这也包括 `pydantic.v1`,在 Python 3.14 及更高版本中不再受支持。 + +如果你想使用 Python 的最新特性,需要确保使用 Pydantic v2。 + +/// + +如果你的旧 FastAPI 应用在用 Pydantic v1,这里将向你展示如何迁移到 Pydantic v2,以及 FastAPI 0.119.0 中可帮助你渐进式迁移的功能。 + +## 官方指南 { #official-guide } + +Pydantic 有一份从 v1 迁移到 v2 的官方 迁移指南。 + +其中包含变更内容、校验如何更准确更严格、可能的注意事项等。 + +你可以阅读以更好地了解变更。 + +## 测试 { #tests } + +请确保你的应用有[测试](../tutorial/testing.md){.internal-link target=_blank},并在持续集成(CI)中运行它们。 + +这样你就可以升级并确保一切仍按预期工作。 + +## `bump-pydantic` { #bump-pydantic } + +在很多情况下,如果你使用的是未做自定义的常规 Pydantic 模型,可以将从 Pydantic v1 迁移到 v2 的大部分过程自动化。 + +你可以使用同一 Pydantic 团队提供的 `bump-pydantic`。 + +该工具会帮助你自动修改大部分需要变更的代码。 + +之后运行测试检查是否一切正常。如果正常,你就完成了。😎 + +## v2 中的 Pydantic v1 { #pydantic-v1-in-v2 } + +Pydantic v2 以子模块 `pydantic.v1` 的形式包含了 Pydantic v1 的全部内容。但在 Python 3.13 以上的版本中不再受支持。 + +这意味着你可以安装最新的 Pydantic v2,并从该子模块导入并使用旧的 Pydantic v1 组件,就像安装了旧版 Pydantic v1 一样。 + +{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} + +### FastAPI 对 v2 中 Pydantic v1 的支持 { #fastapi-support-for-pydantic-v1-in-v2 } + +自 FastAPI 0.119.0 起,FastAPI 也对 Pydantic v2 内的 Pydantic v1 提供了部分支持,以便迁移到 v2。 + +因此,你可以将 Pydantic 升级到最新的 v2,并将导入改为使用 `pydantic.v1` 子模块,在很多情况下就能直接工作。 + +{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} + +/// warning | 警告 + +请注意,由于 Pydantic 团队自 Python 3.14 起不再在较新的 Python 版本中支持 Pydantic v1,使用 `pydantic.v1` 在 Python 3.14 及更高版本中也不受支持。 + +/// + +### 同一应用中同时使用 Pydantic v1 与 v2 { #pydantic-v1-and-v2-on-the-same-app } + +Pydantic 不支持在一个 Pydantic v2 模型的字段中定义 Pydantic v1 模型,反之亦然。 + +```mermaid +graph TB + subgraph "❌ Not Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V1Field["Pydantic v1 Model"] + end + subgraph V1["Pydantic v1 Model"] + V2Field["Pydantic v2 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +...但是,你可以在同一个应用中分别使用 Pydantic v1 和 v2 的独立模型。 + +```mermaid +graph TB + subgraph "✅ Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V2Field["Pydantic v2 Model"] + end + subgraph V1["Pydantic v1 Model"] + V1Field["Pydantic v1 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +在某些情况下,甚至可以在 FastAPI 应用的同一个路径操作中同时使用 Pydantic v1 和 v2 模型: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} + +在上面的示例中,输入模型是 Pydantic v1 模型,输出模型(在 `response_model=ItemV2` 中定义)是 Pydantic v2 模型。 + +### Pydantic v1 参数 { #pydantic-v1-parameters } + +如果你需要在 Pydantic v1 模型中使用 FastAPI 特有的参数工具,如 `Body`、`Query`、`Form` 等,在完成向 Pydantic v2 的迁移前,可以从 `fastapi.temp_pydantic_v1_params` 导入它们: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} + +### 分步迁移 { #migrate-in-steps } + +/// tip | 提示 + +优先尝试 `bump-pydantic`,如果测试通过且可行,那么你就用一个命令完成了。✨ + +/// + +如果 `bump-pydantic` 不适用于你的场景,你可以在同一应用中同时支持 Pydantic v1 和 v2 模型,逐步迁移到 Pydantic v2。 + +你可以首先将 Pydantic 升级到最新的 v2,并将所有模型的导入改为使用 `pydantic.v1`。 + +然后按模块或分组,逐步把模型从 Pydantic v1 迁移到 v2。🚶 diff --git a/docs/zh/docs/how-to/separate-openapi-schemas.md b/docs/zh/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 000000000..c3efe5f1a --- /dev/null +++ b/docs/zh/docs/how-to/separate-openapi-schemas.md @@ -0,0 +1,102 @@ +# 是否为输入和输出分别生成 OpenAPI JSON Schema { #separate-openapi-schemas-for-input-and-output-or-not } + +自从发布了 **Pydantic v2**,生成的 OpenAPI 比之前更精确、更**正确**了。😎 + +事实上,在某些情况下,对于同一个 Pydantic 模型,OpenAPI 中会根据是否带有**默认值**,为输入和输出分别生成**两个 JSON Schema**。 + +我们来看看它如何工作,以及在需要时如何修改。 + +## 用于输入和输出的 Pydantic 模型 { #pydantic-models-for-input-and-output } + +假设你有一个带有默认值的 Pydantic 模型,例如: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} + +### 输入用的模型 { #model-for-input } + +如果你像下面这样把该模型用作输入: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} + +...那么 `description` 字段将**不是必填项**,因为它的默认值是 `None`。 + +### 文档中的输入模型 { #input-model-in-docs } + +你可以在文档中确认,`description` 字段没有**红色星号**,也就是未被标记为必填: + +
+ +
+ +### 输出用的模型 { #model-for-output } + +但如果你把同一个模型用作输出,例如: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} + +...那么因为 `description` 有默认值,即使你**不返回该字段**,它仍然会有这个**默认值**。 + +### 输出响应数据的模型 { #model-for-output-response-data } + +如果你在文档中交互并查看响应,即使代码没有给某个 `description` 字段赋值,JSON 响应中仍包含默认值(`null`): + +
+ +
+ +这意味着它**总会有值**,只是有时该值可能为 `None`(在 JSON 中是 `null`)。 + +这也意味着,使用你的 API 的客户端无需检查该值是否存在,他们可以**假设该字段总会存在**,只是有时它会是默认值 `None`。 + +在 OpenAPI 中描述这一点的方式,是把该字段标记为**必填**,因为它总会存在。 + +因此,一个模型的 JSON Schema 会根据它用于**输入还是输出**而有所不同: + +- 用于**输入**时,`description` **不是必填** +- 用于**输出**时,它是**必填**(并且可能为 `None`,在 JSON 中为 `null`) + +### 文档中的输出模型 { #model-for-output-in-docs } + +你也可以在文档中查看输出模型,`name` 和 `description` **都**被**红色星号**标记为**必填**: + +
+ +
+ +### 文档中的输入/输出模型 { #model-for-input-and-output-in-docs } + +如果你查看 OpenAPI 中可用的所有 Schema(JSON Schema),你会看到有两个,一个是 `Item-Input`,一个是 `Item-Output`。 + +对于 `Item-Input`,`description` **不是必填**,没有红色星号。 + +但对于 `Item-Output`,`description` 是**必填**,带有红色星号。 + +
+ +
+ +借助 **Pydantic v2** 的这个特性,你的 API 文档会更**精确**,如果你有自动生成的客户端和 SDK,它们也会更精确,带来更好的**开发者体验**和一致性。🎉 + +## 不要分离 Schema { #do-not-separate-schemas } + +当然,在某些情况下,你可能希望**输入和输出使用同一个 schema**。 + +最常见的情形是:你已经有一些自动生成的客户端代码/SDK,你暂时不想更新所有这些自动生成的客户端代码/SDK(也许未来会,但不是现在)。 + +这种情况下,你可以在 **FastAPI** 中通过参数 `separate_input_output_schemas=False` 禁用该特性。 + +/// info | 信息 + +对 `separate_input_output_schemas` 的支持是在 FastAPI `0.102.0` 中添加的。🤓 + +/// + +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} + +### 文档中输入/输出使用同一 Schema 的模型 { #same-schema-for-input-and-output-models-in-docs } + +现在该模型的输入和输出将只使用一个 schema,即 `Item`,并且其中的 `description` **不是必填**: + +
+ +
diff --git a/docs/zh/docs/how-to/testing-database.md b/docs/zh/docs/how-to/testing-database.md new file mode 100644 index 000000000..e4a20d843 --- /dev/null +++ b/docs/zh/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# 测试数据库 { #testing-a-database } + +你可以在 SQLModel 文档 中学习数据库、SQL 和 SQLModel。🤓 + +这里有一个关于在 FastAPI 中使用 SQLModel 的小教程:使用 SQLModel 搭配 FastAPI 的教程。✨ + +该教程包含一个关于 测试 SQL 数据库 的章节。😎 diff --git a/docs/zh/docs/index.md b/docs/zh/docs/index.md index 1c2aea328..38e128bf1 100644 --- a/docs/zh/docs/index.md +++ b/docs/zh/docs/index.md @@ -40,7 +40,7 @@ FastAPI 是一个用于构建 API 的现代、快速(高性能)的 Web 框 * **快速**:极高性能,可与 **NodeJS** 和 **Go** 并肩(归功于 Starlette 和 Pydantic)。[最快的 Python 框架之一](#performance)。 * **高效编码**:功能开发速度提升约 200% ~ 300%。* * **更少 bug**:人为(开发者)错误减少约 40%。* -* **直观**:极佳的编辑器支持。处处皆可自动补全。更少的调试时间。 +* **直观**:极佳的编辑器支持。处处皆可自动补全。更少的调试时间。 * **易用**:为易用和易学而设计。更少的文档阅读时间。 * **简短**:最小化代码重复。一次参数声明即可获得多种功能。更少的 bug。 * **健壮**:生产可用级代码。并带有自动生成的交互式文档。 @@ -368,7 +368,7 @@ item: Item * 数据校验: * 当数据无效时自动生成清晰的错误信息。 * 即便是多层嵌套的 JSON 对象也会进行校验。 -* 输入数据的转换:从网络读取到 Python 数据和类型。读取来源: +* 转换输入数据:从网络读取到 Python 数据和类型。读取来源: * JSON。 * 路径参数。 * 查询参数。 @@ -376,7 +376,7 @@ item: Item * Headers。 * Forms。 * Files。 -* 输出数据的转换:从 Python 数据和类型转换为网络数据(JSON): +* 转换输出数据:从 Python 数据和类型转换为网络数据(JSON): * 转换 Python 类型(`str`、`int`、`float`、`bool`、`list` 等)。 * `datetime` 对象。 * `UUID` 对象。 @@ -439,7 +439,7 @@ item: Item * 来自不同位置的**参数**声明:**headers**、**cookies**、**form 字段**和**文件**。 * 如何设置**校验约束**,如 `maximum_length` 或 `regex`。 -* 功能强大且易用的 **依赖注入** 系统。 +* 功能强大且易用的 **依赖注入** 系统。 * 安全与认证,包括对 **OAuth2**、**JWT tokens** 和 **HTTP Basic** 认证的支持。 * 更高级(但同样简单)的 **多层嵌套 JSON 模型** 声明技巧(得益于 Pydantic)。 * 通过 Strawberry 等库进行 **GraphQL** 集成。 @@ -524,7 +524,7 @@ Starlette 使用: * httpx - 使用 `TestClient` 时需要。 * jinja2 - 使用默认模板配置时需要。 -* python-multipart - 使用 `request.form()` 支持表单「解析」时需要。 +* python-multipart - 使用 `request.form()` 支持表单「解析」时需要。 FastAPI 使用: diff --git a/docs/zh/docs/python-types.md b/docs/zh/docs/python-types.md index 3e1c593c1..4824b7558 100644 --- a/docs/zh/docs/python-types.md +++ b/docs/zh/docs/python-types.md @@ -2,11 +2,11 @@ Python 支持可选的“类型提示”(也叫“类型注解”)。 -这些“类型提示”或注解是一种特殊语法,用来声明变量的类型。 +这些“类型提示”或注解是一种特殊语法,用来声明变量的类型。 通过为变量声明类型,编辑器和工具可以为你提供更好的支持。 -这只是一个关于 Python 类型提示的快速入门/复习。它只涵盖与 **FastAPI** 一起使用所需的最少部分……实际上非常少。 +这只是一个关于 Python 类型提示的快速入门/复习。它只涵盖与 **FastAPI** 一起使用所需的最少部分...实际上非常少。 **FastAPI** 完全基于这些类型提示构建,它们带来了许多优势和好处。 @@ -22,7 +22,7 @@ Python 支持可选的“类型提示”(也叫“类型注解”)。 让我们从一个简单的例子开始: -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} 运行这个程序会输出: @@ -34,9 +34,9 @@ John Doe * 接收 `first_name` 和 `last_name`。 * 通过 `title()` 将每个参数的第一个字母转换为大写。 -* 用一个空格将它们拼接起来。 +* 用一个空格将它们拼接起来。 -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### 修改它 { #edit-it } @@ -78,7 +78,7 @@ John Doe 这些就是“类型提示”: -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} 这和声明默认值不同,比如: @@ -106,7 +106,7 @@ John Doe 看这个已经带有类型提示的函数: -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} 因为编辑器知道变量的类型,你不仅能得到补全,还能获得错误检查: @@ -114,7 +114,7 @@ John Doe 现在你知道需要修复它,用 `str(age)` 把 `age` 转成字符串: -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} ## 声明类型 { #declaring-types } @@ -133,29 +133,32 @@ John Doe * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} -### 带类型参数的泛型类型 { #generic-types-with-type-parameters } +### typing 模块 { #typing-module } -有些数据结构可以包含其他值,比如 `dict`、`list`、`set` 和 `tuple`。而内部的值也会有自己的类型。 +在一些额外的用例中,你可能需要从标准库的 `typing` 模块导入内容。比如当你想声明“任意类型”时,可以使用 `typing` 中的 `Any`: -这些带有内部类型的类型称为“泛型”(generic)类型。可以把它们连同内部类型一起声明出来。 +```python +from typing import Any -要声明这些类型以及内部类型,你可以使用 Python 标准库模块 `typing`。它就是为支持这些类型提示而存在的。 -#### 更新的 Python 版本 { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -使用 `typing` 的语法与所有版本兼容,从 Python 3.6 到最新版本(包括 Python 3.9、Python 3.10 等)。 +### 泛型类型 { #generic-types } -随着 Python 的发展,更新的版本对这些类型注解的支持更好,在很多情况下你甚至不需要导入和使用 `typing` 模块来声明类型注解。 +有些类型可以在方括号中接收“类型参数”(type parameters),用于声明其内部值的类型。比如“字符串列表”可以写为 `list[str]`。 -如果你可以为项目选择更高版本的 Python,你将能享受到这种额外的简化。 +这些能接收类型参数的类型称为“泛型类型”(Generic types)或“泛型”(Generics)。 -在整个文档中,会根据不同 Python 版本提供相应的示例(当存在差异时)。 +你可以把相同的内建类型作为泛型使用(带方括号和内部类型): -比如“Python 3.6+”表示兼容 Python 3.6 及以上(包括 3.7、3.8、3.9、3.10 等)。而“Python 3.9+”表示兼容 Python 3.9 及以上(包括 3.10 等)。 - -如果你可以使用最新的 Python 版本,请使用最新版本的示例,它们将拥有最简洁的语法,例如“Python 3.10+”。 +* `list` +* `tuple` +* `set` +* `dict` #### 列表 { #list } @@ -167,7 +170,7 @@ John Doe 因为 list 是一种包含内部类型的类型,把内部类型写在方括号里: -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} /// info | 信息 @@ -193,7 +196,7 @@ John Doe 声明 `tuple` 和 `set` 的方式类似: -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} 这表示: @@ -208,7 +211,7 @@ John Doe 第二个类型参数用于字典的值: -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} 这表示: @@ -220,44 +223,20 @@ John Doe 你可以声明一个变量可以是若干种类型中的任意一种,比如既可以是 `int` 也可以是 `str`。 -在 Python 3.6 及以上(包括 Python 3.10),你可以使用 `typing` 中的 `Union`,把可能的类型放到方括号里。 +定义时使用竖线(`|`)把两种类型分开。 -在 Python 3.10 中还有一种新的语法,可以用竖线(`|`)把可能的类型分隔开。 - -//// tab | Python 3.10+ +这称为“联合类型”(union),因为变量可以是这两类类型集合的并集中的任意一个。 ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -两种方式的含义一致:`item` 可以是 `int` 或 `str`。 +这表示 `item` 可以是 `int` 或 `str`。 #### 可能为 `None` { #possibly-none } 你可以声明一个值的类型是某种类型(比如 `str`),但它也可能是 `None`。 -在 Python 3.6 及以上(包括 Python 3.10),你可以通过从 `typing` 模块导入并使用 `Optional` 来声明: - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -使用 `Optional[str]` 而不是仅仅 `str`,可以让编辑器帮助你发现把值当成总是 `str` 的错误(实际上它也可能是 `None`)。 - -`Optional[Something]` 实际上是 `Union[Something, None]` 的简写,它们等价。 - -这也意味着在 Python 3.10 中,你可以使用 `Something | None`: - //// tab | Python 3.10+ ```Python hl_lines="1" @@ -266,96 +245,7 @@ John Doe //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ 另一种写法 - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### 使用 `Union` 或 `Optional` { #using-union-or-optional } - -如果你使用的是 3.10 以下的 Python 版本,这里有个来自我非常主观的建议: - -* 🚨 避免使用 `Optional[SomeType]` -* 改用 ✨**`Union[SomeType, None]`**✨ - -两者等价,底层相同,但我更推荐 `Union` 而不是 `Optional`,因为“optional(可选)”这个词看起来像是在说“参数可选”,而它实际上表示“它可以是 `None`”,即使它并不是可选的,仍然是必填的。 - -我认为 `Union[SomeType, None]` 更明确地表达了它的意思。 - -这只是关于词语和名字。但这些词会影响你和你的队友如何理解代码。 - -例如,看下面这个函数: - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -参数 `name` 被定义为 `Optional[str]`,但它并不是“可选”的,你不能不传这个参数就调用函数: - -```Python -say_hi() # 哦不,这会抛错!😱 -``` - -参数 `name` 仍然是必填的(不是“可选”),因为它没有默认值。不过,`name` 接受 `None` 作为值: - -```Python -say_hi(name=None) # 这样可以,None 是有效值 🎉 -``` - -好消息是,一旦你使用 Python 3.10,就无需再为此操心,因为你可以直接用 `|` 来定义类型联合: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -这样你就不必再考虑 `Optional` 和 `Union` 这些名字了。😎 - -#### 泛型类型 { #generic-types } - -这些在方括号中接收类型参数的类型称为“泛型类型”(Generic types)或“泛型”(Generics),例如: - -//// tab | Python 3.10+ - -你可以把同样的内建类型作为泛型使用(带方括号和内部类型): - -* `list` -* `tuple` -* `set` -* `dict` - -以及与之前的 Python 版本一样,来自 `typing` 模块的: - -* `Union` -* `Optional` -* ……以及其他。 - -在 Python 3.10 中,作为使用泛型 `Union` 和 `Optional` 的替代,你可以使用竖线(`|`)来声明类型联合,这更好也更简单。 - -//// - -//// tab | Python 3.9+ - -你可以把同样的内建类型作为泛型使用(带方括号和内部类型): - -* `list` -* `tuple` -* `set` -* `dict` - -以及来自 `typing` 模块的泛型: - -* `Union` -* `Optional` -* ……以及其他。 - -//// +使用 `str | None` 而不是仅仅 `str`,可以让编辑器帮助你发现把值当成总是 `str` 的错误(实际上它也可能是 `None`)。 ### 类作为类型 { #classes-as-types } @@ -363,11 +253,11 @@ say_hi(name=None) # 这样可以,None 是有效值 🎉 假设你有一个名为 `Person` 的类,带有 name: -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} 然后你可以声明一个变量是 `Person` 类型: -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} 接着,你会再次获得所有的编辑器支持: @@ -403,19 +293,13 @@ say_hi(name=None) # 这样可以,None 是有效值 🎉 你会在[教程 - 用户指南](tutorial/index.md){.internal-link target=_blank}中看到更多的实战示例。 -/// tip | 提示 - -当你在没有默认值的情况下使用 `Optional` 或 `Union[Something, None]` 时,Pydantic 有一个特殊行为,你可以在 Pydantic 文档的 必填的 Optional 字段 中了解更多。 - -/// - ## 带元数据注解的类型提示 { #type-hints-with-metadata-annotations } -Python 还提供了一个特性,可以使用 `Annotated` 在这些类型提示中放入额外的元数据。 +Python 还提供了一个特性,可以使用 `Annotated` 在这些类型提示中放入额外的元数据。 -从 Python 3.9 起,`Annotated` 是标准库的一部分,因此可以从 `typing` 导入。 +你可以从 `typing` 导入 `Annotated`。 -{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} Python 本身不会对这个 `Annotated` 做任何处理。对于编辑器和其他工具,类型仍然是 `str`。 diff --git a/docs/zh/docs/resources/index.md b/docs/zh/docs/resources/index.md new file mode 100644 index 000000000..2f55ac06f --- /dev/null +++ b/docs/zh/docs/resources/index.md @@ -0,0 +1,3 @@ +# 资源 { #resources } + +更多资源、外部链接等。✈️ diff --git a/docs/zh/docs/translation-banner.md b/docs/zh/docs/translation-banner.md new file mode 100644 index 000000000..76db203b0 --- /dev/null +++ b/docs/zh/docs/translation-banner.md @@ -0,0 +1,11 @@ +/// details | 🌐 由 AI 与人类协作翻译 + +本翻译由人类引导的 AI 生成。🤝 + +可能存在误解原意或不够自然等问题。🤖 + +你可以通过[帮助我们更好地引导 AI LLM](https://fastapi.tiangolo.com/zh/contributing/#translations)来改进此翻译。 + +[英文版本](ENGLISH_VERSION_URL) + +/// diff --git a/docs/zh/docs/tutorial/background-tasks.md b/docs/zh/docs/tutorial/background-tasks.md index 8398472c3..d73fee429 100644 --- a/docs/zh/docs/tutorial/background-tasks.md +++ b/docs/zh/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ 首先导入 `BackgroundTasks` 并在 *路径操作函数* 中使用类型声明 `BackgroundTasks` 定义一个参数: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} **FastAPI** 会创建一个 `BackgroundTasks` 类型的对象并作为该参数传入。 @@ -31,13 +31,13 @@ 由于写操作不使用 `async` 和 `await`,我们用普通的 `def` 定义函数: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} ## 添加后台任务 { #add-the-background-task } 在你的 *路径操作函数* 里,用 `.add_task()` 方法将任务函数传到 *后台任务* 对象中: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} `.add_task()` 接收以下参数: @@ -69,7 +69,7 @@ 在FastAPI中仍然可以单独使用 `BackgroundTask`,但您必须在代码中创建对象,并返回包含它的Starlette `Response`。 -更多细节查看 Starlette's official docs for Background Tasks. +更多细节查看 Starlette 后台任务的官方文档. ## 告诫 { #caveat } diff --git a/docs/zh/docs/tutorial/bigger-applications.md b/docs/zh/docs/tutorial/bigger-applications.md index 1ced002dc..a667d596f 100644 --- a/docs/zh/docs/tutorial/bigger-applications.md +++ b/docs/zh/docs/tutorial/bigger-applications.md @@ -85,7 +85,7 @@ from app.routers import items 你可以导入它并通过与 `FastAPI` 类相同的方式创建一个「实例」: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} ### 使用 `APIRouter` 的*路径操作* { #path-operations-with-apirouter } @@ -93,7 +93,7 @@ from app.routers import items 使用方式与 `FastAPI` 类相同: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} 你可以将 `APIRouter` 视为一个「迷你 `FastAPI`」类。 @@ -117,7 +117,7 @@ from app.routers import items 现在我们将使用一个简单的依赖项来读取一个自定义的 `X-Token` 请求首部: -{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | 提示 @@ -149,7 +149,7 @@ from app.routers import items 因此,我们可以将其添加到 `APIRouter` 中,而不是将其添加到每个路径操作中。 -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} 由于每个*路径操作*的路径都必须以 `/` 开头,例如: @@ -208,7 +208,7 @@ async def read_item(item_id: str): 因此,我们通过 `..` 对依赖项使用了相对导入: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} #### 相对导入如何工作 { #how-relative-imports-work } @@ -279,11 +279,11 @@ from ...dependencies import get_token_header 但是我们仍然可以添加*更多*将会应用于特定的*路径操作*的 `tags`,以及一些特定于该*路径操作*的额外 `responses`: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | 提示 -最后的这个路径操作将包含标签的组合:`["items","custom"]`。 +最后的这个路径操作将包含标签的组合:`["items", "custom"]`。 并且在文档中也会有两个响应,一个用于 `404`,一个用于 `403`。 @@ -305,13 +305,13 @@ from ...dependencies import get_token_header 我们甚至可以声明[全局依赖项](dependencies/global-dependencies.md){.internal-link target=_blank},它会和每个 `APIRouter` 的依赖项组合在一起: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} ### 导入 `APIRouter` { #import-the-apirouter } 现在,我们导入具有 `APIRouter` 的其他子模块: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} 由于文件 `app/routers/users.py` 和 `app/routers/items.py` 是同一 Python 包 `app` 一个部分的子模块,因此我们可以使用单个点 ` .` 通过「相对导入」来导入它们。 @@ -374,13 +374,13 @@ from .routers.users import router 因此,为了能够在同一个文件中使用它们,我们直接导入子模块: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} ### 包含 `users` 和 `items` 的 `APIRouter` { #include-the-apirouters-for-users-and-items } 现在,让我们来包含来自 `users` 和 `items` 子模块的 `router`。 -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} /// info | 信息 @@ -420,13 +420,13 @@ from .routers.users import router 对于此示例,它将非常简单。但是假设由于它是与组织中的其他项目所共享的,因此我们无法对其进行修改,以及直接在 `APIRouter` 中添加 `prefix`、`dependencies`、`tags` 等: -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} 但是我们仍然希望在包含 `APIRouter` 时设置一个自定义的 `prefix`,以便其所有*路径操作*以 `/admin` 开头,我们希望使用本项目已经有的 `dependencies` 保护它,并且我们希望它包含自定义的 `tags` 和 `responses`。 我们可以通过将这些参数传递给 `app.include_router()` 来完成所有的声明,而不必修改原始的 `APIRouter`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} 这样,原始的 `APIRouter` 将保持不变,因此我们仍然可以与组织中的其他项目共享相同的 `app/internal/admin.py` 文件。 @@ -447,7 +447,7 @@ from .routers.users import router 这里我们这样做了...只是为了表明我们可以做到🤷: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} 它将与通过 `app.include_router()` 添加的所有其他*路径操作*一起正常运行。 diff --git a/docs/zh/docs/tutorial/body-multiple-params.md b/docs/zh/docs/tutorial/body-multiple-params.md index 7d0ddfc1e..39b84904f 100644 --- a/docs/zh/docs/tutorial/body-multiple-params.md +++ b/docs/zh/docs/tutorial/body-multiple-params.md @@ -104,12 +104,6 @@ q: str | None = None ``` -或者在 Python 3.9 中: - -```Python -q: Union[str, None] = None -``` - 比如: {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/zh/docs/tutorial/body-nested-models.md b/docs/zh/docs/tutorial/body-nested-models.md index 242aa5822..fe6902e83 100644 --- a/docs/zh/docs/tutorial/body-nested-models.md +++ b/docs/zh/docs/tutorial/body-nested-models.md @@ -163,7 +163,7 @@ images: list[Image] 例如: -{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} ## 无处不在的编辑器支持 { #editor-support-everywhere } @@ -193,7 +193,7 @@ images: list[Image] 在下面的例子中,你将接受任意键为 `int` 类型并且值为 `float` 类型的 `dict`: -{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} /// tip | 提示 diff --git a/docs/zh/docs/tutorial/body.md b/docs/zh/docs/tutorial/body.md index 60088a048..4a72ba17c 100644 --- a/docs/zh/docs/tutorial/body.md +++ b/docs/zh/docs/tutorial/body.md @@ -153,7 +153,7 @@ FastAPI 会根据默认值 `= None` 知道 `q` 的值不是必填的。 -`str | None`(Python 3.10+)或 `Union[str, None]`(Python 3.9+ 中的 `Union`)并不是 FastAPI 用来判断是否必填的依据;是否必填由是否有默认值 `= None` 决定。 +`str | None` 并不是 FastAPI 用来判断是否必填的依据;是否必填由是否有默认值 `= None` 决定。 但添加这些类型注解可以让你的编辑器提供更好的支持并检测错误。 diff --git a/docs/zh/docs/tutorial/cookie-param-models.md b/docs/zh/docs/tutorial/cookie-param-models.md index 707a6a9c7..8e094c7d3 100644 --- a/docs/zh/docs/tutorial/cookie-param-models.md +++ b/docs/zh/docs/tutorial/cookie-param-models.md @@ -46,17 +46,17 @@ 在某些特殊使用情况下(可能并不常见),您可能希望**限制**您想要接收的 cookie。 -您的 API 现在可以控制自己的 cookie 同意。🤪🍪 +您的 API 现在可以控制自己的 cookie 同意。🤪🍪 您可以使用 Pydantic 的模型配置来禁止( `forbid` )任何额外( `extra` )字段: {* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *} -如果客户尝试发送一些**额外的 cookie**,他们将收到**错误**响应。 +如果客户端尝试发送一些**额外的 cookie**,他们将收到**错误**响应。 -可怜的 cookie 通知条,费尽心思为了获得您的同意,却被API 拒绝了。🍪 +可怜的 cookie 通知条,费尽心思为了获得您的同意,却被API 拒绝了。🍪 -例如,如果客户端尝试发送一个值为 `good-list-please` 的 `santa_tracker` cookie,客户端将收到一个**错误**响应,告知他们 `santa_tracker` cookie 是不允许的: +例如,如果客户端尝试发送一个值为 `good-list-please` 的 `santa_tracker` cookie,客户端将收到一个**错误**响应,告知他们 `santa_tracker` cookie 是不允许的: ```json { @@ -73,4 +73,4 @@ ## 总结 { #summary } -您可以使用 **Pydantic 模型**在 **FastAPI** 中声明 **cookie**。😎 +您可以使用 **Pydantic 模型**在 **FastAPI** 中声明 **cookie**。😎 diff --git a/docs/zh/docs/tutorial/cors.md b/docs/zh/docs/tutorial/cors.md index 3a296ca72..2e271ec75 100644 --- a/docs/zh/docs/tutorial/cors.md +++ b/docs/zh/docs/tutorial/cors.md @@ -46,7 +46,7 @@ * 特定的 HTTP 方法(`POST`,`PUT`)或者使用通配符 `"*"` 允许所有方法。 * 特定的 HTTP 请求头或者使用通配符 `"*"` 允许所有请求头。 -{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} 默认情况下,这个 `CORSMiddleware` 实现所使用的默认参数较为保守,所以你需要显式地启用特定的源、方法或者 headers,以便浏览器能够在跨域上下文中使用它们。 @@ -63,7 +63,7 @@ * `expose_headers` - 指示可以被浏览器访问的响应头。默认为 `[]`。 * `max_age` - 设定浏览器缓存 CORS 响应的最长时间,单位是秒。默认为 `600`。 -中间件响应两种特定类型的 HTTP 请求…… +中间件响应两种特定类型的 HTTP 请求... ### CORS 预检请求 { #cors-preflight-requests } diff --git a/docs/zh/docs/tutorial/debugging.md b/docs/zh/docs/tutorial/debugging.md index 6e0cefe5b..1ff7d6127 100644 --- a/docs/zh/docs/tutorial/debugging.md +++ b/docs/zh/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ 在你的 FastAPI 应用中直接导入 `uvicorn` 并运行: -{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} ### 关于 `__name__ == "__main__"` { #about-name-main } @@ -68,7 +68,7 @@ from myapp import app uvicorn.run(app, host="0.0.0.0", port=8000) ``` -/// info +/// info | 信息 更多信息请检查 Python 官方文档. diff --git a/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md index d83321ddd..43bd7a680 100644 --- a/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/zh/docs/tutorial/dependencies/classes-as-dependencies.md @@ -14,7 +14,7 @@ 对此,我们可以做的更好... -## 什么构成了依赖项? { #what-makes-a-dependency } +## 什么构成了依赖项 { #what-makes-a-dependency } 到目前为止,你看到的依赖项都被声明为函数。 @@ -101,7 +101,7 @@ fluffy = Cat(name="Mr Fluffy") 注意,我们在上面的代码中编写了两次`CommonQueryParams`: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ 未使用 Annotated +//// tab | Python 3.10+ 未使用 Annotated /// tip | 提示 @@ -129,7 +129,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ... Depends(CommonQueryParams) ``` -...实际上是 **Fastapi** 用来知道依赖项是什么的。 +...实际上是 **FastAPI** 用来知道依赖项是什么的。 FastAPI 将从依赖项中提取声明的参数,这才是 FastAPI 实际调用的。 @@ -137,7 +137,7 @@ FastAPI 将从依赖项中提取声明的参数,这才是 FastAPI 实际调用 在本例中,第一个 `CommonQueryParams` : -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.9+ 未使用 Annotated +//// tab | Python 3.10+ 未使用 Annotated /// tip | 提示 @@ -163,7 +163,7 @@ commons: CommonQueryParams ... 你实际上可以只这样编写: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ 未使用 Annotated +//// tab | Python 3.10+ 未使用 Annotated /// tip | 提示 @@ -197,7 +197,7 @@ commons = Depends(CommonQueryParams) 但是你可以看到,我们在这里有一些代码重复了,编写了`CommonQueryParams`两次: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ 未使用 Annotated +//// tab | Python 3.10+ 未使用 Annotated /// tip | 提示 @@ -225,7 +225,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) 不是写成这样: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ 未使用 Annotated +//// tab | Python 3.10+ 未使用 Annotated /// tip | 提示 @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...而是这样写: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.9+ 未使用 Annotated +//// tab | Python 3.10+ 未使用 Annotated /// tip | 提示 diff --git a/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 02fcf62a0..23412e465 100644 --- a/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/zh/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -14,7 +14,7 @@ 该参数的值是由 `Depends()` 组成的 `list`: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} 路径操作装饰器依赖项的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给*路径操作函数*。 @@ -22,13 +22,13 @@ 有些编辑器会检查代码中没使用过的函数参数,并显示错误提示。 -在*路径操作装饰器*中使用 `dependencies` 参数,可以确保在执行依赖项的同时,避免编辑器显示错误提示。 +在*路径操作装饰器*中使用 `dependencies` 参数,可以确保在执行依赖项的同时,避免编辑器/工具报错。 使用路径装饰器依赖项还可以避免开发新人误会代码中包含无用的未使用参数。 /// -/// info | 说明 +/// info | 信息 本例中,使用的是自定义响应头 `X-Key` 和 `X-Token`。 @@ -44,13 +44,13 @@ 路径装饰器依赖项可以声明请求的需求项(比如响应头)或其他子依赖项: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} ### 触发异常 { #raise-exceptions } 路径装饰器依赖项与正常的依赖项一样,可以 `raise` 异常: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} ### 返回值 { #return-values } @@ -58,7 +58,7 @@ 因此,可以复用在其他位置使用过的、(能返回值的)普通依赖项,即使没有使用这个值,也会执行该依赖项: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} ## 为一组路径操作定义依赖项 { #dependencies-for-a-group-of-path-operations } diff --git a/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md index bf495c9f3..413dedb96 100644 --- a/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,6 +1,6 @@ # 使用 yield 的依赖项 { #dependencies-with-yield } -FastAPI 支持那些在完成后执行一些额外步骤的依赖项。 +FastAPI 支持那些在完成后执行一些额外步骤的依赖项。 为此,使用 `yield` 而不是 `return`,并把这些额外步骤(代码)写在后面。 @@ -29,15 +29,15 @@ FastAPI 支持那些在完成后执行一些docstring 中声明*路径操作*的描述,**FastAPI** 会从中读取。 +描述内容比较长且占用多行时,可以在函数的 docstring 中声明*路径操作*的描述,**FastAPI** 会从中读取。 文档字符串支持 Markdown,能正确解析和显示 Markdown 的内容,但要注意文档字符串的缩进。 {* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *} -下图为 Markdown 文本在 API 文档中的显示效果: +它会在交互式文档中使用: @@ -72,7 +72,7 @@ OpenAPI 概图会自动添加标签,供 API 文档接口使用: {* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} -/// info | 说明 +/// info | 信息 注意,`response_description` 只用于描述响应,`description` 一般则用于描述*路径操作*。 @@ -90,9 +90,9 @@ OpenAPI 规定每个*路径操作*都要有响应描述。 ## 弃用*路径操作* { #deprecate-a-path-operation } -`deprecated` 参数可以把*路径操作*标记为弃用,无需直接删除: +如果需要把*路径操作*标记为弃用,但不删除它,可以传入 `deprecated` 参数: -{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} API 文档会把该路径操作标记为弃用: diff --git a/docs/zh/docs/tutorial/path-params-numeric-validations.md b/docs/zh/docs/tutorial/path-params-numeric-validations.md index ff8b1762c..608aa69a1 100644 --- a/docs/zh/docs/tutorial/path-params-numeric-validations.md +++ b/docs/zh/docs/tutorial/path-params-numeric-validations.md @@ -54,11 +54,11 @@ FastAPI 在 0.95.0 版本添加了对 `Annotated` 的支持(并开始推荐使 因此,你可以将函数声明为: -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} 但请记住,如果你使用 `Annotated`,你就不会遇到这个问题,因为你没有使用 `Query()` 或 `Path()` 作为函数参数的默认值。 -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## 按需对参数排序的技巧 { #order-the-parameters-as-you-need-tricks } @@ -83,13 +83,13 @@ FastAPI 在 0.95.0 版本添加了对 `Annotated` 的支持(并开始推荐使 Python 不会对这个 `*` 做任何事,但它会知道之后的所有参数都应该作为关键字参数(键值对)来调用,也被称为 kwargs。即使它们没有默认值。 -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### 使用 `Annotated` 更好 { #better-with-annotated } 请记住,如果你使用 `Annotated`,因为你没有使用函数参数的默认值,所以你不会有这个问题,你大概率也不需要使用 `*`。 -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## 数值校验:大于等于 { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ Python 不会对这个 `*` 做任何事,但它会知道之后的所有参数 在这里,使用 `ge=1` 后,`item_id` 必须是一个整数,值要「`g`reater than or `e`qual」1。 -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## 数值校验:大于和小于等于 { #number-validations-greater-than-and-less-than-or-equal } @@ -106,7 +106,7 @@ Python 不会对这个 `*` 做任何事,但它会知道之后的所有参数 * `gt`:大于(`g`reater `t`han) * `le`:小于等于(`l`ess than or `e`qual) -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## 数值校验:浮点数、大于和小于 { #number-validations-floats-greater-than-and-less-than } @@ -118,7 +118,7 @@ Python 不会对这个 `*` 做任何事,但它会知道之后的所有参数 对于 lt 也是一样的。 -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## 总结 { #recap } diff --git a/docs/zh/docs/tutorial/path-params.md b/docs/zh/docs/tutorial/path-params.md index fa4c9514a..06a9f1b44 100644 --- a/docs/zh/docs/tutorial/path-params.md +++ b/docs/zh/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ 你可以使用与 Python 字符串格式化相同的语法声明路径“参数”或“变量”: -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} 路径参数 `item_id` 的值会作为参数 `item_id` 传递给你的函数。 @@ -16,7 +16,7 @@ 使用 Python 标准类型注解,声明路径操作函数中路径参数的类型: -{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params/tutorial002_py310.py hl[7] *} 本例把 `item_id` 的类型声明为 `int`。 @@ -26,7 +26,7 @@ /// -## 数据转换 { #data-conversion } +## 数据转换 { #data-conversion } 运行示例并访问 http://127.0.0.1:8000/items/3,返回的响应如下: @@ -38,7 +38,7 @@ 注意,函数接收并返回的值是 `3`( `int`),不是 `"3"`(`str`)。 -**FastAPI** 通过类型声明自动**解析**请求中的数据。 +**FastAPI** 通过类型声明自动进行请求的解析。 /// @@ -118,19 +118,19 @@ FastAPI 充分地利用了 `Enum` 类型接收预设的路径参数。 +路径操作使用 Python 的 `Enum` 类型接收预设的路径参数。 ### 创建 `Enum` 类 { #create-an-enum-class } @@ -140,11 +140,11 @@ FastAPI 充分地利用了 模型的名字。 +**AlexNet**、**ResNet**、**LeNet** 是机器学习模型的名字。 /// @@ -152,7 +152,7 @@ FastAPI 充分地利用了 解析" +- 数据 "解析" - 数据校验 - API 注解和自动文档 diff --git a/docs/zh/docs/tutorial/query-params-str-validations.md b/docs/zh/docs/tutorial/query-params-str-validations.md index 62552e6d0..d41f30226 100644 --- a/docs/zh/docs/tutorial/query-params-str-validations.md +++ b/docs/zh/docs/tutorial/query-params-str-validations.md @@ -47,40 +47,16 @@ FastAPI 在 0.95.0 版本中添加了对 `Annotated` 的支持(并开始推荐 我们之前的类型标注是: -//// tab | Python 3.10+ - ```Python q: str | None = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Union[str, None] = None -``` - -//// - 我们要做的是用 `Annotated` 把它包起来,变成: -//// tab | Python 3.10+ - ```Python q: Annotated[str | None] = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Annotated[Union[str, None]] = None -``` - -//// - 这两种写法含义相同,`q` 是一个可以是 `str` 或 `None` 的参数,默认是 `None`。 现在进入更有趣的部分。🎉 @@ -109,7 +85,7 @@ FastAPI 现在会: ## 另一种(旧的)方式:把 `Query` 作为默认值 { #alternative-old-query-as-the-default-value } -早期版本的 FastAPI(0.95.0 之前)要求你把 `Query` 作为参数的默认值,而不是放在 `Annotated` 里。你很可能会在别处看到这种写法,所以我也给你解释一下。 +早期版本的 FastAPI(0.95.0 之前)要求你把 `Query` 作为参数的默认值,而不是放在 `Annotated` 里。你很可能会在别处看到这种写法,所以我也给你解释一下。 /// tip | 提示 @@ -191,7 +167,7 @@ q: str = Query(default="rick") ## 添加正则表达式 { #add-regular-expressions } -你可以定义一个参数必须匹配的 正则表达式 `pattern`: +你可以定义一个参数必须匹配的 正则表达式 `pattern`: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -211,7 +187,7 @@ q: str = Query(default="rick") 假设你想要声明查询参数 `q` 的 `min_length` 为 `3`,并且默认值为 `"fixedquery"`: -{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} /// note | 注意 @@ -241,7 +217,7 @@ q: Annotated[str | None, Query(min_length=3)] = None 因此,在使用 `Query` 的同时需要把某个值声明为必填时,只需不声明默认值: -{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} ### 必填,但可以为 `None` { #required-can-be-none } @@ -292,7 +268,7 @@ http://localhost:8000/items/?q=foo&q=bar 你还可以定义在没有给定值时的默认 `list`: -{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} 如果你访问: @@ -315,7 +291,7 @@ http://localhost:8000/items/ 你也可以直接使用 `list`,而不是 `list[str]`: -{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} /// note | 注意 @@ -371,7 +347,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems 现在假设你不再喜欢这个参数了。 -由于还有客户端在使用它,你不得不保留一段时间,但你希望文档清楚地将其展示为已弃用。 +由于还有客户端在使用它,你不得不保留一段时间,但你希望文档清楚地将其展示为已弃用。 那么将参数 `deprecated=True` 传给 `Query`: @@ -401,7 +377,7 @@ Pydantic 还有 ISBN 书号)或 `imdb-`(用于 IMDB 电影 URL 的 ID)开头: +例如,这个自定义校验器会检查条目 ID 是否以 `isbn-`(用于 ISBN 书号)或 `imdb-`(用于 IMDB 电影 URL 的 ID)开头: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -435,7 +411,7 @@ Pydantic 还有 可迭代对象。 +使用 `data.items()` 我们会得到一个包含每个字典项键和值的元组的 可迭代对象。 我们用 `list(data.items())` 把这个可迭代对象转换成一个真正的 `list`。 diff --git a/docs/zh/docs/tutorial/query-params.md b/docs/zh/docs/tutorial/query-params.md index 9ef998731..4ea37d7e0 100644 --- a/docs/zh/docs/tutorial/query-params.md +++ b/docs/zh/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ 声明的参数不是路径参数时,路径操作函数会把该参数自动解释为**查询**参数。 -{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} 查询字符串是键值对的集合,这些键值对位于 URL 的 `?` 之后,以 `&` 分隔。 @@ -12,7 +12,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10 ``` -……查询参数为: +...查询参数为: * `skip`:值为 `0` * `limit`:值为 `10` @@ -24,7 +24,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10 所有应用于路径参数的流程也适用于查询参数: * (显而易见的)编辑器支持 -* 数据**解析** +* 数据"解析" * 数据校验 * 自动文档 @@ -75,7 +75,6 @@ http://127.0.0.1:8000/items/?skip=20 参数还可以声明为 `bool` 类型,FastAPI 会自动转换参数类型: - {* ../../docs_src/query_params/tutorial003_py310.py hl[7] *} 本例中,访问: @@ -129,7 +128,7 @@ FastAPI 通过参数名进行检测: 如果要把查询参数设置为**必选**,就不要声明默认值: -{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} 这里的查询参数 `needy` 是类型为 `str` 的必选查询参数。 @@ -139,7 +138,7 @@ FastAPI 通过参数名进行检测: http://127.0.0.1:8000/items/foo-item ``` -……因为路径中没有必选参数 `needy`,返回的响应中会显示如下错误信息: +...因为路径中没有必选参数 `needy`,返回的响应中会显示如下错误信息: ```JSON { @@ -163,7 +162,7 @@ http://127.0.0.1:8000/items/foo-item http://127.0.0.1:8000/items/foo-item?needy=sooooneedy ``` -……这样就正常了: +...这样就正常了: ```JSON { diff --git a/docs/zh/docs/tutorial/request-files.md b/docs/zh/docs/tutorial/request-files.md index 927bd093b..e3b543539 100644 --- a/docs/zh/docs/tutorial/request-files.md +++ b/docs/zh/docs/tutorial/request-files.md @@ -20,13 +20,13 @@ $ pip install python-multipart 从 `fastapi` 导入 `File` 和 `UploadFile`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} ## 定义 `File` 参数 { #define-file-parameters } 像为 `Body` 或 `Form` 一样创建文件参数: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} /// info | 信息 @@ -54,7 +54,7 @@ $ pip install python-multipart 将文件参数的类型声明为 `UploadFile`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} 与 `bytes` 相比,使用 `UploadFile` 有多项优势: @@ -76,9 +76,9 @@ $ pip install python-multipart `UploadFile` 具有以下 `async` 方法。它们都会在底层调用对应的文件方法(使用内部的 `SpooledTemporaryFile`)。 -* `write(data)`:将 `data`(`str` 或 `bytes`)写入文件。 -* `read(size)`:读取文件中 `size`(`int`)个字节/字符。 -* `seek(offset)`:移动到文件中字节位置 `offset`(`int`)。 +* `write(data)`:将 `data` (`str` 或 `bytes`) 写入文件。 +* `read(size)`:读取文件中 `size` (`int`) 个字节/字符。 +* `seek(offset)`:移动到文件中字节位置 `offset` (`int`)。 * 例如,`await myfile.seek(0)` 会移动到文件开头。 * 如果你先运行过 `await myfile.read()`,然后需要再次读取内容时,这尤其有用。 * `close()`:关闭文件。 @@ -121,7 +121,7 @@ HTML 表单(`
`)向服务器发送数据的方式通常会对数 但当表单包含文件时,会编码为 `multipart/form-data`。如果你使用 `File`,**FastAPI** 会知道需要从请求体的正确位置获取文件。 -如果你想进一步了解这些编码和表单字段,请参阅
MDN web docs for POST。 +如果你想进一步了解这些编码和表单字段,请参阅 MDN 关于 POST 的 Web 文档。 /// @@ -135,7 +135,7 @@ HTML 表单(`
`)向服务器发送数据的方式通常会对数 ## 可选文件上传 { #optional-file-upload } -您可以通过使用标准类型注解并将 None 作为默认值的方式将一个文件参数设为可选: +您可以通过使用标准类型注解并将 `None` 作为默认值的方式将一个文件参数设为可选: {* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} @@ -143,7 +143,7 @@ HTML 表单(`
`)向服务器发送数据的方式通常会对数 您也可以将 `File()` 与 `UploadFile` 一起使用,例如,设置额外的元数据: -{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} ## 多文件上传 { #multiple-file-uploads } @@ -153,7 +153,7 @@ FastAPI 支持同时上传多个文件。 要实现这一点,声明一个由 `bytes` 或 `UploadFile` 组成的列表(`List`): -{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} 接收的也是含 `bytes` 或 `UploadFile` 的列表(`list`)。 @@ -169,7 +169,7 @@ FastAPI 支持同时上传多个文件。 和之前的方式一样,你可以为 `File()` 设置额外参数,即使是 `UploadFile`: -{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} ## 小结 { #recap } diff --git a/docs/zh/docs/tutorial/request-form-models.md b/docs/zh/docs/tutorial/request-form-models.md index 4eb98ea22..63759df08 100644 --- a/docs/zh/docs/tutorial/request-form-models.md +++ b/docs/zh/docs/tutorial/request-form-models.md @@ -24,7 +24,7 @@ $ pip install python-multipart 你只需声明一个 **Pydantic 模型**,其中包含你希望接收的**表单字段**,然后将参数声明为 `Form`: -{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} **FastAPI** 将从请求中的**表单数据**中**提取**出**每个字段**的数据,并提供你定义的 Pydantic 模型。 @@ -48,7 +48,7 @@ $ pip install python-multipart 你可以使用 Pydantic 的模型配置来 `forbid` 任何 `extra` 字段: -{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} 如果客户端尝试发送一些额外的数据,他们将收到**错误**响应。 diff --git a/docs/zh/docs/tutorial/request-forms-and-files.md b/docs/zh/docs/tutorial/request-forms-and-files.md index 3c809868b..484fcd5d6 100644 --- a/docs/zh/docs/tutorial/request-forms-and-files.md +++ b/docs/zh/docs/tutorial/request-forms-and-files.md @@ -16,17 +16,17 @@ $ pip install python-multipart ## 导入 `File` 与 `Form` { #import-file-and-form } -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} ## 定义 `File` 与 `Form` 参数 { #define-file-and-form-parameters } 创建文件和表单参数的方式与 `Body` 和 `Query` 一样: -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} 文件和表单字段作为表单数据上传与接收。 -声明文件可以使用 `bytes` 或 `UploadFile` 。 +并且你可以将部分文件声明为 `bytes`,将部分文件声明为 `UploadFile`。 /// warning | 警告 diff --git a/docs/zh/docs/tutorial/request-forms.md b/docs/zh/docs/tutorial/request-forms.md index 70eb93a26..1fc305a69 100644 --- a/docs/zh/docs/tutorial/request-forms.md +++ b/docs/zh/docs/tutorial/request-forms.md @@ -18,17 +18,17 @@ $ pip install python-multipart 从 `fastapi` 导入 `Form`: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} ## 定义 `Form` 参数 { #define-form-parameters } 创建表单参数的方式与 `Body` 或 `Query` 相同: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} 例如,在 OAuth2 规范的一种使用方式(称为“密码流”)中,要求将 `username` 和 `password` 作为表单字段发送。 -spec 要求这些字段必须精确命名为 `username` 和 `password`,并且作为表单字段发送,而不是 JSON。 +规范 要求这些字段必须精确命名为 `username` 和 `password`,并且作为表单字段发送,而不是 JSON。 使用 `Form` 可以像使用 `Body`(以及 `Query`、`Path`、`Cookie`)一样声明相同的配置,包括校验、示例、别名(例如将 `username` 写成 `user-name`)等。 diff --git a/docs/zh/docs/tutorial/response-model.md b/docs/zh/docs/tutorial/response-model.md index 791eb66fb..df0afa66f 100644 --- a/docs/zh/docs/tutorial/response-model.md +++ b/docs/zh/docs/tutorial/response-model.md @@ -183,7 +183,7 @@ FastAPI 在内部配合 Pydantic 做了多项处理,确保不会把类继承 最常见的情况是[直接返回 Response,详见进阶文档](../advanced/response-directly.md){.internal-link target=_blank}。 -{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} 这个简单场景 FastAPI 会自动处理,因为返回类型注解是 `Response`(或其子类)。 @@ -193,7 +193,7 @@ FastAPI 在内部配合 Pydantic 做了多项处理,确保不会把类继承 你也可以在类型注解中使用 `Response` 的子类: -{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} 这同样可行,因为 `RedirectResponse` 是 `Response` 的子类,FastAPI 会自动处理这个简单场景。 @@ -201,7 +201,7 @@ FastAPI 在内部配合 Pydantic 做了多项处理,确保不会把类继承 但当你返回其他任意对象(如数据库对象)而它不是有效的 Pydantic 类型,并在函数中按此进行了注解时,FastAPI 会尝试基于该类型注解创建一个 Pydantic 响应模型,但会失败。 -如果你有一个在多个类型之间的联合类型,其中一个或多个不是有效的 Pydantic 类型,也会发生同样的情况,例如这个会失败 💥: +如果你有一个在多个类型之间的联合类型,其中一个或多个不是有效的 Pydantic 类型,也会发生同样的情况,例如这个会失败 💥: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} diff --git a/docs/zh/docs/tutorial/response-status-code.md b/docs/zh/docs/tutorial/response-status-code.md index 3630de280..266f919ba 100644 --- a/docs/zh/docs/tutorial/response-status-code.md +++ b/docs/zh/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ * `@app.delete()` * 等... -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} /// note | 注意 @@ -74,7 +74,7 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。 再看下之前的例子: -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} `201` 表示“已创建”的状态码。 @@ -82,7 +82,7 @@ FastAPI 可以进行识别,并生成表明无响应体的 OpenAPI 文档。 可以使用 `fastapi.status` 中的快捷变量。 -{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} 这只是一种快捷方式,具有相同的数字代码,但它可以使用编辑器的自动补全功能: diff --git a/docs/zh/docs/tutorial/schema-extra-example.md b/docs/zh/docs/tutorial/schema-extra-example.md index 818ff5087..ec1fc29e5 100644 --- a/docs/zh/docs/tutorial/schema-extra-example.md +++ b/docs/zh/docs/tutorial/schema-extra-example.md @@ -74,7 +74,7 @@ OpenAPI 3.1.0(自 FastAPI 0.99.0 起使用)增加了对 `examples` 的支持 这样做时,这些示例会成为该请求体数据内部 JSON Schema 的一部分。 -不过,在撰写本文时,用于展示文档 UI 的 Swagger UI 并不支持显示 JSON Schema 中数据的多个示例。但请继续阅读,下面有一种变通方法。 +不过,在撰写本文时,用于展示文档 UI 的 Swagger UI 并不支持显示 JSON Schema 中数据的多个示例。但请继续阅读,下面有一种变通方法。 ### OpenAPI 特定的 `examples` { #openapi-specific-examples } diff --git a/docs/zh/docs/tutorial/security/first-steps.md b/docs/zh/docs/tutorial/security/first-steps.md index 43b7c6657..8b1aeb70b 100644 --- a/docs/zh/docs/tutorial/security/first-steps.md +++ b/docs/zh/docs/tutorial/security/first-steps.md @@ -20,7 +20,7 @@ 把下面的示例代码复制到 `main.py`: -{* ../../docs_src/security/tutorial001_an_py39.py *} +{* ../../docs_src/security/tutorial001_an_py310.py *} ## 运行 { #run-it } @@ -132,7 +132,7 @@ OAuth2 的设计目标是让后端或 API 与负责用户认证的服务器解 创建 `OAuth2PasswordBearer` 类实例时,需要传入 `tokenUrl` 参数。该参数包含客户端(运行在用户浏览器中的前端)用来发送 `username` 和 `password` 以获取令牌的 URL。 -{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} /// tip | 提示 @@ -170,7 +170,7 @@ oauth2_scheme(some, parameters) 现在你可以通过 `Depends` 将 `oauth2_scheme` 作为依赖传入。 -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} 该依赖会提供一个 `str`,赋值给*路径操作函数*的参数 `token`。 diff --git a/docs/zh/docs/tutorial/security/get-current-user.md b/docs/zh/docs/tutorial/security/get-current-user.md index c14bba28a..814ff2c82 100644 --- a/docs/zh/docs/tutorial/security/get-current-user.md +++ b/docs/zh/docs/tutorial/security/get-current-user.md @@ -2,7 +2,7 @@ 上一章中,(基于依赖注入系统的)安全系统向*路径操作函数*传递了 `str` 类型的 `token`: -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} 但这并不实用。 diff --git a/docs/zh/docs/tutorial/security/index.md b/docs/zh/docs/tutorial/security/index.md index 589f93c3e..a4c352110 100644 --- a/docs/zh/docs/tutorial/security/index.md +++ b/docs/zh/docs/tutorial/security/index.md @@ -10,7 +10,7 @@ 但首先,让我们来看一些小的概念。 -## 没有时间? { #in-a-hurry } +## 赶时间 { #in-a-hurry } 如果你不关心这些术语,而只需要*立即*通过基于用户名和密码的身份认证来增加安全性,请跳转到接下来的章节。 diff --git a/docs/zh/docs/tutorial/security/oauth2-jwt.md b/docs/zh/docs/tutorial/security/oauth2-jwt.md index c7eb9bd90..b5ccfd3e3 100644 --- a/docs/zh/docs/tutorial/security/oauth2-jwt.md +++ b/docs/zh/docs/tutorial/security/oauth2-jwt.md @@ -116,7 +116,11 @@ pwdlib 也支持 bcrypt 哈希算法,但不包含遗留算法——如果需 再创建一个工具函数来进行身份验证并返回用户。 -{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +当使用一个在数据库中不存在的用户名调用 `authenticate_user` 时,我们仍然会针对一个虚拟哈希运行 `verify_password`。 + +这可以确保无论用户名是否有效,端点的响应时间大致相同,从而防止可用于枚举已存在用户名的“时间攻击”(timing attacks)。 /// note | 注意 @@ -152,7 +156,7 @@ $ openssl rand -hex 32 创建一个生成新访问令牌的工具函数。 -{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} ## 更新依赖项 { #update-the-dependencies } @@ -162,7 +166,7 @@ $ openssl rand -hex 32 如果令牌无效,立即返回一个 HTTP 错误。 -{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} ## 更新 `/token` 路径操作 { #update-the-token-path-operation } @@ -170,7 +174,7 @@ $ openssl rand -hex 32 创建一个真正的 JWT 访问令牌并返回它。 -{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} ### 关于 JWT “主题” `sub` 的技术细节 { #technical-details-about-the-jwt-subject-sub } diff --git a/docs/zh/docs/tutorial/security/simple-oauth2.md b/docs/zh/docs/tutorial/security/simple-oauth2.md index 3037a983b..95f708ae6 100644 --- a/docs/zh/docs/tutorial/security/simple-oauth2.md +++ b/docs/zh/docs/tutorial/security/simple-oauth2.md @@ -6,9 +6,9 @@ 首先,使用 **FastAPI** 安全工具获取 `username` 和 `password`。 -OAuth2 规范要求使用**密码流**时,客户端或用户必须以表单数据形式发送 `username` 和 `password` 字段。 +OAuth2 规范要求使用“密码流”时,客户端或用户必须以表单数据形式发送 `username` 和 `password` 字段。 -并且,这两个字段必须命名为 `username` 和 `password` ,不能使用 `user-name` 或 `email` 等其它名称。 +并且,这两个字段必须命名为 `username` 和 `password`,不能使用 `user-name` 或 `email` 等其它名称。 不过也不用担心,前端仍可以显示终端用户所需的名称。 @@ -74,7 +74,7 @@ OAuth2 中,**作用域**只是声明指定权限的字符串。 /// info | 信息 -`OAuth2PasswordRequestForm` 与 `OAuth2PasswordBearer` 一样,都不是 FastAPI 的特殊类。 +`OAuth2PasswordRequestForm` 并不像 `OAuth2PasswordBearer` 那样是 **FastAPI** 的特殊类。 **FastAPI** 把 `OAuth2PasswordBearer` 识别为安全方案。因此,可以通过这种方式把它添加至 OpenAPI。 @@ -200,7 +200,7 @@ UserInDB( 此处返回值为 `Bearer` 的响应头 `WWW-Authenticate` 也是规范的一部分。 -任何 401**UNAUTHORIZED**HTTP(错误)状态码都应返回 `WWW-Authenticate` 响应头。 +任何 401“UNAUTHORIZED”HTTP(错误)状态码都应返回 `WWW-Authenticate` 响应头。 本例中,因为使用的是 Bearer Token,该响应头的值应为 `Bearer`。 @@ -220,7 +220,7 @@ UserInDB( ### 身份验证 { #authenticate } -点击**Authorize**按钮。 +点击“Authorize”按钮。 使用以下凭证: @@ -286,4 +286,4 @@ UserInDB( 唯一欠缺的是,它仍然不是真的**安全**。 -下一章,介绍使用密码哈希支持库与 JWT 令牌实现真正的安全机制。 +下一章你将看到如何使用安全的密码哈希库和 JWT 令牌。 diff --git a/docs/zh/docs/tutorial/sql-databases.md b/docs/zh/docs/tutorial/sql-databases.md index 944e960a7..ef0b7c460 100644 --- a/docs/zh/docs/tutorial/sql-databases.md +++ b/docs/zh/docs/tutorial/sql-databases.md @@ -216,7 +216,7 @@ $ fastapi dev main.py 它包含与 `HeroBase` 相同的字段,因此不会包括 `secret_name`。 -终于,我们英雄(hero)的身份得到了保护!🥷 +终于,我们英雄的身份得到了保护!🥷 它还重新声明了 `id: int`。这样我们便与 API 客户端建立了一种**约定**,使他们始终可以期待 `id` 存在并且是一个整数 `int`(永远不会是 `None`)。 diff --git a/docs/zh/docs/tutorial/static-files.md b/docs/zh/docs/tutorial/static-files.md index 5b2f920b2..1f4ebae9a 100644 --- a/docs/zh/docs/tutorial/static-files.md +++ b/docs/zh/docs/tutorial/static-files.md @@ -7,9 +7,9 @@ * 导入 `StaticFiles`。 * 将一个 `StaticFiles()` 实例“挂载”(Mount)到指定路径。 -{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} -/// note | 注意 +/// note | 技术细节 你也可以用 `from starlette.staticfiles import StaticFiles`。 diff --git a/docs/zh/docs/tutorial/testing.md b/docs/zh/docs/tutorial/testing.md index 1aff9f799..7eb32f19e 100644 --- a/docs/zh/docs/tutorial/testing.md +++ b/docs/zh/docs/tutorial/testing.md @@ -30,7 +30,7 @@ $ pip install httpx 为你需要检查的地方用标准的Python表达式写个简单的 `assert` 语句(重申,标准的`pytest`)。 -{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} /// tip | 提示 @@ -76,7 +76,7 @@ $ pip install httpx 在 `main.py` 文件中你有一个 **FastAPI** app: -{* ../../docs_src/app_testing/app_a_py39/main.py *} +{* ../../docs_src/app_testing/app_a_py310/main.py *} ### 测试文件 { #testing-file } @@ -92,7 +92,7 @@ $ pip install httpx 因为这文件在同一个包中,所以你可以通过相对导入从 `main` 模块(`main.py`)导入`app`对象: -{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} ...然后测试代码和之前一样的。 diff --git a/docs/zh/docs/virtual-environments.md b/docs/zh/docs/virtual-environments.md index 29e272b59..60fb9e23f 100644 --- a/docs/zh/docs/virtual-environments.md +++ b/docs/zh/docs/virtual-environments.md @@ -30,7 +30,7 @@ 首先,为你的工程创建一个目录。 -我 (指原作者 —— 译者注) 通常会在我的主目录下创建一个名为 `code` 的目录。 +我通常会在我的主目录下创建一个名为 `code` 的目录。 在这个目录下,我再为每个工程创建一个目录。 @@ -53,7 +53,7 @@ $ cd awesome-project ## 创建一个虚拟环境 { #create-a-virtual-environment } -在开始一个 Python 工程的**第一时间**,**在你的工程内部**创建一个虚拟环境。 +在开始一个 Python 工程的**第一时间**,**在你的工程内部**创建一个虚拟环境。 /// tip | 提示 @@ -316,7 +316,7 @@ $ echo "*" > .venv/.gitignore ### 直接安装包 { #install-packages-directly } -如果你急于安装,不想使用文件来声明工程的软件包依赖,您可以直接安装它们。 +如果你急于安装,不想使用文件来声明工程的软件包依赖,你可以直接安装它们。 /// tip | 提示 @@ -412,7 +412,7 @@ Hello World ## 配置编辑器 { #configure-your-editor } -你可能会用到编辑器(即 IDE —— 译者注),请确保配置它使用与你创建的相同的虚拟环境(它可能会自动检测到),以便你可以获得自动补全和内联错误提示。 +你可能会用到编辑器,请确保配置它使用与你创建的相同的虚拟环境(它可能会自动检测到),以便你可以获得自动补全和内联错误提示。 例如: @@ -437,7 +437,7 @@ $ deactivate
-这样,当你运行 `python` 时,它不会尝试从安装了软件包的虚拟环境中运行。(即,它将不再会尝试从虚拟环境中运行,也不会使用其中安装的软件包。—— 译者注) +这样,当你运行 `python` 时,它不会尝试从那个虚拟环境及其已安装的软件包中运行。 ## 开始工作 { #ready-to-work } From 7dbdb11fb08fc6d379e591a7d4d65e7125d5aaaa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Feb 2026 13:38:24 +0000 Subject: [PATCH 123/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ae5bef77f..af7e7f27d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for zh (update-all). PR [#14917](https://github.com/fastapi/fastapi/pull/14917) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for uk (update-all). PR [#14914](https://github.com/fastapi/fastapi/pull/14914) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for tr (update-all). PR [#14913](https://github.com/fastapi/fastapi/pull/14913) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for ru (update-outdated). PR [#14909](https://github.com/fastapi/fastapi/pull/14909) by [@YuriiMotov](https://github.com/YuriiMotov). From dd50de120582d1fe56cdb51ef0808ffc918c49d5 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:41:41 +0100 Subject: [PATCH 124/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20es=20(update-all=20and=20add-missing)=20(#14911)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all * 🎨 Auto format --------- Co-authored-by: github-actions[bot] --- docs/es/docs/advanced/additional-responses.md | 4 +- .../es/docs/advanced/advanced-dependencies.md | 8 +- .../es/docs/advanced/advanced-python-types.md | 61 ++++++ docs/es/docs/advanced/async-tests.md | 8 +- docs/es/docs/advanced/behind-a-proxy.md | 12 +- docs/es/docs/advanced/custom-response.md | 34 ++-- docs/es/docs/advanced/dataclasses.md | 2 +- docs/es/docs/advanced/events.md | 12 +- docs/es/docs/advanced/generate-clients.md | 10 +- docs/es/docs/advanced/middleware.md | 8 +- docs/es/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 16 +- .../advanced/response-change-status-code.md | 4 +- docs/es/docs/advanced/response-cookies.md | 4 +- docs/es/docs/advanced/response-directly.md | 2 +- docs/es/docs/advanced/response-headers.md | 4 +- .../docs/advanced/security/http-basic-auth.md | 6 +- docs/es/docs/advanced/settings.md | 20 +- docs/es/docs/advanced/sub-applications.md | 6 +- docs/es/docs/advanced/templates.md | 4 +- docs/es/docs/advanced/testing-dependencies.md | 6 +- docs/es/docs/advanced/testing-events.md | 4 +- docs/es/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/es/docs/advanced/websockets.md | 8 +- docs/es/docs/advanced/wsgi.md | 2 +- docs/es/docs/alternatives.md | 16 +- docs/es/docs/async.md | 10 +- docs/es/docs/deployment/cloud.md | 2 +- docs/es/docs/deployment/concepts.md | 2 +- docs/es/docs/deployment/docker.md | 10 +- docs/es/docs/deployment/fastapicloud.md | 4 +- docs/es/docs/deployment/https.md | 2 +- docs/es/docs/deployment/manually.md | 2 +- docs/es/docs/features.md | 16 +- docs/es/docs/help-fastapi.md | 6 +- .../authentication-error-status-code.md | 2 +- docs/es/docs/how-to/conditional-openapi.md | 2 +- docs/es/docs/how-to/configure-swagger-ui.md | 12 +- docs/es/docs/how-to/custom-docs-ui-assets.md | 14 +- docs/es/docs/how-to/extending-openapi.md | 10 +- docs/es/docs/how-to/graphql.md | 2 +- .../docs/how-to/separate-openapi-schemas.md | 2 +- docs/es/docs/how-to/testing-database.md | 4 +- docs/es/docs/index.md | 10 +- docs/es/docs/project-generation.md | 2 +- docs/es/docs/python-types.md | 188 ++++-------------- docs/es/docs/translation-banner.md | 11 + docs/es/docs/tutorial/background-tasks.md | 6 +- docs/es/docs/tutorial/bigger-applications.md | 26 +-- docs/es/docs/tutorial/body-multiple-params.md | 6 - docs/es/docs/tutorial/body-nested-models.md | 4 +- docs/es/docs/tutorial/body.md | 6 +- docs/es/docs/tutorial/cookie-param-models.md | 8 +- docs/es/docs/tutorial/cors.md | 3 +- docs/es/docs/tutorial/debugging.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +-- ...pendencies-in-path-operation-decorators.md | 8 +- .../dependencies/dependencies-with-yield.md | 25 ++- .../dependencies/global-dependencies.md | 2 +- docs/es/docs/tutorial/dependencies/index.md | 2 +- .../tutorial/dependencies/sub-dependencies.md | 8 +- docs/es/docs/tutorial/encoder.md | 2 +- docs/es/docs/tutorial/extra-models.md | 8 +- docs/es/docs/tutorial/first-steps.md | 18 +- docs/es/docs/tutorial/handling-errors.md | 16 +- docs/es/docs/tutorial/metadata.md | 12 +- docs/es/docs/tutorial/middleware.md | 4 +- .../tutorial/path-operation-configuration.md | 8 +- .../path-params-numeric-validations.md | 26 +-- docs/es/docs/tutorial/path-params.md | 28 +-- .../tutorial/query-params-str-validations.md | 78 +++----- docs/es/docs/tutorial/query-params.md | 6 +- docs/es/docs/tutorial/request-files.md | 16 +- docs/es/docs/tutorial/request-form-models.md | 4 +- .../docs/tutorial/request-forms-and-files.md | 4 +- docs/es/docs/tutorial/request-forms.md | 8 +- docs/es/docs/tutorial/response-model.md | 8 +- docs/es/docs/tutorial/response-status-code.md | 8 +- docs/es/docs/tutorial/schema-extra-example.md | 2 +- docs/es/docs/tutorial/security/first-steps.md | 6 +- .../tutorial/security/get-current-user.md | 2 +- docs/es/docs/tutorial/security/oauth2-jwt.md | 12 +- docs/es/docs/tutorial/sql-databases.md | 4 +- docs/es/docs/tutorial/static-files.md | 2 +- docs/es/docs/tutorial/testing.md | 6 +- docs/es/docs/virtual-environments.md | 8 +- 87 files changed, 468 insertions(+), 538 deletions(-) create mode 100644 docs/es/docs/advanced/advanced-python-types.md create mode 100644 docs/es/docs/translation-banner.md diff --git a/docs/es/docs/advanced/additional-responses.md b/docs/es/docs/advanced/additional-responses.md index d0baa97a4..030f8dcc5 100644 --- a/docs/es/docs/advanced/additional-responses.md +++ b/docs/es/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ Cada uno de esos `dict`s de response puede tener una clave `model`, conteniendo Por ejemplo, para declarar otro response con un código de estado `404` y un modelo Pydantic `Message`, puedes escribir: -{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} /// note | Nota @@ -203,7 +203,7 @@ Por ejemplo, puedes declarar un response con un código de estado `404` que usa Y un response con un código de estado `200` que usa tu `response_model`, pero incluye un `example` personalizado: -{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} Todo se combinará e incluirá en tu OpenAPI, y se mostrará en la documentación de la API: diff --git a/docs/es/docs/advanced/advanced-dependencies.md b/docs/es/docs/advanced/advanced-dependencies.md index 622a2caa2..81d8d19bb 100644 --- a/docs/es/docs/advanced/advanced-dependencies.md +++ b/docs/es/docs/advanced/advanced-dependencies.md @@ -18,7 +18,7 @@ No la clase en sí (que ya es un callable), sino una instance de esa clase. Para hacer eso, declaramos un método `__call__`: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} En este caso, este `__call__` es lo que **FastAPI** usará para comprobar parámetros adicionales y sub-dependencias, y es lo que llamará para pasar un valor al parámetro en tu *path operation function* más adelante. @@ -26,7 +26,7 @@ En este caso, este `__call__` es lo que **FastAPI** usará para comprobar parám Y ahora, podemos usar `__init__` para declarar los parámetros de la instance que podemos usar para "parametrizar" la dependencia: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} En este caso, **FastAPI** nunca tocará ni se preocupará por `__init__`, lo usaremos directamente en nuestro código. @@ -34,7 +34,7 @@ En este caso, **FastAPI** nunca tocará ni se preocupará por `__init__`, lo usa Podríamos crear una instance de esta clase con: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} Y de esa manera podemos "parametrizar" nuestra dependencia, que ahora tiene `"bar"` dentro de ella, como el atributo `checker.fixed_content`. @@ -50,7 +50,7 @@ checker(q="somequery") ...y pasará lo que eso retorne como el valor de la dependencia en nuestra *path operation function* como el parámetro `fixed_content_included`: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} /// tip | Consejo diff --git a/docs/es/docs/advanced/advanced-python-types.md b/docs/es/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..a18c5d6cc --- /dev/null +++ b/docs/es/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# Tipos avanzados de Python { #advanced-python-types } + +Aquí tienes algunas ideas adicionales que podrían ser útiles al trabajar con tipos de Python. + +## Usar `Union` u `Optional` { #using-union-or-optional } + +Si por alguna razón tu código no puede usar `|`, por ejemplo si no está en una anotación de tipos sino en algo como `response_model=`, en lugar de usar la barra vertical (`|`) puedes usar `Union` de `typing`. + +Por ejemplo, podrías declarar que algo podría ser un `str` o `None`: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +`typing` también tiene un atajo para declarar que algo podría ser `None`, con `Optional`. + +Aquí va un Consejo desde mi punto de vista muy subjetivo: + +* 🚨 Evita usar `Optional[SomeType]` +* En su lugar ✨ **usa `Union[SomeType, None]`** ✨. + +Ambas son equivalentes y por debajo son lo mismo, pero recomendaría `Union` en lugar de `Optional` porque la palabra "**optional**" parecería implicar que el valor es opcional, y en realidad significa "puede ser `None`", incluso si no es opcional y sigue siendo requerido. + +Creo que `Union[SomeType, None]` es más explícito respecto a lo que significa. + +Se trata solo de palabras y nombres. Pero esas palabras pueden afectar cómo tú y tu equipo piensan sobre el código. + +Como ejemplo, tomemos esta función: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +El parámetro `name` está definido como `Optional[str]`, pero **no es opcional**, no puedes llamar a la función sin el parámetro: + +```Python +say_hi() # ¡Oh, no, esto lanza un error! 😱 +``` + +El parámetro `name` **sigue siendo requerido** (no es *opcional*) porque no tiene un valor por defecto. Aun así, `name` acepta `None` como valor: + +```Python +say_hi(name=None) # Esto funciona, None es válido 🎉 +``` + +La buena noticia es que, en la mayoría de los casos, podrás simplemente usar `|` para definir uniones de tipos: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +Así que, normalmente no tienes que preocuparte por nombres como `Optional` y `Union`. 😎 diff --git a/docs/es/docs/advanced/async-tests.md b/docs/es/docs/advanced/async-tests.md index 4627e9bd1..3485536ce 100644 --- a/docs/es/docs/advanced/async-tests.md +++ b/docs/es/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ Para un ejemplo simple, consideremos una estructura de archivos similar a la des El archivo `main.py` tendría: -{* ../../docs_src/async_tests/app_a_py39/main.py *} +{* ../../docs_src/async_tests/app_a_py310/main.py *} El archivo `test_main.py` tendría los tests para `main.py`, podría verse así ahora: -{* ../../docs_src/async_tests/app_a_py39/test_main.py *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} ## Ejecútalo { #run-it } @@ -56,7 +56,7 @@ $ pytest El marcador `@pytest.mark.anyio` le dice a pytest que esta función de test debe ser llamada asíncronamente: -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} /// tip | Consejo @@ -66,7 +66,7 @@ Nota que la función de test ahora es `async def` en lugar de solo `def` como an Luego podemos crear un `AsyncClient` con la app y enviar requests asíncronos a ella, usando `await`. -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} Esto es equivalente a: diff --git a/docs/es/docs/advanced/behind-a-proxy.md b/docs/es/docs/advanced/behind-a-proxy.md index f81c16ee8..40729ee03 100644 --- a/docs/es/docs/advanced/behind-a-proxy.md +++ b/docs/es/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" Por ejemplo, digamos que defines una *path operation* `/items/`: -{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} Si el cliente intenta ir a `/items`, por defecto, sería redirigido a `/items/`. @@ -115,7 +115,7 @@ En este caso, el path original `/app` realmente sería servido en `/api/v1/app`. Aunque todo tu código esté escrito asumiendo que solo existe `/app`. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} Y el proxy estaría **"eliminando"** el **prefijo del path** sobre la marcha antes de transmitir el request al servidor de aplicaciones (probablemente Uvicorn a través de FastAPI CLI), manteniendo a tu aplicación convencida de que está siendo servida en `/app`, así que no tienes que actualizar todo tu código para incluir el prefijo `/api/v1`. @@ -193,7 +193,7 @@ Puedes obtener el `root_path` actual utilizado por tu aplicación para cada requ Aquí lo estamos incluyendo en el mensaje solo con fines de demostración. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} Luego, si inicias Uvicorn con: @@ -220,7 +220,7 @@ El response sería algo como: Alternativamente, si no tienes una forma de proporcionar una opción de línea de comandos como `--root-path` o su equivalente, puedes configurar el parámetro `root_path` al crear tu app de FastAPI: -{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} Pasar el `root_path` a `FastAPI` sería el equivalente a pasar la opción de línea de comandos `--root-path` a Uvicorn o Hypercorn. @@ -400,7 +400,7 @@ Si pasas una lista personalizada de `servers` y hay un `root_path` (porque tu AP Por ejemplo: -{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} Generará un esquema de OpenAPI como: @@ -455,7 +455,7 @@ Si no especificas el parámetro `servers` y `root_path` es igual a `/`, la propi Si no quieres que **FastAPI** incluya un server automático usando el `root_path`, puedes usar el parámetro `root_path_in_servers=False`: -{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} y entonces no lo incluirá en el esquema de OpenAPI. diff --git a/docs/es/docs/advanced/custom-response.md b/docs/es/docs/advanced/custom-response.md index 0884c41a7..a58f290d6 100644 --- a/docs/es/docs/advanced/custom-response.md +++ b/docs/es/docs/advanced/custom-response.md @@ -30,7 +30,7 @@ Esto se debe a que, por defecto, FastAPI inspeccionará cada elemento dentro y s Pero si estás seguro de que el contenido que estás devolviendo es **serializable con JSON**, puedes pasarlo directamente a la clase de response y evitar la sobrecarga extra que FastAPI tendría al pasar tu contenido de retorno a través de `jsonable_encoder` antes de pasarlo a la clase de response. -{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} /// info | Información @@ -55,7 +55,7 @@ Para devolver un response con HTML directamente desde **FastAPI**, usa `HTMLResp * Importa `HTMLResponse`. * Pasa `HTMLResponse` como parámetro `response_class` de tu *path operation decorator*. -{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} /// info | Información @@ -73,7 +73,7 @@ Como se ve en [Devolver una Response directamente](response-directly.md){.intern El mismo ejemplo de arriba, devolviendo una `HTMLResponse`, podría verse así: -{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} /// warning | Advertencia @@ -97,7 +97,7 @@ El `response_class` solo se usará para documentar el OpenAPI *path operation*, Por ejemplo, podría ser algo así: -{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} En este ejemplo, la función `generate_html_response()` ya genera y devuelve una `Response` en lugar de devolver el HTML en un `str`. @@ -136,7 +136,7 @@ Acepta los siguientes parámetros: FastAPI (de hecho Starlette) incluirá automáticamente un header Content-Length. También incluirá un header Content-Type, basado en el `media_type` y añadiendo un conjunto de caracteres para tipos de texto. -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ### `HTMLResponse` { #htmlresponse } @@ -146,7 +146,7 @@ Toma algún texto o bytes y devuelve un response HTML, como leíste arriba. Toma algún texto o bytes y devuelve un response de texto plano. -{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} ### `JSONResponse` { #jsonresponse } @@ -180,7 +180,7 @@ Esto requiere instalar `ujson`, por ejemplo, con `pip install ujson`. /// -{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} /// tip | Consejo @@ -194,15 +194,15 @@ Devuelve una redirección HTTP. Usa un código de estado 307 (Redirección Tempo Puedes devolver un `RedirectResponse` directamente: -{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} --- O puedes usarlo en el parámetro `response_class`: -{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} -Si haces eso, entonces puedes devolver la URL directamente desde tu *path operation function*. +Si haces eso, entonces puedes devolver la URL directamente desde tu *path operation* function. En este caso, el `status_code` utilizado será el por defecto para `RedirectResponse`, que es `307`. @@ -210,13 +210,13 @@ En este caso, el `status_code` utilizado será el por defecto para `RedirectResp También puedes usar el parámetro `status_code` combinado con el parámetro `response_class`: -{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} ### `StreamingResponse` { #streamingresponse } Toma un generador `async` o un generador/iterador normal y transmite el cuerpo del response. -{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} #### Usando `StreamingResponse` con objetos similares a archivos { #using-streamingresponse-with-file-like-objects } @@ -226,7 +226,7 @@ De esa manera, no tienes que leerlo todo primero en memoria, y puedes pasar esa Esto incluye muchos paquetes para interactuar con almacenamiento en la nube, procesamiento de video y otros. -{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} 1. Esta es la función generadora. Es una "función generadora" porque contiene declaraciones `yield` dentro. 2. Al usar un bloque `with`, nos aseguramos de que el objeto similar a un archivo se cierre después de que la función generadora termine. Así, después de que termina de enviar el response. @@ -255,11 +255,11 @@ Toma un conjunto diferente de argumentos para crear un instance que los otros ti Los responses de archivos incluirán los headers apropiados `Content-Length`, `Last-Modified` y `ETag`. -{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} También puedes usar el parámetro `response_class`: -{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} En este caso, puedes devolver la path del archivo directamente desde tu *path operation* function. @@ -273,7 +273,7 @@ Digamos que quieres que devuelva JSON con sangría y formato, por lo que quieres Podrías crear un `CustomORJSONResponse`. Lo principal que tienes que hacer es crear un método `Response.render(content)` que devuelva el contenido como `bytes`: -{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} Ahora en lugar de devolver: @@ -299,7 +299,7 @@ El parámetro que define esto es `default_response_class`. En el ejemplo a continuación, **FastAPI** usará `ORJSONResponse` por defecto, en todas las *path operations*, en lugar de `JSONResponse`. -{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} /// tip | Consejo diff --git a/docs/es/docs/advanced/dataclasses.md b/docs/es/docs/advanced/dataclasses.md index 3a07482ad..d586d3a27 100644 --- a/docs/es/docs/advanced/dataclasses.md +++ b/docs/es/docs/advanced/dataclasses.md @@ -64,7 +64,7 @@ En ese caso, simplemente puedes intercambiar los `dataclasses` estándar con `py 6. Aquí estamos regresando un diccionario que contiene `items`, que es una lista de dataclasses. - FastAPI todavía es capaz de serializar los datos a JSON. + FastAPI todavía es capaz de serializar los datos a JSON. 7. Aquí el `response_model` está usando una anotación de tipo de una lista de dataclasses `Author`. diff --git a/docs/es/docs/advanced/events.md b/docs/es/docs/advanced/events.md index c2002a6f5..4adb464d3 100644 --- a/docs/es/docs/advanced/events.md +++ b/docs/es/docs/advanced/events.md @@ -30,7 +30,7 @@ Comencemos con un ejemplo y luego veámoslo en detalle. Creamos una función asíncrona `lifespan()` con `yield` así: -{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} Aquí estamos simulando la operación costosa de *startup* de cargar el modelo poniendo la función del (falso) modelo en el diccionario con modelos de machine learning antes del `yield`. Este código será ejecutado **antes** de que la aplicación **comience a tomar requests**, durante el *startup*. @@ -48,7 +48,7 @@ Quizás necesites iniciar una nueva versión, o simplemente te cansaste de ejecu Lo primero que hay que notar es que estamos definiendo una función asíncrona con `yield`. Esto es muy similar a las Dependencias con `yield`. -{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} La primera parte de la función, antes del `yield`, será ejecutada **antes** de que la aplicación comience. @@ -60,7 +60,7 @@ Si revisas, la función está decorada con un `@asynccontextmanager`. Eso convierte a la función en algo llamado un "**async context manager**". -{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} Un **context manager** en Python es algo que puedes usar en un statement `with`, por ejemplo, `open()` puede ser usado como un context manager: @@ -82,7 +82,7 @@ En nuestro ejemplo de código arriba, no lo usamos directamente, pero se lo pasa El parámetro `lifespan` de la app de `FastAPI` toma un **async context manager**, por lo que podemos pasar nuestro nuevo `lifespan` async context manager a él. -{* ../../docs_src/events/tutorial003_py39.py hl[22] *} +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} ## Eventos Alternativos (obsoleto) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ Estas funciones pueden ser declaradas con `async def` o `def` normal. Para añadir una función que debería ejecutarse antes de que la aplicación inicie, declárala con el evento `"startup"`: -{* ../../docs_src/events/tutorial001_py39.py hl[8] *} +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} En este caso, la función manejadora del evento `startup` inicializará los ítems de la "base de datos" (solo un `dict`) con algunos valores. @@ -116,7 +116,7 @@ Y tu aplicación no comenzará a recibir requests hasta que todos los manejadore Para añadir una función que debería ejecutarse cuando la aplicación se esté cerrando, declárala con el evento `"shutdown"`: -{* ../../docs_src/events/tutorial002_py39.py hl[6] *} +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} Aquí, la función manejadora del evento `shutdown` escribirá una línea de texto `"Application shutdown"` a un archivo `log.txt`. diff --git a/docs/es/docs/advanced/generate-clients.md b/docs/es/docs/advanced/generate-clients.md index daf6cefed..a079c41aa 100644 --- a/docs/es/docs/advanced/generate-clients.md +++ b/docs/es/docs/advanced/generate-clients.md @@ -2,7 +2,7 @@ Como **FastAPI** está basado en la especificación **OpenAPI**, sus APIs se pueden describir en un formato estándar que muchas herramientas entienden. -Esto facilita generar **documentación** actualizada, paquetes de cliente (**SDKs**) en múltiples lenguajes y **escribir pruebas** o **flujos de automatización** que se mantengan sincronizados con tu código. +Esto facilita generar **documentación** actualizada, paquetes de cliente (**SDKs**) en múltiples lenguajes y **escribir pruebas** o **flujos de automatización** que se mantengan sincronizados con tu código. En esta guía, aprenderás a generar un **SDK de TypeScript** para tu backend con FastAPI. @@ -40,7 +40,7 @@ Algunas de estas soluciones también pueden ser open source u ofrecer niveles gr Empecemos con una aplicación simple de FastAPI: -{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} Nota que las *path operations* definen los modelos que usan para el payload del request y el payload del response, usando los modelos `Item` y `ResponseMessage`. @@ -98,7 +98,7 @@ En muchos casos tu app de FastAPI será más grande, y probablemente usarás tag Por ejemplo, podrías tener una sección para **items** y otra sección para **users**, y podrían estar separadas por tags: -{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} ### Genera un Cliente TypeScript con tags { #generate-a-typescript-client-with-tags } @@ -145,7 +145,7 @@ Por ejemplo, aquí está usando el primer tag (probablemente tendrás solo un ta Puedes entonces pasar esa función personalizada a **FastAPI** como el parámetro `generate_unique_id_function`: -{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} ### Genera un Cliente TypeScript con operation IDs personalizados { #generate-a-typescript-client-with-custom-operation-ids } @@ -167,7 +167,7 @@ Pero para el cliente generado podríamos **modificar** los operation IDs de Open Podríamos descargar el JSON de OpenAPI a un archivo `openapi.json` y luego podríamos **remover ese tag prefijado** con un script como este: -{* ../../docs_src/generate_clients/tutorial004_py39.py *} +{* ../../docs_src/generate_clients/tutorial004_py310.py *} //// tab | Node.js diff --git a/docs/es/docs/advanced/middleware.md b/docs/es/docs/advanced/middleware.md index 7eead8ae1..ed582c465 100644 --- a/docs/es/docs/advanced/middleware.md +++ b/docs/es/docs/advanced/middleware.md @@ -8,7 +8,7 @@ En esta sección veremos cómo usar otros middlewares. ## Agregando middlewares ASGI { #adding-asgi-middlewares } -Como **FastAPI** está basado en Starlette e implementa la especificación ASGI, puedes usar cualquier middleware ASGI. +Como **FastAPI** está basado en Starlette e implementa la especificación ASGI, puedes usar cualquier middleware ASGI. Un middleware no tiene que estar hecho para FastAPI o Starlette para funcionar, siempre que siga la especificación ASGI. @@ -57,13 +57,13 @@ Impone que todas las requests entrantes deben ser `https` o `wss`. Cualquier request entrante a `http` o `ws` será redirigida al esquema seguro. -{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} ## `TrustedHostMiddleware` { #trustedhostmiddleware } Impone que todas las requests entrantes tengan correctamente configurado el header `Host`, para proteger contra ataques de HTTP Host Header. -{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} Se soportan los siguientes argumentos: @@ -78,7 +78,7 @@ Maneja responses GZip para cualquier request que incluya `"gzip"` en el header ` El middleware manejará tanto responses estándar como en streaming. -{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} Se soportan los siguientes argumentos: diff --git a/docs/es/docs/advanced/openapi-webhooks.md b/docs/es/docs/advanced/openapi-webhooks.md index c358a1400..4f657ad53 100644 --- a/docs/es/docs/advanced/openapi-webhooks.md +++ b/docs/es/docs/advanced/openapi-webhooks.md @@ -32,7 +32,7 @@ Los webhooks están disponibles en OpenAPI 3.1.0 y superiores, soportados por Fa Cuando creas una aplicación de **FastAPI**, hay un atributo `webhooks` que puedes usar para definir *webhooks*, de la misma manera que definirías *path operations*, por ejemplo con `@app.webhooks.post()`. -{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} Los webhooks que defines terminarán en el esquema de **OpenAPI** y en la interfaz automática de **documentación**. diff --git a/docs/es/docs/advanced/path-operation-advanced-configuration.md b/docs/es/docs/advanced/path-operation-advanced-configuration.md index ea58a300a..0ba586c1c 100644 --- a/docs/es/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/es/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ Puedes establecer el `operationId` de OpenAPI para ser usado en tu *path operati Tendrías que asegurarte de que sea único para cada operación. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} ### Usar el nombre de la *path operation function* como el operationId { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ Si quieres usar los nombres de las funciones de tus APIs como `operationId`s, pu Deberías hacerlo después de agregar todas tus *path operations*. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} /// tip | Consejo @@ -40,7 +40,7 @@ Incluso si están en diferentes módulos (archivos de Python). Para excluir una *path operation* del esquema OpenAPI generado (y por lo tanto, de los sistemas de documentación automática), utiliza el parámetro `include_in_schema` y configúralo en `False`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} ## Descripción avanzada desde el docstring { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ Puedes extender el esquema de OpenAPI para una *path operation* usando el parám Este `openapi_extra` puede ser útil, por ejemplo, para declarar [Extensiones de OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): -{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *} Si abres la documentación automática de la API, tu extensión aparecerá en la parte inferior de la *path operation* específica. @@ -139,9 +139,9 @@ Por ejemplo, podrías decidir leer y validar el request con tu propio código, s Podrías hacer eso con `openapi_extra`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *} -En este ejemplo, no declaramos ningún modelo Pydantic. De hecho, el request body ni siquiera se parse como JSON, se lee directamente como `bytes`, y la función `magic_data_reader()` sería la encargada de parsearlo de alguna manera. +En este ejemplo, no declaramos ningún modelo Pydantic. De hecho, el request body ni siquiera es parseado como JSON, se lee directamente como `bytes`, y la función `magic_data_reader()` sería la encargada de parsearlo de alguna manera. Sin embargo, podemos declarar el esquema esperado para el request body. @@ -153,7 +153,7 @@ Y podrías hacer esto incluso si el tipo de datos en el request no es JSON. Por ejemplo, en esta aplicación no usamos la funcionalidad integrada de FastAPI para extraer el JSON Schema de los modelos Pydantic ni la validación automática para JSON. De hecho, estamos declarando el tipo de contenido del request como YAML, no JSON: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *} Sin embargo, aunque no estamos usando la funcionalidad integrada por defecto, aún estamos usando un modelo Pydantic para generar manualmente el JSON Schema para los datos que queremos recibir en YAML. @@ -161,7 +161,7 @@ Luego usamos el request directamente, y extraemos el cuerpo como `bytes`. Esto s Y luego en nuestro código, parseamos ese contenido YAML directamente, y nuevamente estamos usando el mismo modelo Pydantic para validar el contenido YAML: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *} /// tip | Consejo diff --git a/docs/es/docs/advanced/response-change-status-code.md b/docs/es/docs/advanced/response-change-status-code.md index 940f1dd3f..622001291 100644 --- a/docs/es/docs/advanced/response-change-status-code.md +++ b/docs/es/docs/advanced/response-change-status-code.md @@ -20,9 +20,9 @@ Puedes declarar un parámetro de tipo `Response` en tu *path operation function* Y luego puedes establecer el `status_code` en ese objeto de response *temporal*. -{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *} -Y luego puedes devolver cualquier objeto que necesites, como lo harías normalmente (un `dict`, un modelo de base de datos, etc.). +Y luego puedes devolver cualquier objeto que necesites, como lo harías normalmente (un `dict`, un modelo de base de datos, etc). Y si declaraste un `response_model`, todavía se utilizará para filtrar y convertir el objeto que devolviste. diff --git a/docs/es/docs/advanced/response-cookies.md b/docs/es/docs/advanced/response-cookies.md index 550a5d97a..e451d8939 100644 --- a/docs/es/docs/advanced/response-cookies.md +++ b/docs/es/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ Puedes declarar un parámetro de tipo `Response` en tu *path operation function* Y luego puedes establecer cookies en ese objeto de response *temporal*. -{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *} Y entonces puedes devolver cualquier objeto que necesites, como normalmente lo harías (un `dict`, un modelo de base de datos, etc). @@ -24,7 +24,7 @@ Para hacer eso, puedes crear un response como se describe en [Devolver un Respon Luego establece Cookies en ella, y luego devuélvela: -{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *} /// tip | Consejo diff --git a/docs/es/docs/advanced/response-directly.md b/docs/es/docs/advanced/response-directly.md index 2da4e84e7..b9b1df447 100644 --- a/docs/es/docs/advanced/response-directly.md +++ b/docs/es/docs/advanced/response-directly.md @@ -54,7 +54,7 @@ Digamos que quieres devolver un response en IoT) comunicándose con él. +Fue creado para generar el HTML en el backend, no para crear APIs utilizadas por un frontend moderno (como React, Vue.js y Angular) o por otros sistemas (como dispositivos del IoT) comunicándose con él. ### Django REST Framework { #django-rest-framework } @@ -76,7 +76,7 @@ Aun así, FastAPI se inspiró bastante en Requests. Están, más o menos, en extremos opuestos, complementándose entre sí. -Requests tiene un diseño muy simple e intuitivo, es muy fácil de usar, con valores predeterminados sensatos. Pero al mismo tiempo, es muy poderoso y personalizable. +Requests tiene un diseño muy simple e intuitivo, es muy fácil de usar, con valores por defecto sensatos. Pero al mismo tiempo, es muy poderoso y personalizable. Por eso, como se dice en el sitio web oficial: @@ -102,7 +102,7 @@ Mira las similitudes entre `requests.get(...)` y `@app.get(...)`. * Tener un API simple e intuitivo. * Usar nombres de métodos HTTP (operaciones) directamente, de una manera sencilla e intuitiva. -* Tener valores predeterminados sensatos, pero personalizaciones poderosas. +* Tener valores por defecto sensatos, pero personalizaciones poderosas. /// @@ -137,7 +137,7 @@ Existen varios frameworks REST para Flask, pero después de invertir tiempo y tr ### Marshmallow { #marshmallow } -Una de las principales funcionalidades necesitadas por los sistemas API es la "serialización" de datos, que consiste en tomar datos del código (Python) y convertirlos en algo que pueda ser enviado a través de la red. Por ejemplo, convertir un objeto que contiene datos de una base de datos en un objeto JSON. Convertir objetos `datetime` en strings, etc. +Una de las principales funcionalidades necesitadas por los sistemas API es la "serialización" de datos, que consiste en tomar datos del código (Python) y convertirlos en algo que pueda ser enviado a través de la red. Por ejemplo, convertir un objeto que contiene datos de una base de datos en un objeto JSON. Convertir objetos `datetime` en strings, etc. Otra gran funcionalidad necesaria por las APIs es la validación de datos, asegurarse de que los datos sean válidos, dados ciertos parámetros. Por ejemplo, que algún campo sea un `int`, y no algún string aleatorio. Esto es especialmente útil para los datos entrantes. @@ -145,7 +145,7 @@ Sin un sistema de validación de datos, tendrías que hacer todas las comprobaci Estas funcionalidades son para lo que fue creado Marshmallow. Es un gran paquete, y lo he usado mucho antes. -Pero fue creado antes de que existieran las anotaciones de tipos en Python. Así que, para definir cada esquema necesitas usar utilidades y clases específicas proporcionadas por Marshmallow. +Pero fue creado antes de que existieran las anotaciones de tipos en Python. Así que, para definir cada esquema necesitas usar utilidades y clases específicas proporcionadas por Marshmallow. /// check | Inspiró a **FastAPI** a @@ -155,7 +155,7 @@ Usar código para definir "esquemas" que proporcionen tipos de datos y validaci ### Webargs { #webargs } -Otra gran funcionalidad requerida por las APIs es el parse de datos de las requests entrantes. +Otra gran funcionalidad requerida por las APIs es el parsing de datos de las requests entrantes. Webargs es una herramienta que fue creada para proporcionar esa funcionalidad sobre varios frameworks, incluido Flask. @@ -177,7 +177,7 @@ Tener validación automática de datos entrantes en una request. ### APISpec { #apispec } -Marshmallow y Webargs proporcionan validación, parse y serialización como plug-ins. +Marshmallow y Webargs proporcionan validación, parsing y serialización como plug-ins. Pero la documentación todavía falta. Entonces APISpec fue creado. @@ -419,7 +419,7 @@ Manejar toda la validación de datos, serialización de datos y documentación a ### Starlette { #starlette } -Starlette es un framework/toolkit ASGI liviano, ideal para construir servicios asyncio de alto rendimiento. +Starlette es un framework/toolkit ASGI liviano, ideal para construir servicios asyncio de alto rendimiento. Es muy simple e intuitivo. Está diseñado para ser fácilmente extensible y tener componentes modulares. diff --git a/docs/es/docs/async.md b/docs/es/docs/async.md index a1c8f0fb9..a06d3e979 100644 --- a/docs/es/docs/async.md +++ b/docs/es/docs/async.md @@ -4,7 +4,7 @@ Detalles sobre la sintaxis `async def` para *path operation functions* y algunos ## ¿Con prisa? { #in-a-hurry } -TL;DR: +TL;DR: Si estás usando paquetes de terceros que te dicen que los llames con `await`, como: @@ -74,7 +74,7 @@ Luego la computadora / programa 🤖 volverá cada vez que tenga una oportunidad Después, 🤖 toma la primera tarea que termine (digamos, nuestro "archivo-lento" 📝) y continúa con lo que tenía que hacer con ella. -Ese "esperar otra cosa" normalmente se refiere a las operaciones de I/O que son relativamente "lentas" (comparadas con la velocidad del procesador y la memoria RAM), como esperar: +Ese "esperar otra cosa" normalmente se refiere a las operaciones de I/O que son relativamente "lentas" (comparadas con la velocidad del procesador y la memoria RAM), como esperar: * que los datos del cliente se envíen a través de la red * que los datos enviados por tu programa sean recibidos por el cliente a través de la red @@ -85,7 +85,7 @@ Ese "esperar otra cosa" normalmente se refiere a las operaciones de I/O, las llaman operaciones "I/O bound". +Como el tiempo de ejecución se consume principalmente esperando operaciones de I/O, las llaman operaciones "I/O bound". Se llama "asíncrono" porque la computadora / programa no tiene que estar "sincronizado" con la tarea lenta, esperando el momento exacto en que la tarea termine, sin hacer nada, para poder tomar el resultado de la tarea y continuar el trabajo. @@ -277,7 +277,7 @@ Pero en este caso, si pudieras traer a los 8 ex-cajeros/cocineros/ahora-limpiado En este escenario, cada uno de los limpiadores (incluyéndote) sería un procesador, haciendo su parte del trabajo. -Y como la mayor parte del tiempo de ejecución se dedica al trabajo real (en lugar de esperar), y el trabajo en una computadora lo realiza una CPU, llaman a estos problemas "CPU bound". +Y como la mayor parte del tiempo de ejecución se dedica al trabajo real (en lugar de esperar), y el trabajo en una computadora lo realiza una CPU, llaman a estos problemas "CPU bound". --- @@ -417,7 +417,7 @@ Si tienes bastante conocimiento técnico (coroutines, hilos, bloqueo, etc.) y ti Cuando declaras una *path operation function* con `def` normal en lugar de `async def`, se ejecuta en un threadpool externo que luego es esperado, en lugar de ser llamado directamente (ya que bloquearía el servidor). -Si vienes de otro framework async que no funciona de la manera descrita anteriormente y estás acostumbrado a definir funciones de *path operation* solo de cómputo trivial con `def` normal para una pequeña ganancia de rendimiento (alrededor de 100 nanosegundos), ten en cuenta que en **FastAPI** el efecto sería bastante opuesto. En estos casos, es mejor usar `async def` a menos que tus *path operation functions* usen código que realice I/O de bloqueo. +Si vienes de otro framework async que no funciona de la manera descrita anteriormente y estás acostumbrado a definir funciones de *path operation* solo de cómputo trivial con `def` normal para una pequeña ganancia de rendimiento (alrededor de 100 nanosegundos), ten en cuenta que en **FastAPI** el efecto sería bastante opuesto. En estos casos, es mejor usar `async def` a menos que tus *path operation functions* usen código que realice I/O de bloqueo. Aun así, en ambas situaciones, es probable que **FastAPI** [siga siendo más rápida](index.md#performance){.internal-link target=_blank} que (o al menos comparable a) tu framework anterior. diff --git a/docs/es/docs/deployment/cloud.md b/docs/es/docs/deployment/cloud.md index f3c951d98..a3531b97a 100644 --- a/docs/es/docs/deployment/cloud.md +++ b/docs/es/docs/deployment/cloud.md @@ -10,7 +10,7 @@ En la mayoría de los casos, los principales proveedores de nube tienen guías p Simplifica el proceso de **construir**, **desplegar** y **acceder** a una API con un esfuerzo mínimo. -Trae la misma experiencia de desarrollador de construir aplicaciones con FastAPI a desplegarlas en la nube. 🎉 +Trae la misma **experiencia de desarrollador** de construir aplicaciones con FastAPI a **desplegarlas** en la nube. 🎉 FastAPI Cloud es el sponsor principal y proveedor de financiamiento de los proyectos open source *FastAPI and friends*. ✨ diff --git a/docs/es/docs/deployment/concepts.md b/docs/es/docs/deployment/concepts.md index c42ced70b..2ec7af19b 100644 --- a/docs/es/docs/deployment/concepts.md +++ b/docs/es/docs/deployment/concepts.md @@ -318,4 +318,4 @@ Has estado leyendo aquí algunos de los conceptos principales que probablemente Comprender estas ideas y cómo aplicarlas debería darte la intuición necesaria para tomar decisiones al configurar y ajustar tus implementaciones. 🤓 -En las próximas secciones, te daré ejemplos más concretos de posibles estrategias que puedes seguir. 🚀 +En las próximas secciones, te daré más ejemplos concretos de posibles estrategias que puedes seguir. 🚀 diff --git a/docs/es/docs/deployment/docker.md b/docs/es/docs/deployment/docker.md index 9a0b88955..105a5902b 100644 --- a/docs/es/docs/deployment/docker.md +++ b/docs/es/docs/deployment/docker.md @@ -14,7 +14,7 @@ Usar contenedores de Linux tiene varias ventajas, incluyendo **seguridad**, **re Vista previa del Dockerfile 👀 ```Dockerfile -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -166,7 +166,7 @@ Ahora, en el mismo directorio del proyecto, crea un archivo `Dockerfile` con: ```{ .dockerfile .annotate } # (1)! -FROM python:3.9 +FROM python:3.14 # (2)! WORKDIR /code @@ -390,7 +390,7 @@ Si tu FastAPI es un solo archivo, por ejemplo, `main.py` sin un directorio `./ap Entonces solo tendrías que cambiar las rutas correspondientes para copiar el archivo dentro del `Dockerfile`: ```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -454,7 +454,7 @@ Sin usar contenedores, hacer que las aplicaciones se ejecuten al inicio y con re ## Replicación - Número de Procesos { #replication-number-of-processes } -Si tienes un cluster de máquinas con **Kubernetes**, Docker Swarm Mode, Nomad, u otro sistema complejo similar para gestionar contenedores distribuidos en varias máquinas, entonces probablemente querrás manejar la **replicación** a nivel de **cluster** en lugar de usar un **gestor de procesos** (como Uvicorn con workers) en cada contenedor. +Si tienes un clúster de máquinas con **Kubernetes**, Docker Swarm Mode, Nomad, u otro sistema complejo similar para gestionar contenedores distribuidos en varias máquinas, entonces probablemente querrás manejar la **replicación** a nivel de **cluster** en lugar de usar un **gestor de procesos** (como Uvicorn con workers) en cada contenedor. Uno de esos sistemas de gestión de contenedores distribuidos como Kubernetes normalmente tiene alguna forma integrada de manejar la **replicación de contenedores** mientras aún soporta el **load balancing** para las requests entrantes. Todo a nivel de **cluster**. @@ -499,7 +499,7 @@ Por supuesto, hay **casos especiales** donde podrías querer tener **un contened En esos casos, puedes usar la opción de línea de comandos `--workers` para establecer el número de workers que deseas ejecutar: ```{ .dockerfile .annotate } -FROM python:3.9 +FROM python:3.14 WORKDIR /code diff --git a/docs/es/docs/deployment/fastapicloud.md b/docs/es/docs/deployment/fastapicloud.md index af3e7ce68..9763af48c 100644 --- a/docs/es/docs/deployment/fastapicloud.md +++ b/docs/es/docs/deployment/fastapicloud.md @@ -1,6 +1,6 @@ # FastAPI Cloud { #fastapi-cloud } -Puedes desplegar tu app de FastAPI en FastAPI Cloud con un solo comando; ve y únete a la lista de espera si aún no lo has hecho. 🚀 +Puedes desplegar tu app de FastAPI en FastAPI Cloud con **un solo comando**; ve y únete a la lista de espera si aún no lo has hecho. 🚀 ## Iniciar sesión { #login } @@ -20,7 +20,7 @@ You are logged in to FastAPI Cloud 🚀 ## Desplegar { #deploy } -Ahora despliega tu app, con un solo comando: +Ahora despliega tu app, con **un solo comando**:
diff --git a/docs/es/docs/deployment/https.md b/docs/es/docs/deployment/https.md index e62bf8b15..d7a244107 100644 --- a/docs/es/docs/deployment/https.md +++ b/docs/es/docs/deployment/https.md @@ -65,7 +65,7 @@ Aquí tienes un ejemplo de cómo podría ser una API HTTPS, paso a paso, prestan Probablemente todo comenzaría adquiriendo un **nombre de dominio**. Luego, lo configurarías en un servidor DNS (posiblemente tu mismo proveedor de la nube). -Probablemente conseguirías un servidor en la nube (una máquina virtual) o algo similar, y tendría una **dirección IP pública** fija. +Probablemente conseguirías un servidor en la nube (una máquina virtual) o algo similar, y tendría una **dirección IP pública** fija. En el/los servidor(es) DNS configurarías un registro (un "`A record`") para apuntar **tu dominio** a la **dirección IP pública de tu servidor**. diff --git a/docs/es/docs/deployment/manually.md b/docs/es/docs/deployment/manually.md index 50ba79c22..3c597ff69 100644 --- a/docs/es/docs/deployment/manually.md +++ b/docs/es/docs/deployment/manually.md @@ -46,7 +46,7 @@ Podrías usar ese comando, por ejemplo, para iniciar tu app **FastAPI** en un co Vamos a profundizar un poquito en los detalles. -FastAPI usa un estándar para construir frameworks de web y servidores de Python llamado ASGI. FastAPI es un framework web ASGI. +FastAPI usa un estándar para construir frameworks de web y servidores de Python llamado ASGI. FastAPI es un framework web ASGI. Lo principal que necesitas para ejecutar una aplicación **FastAPI** (o cualquier otra aplicación ASGI) en una máquina de servidor remota es un programa de servidor ASGI como **Uvicorn**, que es el que viene por defecto en el comando `fastapi`. diff --git a/docs/es/docs/features.md b/docs/es/docs/features.md index 9902b21fa..947ef312d 100644 --- a/docs/es/docs/features.md +++ b/docs/es/docs/features.md @@ -6,7 +6,7 @@ ### Basado en estándares abiertos { #based-on-open-standards } -* OpenAPI para la creación de APIs, incluyendo declaraciones de path operations, parámetros, request bodies, seguridad, etc. +* OpenAPI para la creación de APIs, incluyendo declaraciones de path operations, parámetros, request bodies, seguridad, etc. * Documentación automática de modelos de datos con JSON Schema (ya que OpenAPI en sí mismo está basado en JSON Schema). * Diseñado alrededor de estos estándares, tras un estudio meticuloso. En lugar de ser una capa adicional. * Esto también permite el uso de **generación de código cliente automática** en muchos idiomas. @@ -105,8 +105,8 @@ Pero por defecto, todo **"simplemente funciona"**. * Validación para la mayoría (¿o todas?) de los **tipos de datos** de Python, incluyendo: * Objetos JSON (`dict`). - * Array JSON (`list`) definiendo tipos de elementos. - * Campos de cadena de caracteres (`str`), definiendo longitudes mínimas y máximas. + * array JSON (`list`) definiendo tipos de elementos. + * Campos de string (`str`), definiendo longitudes mínimas y máximas. * Números (`int`, `float`) con valores mínimos y máximos, etc. * Validación para tipos más exóticos, como: @@ -136,7 +136,7 @@ Todo construido como herramientas y componentes reutilizables que son fáciles d ### Inyección de dependencias { #dependency-injection } -FastAPI incluye un sistema de Inyección de Dependencias extremadamente fácil de usar, pero extremadamente potente. +FastAPI incluye un sistema de Inyección de Dependencias extremadamente fácil de usar, pero extremadamente potente. * Incluso las dependencias pueden tener dependencias, creando una jerarquía o **"grafo de dependencias"**. * Todo **manejado automáticamente** por el framework. @@ -153,8 +153,8 @@ Cualquier integración está diseñada para ser tan simple de usar (con dependen ### Probado { #tested } -* 100% de cobertura de tests. -* 100% anotada con tipos code base. +* 100% de cobertura de tests. +* 100% anotada con tipos code base. * Usado en aplicaciones en producción. ## Funcionalidades de Starlette { #starlette-features } @@ -173,7 +173,7 @@ Con **FastAPI** obtienes todas las funcionalidades de **Starlette** (ya que Fast * **CORS**, GZip, archivos estáticos, responses en streaming. * Soporte para **Session y Cookie**. * Cobertura de tests del 100%. -* code base completamente anotada con tipos. +* code base 100% anotada con tipos. ## Funcionalidades de Pydantic { #pydantic-features } @@ -190,7 +190,7 @@ Con **FastAPI** obtienes todas las funcionalidades de **Pydantic** (ya que FastA * **Sin complicaciones**: * Sin micro-lenguaje de definición de esquemas nuevo que aprender. * Si conoces los tipos en Python sabes cómo usar Pydantic. -* Se lleva bien con tu **IDE/linter/cerebro**: +* Se lleva bien con tu **IDE/linter/cerebro**: * Porque las estructuras de datos de pydantic son solo instances de clases que defines; autocompletado, linting, mypy y tu intuición deberían funcionar correctamente con tus datos validados. * Valida **estructuras complejas**: * Uso de modelos jerárquicos de Pydantic, `List` y `Dict` de `typing` de Python, etc. diff --git a/docs/es/docs/help-fastapi.md b/docs/es/docs/help-fastapi.md index cef956224..9b727dab0 100644 --- a/docs/es/docs/help-fastapi.md +++ b/docs/es/docs/help-fastapi.md @@ -110,7 +110,7 @@ En muchos casos solo copiarán un fragmento del código, pero eso no es suficien ### Sugerir soluciones { #suggest-solutions } -* Después de poder entender la pregunta, puedes darles un posible **respuesta**. +* Después de poder entender la pregunta, puedes darles una posible **respuesta**. * En muchos casos, es mejor entender su **problema subyacente o caso de uso**, porque podría haber una mejor manera de resolverlo que lo que están intentando hacer. @@ -247,9 +247,9 @@ Las conversaciones en los sistemas de chat tampoco son tan fácilmente buscables Por otro lado, hay miles de usuarios en los sistemas de chat, por lo que hay muchas posibilidades de que encuentres a alguien con quien hablar allí, casi todo el tiempo. 😄 -## Patrocina al autor { #sponsor-the-author } +## Hazte sponsor del autor { #sponsor-the-author } -Si tu **producto/empresa** depende de o está relacionado con **FastAPI** y quieres llegar a sus usuarios, puedes patrocinar al autor (a mí) a través de GitHub sponsors. Según el nivel, podrías obtener algunos beneficios extra, como una insignia en la documentación. 🎁 +Si tu **producto/empresa** depende de o está relacionado con **FastAPI** y quieres llegar a sus usuarios, puedes hacerte sponsor del autor (de mí) a través de GitHub sponsors. Según el nivel, podrías obtener algunos beneficios extra, como una insignia en la documentación. 🎁 --- diff --git a/docs/es/docs/how-to/authentication-error-status-code.md b/docs/es/docs/how-to/authentication-error-status-code.md index 9fff6c93d..54afcec8c 100644 --- a/docs/es/docs/how-to/authentication-error-status-code.md +++ b/docs/es/docs/how-to/authentication-error-status-code.md @@ -8,7 +8,7 @@ Pero si por alguna razón tus clientes dependen del comportamiento anterior, pue Por ejemplo, puedes crear una subclase de `HTTPBearer` que devuelva un error `403 Forbidden` en lugar del `401 Unauthorized` por defecto: -{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} /// tip | Consejo diff --git a/docs/es/docs/how-to/conditional-openapi.md b/docs/es/docs/how-to/conditional-openapi.md index a06ad9548..671100cf8 100644 --- a/docs/es/docs/how-to/conditional-openapi.md +++ b/docs/es/docs/how-to/conditional-openapi.md @@ -29,7 +29,7 @@ Puedes usar fácilmente las mismas configuraciones de Pydantic para configurar t Por ejemplo: -{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} Aquí declaramos la configuración `openapi_url` con el mismo valor por defecto de `"/openapi.json"`. diff --git a/docs/es/docs/how-to/configure-swagger-ui.md b/docs/es/docs/how-to/configure-swagger-ui.md index b2865d77d..092c31001 100644 --- a/docs/es/docs/how-to/configure-swagger-ui.md +++ b/docs/es/docs/how-to/configure-swagger-ui.md @@ -18,7 +18,7 @@ Sin cambiar la configuración, el resaltado de sintaxis está activado por defec Pero puedes desactivarlo estableciendo `syntaxHighlight` en `False`: -{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} ...y entonces Swagger UI ya no mostrará el resaltado de sintaxis: @@ -28,17 +28,17 @@ Pero puedes desactivarlo estableciendo `syntaxHighlight` en `False`: De la misma manera, podrías configurar el tema del resaltado de sintaxis con la clave `"syntaxHighlight.theme"` (ten en cuenta que tiene un punto en el medio): -{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} Esa configuración cambiaría el tema de color del resaltado de sintaxis: -## Cambiar los parámetros predeterminados de Swagger UI { #change-default-swagger-ui-parameters } +## Cambiar los parámetros por defecto de Swagger UI { #change-default-swagger-ui-parameters } -FastAPI incluye algunos parámetros de configuración predeterminados apropiados para la mayoría de los casos de uso. +FastAPI incluye algunos parámetros de configuración por defecto apropiados para la mayoría de los casos de uso. -Incluye estas configuraciones predeterminadas: +Incluye estas configuraciones por defecto: {* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} @@ -46,7 +46,7 @@ Puedes sobrescribir cualquiera de ellos estableciendo un valor diferente en el a Por ejemplo, para desactivar `deepLinking` podrías pasar estas configuraciones a `swagger_ui_parameters`: -{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} ## Otros parámetros de Swagger UI { #other-swagger-ui-parameters } diff --git a/docs/es/docs/how-to/custom-docs-ui-assets.md b/docs/es/docs/how-to/custom-docs-ui-assets.md index acd3f8d6d..faddab0d8 100644 --- a/docs/es/docs/how-to/custom-docs-ui-assets.md +++ b/docs/es/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ El primer paso es desactivar la documentación automática, ya que por defecto, Para desactivarlos, establece sus URLs en `None` cuando crees tu aplicación de `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} ### Incluye la documentación personalizada { #include-the-custom-docs } @@ -34,7 +34,7 @@ Puedes reutilizar las funciones internas de FastAPI para crear las páginas HTML Y de manera similar para ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} /// tip | Consejo @@ -50,7 +50,7 @@ Swagger UI lo manejará detrás de escena para ti, pero necesita este auxiliar d Ahora, para poder probar que todo funciona, crea una *path operation*: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} ### Pruébalo { #test-it } @@ -118,7 +118,7 @@ Después de eso, tu estructura de archivos podría verse así: * Importa `StaticFiles`. * "Monta" una instance de `StaticFiles()` en un path específico. -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} ### Prueba los archivos estáticos { #test-the-static-files } @@ -144,7 +144,7 @@ Igual que cuando usas un CDN personalizado, el primer paso es desactivar la docu Para desactivarlos, establece sus URLs en `None` cuando crees tu aplicación de `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} ### Incluye la documentación personalizada para archivos estáticos { #include-the-custom-docs-for-static-files } @@ -160,7 +160,7 @@ Nuevamente, puedes reutilizar las funciones internas de FastAPI para crear las p Y de manera similar para ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} /// tip | Consejo @@ -176,7 +176,7 @@ Swagger UI lo manejará detrás de escena para ti, pero necesita este auxiliar d Ahora, para poder probar que todo funciona, crea una *path operation*: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} ### Prueba la UI de Archivos Estáticos { #test-static-files-ui } diff --git a/docs/es/docs/how-to/extending-openapi.md b/docs/es/docs/how-to/extending-openapi.md index 2611b6e1b..d08fae073 100644 --- a/docs/es/docs/how-to/extending-openapi.md +++ b/docs/es/docs/how-to/extending-openapi.md @@ -43,19 +43,19 @@ Por ejemplo, vamos a añadir documentación de Strawberry. diff --git a/docs/es/docs/how-to/separate-openapi-schemas.md b/docs/es/docs/how-to/separate-openapi-schemas.md index 903313599..db9b46ddb 100644 --- a/docs/es/docs/how-to/separate-openapi-schemas.md +++ b/docs/es/docs/how-to/separate-openapi-schemas.md @@ -85,7 +85,7 @@ Probablemente el caso principal para esto es si ya tienes algún código cliente En ese caso, puedes desactivar esta funcionalidad en **FastAPI**, con el parámetro `separate_input_output_schemas=False`. -/// info +/// info | Información El soporte para `separate_input_output_schemas` fue agregado en FastAPI `0.102.0`. 🤓 diff --git a/docs/es/docs/how-to/testing-database.md b/docs/es/docs/how-to/testing-database.md index 2fa260312..0717ea5ff 100644 --- a/docs/es/docs/how-to/testing-database.md +++ b/docs/es/docs/how-to/testing-database.md @@ -1,7 +1,7 @@ -# Probando una Base de Datos { #testing-a-database } +# Escribiendo pruebas para una base de datos { #testing-a-database } Puedes estudiar sobre bases de datos, SQL y SQLModel en la documentación de SQLModel. 🤓 Hay un mini tutorial sobre el uso de SQLModel con FastAPI. ✨ -Ese tutorial incluye una sección sobre cómo probar bases de datos SQL. 😎 +Ese tutorial incluye una sección sobre escribir pruebas para bases de datos SQL. 😎 diff --git a/docs/es/docs/index.md b/docs/es/docs/index.md index ffea0ed54..0544eb9ba 100644 --- a/docs/es/docs/index.md +++ b/docs/es/docs/index.md @@ -40,7 +40,7 @@ Las funcionalidades clave son: * **Rápido**: Muy alto rendimiento, a la par con **NodeJS** y **Go** (gracias a Starlette y Pydantic). [Uno de los frameworks Python más rápidos disponibles](#performance). * **Rápido de programar**: Aumenta la velocidad para desarrollar funcionalidades en aproximadamente un 200% a 300%. * * **Menos bugs**: Reduce en aproximadamente un 40% los errores inducidos por humanos (desarrolladores). * -* **Intuitivo**: Gran soporte para editores. Autocompletado en todas partes. Menos tiempo depurando. +* **Intuitivo**: Gran soporte para editores. Autocompletado en todas partes. Menos tiempo depurando. * **Fácil**: Diseñado para ser fácil de usar y aprender. Menos tiempo leyendo documentación. * **Corto**: Minimiza la duplicación de código. Múltiples funcionalidades desde cada declaración de parámetro. Menos bugs. * **Robusto**: Obtén código listo para producción. Con documentación interactiva automática. @@ -368,7 +368,7 @@ item: Item * Validación de datos: * Errores automáticos y claros cuando los datos son inválidos. * Validación incluso para objetos JSON profundamente anidados. -* Conversión de datos de entrada: de la red a los datos y tipos de Python. Leyendo desde: +* Conversión de datos de entrada: de la red a los datos y tipos de Python. Leyendo desde: * JSON. * Parámetros de path. * Parámetros de query. @@ -376,7 +376,7 @@ item: Item * Headers. * Forms. * Archivos. -* Conversión de datos de salida: convirtiendo de datos y tipos de Python a datos de red (como JSON): +* Conversión de datos de salida: convirtiendo de datos y tipos de Python a datos de red (como JSON): * Convertir tipos de Python (`str`, `int`, `float`, `bool`, `list`, etc). * Objetos `datetime`. * Objetos `UUID`. @@ -439,7 +439,7 @@ Para un ejemplo más completo incluyendo más funcionalidades, ve al Inyección de Dependencias** muy poderoso y fácil de usar. +* Un sistema de **Inyección de Dependencias** muy poderoso y fácil de usar. * Seguridad y autenticación, incluyendo soporte para **OAuth2** con **tokens JWT** y autenticación **HTTP Basic**. * Técnicas más avanzadas (pero igualmente fáciles) para declarar **modelos JSON profundamente anidados** (gracias a Pydantic). * Integración con **GraphQL** usando Strawberry y otros paquetes. @@ -524,7 +524,7 @@ Usadas por Starlette: * httpx - Requerido si deseas usar el `TestClient`. * jinja2 - Requerido si deseas usar la configuración de plantilla por defecto. -* python-multipart - Requerido si deseas soportar "parsing" de forms, con `request.form()`. +* python-multipart - Requerido si deseas soportar form "parsing", con `request.form()`. Usadas por FastAPI: diff --git a/docs/es/docs/project-generation.md b/docs/es/docs/project-generation.md index b4aa11d0d..6d48d0be5 100644 --- a/docs/es/docs/project-generation.md +++ b/docs/es/docs/project-generation.md @@ -6,7 +6,7 @@ Puedes usar esta plantilla para comenzar, ya que incluye gran parte de la config Repositorio de GitHub: Plantilla Full Stack FastAPI -## Plantilla Full Stack FastAPI - Tecnología y Funcionalidades { #full-stack-fastapi-template-technology-stack-and-features } +## Plantilla Full Stack FastAPI - Stack de tecnología y funcionalidades { #full-stack-fastapi-template-technology-stack-and-features } - ⚡ [**FastAPI**](https://fastapi.tiangolo.com/es) para la API del backend en Python. - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) para las interacciones con bases de datos SQL en Python (ORM). diff --git a/docs/es/docs/python-types.md b/docs/es/docs/python-types.md index 60b50a08f..28e2953d3 100644 --- a/docs/es/docs/python-types.md +++ b/docs/es/docs/python-types.md @@ -2,7 +2,7 @@ Python tiene soporte para "anotaciones de tipos" opcionales (también llamadas "type hints"). -Estas **"anotaciones de tipos"** o type hints son una sintaxis especial que permite declarar el tipo de una variable. +Estas **"anotaciones de tipos"** o type hints son una sintaxis especial que permite declarar el tipo de una variable. Al declarar tipos para tus variables, los editores y herramientas te pueden proporcionar un mejor soporte. @@ -22,7 +22,7 @@ Si eres un experto en Python, y ya sabes todo sobre las anotaciones de tipos, sa Comencemos con un ejemplo simple: -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} Llamar a este programa genera: @@ -34,9 +34,9 @@ La función hace lo siguiente: * Toma un `first_name` y `last_name`. * Convierte la primera letra de cada uno a mayúsculas con `title()`. -* Concatena ambos con un espacio en el medio. +* Concatena ambos con un espacio en el medio. -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### Edítalo { #edit-it } @@ -78,7 +78,7 @@ Eso es todo. Esas son las "anotaciones de tipos": -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} Eso no es lo mismo que declarar valores predeterminados como sería con: @@ -106,7 +106,7 @@ Con eso, puedes desplazarte, viendo las opciones, hasta que encuentres la que "t Revisa esta función, ya tiene anotaciones de tipos: -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} Porque el editor conoce los tipos de las variables, no solo obtienes autocompletado, también obtienes chequeo de errores: @@ -114,7 +114,7 @@ Porque el editor conoce los tipos de las variables, no solo obtienes autocomplet Ahora sabes que debes corregirlo, convertir `age` a un string con `str(age)`: -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} ## Declaración de tipos { #declaring-types } @@ -133,29 +133,32 @@ Puedes usar, por ejemplo: * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} -### Tipos genéricos con parámetros de tipo { #generic-types-with-type-parameters } +### Módulo `typing` { #typing-module } -Hay algunas estructuras de datos que pueden contener otros valores, como `dict`, `list`, `set` y `tuple`. Y los valores internos también pueden tener su propio tipo. +Para algunos casos adicionales, podrías necesitar importar algunas cosas del módulo `typing` de la standard library, por ejemplo cuando quieres declarar que algo tiene "cualquier tipo", puedes usar `Any` de `typing`: -Estos tipos que tienen tipos internos se denominan tipos "**genéricos**". Y es posible declararlos, incluso con sus tipos internos. +```python +from typing import Any -Para declarar esos tipos y los tipos internos, puedes usar el módulo estándar de Python `typing`. Existe específicamente para soportar estas anotaciones de tipos. -#### Versiones más recientes de Python { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -La sintaxis que utiliza `typing` es **compatible** con todas las versiones, desde Python 3.6 hasta las versiones más recientes, incluyendo Python 3.9, Python 3.10, etc. +### Tipos genéricos { #generic-types } -A medida que avanza Python, las **versiones más recientes** vienen con soporte mejorado para estas anotaciones de tipos y en muchos casos ni siquiera necesitarás importar y usar el módulo `typing` para declarar las anotaciones de tipos. +Algunos tipos pueden tomar "parámetros de tipo" entre corchetes, para definir sus tipos internos, por ejemplo una "lista de strings" se declararía `list[str]`. -Si puedes elegir una versión más reciente de Python para tu proyecto, podrás aprovechar esa simplicidad adicional. +Estos tipos que pueden tomar parámetros de tipo se llaman **Tipos Genéricos** o **Genéricos**. -En toda la documentación hay ejemplos compatibles con cada versión de Python (cuando hay una diferencia). +Puedes usar los mismos tipos integrados como genéricos (con corchetes y tipos dentro): -Por ejemplo, "**Python 3.6+**" significa que es compatible con Python 3.6 o superior (incluyendo 3.7, 3.8, 3.9, 3.10, etc). Y "**Python 3.9+**" significa que es compatible con Python 3.9 o superior (incluyendo 3.10, etc). - -Si puedes usar las **últimas versiones de Python**, utiliza los ejemplos para la última versión, esos tendrán la **mejor y más simple sintaxis**, por ejemplo, "**Python 3.10+**". +* `list` +* `tuple` +* `set` +* `dict` #### Lista { #list } @@ -167,7 +170,7 @@ Como tipo, pon `list`. Como la lista es un tipo que contiene algunos tipos internos, los pones entre corchetes: -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} /// info | Información @@ -193,7 +196,7 @@ Y aún así, el editor sabe que es un `str` y proporciona soporte para eso. Harías lo mismo para declarar `tuple`s y `set`s: -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} Esto significa: @@ -208,7 +211,7 @@ El primer parámetro de tipo es para las claves del `dict`. El segundo parámetro de tipo es para los valores del `dict`: -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} Esto significa: @@ -218,46 +221,22 @@ Esto significa: #### Union { #union } -Puedes declarar que una variable puede ser cualquier de **varios tipos**, por ejemplo, un `int` o un `str`. +Puedes declarar que una variable puede ser cualquiera de **varios tipos**, por ejemplo, un `int` o un `str`. -En Python 3.6 y posterior (incluyendo Python 3.10) puedes usar el tipo `Union` de `typing` y poner dentro de los corchetes los posibles tipos a aceptar. +Para definirlo usas la barra vertical (`|`) para separar ambos tipos. -En Python 3.10 también hay una **nueva sintaxis** donde puedes poner los posibles tipos separados por una barra vertical (`|`). - -//// tab | Python 3.10+ +Esto se llama una "unión", porque la variable puede ser cualquiera en la unión de esos dos conjuntos de tipos. ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -En ambos casos, esto significa que `item` podría ser un `int` o un `str`. +Esto significa que `item` podría ser un `int` o un `str`. #### Posiblemente `None` { #possibly-none } Puedes declarar que un valor podría tener un tipo, como `str`, pero que también podría ser `None`. -En Python 3.6 y posteriores (incluyendo Python 3.10) puedes declararlo importando y usando `Optional` del módulo `typing`. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -Usar `Optional[str]` en lugar de solo `str` te permitirá al editor ayudarte a detectar errores donde podrías estar asumiendo que un valor siempre es un `str`, cuando en realidad también podría ser `None`. - -`Optional[Something]` es realmente un atajo para `Union[Something, None]`, son equivalentes. - -Esto también significa que en Python 3.10, puedes usar `Something | None`: - //// tab | Python 3.10+ ```Python hl_lines="1" @@ -266,96 +245,7 @@ Esto también significa que en Python 3.10, puedes usar `Something | None`: //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ alternativa - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### Uso de `Union` u `Optional` { #using-union-or-optional } - -Si estás usando una versión de Python inferior a 3.10, aquí tienes un consejo desde mi punto de vista muy **subjetivo**: - -* 🚨 Evita usar `Optional[SomeType]` -* En su lugar ✨ **usa `Union[SomeType, None]`** ✨. - -Ambos son equivalentes y debajo son lo mismo, pero recomendaría `Union` en lugar de `Optional` porque la palabra "**opcional**" parecería implicar que el valor es opcional, y en realidad significa "puede ser `None`", incluso si no es opcional y aún es requerido. - -Creo que `Union[SomeType, None]` es más explícito sobre lo que significa. - -Se trata solo de las palabras y nombres. Pero esas palabras pueden afectar cómo tú y tus compañeros de equipo piensan sobre el código. - -Como ejemplo, tomemos esta función: - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -El parámetro `name` está definido como `Optional[str]`, pero **no es opcional**, no puedes llamar a la función sin el parámetro: - -```Python -say_hi() # ¡Oh, no, esto lanza un error! 😱 -``` - -El parámetro `name` sigue siendo **requerido** (no *opcional*) porque no tiene un valor predeterminado. Aún así, `name` acepta `None` como valor: - -```Python -say_hi(name=None) # Esto funciona, None es válido 🎉 -``` - -La buena noticia es que, una vez que estés en Python 3.10, no tendrás que preocuparte por eso, ya que podrás simplemente usar `|` para definir uniones de tipos: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -Y entonces no tendrás que preocuparte por nombres como `Optional` y `Union`. 😎 - -#### Tipos genéricos { #generic-types } - -Estos tipos que toman parámetros de tipo en corchetes se llaman **Tipos Genéricos** o **Genéricos**, por ejemplo: - -//// tab | Python 3.10+ - -Puedes usar los mismos tipos integrados como genéricos (con corchetes y tipos dentro): - -* `list` -* `tuple` -* `set` -* `dict` - -Y, como con versiones anteriores de Python, desde el módulo `typing`: - -* `Union` -* `Optional` -* ...y otros. - -En Python 3.10, como alternativa a usar los genéricos `Union` y `Optional`, puedes usar la barra vertical (`|`) para declarar uniones de tipos, eso es mucho mejor y más simple. - -//// - -//// tab | Python 3.9+ - -Puedes usar los mismos tipos integrados como genéricos (con corchetes y tipos dentro): - -* `list` -* `tuple` -* `set` -* `dict` - -Y generics desde el módulo `typing`: - -* `Union` -* `Optional` -* ...y otros. - -//// +Usar `str | None` en lugar de solo `str` te permitirá al editor ayudarte a detectar errores donde podrías estar asumiendo que un valor siempre es un `str`, cuando en realidad también podría ser `None`. ### Clases como tipos { #classes-as-types } @@ -363,11 +253,11 @@ También puedes declarar una clase como el tipo de una variable. Digamos que tienes una clase `Person`, con un nombre: -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} Luego puedes declarar una variable para que sea de tipo `Person`: -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} Y luego, nuevamente, obtienes todo el soporte del editor: @@ -403,19 +293,13 @@ Para saber más sobre Required Optional fields. - -/// - ## Anotaciones de tipos con metadata { #type-hints-with-metadata-annotations } -Python también tiene una funcionalidad que permite poner **metadatos adicional** en estas anotaciones de tipos usando `Annotated`. +Python también tiene una funcionalidad que permite poner **metadata adicional** en estas anotaciones de tipos usando `Annotated`. -Desde Python 3.9, `Annotated` es parte de la standard library, así que puedes importarlo desde `typing`. +Puedes importar `Annotated` desde `typing`. -{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} Python en sí no hace nada con este `Annotated`. Y para los editores y otras herramientas, el tipo sigue siendo `str`. diff --git a/docs/es/docs/translation-banner.md b/docs/es/docs/translation-banner.md new file mode 100644 index 000000000..e38f20cbb --- /dev/null +++ b/docs/es/docs/translation-banner.md @@ -0,0 +1,11 @@ +/// details | 🌐 Traducción por IA y humanos + +Esta traducción fue hecha por IA guiada por humanos. 🤝 + +Podría tener errores al interpretar el significado original, o sonar poco natural, etc. 🤖 + +Puedes mejorar esta traducción [ayudándonos a guiar mejor al LLM de IA](https://fastapi.tiangolo.com/es/contributing/#translations). + +[Versión en inglés](ENGLISH_VERSION_URL) + +/// diff --git a/docs/es/docs/tutorial/background-tasks.md b/docs/es/docs/tutorial/background-tasks.md index cc8a2c9cb..10ad4b5eb 100644 --- a/docs/es/docs/tutorial/background-tasks.md +++ b/docs/es/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ Esto incluye, por ejemplo: Primero, importa `BackgroundTasks` y define un parámetro en tu *path operation function* con una declaración de tipo de `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} **FastAPI** creará el objeto de tipo `BackgroundTasks` por ti y lo pasará como ese parámetro. @@ -31,13 +31,13 @@ En este caso, la función de tarea escribirá en un archivo (simulando el envío Y como la operación de escritura no usa `async` y `await`, definimos la función con un `def` normal: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} ## Agregar la tarea en segundo plano { #add-the-background-task } Dentro de tu *path operation function*, pasa tu función de tarea al objeto de *background tasks* con el método `.add_task()`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} `.add_task()` recibe como argumentos: diff --git a/docs/es/docs/tutorial/bigger-applications.md b/docs/es/docs/tutorial/bigger-applications.md index 7938a1215..96b58a720 100644 --- a/docs/es/docs/tutorial/bigger-applications.md +++ b/docs/es/docs/tutorial/bigger-applications.md @@ -85,7 +85,7 @@ Puedes crear las *path operations* para ese módulo usando `APIRouter`. Lo importas y creas una "instance" de la misma manera que lo harías con la clase `FastAPI`: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} ### *Path operations* con `APIRouter` { #path-operations-with-apirouter } @@ -93,7 +93,7 @@ Y luego lo usas para declarar tus *path operations*. Úsalo de la misma manera que usarías la clase `FastAPI`: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} Puedes pensar en `APIRouter` como una clase "mini `FastAPI`". @@ -117,7 +117,7 @@ Así que las ponemos en su propio módulo `dependencies` (`app/dependencies.py`) Ahora utilizaremos una dependencia simple para leer un header `X-Token` personalizado: -{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | Consejo @@ -149,7 +149,7 @@ Sabemos que todas las *path operations* en este módulo tienen el mismo: Entonces, en lugar de agregar todo eso a cada *path operation*, podemos agregarlo al `APIRouter`. -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} Como el path de cada *path operation* tiene que empezar con `/`, como en: @@ -208,7 +208,7 @@ Y necesitamos obtener la función de dependencia del módulo `app.dependencies`, Así que usamos un import relativo con `..` para las dependencias: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} #### Cómo funcionan los imports relativos { #how-relative-imports-work } @@ -279,7 +279,7 @@ No estamos agregando el prefijo `/items` ni los `tags=["items"]` a cada *path op Pero aún podemos agregar _más_ `tags` que se aplicarán a una *path operation* específica, y también algunas `responses` extra específicas para esa *path operation*: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | Consejo @@ -305,13 +305,13 @@ Importas y creas una clase `FastAPI` como normalmente. Y podemos incluso declarar [dependencias globales](dependencies/global-dependencies.md){.internal-link target=_blank} que se combinarán con las dependencias para cada `APIRouter`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} ### Importar el `APIRouter` { #import-the-apirouter } Ahora importamos los otros submódulos que tienen `APIRouter`s: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} Como los archivos `app/routers/users.py` y `app/routers/items.py` son submódulos que son parte del mismo paquete de Python `app`, podemos usar un solo punto `.` para importarlos usando "imports relativos". @@ -374,13 +374,13 @@ el `router` de `users` sobrescribiría el de `items` y no podríamos usarlos al Así que, para poder usar ambos en el mismo archivo, importamos los submódulos directamente: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} ### Incluir los `APIRouter`s para `users` y `items` { #include-the-apirouters-for-users-and-items } Ahora, incluyamos los `router`s de los submódulos `users` y `items`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} /// info | Información @@ -420,13 +420,13 @@ Contiene un `APIRouter` con algunas *path operations* de administración que tu Para este ejemplo será súper simple. Pero digamos que porque está compartido con otros proyectos en la organización, no podemos modificarlo y agregar un `prefix`, `dependencies`, `tags`, etc. directamente al `APIRouter`: -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} Pero aún queremos configurar un `prefix` personalizado al incluir el `APIRouter` para que todas sus *path operations* comiencen con `/admin`, queremos asegurarlo con las `dependencies` que ya tenemos para este proyecto, y queremos incluir `tags` y `responses`. Podemos declarar todo eso sin tener que modificar el `APIRouter` original pasando esos parámetros a `app.include_router()`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} De esa manera, el `APIRouter` original permanecerá sin modificar, por lo que aún podemos compartir ese mismo archivo `app/internal/admin.py` con otros proyectos en la organización. @@ -447,7 +447,7 @@ También podemos agregar *path operations* directamente a la app de `FastAPI`. Aquí lo hacemos... solo para mostrar que podemos 🤷: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} y funcionará correctamente, junto con todas las otras *path operations* añadidas con `app.include_router()`. diff --git a/docs/es/docs/tutorial/body-multiple-params.md b/docs/es/docs/tutorial/body-multiple-params.md index c52486f9b..c78dd2881 100644 --- a/docs/es/docs/tutorial/body-multiple-params.md +++ b/docs/es/docs/tutorial/body-multiple-params.md @@ -104,12 +104,6 @@ Como, por defecto, los valores singulares se interpretan como parámetros de que q: str | None = None ``` -O en Python 3.9: - -```Python -q: Union[str, None] = None -``` - Por ejemplo: {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/es/docs/tutorial/body-nested-models.md b/docs/es/docs/tutorial/body-nested-models.md index 0dfd6576f..5f723e4c9 100644 --- a/docs/es/docs/tutorial/body-nested-models.md +++ b/docs/es/docs/tutorial/body-nested-models.md @@ -164,7 +164,7 @@ images: list[Image] como en: -{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} ## Soporte de editor en todas partes { #editor-support-everywhere } @@ -194,7 +194,7 @@ Eso es lo que vamos a ver aquí. En este caso, aceptarías cualquier `dict` siempre que tenga claves `int` con valores `float`: -{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} /// tip | Consejo diff --git a/docs/es/docs/tutorial/body.md b/docs/es/docs/tutorial/body.md index dde39f78c..3adf2e65c 100644 --- a/docs/es/docs/tutorial/body.md +++ b/docs/es/docs/tutorial/body.md @@ -74,7 +74,7 @@ Con solo esa declaración de tipo en Python, **FastAPI** hará lo siguiente: * Proporcionar los datos recibidos en el parámetro `item`. * Como lo declaraste en la función como de tipo `Item`, también tendrás todo el soporte del editor (autocompletado, etc.) para todos los atributos y sus tipos. * Generar definiciones de JSON Schema para tu modelo, que también puedes usar en cualquier otro lugar si tiene sentido para tu proyecto. -* Esos esquemas serán parte del esquema de OpenAPI generado y usados por las UIs de documentación automática. +* Esos esquemas serán parte del esquema de OpenAPI generado y usados por las UIs de documentación automática. ## Documentación automática { #automatic-docs } @@ -155,7 +155,7 @@ Los parámetros de la función se reconocerán de la siguiente manera: FastAPI sabrá que el valor de `q` no es requerido debido al valor por defecto `= None`. -El `str | None` (Python 3.10+) o `Union` en `Union[str, None]` (Python 3.9+) no es utilizado por FastAPI para determinar que el valor no es requerido, sabrá que no es requerido porque tiene un valor por defecto de `= None`. +El `str | None` no es utilizado por FastAPI para determinar que el valor no es requerido, sabrá que no es requerido porque tiene un valor por defecto de `= None`. Pero agregar las anotaciones de tipos permitirá que tu editor te brinde un mejor soporte y detecte errores. @@ -163,4 +163,4 @@ Pero agregar las anotaciones de tipos permitirá que tu editor te brinde un mejo ## Sin Pydantic { #without-pydantic } -Si no quieres usar modelos de Pydantic, también puedes usar parámetros **Body**. Consulta la documentación para [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. +Si no quieres usar modelos de Pydantic, también puedes usar parámetros **Body**. Consulta la documentación para [Body - Múltiples parámetros: Valores singulares en el body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. diff --git a/docs/es/docs/tutorial/cookie-param-models.md b/docs/es/docs/tutorial/cookie-param-models.md index dab7d8c0a..4e6038a46 100644 --- a/docs/es/docs/tutorial/cookie-param-models.md +++ b/docs/es/docs/tutorial/cookie-param-models.md @@ -46,7 +46,7 @@ Pero incluso si **rellenas los datos** y haces clic en "Execute", como la UI de En algunos casos de uso especiales (probablemente no muy comunes), podrías querer **restringir** las cookies que deseas recibir. -Tu API ahora tiene el poder de controlar su propio consentimiento de cookies. 🤪🍪 +Tu API ahora tiene el poder de controlar su propio consentimiento de cookies. 🤪🍪 Puedes usar la configuración del modelo de Pydantic para `prohibir` cualquier campo `extra`: @@ -54,9 +54,9 @@ Puedes usar la configuración del modelo de Pydantic para `prohibir` cualquier c Si un cliente intenta enviar algunas **cookies extra**, recibirán un response de **error**. -Pobres banners de cookies con todo su esfuerzo para obtener tu consentimiento para que la API lo rechace. 🍪 +Pobres banners de cookies con todo su esfuerzo para obtener tu consentimiento para que la API lo rechace. 🍪 -Por ejemplo, si el cliente intenta enviar una cookie `santa_tracker` con un valor de `good-list-please`, el cliente recibirá un response de **error** que le informa que la cookie `santa_tracker` no está permitida: +Por ejemplo, si el cliente intenta enviar una cookie `santa_tracker` con un valor de `good-list-please`, el cliente recibirá un response de **error** que le informa que la `santa_tracker` cookie no está permitida: ```json { @@ -73,4 +73,4 @@ Por ejemplo, si el cliente intenta enviar una cookie `santa_tracker` con un valo ## Resumen { #summary } -Puedes usar **modelos de Pydantic** para declarar **cookies** en **FastAPI**. 😎 +Puedes usar **modelos de Pydantic** para declarar **cookies** en **FastAPI**. 😎 diff --git a/docs/es/docs/tutorial/cors.md b/docs/es/docs/tutorial/cors.md index c1a23295e..a118d814b 100644 --- a/docs/es/docs/tutorial/cors.md +++ b/docs/es/docs/tutorial/cors.md @@ -46,7 +46,8 @@ También puedes especificar si tu backend permite: * Métodos HTTP específicos (`POST`, `PUT`) o todos ellos con el comodín `"*"`. * Headers HTTP específicos o todos ellos con el comodín `"*"`. -{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} + Los parámetros predeterminados utilizados por la implementación de `CORSMiddleware` son restrictivos por defecto, por lo que necesitarás habilitar explícitamente orígenes, métodos o headers particulares para que los navegadores estén permitidos de usarlos en un contexto de Cross-Domain. diff --git a/docs/es/docs/tutorial/debugging.md b/docs/es/docs/tutorial/debugging.md index c31daf40f..a2d47f2da 100644 --- a/docs/es/docs/tutorial/debugging.md +++ b/docs/es/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ Puedes conectar el depurador en tu editor, por ejemplo con Visual Studio Code o En tu aplicación de FastAPI, importa y ejecuta `uvicorn` directamente: -{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} ### Acerca de `__name__ == "__main__"` { #about-name-main } diff --git a/docs/es/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/es/docs/tutorial/dependencies/classes-as-dependencies.md index a3a75efcd..7506bda47 100644 --- a/docs/es/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/es/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ Ahora puedes declarar tu dependencia usando esta clase. Nota cómo escribimos `CommonQueryParams` dos veces en el código anterior: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ sin `Annotated` +//// tab | Python 3.10+ sin `Annotated` /// tip | Consejo @@ -137,7 +137,7 @@ Es a partir de este que **FastAPI** extraerá los parámetros declarados y es lo En este caso, el primer `CommonQueryParams`, en: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.9+ sin `Annotated` +//// tab | Python 3.10+ sin `Annotated` /// tip | Consejo @@ -163,7 +163,7 @@ commons: CommonQueryParams ... De hecho, podrías escribir simplemente: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ sin `Annotated` +//// tab | Python 3.10+ sin `Annotated` /// tip | Consejo @@ -197,7 +197,7 @@ Pero declarar el tipo es recomendable, ya que de esa manera tu editor sabrá lo Pero ves que estamos teniendo algo de repetición de código aquí, escribiendo `CommonQueryParams` dos veces: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ sin `Annotated` +//// tab | Python 3.10+ sin `Annotated` /// tip | Consejo @@ -225,7 +225,7 @@ Para esos casos específicos, puedes hacer lo siguiente: En lugar de escribir: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ sin `Annotated` +//// tab | Python 3.10+ sin `Annotated` /// tip | Consejo @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...escribes: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.9+ sin `Annotated` +//// tab | Python 3.10+ sin `Annotated` /// tip | Consejo diff --git a/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 60baa93a9..5eadbe7e1 100644 --- a/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -14,7 +14,7 @@ El decorador de *path operation* recibe un argumento opcional `dependencies`. Debe ser una `list` de `Depends()`: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} Estas dependencias serán ejecutadas/resueltas de la misma manera que las dependencias normales. Pero su valor (si devuelven alguno) no será pasado a tu *path operation function*. @@ -44,13 +44,13 @@ Puedes usar las mismas *funciones* de dependencia que usas normalmente. Pueden declarar requisitos de request (como headers) u otras sub-dependencias: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} ### Lanzar excepciones { #raise-exceptions } Estas dependencias pueden `raise` excepciones, igual que las dependencias normales: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} ### Valores de retorno { #return-values } @@ -58,7 +58,7 @@ Y pueden devolver valores o no, los valores no serán usados. Así que, puedes reutilizar una dependencia normal (que devuelve un valor) que ya uses en otro lugar, y aunque el valor no se use, la dependencia será ejecutada: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} ## Dependencias para un grupo de *path operations* { #dependencies-for-a-group-of-path-operations } diff --git a/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md index aa645daa4..2cd68eddd 100644 --- a/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,6 +1,6 @@ # Dependencias con yield { #dependencies-with-yield } -FastAPI admite dependencias que realizan algunos pasos adicionales después de finalizar. +FastAPI admite dependencias que realizan algunos pasos adicionales después de finalizar. Para hacer esto, usa `yield` en lugar de `return`, y escribe los pasos adicionales (código) después. @@ -29,15 +29,15 @@ Por ejemplo, podrías usar esto para crear una sesión de base de datos y cerrar Solo el código anterior e incluyendo la declaración `yield` se ejecuta antes de crear un response: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[2:4] *} El valor generado es lo que se inyecta en *path operations* y otras dependencias: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[4] *} El código posterior a la declaración `yield` se ejecuta después del response: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[5:6] *} /// tip | Consejo @@ -57,7 +57,7 @@ Por lo tanto, puedes buscar esa excepción específica dentro de la dependencia Del mismo modo, puedes usar `finally` para asegurarte de que los pasos de salida se ejecuten, sin importar si hubo una excepción o no. -{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[3,5] *} ## Sub-dependencias con `yield` { #sub-dependencies-with-yield } @@ -67,7 +67,7 @@ Puedes tener sub-dependencias y "árboles" de sub-dependencias de cualquier tama Por ejemplo, `dependency_c` puede tener una dependencia de `dependency_b`, y `dependency_b` de `dependency_a`: -{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[6,14,22] *} Y todas ellas pueden usar `yield`. @@ -75,7 +75,7 @@ En este caso, `dependency_c`, para ejecutar su código de salida, necesita que e Y, a su vez, `dependency_b` necesita que el valor de `dependency_a` (aquí llamado `dep_a`) esté disponible para su código de salida. -{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *} +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[18:19,26:27] *} De la misma manera, podrías tener algunas dependencias con `yield` y otras dependencias con `return`, y hacer que algunas de esas dependan de algunas de las otras. @@ -109,7 +109,7 @@ Pero está ahí para ti si la necesitas. 🤓 /// -{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *} +{* ../../docs_src/dependencies/tutorial008b_an_py310.py hl[18:22,31] *} Si quieres capturar excepciones y crear un response personalizado en base a eso, crea un [Manejador de Excepciones Personalizado](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. @@ -117,7 +117,7 @@ Si quieres capturar excepciones y crear un response personalizado en base a eso, Si capturas una excepción usando `except` en una dependencia con `yield` y no la lanzas nuevamente (o lanzas una nueva excepción), FastAPI no podrá notar que hubo una excepción, al igual que sucedería con Python normal: -{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} +{* ../../docs_src/dependencies/tutorial008c_an_py310.py hl[15:16] *} En este caso, el cliente verá un response *HTTP 500 Internal Server Error* como debería, dado que no estamos lanzando una `HTTPException` o similar, pero el servidor **no tendrá ningún registro** ni ninguna otra indicación de cuál fue el error. 😱 @@ -127,7 +127,7 @@ Si capturas una excepción en una dependencia con `yield`, a menos que estés la Puedes volver a lanzar la misma excepción usando `raise`: -{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *} +{* ../../docs_src/dependencies/tutorial008d_an_py310.py hl[17] *} Ahora el cliente obtendrá el mismo response *HTTP 500 Internal Server Error*, pero el servidor tendrá nuestro `InternalError` personalizado en los registros. 😎 @@ -190,7 +190,7 @@ Normalmente, el código de salida de las dependencias con `yield` se ejecuta **d Pero si sabes que no necesitarás usar la dependencia después de regresar de la *path operation function*, puedes usar `Depends(scope="function")` para decirle a FastAPI que debe cerrar la dependencia después de que la *path operation function* regrese, pero **antes** de que se envíe el **response**. -{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *} +{* ../../docs_src/dependencies/tutorial008e_an_py310.py hl[12,16] *} `Depends()` recibe un parámetro `scope` que puede ser: @@ -234,7 +234,6 @@ participant operation as Path Operation Las dependencias con `yield` han evolucionado con el tiempo para cubrir diferentes casos de uso y corregir algunos problemas. Si quieres ver qué ha cambiado en diferentes versiones de FastAPI, puedes leer más al respecto en la guía avanzada, en [Dependencias avanzadas - Dependencias con `yield`, `HTTPException`, `except` y Tareas en Background](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}. - ## Context Managers { #context-managers } ### Qué son los "Context Managers" { #what-are-context-managers } @@ -270,7 +269,7 @@ En Python, puedes crear Context Managers Inyección de Dependencias** muy poderoso pero intuitivo. +**FastAPI** tiene un sistema de **Inyección de Dependencias** muy poderoso pero intuitivo. Está diseñado para ser muy simple de usar, y para hacer que cualquier desarrollador integre otros componentes con **FastAPI** de forma muy sencilla. diff --git a/docs/es/docs/tutorial/dependencies/sub-dependencies.md b/docs/es/docs/tutorial/dependencies/sub-dependencies.md index e74d65d7e..95f3fe817 100644 --- a/docs/es/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/es/docs/tutorial/dependencies/sub-dependencies.md @@ -58,11 +58,11 @@ query_extractor --> query_or_cookie_extractor --> read_query Si una de tus dependencias se declara varias veces para la misma *path operation*, por ejemplo, múltiples dependencias tienen una sub-dependencia común, **FastAPI** sabrá llamar a esa sub-dependencia solo una vez por request. -Y guardará el valor devuelto en un "cache" y lo pasará a todos los "dependants" que lo necesiten en ese request específico, en lugar de llamar a la dependencia varias veces para el mismo request. +Y guardará el valor devuelto en un "caché" y lo pasará a todos los "dependants" que lo necesiten en ese request específico, en lugar de llamar a la dependencia varias veces para el mismo request. -En un escenario avanzado donde sabes que necesitas que la dependencia se llame en cada paso (posiblemente varias veces) en el mismo request en lugar de usar el valor "cache", puedes establecer el parámetro `use_cache=False` al usar `Depends`: +En un escenario avanzado donde sabes que necesitas que la dependencia se llame en cada paso (posiblemente varias veces) en el mismo request en lugar de usar el valor "en caché", puedes establecer el parámetro `use_cache=False` al usar `Depends`: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python hl_lines="1" async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): @@ -71,7 +71,7 @@ async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_ca //// -//// tab | Python 3.9+ sin Anotaciones +//// tab | Python 3.10+ sin Anotaciones /// tip | Consejo diff --git a/docs/es/docs/tutorial/encoder.md b/docs/es/docs/tutorial/encoder.md index 319ae1bde..df6099a8b 100644 --- a/docs/es/docs/tutorial/encoder.md +++ b/docs/es/docs/tutorial/encoder.md @@ -26,7 +26,7 @@ En este ejemplo, convertiría el modelo de Pydantic a un `dict`, y el `datetime` El resultado de llamarlo es algo que puede ser codificado con la función estándar de Python `json.dumps()`. -No devuelve un gran `str` que contenga los datos en formato JSON (como una cadena de texto). Devuelve una estructura de datos estándar de Python (por ejemplo, un `dict`) con valores y sub-valores que son todos compatibles con JSON. +No devuelve un gran `str` que contenga los datos en formato JSON (como un string). Devuelve una estructura de datos estándar de Python (por ejemplo, un `dict`) con valores y sub-valores que son todos compatibles con JSON. /// note | Nota diff --git a/docs/es/docs/tutorial/extra-models.md b/docs/es/docs/tutorial/extra-models.md index d72c73e24..4621b2db2 100644 --- a/docs/es/docs/tutorial/extra-models.md +++ b/docs/es/docs/tutorial/extra-models.md @@ -190,9 +190,9 @@ Pero si ponemos eso en la asignación `response_model=PlaneItem | CarItem` obten De la misma manera, puedes declarar responses de listas de objetos. -Para eso, usa el `typing.List` estándar de Python (o simplemente `list` en Python 3.9 y posteriores): +Para eso, usa la `list` estándar de Python: -{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} +{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *} ## Response con `dict` arbitrario { #response-with-arbitrary-dict } @@ -200,9 +200,9 @@ También puedes declarar un response usando un `dict` arbitrario plano, declaran Esto es útil si no conoces los nombres de los campos/atributos válidos (que serían necesarios para un modelo Pydantic) de antemano. -En este caso, puedes usar `typing.Dict` (o solo `dict` en Python 3.9 y posteriores): +En este caso, puedes usar `dict`: -{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} +{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *} ## Recapitulación { #recap } diff --git a/docs/es/docs/tutorial/first-steps.md b/docs/es/docs/tutorial/first-steps.md index 789b2a011..b7ffd2c2a 100644 --- a/docs/es/docs/tutorial/first-steps.md +++ b/docs/es/docs/tutorial/first-steps.md @@ -2,7 +2,7 @@ El archivo FastAPI más simple podría verse así: -{* ../../docs_src/first_steps/tutorial001_py39.py *} +{* ../../docs_src/first_steps/tutorial001_py310.py *} Copia eso en un archivo `main.py`. @@ -56,7 +56,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) Esa línea muestra la URL donde tu aplicación está siendo servida, en tu máquina local. -### Compruébalo { #check-it } +### Revisa { #check-it } Abre tu navegador en http://127.0.0.1:8000. @@ -183,7 +183,7 @@ Deploying to FastAPI Cloud... ### Paso 1: importa `FastAPI` { #step-1-import-fastapi } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[1] *} `FastAPI` es una clase de Python que proporciona toda la funcionalidad para tu API. @@ -197,7 +197,7 @@ Puedes usar toda la funcionalidad de get operation +* usando una get operación /// info | Información sobre `@decorator` @@ -320,7 +320,7 @@ Esta es nuestra "**path operation function**": * **operation**: es `get`. * **function**: es la función debajo del "decorador" (debajo de `@app.get("/")`). -{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[7] *} Esta es una función de Python. @@ -332,7 +332,7 @@ En este caso, es una función `async`. También podrías definirla como una función normal en lugar de `async def`: -{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py310.py hl[7] *} /// note | Nota @@ -342,7 +342,7 @@ Si no sabes la diferencia, Revisa la sección [Async: *"¿Tienes prisa?"*](../as ### Paso 5: retorna el contenido { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[8] *} Puedes retornar un `dict`, `list`, valores singulares como `str`, `int`, etc. diff --git a/docs/es/docs/tutorial/handling-errors.md b/docs/es/docs/tutorial/handling-errors.md index 71e056320..265269e87 100644 --- a/docs/es/docs/tutorial/handling-errors.md +++ b/docs/es/docs/tutorial/handling-errors.md @@ -25,7 +25,7 @@ Para devolver responses HTTP con errores al cliente, usa `HTTPException`. ### Importa `HTTPException` { #import-httpexception } -{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *} +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[1] *} ### Lanza un `HTTPException` en tu código { #raise-an-httpexception-in-your-code } @@ -39,7 +39,7 @@ El beneficio de lanzar una excepción en lugar de `return`ar un valor será más En este ejemplo, cuando el cliente solicita un ítem por un ID que no existe, lanza una excepción con un código de estado de `404`: -{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *} +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[11] *} ### El response resultante { #the-resulting-response } @@ -77,7 +77,7 @@ Probablemente no necesitarás usarlos directamente en tu código. Pero en caso de que los necesites para un escenario avanzado, puedes agregar headers personalizados: -{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial002_py310.py hl[14] *} ## Instalar manejadores de excepciones personalizados { #install-custom-exception-handlers } @@ -89,7 +89,7 @@ Y quieres manejar esta excepción globalmente con FastAPI. Podrías agregar un manejador de excepciones personalizado con `@app.exception_handler()`: -{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *} +{* ../../docs_src/handling_errors/tutorial003_py310.py hl[5:7,13:18,24] *} Aquí, si solicitas `/unicorns/yolo`, la *path operation* lanzará un `UnicornException`. @@ -127,7 +127,7 @@ Para sobrescribirlo, importa el `RequestValidationError` y úsalo con `@app.exce El manejador de excepciones recibirá un `Request` y la excepción. -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[2,14:19] *} Ahora, si vas a `/items/foo`, en lugar de obtener el error JSON por defecto con: @@ -159,7 +159,7 @@ De la misma manera, puedes sobrescribir el manejador de `HTTPException`. Por ejemplo, podrías querer devolver un response de texto plano en lugar de JSON para estos errores: -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[3:4,9:11,25] *} /// note | Nota Técnica @@ -183,7 +183,7 @@ El `RequestValidationError` contiene el `body` que recibió con datos inválidos Podrías usarlo mientras desarrollas tu aplicación para registrar el body y depurarlo, devolverlo al usuario, etc. -{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial005_py310.py hl[14] *} Ahora intenta enviar un ítem inválido como: @@ -239,6 +239,6 @@ from starlette.exceptions import HTTPException as StarletteHTTPException Si quieres usar la excepción junto con los mismos manejadores de excepciones predeterminados de **FastAPI**, puedes importar y reutilizar los manejadores de excepciones predeterminados de `fastapi.exception_handlers`: -{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *} +{* ../../docs_src/handling_errors/tutorial006_py310.py hl[2:5,15,21] *} En este ejemplo solo estás `print`eando el error con un mensaje muy expresivo, pero te haces una idea. Puedes usar la excepción y luego simplemente reutilizar los manejadores de excepciones predeterminados. diff --git a/docs/es/docs/tutorial/metadata.md b/docs/es/docs/tutorial/metadata.md index a5d9b5ead..2163a1cb2 100644 --- a/docs/es/docs/tutorial/metadata.md +++ b/docs/es/docs/tutorial/metadata.md @@ -18,7 +18,7 @@ Puedes establecer los siguientes campos que se usan en la especificación OpenAP Puedes configurarlos de la siguiente manera: -{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} +{* ../../docs_src/metadata/tutorial001_py310.py hl[3:16, 19:32] *} /// tip | Consejo @@ -36,7 +36,7 @@ Desde OpenAPI 3.1.0 y FastAPI 0.99.0, también puedes establecer la `license_inf Por ejemplo: -{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} +{* ../../docs_src/metadata/tutorial001_1_py310.py hl[31] *} ## Metadata para etiquetas { #metadata-for-tags } @@ -58,7 +58,7 @@ Probemos eso en un ejemplo con etiquetas para `users` y `items`. Crea metadata para tus etiquetas y pásala al parámetro `openapi_tags`: -{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[3:16,18] *} Nota que puedes utilizar Markdown dentro de las descripciones, por ejemplo "login" se mostrará en negrita (**login**) y "fancy" se mostrará en cursiva (_fancy_). @@ -72,7 +72,7 @@ No tienes que agregar metadata para todas las etiquetas que uses. Usa el parámetro `tags` con tus *path operations* (y `APIRouter`s) para asignarlas a diferentes etiquetas: -{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[21,26] *} /// info | Información @@ -100,7 +100,7 @@ Pero puedes configurarlo con el parámetro `openapi_url`. Por ejemplo, para configurarlo para que se sirva en `/api/v1/openapi.json`: -{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py310.py hl[3] *} Si quieres deshabilitar el esquema OpenAPI completamente, puedes establecer `openapi_url=None`, eso también deshabilitará las interfaces de usuario de documentación que lo usan. @@ -117,4 +117,4 @@ Puedes configurar las dos interfaces de usuario de documentación incluidas: Por ejemplo, para configurar Swagger UI para que se sirva en `/documentation` y deshabilitar ReDoc: -{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial003_py310.py hl[3] *} diff --git a/docs/es/docs/tutorial/middleware.md b/docs/es/docs/tutorial/middleware.md index de636a485..766e30cad 100644 --- a/docs/es/docs/tutorial/middleware.md +++ b/docs/es/docs/tutorial/middleware.md @@ -31,7 +31,7 @@ La función middleware recibe: * Luego devuelve la `response` generada por la correspondiente *path operation*. * Puedes entonces modificar aún más la `response` antes de devolverla. -{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[8:9,11,14] *} /// tip | Consejo @@ -57,7 +57,7 @@ Y también después de que se genere la `response`, antes de devolverla. Por ejemplo, podrías añadir un custom header `X-Process-Time` que contenga el tiempo en segundos que tomó procesar la request y generar una response: -{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[10,12:13] *} /// tip | Consejo diff --git a/docs/es/docs/tutorial/path-operation-configuration.md b/docs/es/docs/tutorial/path-operation-configuration.md index f57b322fb..90e4335b3 100644 --- a/docs/es/docs/tutorial/path-operation-configuration.md +++ b/docs/es/docs/tutorial/path-operation-configuration.md @@ -46,7 +46,7 @@ En estos casos, podría tener sentido almacenar las tags en un `Enum`. **FastAPI** soporta eso de la misma manera que con strings normales: -{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *} +{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *} ## Resumen y Descripción { #summary-and-description } @@ -56,7 +56,7 @@ Puedes añadir un `summary` y `description`: ## Descripción desde docstring { #description-from-docstring } -Como las descripciones tienden a ser largas y cubrir múltiples líneas, puedes declarar la descripción de la *path operation* en la docstring de la función y **FastAPI** la leerá desde allí. +Como las descripciones tienden a ser largas y cubrir múltiples líneas, puedes declarar la descripción de la *path operation* en la docstring de la función y **FastAPI** la leerá desde allí. Puedes escribir Markdown en el docstring, se interpretará y mostrará correctamente (teniendo en cuenta la indentación del docstring). @@ -90,9 +90,9 @@ Entonces, si no proporcionas una, **FastAPI** generará automáticamente una de ## Deprecar una *path operation* { #deprecate-a-path-operation } -Si necesitas marcar una *path operation* como deprecated, pero sin eliminarla, pasa el parámetro `deprecated`: +Si necesitas marcar una *path operation* como deprecated, pero sin eliminarla, pasa el parámetro `deprecated`: -{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} Se marcará claramente como deprecado en la documentación interactiva: diff --git a/docs/es/docs/tutorial/path-params-numeric-validations.md b/docs/es/docs/tutorial/path-params-numeric-validations.md index 569dd03dd..3a38d1d63 100644 --- a/docs/es/docs/tutorial/path-params-numeric-validations.md +++ b/docs/es/docs/tutorial/path-params-numeric-validations.md @@ -2,7 +2,7 @@ De la misma manera que puedes declarar más validaciones y metadatos para los parámetros de query con `Query`, puedes declarar el mismo tipo de validaciones y metadatos para los parámetros de path con `Path`. -## Importar Path { #import-path } +## Importar `Path` { #import-path } Primero, importa `Path` de `fastapi`, e importa `Annotated`: @@ -46,19 +46,19 @@ Y no necesitas declarar nada más para ese parámetro, así que realmente no nec Pero aún necesitas usar `Path` para el parámetro de path `item_id`. Y no quieres usar `Annotated` por alguna razón. -Python se quejará si pones un valor con un "default" antes de un valor que no tenga un "default". +Python se quejará si pones un valor con "por defecto" antes de un valor que no tenga "por defecto". -Pero puedes reordenarlos y poner el valor sin un default (el parámetro de query `q`) primero. +Pero puedes reordenarlos y poner el valor sin un valor por defecto (el parámetro de query `q`) primero. -No importa para **FastAPI**. Detectará los parámetros por sus nombres, tipos y declaraciones por defecto (`Query`, `Path`, etc.), no le importa el orden. +No importa para **FastAPI**. Detectará los parámetros por sus nombres, tipos y declaraciones por defecto (`Query`, `Path`, etc), no le importa el orden. Así que puedes declarar tu función como: -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} Pero ten en cuenta que si usas `Annotated`, no tendrás este problema, no importará ya que no estás usando los valores por defecto de los parámetros de la función para `Query()` o `Path()`. -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## Ordena los parámetros como necesites, trucos { #order-the-parameters-as-you-need-tricks } @@ -83,13 +83,13 @@ Pasa `*`, como el primer parámetro de la función. Python no hará nada con ese `*`, pero sabrá que todos los parámetros siguientes deben ser llamados como argumentos de palabras clave (parejas key-value), también conocidos como kwargs. Incluso si no tienen un valor por defecto. -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### Mejor con `Annotated` { #better-with-annotated } Ten en cuenta que si usas `Annotated`, como no estás usando valores por defecto de los parámetros de la función, no tendrás este problema y probablemente no necesitarás usar `*`. -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## Validaciones numéricas: mayor o igual { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ Con `Query` y `Path` (y otros que verás más adelante) puedes declarar restricc Aquí, con `ge=1`, `item_id` necesitará ser un número entero "`g`reater than or `e`qual" a `1`. -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## Validaciones numéricas: mayor que y menor o igual { #number-validations-greater-than-and-less-than-or-equal } @@ -106,19 +106,19 @@ Lo mismo aplica para: * `gt`: `g`reater `t`han * `le`: `l`ess than or `e`qual -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## Validaciones numéricas: flotantes, mayor y menor { #number-validations-floats-greater-than-and-less-than } Las validaciones numéricas también funcionan para valores `float`. -Aquí es donde se convierte en importante poder declarar gt y no solo ge. Ya que con esto puedes requerir, por ejemplo, que un valor sea mayor que `0`, incluso si es menor que `1`. +Aquí es donde se convierte en importante poder declarar gt y no solo ge. Ya que con esto puedes requerir, por ejemplo, que un valor sea mayor que `0`, incluso si es menor que `1`. Así, `0.5` sería un valor válido. Pero `0.0` o `0` no lo serían. -Y lo mismo para lt. +Y lo mismo para lt. -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## Resumen { #recap } diff --git a/docs/es/docs/tutorial/path-params.md b/docs/es/docs/tutorial/path-params.md index 7ba49f3b0..8dc3db351 100644 --- a/docs/es/docs/tutorial/path-params.md +++ b/docs/es/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Puedes declarar "parámetros" o "variables" de path con la misma sintaxis que se usa en los format strings de Python: -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} El valor del parámetro de path `item_id` se pasará a tu función como el argumento `item_id`. @@ -16,7 +16,7 @@ Así que, si ejecutas este ejemplo y vas a Conversión de datos { #data-conversion } +## Conversión de datos { #data-conversion } Si ejecutas este ejemplo y abres tu navegador en http://127.0.0.1:8000/items/3, verás un response de: @@ -38,7 +38,7 @@ Si ejecutas este ejemplo y abres tu navegador en "parsing" automático de request. +Entonces, con esa declaración de tipo, **FastAPI** te ofrece "parsing" automático de request. /// @@ -118,13 +118,13 @@ Y luego también puedes tener un path `/users/{user_id}` para obtener datos sobr Debido a que las *path operations* se evalúan en orden, necesitas asegurarte de que el path para `/users/me` se declara antes que el de `/users/{user_id}`: -{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *} De lo contrario, el path para `/users/{user_id}` también coincidiría para `/users/me`, "pensando" que está recibiendo un parámetro `user_id` con un valor de `"me"`. De manera similar, no puedes redefinir una path operation: -{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *} La primera siempre será utilizada ya que el path coincide primero. @@ -140,11 +140,11 @@ Al heredar de `str`, la documentación de la API podrá saber que los valores de Luego crea atributos de clase con valores fijos, que serán los valores válidos disponibles: -{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *} /// tip | Consejo -Si te estás preguntando, "AlexNet", "ResNet" y "LeNet" son solo nombres de modelos de Machine Learning. +Si te estás preguntando, "AlexNet", "ResNet" y "LeNet" son solo nombres de modelos de Machine Learning. /// @@ -152,7 +152,7 @@ Si te estás preguntando, "AlexNet", "ResNet" y "LeNet" son solo nombres de parsing" de datos +* " parsing " de datos * Validación de datos * Anotación de API y documentación automática diff --git a/docs/es/docs/tutorial/query-params-str-validations.md b/docs/es/docs/tutorial/query-params-str-validations.md index 4af4782f8..b4339e193 100644 --- a/docs/es/docs/tutorial/query-params-str-validations.md +++ b/docs/es/docs/tutorial/query-params-str-validations.md @@ -18,7 +18,7 @@ Tener `str | None` permitirá que tu editor te dé un mejor soporte y detecte er ## Validaciones adicionales { #additional-validation } -Vamos a hacer que, aunque `q` sea opcional, siempre que se proporcione, **su longitud no exceda los 50 caracteres**. +Vamos a hacer que, aunque `q` sea opcional, siempre que se proporcione, su longitud no exceda los 50 caracteres. ### Importar `Query` y `Annotated` { #import-query-and-annotated } @@ -47,40 +47,16 @@ Ahora es el momento de usarlo con FastAPI. 🚀 Teníamos esta anotación de tipo: -//// tab | Python 3.10+ - ```Python q: str | None = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Union[str, None] = None -``` - -//// - Lo que haremos es envolver eso con `Annotated`, para que se convierta en: -//// tab | Python 3.10+ - ```Python q: Annotated[str | None] = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Annotated[Union[str, None]] = None -``` - -//// - Ambas versiones significan lo mismo, `q` es un parámetro que puede ser un `str` o `None`, y por defecto, es `None`. Ahora vamos a lo divertido. 🎉 @@ -93,7 +69,7 @@ Ahora que tenemos este `Annotated` donde podemos poner más información (en est Nota que el valor por defecto sigue siendo `None`, por lo que el parámetro sigue siendo opcional. -Pero ahora, al tener `Query(max_length=50)` dentro de `Annotated`, le estamos diciendo a FastAPI que queremos que tenga **validación adicional** para este valor, queremos que tenga un máximo de 50 caracteres. 😎 +Pero ahora, al tener `Query(max_length=50)` dentro de `Annotated`, le estamos diciendo a FastAPI que queremos que tenga validación adicional para este valor, queremos que tenga un máximo de 50 caracteres. 😎 /// tip | Consejo @@ -103,13 +79,13 @@ Aquí estamos usando `Query()` porque este es un **parámetro de query**. Más a FastAPI ahora: -* **Validará** los datos asegurándose de que la longitud máxima sea de 50 caracteres -* Mostrará un **error claro** para el cliente cuando los datos no sean válidos -* **Documentará** el parámetro en el OpenAPI esquema *path operation* (así aparecerá en la **UI de documentación automática**) +* Validará los datos asegurándose de que la longitud máxima sea de 50 caracteres +* Mostrará un error claro para el cliente cuando los datos no sean válidos +* Documentará el parámetro en el OpenAPI esquema *path operation* (así aparecerá en la UI de documentación automática) ## Alternativa (antigua): `Query` como valor por defecto { #alternative-old-query-as-the-default-value } -Versiones anteriores de FastAPI (antes de 0.95.0) requerían que usaras `Query` como el valor por defecto de tu parámetro, en lugar de ponerlo en `Annotated`, hay una alta probabilidad de que veas código usándolo alrededor, así que te lo explicaré. +Versiones anteriores de FastAPI (antes de 0.95.0) requerían que usaras `Query` como el valor por defecto de tu parámetro, en lugar de ponerlo en `Annotated`, hay una alta probabilidad de que veas código usándolo alrededor, así que te lo explicaré. /// tip | Consejo @@ -174,13 +150,13 @@ q: str = Query(default="rick") ### Ventajas de `Annotated` { #advantages-of-annotated } -**Usar `Annotated` es recomendado** en lugar del valor por defecto en los parámetros de función, es **mejor** por múltiples razones. 🤓 +Usar `Annotated` es recomendado en lugar del valor por defecto en los parámetros de función, es mejor por múltiples razones. 🤓 -El valor **por defecto** del **parámetro de función** es el valor **real por defecto**, eso es más intuitivo con Python en general. 😌 +El valor por defecto del parámetro de función es el valor real por defecto, eso es más intuitivo con Python en general. 😌 -Podrías **llamar** a esa misma función en **otros lugares** sin FastAPI, y **funcionaría como se espera**. Si hay un parámetro **requerido** (sin un valor por defecto), tu **editor** te avisará con un error, **Python** también se quejará si lo ejecutas sin pasar el parámetro requerido. +Podrías llamar a esa misma función en otros lugares sin FastAPI, y funcionaría como se espera. Si hay un parámetro requerido (sin un valor por defecto), tu editor te avisará con un error, Python también se quejará si lo ejecutas sin pasar el parámetro requerido. -Cuando no usas `Annotated` y en su lugar usas el estilo de valor por defecto **(antiguo)**, si llamas a esa función sin FastAPI en **otros lugares**, tienes que **recordar** pasar los argumentos a la función para que funcione correctamente, de lo contrario, los valores serán diferentes de lo que esperas (por ejemplo, `QueryInfo` o algo similar en lugar de `str`). Y tu editor no se quejará, y Python no se quejará al ejecutar esa función, solo cuando los errores dentro de las operaciones hagan que funcione incorrectamente. +Cuando no usas `Annotated` y en su lugar usas el estilo de valor por defecto (antiguo), si llamas a esa función sin FastAPI en otros lugares, tienes que recordar pasar los argumentos a la función para que funcione correctamente, de lo contrario, los valores serán diferentes de lo que esperas (por ejemplo, `QueryInfo` o algo similar en lugar de `str`). Y tu editor no se quejará, y Python no se quejará al ejecutar esa función, solo cuando los errores dentro de las operaciones hagan que funcione incorrectamente. Dado que `Annotated` puede tener más de una anotación de metadato, ahora podrías incluso usar la misma función con otras herramientas, como Typer. 🚀 @@ -192,7 +168,7 @@ También puedes agregar un parámetro `min_length`: ## Agregar expresiones regulares { #add-regular-expressions } -Puedes definir una expresión regular `pattern` que el parámetro debe coincidir: +Puedes definir una expresión regular `pattern` que el parámetro debe coincidir: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -202,7 +178,7 @@ Este patrón específico de expresión regular comprueba que el valor recibido d * `fixedquery`: tiene el valor exacto `fixedquery`. * `$`: termina allí, no tiene más caracteres después de `fixedquery`. -Si te sientes perdido con todas estas ideas de **"expresión regular"**, no te preocupes. Son un tema difícil para muchas personas. Aún puedes hacer muchas cosas sin necesitar expresiones regulares todavía. +Si te sientes perdido con todas estas ideas de "expresión regular", no te preocupes. Son un tema difícil para muchas personas. Aún puedes hacer muchas cosas sin necesitar expresiones regulares todavía. Ahora sabes que cuando las necesites puedes usarlas en **FastAPI**. @@ -212,7 +188,7 @@ Puedes, por supuesto, usar valores por defecto diferentes de `None`. Digamos que quieres declarar el parámetro de query `q` para que tenga un `min_length` de `3`, y para que tenga un valor por defecto de `"fixedquery"`: -{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} /// note | Nota @@ -242,7 +218,7 @@ q: Annotated[str | None, Query(min_length=3)] = None Así que, cuando necesites declarar un valor como requerido mientras usas `Query`, simplemente puedes no declarar un valor por defecto: -{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} ### Requerido, puede ser `None` { #required-can-be-none } @@ -293,7 +269,7 @@ La documentación interactiva de API se actualizará en consecuencia, para permi También puedes definir un valor por defecto `list` de valores si no se proporciona ninguno: -{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} Si vas a: @@ -316,7 +292,7 @@ el valor por defecto de `q` será: `["foo", "bar"]` y tu response será: También puedes usar `list` directamente en lugar de `list[str]`: -{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} /// note | Nota @@ -372,7 +348,7 @@ Entonces puedes declarar un `alias`, y ese alias será usado para encontrar el v Ahora digamos que ya no te gusta este parámetro. -Tienes que dejarlo allí por un tiempo porque hay clientes usándolo, pero quieres que la documentación lo muestre claramente como deprecated. +Tienes que dejarlo allí por un tiempo porque hay clientes usándolo, pero quieres que la documentación lo muestre claramente como obsoleto. Luego pasa el parámetro `deprecated=True` a `Query`: @@ -390,9 +366,9 @@ Para excluir un parámetro de query del esquema de OpenAPI generado (y por lo ta ## Validación personalizada { #custom-validation } -Podría haber casos donde necesites hacer alguna **validación personalizada** que no puede hacerse con los parámetros mostrados arriba. +Podría haber casos donde necesites hacer alguna validación personalizada que no puede hacerse con los parámetros mostrados arriba. -En esos casos, puedes usar una **función validadora personalizada** que se aplique después de la validación normal (por ejemplo, después de validar que el valor es un `str`). +En esos casos, puedes usar una función validadora personalizada que se aplique después de la validación normal (por ejemplo, después de validar que el valor es un `str`). Puedes lograr eso usando `AfterValidator` de Pydantic dentro de `Annotated`. @@ -402,7 +378,7 @@ Pydantic también tiene ISBN o con `imdb-` para un ID de URL de película de IMDB: +Por ejemplo, este validador personalizado comprueba que el ID del ítem empiece con `isbn-` para un número de libro ISBN o con `imdb-` para un ID de URL de película de IMDB: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -414,15 +390,15 @@ Esto está disponible con Pydantic versión 2 o superior. 😎 /// tip | Consejo -Si necesitas hacer cualquier tipo de validación que requiera comunicarte con algún **componente externo**, como una base de datos u otra API, deberías usar **Dependencias de FastAPI**, las aprenderás más adelante. +Si necesitas hacer cualquier tipo de validación que requiera comunicarte con algún componente externo, como una base de datos u otra API, deberías usar Dependencias de FastAPI, las aprenderás más adelante. -Estos validadores personalizados son para cosas que pueden comprobarse **solo** con los **mismos datos** provistos en el request. +Estos validadores personalizados son para cosas que pueden comprobarse solo con los mismos datos provistos en el request. /// ### Entiende ese código { #understand-that-code } -El punto importante es solo usar **`AfterValidator` con una función dentro de `Annotated`**. Si quieres, sáltate esta parte. 🤸 +El punto importante es solo usar `AfterValidator` con una función dentro de `Annotated`. Si quieres, sáltate esta parte. 🤸 --- @@ -436,17 +412,17 @@ Pero si te da curiosidad este ejemplo de código específico y sigues entretenid #### Un ítem aleatorio { #a-random-item } -Con `data.items()` obtenemos un objeto iterable con tuplas que contienen la clave y el valor para cada elemento del diccionario. +Con `data.items()` obtenemos un objeto iterable con tuplas que contienen la clave y el valor para cada elemento del diccionario. Convertimos este objeto iterable en una `list` propiamente dicha con `list(data.items())`. -Luego con `random.choice()` podemos obtener un **valor aleatorio** de la lista, así que obtenemos una tupla con `(id, name)`. Será algo como `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. +Luego con `random.choice()` podemos obtener un valor aleatorio de la lista, así que obtenemos una tupla con `(id, name)`. Será algo como `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. -Luego **asignamos esos dos valores** de la tupla a las variables `id` y `name`. +Luego asignamos esos dos valores de la tupla a las variables `id` y `name`. Así, si el usuario no proporcionó un ID de ítem, aún recibirá una sugerencia aleatoria. -...hacemos todo esto en una **sola línea simple**. 🤯 ¿No te encanta Python? 🐍 +...hacemos todo esto en una sola línea simple. 🤯 ¿No te encanta Python? 🐍 {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} diff --git a/docs/es/docs/tutorial/query-params.md b/docs/es/docs/tutorial/query-params.md index 2b58a2b4b..edbe51a0e 100644 --- a/docs/es/docs/tutorial/query-params.md +++ b/docs/es/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ Cuando declaras otros parámetros de función que no son parte de los parámetros de path, son automáticamente interpretados como parámetros de "query". -{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} La query es el conjunto de pares clave-valor que van después del `?` en una URL, separados por caracteres `&`. @@ -24,7 +24,7 @@ Pero cuando los declaras con tipos de Python (en el ejemplo anterior, como `int` Todo el mismo proceso que se aplica para los parámetros de path también se aplica para los parámetros de query: * Soporte del editor (obviamente) -* "parsing" de datos +* "parsing" de datos * Validación de datos * Documentación automática @@ -127,7 +127,7 @@ Si no quieres agregar un valor específico pero solo hacer que sea opcional, est Pero cuando quieres hacer un parámetro de query requerido, simplemente no declares ningún valor por defecto: -{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} Aquí el parámetro de query `needy` es un parámetro de query requerido de tipo `str`. diff --git a/docs/es/docs/tutorial/request-files.md b/docs/es/docs/tutorial/request-files.md index cc99deb2e..717968d74 100644 --- a/docs/es/docs/tutorial/request-files.md +++ b/docs/es/docs/tutorial/request-files.md @@ -20,13 +20,13 @@ Esto es porque los archivos subidos se envían como "form data". Importa `File` y `UploadFile` desde `fastapi`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} ## Definir Parámetros `File` { #define-file-parameters } Crea parámetros de archivo de la misma manera que lo harías para `Body` o `Form`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} /// info | Información @@ -54,7 +54,7 @@ Pero hay varios casos en los que podrías beneficiarte de usar `UploadFile`. Define un parámetro de archivo con un tipo de `UploadFile`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} Usar `UploadFile` tiene varias ventajas sobre `bytes`: @@ -121,7 +121,7 @@ Los datos de los forms normalmente se codifican usando el "media type" `applicat Pero cuando el formulario incluye archivos, se codifica como `multipart/form-data`. Si usas `File`, **FastAPI** sabrá que tiene que obtener los archivos de la parte correcta del cuerpo. -Si deseas leer más sobre estas codificaciones y campos de formularios, dirígete a la MDN web docs para POST. +Si deseas leer más sobre estas codificaciones y campos de formularios, dirígete a la MDN web docs para POST. /// @@ -143,7 +143,7 @@ Puedes hacer un archivo opcional utilizando anotaciones de tipos estándar y est También puedes usar `File()` con `UploadFile`, por ejemplo, para establecer metadatos adicionales: -{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} ## Subidas de Múltiples Archivos { #multiple-file-uploads } @@ -151,9 +151,9 @@ Es posible subir varios archivos al mismo tiempo. Estarían asociados al mismo "campo de formulario" enviado usando "form data". -Para usar eso, declara una lista de `bytes` o `UploadFile`: +Para usar eso, declara una `list` de `bytes` o `UploadFile`: -{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} Recibirás, como se declaró, una `list` de `bytes` o `UploadFile`s. @@ -169,7 +169,7 @@ También podrías usar `from starlette.responses import HTMLResponse`. Y de la misma manera que antes, puedes usar `File()` para establecer parámetros adicionales, incluso para `UploadFile`: -{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} ## Recapitulación { #recap } diff --git a/docs/es/docs/tutorial/request-form-models.md b/docs/es/docs/tutorial/request-form-models.md index 1f4668c84..9afadf0b2 100644 --- a/docs/es/docs/tutorial/request-form-models.md +++ b/docs/es/docs/tutorial/request-form-models.md @@ -24,7 +24,7 @@ Esto es compatible desde la versión `0.113.0` de FastAPI. 🤓 Solo necesitas declarar un **modelo de Pydantic** con los campos que quieres recibir como **campos de formulario**, y luego declarar el parámetro como `Form`: -{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} **FastAPI** **extraerá** los datos de **cada campo** de los **form data** en el request y te dará el modelo de Pydantic que definiste. @@ -48,7 +48,7 @@ Esto es compatible desde la versión `0.114.0` de FastAPI. 🤓 Puedes usar la configuración del modelo de Pydantic para `forbid` cualquier campo `extra`: -{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} Si un cliente intenta enviar datos extra, recibirá un response de **error**. diff --git a/docs/es/docs/tutorial/request-forms-and-files.md b/docs/es/docs/tutorial/request-forms-and-files.md index 363553e86..738a2bc4b 100644 --- a/docs/es/docs/tutorial/request-forms-and-files.md +++ b/docs/es/docs/tutorial/request-forms-and-files.md @@ -16,13 +16,13 @@ $ pip install python-multipart ## Importa `File` y `Form` { #import-file-and-form } -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} ## Define parámetros `File` y `Form` { #define-file-and-form-parameters } Crea parámetros de archivo y formulario de la misma manera que lo harías para `Body` o `Query`: -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} Los archivos y campos de formulario se subirán como form data y recibirás los archivos y campos de formulario. diff --git a/docs/es/docs/tutorial/request-forms.md b/docs/es/docs/tutorial/request-forms.md index 33061e6a1..cc29296ee 100644 --- a/docs/es/docs/tutorial/request-forms.md +++ b/docs/es/docs/tutorial/request-forms.md @@ -18,17 +18,17 @@ $ pip install python-multipart Importar `Form` desde `fastapi`: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} ## Definir parámetros de `Form` { #define-form-parameters } Crea parámetros de formulario de la misma manera que lo harías para `Body` o `Query`: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} Por ejemplo, en una de las formas en las que se puede usar la especificación OAuth2 (llamada "password flow") se requiere enviar un `username` y `password` como campos de formulario. -La spec requiere que los campos se llamen exactamente `username` y `password`, y que se envíen como campos de formulario, no JSON. +La especificación requiere que los campos se llamen exactamente `username` y `password`, y que se envíen como campos de formulario, no JSON. Con `Form` puedes declarar las mismas configuraciones que con `Body` (y `Query`, `Path`, `Cookie`), incluyendo validación, ejemplos, un alias (por ejemplo, `user-name` en lugar de `username`), etc. @@ -56,7 +56,7 @@ Los datos de formularios normalmente se codifican usando el "media type" `applic Pero cuando el formulario incluye archivos, se codifica como `multipart/form-data`. Leerás sobre la gestión de archivos en el próximo capítulo. -Si quieres leer más sobre estas codificaciones y campos de formulario, dirígete a la MDN web docs para POST. +Si quieres leer más sobre estas codificaciones y campos de formulario, dirígete a la MDN web docs para POST. /// diff --git a/docs/es/docs/tutorial/response-model.md b/docs/es/docs/tutorial/response-model.md index 8cfe69e78..c9e931d47 100644 --- a/docs/es/docs/tutorial/response-model.md +++ b/docs/es/docs/tutorial/response-model.md @@ -51,7 +51,7 @@ FastAPI usará este `response_model` para hacer toda la documentación de datos, /// tip | Consejo -Si tienes chequeos estrictos de tipos en tu editor, mypy, etc., puedes declarar el tipo de retorno de la función como `Any`. +Si tienes chequeo de tipos estricto en tu editor, mypy, etc., puedes declarar el tipo de retorno de la función como `Any`. De esa manera le dices al editor que intencionalmente estás devolviendo cualquier cosa. Pero FastAPI todavía hará la documentación de datos, validación, filtrado, etc. con `response_model`. @@ -183,7 +183,7 @@ Podría haber casos en los que devuelvas algo que no es un campo válido de Pyda El caso más común sería [devolver un Response directamente como se explica más adelante en la documentación avanzada](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} Este caso simple es manejado automáticamente por FastAPI porque la anotación del tipo de retorno es la clase (o una subclase de) `Response`. @@ -193,7 +193,7 @@ Y las herramientas también estarán felices porque tanto `RedirectResponse` com También puedes usar una subclase de `Response` en la anotación del tipo: -{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} Esto también funcionará porque `RedirectResponse` es una subclase de `Response`, y FastAPI manejará automáticamente este caso simple. @@ -201,7 +201,7 @@ Esto también funcionará porque `RedirectResponse` es una subclase de `Response Pero cuando devuelves algún otro objeto arbitrario que no es un tipo válido de Pydantic (por ejemplo, un objeto de base de datos) y lo anotas así en la función, FastAPI intentará crear un modelo de response de Pydantic a partir de esa anotación de tipo, y fallará. -Lo mismo sucedería si tuvieras algo como un union entre diferentes tipos donde uno o más de ellos no son tipos válidos de Pydantic, por ejemplo esto fallaría 💥: +Lo mismo sucedería si tuvieras algo como una unión entre diferentes tipos donde uno o más de ellos no son tipos válidos de Pydantic, por ejemplo esto fallaría 💥: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} diff --git a/docs/es/docs/tutorial/response-status-code.md b/docs/es/docs/tutorial/response-status-code.md index dce69f9bc..35235eb88 100644 --- a/docs/es/docs/tutorial/response-status-code.md +++ b/docs/es/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ De la misma manera que puedes especificar un modelo de response, también puedes * `@app.delete()` * etc. -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} /// note | Nota @@ -66,7 +66,7 @@ En breve: /// tip | Consejo -Para saber más sobre cada código de estado y qué código es para qué, revisa la documentación de MDN sobre códigos de estado HTTP. +Para saber más sobre cada código de estado y qué código es para qué, revisa la documentación de MDN sobre códigos de estado HTTP. /// @@ -74,7 +74,7 @@ Para saber más sobre cada código de estado y qué código es para qué, revisa Veamos de nuevo el ejemplo anterior: -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} `201` es el código de estado para "Created". @@ -82,7 +82,7 @@ Pero no tienes que memorizar lo que significa cada uno de estos códigos. Puedes usar las variables de conveniencia de `fastapi.status`. -{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} Son solo una conveniencia, mantienen el mismo número, pero de esa manera puedes usar el autocompletado del editor para encontrarlos: diff --git a/docs/es/docs/tutorial/schema-extra-example.md b/docs/es/docs/tutorial/schema-extra-example.md index 396a2a6bf..9af826138 100644 --- a/docs/es/docs/tutorial/schema-extra-example.md +++ b/docs/es/docs/tutorial/schema-extra-example.md @@ -74,7 +74,7 @@ Por supuesto, también puedes pasar múltiples `examples`: Cuando haces esto, los ejemplos serán parte del **JSON Schema** interno para esos datos del body. -Sin embargo, al momento de escribir esto, Swagger UI, la herramienta encargada de mostrar la interfaz de documentación, no soporta mostrar múltiples ejemplos para los datos en **JSON Schema**. Pero lee más abajo para una solución alternativa. +Sin embargo, al momento de escribir esto, Swagger UI, la herramienta encargada de mostrar la interfaz de documentación, no soporta mostrar múltiples ejemplos para los datos en **JSON Schema**. Pero lee más abajo para una solución alternativa. ### `examples` específicos de OpenAPI { #openapi-specific-examples } diff --git a/docs/es/docs/tutorial/security/first-steps.md b/docs/es/docs/tutorial/security/first-steps.md index 604adedad..909f14765 100644 --- a/docs/es/docs/tutorial/security/first-steps.md +++ b/docs/es/docs/tutorial/security/first-steps.md @@ -20,7 +20,7 @@ Primero solo usemos el código y veamos cómo funciona, y luego volveremos para Copia el ejemplo en un archivo `main.py`: -{* ../../docs_src/security/tutorial001_an_py39.py *} +{* ../../docs_src/security/tutorial001_an_py310.py *} ## Ejecútalo { #run-it } @@ -132,7 +132,7 @@ En ese caso, **FastAPI** también te proporciona las herramientas para construir Cuando creamos una instance de la clase `OAuth2PasswordBearer` pasamos el parámetro `tokenUrl`. Este parámetro contiene la URL que el cliente (el frontend corriendo en el navegador del usuario) usará para enviar el `username` y `password` a fin de obtener un token. -{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} /// tip | Consejo @@ -170,7 +170,7 @@ Así que, puede usarse con `Depends`. Ahora puedes pasar ese `oauth2_scheme` en una dependencia con `Depends`. -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} Esta dependencia proporcionará un `str` que se asigna al parámetro `token` de la *path operation function*. diff --git a/docs/es/docs/tutorial/security/get-current-user.md b/docs/es/docs/tutorial/security/get-current-user.md index ced002f59..67b6c5835 100644 --- a/docs/es/docs/tutorial/security/get-current-user.md +++ b/docs/es/docs/tutorial/security/get-current-user.md @@ -2,7 +2,7 @@ En el capítulo anterior, el sistema de seguridad (que se basa en el sistema de inyección de dependencias) le estaba dando a la *path operation function* un `token` como un `str`: -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} Pero eso aún no es tan útil. Vamos a hacer que nos dé el usuario actual. diff --git a/docs/es/docs/tutorial/security/oauth2-jwt.md b/docs/es/docs/tutorial/security/oauth2-jwt.md index f7004ffb2..e481fb646 100644 --- a/docs/es/docs/tutorial/security/oauth2-jwt.md +++ b/docs/es/docs/tutorial/security/oauth2-jwt.md @@ -116,7 +116,11 @@ Y otra utilidad para verificar si una contraseña recibida coincide con el hash Y otra más para autenticar y devolver un usuario. -{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +Cuando `authenticate_user` se llama con un nombre de usuario que no existe en la base de datos, aun así ejecutamos `verify_password` contra un hash ficticio. + +Esto asegura que el endpoint tarda aproximadamente la misma cantidad de tiempo en responder tanto si el nombre de usuario es válido como si no, previniendo los **timing attacks** que podrían usarse para enumerar nombres de usuario existentes. /// note | Nota @@ -152,7 +156,7 @@ Define un Modelo de Pydantic que se usará en el endpoint de token para el respo Crea una función de utilidad para generar un nuevo token de acceso. -{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} ## Actualizar las dependencias { #update-the-dependencies } @@ -162,7 +166,7 @@ Decodifica el token recibido, verifícalo y devuelve el usuario actual. Si el token es inválido, devuelve un error HTTP de inmediato. -{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} ## Actualizar la *path operation* `/token` { #update-the-token-path-operation } @@ -170,7 +174,7 @@ Crea un `timedelta` con el tiempo de expiración del token. Crea un verdadero token de acceso JWT y devuélvelo. -{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} ### Detalles técnicos sobre el "sujeto" `sub` de JWT { #technical-details-about-the-jwt-subject-sub } diff --git a/docs/es/docs/tutorial/sql-databases.md b/docs/es/docs/tutorial/sql-databases.md index 6273c7059..b57ebdbbe 100644 --- a/docs/es/docs/tutorial/sql-databases.md +++ b/docs/es/docs/tutorial/sql-databases.md @@ -8,7 +8,7 @@ Aquí veremos un ejemplo usando "ORMs"), FastAPI no te obliga a usar nada. 😎 +Puedes usar cualquier otro paquete de bases de datos SQL o NoSQL que quieras (en algunos casos llamadas "ORMs"), FastAPI no te obliga a usar nada. 😎 /// @@ -354,4 +354,4 @@ Si vas a la interfaz de `/docs` de la API, verás que ahora está actualizada, y Puedes usar **SQLModel** para interactuar con una base de datos SQL y simplificar el código con *modelos de datos* y *modelos de tablas*. -Puedes aprender mucho más en la documentación de **SQLModel**, hay un mini tutorial sobre el uso de SQLModel con **FastAPI**. 🚀 +Puedes aprender mucho más en la documentación de **SQLModel**, hay un mini tutorial más largo sobre el uso de SQLModel con **FastAPI**. 🚀 diff --git a/docs/es/docs/tutorial/static-files.md b/docs/es/docs/tutorial/static-files.md index f1badd2af..84d2e94a9 100644 --- a/docs/es/docs/tutorial/static-files.md +++ b/docs/es/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ Puedes servir archivos estáticos automáticamente desde un directorio utilizand * Importa `StaticFiles`. * "Monta" una instance de `StaticFiles()` en un path específico. -{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} /// note | Detalles Técnicos diff --git a/docs/es/docs/tutorial/testing.md b/docs/es/docs/tutorial/testing.md index 555da55bf..c47712903 100644 --- a/docs/es/docs/tutorial/testing.md +++ b/docs/es/docs/tutorial/testing.md @@ -30,7 +30,7 @@ Usa el objeto `TestClient` de la misma manera que con `httpx`. Escribe declaraciones `assert` simples con las expresiones estándar de Python que necesites revisar (otra vez, estándar de `pytest`). -{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} /// tip | Consejo @@ -75,7 +75,7 @@ Digamos que tienes una estructura de archivos como se describe en [Aplicaciones En el archivo `main.py` tienes tu aplicación de **FastAPI**: -{* ../../docs_src/app_testing/app_a_py39/main.py *} +{* ../../docs_src/app_testing/app_a_py310/main.py *} ### Archivo de prueba { #testing-file } @@ -91,7 +91,7 @@ Entonces podrías tener un archivo `test_main.py` con tus pruebas. Podría estar Debido a que este archivo está en el mismo paquete, puedes usar importaciones relativas para importar el objeto `app` desde el módulo `main` (`main.py`): -{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} ...y tener el código para las pruebas tal como antes. diff --git a/docs/es/docs/virtual-environments.md b/docs/es/docs/virtual-environments.md index 8c90c28f0..f8839ac6c 100644 --- a/docs/es/docs/virtual-environments.md +++ b/docs/es/docs/virtual-environments.md @@ -53,7 +53,7 @@ $ cd awesome-project ## Crea un Entorno Virtual { #create-a-virtual-environment } -Cuando empiezas a trabajar en un proyecto de Python **por primera vez**, crea un entorno virtual **dentro de tu proyecto**. +Cuando empiezas a trabajar en un proyecto de Python **por primera vez**, crea un entorno virtual **dentro de tu proyecto**. /// tip | Consejo @@ -170,9 +170,9 @@ Esto asegura que si usas un programa de **terminal ( Date: Fri, 13 Feb 2026 13:42:06 +0000 Subject: [PATCH 125/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index af7e7f27d..deee633c2 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for es (update-all and add-missing). PR [#14911](https://github.com/fastapi/fastapi/pull/14911) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for zh (update-all). PR [#14917](https://github.com/fastapi/fastapi/pull/14917) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for uk (update-all). PR [#14914](https://github.com/fastapi/fastapi/pull/14914) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for tr (update-all). PR [#14913](https://github.com/fastapi/fastapi/pull/14913) by [@YuriiMotov](https://github.com/YuriiMotov). From 0d1f4d935b43628c966903d1e213d1bffa860953 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:48:53 +0100 Subject: [PATCH 126/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20pt=20(update-all)=20(#14912)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all * 🎨 Auto format --------- Co-authored-by: github-actions[bot] --- docs/pt/docs/_llm-test.md | 8 +- docs/pt/docs/advanced/additional-responses.md | 8 +- .../pt/docs/advanced/advanced-dependencies.md | 8 +- .../pt/docs/advanced/advanced-python-types.md | 61 +++++ docs/pt/docs/advanced/async-tests.md | 8 +- docs/pt/docs/advanced/behind-a-proxy.md | 18 +- docs/pt/docs/advanced/custom-response.md | 32 +-- docs/pt/docs/advanced/dataclasses.md | 2 +- docs/pt/docs/advanced/events.md | 18 +- docs/pt/docs/advanced/generate-clients.md | 10 +- docs/pt/docs/advanced/index.md | 2 +- docs/pt/docs/advanced/middleware.md | 8 +- docs/pt/docs/advanced/openapi-callbacks.md | 4 +- docs/pt/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 16 +- .../advanced/response-change-status-code.md | 4 +- docs/pt/docs/advanced/response-cookies.md | 4 +- docs/pt/docs/advanced/response-directly.md | 4 +- docs/pt/docs/advanced/response-headers.md | 6 +- .../docs/advanced/security/http-basic-auth.md | 6 +- .../docs/advanced/security/oauth2-scopes.md | 2 +- docs/pt/docs/advanced/settings.md | 20 +- docs/pt/docs/advanced/sub-applications.md | 6 +- docs/pt/docs/advanced/templates.md | 2 +- docs/pt/docs/advanced/testing-dependencies.md | 3 +- docs/pt/docs/advanced/testing-events.md | 4 +- docs/pt/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 4 +- docs/pt/docs/advanced/websockets.md | 8 +- docs/pt/docs/advanced/wsgi.md | 2 +- docs/pt/docs/alternatives.md | 10 +- docs/pt/docs/benchmarks.md | 2 +- docs/pt/docs/deployment/cloud.md | 2 +- docs/pt/docs/deployment/docker.md | 12 +- docs/pt/docs/deployment/https.md | 8 +- docs/pt/docs/deployment/index.md | 2 +- docs/pt/docs/deployment/manually.md | 2 +- docs/pt/docs/deployment/versions.md | 4 +- docs/pt/docs/fastapi-cli.md | 2 +- docs/pt/docs/features.md | 12 +- docs/pt/docs/history-design-future.md | 4 +- .../authentication-error-status-code.md | 2 +- docs/pt/docs/how-to/conditional-openapi.md | 4 +- docs/pt/docs/how-to/configure-swagger-ui.md | 6 +- docs/pt/docs/how-to/custom-docs-ui-assets.md | 18 +- docs/pt/docs/how-to/extending-openapi.md | 10 +- docs/pt/docs/how-to/general.md | 2 +- docs/pt/docs/how-to/graphql.md | 2 +- docs/pt/docs/how-to/index.md | 4 +- docs/pt/docs/index.md | 10 +- docs/pt/docs/python-types.md | 222 +++++------------- docs/pt/docs/translation-banner.md | 11 + docs/pt/docs/tutorial/background-tasks.md | 6 +- docs/pt/docs/tutorial/bigger-applications.md | 48 ++-- docs/pt/docs/tutorial/body-multiple-params.md | 6 - docs/pt/docs/tutorial/body-nested-models.md | 4 +- docs/pt/docs/tutorial/body.md | 8 +- docs/pt/docs/tutorial/cookie-param-models.md | 10 +- docs/pt/docs/tutorial/cors.md | 3 +- docs/pt/docs/tutorial/debugging.md | 4 +- .../dependencies/classes-as-dependencies.md | 24 +- ...pendencies-in-path-operation-decorators.md | 10 +- .../dependencies/dependencies-with-yield.md | 24 +- .../dependencies/global-dependencies.md | 2 +- docs/pt/docs/tutorial/dependencies/index.md | 12 +- .../tutorial/dependencies/sub-dependencies.md | 6 +- docs/pt/docs/tutorial/extra-models.md | 8 +- docs/pt/docs/tutorial/first-steps.md | 16 +- docs/pt/docs/tutorial/handling-errors.md | 20 +- docs/pt/docs/tutorial/header-params.md | 2 +- docs/pt/docs/tutorial/metadata.md | 16 +- docs/pt/docs/tutorial/middleware.md | 6 +- .../tutorial/path-operation-configuration.md | 8 +- .../path-params-numeric-validations.md | 16 +- docs/pt/docs/tutorial/path-params.md | 32 +-- docs/pt/docs/tutorial/query-param-models.md | 11 +- .../tutorial/query-params-str-validations.md | 88 +++---- docs/pt/docs/tutorial/query-params.md | 6 +- docs/pt/docs/tutorial/request-files.md | 16 +- docs/pt/docs/tutorial/request-form-models.md | 6 +- .../docs/tutorial/request-forms-and-files.md | 8 +- docs/pt/docs/tutorial/request-forms.md | 6 +- docs/pt/docs/tutorial/response-model.md | 8 +- docs/pt/docs/tutorial/response-status-code.md | 8 +- docs/pt/docs/tutorial/schema-extra-example.md | 2 +- docs/pt/docs/tutorial/security/first-steps.md | 8 +- .../tutorial/security/get-current-user.md | 2 +- docs/pt/docs/tutorial/security/index.md | 3 +- docs/pt/docs/tutorial/security/oauth2-jwt.md | 12 +- docs/pt/docs/tutorial/static-files.md | 2 +- docs/pt/docs/tutorial/testing.md | 6 +- docs/pt/docs/virtual-environments.md | 12 +- 92 files changed, 529 insertions(+), 599 deletions(-) create mode 100644 docs/pt/docs/advanced/advanced-python-types.md create mode 100644 docs/pt/docs/translation-banner.md diff --git a/docs/pt/docs/_llm-test.md b/docs/pt/docs/_llm-test.md index 3a2801fdd..9f03c4b88 100644 --- a/docs/pt/docs/_llm-test.md +++ b/docs/pt/docs/_llm-test.md @@ -104,8 +104,8 @@ $ cd code ...e um exemplo de código Python... ```Python -wont_work() # This won't work 😱 -works(foo="bar") # This works 🎉 +wont_work() # Isto não vai funcionar 😱 +works(foo="bar") # Isto funciona 🎉 ``` ...e é isso. @@ -204,8 +204,8 @@ Aqui estão algumas coisas envolvidas em elementos HTML "abbr" (algumas são inv ### O abbr fornece uma frase completa e uma explicação { #the-abbr-gives-a-full-phrase-and-an-explanation } -* MDN -* I/O. +* MDN +* I/O. //// diff --git a/docs/pt/docs/advanced/additional-responses.md b/docs/pt/docs/advanced/additional-responses.md index 688bc1696..2e277ac10 100644 --- a/docs/pt/docs/advanced/additional-responses.md +++ b/docs/pt/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ O **FastAPI** pegará este modelo, gerará o esquema JSON dele e incluirá no lo Por exemplo, para declarar um outro retorno com o status code `404` e um modelo do Pydantic chamado `Message`, você pode escrever: -{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} /// note | Nota @@ -203,7 +203,7 @@ Por exemplo, você pode declarar um retorno com o código de status `404` que ut E um retorno com o código de status `200` que utiliza o seu `response_model`, porém inclui um `example` customizado: -{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} Isso será combinado e incluído em seu OpenAPI, e disponibilizado na documentação da sua API: @@ -243,5 +243,5 @@ Por exemplo: Para verificar exatamente o que você pode incluir nos retornos, você pode conferir estas seções na especificação do OpenAPI: -* Objeto de Retorno OpenAPI, inclui o `Response Object`. -* Objeto de Retorno OpenAPI, você pode incluir qualquer coisa dele diretamente em cada retorno dentro do seu parâmetro `responses`. Incluindo `description`, `headers`, `content` (dentro dele que você declara diferentes media types e esquemas JSON), e `links`. +* Objeto de Retornos do OpenAPI, inclui o `Response Object`. +* Objeto de Retorno do OpenAPI, você pode incluir qualquer coisa dele diretamente em cada retorno dentro do seu parâmetro `responses`. Incluindo `description`, `headers`, `content` (dentro dele que você declara diferentes media types e esquemas JSON), e `links`. diff --git a/docs/pt/docs/advanced/advanced-dependencies.md b/docs/pt/docs/advanced/advanced-dependencies.md index 959a7f3c2..419a092c8 100644 --- a/docs/pt/docs/advanced/advanced-dependencies.md +++ b/docs/pt/docs/advanced/advanced-dependencies.md @@ -18,7 +18,7 @@ Não propriamente a classe (que já é um chamável), mas a instância desta cla Para fazer isso, nós declaramos o método `__call__`: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} Neste caso, o `__call__` é o que o **FastAPI** utilizará para verificar parâmetros adicionais e sub dependências, e isso é o que será chamado para passar o valor ao parâmetro na sua *função de operação de rota* posteriormente. @@ -26,7 +26,7 @@ Neste caso, o `__call__` é o que o **FastAPI** utilizará para verificar parâm E agora, nós podemos utilizar o `__init__` para declarar os parâmetros da instância que podemos utilizar para "parametrizar" a dependência: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} Neste caso, o **FastAPI** nunca tocará ou se importará com o `__init__`, nós vamos utilizar diretamente em nosso código. @@ -34,7 +34,7 @@ Neste caso, o **FastAPI** nunca tocará ou se importará com o `__init__`, nós Nós poderíamos criar uma instância desta classe com: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} E deste modo nós podemos "parametrizar" a nossa dependência, que agora possui `"bar"` dentro dele, como o atributo `checker.fixed_content`. @@ -50,7 +50,7 @@ checker(q="somequery") ...e passar o que quer que isso retorne como valor da dependência em nossa *função de operação de rota* como o parâmetro `fixed_content_included`: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} /// tip | Dica diff --git a/docs/pt/docs/advanced/advanced-python-types.md b/docs/pt/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..f92a20bd4 --- /dev/null +++ b/docs/pt/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# Tipos Avançados de Python { #advanced-python-types } + +Aqui estão algumas ideias adicionais que podem ser úteis ao trabalhar com tipos em Python. + +## Usando `Union` ou `Optional` { #using-union-or-optional } + +Se, por algum motivo, seu código não puder usar `|`, por exemplo, se não for em uma anotação de tipo, mas em algo como `response_model=`, em vez de usar a barra vertical (`|`) você pode usar `Union` do `typing`. + +Por exemplo, você poderia declarar que algo pode ser `str` ou `None`: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +O `typing` também tem um atalho para declarar que algo pode ser `None`, com `Optional`. + +Aqui vai uma dica do meu ponto de vista bem subjetivo: + +* 🚨 Evite usar `Optional[SomeType]` +* Em vez disso ✨ use **`Union[SomeType, None]`** ✨. + +Ambos são equivalentes e, por baixo, são a mesma coisa, mas eu recomendaria `Union` em vez de `Optional` porque a palavra "opcional" sugere que o valor é opcional; na verdade, significa "pode ser `None`", mesmo quando não é opcional e continua sendo obrigatório. + +Acho que `Union[SomeType, None]` é mais explícito quanto ao significado. + +É apenas uma questão de palavras e nomes. Mas essas palavras podem influenciar como você e sua equipe pensam sobre o código. + +Como exemplo, veja esta função: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +O parâmetro `name` é definido como `Optional[str]`, mas não é opcional; não é possível chamar a função sem o parâmetro: + +```Python +say_hi() # Ah, não, isso gera um erro! 😱 +``` + +O parâmetro `name` continua obrigatório (não é opcional) porque não tem valor padrão. Ainda assim, `name` aceita `None` como valor: + +```Python +say_hi(name=None) # Isso funciona, None é válido 🎉 +``` + +A boa notícia é que, na maioria dos casos, você poderá simplesmente usar `|` para definir uniões de tipos: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +Então, normalmente você não precisa se preocupar com nomes como `Optional` e `Union`. 😎 diff --git a/docs/pt/docs/advanced/async-tests.md b/docs/pt/docs/advanced/async-tests.md index 953ebedd9..2fe678adb 100644 --- a/docs/pt/docs/advanced/async-tests.md +++ b/docs/pt/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ Para um exemplos simples, vamos considerar uma estrutura de arquivos semelhante O arquivo `main.py` teria: -{* ../../docs_src/async_tests/app_a_py39/main.py *} +{* ../../docs_src/async_tests/app_a_py310/main.py *} O arquivo `test_main.py` teria os testes para para o arquivo `main.py`, ele poderia ficar assim: -{* ../../docs_src/async_tests/app_a_py39/test_main.py *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} ## Executá-lo { #run-it } @@ -56,7 +56,7 @@ $ pytest O marcador `@pytest.mark.anyio` informa ao pytest que esta função de teste deve ser invocada de maneira assíncrona: -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} /// tip | Dica @@ -66,7 +66,7 @@ Note que a função de teste é `async def` agora, no lugar de apenas `def` como Então podemos criar um `AsyncClient` com a aplicação, e enviar requisições assíncronas para ela utilizando `await`. -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} Isso é equivalente a: diff --git a/docs/pt/docs/advanced/behind-a-proxy.md b/docs/pt/docs/advanced/behind-a-proxy.md index bf0d12428..158141515 100644 --- a/docs/pt/docs/advanced/behind-a-proxy.md +++ b/docs/pt/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" Por exemplo, suponha que você defina uma *operação de rota* `/items/`: -{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} Se o cliente tentar ir para `/items`, por padrão, ele seria redirecionado para `/items/`. @@ -91,9 +91,9 @@ O **proxy** intercepta a requisição original do cliente e adiciona os headers Esses headers preservam informações sobre a requisição original que, de outra forma, seriam perdidas: -* X-Forwarded-For: o endereço IP original do cliente -* X-Forwarded-Proto: o protocolo original (`https`) -* X-Forwarded-Host: o host original (`mysuperapp.com`) +* **X-Forwarded-For**: o endereço IP original do cliente +* **X-Forwarded-Proto**: o protocolo original (`https`) +* **X-Forwarded-Host**: o host original (`mysuperapp.com`) Quando a **CLI do FastAPI** é configurada com `--forwarded-allow-ips`, ela confia nesses headers e os utiliza, por exemplo, para gerar as URLs corretas em redirecionamentos. @@ -115,7 +115,7 @@ Nesse caso, o path original `/app` seria servido em `/api/v1/app`. Embora todo o seu código esteja escrito assumindo que existe apenas `/app`. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} E o proxy estaria **"removendo"** o **prefixo de path** dinamicamente antes de transmitir a solicitação para o servidor da aplicação (provavelmente Uvicorn via CLI do FastAPI), mantendo sua aplicação convencida de que está sendo servida em `/app`, para que você não precise atualizar todo o seu código para incluir o prefixo `/api/v1`. @@ -193,7 +193,7 @@ Você pode obter o `root_path` atual usado pela sua aplicação para cada solici Aqui estamos incluindo-o na mensagem apenas para fins de demonstração. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} Então, se você iniciar o Uvicorn com: @@ -220,7 +220,7 @@ A resposta seria algo como: Alternativamente, se você não tiver uma maneira de fornecer uma opção de linha de comando como `--root-path` ou equivalente, você pode definir o parâmetro `root_path` ao criar sua aplicação FastAPI: -{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} Passar o `root_path` para `FastAPI` seria o equivalente a passar a opção de linha de comando `--root-path` para Uvicorn ou Hypercorn. @@ -400,7 +400,7 @@ Se você passar uma lista personalizada de `servers` e houver um `root_path` (po Por exemplo: -{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} Gerará um OpenAPI schema como: @@ -455,7 +455,7 @@ Se você não especificar o parâmetro `servers` e `root_path` for igual a `/`, Se você não quiser que o **FastAPI** inclua um servidor automático usando o `root_path`, você pode usar o parâmetro `root_path_in_servers=False`: -{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} e então ele não será incluído no OpenAPI schema. diff --git a/docs/pt/docs/advanced/custom-response.md b/docs/pt/docs/advanced/custom-response.md index 5f26390c2..a409f1dc8 100644 --- a/docs/pt/docs/advanced/custom-response.md +++ b/docs/pt/docs/advanced/custom-response.md @@ -30,7 +30,7 @@ Isso ocorre por que, por padrão, o FastAPI irá verificar cada item dentro do d Mas se você tem certeza que o conteúdo que você está retornando é **serializável com JSON**, você pode passá-lo diretamente para a classe de resposta e evitar o trabalho extra que o FastAPI teria ao passar o conteúdo pelo `jsonable_encoder` antes de passar para a classe de resposta. -{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} /// info | Informação @@ -55,7 +55,7 @@ Para retornar uma resposta com HTML diretamente do **FastAPI**, utilize `HTMLRes * Importe `HTMLResponse` * Passe `HTMLResponse` como o parâmetro de `response_class` do seu *decorador de operação de rota*. -{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} /// info | Informação @@ -73,7 +73,7 @@ Como visto em [Retornando uma Resposta Diretamente](response-directly.md){.inter O mesmo exemplo de antes, retornando uma `HTMLResponse`, poderia parecer com: -{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} /// warning | Atenção @@ -97,7 +97,7 @@ A `response_class` será usada apenas para documentar o OpenAPI da *operação d Por exemplo, poderia ser algo como: -{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} Neste exemplo, a função `generate_html_response()` já cria e retorna uma `Response` em vez de retornar o HTML em uma `str`. @@ -136,7 +136,7 @@ Ela aceita os seguintes parâmetros: O FastAPI (Starlette, na verdade) irá incluir o cabeçalho Content-Length automaticamente. Ele também irá incluir o cabeçalho Content-Type, baseado no `media_type` e acrescentando uma codificação para tipos textuais. -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ### `HTMLResponse` { #htmlresponse } @@ -146,7 +146,7 @@ Usa algum texto ou sequência de bytes e retorna uma resposta HTML. Como você l Usa algum texto ou sequência de bytes para retornar uma resposta de texto não formatado. -{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} ### `JSONResponse` { #jsonresponse } @@ -180,7 +180,7 @@ Essa resposta requer a instalação do pacote `ujson`, com o comando `pip instal /// -{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} /// tip | Dica @@ -194,13 +194,13 @@ Retorna um redirecionamento HTTP. Utiliza o código de status 307 (Redirecioname Você pode retornar uma `RedirectResponse` diretamente: -{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} --- Ou você pode utilizá-la no parâmetro `response_class`: -{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} Se você fizer isso, então você pode retornar a URL diretamente da sua *função de operação de rota* @@ -210,13 +210,13 @@ Neste caso, o `status_code` utilizada será o padrão de `RedirectResponse`, que Você também pode utilizar o parâmetro `status_code` combinado com o parâmetro `response_class`: -{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} ### `StreamingResponse` { #streamingresponse } Recebe um gerador assíncrono ou um gerador/iterador comum e retorna o corpo da resposta de forma contínua (stream). -{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} #### Utilizando `StreamingResponse` com objetos semelhantes a arquivos { #using-streamingresponse-with-file-like-objects } @@ -226,7 +226,7 @@ Dessa forma, você não precisa ler todo o arquivo na memória primeiro, e você Isso inclui muitas bibliotecas que interagem com armazenamento em nuvem, processamento de vídeos, entre outras. -{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} 1. Essa é a função geradora. É definida como "função geradora" porque contém declarações `yield` nela. 2. Ao utilizar o bloco `with`, nós garantimos que o objeto semelhante a um arquivo é fechado após a função geradora ser finalizada. Isto é, após a resposta terminar de ser enviada. @@ -255,11 +255,11 @@ Recebe um conjunto de argumentos do construtor diferente dos outros tipos de res Respostas de Arquivos incluem o tamanho do arquivo, data da última modificação e ETags apropriados, nos cabeçalhos `Content-Length`, `Last-Modified` e `ETag`, respectivamente. -{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} Você também pode usar o parâmetro `response_class`: -{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} Nesse caso, você pode retornar o caminho do arquivo diretamente da sua *função de operação de rota*. @@ -273,7 +273,7 @@ Vamos supor também que você queira retornar um JSON indentado e formatado, ent Você poderia criar uma classe `CustomORJSONResponse`. A principal coisa a ser feita é sobrecarregar o método render da classe Response, `Response.render(content)`, que retorna o conteúdo em bytes: -{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} Agora em vez de retornar: @@ -299,7 +299,7 @@ O padrão que define isso é o `default_response_class`. No exemplo abaixo, o **FastAPI** irá utilizar `ORJSONResponse` por padrão, em todas as *operações de rota*, em vez de `JSONResponse`. -{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} /// tip | Dica diff --git a/docs/pt/docs/advanced/dataclasses.md b/docs/pt/docs/advanced/dataclasses.md index 6dc9feb29..c2af6fac6 100644 --- a/docs/pt/docs/advanced/dataclasses.md +++ b/docs/pt/docs/advanced/dataclasses.md @@ -64,7 +64,7 @@ Nesse caso, você pode simplesmente trocar as `dataclasses` padrão por `pydanti 6. Aqui estamos retornando um dicionário que contém `items`, que é uma lista de dataclasses. - O FastAPI ainda é capaz de serializar os dados para JSON. + O FastAPI ainda é capaz de serializar os dados para JSON. 7. Aqui o `response_model` está usando uma anotação de tipo de uma lista de dataclasses `Author`. diff --git a/docs/pt/docs/advanced/events.md b/docs/pt/docs/advanced/events.md index 8cdc35828..551053508 100644 --- a/docs/pt/docs/advanced/events.md +++ b/docs/pt/docs/advanced/events.md @@ -6,13 +6,13 @@ Da mesma forma, você pode definir a lógica (código) que deve ser executada qu Como esse código é executado antes de a aplicação **começar** a receber requisições e logo depois que ela **termina** de lidar com as requisições, ele cobre todo o **lifespan** da aplicação (a palavra "lifespan" será importante em um segundo 😉). -Isso pode ser muito útil para configurar **recursos** que você precisa usar por toda a aplicação, e que são **compartilhados** entre as requisições e/ou que você precisa **limpar** depois. Por exemplo, um pool de conexões com o banco de dados ou o carregamento de um modelo de machine learning compartilhado. +Isso pode ser muito útil para configurar **recursos** que você precisa usar por toda a aplicação, e que são **compartilhados** entre as requisições e/ou que você precisa **limpar** depois. Por exemplo, um pool de conexões com o banco de dados ou o carregamento de um modelo de Aprendizado de Máquina compartilhado. ## Caso de uso { #use-case } Vamos começar com um exemplo de **caso de uso** e então ver como resolvê-lo com isso. -Vamos imaginar que você tem alguns **modelos de machine learning** que deseja usar para lidar com as requisições. 🤖 +Vamos imaginar que você tem alguns **modelos de Aprendizado de Máquina** que deseja usar para lidar com as requisições. 🤖 Os mesmos modelos são compartilhados entre as requisições, então não é um modelo por requisição, ou um por usuário, ou algo parecido. @@ -30,9 +30,9 @@ Vamos começar com um exemplo e depois ver em detalhes. Nós criamos uma função assíncrona `lifespan()` com `yield` assim: -{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} -Aqui estamos simulando a operação de *inicialização* custosa de carregar o modelo, colocando a (falsa) função do modelo no dicionário com modelos de machine learning antes do `yield`. Esse código será executado **antes** de a aplicação **começar a receber requisições**, durante a *inicialização*. +Aqui estamos simulando a operação de *inicialização* custosa de carregar o modelo, colocando a (falsa) função do modelo no dicionário com modelos de Aprendizado de Máquina antes do `yield`. Esse código será executado **antes** de a aplicação **começar a receber requisições**, durante a *inicialização*. E então, logo após o `yield`, descarregamos o modelo. Esse código será executado **depois** de a aplicação **terminar de lidar com as requisições**, pouco antes do *encerramento*. Isso poderia, por exemplo, liberar recursos como memória ou uma GPU. @@ -48,7 +48,7 @@ Talvez você precise iniciar uma nova versão, ou apenas cansou de executá-la. A primeira coisa a notar é que estamos definindo uma função assíncrona com `yield`. Isso é muito semelhante a Dependências com `yield`. -{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} A primeira parte da função, antes do `yield`, será executada **antes** de a aplicação iniciar. @@ -60,7 +60,7 @@ Se você verificar, a função está decorada com um `@asynccontextmanager`. Isso converte a função em algo chamado "**gerenciador de contexto assíncrono**". -{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} Um **gerenciador de contexto** em Python é algo que você pode usar em uma declaração `with`, por exemplo, `open()` pode ser usado como um gerenciador de contexto: @@ -82,7 +82,7 @@ No nosso exemplo de código acima, não o usamos diretamente, mas passamos para O parâmetro `lifespan` da aplicação `FastAPI` aceita um **gerenciador de contexto assíncrono**, então podemos passar para ele nosso novo gerenciador de contexto assíncrono `lifespan`. -{* ../../docs_src/events/tutorial003_py39.py hl[22] *} +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} ## Eventos alternativos (descontinuados) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ Essas funções podem ser declaradas com `async def` ou `def` normal. Para adicionar uma função que deve rodar antes de a aplicação iniciar, declare-a com o evento `"startup"`: -{* ../../docs_src/events/tutorial001_py39.py hl[8] *} +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} Nesse caso, a função de manipulador do evento `startup` inicializará os itens do "banco de dados" (apenas um `dict`) com alguns valores. @@ -116,7 +116,7 @@ E sua aplicação não começará a receber requisições até que todos os mani Para adicionar uma função que deve ser executada quando a aplicação estiver encerrando, declare-a com o evento `"shutdown"`: -{* ../../docs_src/events/tutorial002_py39.py hl[6] *} +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} Aqui, a função de manipulador do evento `shutdown` escreverá uma linha de texto `"Application shutdown"` no arquivo `log.txt`. diff --git a/docs/pt/docs/advanced/generate-clients.md b/docs/pt/docs/advanced/generate-clients.md index 5134bc7cb..c6c7785a0 100644 --- a/docs/pt/docs/advanced/generate-clients.md +++ b/docs/pt/docs/advanced/generate-clients.md @@ -40,7 +40,7 @@ Algumas dessas soluções também podem ser open source ou oferecer planos gratu Vamos começar com uma aplicação FastAPI simples: -{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} Note que as *operações de rota* definem os modelos que usam para o corpo da requisição e o corpo da resposta, usando os modelos `Item` e `ResponseMessage`. @@ -98,7 +98,7 @@ Em muitos casos, sua aplicação FastAPI será maior, e você provavelmente usar Por exemplo, você poderia ter uma seção para **items** e outra seção para **users**, e elas poderiam ser separadas por tags: -{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} ### Gere um cliente TypeScript com Tags { #generate-a-typescript-client-with-tags } @@ -141,11 +141,11 @@ O FastAPI usa um **ID exclusivo** para cada *operação de rota*, ele é usado p Você pode personalizar essa função. Ela recebe uma `APIRoute` e retorna uma string. -Por exemplo, aqui está usando a primeira tag (você provavelmente terá apenas uma tag) e o nome da *operação de rota* (o nome da função). +Por exemplo, aqui está usando a primeira tag (Você provavelmente terá apenas uma tag) e o nome da *operação de rota* (o nome da função). Você pode então passar essa função personalizada para o **FastAPI** como o parâmetro `generate_unique_id_function`: -{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} ### Gere um cliente TypeScript com IDs de operação personalizados { #generate-a-typescript-client-with-custom-operation-ids } @@ -167,7 +167,7 @@ Mas para o cliente gerado, poderíamos **modificar** os IDs de operação do Ope Poderíamos baixar o JSON do OpenAPI para um arquivo `openapi.json` e então poderíamos **remover essa tag prefixada** com um script como este: -{* ../../docs_src/generate_clients/tutorial004_py39.py *} +{* ../../docs_src/generate_clients/tutorial004_py310.py *} //// tab | Node.js diff --git a/docs/pt/docs/advanced/index.md b/docs/pt/docs/advanced/index.md index 23e551074..d2727be43 100644 --- a/docs/pt/docs/advanced/index.md +++ b/docs/pt/docs/advanced/index.md @@ -8,7 +8,7 @@ Nas próximas seções você verá outras opções, configurações, e recursos /// tip | Dica -As próximas seções **não são necessáriamente "avançadas"** +As próximas seções **não são necessariamente "avançadas"**. E é possível que para seu caso de uso, a solução esteja em uma delas. diff --git a/docs/pt/docs/advanced/middleware.md b/docs/pt/docs/advanced/middleware.md index 30c183479..6bc4bfd2f 100644 --- a/docs/pt/docs/advanced/middleware.md +++ b/docs/pt/docs/advanced/middleware.md @@ -57,13 +57,13 @@ Garante que todas as requisições devem ser `https` ou `wss`. Qualquer requisição para `http` ou `ws` será redirecionada para o esquema seguro. -{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} ## `TrustedHostMiddleware` { #trustedhostmiddleware } Garante que todas as requisições recebidas tenham um cabeçalho `Host` corretamente configurado, a fim de proteger contra ataques de cabeçalho de host HTTP. -{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} Os seguintes argumentos são suportados: @@ -78,7 +78,7 @@ Gerencia respostas GZip para qualquer requisição que inclua `"gzip"` no cabeç O middleware lidará com respostas padrão e de streaming. -{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} Os seguintes argumentos são suportados: @@ -91,7 +91,7 @@ Há muitos outros middlewares ASGI. Por exemplo: -* Uvicorn's `ProxyHeadersMiddleware` +* `ProxyHeadersMiddleware` do Uvicorn * MessagePack Para checar outros middlewares disponíveis, confira Documentação de Middlewares do Starlette e a Lista Incrível do ASGI. diff --git a/docs/pt/docs/advanced/openapi-callbacks.md b/docs/pt/docs/advanced/openapi-callbacks.md index 57c8c5e81..653c26d99 100644 --- a/docs/pt/docs/advanced/openapi-callbacks.md +++ b/docs/pt/docs/advanced/openapi-callbacks.md @@ -106,11 +106,11 @@ Ela deve parecer exatamente como uma *operação de rota* normal do FastAPI: Há 2 diferenças principais de uma *operação de rota* normal: * Ela não necessita ter nenhum código real, porque seu aplicativo nunca chamará esse código. Ele é usado apenas para documentar a *API externa*. Então, a função poderia ter apenas `pass`. -* O *path* pode conter uma expressão OpenAPI 3 (veja mais abaixo) em que pode usar variáveis com parâmetros e partes da solicitação original enviada para *sua API*. +* O *path* pode conter uma expressão OpenAPI 3 (veja mais abaixo) em que pode usar variáveis com parâmetros e partes do request original enviado para *sua API*. ### A expressão do path do callback { #the-callback-path-expression } -O *path* do callback pode ter uma expressão OpenAPI 3 que pode conter partes da solicitação original enviada para *sua API*. +O *path* do callback pode ter uma expressão OpenAPI 3 que pode conter partes do request original enviado para *sua API*. Nesse caso, é a `str`: diff --git a/docs/pt/docs/advanced/openapi-webhooks.md b/docs/pt/docs/advanced/openapi-webhooks.md index 011898e8c..ed0d702b2 100644 --- a/docs/pt/docs/advanced/openapi-webhooks.md +++ b/docs/pt/docs/advanced/openapi-webhooks.md @@ -32,7 +32,7 @@ Webhooks estão disponíveis a partir do OpenAPI 3.1.0, e possui suporte do Fast Quando você cria uma aplicação com o **FastAPI**, existe um atributo chamado `webhooks`, que você utilizar para defini-los da mesma maneira que você definiria as suas **operações de rotas**, utilizando por exemplo `@app.webhooks.post()`. -{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} Os webhooks que você define aparecerão no esquema do **OpenAPI** e na **página de documentação** gerada automaticamente. diff --git a/docs/pt/docs/advanced/path-operation-advanced-configuration.md b/docs/pt/docs/advanced/path-operation-advanced-configuration.md index b3af116a2..c4dd3cbe7 100644 --- a/docs/pt/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/pt/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ Você pode definir o `operationId` do OpenAPI que será utilizado na sua *opera Você deveria ter certeza que ele é único para cada operação. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} ### Utilizando o nome da *função de operação de rota* como o operationId { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ Se você quiser utilizar o nome das funções da sua API como `operationId`s, vo Você deveria fazer isso depois de adicionar todas as suas *operações de rota*. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} /// tip | Dica @@ -40,7 +40,7 @@ Mesmo que elas estejam em módulos (arquivos Python) diferentes. Para excluir uma *operação de rota* do esquema OpenAPI gerado (e por consequência, dos sistemas de documentação automáticos), utilize o parâmetro `include_in_schema` e defina ele como `False`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} ## Descrição avançada a partir de docstring { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ Você pode estender o esquema do OpenAPI para uma *operação de rota* utilizand Esse parâmetro `openapi_extra` pode ser útil, por exemplo, para declarar [Extensões do OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): -{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *} Se você abrir os documentos criados automaticamente para a API, sua extensão aparecerá no final da *operação de rota* específica. @@ -139,9 +139,9 @@ Por exemplo, você poderia decidir ler e validar a requisição com seu próprio Você pode fazer isso com `openapi_extra`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *} -Nesse exemplo, nós não declaramos nenhum modelo do Pydantic. Na verdade, o corpo da requisição não está nem mesmo analisado como JSON, ele é lido diretamente como `bytes`, e a função `magic_data_reader()` seria a responsável por analisar ele de alguma forma. +Nesse exemplo, nós não declaramos nenhum modelo do Pydantic. Na verdade, o corpo da requisição não está nem mesmo analisado como JSON, ele é lido diretamente como `bytes`, e a função `magic_data_reader()` seria a responsável por analisá-lo de alguma forma. De toda forma, nós podemos declarar o esquema esperado para o corpo da requisição. @@ -153,7 +153,7 @@ E você pode fazer isso até mesmo quando o tipo de dados na requisição não Por exemplo, nesta aplicação nós não usamos a funcionalidade integrada ao FastAPI de extrair o JSON Schema dos modelos Pydantic nem a validação automática para JSON. Na verdade, estamos declarando o tipo de conteúdo da requisição como YAML, em vez de JSON: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *} Entretanto, mesmo que não utilizemos a funcionalidade integrada por padrão, ainda estamos usando um modelo Pydantic para gerar um JSON Schema manualmente para os dados que queremos receber em YAML. @@ -161,7 +161,7 @@ Então utilizamos a requisição diretamente e extraímos o corpo como `bytes`. E então no nosso código, nós analisamos o conteúdo YAML diretamente e, em seguida, estamos usando novamente o mesmo modelo Pydantic para validar o conteúdo YAML: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *} /// tip | Dica diff --git a/docs/pt/docs/advanced/response-change-status-code.md b/docs/pt/docs/advanced/response-change-status-code.md index ee81f0bfc..76d9462a8 100644 --- a/docs/pt/docs/advanced/response-change-status-code.md +++ b/docs/pt/docs/advanced/response-change-status-code.md @@ -18,9 +18,9 @@ Para estes casos, você pode utilizar um parâmetro `Response`. Você pode declarar um parâmetro do tipo `Response` em sua *função de operação de rota* (assim como você pode fazer para cookies e headers). -E então você pode definir o `status_code` neste objeto de retorno temporal. +E então você pode definir o `status_code` neste objeto de retorno *temporal*. -{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *} E então você pode retornar qualquer objeto que você precise, como você faria normalmente (um `dict`, um modelo de banco de dados, etc.). diff --git a/docs/pt/docs/advanced/response-cookies.md b/docs/pt/docs/advanced/response-cookies.md index 67820b433..ae9660743 100644 --- a/docs/pt/docs/advanced/response-cookies.md +++ b/docs/pt/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ Você pode declarar um parâmetro do tipo `Response` na sua *função de operaç E então você pode definir cookies nesse objeto de resposta *temporário*. -{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *} Em seguida, você pode retornar qualquer objeto que precise, como normalmente faria (um `dict`, um modelo de banco de dados, etc). @@ -24,7 +24,7 @@ Para fazer isso, você pode criar uma resposta como descrito em [Retorne uma Res Então, defina os cookies nela e a retorne: -{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *} /// tip | Dica diff --git a/docs/pt/docs/advanced/response-directly.md b/docs/pt/docs/advanced/response-directly.md index bbbef2f91..311aba56c 100644 --- a/docs/pt/docs/advanced/response-directly.md +++ b/docs/pt/docs/advanced/response-directly.md @@ -2,7 +2,7 @@ Quando você cria uma *operação de rota* no **FastAPI** você pode retornar qualquer dado nela: um dicionário (`dict`), uma lista (`list`), um modelo do Pydantic ou do seu banco de dados, etc. -Por padrão, o **FastAPI** irá converter automaticamente o valor do retorno para JSON utilizando o `jsonable_encoder` explicado em [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}. +Por padrão, o **FastAPI** irá converter automaticamente o valor do retorno para JSON utilizando o `jsonable_encoder` explicado em [Codificador Compatível com JSON](../tutorial/encoder.md){.internal-link target=_blank}. Então, por baixo dos panos, ele incluiria esses dados compatíveis com JSON (e.g. um `dict`) dentro de uma `JSONResponse` que é utilizada para enviar uma resposta para o cliente. @@ -54,7 +54,7 @@ Vamos dizer que você quer retornar uma resposta usando o prefixo `X-`. -Porém, se voce tiver cabeçalhos personalizados que deseja que um cliente no navegador possa ver, você precisa adicioná-los às suas configurações de CORS (saiba mais em [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), usando o parâmetro `expose_headers` descrito na documentação de CORS do Starlette. +Porém, se você tiver cabeçalhos personalizados que deseja que um cliente no navegador possa ver, você precisa adicioná-los às suas configurações de CORS (saiba mais em [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), usando o parâmetro `expose_headers` descrito na documentação de CORS do Starlette. diff --git a/docs/pt/docs/advanced/security/http-basic-auth.md b/docs/pt/docs/advanced/security/http-basic-auth.md index bd572217b..0ebdb1eb9 100644 --- a/docs/pt/docs/advanced/security/http-basic-auth.md +++ b/docs/pt/docs/advanced/security/http-basic-auth.md @@ -20,7 +20,7 @@ Então, quando você digitar o usuário e senha, o navegador os envia automatica * Isso retorna um objeto do tipo `HTTPBasicCredentials`: * Isto contém o `username` e o `password` enviado. -{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} +{* ../../docs_src/security/tutorial006_an_py310.py hl[4,8,12] *} Quando você tentar abrir a URL pela primeira vez (ou clicar no botão "Executar" na documentação) o navegador vai pedir pelo seu usuário e senha: @@ -40,7 +40,7 @@ Para lidar com isso, primeiramente nós convertemos o `username` e o `password` Então nós podemos utilizar o `secrets.compare_digest()` para garantir que o `credentials.username` é `"stanleyjobson"`, e que o `credentials.password` é `"swordfish"`. -{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} +{* ../../docs_src/security/tutorial007_an_py310.py hl[1,12:24] *} Isso seria parecido com: @@ -104,4 +104,4 @@ Deste modo, ao utilizar `secrets.compare_digest()` no código de sua aplicação Após detectar que as credenciais estão incorretas, retorne um `HTTPException` com o status 401 (o mesmo retornado quando nenhuma credencial foi informada) e adicione o cabeçalho `WWW-Authenticate` para fazer com que o navegador mostre o prompt de login novamente: -{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} +{* ../../docs_src/security/tutorial007_an_py310.py hl[26:30] *} diff --git a/docs/pt/docs/advanced/security/oauth2-scopes.md b/docs/pt/docs/advanced/security/oauth2-scopes.md index 591ac9b4a..0a0c785a0 100644 --- a/docs/pt/docs/advanced/security/oauth2-scopes.md +++ b/docs/pt/docs/advanced/security/oauth2-scopes.md @@ -94,7 +94,7 @@ E nós retornamos os escopos como parte do token JWT. Para manter as coisas simples, aqui nós estamos apenas adicionando os escopos recebidos diretamente ao token. -Porém em sua aplicação, por segurança, você deve garantir que você apenas adiciona os escopos que o usuário possui permissão de fato, ou aqueles que você predefiniu. +Porém em sua aplicação, por segurança, você deveria garantir que você apenas adiciona os escopos que o usuário possui permissão de fato, ou aqueles que você predefiniu. /// diff --git a/docs/pt/docs/advanced/settings.md b/docs/pt/docs/advanced/settings.md index 28411269b..88e7f591c 100644 --- a/docs/pt/docs/advanced/settings.md +++ b/docs/pt/docs/advanced/settings.md @@ -54,7 +54,7 @@ Da mesma forma que com modelos do Pydantic, você declara atributos de classe co Você pode usar as mesmas funcionalidades e ferramentas de validação que usa em modelos do Pydantic, como diferentes tipos de dados e validações adicionais com `Field()`. -{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} +{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *} /// tip | Dica @@ -70,7 +70,7 @@ Em seguida, ele converterá e validará os dados. Assim, quando você usar esse Depois você pode usar o novo objeto `settings` na sua aplicação: -{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *} +{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *} ### Executar o servidor { #run-the-server } @@ -104,11 +104,11 @@ Você pode colocar essas configurações em outro arquivo de módulo como visto Por exemplo, você poderia ter um arquivo `config.py` com: -{* ../../docs_src/settings/app01_py39/config.py *} +{* ../../docs_src/settings/app01_py310/config.py *} E então usá-lo em um arquivo `main.py`: -{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *} +{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *} /// tip | Dica @@ -126,7 +126,7 @@ Isso pode ser especialmente útil durante os testes, pois é muito fácil sobres Vindo do exemplo anterior, seu arquivo `config.py` poderia ser assim: -{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} +{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *} Perceba que agora não criamos uma instância padrão `settings = Settings()`. @@ -134,7 +134,7 @@ Perceba que agora não criamos uma instância padrão `settings = Settings()`. Agora criamos uma dependência que retorna um novo `config.Settings()`. -{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} +{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *} /// tip | Dica @@ -146,13 +146,13 @@ Por enquanto, você pode assumir que `get_settings()` é uma função normal. E então podemos exigi-la na *função de operação de rota* como dependência e usá-la onde for necessário. -{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} +{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *} ### Configurações e testes { #settings-and-testing } Então seria muito fácil fornecer um objeto de configurações diferente durante os testes criando uma sobrescrita de dependência para `get_settings`: -{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} +{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *} Na sobrescrita da dependência definimos um novo valor para `admin_email` ao criar o novo objeto `Settings`, e então retornamos esse novo objeto. @@ -193,7 +193,7 @@ APP_NAME="ChimichangApp" E então atualizar seu `config.py` com: -{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} +{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *} /// tip | Dica @@ -226,7 +226,7 @@ criaríamos esse objeto para cada requisição e leríamos o arquivo `.env` para Mas como estamos usando o decorador `@lru_cache` por cima, o objeto `Settings` será criado apenas uma vez, na primeira vez em que for chamado. ✔️ -{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} +{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *} Em qualquer chamada subsequente de `get_settings()` nas dependências das próximas requisições, em vez de executar o código interno de `get_settings()` e criar um novo objeto `Settings`, ele retornará o mesmo objeto que foi retornado na primeira chamada, repetidamente. diff --git a/docs/pt/docs/advanced/sub-applications.md b/docs/pt/docs/advanced/sub-applications.md index c61d1e92a..7f176e98d 100644 --- a/docs/pt/docs/advanced/sub-applications.md +++ b/docs/pt/docs/advanced/sub-applications.md @@ -10,7 +10,7 @@ Se você precisar ter duas aplicações FastAPI independentes, cada uma com seu Primeiro, crie a aplicação principal, de nível superior, **FastAPI**, e suas *operações de rota*: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *} ### Sub-aplicação { #sub-application } @@ -18,7 +18,7 @@ Em seguida, crie sua sub-aplicação e suas *operações de rota*. Essa sub-aplicação é apenas outra aplicação FastAPI padrão, mas esta é a que será "montada": -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *} ### Monte a sub-aplicação { #mount-the-sub-application } @@ -26,7 +26,7 @@ Na sua aplicação de nível superior, `app`, monte a sub-aplicação, `subapi`. Neste caso, ela será montada no path `/subapi`: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *} ### Verifique a documentação automática da API { #check-the-automatic-api-docs } diff --git a/docs/pt/docs/advanced/templates.md b/docs/pt/docs/advanced/templates.md index eb64e72bb..843727f4f 100644 --- a/docs/pt/docs/advanced/templates.md +++ b/docs/pt/docs/advanced/templates.md @@ -27,7 +27,7 @@ $ pip install jinja2 * Declare um parâmetro `Request` no *path operation* que retornará um template. * Use o `templates` que você criou para renderizar e retornar uma `TemplateResponse`, passe o nome do template, o objeto `request` e um dicionário "context" com pares chave-valor a serem usados dentro do template do Jinja2. -{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *} +{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *} /// note | Nota diff --git a/docs/pt/docs/advanced/testing-dependencies.md b/docs/pt/docs/advanced/testing-dependencies.md index 52b12dddb..f68cdc3a4 100644 --- a/docs/pt/docs/advanced/testing-dependencies.md +++ b/docs/pt/docs/advanced/testing-dependencies.md @@ -36,7 +36,7 @@ Você pode definir uma sobreposição de dependência para uma dependência que A dependência original pode estar sendo utilizada em uma *função de operação de rota*, um *decorador de operação de rota* (quando você não utiliza o valor retornado), uma chamada ao `.include_router()`, etc. -O FastAPI ainda poderá sobrescrevê-lo. +O FastAPI ainda poderá sobrescrevê-la. /// @@ -46,6 +46,7 @@ E então você pode redefinir as suas sobreposições (removê-las) definindo o app.dependency_overrides = {} ``` + /// tip | Dica Se você quer sobrepor uma dependência apenas para alguns testes, você pode definir a sobreposição no início do teste (dentro da função de teste) e reiniciá-la ao final (no final da função de teste). diff --git a/docs/pt/docs/advanced/testing-events.md b/docs/pt/docs/advanced/testing-events.md index 971b43763..56c5d45c8 100644 --- a/docs/pt/docs/advanced/testing-events.md +++ b/docs/pt/docs/advanced/testing-events.md @@ -2,10 +2,10 @@ Quando você precisa que o `lifespan` seja executado em seus testes, você pode utilizar o `TestClient` com a instrução `with`: -{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *} +{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *} Você pode ler mais detalhes sobre o ["Executando lifespan em testes no site oficial da documentação do Starlette."](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) Para os eventos `startup` e `shutdown` descontinuados, você pode usar o `TestClient` da seguinte forma: -{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *} +{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *} diff --git a/docs/pt/docs/advanced/testing-websockets.md b/docs/pt/docs/advanced/testing-websockets.md index d9d1723a6..ffb0ba338 100644 --- a/docs/pt/docs/advanced/testing-websockets.md +++ b/docs/pt/docs/advanced/testing-websockets.md @@ -4,7 +4,7 @@ Você pode usar o mesmo `TestClient` para testar WebSockets. Para isso, você utiliza o `TestClient` dentro de uma instrução `with`, conectando com o WebSocket: -{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *} +{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *} /// note | Nota diff --git a/docs/pt/docs/advanced/using-request-directly.md b/docs/pt/docs/advanced/using-request-directly.md index ab1ef9ff4..283a831d9 100644 --- a/docs/pt/docs/advanced/using-request-directly.md +++ b/docs/pt/docs/advanced/using-request-directly.md @@ -5,7 +5,7 @@ Até agora você declarou as partes da requisição que você precisa utilizando Obtendo dados de: * O path como parâmetros. -* Cabeçalhos (*Headers*). +* Cabeçalhos. * Cookies. * etc. @@ -29,7 +29,7 @@ Vamos imaginar que você deseja obter o endereço de IP/host do cliente dentro d Para isso você precisa acessar a requisição diretamente. -{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *} +{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *} Ao declarar o parâmetro com o tipo sendo um `Request` em sua *função de operação de rota*, o **FastAPI** saberá como passar o `Request` neste parâmetro. diff --git a/docs/pt/docs/advanced/websockets.md b/docs/pt/docs/advanced/websockets.md index 021a73bed..c294b7603 100644 --- a/docs/pt/docs/advanced/websockets.md +++ b/docs/pt/docs/advanced/websockets.md @@ -38,13 +38,13 @@ Na produção, você teria uma das opções acima. Mas é a maneira mais simples de focar no lado do servidor de WebSockets e ter um exemplo funcional: -{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} ## Crie um `websocket` { #create-a-websocket } Em sua aplicação **FastAPI**, crie um `websocket`: -{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} /// note | Detalhes Técnicos @@ -58,7 +58,7 @@ A **FastAPI** fornece o mesmo `WebSocket` diretamente apenas como uma conveniên Em sua rota WebSocket você pode esperar (`await`) por mensagens e enviar mensagens. -{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} Você pode receber e enviar dados binários, de texto e JSON. @@ -154,7 +154,7 @@ Com isso você pode conectar o WebSocket e então enviar e receber mensagens: Quando uma conexão WebSocket é fechada, o `await websocket.receive_text()` levantará uma exceção `WebSocketDisconnect`, que você pode então capturar e lidar como neste exemplo. -{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} +{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} Para testar: diff --git a/docs/pt/docs/advanced/wsgi.md b/docs/pt/docs/advanced/wsgi.md index 99b783cdb..3178b85eb 100644 --- a/docs/pt/docs/advanced/wsgi.md +++ b/docs/pt/docs/advanced/wsgi.md @@ -18,7 +18,7 @@ Em seguida, encapsule a aplicação WSGI (e.g. Flask) com o middleware. E então monte isso sob um path. -{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} +{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *} /// note | Nota diff --git a/docs/pt/docs/alternatives.md b/docs/pt/docs/alternatives.md index fd992ec95..17ef260dd 100644 --- a/docs/pt/docs/alternatives.md +++ b/docs/pt/docs/alternatives.md @@ -20,7 +20,7 @@ Mas em algum momento, não havia outra opção senão criar algo que fornecesse É relativamente bem acoplado com bancos de dados relacionais (como MySQL ou PostgreSQL), então, ter um banco de dados NoSQL (como Couchbase, MongoDB, Cassandra, etc.) como mecanismo principal de armazenamento não é muito fácil. -Foi criado para gerar o HTML no backend, não para criar APIs usadas por um frontend moderno (como React, Vue.js e Angular) ou por outros sistemas (como dispositivos IoT) comunicando com ele. +Foi criado para gerar o HTML no backend, não para criar APIs usadas por um frontend moderno (como React, Vue.js e Angular) ou por outros sistemas (como dispositivos IoT) comunicando com ele. ### Django REST Framework { #django-rest-framework } @@ -137,7 +137,7 @@ Existem vários Flask REST frameworks, mas depois de investir tempo e trabalho i ### Marshmallow { #marshmallow } -Uma das principais funcionalidades necessárias em sistemas de API é a "serialização" de dados, que é pegar dados do código (Python) e convertê-los em algo que possa ser enviado pela rede. Por exemplo, converter um objeto contendo dados de um banco de dados em um objeto JSON. Converter objetos `datetime` em strings, etc. +Uma das principais funcionalidades necessárias em sistemas de API é a "serialização" de dados, que é pegar dados do código (Python) e convertê-los em algo que possa ser enviado pela rede. Por exemplo, converter um objeto contendo dados de um banco de dados em um objeto JSON. Converter objetos `datetime` em strings, etc. Outra grande funcionalidade necessária pelas APIs é a validação de dados, garantindo que os dados são válidos, dados certos parâmetros. Por exemplo, que algum campo seja `int`, e não alguma string aleatória. Isso é especialmente útil para dados de entrada. @@ -145,7 +145,7 @@ Sem um sistema de validação de dados, você teria que realizar todas as verifi Essas funcionalidades são o que o Marshmallow foi construído para fornecer. É uma ótima biblioteca, e eu a utilizei bastante antes. -Mas ele foi criado antes de existirem as anotações de tipo do Python. Então, para definir cada schema você precisa utilizar utilitários e classes específicos fornecidos pelo Marshmallow. +Mas ele foi criado antes de existirem as anotações de tipo do Python. Então, para definir cada schema você precisa utilizar utilitários e classes específicos fornecidos pelo Marshmallow. /// check | **FastAPI** inspirado para @@ -155,7 +155,7 @@ Usar código para definir "schemas" que forneçam, automaticamente, tipos de dad ### Webargs { #webargs } -Outra grande funcionalidade requerida pelas APIs é o parsing de dados vindos de requisições de entrada. +Outra grande funcionalidade requerida pelas APIs é o parsing de dados vindos de requisições de entrada. Webargs é uma ferramenta feita para fornecer isso no topo de vários frameworks, inclusive Flask. @@ -419,7 +419,7 @@ Controlar toda a validação de dados, serialização de dados e documentação ### Starlette { #starlette } -Starlette é um framework/caixa de ferramentas ASGI leve, o que é ideal para construir serviços asyncio de alta performance. +Starlette é um framework/caixa de ferramentas ASGI leve, o que é ideal para construir serviços asyncio de alta performance. Ele é muito simples e intuitivo. É projetado para ser facilmente extensível, e ter componentes modulares. diff --git a/docs/pt/docs/benchmarks.md b/docs/pt/docs/benchmarks.md index c0b0c4c46..a54df3d9d 100644 --- a/docs/pt/docs/benchmarks.md +++ b/docs/pt/docs/benchmarks.md @@ -29,6 +29,6 @@ A hierarquia segue assim: * **FastAPI**: * Do mesmo modo que Starlette utiliza Uvicorn e não pode ser mais rápido que ele, **FastAPI** utiliza o Starlette, então não tem como ser mais rápido do que o Starlette. * FastAPI fornece mais recursos acima do Starlette. Recursos que você quase sempre precisará quando construir APIs, como validação de dados e serialização. E utilizando eles, você terá uma documentação automática de graça (a documentação automática nem sequer adiciona peso para rodar as aplicações, ela é gerada na inicialização). - * Se você nunca utilizou FastAPI mas utilizou diretamente o Starlette (ou outra ferramenta, como Sanic, Flask, Responder, etc) você teria que implementar toda validação de dados e serialização por conta. Então, sua aplicação final poderia ainda ter a mesma sobrecarga como se fosse desenvolvida com FastAPI. Em muitos casos, a validação de dados e serialização é o maior pedaço de código escrito em aplicações. + * Se você não utilizasse o FastAPI e utilizasse diretamente o Starlette (ou outra ferramenta, como Sanic, Flask, Responder, etc), você teria que implementar toda a validação de dados e serialização por conta. Então, sua aplicação final poderia ainda ter a mesma sobrecarga como se fosse desenvolvida com FastAPI. Em muitos casos, a validação de dados e serialização é o maior pedaço de código escrito em aplicações. * Então, ao utilizar o FastAPI você estará economizando tempo de desenvolvimento, evitará _bugs_, linhas de código, e você provavelmente terá a mesma performance (ou melhor) do que não utilizá-lo (já que você teria que implementar tudo isso em seu código). * Se você quer fazer comparações com o FastAPI, compare com um _framework_ (ou conjunto de ferramentas) para aplicações _web_ que forneça validação de dados, serialização e documentação, como Flask-apispec, NestJS, Molten, etc. _Frameworks_ com validação de dados automática, serialização e documentação integradas. diff --git a/docs/pt/docs/deployment/cloud.md b/docs/pt/docs/deployment/cloud.md index 419fd7626..2e181146b 100644 --- a/docs/pt/docs/deployment/cloud.md +++ b/docs/pt/docs/deployment/cloud.md @@ -1,6 +1,6 @@ # Implantar FastAPI em provedores de nuvem { #deploy-fastapi-on-cloud-providers } -Você pode usar praticamente **qualquer provedor de nuvem** para implantar seu aplicativo FastAPI. +Você pode usar praticamente **qualquer provedor de nuvem** para implantar sua aplicação FastAPI. Na maioria dos casos, os principais provedores de nuvem têm tutoriais para implantar o FastAPI com eles. diff --git a/docs/pt/docs/deployment/docker.md b/docs/pt/docs/deployment/docker.md index d47a15394..4663e96a1 100644 --- a/docs/pt/docs/deployment/docker.md +++ b/docs/pt/docs/deployment/docker.md @@ -14,7 +14,7 @@ Está com pressa e já sabe dessas coisas? Pode ir direto para o [`Dockerfile` a Visualização do Dockerfile 👀 ```Dockerfile -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -166,7 +166,7 @@ Agora, no mesmo diretório do projeto, crie um arquivo `Dockerfile` com: ```{ .dockerfile .annotate } # (1)! -FROM python:3.9 +FROM python:3.14 # (2)! WORKDIR /code @@ -390,7 +390,7 @@ Se seu FastAPI for um único arquivo, por exemplo, `main.py` sem um diretório ` Então você só teria que alterar os caminhos correspondentes para copiar o arquivo dentro do `Dockerfile`: ```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -454,7 +454,7 @@ Sem usar contêineres, fazer aplicativos executarem na inicialização e com rei ## Replicação - Número de Processos { #replication-number-of-processes } -Se você tiver um cluster de máquinas com **Kubernetes**, Docker Swarm Mode, Nomad ou outro sistema complexo semelhante para gerenciar contêineres distribuídos em várias máquinas, então provavelmente desejará **lidar com a replicação** no **nível do cluster** em vez de usar um **gerenciador de processos** (como Uvicorn com workers) em cada contêiner. +Se você tiver um cluster de máquinas com **Kubernetes**, Docker Swarm Mode, Nomad ou outro sistema complexo semelhante para gerenciar contêineres distribuídos em várias máquinas, então provavelmente desejará **lidar com a replicação** no **nível do cluster** em vez de usar um **gerenciador de processos** (como Uvicorn com workers) em cada contêiner. Um desses sistemas de gerenciamento de contêineres distribuídos como o Kubernetes normalmente tem alguma maneira integrada de lidar com a **replicação de contêineres** enquanto ainda oferece **balanceamento de carga** para as solicitações recebidas. Tudo no **nível do cluster**. @@ -499,7 +499,7 @@ Claro, existem **casos especiais** em que você pode querer ter **um contêiner* Nesses casos, você pode usar a opção de linha de comando `--workers` para definir o número de workers que deseja executar: ```{ .dockerfile .annotate } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -570,7 +570,7 @@ Se você tiver uma configuração simples, com um **único contêiner** que ent ### Imagem Docker base { #base-docker-image } -Antes havia uma imagem oficial do FastAPI para Docker: tiangolo/uvicorn-gunicorn-fastapi-docker. Mas agora ela está descontinuada. ⛔️ +Antes havia uma imagem oficial do FastAPI para Docker: tiangolo/uvicorn-gunicorn-fastapi. Mas agora ela está descontinuada. ⛔️ Você provavelmente **não** deve usar essa imagem base do Docker (ou qualquer outra semelhante). diff --git a/docs/pt/docs/deployment/https.md b/docs/pt/docs/deployment/https.md index 6195cefab..ccd842adb 100644 --- a/docs/pt/docs/deployment/https.md +++ b/docs/pt/docs/deployment/https.md @@ -28,7 +28,7 @@ Agora, a partir de uma perspectiva do desenvolvedor, aqui estão algumas coisas * Por padrão, isso significa que você só pode ter um certificado HTTPS por endereço IP. * Não importa o tamanho do seu servidor ou quão pequeno cada aplicativo que você tem nele possa ser. * No entanto, existe uma solução para isso. -* Há uma extensão para o protocolo TLS (aquele que lida com a criptografia no nível TCP, antes do HTTP) chamada SNI. +* Há uma extensão para o protocolo TLS (aquele que lida com a criptografia no nível TCP, antes do HTTP) chamada SNI. * Esta extensão SNI permite que um único servidor (com um único endereço IP) tenha vários certificados HTTPS e atenda a vários domínios / aplicativos HTTPS. * Para que isso funcione, um único componente (programa) em execução no servidor, ouvindo no endereço IP público, deve ter todos os certificados HTTPS no servidor. * Depois de obter uma conexão segura, o protocolo de comunicação ainda é HTTP. @@ -65,7 +65,7 @@ Aqui está um exemplo de como uma API HTTPS poderia ser estruturada, passo a pas A etapa inicial provavelmente seria adquirir algum nome de domínio. Então, você iria configurá-lo em um servidor DNS (possivelmente no mesmo provedor em nuvem). -Você provavelmente usaria um servidor em nuvem (máquina virtual) ou algo parecido, e ele teria um fixo Endereço IP público. +Você provavelmente usaria um servidor em nuvem (máquina virtual) ou algo parecido, e ele teria um fixo Endereço IP público. No(s) servidor(es) DNS, você configuraria um registro (um `A record`) para apontar seu domínio para o endereço IP público do seu servidor. @@ -135,7 +135,7 @@ Então, o cliente envia uma solicitação HTTPS. Que é apenas uma solicitação -### Desencriptando a Solicitação { #decrypt-the-request } +### Desencripte a Solicitação { #decrypt-the-request } O Proxy de Terminação TLS então usaria a encriptação combinada para desencriptar a solicitação, e transmitiria a solicitação básica (desencriptada) para o processo executando a aplicação (por exemplo, um processo com Uvicorn executando a aplicação FastAPI). @@ -186,7 +186,7 @@ Para fazer isso, e acomodar as necessidades de diferentes aplicações, existem * Executar como um servidor (ao menos durante o processo de aquisição do certificado) no endereço IP público associado com o domínio. * Como dito anteriormente, apenas um processo pode estar ligado a uma porta e IP específicos. * Essa é uma dos motivos que fazem utilizar o mesmo Proxy de Terminação TLS para gerenciar a renovação de certificados ser tão útil. - * Caso contrário, você pode ter que parar a execução do Proxy de Terminação TLS momentaneamente, inicializar o programa de renovação para renovar os certificados, e então reiniciar o Proxy de Terminação TLS. Isso não é o ideal, já que sua(s) aplicação(ões) não vão estar disponíveis enquanto o Proxy de Terminação TLS estiver desligado. + * Caso contrário, você pode ter que parar a execução do Proxy de Terminação TLS momentaneamente, inicializar o programa de renovação para adquirir os certificados, depois configurá-los com o Proxy de Terminação TLS, e então reiniciar o Proxy de Terminação TLS. Isso não é o ideal, já que sua(s) aplicação(ões) não vão estar disponíveis enquanto o Proxy de Terminação TLS estiver desligado. Todo esse processo de renovação, enquanto o aplicativo ainda funciona, é uma das principais razões para preferir um sistema separado para gerenciar HTTPS com um Proxy de Terminação TLS em vez de usar os certificados TLS no servidor da aplicação diretamente (e.g. com o Uvicorn). diff --git a/docs/pt/docs/deployment/index.md b/docs/pt/docs/deployment/index.md index 6a6c21804..d9755d0a2 100644 --- a/docs/pt/docs/deployment/index.md +++ b/docs/pt/docs/deployment/index.md @@ -4,7 +4,7 @@ Implantar uma aplicação **FastAPI** é relativamente fácil. ## O que significa Implantação { #what-does-deployment-mean } -Implantar uma aplicação significa executar as etapas necessárias para torná-la disponível para os usuários. +**Implantar** uma aplicação significa executar as etapas necessárias para torná-la **disponível para os usuários**. Para uma **web API**, isso normalmente envolve colocá-la em uma **máquina remota**, com um **programa de servidor** que ofereça bom desempenho, estabilidade, etc., de modo que seus **usuários** possam **acessar** a aplicação com eficiência e sem interrupções ou problemas. diff --git a/docs/pt/docs/deployment/manually.md b/docs/pt/docs/deployment/manually.md index 21d0f44cd..da3a52cf3 100644 --- a/docs/pt/docs/deployment/manually.md +++ b/docs/pt/docs/deployment/manually.md @@ -46,7 +46,7 @@ Você pode utilizar esse comando, por exemplo, para iniciar sua aplicação **Fa Vamos nos aprofundar um pouco mais em detalhes. -FastAPI utiliza um padrão para construir frameworks e servidores web em Python chamado ASGI. FastAPI é um framework web ASGI. +FastAPI utiliza um padrão para construir frameworks e servidores web em Python chamado ASGI. FastAPI é um framework web ASGI. A principal coisa que você precisa para executar uma aplicação **FastAPI** (ou qualquer outra aplicação ASGI) em uma máquina de servidor remoto é um programa de servidor ASGI como o **Uvicorn**, que é o que vem por padrão no comando `fastapi`. diff --git a/docs/pt/docs/deployment/versions.md b/docs/pt/docs/deployment/versions.md index a2aca5a17..32676da23 100644 --- a/docs/pt/docs/deployment/versions.md +++ b/docs/pt/docs/deployment/versions.md @@ -64,9 +64,9 @@ O "MINOR" é o número do meio, por exemplo, em `0.2.3`, a versão MINOR é `2`. ## Atualizando as versões do FastAPI { #upgrading-the-fastapi-versions } -Você deve adicionar testes para a sua aplicação. +Você deveria adicionar testes para a sua aplicação. -Com **FastAPI** isso é muito fácil (graças ao Starlette), veja a documentação: [Testing](../tutorial/testing.md){.internal-link target=_blank} +Com **FastAPI** isso é muito fácil (graças ao Starlette), veja a documentação: [Testes](../tutorial/testing.md){.internal-link target=_blank} Depois que você tiver testes, você pode atualizar a sua versão do **FastAPI** para uma mais recente e se certificar de que todo o seu código está funcionando corretamente executando seus testes. diff --git a/docs/pt/docs/fastapi-cli.md b/docs/pt/docs/fastapi-cli.md index 0bd7bcd21..f1e633a23 100644 --- a/docs/pt/docs/fastapi-cli.md +++ b/docs/pt/docs/fastapi-cli.md @@ -58,7 +58,7 @@ Internamente, o **FastAPI CLI** usa o OpenAPI para criação de APIs, incluindo declarações de caminho operações, parâmetros, requisições de corpo, segurança etc. +* OpenAPI para criação de APIs, incluindo declarações de caminho operações, parâmetros, requisições de corpo, segurança etc. * Modelo de documentação automática com JSON Schema (já que o OpenAPI em si é baseado no JSON Schema). * Projetado em cima desses padrões após um estudo meticuloso, em vez de uma reflexão breve. * Isso também permite o uso de **geração de código do cliente** automaticamente em muitas linguagens. @@ -136,7 +136,7 @@ Tudo construído como ferramentas e componentes reutilizáveis que são fáceis ### Injeção de dependência { #dependency-injection } -FastAPI inclui um sistema de injeção de dependência extremamente fácil de usar, mas extremamente poderoso. +FastAPI inclui um sistema de injeção de dependência extremamente fácil de usar, mas extremamente poderoso. * Mesmo dependências podem ter dependências, criando uma hierarquia ou **"grafo" de dependências**. * Tudo **automaticamente controlado** pelo framework. @@ -153,8 +153,8 @@ Qualquer integração é projetada para ser tão simples de usar (com dependênc ### Testado { #tested } -* 100% de cobertura de testes. -* 100% do código com anotações de tipo. +* 100% de cobertura de testes. +* 100% do código com anotações de tipo. * Usado para aplicações em produção. ## Recursos do Starlette { #starlette-features } @@ -179,7 +179,7 @@ Com **FastAPI**, você terá todos os recursos do **Starlette** (já que FastAPI **FastAPI** é totalmente compatível com (e baseado no) Pydantic. Então, qualquer código Pydantic adicional que você tiver, também funcionará. -Incluindo bibliotecas externas também baseadas no Pydantic, como ORMs e ODMs para bancos de dados. +Incluindo bibliotecas externas também baseadas no Pydantic, como ORMs e ODMs para bancos de dados. Isso também significa que em muitos casos você poderá passar o mesmo objeto que você receber de uma requisição **diretamente para o banco de dados**, já que tudo é validado automaticamente. @@ -190,7 +190,7 @@ Com **FastAPI** você terá todos os recursos do **Pydantic** (já que FastAPI u * **Sem pegadinhas**: * Sem novas definições de esquema de micro-linguagem para aprender. * Se você conhece os tipos do Python, você sabe como usar o Pydantic. -* Vai bem com o/a seu/sua **IDE/linter/cérebro**: +* Vai bem com o/a seu/sua **IDE/linter/cérebro**: * Como as estruturas de dados do Pydantic são apenas instâncias de classes que você define, o preenchimento automático, linting, mypy e a sua intuição devem funcionar corretamente com seus dados validados. * Valida **estruturas complexas**: * Use modelos hierárquicos do Pydantic, `List` e `Dict` do `typing` do Python, etc. diff --git a/docs/pt/docs/history-design-future.md b/docs/pt/docs/history-design-future.md index 699528739..fb90d1e1c 100644 --- a/docs/pt/docs/history-design-future.md +++ b/docs/pt/docs/history-design-future.md @@ -24,7 +24,7 @@ Há muitas ferramentas criadas antes que ajudaram a inspirar sua criação. Eu estive evitando a criação de um novo _framework_ por vários anos. Primeiro tentei resolver todas as funcionalidades cobertas por **FastAPI** usando muitos _frameworks_, _plug-ins_ e ferramentas diferentes. -Mas em algum ponto, não havia outra opção senão criar algo que oferecia todas as funcionalidades, aproveitando as melhores ideias de ferramentas anteriores, e combinando-as da melhor maneira possível, usando funcionalidades da linguagem que nem estavam disponíveis antes (_type hints_ do Python 3.6+). +Mas em algum ponto, não havia outra opção senão criar algo que oferecia todas as funcionalidades, aproveitando as melhores ideias de ferramentas anteriores, e combinando-as da melhor maneira possível, usando funcionalidades da linguagem que nem estavam disponíveis antes (anotações de tipo do Python 3.6+). @@ -32,7 +32,7 @@ Mas em algum ponto, não havia outra opção senão criar algo que oferecia toda Ao usar todas as alternativas anteriores, eu tive a chance de aprender com todas elas, aproveitar ideias e combiná-las da melhor maneira que encontrei para mim e para os times de desenvolvedores com os quais trabalhava. -Por exemplo, estava claro que idealmente ele deveria ser baseado nos _type hints_ padrões do Python. +Por exemplo, estava claro que idealmente ele deveria ser baseado nas anotações de tipo padrão do Python. Também, a melhor abordagem era usar padrões já existentes. diff --git a/docs/pt/docs/how-to/authentication-error-status-code.md b/docs/pt/docs/how-to/authentication-error-status-code.md index 878a1ca1a..40790f180 100644 --- a/docs/pt/docs/how-to/authentication-error-status-code.md +++ b/docs/pt/docs/how-to/authentication-error-status-code.md @@ -8,7 +8,7 @@ Mas, se por algum motivo seus clientes dependem do comportamento antigo, você p Por exemplo, você pode criar uma subclasse de `HTTPBearer` que retorne um erro `403 Forbidden` em vez do erro padrão `401 Unauthorized`: -{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} /// tip | Dica diff --git a/docs/pt/docs/how-to/conditional-openapi.md b/docs/pt/docs/how-to/conditional-openapi.md index b475dae6d..b77600a7b 100644 --- a/docs/pt/docs/how-to/conditional-openapi.md +++ b/docs/pt/docs/how-to/conditional-openapi.md @@ -29,13 +29,13 @@ Você pode usar facilmente as mesmas configurações do Pydantic para configurar Por exemplo: -{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} Aqui declaramos a configuração `openapi_url` com o mesmo padrão de `"/openapi.json"`. E então a usamos ao criar a aplicação `FastAPI`. -Então você pode desabilitar o OpenAPI (incluindo a documentação da interface do usuário) definindo a variável de ambiente `OPENAPI_URL` como uma string vazia, como: +Então você pode desabilitar o OpenAPI (incluindo a documentação da interface do usuário) definindo a variável de ambiente `OPENAPI_URL` como a string vazia, como:
diff --git a/docs/pt/docs/how-to/configure-swagger-ui.md b/docs/pt/docs/how-to/configure-swagger-ui.md index 2d1863c64..a8f9bed47 100644 --- a/docs/pt/docs/how-to/configure-swagger-ui.md +++ b/docs/pt/docs/how-to/configure-swagger-ui.md @@ -18,7 +18,7 @@ Sem alterar as configurações, o destaque de sintaxe é habilitado por padrão: Mas você pode desabilitá-lo definindo `syntaxHighlight` como `False`: -{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} ...e então o Swagger UI não mostrará mais o destaque de sintaxe: @@ -28,7 +28,7 @@ Mas você pode desabilitá-lo definindo `syntaxHighlight` como `False`: Da mesma forma que você pode definir o tema de destaque de sintaxe com a chave `"syntaxHighlight.theme"` (observe que há um ponto no meio): -{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} Essa configuração alteraria o tema de cores de destaque de sintaxe: @@ -46,7 +46,7 @@ Você pode substituir qualquer um deles definindo um valor diferente no argument Por exemplo, para desabilitar `deepLinking` você pode passar essas configurações para `swagger_ui_parameters`: -{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} ## Outros parâmetros da UI do Swagger { #other-swagger-ui-parameters } diff --git a/docs/pt/docs/how-to/custom-docs-ui-assets.md b/docs/pt/docs/how-to/custom-docs-ui-assets.md index adda9eca5..c7a62aefd 100644 --- a/docs/pt/docs/how-to/custom-docs-ui-assets.md +++ b/docs/pt/docs/how-to/custom-docs-ui-assets.md @@ -2,13 +2,13 @@ A documentação da API usa **Swagger UI** e **ReDoc**, e cada um deles precisa de alguns arquivos JavaScript e CSS. -Por padrão, esses arquivos são fornecidos por um CDN. +Por padrão, esses arquivos são fornecidos por um CDN. Mas é possível personalizá-los, você pode definir um CDN específico ou providenciar os arquivos você mesmo. ## CDN Personalizado para JavaScript e CSS { #custom-cdn-for-javascript-and-css } -Vamos supor que você deseja usar um CDN diferente, por exemplo, você deseja usar `https://unpkg.com/`. +Vamos supor que você deseja usar um CDN diferente, por exemplo, você deseja usar `https://unpkg.com/`. Isso pode ser útil se, por exemplo, você mora em um país que restringe algumas URLs. @@ -18,7 +18,7 @@ O primeiro passo é desativar a documentação automática, pois por padrão, el Para desativá-los, defina suas URLs como `None` ao criar sua aplicação FastAPI: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} ### Incluir a documentação personalizada { #include-the-custom-docs } @@ -34,7 +34,7 @@ Você pode reutilizar as funções internas do FastAPI para criar as páginas HT E de forma semelhante para o ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} /// tip | Dica @@ -50,7 +50,7 @@ Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse aux Agora, para poder testar se tudo funciona, crie uma *operação de rota*: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} ### Teste { #test-it } @@ -118,7 +118,7 @@ Depois disso, sua estrutura de arquivos deve se parecer com: * Importe `StaticFiles`. * "Monte" a instância `StaticFiles()` em um caminho específico. -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} ### Teste os arquivos estáticos { #test-the-static-files } @@ -144,7 +144,7 @@ Da mesma forma que ao usar um CDN personalizado, o primeiro passo é desativar a Para desativá-los, defina suas URLs como `None` ao criar sua aplicação FastAPI: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} ### Incluir a documentação personalizada para arquivos estáticos { #include-the-custom-docs-for-static-files } @@ -160,7 +160,7 @@ Novamente, você pode reutilizar as funções internas do FastAPI para criar as E de forma semelhante para o ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} /// tip | Dica @@ -176,7 +176,7 @@ Swagger UI lidará com isso nos bastidores para você, mas ele precisa desse aux Agora, para poder testar se tudo funciona, crie uma *operação de rota*: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} ### Teste a UI de Arquivos Estáticos { #test-static-files-ui } diff --git a/docs/pt/docs/how-to/extending-openapi.md b/docs/pt/docs/how-to/extending-openapi.md index b1b50ce65..b8277c1c4 100644 --- a/docs/pt/docs/how-to/extending-openapi.md +++ b/docs/pt/docs/how-to/extending-openapi.md @@ -43,19 +43,19 @@ Por exemplo, vamos adicionar documentação do Strawberry. diff --git a/docs/pt/docs/how-to/index.md b/docs/pt/docs/how-to/index.md index a3d16380f..91df638d7 100644 --- a/docs/pt/docs/how-to/index.md +++ b/docs/pt/docs/how-to/index.md @@ -2,9 +2,9 @@ Aqui você encontrará diferentes exemplos práticos ou tutoriais de "como fazer" para **vários tópicos**. -A maioria dessas ideias será mais ou menos **independente**, e na maioria dos casos você só precisará estudá-las se elas se aplicarem diretamente ao **seu projeto**. +A maioria dessas ideias será mais ou menos **independente**, e na maioria dos casos você deveria estudá-las apenas se elas se aplicarem diretamente ao **seu projeto**. -Se algo parecer interessante e útil para o seu projeto, vá em frente e dê uma olhada. Caso contrário, você pode simplesmente ignorá-lo. +Se algo parecer interessante e útil para o seu projeto, vá em frente e dê uma olhada. Caso contrário, você pode simplesmente ignorá-las. /// tip | Dica diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md index e61cc2e2f..c337f6d3a 100644 --- a/docs/pt/docs/index.md +++ b/docs/pt/docs/index.md @@ -40,7 +40,7 @@ Os recursos chave são: * **Rápido**: alta performance, equivalente a **NodeJS** e **Go** (graças ao Starlette e Pydantic). [Um dos frameworks mais rápidos disponíveis](#performance). * **Rápido para codar**: Aumenta a velocidade para desenvolver recursos entre 200% a 300%. * * **Poucos bugs**: Reduz cerca de 40% de erros induzidos por humanos (desenvolvedores). * -* **Intuitivo**: Grande suporte a editores. Completação em todos os lugares. Menos tempo debugando. +* **Intuitivo**: Grande suporte a editores. Completação em todos os lugares. Menos tempo debugando. * **Fácil**: Projetado para ser fácil de aprender e usar. Menos tempo lendo docs. * **Enxuto**: Minimize duplicação de código. Múltiplas funcionalidades para cada declaração de parâmetro. Menos bugs. * **Robusto**: Tenha código pronto para produção. E com documentação interativa automática. @@ -368,7 +368,7 @@ item: Item * Validação de dados: * Erros automáticos e claros quando o dado é inválido. * Validação até para objetos JSON profundamente aninhados. -* Conversão de dados de entrada: vindo da rede para dados e tipos Python. Consegue ler: +* Conversão de dados de entrada: vindo da rede para dados e tipos Python. Consegue ler: * JSON. * Parâmetros de path. * Parâmetros query. @@ -376,7 +376,7 @@ item: Item * Cabeçalhos. * Formulários. * Arquivos. -* Conversão de dados de saída: convertendo de tipos e dados Python para dados de rede (como JSON): +* Conversão de dados de saída: convertendo de tipos e dados Python para dados de rede (como JSON): * Converte tipos Python (`str`, `int`, `float`, `bool`, `list` etc). * Objetos `datetime`. * Objetos `UUID`. @@ -439,7 +439,7 @@ Para um exemplo mais completo incluindo mais recursos, veja Injeção de Dependência**. +* Um poderoso e fácil de usar sistema de **Injeção de Dependência**. * Segurança e autenticação, incluindo suporte para **OAuth2** com autenticação com **JWT tokens** e **HTTP Basic**. * Técnicas mais avançadas (mas igualmente fáceis) para declaração de **modelos JSON profundamente aninhados** (graças ao Pydantic). * Integrações **GraphQL** com o Strawberry e outras bibliotecas. @@ -524,7 +524,7 @@ Utilizado pelo Starlette: * httpx - Obrigatório caso você queira utilizar o `TestClient`. * jinja2 - Obrigatório se você quer utilizar a configuração padrão de templates. -* python-multipart - Obrigatório se você deseja suporte a "parsing" de formulário, com `request.form()`. +* python-multipart - Obrigatório se você deseja suporte a "parsing" de formulário, com `request.form()`. Utilizado pelo FastAPI: diff --git a/docs/pt/docs/python-types.md b/docs/pt/docs/python-types.md index fc983d1df..bc3f47858 100644 --- a/docs/pt/docs/python-types.md +++ b/docs/pt/docs/python-types.md @@ -1,14 +1,14 @@ # Introdução aos tipos Python { #python-types-intro } -O Python possui suporte para "dicas de tipo" ou "type hints" (também chamado de "anotações de tipo" ou "type annotations") +O Python possui suporte para "type hints" opcionais (também chamados de "type annotations"). -Esses **"type hints"** são uma sintaxe especial que permite declarar o tipo de uma variável. +Esses **"type hints"** ou anotações são uma sintaxe especial que permite declarar o tipo de uma variável. Ao declarar tipos para suas variáveis, editores e ferramentas podem oferecer um melhor suporte. Este é apenas um **tutorial rápido / atualização** sobre type hints do Python. Ele cobre apenas o mínimo necessário para usá-los com o **FastAPI**... que é realmente muito pouco. -O **FastAPI** é baseado nesses type hints, eles oferecem muitas vantagens e benefícios. +O **FastAPI** é todo baseado nesses type hints, eles oferecem muitas vantagens e benefícios. Mas mesmo que você nunca use o **FastAPI**, você se beneficiaria de aprender um pouco sobre eles. @@ -22,7 +22,7 @@ Se você é um especialista em Python e já sabe tudo sobre type hints, pule par Vamos começar com um exemplo simples: -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} A chamada deste programa gera: @@ -34,9 +34,9 @@ A função faz o seguinte: * Pega um `first_name` e `last_name`. * Converte a primeira letra de cada uma em maiúsculas com `title()`. -* Concatena com um espaço no meio. +* Concatena com um espaço no meio. -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### Edite-o { #edit-it } @@ -52,7 +52,7 @@ Era `upper`? Era `uppercase`? `first_uppercase`? `capitalize`? Em seguida, tente com o velho amigo do programador, o preenchimento automático do editor. -Você digita o primeiro parâmetro da função, `first_name`, depois um ponto (`.`) e, em seguida, pressiona `Ctrl + Space` para acionar a conclusão. +Você digita o primeiro parâmetro da função, `first_name`, depois um ponto (`.`) e, em seguida, pressiona `Ctrl+Space` para acionar o preenchimento automático. Mas, infelizmente, você não obtém nada útil: @@ -78,7 +78,7 @@ para: Esses são os "type hints": -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} Isso não é o mesmo que declarar valores padrão como seria com: @@ -88,7 +88,7 @@ Isso não é o mesmo que declarar valores padrão como seria com: É uma coisa diferente. -Estamos usando dois pontos (`:`), não é igual a (`=`). +Estamos usando dois pontos (`:`), não sinal de igual (`=`). E adicionar type hints normalmente não muda o que acontece do que aconteceria sem eles. @@ -106,17 +106,17 @@ Com isso, você pode rolar, vendo as opções, até encontrar o que "soa familia Verifique esta função, ela já possui type hints: -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} -Como o editor conhece os tipos de variáveis, você não obtém apenas o preenchimento automático, mas também as verificações de erro: +Como o editor conhece os tipos das variáveis, você não obtém apenas o preenchimento automático, mas também as verificações de erro: -Agora você sabe que precisa corrigí-lo, converta `age` em uma string com `str(age)`: +Agora você sabe que precisa corrigi-la, convertendo `age` em uma string com `str(age)`: -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} -## Declarando Tipos { #declaring-types } +## Declarando tipos { #declaring-types } Você acabou de ver o local principal para declarar type hints. Como parâmetros de função. @@ -133,29 +133,32 @@ Você pode usar, por exemplo: * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} -### Tipos genéricos com parâmetros de tipo { #generic-types-with-type-parameters } +### Módulo `typing` { #typing-module } -Existem algumas estruturas de dados que podem conter outros valores, como `dict`, `list`, `set` e `tuple`. E os valores internos também podem ter seu próprio tipo. +Para alguns casos adicionais, você pode precisar importar alguns itens do módulo padrão `typing`, por exemplo, quando quiser declarar que algo pode ter "qualquer tipo", você pode usar `Any` de `typing`: -Estes tipos que possuem tipos internos são chamados de tipos "**genéricos**". E é possível declará-los mesmo com os seus tipos internos. +```python +from typing import Any -Para declarar esses tipos e os tipos internos, você pode usar o módulo Python padrão `typing`. Ele existe especificamente para suportar esses type hints. -#### Versões mais recentes do Python { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -A sintaxe utilizando `typing` é **compatível** com todas as versões, desde o Python 3.6 até as últimas, incluindo o Python 3.9, 3.10, etc. +### Tipos genéricos { #generic-types } -Conforme o Python evolui, **novas versões** chegam com suporte melhorado para esses type annotations, e em muitos casos, você não precisará nem importar e utilizar o módulo `typing` para declarar os type annotations. +Alguns tipos podem receber "parâmetros de tipo" entre colchetes, para definir seus tipos internos, por exemplo, uma "lista de strings" seria declarada como `list[str]`. -Se você pode escolher uma versão mais recente do Python para o seu projeto, você poderá aproveitar isso ao seu favor. +Esses tipos que podem receber parâmetros de tipo são chamados **tipos genéricos** ou **genéricos**. -Em todos os documentos existem exemplos compatíveis com cada versão do Python (quando existem diferenças). +Você pode usar os mesmos tipos internos como genéricos (com colchetes e tipos dentro): -Por exemplo, "**Python 3.6+**" significa que é compatível com o Python 3.6 ou superior (incluindo o 3.7, 3.8, 3.9, 3.10, etc). E "**Python 3.9+**" significa que é compatível com o Python 3.9 ou mais recente (incluindo o 3.10, etc). - -Se você pode utilizar a **versão mais recente do Python**, utilize os exemplos para as últimas versões. Eles terão as **melhores e mais simples sintaxes**, como por exemplo, "**Python 3.10+**". +* `list` +* `tuple` +* `set` +* `dict` #### List { #list } @@ -167,11 +170,11 @@ Como o tipo, coloque `list`. Como a lista é um tipo que contém tipos internos, você os coloca entre colchetes: -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} /// info | Informação -Estes tipos internos dentro dos colchetes são chamados "parâmetros de tipo" (type parameters). +Esses tipos internos dentro dos colchetes são chamados de "parâmetros de tipo". Neste caso, `str` é o parâmetro de tipo passado para `list`. @@ -193,9 +196,9 @@ E, ainda assim, o editor sabe que é um `str` e fornece suporte para isso. Você faria o mesmo para declarar `tuple`s e `set`s: -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} -Isso significa que: +Isso significa: * A variável `items_t` é uma `tuple` com 3 itens, um `int`, outro `int` e uma `str`. * A variável `items_s` é um `set`, e cada um de seus itens é do tipo `bytes`. @@ -208,7 +211,7 @@ O primeiro parâmetro de tipo é para as chaves do `dict`. O segundo parâmetro de tipo é para os valores do `dict`: -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} Isso significa que: @@ -218,46 +221,22 @@ Isso significa que: #### Union { #union } -Você pode declarar que uma variável pode ser de qualquer um dentre **diversos tipos**. Por exemplo, um `int` ou um `str`. +Você pode declarar que uma variável pode ser de qualquer um dentre **vários tipos**, por exemplo, um `int` ou um `str`. -No Python 3.6 e superior (incluindo o Python 3.10), você pode utilizar o tipo `Union` de `typing`, e colocar dentro dos colchetes os possíveis tipos aceitáveis. +Para defini-la, você usa a barra vertical (`|`) para separar ambos os tipos. -No Python 3.10 também existe uma **nova sintaxe** onde você pode colocar os possíveis tipos separados por uma barra vertical (`|`). - -//// tab | Python 3.10+ +Isso é chamado de "união", porque a variável pode ser qualquer coisa na união desses dois conjuntos de tipos. ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -Em ambos os casos, isso significa que `item` poderia ser um `int` ou um `str`. +Isso significa que `item` pode ser um `int` ou um `str`. #### Possivelmente `None` { #possibly-none } Você pode declarar que um valor pode ter um tipo, como `str`, mas que ele também pode ser `None`. -No Python 3.6 e superior (incluindo o Python 3.10) você pode declará-lo importando e utilizando `Optional` do módulo `typing`. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -O uso de `Optional[str]` em vez de apenas `str` permitirá que o editor o ajude a detectar erros, onde você pode estar assumindo que um valor é sempre um `str`, quando na verdade também pode ser `None`. - -`Optional[Something]` é na verdade um atalho para `Union[Something, None]`, eles são equivalentes. - -Isso também significa que no Python 3.10, você pode utilizar `Something | None`: - //// tab | Python 3.10+ ```Python hl_lines="1" @@ -266,96 +245,7 @@ Isso também significa que no Python 3.10, você pode utilizar `Something | None //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ alternativa - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### Utilizando `Union` ou `Optional` { #using-union-or-optional } - -Se você está utilizando uma versão do Python abaixo da 3.10, aqui vai uma dica do meu ponto de vista bem **subjetivo**: - -* 🚨 Evite utilizar `Optional[SomeType]` -* No lugar, ✨ **use `Union[SomeType, None]`** ✨. - -Ambos são equivalentes, e no final das contas, eles são o mesmo. Mas eu recomendaria o `Union` ao invés de `Optional` porque a palavra **Optional** parece implicar que o valor é opcional, quando na verdade significa "isso pode ser `None`", mesmo que ele não seja opcional e ainda seja obrigatório. - -Eu penso que `Union[SomeType, None]` é mais explícito sobre o que ele significa. - -Isso é apenas sobre palavras e nomes. Mas estas palavras podem afetar como os seus colegas de trabalho pensam sobre o código. - -Por exemplo, vamos pegar esta função: - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -O parâmetro `name` é definido como `Optional[str]`, mas ele **não é opcional**, você não pode chamar a função sem o parâmetro: - -```Python -say_hi() # Oh, no, this throws an error! 😱 -``` - -O parâmetro `name` **ainda é obrigatório** (não *opcional*) porque ele não possui um valor padrão. Mesmo assim, `name` aceita `None` como valor: - -```Python -say_hi(name=None) # This works, None is valid 🎉 -``` - -A boa notícia é, quando você estiver no Python 3.10 você não precisará se preocupar mais com isso, pois você poderá simplesmente utilizar o `|` para definir uniões de tipos: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -E então você não precisará mais se preocupar com nomes como `Optional` e `Union`. 😎 - -#### Tipos genéricos { #generic-types } - -Esses tipos que usam parâmetros de tipo entre colchetes são chamados **tipos genéricos** ou **genéricos**. Por exemplo: - -//// tab | Python 3.10+ - -Você pode utilizar os mesmos tipos internos como genéricos (com colchetes e tipos dentro): - -* `list` -* `tuple` -* `set` -* `dict` - -E o mesmo que com versões anteriores do Python, do módulo `typing`: - -* `Union` -* `Optional` -* ...entre outros. - -No Python 3.10, como uma alternativa para a utilização dos genéricos `Union` e `Optional`, você pode usar a barra vertical (`|`) para declarar uniões de tipos. Isso é muito melhor e mais simples. - -//// - -//// tab | Python 3.9+ - -Você pode utilizar os mesmos tipos internos como genéricos (com colchetes e tipos dentro): - -* `list` -* `tuple` -* `set` -* `dict` - -E genéricos do módulo `typing`: - -* `Union` -* `Optional` -* ...entre outros. - -//// +Usar `str | None` em vez de apenas `str` permitirá que o editor o ajude a detectar erros em que você poderia estar assumindo que um valor é sempre um `str`, quando na verdade ele também pode ser `None`. ### Classes como tipos { #classes-as-types } @@ -363,13 +253,13 @@ Você também pode declarar uma classe como o tipo de uma variável. Digamos que você tenha uma classe `Person`, com um nome: -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} Então você pode declarar que uma variável é do tipo `Person`: -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} -E então, novamente, você recebe todo o apoio do editor: +E então, novamente, você recebe todo o suporte do editor: @@ -379,7 +269,7 @@ Isso não significa que "`one_person` é a **classe** chamada `Person`". ## Modelos Pydantic { #pydantic-models } -O Pydantic é uma biblioteca Python para executar a validação de dados. +Pydantic é uma biblioteca Python para executar a validação de dados. Você declara a "forma" dos dados como classes com atributos. @@ -403,23 +293,17 @@ O **FastAPI** é todo baseado em Pydantic. Você verá muito mais disso na prática no [Tutorial - Guia do usuário](tutorial/index.md){.internal-link target=_blank}. -/// tip | Dica - -O Pydantic tem um comportamento especial quando você usa `Optional` ou `Union[Something, None]` sem um valor padrão. Você pode ler mais sobre isso na documentação do Pydantic sobre campos Opcionais Obrigatórios. - -/// - ## Type Hints com Metadados de Anotações { #type-hints-with-metadata-annotations } -O Python possui uma funcionalidade que nos permite incluir **metadados adicionais** nos type hints utilizando `Annotated`. +O Python também possui uma funcionalidade que permite incluir **metadados adicionais** nesses type hints utilizando `Annotated`. -Desde o Python 3.9, `Annotated` faz parte da biblioteca padrão, então você pode importá-lo de `typing`. +Você pode importar `Annotated` de `typing`. -{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} O Python em si não faz nada com este `Annotated`. E para editores e outras ferramentas, o tipo ainda é `str`. -Mas você pode utilizar este espaço dentro do `Annotated` para fornecer ao **FastAPI** metadata adicional sobre como você deseja que a sua aplicação se comporte. +Mas você pode utilizar este espaço dentro do `Annotated` para fornecer ao **FastAPI** metadados adicionais sobre como você deseja que a sua aplicação se comporte. O importante aqui de se lembrar é que **o primeiro *type parameter*** que você informar ao `Annotated` é o **tipo de fato**. O resto é apenas metadado para outras ferramentas. @@ -446,12 +330,12 @@ Com o **FastAPI**, você declara parâmetros com type hints e obtém: ... e o **FastAPI** usa as mesmas declarações para: -* **Definir requisitos**: dos parâmetros de rota, parâmetros da consulta, cabeçalhos, corpos, dependências, etc. -* **Converter dados**: da solicitação para o tipo necessário. -* **Validar dados**: provenientes de cada solicitação: +* **Definir requisitos**: dos parâmetros de path da request, parâmetros da query, cabeçalhos, corpos, dependências, etc. +* **Converter dados**: da request para o tipo necessário. +* **Validar dados**: provenientes de cada request: * Gerando **erros automáticos** retornados ao cliente quando os dados são inválidos. * **Documentar** a API usando OpenAPI: - * que é usado pelas interfaces de usuário da documentação interativa automática. + * que é usada pelas interfaces de usuário da documentação interativa automática. Tudo isso pode parecer abstrato. Não se preocupe. Você verá tudo isso em ação no [Tutorial - Guia do usuário](tutorial/index.md){.internal-link target=_blank}. @@ -459,6 +343,6 @@ O importante é que, usando tipos padrão de Python, em um único local (em vez /// info | Informação -Se você já passou por todo o tutorial e voltou para ver mais sobre os tipos, um bom recurso é a "cheat sheet" do `mypy` . +Se você já passou por todo o tutorial e voltou para ver mais sobre os tipos, um bom recurso é a "cheat sheet" do `mypy`. /// diff --git a/docs/pt/docs/translation-banner.md b/docs/pt/docs/translation-banner.md new file mode 100644 index 000000000..f3069ddd7 --- /dev/null +++ b/docs/pt/docs/translation-banner.md @@ -0,0 +1,11 @@ +/// details | 🌐 Tradução por IA e humanos + +Esta tradução foi feita por IA orientada por humanos. 🤝 + +Ela pode conter erros de interpretação do significado original ou soar pouco natural, etc. 🤖 + +Você pode melhorar esta tradução [ajudando-nos a orientar melhor o LLM de IA](https://fastapi.tiangolo.com/pt/contributing/#translations). + +[Versão em inglês](ENGLISH_VERSION_URL) + +/// diff --git a/docs/pt/docs/tutorial/background-tasks.md b/docs/pt/docs/tutorial/background-tasks.md index 34805364b..462fb00dc 100644 --- a/docs/pt/docs/tutorial/background-tasks.md +++ b/docs/pt/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ Isso inclui, por exemplo: Primeiro, importe `BackgroundTasks` e defina um parâmetro na sua *função de operação de rota* com uma declaração de tipo `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} O **FastAPI** criará o objeto do tipo `BackgroundTasks` para você e o passará como esse parâmetro. @@ -31,13 +31,13 @@ Neste caso, a função da tarefa escreverá em um arquivo (simulando o envio de E como a operação de escrita não usa `async` e `await`, definimos a função com um `def` normal: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} ## Adicione a tarefa em segundo plano { #add-the-background-task } Dentro da sua *função de operação de rota*, passe sua função de tarefa para o objeto de *tarefas em segundo plano* com o método `.add_task()`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} O `.add_task()` recebe como argumentos: diff --git a/docs/pt/docs/tutorial/bigger-applications.md b/docs/pt/docs/tutorial/bigger-applications.md index 87bd13375..ad758988f 100644 --- a/docs/pt/docs/tutorial/bigger-applications.md +++ b/docs/pt/docs/tutorial/bigger-applications.md @@ -58,17 +58,17 @@ A mesma estrutura de arquivos com comentários: ```bash . -├── app # "app" is a Python package -│   ├── __init__.py # this file makes "app" a "Python package" -│   ├── main.py # "main" module, e.g. import app.main -│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies -│   └── routers # "routers" is a "Python subpackage" -│   │ ├── __init__.py # makes "routers" a "Python subpackage" -│   │ ├── items.py # "items" submodule, e.g. import app.routers.items -│   │ └── users.py # "users" submodule, e.g. import app.routers.users -│   └── internal # "internal" is a "Python subpackage" -│   ├── __init__.py # makes "internal" a "Python subpackage" -│   └── admin.py # "admin" submodule, e.g. import app.internal.admin +├── app # "app" é um pacote Python +│   ├── __init__.py # este arquivo torna "app" um "pacote Python" +│   ├── main.py # módulo "main", p.ex., import app.main +│   ├── dependencies.py # módulo "dependencies", p.ex., import app.dependencies +│   └── routers # "routers" é um "subpacote Python" +│   │ ├── __init__.py # torna "routers" um "subpacote Python" +│   │ ├── items.py # submódulo "items", p.ex., import app.routers.items +│   │ └── users.py # submódulo "users", p.ex., import app.routers.users +│   └── internal # "internal" é um "subpacote Python" +│   ├── __init__.py # torna "internal" um "subpacote Python" +│   └── admin.py # submódulo "admin", p.ex., import app.internal.admin ``` ## `APIRouter` { #apirouter } @@ -85,7 +85,7 @@ Você pode criar as *operações de rota* para esse módulo usando o `APIRouter` Você o importa e cria uma "instância" da mesma maneira que faria com a classe `FastAPI`: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} ### *Operações de Rota* com `APIRouter` { #path-operations-with-apirouter } @@ -93,7 +93,7 @@ E então você o utiliza para declarar suas *operações de rota*. Utilize-o da mesma maneira que utilizaria a classe `FastAPI`: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} Você pode pensar em `APIRouter` como uma classe "mini `FastAPI`". @@ -117,7 +117,7 @@ Então, as colocamos em seu próprio módulo de `dependencies` (`app/dependencie Agora usaremos uma dependência simples para ler um cabeçalho `X-Token` personalizado: -{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | Dica @@ -149,7 +149,7 @@ Sabemos que todas as *operações de rota* neste módulo têm o mesmo: Então, em vez de adicionar tudo isso a cada *operação de rota*, podemos adicioná-lo ao `APIRouter`. -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} Como o path de cada *operação de rota* tem que começar com `/`, como em: @@ -208,7 +208,7 @@ E precisamos obter a função de dependência do módulo `app.dependencies`, o a Então usamos uma importação relativa com `..` para as dependências: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} #### Como funcionam as importações relativas { #how-relative-imports-work } @@ -279,7 +279,7 @@ Não estamos adicionando o prefixo `/items` nem `tags=["items"]` a cada *operaç Mas ainda podemos adicionar _mais_ `tags` que serão aplicadas a uma *operação de rota* específica, e também algumas `responses` extras específicas para essa *operação de rota*: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | Dica @@ -305,13 +305,13 @@ Você importa e cria uma classe `FastAPI` normalmente. E podemos até declarar [dependências globais](dependencies/global-dependencies.md){.internal-link target=_blank} que serão combinadas com as dependências para cada `APIRouter`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} ### Importe o `APIRouter` { #import-the-apirouter } Agora importamos os outros submódulos que possuem `APIRouter`s: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} Como os arquivos `app/routers/users.py` e `app/routers/items.py` são submódulos que fazem parte do mesmo pacote Python `app`, podemos usar um único ponto `.` para importá-los usando "importações relativas". @@ -374,13 +374,13 @@ o `router` de `users` sobrescreveria o de `items` e não poderíamos usá-los ao Então, para poder usar ambos no mesmo arquivo, importamos os submódulos diretamente: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} ### Inclua os `APIRouter`s para `users` e `items` { #include-the-apirouters-for-users-and-items } Agora, vamos incluir os `router`s dos submódulos `users` e `items`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} /// info | Informação @@ -420,13 +420,13 @@ Ele contém um `APIRouter` com algumas *operações de rota* de administração Para este exemplo, será super simples. Mas digamos que, como ele é compartilhado com outros projetos na organização, não podemos modificá-lo e adicionar um `prefix`, `dependencies`, `tags`, etc. diretamente ao `APIRouter`: -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} Mas ainda queremos definir um `prefix` personalizado ao incluir o `APIRouter` para que todas as suas *operações de rota* comecem com `/admin`, queremos protegê-lo com as `dependencies` que já temos para este projeto e queremos incluir `tags` e `responses`. Podemos declarar tudo isso sem precisar modificar o `APIRouter` original passando esses parâmetros para `app.include_router()`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} Dessa forma, o `APIRouter` original permanecerá inalterado, para que possamos compartilhar o mesmo arquivo `app/internal/admin.py` com outros projetos na organização. @@ -447,7 +447,7 @@ Também podemos adicionar *operações de rota* diretamente ao aplicativo `FastA Aqui fazemos isso... só para mostrar que podemos 🤷: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} e funcionará corretamente, junto com todas as outras *operações de rota* adicionadas com `app.include_router()`. diff --git a/docs/pt/docs/tutorial/body-multiple-params.md b/docs/pt/docs/tutorial/body-multiple-params.md index 24f35b210..828cde633 100644 --- a/docs/pt/docs/tutorial/body-multiple-params.md +++ b/docs/pt/docs/tutorial/body-multiple-params.md @@ -104,12 +104,6 @@ Dado que, por padrão, valores singulares são interpretados como parâmetros de q: str | None = None ``` -Ou em Python 3.9: - -```Python -q: Union[str, None] = None -``` - Por exemplo: {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/pt/docs/tutorial/body-nested-models.md b/docs/pt/docs/tutorial/body-nested-models.md index f2bec19a2..d53d3f649 100644 --- a/docs/pt/docs/tutorial/body-nested-models.md +++ b/docs/pt/docs/tutorial/body-nested-models.md @@ -164,7 +164,7 @@ images: list[Image] como em: -{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} ## Suporte de editor em todo canto { #editor-support-everywhere } @@ -194,7 +194,7 @@ Outro caso útil é quando você deseja ter chaves de outro tipo, por exemplo, ` Neste caso, você aceitaria qualquer `dict`, desde que tenha chaves` int` com valores `float`: -{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} /// tip | Dica diff --git a/docs/pt/docs/tutorial/body.md b/docs/pt/docs/tutorial/body.md index 669334439..bec553e21 100644 --- a/docs/pt/docs/tutorial/body.md +++ b/docs/pt/docs/tutorial/body.md @@ -72,9 +72,9 @@ Apenas com essa declaração de tipos do Python, o **FastAPI** irá: * Validar os dados. * Se algum dado for inválido, irá retornar um erro bem claro, indicando exatamente onde e o que estava incorreto. * Entregar a você a informação recebida no parâmetro `item`. - * Como você o declarou na função como do tipo `Item`, você também terá o suporte do editor (completação, etc) para todos os atributos e seus tipos. + * Como você o declarou na função como do tipo `Item`, você também terá o suporte do editor (preenchimento automático, etc) para todos os atributos e seus tipos. * Gerar definições de JSON Schema para o seu modelo; você também pode usá-las em qualquer outro lugar se fizer sentido para o seu projeto. -* Esses schemas farão parte do esquema OpenAPI gerado, e serão usados pelas UIs de documentação automática. +* Esses schemas farão parte do esquema OpenAPI gerado, e serão usados pelas UIs de documentação automática. ## Documentação automática { #automatic-docs } @@ -88,7 +88,7 @@ E também serão utilizados na documentação da API dentro de cada *operação ## Suporte do editor { #editor-support } -No seu editor, dentro da função você receberá dicas de tipos e completação em todo lugar (isso não aconteceria se você recebesse um `dict` em vez de um modelo Pydantic): +No seu editor, dentro da função você receberá dicas de tipos e preenchimento automático em todo lugar (isso não aconteceria se você recebesse um `dict` em vez de um modelo Pydantic): @@ -155,7 +155,7 @@ Os parâmetros da função serão reconhecidos conforme abaixo: O FastAPI saberá que o valor de `q` não é obrigatório por causa do valor padrão `= None`. -O `str | None` (Python 3.10+) ou o `Union` em `Union[str, None]` (Python 3.9+) não é utilizado pelo FastAPI para determinar que o valor não é obrigatório, ele saberá que não é obrigatório porque tem um valor padrão `= None`. +O `str | None` não é utilizado pelo FastAPI para determinar que o valor não é obrigatório, ele saberá que não é obrigatório porque tem um valor padrão `= None`. Mas adicionar as anotações de tipo permitirá ao seu editor oferecer um suporte melhor e detectar erros. diff --git a/docs/pt/docs/tutorial/cookie-param-models.md b/docs/pt/docs/tutorial/cookie-param-models.md index 73c94050f..f125314c8 100644 --- a/docs/pt/docs/tutorial/cookie-param-models.md +++ b/docs/pt/docs/tutorial/cookie-param-models.md @@ -18,7 +18,7 @@ Essa mesma técnica se aplica para `Query`, `Cookie`, e `Header`. 😎 ## Cookies com Modelos Pydantic { #cookies-with-a-pydantic-model } -Declare o parâmetro de **cookie** que você precisa em um **modelo Pydantic**, e depois declare o parâmetro como um `Cookie`: +Declare os parâmetros de **cookie** de que você precisa em um **modelo Pydantic**, e depois declare o parâmetro como `Cookie`: {* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} @@ -46,7 +46,7 @@ Mas mesmo que você **adicionar os dados** e clicar em "Executar", pelo motivo d Em alguns casos especiais (provavelmente não muito comuns), você pode querer **restringir** os cookies que você deseja receber. -Agora a sua API possui o poder de controlar o seu próprio consentimento de cookie. 🤪🍪 +Agora a sua API possui o poder de controlar o seu próprio consentimento de cookie. 🤪🍪 Você pode utilizar a configuração do modelo Pydantic para `proibir` qualquer campo `extra`: @@ -54,9 +54,9 @@ Você pode utilizar a configuração do modelo Pydantic para `proibir` qualquer Se o cliente tentar enviar alguns **cookies extras**, eles receberão um retorno de **erro**. -Coitados dos banners de cookies com todo o seu esforço para obter o seu consentimento para a API rejeitá-lo. 🍪 +Coitados dos banners de cookies com todo o seu esforço para obter o seu consentimento para a API rejeitá-lo. 🍪 -Por exemplo, se o cliente tentar enviar um cookie `santa_tracker` com o valor de `good-list-please`, o cliente receberá uma resposta de **erro** informando que o `santa_tracker` cookie não é permitido: +Por exemplo, se o cliente tentar enviar um cookie `santa_tracker` com o valor de `good-list-please`, o cliente receberá uma resposta de **erro** informando que o `santa_tracker` cookie não é permitido: ```json { @@ -73,4 +73,4 @@ Por exemplo, se o cliente tentar enviar um cookie `santa_tracker` com o valor de ## Resumo { #summary } -Você consegue utilizar **modelos Pydantic** para declarar **cookies** no **FastAPI**. 😎 +Você consegue utilizar **modelos Pydantic** para declarar **cookies** no **FastAPI**. 😎 diff --git a/docs/pt/docs/tutorial/cors.md b/docs/pt/docs/tutorial/cors.md index 0f99db888..055cfeaca 100644 --- a/docs/pt/docs/tutorial/cors.md +++ b/docs/pt/docs/tutorial/cors.md @@ -46,7 +46,8 @@ Você também pode especificar se o seu backend permite: * Métodos HTTP específicos (`POST`, `PUT`) ou todos eles com o curinga `"*"`. * Cabeçalhos HTTP específicos ou todos eles com o curinga `"*"`. -{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} + Os parâmetros padrão usados ​​pela implementação `CORSMiddleware` são restritivos por padrão, então você precisará habilitar explicitamente as origens, métodos ou cabeçalhos específicos para que os navegadores tenham permissão para usá-los em um contexto cross domain. diff --git a/docs/pt/docs/tutorial/debugging.md b/docs/pt/docs/tutorial/debugging.md index e39c7d128..773921bb3 100644 --- a/docs/pt/docs/tutorial/debugging.md +++ b/docs/pt/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ Você pode conectar o depurador no seu editor, por exemplo, com o Visual Studio Em sua aplicação FastAPI, importe e execute `uvicorn` diretamente: -{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} ### Sobre `__name__ == "__main__"` { #about-name-main } @@ -62,7 +62,7 @@ from myapp import app # Mais um pouco de código ``` -nesse caso, a variável criada automaticamente dentro de `myapp.py` não terá a variável `__name__` com o valor `"__main__"`. +nesse caso, a variável `__name__` criada automaticamente dentro de `myapp.py` não terá o valor `"__main__"`. Então, a linha: diff --git a/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md index c30d0b5f0..7231373a7 100644 --- a/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ O **FastAPI** chama a classe `CommonQueryParams`. Isso cria uma "instância" des Perceba como escrevemos `CommonQueryParams` duas vezes no código abaixo: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip | Dica @@ -137,7 +137,7 @@ O último `CommonQueryParams`, em: Nesse caso, o primeiro `CommonQueryParams`, em: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip | Dica @@ -163,7 +163,7 @@ commons: CommonQueryParams ... Na verdade você poderia escrever apenas: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip | Dica @@ -197,7 +197,7 @@ Mas declarar o tipo é encorajado por que é a forma que o seu editor de texto s Mas você pode ver que temos uma repetição do código neste exemplo, escrevendo `CommonQueryParams` duas vezes: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip | Dica @@ -225,7 +225,7 @@ Para esses casos específicos, você pode fazer o seguinte: Em vez de escrever: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip | Dica @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...escreva: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip | Dica diff --git a/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index ee8a58dc2..4a99091d1 100644 --- a/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/pt/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -1,6 +1,6 @@ # Dependências em decoradores de operações de rota { #dependencies-in-path-operation-decorators } -Em alguns casos você não precisa necessariamente retornar o valor de uma dependência dentro de uma *função de operação de rota*. +Em alguns casos você não precisa necessariamente do valor de retorno de uma dependência dentro de uma *função de operação de rota*. Ou a dependência não retorna nenhum valor. @@ -14,7 +14,7 @@ O *decorador da operação de rota* recebe um argumento opcional `dependencies`. Ele deve ser uma lista de `Depends()`: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} Essas dependências serão executadas/resolvidas da mesma forma que dependências comuns. Mas o valor delas (se existir algum) não será passado para a sua *função de operação de rota*. @@ -44,13 +44,13 @@ Você pode utilizar as mesmas *funções* de dependências que você usaria norm Dependências podem declarar requisitos de requisições (como cabeçalhos) ou outras subdependências: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} ### Levantar exceções { #raise-exceptions } Essas dependências podem `raise` exceções, da mesma forma que dependências comuns: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} ### Valores de retorno { #return-values } @@ -58,7 +58,7 @@ E elas também podem ou não retornar valores, eles não serão utilizados. Então, você pode reutilizar uma dependência comum (que retorna um valor) que já seja utilizada em outro lugar, e mesmo que o valor não seja utilizado, a dependência será executada: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} ## Dependências para um grupo de *operações de rota* { #dependencies-for-a-group-of-path-operations } diff --git a/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md index 367873013..dd9d9fbe6 100644 --- a/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,6 +1,6 @@ # Dependências com yield { #dependencies-with-yield } -O **FastAPI** possui suporte para dependências que realizam alguns passos extras ao finalizar. +O **FastAPI** possui suporte para dependências que realizam alguns passos extras ao finalizar. Para fazer isso, utilize `yield` em vez de `return`, e escreva os passos extras (código) depois. @@ -29,15 +29,15 @@ Por exemplo, você poderia utilizar isso para criar uma sessão do banco de dado Apenas o código anterior à declaração com `yield` e o código contendo essa declaração são executados antes de criar uma resposta: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[2:4] *} O valor gerado (yielded) é o que é injetado nas *operações de rota* e outras dependências: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[4] *} O código após o `yield` é executado após a resposta: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[5:6] *} /// tip | Dica @@ -57,7 +57,7 @@ Então, você pode procurar por essa exceção específica dentro da dependênci Da mesma forma, você pode utilizar `finally` para garantir que os passos de saída são executados, com ou sem exceções. -{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[3,5] *} ## Subdependências com `yield` { #sub-dependencies-with-yield } @@ -67,7 +67,7 @@ O **FastAPI** garantirá que o "código de saída" em cada dependência com `yie Por exemplo, `dependency_c` pode depender de `dependency_b`, e `dependency_b` depender de `dependency_a`: -{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[6,14,22] *} E todas elas podem utilizar `yield`. @@ -75,7 +75,7 @@ Neste caso, `dependency_c`, para executar seu código de saída, precisa que o v E, por outro lado, `dependency_b` precisa que o valor de `dependency_a` (nomeado de `dep_a`) esteja disponível para executar seu código de saída. -{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *} +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[18:19,26:27] *} Da mesma forma, você pode ter algumas dependências com `yield` e outras com `return` e ter uma relação de dependência entre algumas das duas. @@ -109,7 +109,7 @@ Mas ela existe para ser utilizada caso você precise. 🤓 /// -{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *} +{* ../../docs_src/dependencies/tutorial008b_an_py310.py hl[18:22,31] *} Se você quiser capturar exceções e criar uma resposta personalizada com base nisso, crie um [Manipulador de Exceções Customizado](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. @@ -117,7 +117,7 @@ Se você quiser capturar exceções e criar uma resposta personalizada com base Se você capturar uma exceção com `except` em uma dependência que utilize `yield` e ela não for levantada novamente (ou uma nova exceção for levantada), o FastAPI não será capaz de identificar que houve uma exceção, da mesma forma que aconteceria com Python puro: -{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} +{* ../../docs_src/dependencies/tutorial008c_an_py310.py hl[15:16] *} Neste caso, o cliente irá ver uma resposta *HTTP 500 Internal Server Error* como deveria acontecer, já que não estamos levantando nenhuma `HTTPException` ou coisa parecida, mas o servidor **não terá nenhum log** ou qualquer outra indicação de qual foi o erro. 😱 @@ -127,7 +127,7 @@ Se você capturar uma exceção em uma dependência com `yield`, a menos que voc Você pode relançar a mesma exceção utilizando `raise`: -{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *} +{* ../../docs_src/dependencies/tutorial008d_an_py310.py hl[17] *} Agora o cliente irá receber a mesma resposta *HTTP 500 Internal Server Error*, mas o servidor terá nosso `InternalError` personalizado nos logs. 😎 @@ -190,7 +190,7 @@ Normalmente, o código de saída das dependências com `yield` é executado **ap Mas se você sabe que não precisará usar a dependência depois de retornar da *função de operação de rota*, você pode usar `Depends(scope="function")` para dizer ao FastAPI que deve fechar a dependência depois que a *função de operação de rota* retornar, mas **antes** de a **resposta ser enviada**. -{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *} +{* ../../docs_src/dependencies/tutorial008e_an_py310.py hl[12,16] *} `Depends()` recebe um parâmetro `scope` que pode ser: @@ -269,7 +269,7 @@ Em Python, você pode criar Gerenciadores de Contexto ao Injeção de Dependência**. +O **FastAPI** possui um poderoso, mas intuitivo sistema de **Injeção de Dependência**. Esse sistema foi pensado para ser fácil de usar, e permitir que qualquer desenvolvedor possa integrar facilmente outros componentes ao **FastAPI**. @@ -25,7 +25,7 @@ Vamos ver um exemplo simples. Tão simples que não será muito útil, por enqua Mas dessa forma podemos focar em como o sistema de **Injeção de Dependência** funciona. -### Criando uma dependência, ou "injetável" { #create-a-dependency-or-dependable } +### Criando uma dependência, ou "dependable" { #create-a-dependency-or-dependable } Primeiro vamos focar na dependência. @@ -89,7 +89,7 @@ Você verá quais outras "coisas", além de funções, podem ser usadas como dep Sempre que uma nova requisição for realizada, o **FastAPI** se encarrega de: -* Chamar sua dependência ("injetável") com os parâmetros corretos. +* Chamar sua dependência ("dependable") com os parâmetros corretos. * Obter o resultado da função. * Atribuir esse resultado para o parâmetro em sua *função de operação de rota*. @@ -186,7 +186,7 @@ Outros termos comuns para essa mesma ideia de "injeção de dependência" são: Integrações e "plug-ins" podem ser construídos com o sistema de **Injeção de Dependência**. Mas na verdade, **não há necessidade de criar "plug-ins"**, já que utilizando dependências é possível declarar um número infinito de integrações e interações que se tornam disponíveis para as suas *funções de operação de rota*. -E as dependências pode ser criadas de uma forma bastante simples e intuitiva que permite que você importe apenas os pacotes Python que forem necessários, e integrá-los com as funções de sua API em algumas linhas de código, *literalmente*. +E as dependências podem ser criadas de uma forma bastante simples e intuitiva que permite que você importe apenas os pacotes Python que forem necessários, e integrá-los com as funções de sua API em algumas linhas de código, *literalmente*. Você verá exemplos disso nos próximos capítulos, acerca de bancos de dados relacionais e NoSQL, segurança, etc. @@ -199,7 +199,7 @@ A simplicidade do sistema de injeção de dependência do **FastAPI** faz ele co * pacotes externos * APIs externas * sistemas de autenticação e autorização -* istemas de monitoramento de uso para APIs +* sistemas de monitoramento de uso para APIs * sistemas de injeção de dados de resposta * etc. @@ -209,7 +209,7 @@ Mesmo que o sistema hierárquico de injeção de dependência seja simples de de Você pode definir dependências que por sua vez definem suas próprias dependências. -No fim, uma árvore hierárquica de dependências é criadas, e o sistema de **Injeção de Dependência** toma conta de resolver todas essas dependências (e as sub-dependências delas) para você, e provê (injeta) os resultados em cada passo. +Por fim, uma árvore hierárquica de dependências é criada, e o sistema de **Injeção de Dependência** toma conta de resolver todas essas dependências (e as sub-dependências delas) para você, e provê (injeta) os resultados em cada passo. Por exemplo, vamos supor que você possua 4 endpoints na sua API (*operações de rota*): diff --git a/docs/pt/docs/tutorial/dependencies/sub-dependencies.md b/docs/pt/docs/tutorial/dependencies/sub-dependencies.md index e2dc9bbf9..63ed0e48a 100644 --- a/docs/pt/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/pt/docs/tutorial/dependencies/sub-dependencies.md @@ -58,11 +58,11 @@ query_extractor --> query_or_cookie_extractor --> read_query Se uma de suas dependências é declarada várias vezes para a mesma *operação de rota*, por exemplo, múltiplas dependências com uma mesma subdependência, o **FastAPI** irá chamar essa subdependência uma única vez para cada requisição. -E o valor retornado é salvo em um "cache" e repassado para todos os "dependentes" que precisam dele em uma requisição específica, em vez de chamar a dependência múltiplas vezes para uma mesma requisição. +E o valor retornado é salvo em um "cache" e repassado para todos os "dependentes" que precisam dele em uma requisição específica, em vez de chamar a dependência múltiplas vezes para uma mesma requisição. Em um cenário avançado onde você precise que a dependência seja calculada em cada passo (possivelmente várias vezes) de uma requisição em vez de utilizar o valor em "cache", você pode definir o parâmetro `use_cache=False` em `Depends`: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python hl_lines="1" async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): @@ -71,7 +71,7 @@ async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_ca //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ non-Annotated /// tip | Dica diff --git a/docs/pt/docs/tutorial/extra-models.md b/docs/pt/docs/tutorial/extra-models.md index 24eafce01..313659741 100644 --- a/docs/pt/docs/tutorial/extra-models.md +++ b/docs/pt/docs/tutorial/extra-models.md @@ -190,9 +190,9 @@ Mas se colocarmos isso na atribuição `response_model=PlaneItem | CarItem`, ter Da mesma forma, você pode declarar respostas de listas de objetos. -Para isso, use o padrão Python `typing.List` (ou simplesmente `list` no Python 3.9 e superior): +Para isso, use o padrão Python `list`: -{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} +{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *} ## Resposta com `dict` arbitrário { #response-with-arbitrary-dict } @@ -200,9 +200,9 @@ Você também pode declarar uma resposta usando um simples `dict` arbitrário, d Isso é útil se você não souber os nomes de campo / atributo válidos (que seriam necessários para um modelo Pydantic) antecipadamente. -Neste caso, você pode usar `typing.Dict` (ou simplesmente `dict` no Python 3.9 e superior): +Neste caso, você pode usar `dict`: -{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} +{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *} ## Recapitulação { #recap } diff --git a/docs/pt/docs/tutorial/first-steps.md b/docs/pt/docs/tutorial/first-steps.md index 86cddde5d..4ccc7cf04 100644 --- a/docs/pt/docs/tutorial/first-steps.md +++ b/docs/pt/docs/tutorial/first-steps.md @@ -2,7 +2,7 @@ O arquivo FastAPI mais simples pode se parecer com: -{* ../../docs_src/first_steps/tutorial001_py39.py *} +{* ../../docs_src/first_steps/tutorial001_py310.py *} Copie o conteúdo para um arquivo `main.py`. @@ -183,7 +183,7 @@ Deploying to FastAPI Cloud... ### Passo 1: importe `FastAPI` { #step-1-import-fastapi } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[1] *} `FastAPI` é uma classe Python que fornece todas as funcionalidades para sua API. @@ -197,7 +197,7 @@ Você pode usar todas as funcionalidades do operação get +* usando uma get operação /// info | Informações sobre `@decorator` @@ -320,7 +320,7 @@ Esta é a nossa "**função de operação de rota**": * **operação**: é `get`. * **função**: é a função abaixo do "decorador" (abaixo do `@app.get("/")`). -{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[7] *} Esta é uma função Python. @@ -332,7 +332,7 @@ Neste caso, é uma função `async`. Você também pode defini-la como uma função normal em vez de `async def`: -{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py310.py hl[7] *} /// note | Nota @@ -342,7 +342,7 @@ Se você não sabe a diferença, verifique o [Async: *"Com pressa?"*](../async.m ### Passo 5: retorne o conteúdo { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[8] *} Você pode retornar um `dict`, `list` e valores singulares como `str`, `int`, etc. diff --git a/docs/pt/docs/tutorial/handling-errors.md b/docs/pt/docs/tutorial/handling-errors.md index 1c162c205..252dbb06f 100644 --- a/docs/pt/docs/tutorial/handling-errors.md +++ b/docs/pt/docs/tutorial/handling-errors.md @@ -25,7 +25,7 @@ Para retornar ao cliente *responses* HTTP com erros, use o `HTTPException`. ### Import `HTTPException` { #import-httpexception } -{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *} +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[1] *} ### Lance o `HTTPException` no seu código { #raise-an-httpexception-in-your-code } @@ -33,13 +33,13 @@ Para retornar ao cliente *responses* HTTP com erros, use o `HTTPException`. E porque é uma exceção do Python, você não **retorna** (return) o `HTTPException`, você lança o (raise) no seu código. -Isso também significa que, se você está escrevendo uma função de utilidade, a qual você está chamando dentro da sua função de operações de caminhos, e você lança o `HTTPException` dentro da função de utilidade, o resto do seu código não será executado dentro da função de operações de caminhos. Ao contrário, o `HTTPException` irá finalizar a requisição no mesmo instante e enviará o erro HTTP oriundo do `HTTPException` para o cliente. +Isso também significa que, se você está escrevendo uma função de utilidade, a qual você está chamando dentro da sua função de operação de rota, e você lança o `HTTPException` dentro da função de utilidade, o resto do seu código não será executado dentro da função de operação de rota. Ao contrário, o `HTTPException` irá finalizar a requisição no mesmo instante e enviará o erro HTTP oriundo do `HTTPException` para o cliente. O benefício de lançar uma exceção em vez de retornar um valor ficará mais evidente na seção sobre Dependências e Segurança. Neste exemplo, quando o cliente pede, na requisição, por um item cujo ID não existe, a exceção com o status code `404` é lançada: -{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *} +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[11] *} ### A response resultante { #the-resulting-response } @@ -77,7 +77,7 @@ Você provavelmente não precisará utilizar esses headers diretamente no seu c Mas caso você precise, para um cenário mais complexo, você pode adicionar headers customizados: -{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial002_py310.py hl[14] *} ## Instale manipuladores de exceções customizados { #install-custom-exception-handlers } @@ -87,9 +87,9 @@ Digamos que você tenha uma exceção customizada `UnicornException` que você ( Nesse cenário, se você precisa manipular essa exceção de modo global com o FastAPI, você pode adicionar um manipulador de exceção customizada com `@app.exception_handler()`. -{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *} +{* ../../docs_src/handling_errors/tutorial003_py310.py hl[5:7,13:18,24] *} -Nesse cenário, se você fizer uma requisição para `/unicorns/yolo`, a *operação de caminho* vai lançar (`raise`) o `UnicornException`. +Nesse cenário, se você fizer uma requisição para `/unicorns/yolo`, a *operação de rota* vai lançar (`raise`) o `UnicornException`. Essa exceção será manipulada, contudo, pelo `unicorn_exception_handler`. @@ -125,7 +125,7 @@ Para sobrescrevê-lo, importe o `RequestValidationError` e use-o com o `@app.exc O manipulador de exceções receberá um `Request` e a exceção. -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[2,14:19] *} Se você for ao `/items/foo`, em vez de receber o JSON padrão com o erro: @@ -157,7 +157,7 @@ Do mesmo modo, você pode sobrescrever o `HTTPException`. Por exemplo, você pode querer retornar uma *response* em *plain text* ao invés de um JSON para os seguintes erros: -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[3:4,9:11,25] *} /// note | Detalhes Técnicos @@ -181,7 +181,7 @@ O `RequestValidationError` contém o `body` que ele recebeu de dados inválidos. Você pode utilizá-lo enquanto desenvolve seu app para registrar o *body* e debugá-lo, e assim retorná-lo ao usuário, etc. -{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial005_py310.py hl[14] *} Tente enviar um item inválido como este: @@ -237,6 +237,6 @@ from starlette.exceptions import HTTPException as StarletteHTTPException Se você quer usar a exceção em conjunto com o mesmo manipulador de exceção *default* do **FastAPI**, você pode importar e re-usar esses manipuladores de exceção do `fastapi.exception_handlers`: -{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *} +{* ../../docs_src/handling_errors/tutorial006_py310.py hl[2:5,15,21] *} Nesse exemplo você apenas imprime (`print`) o erro com uma mensagem expressiva. Mesmo assim, dá para pegar a ideia. Você pode usar a exceção e então apenas re-usar o manipulador de exceção *default*. diff --git a/docs/pt/docs/tutorial/header-params.md b/docs/pt/docs/tutorial/header-params.md index a0deb98be..a56f5805e 100644 --- a/docs/pt/docs/tutorial/header-params.md +++ b/docs/pt/docs/tutorial/header-params.md @@ -12,7 +12,7 @@ Primeiro importe `Header`: Então declare os paramêtros de cabeçalho usando a mesma estrutura que em `Path`, `Query` e `Cookie`. -O primeiro valor é o valor padrão, você pode passar todas as validações adicionais ou parâmetros de anotação: +Você pode definir o valor padrão, assim como todas as validações extras ou parâmetros de anotação: {* ../../docs_src/header_params/tutorial001_an_py310.py hl[9] *} diff --git a/docs/pt/docs/tutorial/metadata.md b/docs/pt/docs/tutorial/metadata.md index df77e5648..476b5c806 100644 --- a/docs/pt/docs/tutorial/metadata.md +++ b/docs/pt/docs/tutorial/metadata.md @@ -1,4 +1,4 @@ -# Metadados e Urls de Documentos { #metadata-and-docs-urls } +# Metadados e URLs da Documentação { #metadata-and-docs-urls } Você pode personalizar várias configurações de metadados na sua aplicação **FastAPI**. @@ -18,7 +18,7 @@ Você pode definir os seguintes campos que são usados na especificação OpenAP Você pode defini-los da seguinte maneira: -{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} +{* ../../docs_src/metadata/tutorial001_py310.py hl[3:16, 19:32] *} /// tip | Dica @@ -32,11 +32,11 @@ Com essa configuração, a documentação automática da API se pareceria com: ## Identificador de Licença { #license-identifier } -Desde o OpenAPI 3.1.0 e FastAPI 0.99.0, você também pode definir o license_info com um identifier em vez de uma url. +Desde o OpenAPI 3.1.0 e FastAPI 0.99.0, você também pode definir o `license_info` com um `identifier` em vez de uma `url`. Por exemplo: -{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} +{* ../../docs_src/metadata/tutorial001_1_py310.py hl[31] *} ## Metadados para tags { #metadata-for-tags } @@ -58,7 +58,7 @@ Vamos tentar isso em um exemplo com tags para `users` e `items`. Crie metadados para suas tags e passe-os para o parâmetro `openapi_tags`: -{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[3:16,18] *} Observe que você pode usar Markdown dentro das descrições. Por exemplo, "login" será exibido em negrito (**login**) e "fancy" será exibido em itálico (_fancy_). @@ -72,7 +72,7 @@ Você não precisa adicionar metadados para todas as tags que você usa. Use o parâmetro `tags` com suas *operações de rota* (e `APIRouter`s) para atribuí-los a diferentes tags: -{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[21,26] *} /// info | Informação @@ -100,7 +100,7 @@ Mas você pode configurá-lo com o parâmetro `openapi_url`. Por exemplo, para defini-lo para ser servido em `/api/v1/openapi.json`: -{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py310.py hl[3] *} Se você quiser desativar completamente o esquema OpenAPI, pode definir `openapi_url=None`, o que também desativará as interfaces de documentação que o utilizam. @@ -117,4 +117,4 @@ Você pode configurar as duas interfaces de documentação incluídas: Por exemplo, para definir o Swagger UI para ser servido em `/documentation` e desativar o ReDoc: -{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial003_py310.py hl[3] *} diff --git a/docs/pt/docs/tutorial/middleware.md b/docs/pt/docs/tutorial/middleware.md index b49c1eaa1..7cccfcb6a 100644 --- a/docs/pt/docs/tutorial/middleware.md +++ b/docs/pt/docs/tutorial/middleware.md @@ -31,13 +31,13 @@ A função middleware recebe: * Então ela retorna a `response` gerada pela *operação de rota* correspondente. * Você pode então modificar ainda mais o `response` antes de retorná-lo. -{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[8:9,11,14] *} /// tip | Dica Tenha em mente que cabeçalhos proprietários personalizados podem ser adicionados usando o prefixo `X-`. -Mas se você tiver cabeçalhos personalizados desejando que um cliente em um navegador esteja apto a ver, você precisa adicioná-los às suas configurações CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) usando o parâmetro `expose_headers` documentado em Documentos CORS da Starlette. +Mas se você tiver cabeçalhos personalizados desejando que um cliente em um navegador esteja apto a ver, você precisa adicioná-los às suas configurações CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) usando o parâmetro `expose_headers` documentado em Documentação CORS da Starlette. /// @@ -57,7 +57,7 @@ E também depois que a `response` é gerada, antes de retorná-la. Por exemplo, você pode adicionar um cabeçalho personalizado `X-Process-Time` contendo o tempo em segundos que levou para processar a solicitação e gerar uma resposta: -{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[10,12:13] *} /// tip | Dica diff --git a/docs/pt/docs/tutorial/path-operation-configuration.md b/docs/pt/docs/tutorial/path-operation-configuration.md index 2527c2892..c17b12e2b 100644 --- a/docs/pt/docs/tutorial/path-operation-configuration.md +++ b/docs/pt/docs/tutorial/path-operation-configuration.md @@ -46,7 +46,7 @@ Nestes casos, pode fazer sentido armazenar as tags em um `Enum`. **FastAPI** suporta isso da mesma maneira que com strings simples: -{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *} +{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *} ## Resumo e descrição { #summary-and-description } @@ -56,7 +56,7 @@ Você pode adicionar um `summary` e uma `description`: ## Descrição do docstring { #description-from-docstring } -Como as descrições tendem a ser longas e cobrir várias linhas, você pode declarar a descrição da *operação de rota* na docstring da função e o **FastAPI** irá lê-la de lá. +Como as descrições tendem a ser longas e cobrir várias linhas, você pode declarar a descrição da *operação de rota* na docstring da função e o **FastAPI** irá lê-la de lá. Você pode escrever Markdown na docstring, ele será interpretado e exibido corretamente (levando em conta a indentação da docstring). @@ -90,9 +90,9 @@ Então, se você não fornecer uma, o **FastAPI** irá gerar automaticamente uma ## Descontinuar uma *operação de rota* { #deprecate-a-path-operation } -Se você precisar marcar uma *operação de rota* como descontinuada, mas sem removê-la, passe o parâmetro `deprecated`: +Se você precisar marcar uma *operação de rota* como descontinuada, mas sem removê-la, passe o parâmetro `deprecated`: -{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} Ela será claramente marcada como descontinuada nas documentações interativas: diff --git a/docs/pt/docs/tutorial/path-params-numeric-validations.md b/docs/pt/docs/tutorial/path-params-numeric-validations.md index 9f12ba38f..bb2e154f4 100644 --- a/docs/pt/docs/tutorial/path-params-numeric-validations.md +++ b/docs/pt/docs/tutorial/path-params-numeric-validations.md @@ -54,11 +54,11 @@ Isso não faz diferença para o **FastAPI**. Ele vai detectar os parâmetros pel Então, você pode declarar sua função assim: -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} Mas tenha em mente que, se você usar `Annotated`, você não terá esse problema, não fará diferença, pois você não está usando valores padrão de parâmetros de função para `Query()` ou `Path()`. -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## Ordene os parâmetros de acordo com sua necessidade, truques { #order-the-parameters-as-you-need-tricks } @@ -81,15 +81,15 @@ Se você quiser: Passe `*`, como o primeiro parâmetro da função. -O Python não fará nada com esse `*`, mas saberá que todos os parâmetros seguintes devem ser chamados como argumentos nomeados (pares chave-valor), também conhecidos como kwargs. Mesmo que eles não tenham um valor padrão. +O Python não fará nada com esse `*`, mas saberá que todos os parâmetros seguintes devem ser chamados como argumentos nomeados (pares chave-valor), também conhecidos como kwargs. Mesmo que eles não tenham um valor padrão. -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### Melhor com `Annotated` { #better-with-annotated } Tenha em mente que, se você usar `Annotated`, como você não está usando valores padrão de parâmetros de função, você não terá esse problema e provavelmente não precisará usar `*`. -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## Validações numéricas: maior que ou igual { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ Com `Query` e `Path` (e outras que você verá depois) você pode declarar restr Aqui, com `ge=1`, `item_id` precisará ser um número inteiro “`g`reater than or `e`qual” a `1`. -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## Validações numéricas: maior que e menor que ou igual { #number-validations-greater-than-and-less-than-or-equal } @@ -106,7 +106,7 @@ O mesmo se aplica a: * `gt`: maior que (`g`reater `t`han) * `le`: menor que ou igual (`l`ess than or `e`qual) -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## Validações numéricas: floats, maior que e menor que { #number-validations-floats-greater-than-and-less-than } @@ -118,7 +118,7 @@ Assim, `0.5` seria um valor válido. Mas `0.0` ou `0` não seriam. E o mesmo para lt. -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## Recapitulando { #recap } diff --git a/docs/pt/docs/tutorial/path-params.md b/docs/pt/docs/tutorial/path-params.md index 1f47ca6e5..e8e420ad0 100644 --- a/docs/pt/docs/tutorial/path-params.md +++ b/docs/pt/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Você pode declarar "parâmetros" ou "variáveis" de path com a mesma sintaxe usada por strings de formatação do Python: -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} O valor do parâmetro de path `item_id` será passado para a sua função como o argumento `item_id`. @@ -16,7 +16,7 @@ Então, se você executar este exemplo e acessar conversão { #data-conversion } +## Dados conversão { #data-conversion } Se você executar este exemplo e abrir seu navegador em http://127.0.0.1:8000/items/3, você verá uma resposta: @@ -35,7 +35,7 @@ Se você executar este exemplo e abrir seu navegador em "parsing" automático do request. +Então, com essa declaração de tipo, o **FastAPI** fornece "parsing" automático do request. /// ## Validação de dados { #data-validation } @@ -110,19 +110,19 @@ E então você também pode ter um path `/users/{user_id}` para obter dados sobr Como as *operações de rota* são avaliadas em ordem, você precisa garantir que o path para `/users/me` seja declarado antes do de `/users/{user_id}`: -{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *} Caso contrário, o path para `/users/{user_id}` também corresponderia a `/users/me`, "achando" que está recebendo um parâmetro `user_id` com o valor `"me"`. Da mesma forma, você não pode redefinir uma operação de rota: -{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *} A primeira sempre será usada, já que o path corresponde primeiro. ## Valores predefinidos { #predefined-values } -Se você tem uma *operação de rota* que recebe um *parâmetro de path*, mas quer que os valores válidos possíveis do *parâmetro de path* sejam predefinidos, você pode usar um `Enum` padrão do Python. +Se você tem uma *operação de rota* que recebe um *parâmetro de path*, mas quer que os valores válidos possíveis do *parâmetro de path* sejam predefinidos, você pode usar um `Enum` padrão do Python. ### Crie uma classe `Enum` { #create-an-enum-class } @@ -132,17 +132,17 @@ Ao herdar de `str`, a documentação da API saberá que os valores devem ser do Em seguida, crie atributos de classe com valores fixos, que serão os valores válidos disponíveis: -{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *} /// tip | Dica -Se você está se perguntando, "AlexNet", "ResNet" e "LeNet" são apenas nomes de modelos de Aprendizado de Máquina. +Se você está se perguntando, "AlexNet", "ResNet" e "LeNet" são apenas nomes de modelos de Aprendizado de Máquina modelos. /// ### Declare um parâmetro de path { #declare-a-path-parameter } Em seguida, crie um *parâmetro de path* com anotação de tipo usando a classe enum que você criou (`ModelName`): -{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[16] *} ### Verifique a documentação { #check-the-docs } @@ -158,13 +158,13 @@ O valor do *parâmetro de path* será um *membro de enumeração*. Você pode compará-lo com o *membro de enumeração* no seu enum `ModelName` criado: -{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[17] *} #### Obtenha o valor da enumeração { #get-the-enumeration-value } Você pode obter o valor real (um `str` neste caso) usando `model_name.value`, ou, em geral, `your_enum_member.value`: -{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[20] *} /// tip | Dica Você também pode acessar o valor `"lenet"` com `ModelName.lenet.value`. @@ -176,7 +176,7 @@ Você pode retornar *membros de enum* da sua *operação de rota*, até mesmo an Eles serão convertidos para seus valores correspondentes (strings neste caso) antes de serem retornados ao cliente: -{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[18,21,23] *} No seu cliente, você receberá uma resposta JSON como: @@ -215,7 +215,7 @@ Nesse caso, o nome do parâmetro é `file_path`, e a última parte, `:path`, diz Então, você pode usá-lo com: -{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py310.py hl[6] *} /// tip | Dica Você pode precisar que o parâmetro contenha `/home/johndoe/myfile.txt`, com uma barra inicial (`/`). @@ -227,8 +227,8 @@ Nesse caso, a URL seria: `/files//home/johndoe/myfile.txt`, com uma barra dupla Com o **FastAPI**, ao usar declarações de tipo do Python curtas, intuitivas e padrão, você obtém: -- Suporte no editor: verificações de erro, autocompletar, etc. -- "Parsing" de dados +- Suporte no editor: verificações de erro, preenchimento automático, etc. +- "parsing" de dados - Validação de dados - Anotação da API e documentação automática diff --git a/docs/pt/docs/tutorial/query-param-models.md b/docs/pt/docs/tutorial/query-param-models.md index 42d2604cd..7fc59c033 100644 --- a/docs/pt/docs/tutorial/query-param-models.md +++ b/docs/pt/docs/tutorial/query-param-models.md @@ -18,10 +18,9 @@ Declare os **parâmetros de consulta** que você precisa em um **modelo Pydantic O **FastAPI** **extrairá** os dados para **cada campo** dos **parâmetros de consulta** presentes na requisição, e fornecerá o modelo Pydantic que você definiu. +## Verifique a Documentação { #check-the-docs } -## Verifique os Documentos { #check-the-docs } - -Você pode ver os parâmetros de consulta nos documentos de IU em `/docs`: +Você pode ver os parâmetros de consulta na IU da documentação em `/docs`:
@@ -29,9 +28,9 @@ Você pode ver os parâmetros de consulta nos documentos de IU em `/docs`: ## Restrinja Parâmetros de Consulta Extras { #forbid-extra-query-parameters } -Em alguns casos especiais (provavelmente não muito comuns), você queira **restrinjir** os parâmetros de consulta que deseja receber. +Em alguns casos especiais (provavelmente não muito comuns), você queira **restringir** os parâmetros de consulta que deseja receber. -Você pode usar a configuração do modelo Pydantic para `forbid` (proibir) qualquer campo `extra`: +Você pode usar a configuração do modelo Pydantic para `forbid` qualquer campo `extra`: {* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} @@ -43,7 +42,7 @@ Por exemplo, se o cliente tentar enviar um parâmetro de consulta `tool` com o v https://example.com/items/?limit=10&tool=plumbus ``` -Eles receberão um retorno de **erro** informando-os que o parâmentro de consulta `tool` não é permitido: +Eles receberão um retorno de **erro** informando-os que o parâmetro de consulta `tool` não é permitido: ```json { diff --git a/docs/pt/docs/tutorial/query-params-str-validations.md b/docs/pt/docs/tutorial/query-params-str-validations.md index c93a941e5..b76b76a26 100644 --- a/docs/pt/docs/tutorial/query-params-str-validations.md +++ b/docs/pt/docs/tutorial/query-params-str-validations.md @@ -18,7 +18,7 @@ Ter `str | None` permitirá que seu editor lhe ofereça melhor suporte e detecte ## Validação adicional { #additional-validation } -Vamos impor que, embora `q` seja opcional, sempre que for fornecido, **seu comprimento não exceda 50 caracteres**. +Vamos impor que, embora `q` seja opcional, sempre que for fornecido, seu comprimento não exceda 50 caracteres. ### Importe `Query` e `Annotated` { #import-query-and-annotated } @@ -47,40 +47,16 @@ Agora é a hora de usá-lo com FastAPI. 🚀 Tínhamos esta anotação de tipo: -//// tab | Python 3.10+ - ```Python q: str | None = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Union[str, None] = None -``` - -//// - O que faremos é envolver isso com `Annotated`, para que fique assim: -//// tab | Python 3.10+ - ```Python q: Annotated[str | None] = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Annotated[Union[str, None]] = None -``` - -//// - Ambas as versões significam a mesma coisa, `q` é um parâmetro que pode ser `str` ou `None`, e por padrão é `None`. Agora vamos pular para a parte divertida. 🎉 @@ -93,23 +69,23 @@ Agora que temos esse `Annotated` onde podemos colocar mais informações (neste Perceba que o valor padrão continua sendo `None`, então o parâmetro ainda é opcional. -Mas agora, com `Query(max_length=50)` dentro de `Annotated`, estamos dizendo ao FastAPI que queremos **validação adicional** para este valor, queremos que tenha no máximo 50 caracteres. 😎 +Mas agora, com `Query(max_length=50)` dentro de `Annotated`, estamos dizendo ao FastAPI que queremos validação adicional para este valor, queremos que tenha no máximo 50 caracteres. 😎 /// tip | Dica -Aqui estamos usando `Query()` porque este é um **parâmetro de consulta**. Mais adiante veremos outros como `Path()`, `Body()`, `Header()` e `Cookie()`, que também aceitam os mesmos argumentos que `Query()`. +Aqui estamos usando `Query()` porque este é um parâmetro de consulta. Mais adiante veremos outros como `Path()`, `Body()`, `Header()` e `Cookie()`, que também aceitam os mesmos argumentos que `Query()`. /// Agora o FastAPI vai: -* **Validar** os dados garantindo que o comprimento máximo seja de 50 caracteres -* Mostrar um **erro claro** para o cliente quando os dados não forem válidos -* **Documentar** o parâmetro na *operação de rota* do esquema OpenAPI (então ele aparecerá na **UI de docs automática**) +* Validar os dados garantindo que o comprimento máximo seja de 50 caracteres +* Mostrar um erro claro para o cliente quando os dados não forem válidos +* Documentar o parâmetro na operação de rota do esquema OpenAPI (então ele aparecerá na UI de docs automática) ## Alternativa (antiga): `Query` como valor padrão { #alternative-old-query-as-the-default-value } -Versões anteriores do FastAPI (antes de 0.95.0) exigiam que você usasse `Query` como valor padrão do seu parâmetro, em vez de colocá-lo em `Annotated`, há uma grande chance de você ver código usando isso por aí, então vou explicar. +Versões anteriores do FastAPI (antes de 0.95.0) exigiam que você usasse `Query` como valor padrão do seu parâmetro, em vez de colocá-lo em `Annotated`, há uma grande chance de você ver código usando isso por aí, então vou explicar. /// tip | Dica @@ -144,7 +120,7 @@ Então, podemos passar mais parâmetros para `Query`. Neste caso, o parâmetro ` q: str | None = Query(default=None, max_length=50) ``` -Isso validará os dados, mostrará um erro claro quando os dados não forem válidos e documentará o parâmetro na *operação de rota* do esquema OpenAPI. +Isso validará os dados, mostrará um erro claro quando os dados não forem válidos e documentará o parâmetro na operação de rota do esquema OpenAPI. ### `Query` como valor padrão ou em `Annotated` { #query-as-the-default-value-or-in-annotated } @@ -174,13 +150,13 @@ q: str = Query(default="rick") ### Vantagens de `Annotated` { #advantages-of-annotated } -**Usar `Annotated` é recomendado** em vez do valor padrão nos parâmetros da função, é **melhor** por vários motivos. 🤓 +Usar `Annotated` é recomendado em vez do valor padrão nos parâmetros da função, é melhor por vários motivos. 🤓 -O valor **padrão** do **parâmetro da função** é o **valor padrão real**, isso é mais intuitivo com Python em geral. 😌 +O valor padrão do parâmetro da função é o valor padrão real, isso é mais intuitivo com Python em geral. 😌 -Você poderia **chamar** essa mesma função em **outros lugares** sem FastAPI, e ela **funcionaria como esperado**. Se houver um parâmetro **obrigatório** (sem valor padrão), seu **editor** vai avisar com um erro, e o **Python** também reclamará se você executá-la sem passar o parâmetro obrigatório. +Você poderia chamar essa mesma função em outros lugares sem FastAPI, e ela funcionaria como esperado. Se houver um parâmetro obrigatório (sem valor padrão), seu editor vai avisar com um erro, e o Python também reclamará se você executá-la sem passar o parâmetro obrigatório. -Quando você não usa `Annotated` e em vez disso usa o estilo de **valor padrão (antigo)**, se você chamar essa função sem FastAPI em **outros lugares**, terá que **lembrar** de passar os argumentos para a função para que funcione corretamente, caso contrário os valores serão diferentes do esperado (por exemplo, `QueryInfo` ou algo parecido em vez de `str`). E seu editor não vai avisar, e o Python também não vai reclamar ao executar a função, apenas quando as operações internas falharem. +Quando você não usa `Annotated` e em vez disso usa o estilo de valor padrão (antigo), se você chamar essa função sem FastAPI em outros lugares, terá que lembrar de passar os argumentos para a função para que funcione corretamente, caso contrário os valores serão diferentes do esperado (por exemplo, `QueryInfo` ou algo parecido em vez de `str`). E seu editor não vai avisar, e o Python também não vai reclamar ao executar a função, apenas quando as operações internas falharem. Como `Annotated` pode ter mais de uma anotação de metadados, você agora pode até usar a mesma função com outras ferramentas, como o Typer. 🚀 @@ -192,7 +168,7 @@ Você também pode adicionar um parâmetro `min_length`: ## Adicione expressões regulares { #add-regular-expressions } -Você pode definir um `pattern` de expressão regular que o parâmetro deve corresponder: +Você pode definir um `pattern` de expressão regular que o parâmetro deve corresponder: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -202,9 +178,9 @@ Esse padrão específico de expressão regular verifica se o valor recebido no p * `fixedquery`: tem exatamente o valor `fixedquery`. * `$`: termina ali, não tem mais caracteres depois de `fixedquery`. -Se você se sentir perdido com essas ideias de **"expressão regular"**, não se preocupe. Esse é um assunto difícil para muitas pessoas. Você ainda pode fazer muitas coisas sem precisar de expressões regulares por enquanto. +Se você se sentir perdido com essas ideias de "expressão regular", não se preocupe. Esse é um assunto difícil para muitas pessoas. Você ainda pode fazer muitas coisas sem precisar de expressões regulares por enquanto. -Agora você sabe que, sempre que precisar delas, pode usá-las no **FastAPI**. +Agora você sabe que, sempre que precisar delas, pode usá-las no FastAPI. ## Valores padrão { #default-values } @@ -212,7 +188,7 @@ Você pode, claro, usar valores padrão diferentes de `None`. Digamos que você queira declarar o parâmetro de consulta `q` com `min_length` de `3` e ter um valor padrão de `"fixedquery"`: -{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} /// note | Nota @@ -242,7 +218,7 @@ q: Annotated[str | None, Query(min_length=3)] = None Então, quando você precisa declarar um valor como obrigatório usando `Query`, você pode simplesmente não declarar um valor padrão: -{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} ### Obrigatório, pode ser `None` { #required-can-be-none } @@ -266,7 +242,7 @@ Então, com uma URL como: http://localhost:8000/items/?q=foo&q=bar ``` -você receberia os múltiplos valores dos *parâmetros de consulta* `q` (`foo` e `bar`) em uma `list` Python dentro da sua *função de operação de rota*, no *parâmetro da função* `q`. +você receberia os múltiplos valores dos parâmetros de consulta `q` (`foo` e `bar`) em uma `list` Python dentro da sua função de operação de rota, no parâmetro da função `q`. Assim, a resposta para essa URL seria: @@ -293,7 +269,7 @@ A documentação interativa da API será atualizada de acordo, permitindo múlti Você também pode definir uma `list` de valores padrão caso nenhum seja fornecido: -{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} Se você for até: @@ -316,13 +292,13 @@ o valor padrão de `q` será: `["foo", "bar"]` e sua resposta será: Você também pode usar `list` diretamente em vez de `list[str]`: -{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} /// note | Nota Tenha em mente que, neste caso, o FastAPI não verificará o conteúdo da lista. -Por exemplo, `list[int]` verificaria (e documentaria) que os conteúdos da lista são inteiros. Mas `list` sozinho não. +Por exemplo, `list[int]` verificaria (and documentaria) que os conteúdos da lista são inteiros. Mas `list` sozinho não. /// @@ -372,7 +348,7 @@ Então você pode declarar um `alias`, e esse alias será usado para encontrar o Agora digamos que você não gosta mais desse parâmetro. -Você tem que deixá-lo por um tempo, pois há clientes usando-o, mas quer que a documentação mostre claramente que ele está deprecated. +Você tem que deixá-lo por um tempo, pois há clientes usando-o, mas quer que a documentação mostre claramente que ele está descontinuado. Então passe o parâmetro `deprecated=True` para `Query`: @@ -390,9 +366,9 @@ Para excluir um parâmetro de consulta do OpenAPI gerado (e portanto, dos sistem ## Validação personalizada { #custom-validation } -Podem existir casos em que você precise fazer alguma **validação personalizada** que não pode ser feita com os parâmetros mostrados acima. +Podem existir casos em que você precise fazer alguma validação personalizada que não pode ser feita com os parâmetros mostrados acima. -Nesses casos, você pode usar uma **função validadora personalizada** que é aplicada após a validação normal (por exemplo, depois de validar que o valor é uma `str`). +Nesses casos, você pode usar uma função validadora personalizada que é aplicada após a validação normal (por exemplo, depois de validar que o valor é uma `str`). Você pode fazer isso usando o `AfterValidator` do Pydantic dentro de `Annotated`. @@ -402,7 +378,7 @@ O Pydantic também tem ISBN ou com `imdb-` para um ID de URL de filme IMDB: +Por exemplo, este validador personalizado verifica se o ID do item começa com `isbn-` para um número de livro ISBN ou com `imdb-` para um ID de URL de filme IMDB: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -414,15 +390,15 @@ Isso está disponível com a versão 2 do Pydantic ou superior. 😎 /// tip | Dica -Se você precisar fazer qualquer tipo de validação que exija comunicação com algum **componente externo**, como um banco de dados ou outra API, você deveria usar **Dependências do FastAPI** em vez disso; você aprenderá sobre elas mais adiante. +Se você precisar fazer qualquer tipo de validação que exija comunicação com algum componente externo, como um banco de dados ou outra API, você deveria usar Dependências do FastAPI em vez disso; você aprenderá sobre elas mais adiante. -Esses validadores personalizados são para coisas que podem ser verificadas **apenas** com os **mesmos dados** fornecidos na requisição. +Esses validadores personalizados são para coisas que podem ser verificadas apenas com os mesmos dados fornecidos na requisição. /// ### Entenda esse código { #understand-that-code } -O ponto importante é apenas usar **`AfterValidator` com uma função dentro de `Annotated`**. Sinta-se à vontade para pular esta parte. 🤸 +O ponto importante é apenas usar `AfterValidator` com uma função dentro de `Annotated`. Sinta-se à vontade para pular esta parte. 🤸 --- @@ -436,17 +412,17 @@ Percebeu? Uma string usando `value.startswith()` pode receber uma tupla, e verif #### Um item aleatório { #a-random-item } -Com `data.items()` obtemos um objeto iterável com tuplas contendo a chave e o valor de cada item do dicionário. +Com `data.items()` obtemos um objeto iterável com tuplas contendo a chave e o valor de cada item do dicionário. Convertimos esse objeto iterável em uma `list` adequada com `list(data.items())`. -Em seguida, com `random.choice()` podemos obter um **valor aleatório** da lista, então obtemos uma tupla com `(id, name)`. Será algo como `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. +Em seguida, com `random.choice()` podemos obter um valor aleatório da lista, então obtemos uma tupla com `(id, name)`. Será algo como `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. -Depois **atribuímos esses dois valores** da tupla às variáveis `id` e `name`. +Depois atribuímos esses dois valores da tupla às variáveis `id` e `name`. Assim, se o usuário não fornecer um ID de item, ele ainda receberá uma sugestão aleatória. -...fazemos tudo isso em **uma única linha simples**. 🤯 Você não ama Python? 🐍 +...fazemos tudo isso em uma única linha simples. 🤯 Você não ama Python? 🐍 {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} diff --git a/docs/pt/docs/tutorial/query-params.md b/docs/pt/docs/tutorial/query-params.md index 8826602a2..f42988566 100644 --- a/docs/pt/docs/tutorial/query-params.md +++ b/docs/pt/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ Quando você declara outros parâmetros na função que não fazem parte dos parâmetros da rota, esses parâmetros são automaticamente interpretados como parâmetros de "consulta". -{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} A consulta é o conjunto de pares chave-valor que vai depois de `?` na URL, separado pelo caractere `&`. @@ -24,7 +24,7 @@ Mas quando você declara eles com os tipos do Python (no exemplo acima, como `in Todo o processo que era aplicado para parâmetros de rota também é aplicado para parâmetros de consulta: * Suporte do editor (obviamente) -* "Parsing" de dados +* "análise" de dados * Validação de dados * Documentação automática @@ -127,7 +127,7 @@ Caso você não queira adicionar um valor específico mas queira apenas torná-l Porém, quando você quiser fazer com que o parâmetro de consulta seja obrigatório, você pode simplesmente não declarar nenhum valor como padrão. -{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} Aqui o parâmetro de consulta `needy` é um valor obrigatório, do tipo `str`. diff --git a/docs/pt/docs/tutorial/request-files.md b/docs/pt/docs/tutorial/request-files.md index 5d0891163..1364a1dd4 100644 --- a/docs/pt/docs/tutorial/request-files.md +++ b/docs/pt/docs/tutorial/request-files.md @@ -20,13 +20,13 @@ Isso é necessário, visto que os arquivos enviados são enviados como "dados de Importe `File` e `UploadFile` de `fastapi`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} ## Definir Parâmetros `File` { #define-file-parameters } Crie parâmetros de arquivo da mesma forma que você faria para `Body` ou `Form`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} /// info | Informação @@ -54,7 +54,7 @@ Mas há muitos casos em que você pode se beneficiar do uso de `UploadFile`. Defina um parâmetro de arquivo com um tipo de `UploadFile`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} Utilizar `UploadFile` tem várias vantagens sobre `bytes`: @@ -121,7 +121,7 @@ Dados de formulários normalmente são codificados usando o "media type" `applic Mas quando o formulário inclui arquivos, ele é codificado como `multipart/form-data`. Se você usar `File`, o **FastAPI** saberá que tem que pegar os arquivos da parte correta do corpo da requisição. -Se você quiser ler mais sobre essas codificações e campos de formulário, vá para a MDN web docs para POST. +Se você quiser ler mais sobre essas codificações e campos de formulário, vá para a MDN web docs para POST. /// @@ -143,7 +143,7 @@ Você pode tornar um arquivo opcional usando anotações de tipo padrão e defin Você também pode usar `File()` com `UploadFile`, por exemplo, para definir metadados adicionais: -{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} ## Uploads de Múltiplos Arquivos { #multiple-file-uploads } @@ -153,13 +153,13 @@ Eles serão associados ao mesmo "campo de formulário" enviado usando "dados de Para usar isso, declare uma lista de `bytes` ou `UploadFile`: -{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} Você receberá, tal como declarado, uma `list` de `bytes` ou `UploadFile`. /// note | Detalhes Técnicos -Você pode também pode usar `from starlette.responses import HTMLResponse`. +Você também pode usar `from starlette.responses import HTMLResponse`. **FastAPI** providencia o mesmo `starlette.responses` que `fastapi.responses` apenas como uma conveniência para você, o desenvolvedor. Mas a maioria das respostas disponíveis vem diretamente do Starlette. @@ -169,7 +169,7 @@ Você pode também pode usar `from starlette.responses import HTMLResponse`. Da mesma forma de antes, você pode usar `File()` para definir parâmetros adicionais, mesmo para `UploadFile`: -{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} ## Recapitulando { #recap } diff --git a/docs/pt/docs/tutorial/request-form-models.md b/docs/pt/docs/tutorial/request-form-models.md index 8eeffac2a..38f160aa8 100644 --- a/docs/pt/docs/tutorial/request-form-models.md +++ b/docs/pt/docs/tutorial/request-form-models.md @@ -24,7 +24,7 @@ Isto é suportado desde a versão `0.113.0` do FastAPI. 🤓 Você precisa apenas declarar um **modelo Pydantic** com os campos que deseja receber como **campos de formulários**, e então declarar o parâmetro como um `Form`: -{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} O **FastAPI** irá **extrair** as informações para **cada campo** dos **dados do formulário** na requisição e dar para você o modelo Pydantic que você definiu. @@ -42,13 +42,13 @@ Em alguns casos de uso especiais (provavelmente não muito comum), você pode de /// note | Nota -Isso é suportado deste a versão `0.114.0` do FastAPI. 🤓 +Isso é suportado desde a versão `0.114.0` do FastAPI. 🤓 /// Você pode utilizar a configuração de modelo do Pydantic para `proibir` qualquer campo `extra`: -{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} Caso um cliente tente enviar informações adicionais, ele receberá um retorno de **erro**. diff --git a/docs/pt/docs/tutorial/request-forms-and-files.md b/docs/pt/docs/tutorial/request-forms-and-files.md index 277fc2f60..8b5f034e9 100644 --- a/docs/pt/docs/tutorial/request-forms-and-files.md +++ b/docs/pt/docs/tutorial/request-forms-and-files.md @@ -16,13 +16,13 @@ $ pip install python-multipart ## Importe `File` e `Form` { #import-file-and-form } -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} ## Defina parâmetros de `File` e `Form` { #define-file-and-form-parameters } Crie parâmetros de arquivo e formulário da mesma forma que você faria para `Body` ou `Query`: -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} Os arquivos e campos de formulário serão carregados como dados de formulário e você receberá os arquivos e campos de formulário. @@ -30,7 +30,7 @@ E você pode declarar alguns dos arquivos como `bytes` e alguns como `UploadFile /// warning | Atenção -Você pode declarar vários parâmetros `File` e `Form` em uma *operação de caminho*, mas não é possível declarar campos `Body` para receber como JSON, pois a requisição terá o corpo codificado usando `multipart/form-data` ao invés de `application/json`. +Você pode declarar vários parâmetros `File` e `Form` em uma *operação de rota*, mas não é possível declarar campos `Body` para receber como JSON, pois a requisição terá o corpo codificado usando `multipart/form-data` ao invés de `application/json`. Isso não é uma limitação do **FastAPI**, é parte do protocolo HTTP. @@ -38,4 +38,4 @@ Isso não é uma limitação do **FastAPI**, é parte do protocolo HTTP. ## Recapitulando { #recap } -Usar `File` e `Form` juntos quando precisar receber dados e arquivos na mesma requisição. +Use `File` e `Form` juntos quando precisar receber dados e arquivos na mesma requisição. diff --git a/docs/pt/docs/tutorial/request-forms.md b/docs/pt/docs/tutorial/request-forms.md index faa50bcbf..d255d0f9b 100644 --- a/docs/pt/docs/tutorial/request-forms.md +++ b/docs/pt/docs/tutorial/request-forms.md @@ -18,17 +18,17 @@ $ pip install python-multipart Importe `Form` de `fastapi`: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} ## Defina parâmetros de `Form` { #define-form-parameters } Crie parâmetros de formulário da mesma forma que você faria para `Body` ou `Query`: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} Por exemplo, em uma das maneiras que a especificação OAuth2 pode ser usada (chamada "fluxo de senha"), é necessário enviar um `username` e uma `password` como campos do formulário. -A spec exige que os campos sejam exatamente nomeados como `username` e `password` e sejam enviados como campos de formulário, não JSON. +A especificação exige que os campos sejam exatamente nomeados como `username` e `password` e sejam enviados como campos de formulário, não JSON. Com `Form` você pode declarar as mesmas configurações que com `Body` (e `Query`, `Path`, `Cookie`), incluindo validação, exemplos, um alias (por exemplo, `user-name` em vez de `username`), etc. diff --git a/docs/pt/docs/tutorial/response-model.md b/docs/pt/docs/tutorial/response-model.md index 8a7a71248..e3b97b630 100644 --- a/docs/pt/docs/tutorial/response-model.md +++ b/docs/pt/docs/tutorial/response-model.md @@ -183,7 +183,7 @@ Pode haver casos em que você retorna algo que não é um campo Pydantic válido O caso mais comum seria [retornar uma Response diretamente, conforme explicado posteriormente na documentação avançada](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} Este caso simples é tratado automaticamente pelo FastAPI porque a anotação do tipo de retorno é a classe (ou uma subclasse de) `Response`. @@ -193,7 +193,7 @@ E as ferramentas também ficarão felizes porque `RedirectResponse` e ​​`JSO Você também pode usar uma subclasse de `Response` na anotação de tipo: -{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} Isso também funcionará porque `RedirectResponse` é uma subclasse de `Response`, e o FastAPI tratará automaticamente este caso simples. @@ -201,7 +201,7 @@ Isso também funcionará porque `RedirectResponse` é uma subclasse de `Response Mas quando você retorna algum outro objeto arbitrário que não é um tipo Pydantic válido (por exemplo, um objeto de banco de dados) e você o anota dessa forma na função, o FastAPI tentará criar um modelo de resposta Pydantic a partir dessa anotação de tipo e falhará. -O mesmo aconteceria se você tivesse algo como uma união entre tipos diferentes onde um ou mais deles não são tipos Pydantic válidos, por exemplo, isso falharia 💥: +O mesmo aconteceria se você tivesse algo como uma união entre tipos diferentes onde um ou mais deles não são tipos Pydantic válidos, por exemplo, isso falharia 💥: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} @@ -290,7 +290,7 @@ Se os dados tiverem os mesmos valores que os padrões, como o item com ID `baz`: } ``` -O FastAPI é inteligente o suficiente (na verdade, o Pydantic é inteligente o suficiente) para perceber que, embora `description`, `tax` e `tags` tenham os mesmos valores que os padrões, eles foram definidos explicitamente (em vez de retirados dos padrões). +O FastAPI é inteligente o suficiente (na verdade, o Pydantic é inteligente o suficiente) para perceber que, embora `description`, `tax` e `tags` tenham os mesmos valores que os padrões, eles foram definidos explícita e diretamente (em vez de retirados dos padrões). Portanto, eles serão incluídos na resposta JSON. diff --git a/docs/pt/docs/tutorial/response-status-code.md b/docs/pt/docs/tutorial/response-status-code.md index 756c86dad..a3f8d8a56 100644 --- a/docs/pt/docs/tutorial/response-status-code.md +++ b/docs/pt/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ Da mesma forma que você pode especificar um modelo de resposta, você também p * `@app.delete()` * etc. -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} /// note | Nota @@ -66,7 +66,7 @@ Resumidamente: /// tip | Dica -Para saber mais sobre cada código de status e qual código serve para quê, verifique a documentação do MDN sobre códigos de status HTTP. +Para saber mais sobre cada código de status e qual código serve para quê, verifique a documentação do MDN sobre códigos de status HTTP. /// @@ -74,7 +74,7 @@ Para saber mais sobre cada código de status e qual código serve para quê, ver Vamos ver o exemplo anterior novamente: -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} `201` é o código de status para "Criado". @@ -82,7 +82,7 @@ Mas você não precisa memorizar o que cada um desses códigos significa. Você pode usar as variáveis de conveniência de `fastapi.status`. -{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} Eles são apenas uma conveniência, eles possuem o mesmo número, mas dessa forma você pode usar o preenchimento automático do editor para encontrá-los: diff --git a/docs/pt/docs/tutorial/schema-extra-example.md b/docs/pt/docs/tutorial/schema-extra-example.md index 2d62ffd85..560fda908 100644 --- a/docs/pt/docs/tutorial/schema-extra-example.md +++ b/docs/pt/docs/tutorial/schema-extra-example.md @@ -74,7 +74,7 @@ Você também pode, é claro, passar vários `examples`: Quando fizer isso, os exemplos farão parte do **JSON Schema** interno para esses dados do body. -No entanto, no momento em que isto foi escrito, o Swagger UI, a ferramenta responsável por exibir a UI da documentação, não suporta mostrar vários exemplos para os dados no **JSON Schema**. Mas leia abaixo para uma solução alternativa. +No entanto, no momento em que isto foi escrito, o Swagger UI, a ferramenta responsável por exibir a UI da documentação, não suporta mostrar vários exemplos para os dados no **JSON Schema**. Mas leia abaixo para uma solução alternativa. ### `examples` específicos do OpenAPI { #openapi-specific-examples } diff --git a/docs/pt/docs/tutorial/security/first-steps.md b/docs/pt/docs/tutorial/security/first-steps.md index 715a21b6e..f0edd5753 100644 --- a/docs/pt/docs/tutorial/security/first-steps.md +++ b/docs/pt/docs/tutorial/security/first-steps.md @@ -20,7 +20,7 @@ Vamos primeiro usar o código e ver como funciona, e depois voltaremos para ente Copie o exemplo em um arquivo `main.py`: -{* ../../docs_src/security/tutorial001_an_py39.py *} +{* ../../docs_src/security/tutorial001_an_py310.py *} ## Execute-o { #run-it } @@ -132,7 +132,7 @@ Nesse caso, o **FastAPI** também fornece as ferramentas para construí-la. Quando criamos uma instância da classe `OAuth2PasswordBearer`, passamos o parâmetro `tokenUrl`. Esse parâmetro contém a URL que o client (o frontend rodando no navegador do usuário) usará para enviar o `username` e o `password` para obter um token. -{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} /// tip | Dica @@ -156,7 +156,7 @@ Isso ocorre porque ele usa o mesmo nome da especificação do OpenAPI. Assim, se /// -A variável `oauth2_scheme` é uma instância de `OAuth2PasswordBearer`, mas também é "chamável" (callable). +A variável `oauth2_scheme` é uma instância de `OAuth2PasswordBearer`, mas também é um "callable". Ela pode ser chamada como: @@ -170,7 +170,7 @@ Então, pode ser usada com `Depends`. Agora você pode passar esse `oauth2_scheme` em uma dependência com `Depends`. -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} Essa dependência fornecerá uma `str` que é atribuída ao parâmetro `token` da função de operação de rota. diff --git a/docs/pt/docs/tutorial/security/get-current-user.md b/docs/pt/docs/tutorial/security/get-current-user.md index 2135ae236..4c6397c31 100644 --- a/docs/pt/docs/tutorial/security/get-current-user.md +++ b/docs/pt/docs/tutorial/security/get-current-user.md @@ -2,7 +2,7 @@ No capítulo anterior, o sistema de segurança (que é baseado no sistema de injeção de dependências) estava fornecendo à *função de operação de rota* um `token` como uma `str`: -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} Mas isso ainda não é tão útil. diff --git a/docs/pt/docs/tutorial/security/index.md b/docs/pt/docs/tutorial/security/index.md index d3de3e050..f4c92ea56 100644 --- a/docs/pt/docs/tutorial/security/index.md +++ b/docs/pt/docs/tutorial/security/index.md @@ -88,7 +88,6 @@ OpenAPI define os seguintes esquemas de segurança: * `openIdConnect`: tem uma forma para definir como descobrir automaticamente o dado da autenticação OAuth2. * Essa descoberta automática é o que é definido na especificação OpenID Connect. - /// tip | Dica Integração com outros provedores de autenticação/autorização como Google, Facebook, X (Twitter), GitHub, etc. é bem possível e relativamente fácil. @@ -99,7 +98,7 @@ O problema mais complexo é criar um provedor de autenticação/autorização co ## **FastAPI** utilitários { #fastapi-utilities } -**FastAPI** fornece várias ferramentas para cada um desses esquemas de segurança no módulo `fastapi.security` que simplesmente usa esses mecanismos de segurança. +**FastAPI** fornece várias ferramentas para cada um desses esquemas de segurança no módulo `fastapi.security` que simplificam o uso desses mecanismos de segurança. Nos próximos capítulos você irá ver como adicionar segurança à sua API usando essas ferramentas disponibilizadas pelo **FastAPI**. diff --git a/docs/pt/docs/tutorial/security/oauth2-jwt.md b/docs/pt/docs/tutorial/security/oauth2-jwt.md index f68b8c39e..4ba38b9f0 100644 --- a/docs/pt/docs/tutorial/security/oauth2-jwt.md +++ b/docs/pt/docs/tutorial/security/oauth2-jwt.md @@ -116,7 +116,11 @@ E outra função utilitária para verificar se uma senha recebida corresponde ao E outra para autenticar e retornar um usuário. -{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +Quando `authenticate_user` é chamado com um nome de usuário que não existe no banco de dados, ainda executamos `verify_password` contra um hash fictício. + +Isso garante que o endpoint leve aproximadamente o mesmo tempo para responder, seja o nome de usuário válido ou não, prevenindo **timing attacks** que poderiam ser usados para enumerar nomes de usuário existentes. /// note | Nota @@ -152,7 +156,7 @@ Defina um modelo Pydantic que será usado no endpoint de token para a resposta. Crie uma função utilitária para gerar um novo token de acesso. -{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} ## Atualize as dependências { #update-the-dependencies } @@ -162,7 +166,7 @@ Decodifique o token recebido, verifique-o e retorne o usuário atual. Se o token for inválido, retorne um erro HTTP imediatamente. -{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} ## Atualize a *operação de rota* `/token` { #update-the-token-path-operation } @@ -170,7 +174,7 @@ Crie um `timedelta` com o tempo de expiração do token. Crie um token de acesso JWT real e o retorne. -{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} ### Detalhes técnicos sobre o "sujeito" `sub` do JWT { #technical-details-about-the-jwt-subject-sub } diff --git a/docs/pt/docs/tutorial/static-files.md b/docs/pt/docs/tutorial/static-files.md index 04a02c7f9..0b2d0718f 100644 --- a/docs/pt/docs/tutorial/static-files.md +++ b/docs/pt/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ Você pode servir arquivos estáticos automaticamente a partir de um diretório * Importe `StaticFiles`. * "Monte" uma instância de `StaticFiles()` em um path específico. -{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} /// note | Detalhes Técnicos diff --git a/docs/pt/docs/tutorial/testing.md b/docs/pt/docs/tutorial/testing.md index e56edcb8c..44dc2d225 100644 --- a/docs/pt/docs/tutorial/testing.md +++ b/docs/pt/docs/tutorial/testing.md @@ -30,7 +30,7 @@ Use o objeto `TestClient` da mesma forma que você faz com `httpx`. Escreva instruções `assert` simples com as expressões Python padrão que você precisa verificar (novamente, `pytest` padrão). -{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} /// tip | Dica @@ -76,7 +76,7 @@ Digamos que você tenha uma estrutura de arquivo conforme descrito em [Aplicaç No arquivo `main.py` você tem sua aplicação **FastAPI**: -{* ../../docs_src/app_testing/app_a_py39/main.py *} +{* ../../docs_src/app_testing/app_a_py310/main.py *} ### Arquivo de teste { #testing-file } @@ -92,7 +92,7 @@ Então você poderia ter um arquivo `test_main.py` com seus testes. Ele poderia Como esse arquivo está no mesmo pacote, você pode usar importações relativas para importar o objeto `app` do módulo `main` (`main.py`): -{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} ...e ter o código para os testes como antes. diff --git a/docs/pt/docs/virtual-environments.md b/docs/pt/docs/virtual-environments.md index 5736f7109..e222c61ad 100644 --- a/docs/pt/docs/virtual-environments.md +++ b/docs/pt/docs/virtual-environments.md @@ -1,6 +1,6 @@ # Ambientes Virtuais { #virtual-environments } -Ao trabalhar em projetos Python, você provavelmente deve usar um **ambiente virtual** (ou um mecanismo similar) para isolar os pacotes que você instala para cada projeto. +Ao trabalhar em projetos Python, você provavelmente deveria usar um **ambiente virtual** (ou um mecanismo similar) para isolar os pacotes que você instala para cada projeto. /// info | Informação @@ -53,7 +53,7 @@ $ cd awesome-project ## Crie um ambiente virtual { #create-a-virtual-environment } -Ao começar a trabalhar em um projeto Python **pela primeira vez**, crie um ambiente virtual **dentro do seu projeto**. +Ao começar a trabalhar em um projeto Python **pela primeira vez**, crie um ambiente virtual **dentro do seu projeto**. /// tip | Dica @@ -166,7 +166,7 @@ $ source .venv/Scripts/activate Toda vez que você instalar um **novo pacote** naquele ambiente, **ative** o ambiente novamente. -Isso garante que, se você usar um **programa de terminal (CLI)** instalado por esse pacote, você usará aquele do seu ambiente virtual e não qualquer outro que possa ser instalado globalmente, provavelmente com uma versão diferente do que você precisa. +Isso garante que, se você usar um **programa de terminal (CLI)** instalado por esse pacote, você usará aquele do seu ambiente virtual e não qualquer outro que possa ser instalado globalmente, provavelmente com uma versão diferente do que você precisa. /// @@ -176,7 +176,7 @@ Verifique se o ambiente virtual está ativo (o comando anterior funcionou). /// tip | Dica -Isso é **opcional**, mas é uma boa maneira de **verificar** se tudo está funcionando conforme o esperado e se você está usando o ambiente virtual intendido. +Isso é **opcional**, mas é uma boa maneira de **verificar** se tudo está funcionando conforme o esperado e se você está usando o ambiente virtual pretendido. /// @@ -220,7 +220,7 @@ Se você usar From d213dd363d28fcc63ec61b5e206b5a7a71b3337f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Feb 2026 13:49:19 +0000 Subject: [PATCH 127/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index deee633c2..152f684e7 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for pt (update-all). PR [#14912](https://github.com/fastapi/fastapi/pull/14912) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for es (update-all and add-missing). PR [#14911](https://github.com/fastapi/fastapi/pull/14911) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for zh (update-all). PR [#14917](https://github.com/fastapi/fastapi/pull/14917) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for uk (update-all). PR [#14914](https://github.com/fastapi/fastapi/pull/14914) by [@YuriiMotov](https://github.com/YuriiMotov). From 5ad9d258e4f50e8830d0b08601a88e9eb6ae1aaf Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 13 Feb 2026 16:24:30 +0100 Subject: [PATCH 128/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20ja=20(update-all)=20(#14916)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all * 🎨 Auto format * Add missing * 🎨 Auto format --------- Co-authored-by: github-actions[bot] --- docs/ja/docs/_llm-test.md | 503 +++++++++++++++++ docs/ja/docs/about/index.md | 3 + docs/ja/docs/advanced/additional-responses.md | 247 +++++++++ .../docs/advanced/additional-status-codes.md | 4 +- .../ja/docs/advanced/advanced-dependencies.md | 163 ++++++ .../ja/docs/advanced/advanced-python-types.md | 61 +++ docs/ja/docs/advanced/async-tests.md | 99 ++++ docs/ja/docs/advanced/behind-a-proxy.md | 466 ++++++++++++++++ docs/ja/docs/advanced/custom-response.md | 32 +- docs/ja/docs/advanced/dataclasses.md | 95 ++++ docs/ja/docs/advanced/events.md | 165 ++++++ docs/ja/docs/advanced/generate-clients.md | 208 ++++++++ docs/ja/docs/advanced/middleware.md | 97 ++++ docs/ja/docs/advanced/openapi-callbacks.md | 186 +++++++ docs/ja/docs/advanced/openapi-webhooks.md | 55 ++ .../path-operation-advanced-configuration.md | 16 +- .../advanced/response-change-status-code.md | 31 ++ docs/ja/docs/advanced/response-cookies.md | 51 ++ docs/ja/docs/advanced/response-directly.md | 2 +- docs/ja/docs/advanced/response-headers.md | 41 ++ .../docs/advanced/security/http-basic-auth.md | 107 ++++ docs/ja/docs/advanced/security/index.md | 19 + .../docs/advanced/security/oauth2-scopes.md | 274 ++++++++++ docs/ja/docs/advanced/settings.md | 302 +++++++++++ docs/ja/docs/advanced/sub-applications.md | 67 +++ docs/ja/docs/advanced/templates.md | 126 +++++ docs/ja/docs/advanced/testing-dependencies.md | 53 ++ docs/ja/docs/advanced/testing-events.md | 11 + docs/ja/docs/advanced/testing-websockets.md | 13 + .../docs/advanced/using-request-directly.md | 56 ++ docs/ja/docs/advanced/websockets.md | 8 +- docs/ja/docs/advanced/wsgi.md | 51 ++ docs/ja/docs/alternatives.md | 73 ++- docs/ja/docs/async.md | 379 +++++++------ docs/ja/docs/deployment/cloud.md | 24 + docs/ja/docs/deployment/concepts.md | 2 - docs/ja/docs/deployment/docker.md | 10 +- docs/ja/docs/deployment/fastapicloud.md | 65 +++ docs/ja/docs/deployment/https.md | 4 +- docs/ja/docs/deployment/manually.md | 152 ++++-- docs/ja/docs/environment-variables.md | 4 +- docs/ja/docs/fastapi-cli.md | 75 +++ docs/ja/docs/features.md | 223 ++++---- docs/ja/docs/help-fastapi.md | 264 +++++++-- docs/ja/docs/history-design-future.md | 21 +- .../authentication-error-status-code.md | 17 + docs/ja/docs/how-to/conditional-openapi.md | 4 +- docs/ja/docs/how-to/configure-swagger-ui.md | 70 +++ docs/ja/docs/how-to/custom-docs-ui-assets.md | 185 +++++++ .../docs/how-to/custom-request-and-route.md | 109 ++++ docs/ja/docs/how-to/extending-openapi.md | 80 +++ docs/ja/docs/how-to/general.md | 39 ++ docs/ja/docs/how-to/graphql.md | 60 +++ docs/ja/docs/how-to/index.md | 13 + ...migrate-from-pydantic-v1-to-pydantic-v2.md | 135 +++++ .../docs/how-to/separate-openapi-schemas.md | 102 ++++ docs/ja/docs/how-to/testing-database.md | 7 + docs/ja/docs/index.md | 12 +- docs/ja/docs/python-types.md | 286 +++------- docs/ja/docs/resources/index.md | 3 + docs/ja/docs/translation-banner.md | 11 + docs/ja/docs/tutorial/background-tasks.md | 6 +- docs/ja/docs/tutorial/bigger-applications.md | 504 ++++++++++++++++++ docs/ja/docs/tutorial/body-multiple-params.md | 6 - docs/ja/docs/tutorial/body-nested-models.md | 4 +- docs/ja/docs/tutorial/body-updates.md | 2 +- docs/ja/docs/tutorial/body.md | 4 +- docs/ja/docs/tutorial/cookie-param-models.md | 8 +- docs/ja/docs/tutorial/cors.md | 2 +- docs/ja/docs/tutorial/debugging.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +- ...pendencies-in-path-operation-decorators.md | 8 +- .../dependencies/dependencies-with-yield.md | 24 +- .../dependencies/global-dependencies.md | 15 + docs/ja/docs/tutorial/dependencies/index.md | 2 +- .../tutorial/dependencies/sub-dependencies.md | 6 +- docs/ja/docs/tutorial/extra-models.md | 28 +- docs/ja/docs/tutorial/first-steps.md | 18 +- docs/ja/docs/tutorial/handling-errors.md | 16 +- docs/ja/docs/tutorial/header-param-models.md | 72 +++ docs/ja/docs/tutorial/metadata.md | 12 +- docs/ja/docs/tutorial/middleware.md | 6 +- .../tutorial/path-operation-configuration.md | 8 +- .../path-params-numeric-validations.md | 16 +- docs/ja/docs/tutorial/path-params.md | 28 +- .../tutorial/query-params-str-validations.md | 42 +- docs/ja/docs/tutorial/query-params.md | 6 +- docs/ja/docs/tutorial/request-files.md | 176 ++++++ docs/ja/docs/tutorial/request-form-models.md | 78 +++ .../docs/tutorial/request-forms-and-files.md | 4 +- docs/ja/docs/tutorial/request-forms.md | 10 +- docs/ja/docs/tutorial/response-model.md | 8 +- docs/ja/docs/tutorial/response-status-code.md | 6 +- docs/ja/docs/tutorial/schema-extra-example.md | 2 +- docs/ja/docs/tutorial/security/first-steps.md | 8 +- .../tutorial/security/get-current-user.md | 2 +- docs/ja/docs/tutorial/security/index.md | 16 +- docs/ja/docs/tutorial/security/oauth2-jwt.md | 14 +- .../docs/tutorial/security/simple-oauth2.md | 289 ++++++++++ docs/ja/docs/tutorial/sql-databases.md | 357 +++++++++++++ docs/ja/docs/tutorial/static-files.md | 2 +- docs/ja/docs/tutorial/testing.md | 6 +- docs/ja/docs/virtual-environments.md | 4 +- 103 files changed, 7024 insertions(+), 868 deletions(-) create mode 100644 docs/ja/docs/_llm-test.md create mode 100644 docs/ja/docs/about/index.md create mode 100644 docs/ja/docs/advanced/additional-responses.md create mode 100644 docs/ja/docs/advanced/advanced-dependencies.md create mode 100644 docs/ja/docs/advanced/advanced-python-types.md create mode 100644 docs/ja/docs/advanced/async-tests.md create mode 100644 docs/ja/docs/advanced/behind-a-proxy.md create mode 100644 docs/ja/docs/advanced/dataclasses.md create mode 100644 docs/ja/docs/advanced/events.md create mode 100644 docs/ja/docs/advanced/generate-clients.md create mode 100644 docs/ja/docs/advanced/middleware.md create mode 100644 docs/ja/docs/advanced/openapi-callbacks.md create mode 100644 docs/ja/docs/advanced/openapi-webhooks.md create mode 100644 docs/ja/docs/advanced/response-change-status-code.md create mode 100644 docs/ja/docs/advanced/response-cookies.md create mode 100644 docs/ja/docs/advanced/response-headers.md create mode 100644 docs/ja/docs/advanced/security/http-basic-auth.md create mode 100644 docs/ja/docs/advanced/security/index.md create mode 100644 docs/ja/docs/advanced/security/oauth2-scopes.md create mode 100644 docs/ja/docs/advanced/settings.md create mode 100644 docs/ja/docs/advanced/sub-applications.md create mode 100644 docs/ja/docs/advanced/templates.md create mode 100644 docs/ja/docs/advanced/testing-dependencies.md create mode 100644 docs/ja/docs/advanced/testing-events.md create mode 100644 docs/ja/docs/advanced/testing-websockets.md create mode 100644 docs/ja/docs/advanced/using-request-directly.md create mode 100644 docs/ja/docs/advanced/wsgi.md create mode 100644 docs/ja/docs/deployment/cloud.md create mode 100644 docs/ja/docs/deployment/fastapicloud.md create mode 100644 docs/ja/docs/fastapi-cli.md create mode 100644 docs/ja/docs/how-to/authentication-error-status-code.md create mode 100644 docs/ja/docs/how-to/configure-swagger-ui.md create mode 100644 docs/ja/docs/how-to/custom-docs-ui-assets.md create mode 100644 docs/ja/docs/how-to/custom-request-and-route.md create mode 100644 docs/ja/docs/how-to/extending-openapi.md create mode 100644 docs/ja/docs/how-to/general.md create mode 100644 docs/ja/docs/how-to/graphql.md create mode 100644 docs/ja/docs/how-to/index.md create mode 100644 docs/ja/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md create mode 100644 docs/ja/docs/how-to/separate-openapi-schemas.md create mode 100644 docs/ja/docs/how-to/testing-database.md create mode 100644 docs/ja/docs/resources/index.md create mode 100644 docs/ja/docs/translation-banner.md create mode 100644 docs/ja/docs/tutorial/bigger-applications.md create mode 100644 docs/ja/docs/tutorial/dependencies/global-dependencies.md create mode 100644 docs/ja/docs/tutorial/header-param-models.md create mode 100644 docs/ja/docs/tutorial/request-files.md create mode 100644 docs/ja/docs/tutorial/request-form-models.md create mode 100644 docs/ja/docs/tutorial/security/simple-oauth2.md create mode 100644 docs/ja/docs/tutorial/sql-databases.md diff --git a/docs/ja/docs/_llm-test.md b/docs/ja/docs/_llm-test.md new file mode 100644 index 000000000..b3d9fc28c --- /dev/null +++ b/docs/ja/docs/_llm-test.md @@ -0,0 +1,503 @@ +# LLM テストファイル { #llm-test-file } + +このドキュメントは、ドキュメントを翻訳する LLM が、`scripts/translate.py` の `general_prompt` と、`docs/{language code}/llm-prompt.md` の言語固有プロンプトを理解しているかをテストします。言語固有プロンプトは `general_prompt` の末尾に追加されます。 + +ここに追加したテストは、すべての言語固有プロンプトの設計者が参照します。 + +使い方: + +* 言語固有プロンプトを用意します - `docs/{language code}/llm-prompt.md`。 +* この文書を希望するターゲット言語に新規で翻訳します(例: `translate.py` の `translate-page` コマンド)。これにより `docs/{language code}/docs/_llm-test.md` に翻訳が作成されます。 +* 翻訳が問題ないか確認します。 +* 必要であれば、言語固有プロンプト、general プロンプト、または英語ドキュメントを改善します。 +* その後、翻訳に残っている問題を手動で修正し、良い翻訳にします。 +* 良い翻訳を用意した状態でもう一度翻訳します。理想的な結果は、LLM が翻訳に一切変更を加えないことです。つまり general プロンプトと言語固有プロンプトが最良であることを意味します(時々いくつかランダムに見える変更を行うことがあります。理由は LLM は決定論的アルゴリズムではない ためです)。 + +テスト内容: + +## コードスニペット { #code-snippets } + +//// tab | テスト + +これはコードスニペットです: `foo`。そしてこれもコードスニペットです: `bar`。さらにもう一つ: `baz quux`。 + +//// + +//// tab | 情報 + +コードスニペットの内容はそのままにしておく必要があります。 + +`scripts/translate.py` の general プロンプト内「### Content of code snippets」の節を参照してください。 + +//// + +## 引用 { #quotes } + +//// tab | テスト + +昨日、友人はこう書きました。「incorrectly を正しく綴れば、あなたはそれを間違って綴ったことになる」。それに対して私はこう答えました。「そのとおり。ただし『incorrectly』は誤りで、『"incorrectly"』ではありません」。 + +/// note | 備考 + +LLM はおそらくここを誤って翻訳します。重要なのは、再翻訳時に修正済みの翻訳を維持できるかどうかだけです。 + +/// + +//// + +//// tab | 情報 + +プロンプト設計者は、ストレートクォートをタイポグラフィックな引用符に変換するかどうかを選べます。そのままでも問題ありません。 + +例として `docs/de/llm-prompt.md` の「### Quotes」の節を参照してください。 + +//// + +## コードスニペット内の引用 { #quotes-in-code-snippets } + +//// tab | テスト + +`pip install "foo[bar]"` + +コードスニペット中の文字列リテラルの例: `"this"`, `'that'`. + +難しい文字列リテラルの例: `f"I like {'oranges' if orange else "apples"}"` + +ハードコア: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` + +//// + +//// tab | 情報 + +... ただし、コードスニペット内の引用符はそのままにしておく必要があります。 + +//// + +## コードブロック { #code-blocks } + +//// tab | テスト + +Bash のコード例です... + +```bash +# 宇宙にあいさつを表示 +echo "Hello universe" +``` + +...そしてコンソールのコード例です... + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +...さらに別のコンソールのコード例です... + +```console +// ディレクトリ "code" を作成 +$ mkdir code +// そのディレクトリに移動 +$ cd code +``` + +...そして Python のコード例です... + +```Python +wont_work() # これは動作しません 😱 +works(foo="bar") # これは動作します 🎉 +``` + +...以上です。 + +//// + +//// tab | 情報 + +コードブロック内のコードは、コメントを除き、変更してはいけません。 + +`scripts/translate.py` の general プロンプト内「### Content of code blocks」の節を参照してください。 + +//// + +## タブと色付きボックス { #tabs-and-colored-boxes } + +//// tab | テスト + +/// info | 情報 +いくつかのテキスト +/// + +/// note | 備考 +いくつかのテキスト +/// + +/// note | 技術詳細 +いくつかのテキスト +/// + +/// check | 確認 +いくつかのテキスト +/// + +/// tip | 豆知識 +いくつかのテキスト +/// + +/// warning | 注意 +いくつかのテキスト +/// + +/// danger | 警告 +いくつかのテキスト +/// + +//// + +//// tab | 情報 + +タブおよび `Info`/`Note`/`Warning` などのブロックには、タイトルの翻訳を縦棒(`|`)の後ろに追加します。 + +`scripts/translate.py` の general プロンプト内「### Special blocks」と「### Tab blocks」の節を参照してください。 + +//// + +## Web リンクと内部リンク { #web-and-internal-links } + +//// tab | テスト + +リンクのテキストは翻訳し、リンク先のアドレスは変更しないでください: + +* [上の見出しへのリンク](#code-snippets) +* [内部リンク](index.md#installation){.internal-link target=_blank} +* 外部リンク +* スタイルへのリンク +* スクリプトへのリンク +* 画像へのリンク + +リンクのテキストは翻訳し、リンク先のアドレスは翻訳版を指すようにしてください: + +* FastAPI リンク + +//// + +//// tab | 情報 + +リンクのテキストは翻訳し、アドレスは変更しないでください。例外は、FastAPI ドキュメントのページへの絶対 URL です。その場合は翻訳版へのリンクにします。 + +`scripts/translate.py` の general プロンプト内「### Links」の節を参照してください。 + +//// + +## HTML "abbr" 要素 { #html-abbr-elements } + +//// tab | テスト + +ここでは HTML の "abbr" 要素で包まれたものをいくつか示します(いくつかは架空です): + +### abbr が完全な語句を示す { #the-abbr-gives-a-full-phrase } + +* GTD +* lt +* XWT +* PSGI + +### abbr が完全な語句と説明を示す { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* MDN +* I/O. + +//// + +//// tab | 情報 + +"abbr" 要素の "title" 属性は特定の指示に従って翻訳します。 + +翻訳は、英語の語を説明するために独自の "abbr" 要素を追加してもよく、LLM はそれらを削除してはいけません。 + +`scripts/translate.py` の general プロンプト内「### HTML abbr elements」の節を参照してください。 + +//// + +## HTML "dfn" 要素 { #html-dfn-elements } + +* クラスター +* ディープラーニング + +## 見出し { #headings } + +//// tab | テスト + +### Web アプリを開発する - チュートリアル { #develop-a-webapp-a-tutorial } + +こんにちは。 + +### 型ヒントとアノテーション { #type-hints-and-annotations } + +またこんにちは。 + +### スーパークラスとサブクラス { #super-and-subclasses } + +またこんにちは。 + +//// + +//// tab | 情報 + +見出しに関する唯一の厳格なルールは、リンクが壊れないように、LLM が中括弧内のハッシュ部分を変更しないことです。 + +`scripts/translate.py` の general プロンプト内「### Headings」の節を参照してください。 + +言語固有の指示については、例として `docs/de/llm-prompt.md` の「### Headings」の節を参照してください。 + +//// + +## ドキュメントで使う用語 { #terms-used-in-the-docs } + +//// tab | テスト + +* you +* your + +* e.g. +* etc. + +* `foo` を `int` として +* `bar` を `str` として +* `baz` を `list` として + +* チュートリアル - ユーザーガイド +* 上級ユーザーガイド +* SQLModel ドキュメント +* API ドキュメント +* 自動生成ドキュメント + +* データサイエンス +* ディープラーニング +* 機械学習 +* 依存性注入 +* HTTP Basic 認証 +* HTTP Digest +* ISO 形式 +* JSON Schema 規格 +* JSON スキーマ +* スキーマ定義 +* Password Flow +* モバイル + +* 非推奨 +* 設計された +* 無効 +* オンザフライ +* 標準 +* デフォルト +* 大文字小文字を区別 +* 大文字小文字を区別しない + +* アプリケーションを提供する +* ページを配信する + +* アプリ +* アプリケーション + +* リクエスト +* レスポンス +* エラーレスポンス + +* path operation +* path operation デコレータ +* path operation 関数 + +* ボディ +* リクエストボディ +* レスポンスボディ +* JSON ボディ +* フォームボディ +* ファイルボディ +* 関数本体 + +* パラメータ +* ボディパラメータ +* パスパラメータ +* クエリパラメータ +* Cookie パラメータ +* ヘッダーパラメータ +* フォームパラメータ +* 関数パラメータ + +* イベント +* 起動イベント +* サーバーの起動 +* シャットダウンイベント +* lifespan イベント + +* ハンドラ +* イベントハンドラ +* 例外ハンドラ +* 処理する + +* モデル +* Pydantic モデル +* データモデル +* データベースモデル +* フォームモデル +* モデルオブジェクト + +* クラス +* 基底クラス +* 親クラス +* サブクラス +* 子クラス +* 兄弟クラス +* クラスメソッド + +* ヘッダー +* ヘッダー(複数) +* 認可ヘッダー +* `Authorization` ヘッダー +* Forwarded ヘッダー + +* 依存性注入システム +* 依存関係 +* dependable +* dependant + +* I/O バウンド +* CPU バウンド +* 同時実行性 +* 並列性 +* マルチプロセッシング + +* env var +* 環境変数 +* `PATH` +* `PATH` 環境変数 + +* 認証 +* 認証プロバイダ +* 認可 +* 認可フォーム +* 認可プロバイダ +* ユーザーが認証する +* システムがユーザーを認証する + +* CLI +* コマンドラインインターフェース + +* サーバー +* クライアント + +* クラウドプロバイダ +* クラウドサービス + +* 開発 +* 開発段階 + +* dict +* 辞書 +* 列挙型 +* Enum +* 列挙メンバー + +* エンコーダー +* デコーダー +* エンコードする +* デコードする + +* 例外 +* 送出する + +* 式 +* 文 + +* フロントエンド +* バックエンド + +* GitHub ディスカッション +* GitHub Issue + +* パフォーマンス +* パフォーマンス最適化 + +* 戻り値の型 +* 戻り値 + +* セキュリティ +* セキュリティスキーム + +* タスク +* バックグラウンドタスク +* タスク関数 + +* テンプレート +* テンプレートエンジン + +* 型アノテーション +* 型ヒント + +* サーバーワーカー +* Uvicorn ワーカー +* Gunicorn ワーカー +* ワーカープロセス +* ワーカークラス +* ワークロード + +* デプロイ +* デプロイする + +* SDK +* ソフトウェア開発キット + +* `APIRouter` +* `requirements.txt` +* Bearer Token +* 破壊的変更 +* バグ +* ボタン +* 呼び出し可能 +* コード +* コミット +* コンテキストマネージャ +* コルーチン +* データベースセッション +* ディスク +* ドメイン +* エンジン +* フェイクの X +* HTTP GET メソッド +* アイテム +* ライブラリ +* ライフスパン +* ロック +* ミドルウェア +* モバイルアプリケーション +* モジュール +* マウント +* ネットワーク +* オリジン +* オーバーライド +* ペイロード +* プロセッサ +* プロパティ +* プロキシ +* プルリクエスト +* クエリ +* RAM +* リモートマシン +* ステータスコード +* 文字列 +* タグ +* Web フレームワーク +* ワイルドカード +* 返す +* 検証する + +//// + +//// tab | 情報 + +これはドキュメントで見られる(主に)技術用語の不完全かつ規範的でない一覧です。プロンプト設計者が、LLM がどの用語で手助けを必要としているかを把握するのに役立つかもしれません。例えば、良い翻訳を最適でない翻訳に戻してしまう場合や、あなたの言語での活用・格変化に問題がある場合などです。 + +`docs/de/llm-prompt.md` の「### List of English terms and their preferred German translations」の節を参照してください。 + +//// diff --git a/docs/ja/docs/about/index.md b/docs/ja/docs/about/index.md new file mode 100644 index 000000000..b099df7fa --- /dev/null +++ b/docs/ja/docs/about/index.md @@ -0,0 +1,3 @@ +# 概要 { #about } + +FastAPI の概要、その設計やインスピレーションなどについて解説します。🤓 diff --git a/docs/ja/docs/advanced/additional-responses.md b/docs/ja/docs/advanced/additional-responses.md new file mode 100644 index 000000000..4c4425788 --- /dev/null +++ b/docs/ja/docs/advanced/additional-responses.md @@ -0,0 +1,247 @@ +# OpenAPI の追加レスポンス { #additional-responses-in-openapi } + +/// warning | 注意 + +これは比較的高度なトピックです。 + +FastAPI を使い始めたばかりであれば、これは不要かもしれません。 + +/// + +追加のステータスコード、メディアタイプ、説明などを伴う追加レスポンスを宣言できます。 + +それらの追加レスポンスは OpenAPI スキーマに含まれ、API ドキュメントにも表示されます。 + +ただし、それらの追加レスポンスについては、ステータスコードとコンテンツを指定して `JSONResponse` などの `Response` を直接返す必要があります。 + +## `model` を使った追加レスポンス { #additional-response-with-model } + +*path operation デコレータ*に `responses` パラメータを渡せます。 + +これは `dict` を受け取り、キーは各レスポンスのステータスコード(例: `200`)、値は各レスポンスの情報を含む別の `dict` です。 + +それぞれのレスポンス `dict` には、`response_model` と同様に Pydantic モデルを格納する `model` キーを含められます。 + +FastAPI はそのモデルから JSON Schema を生成し、OpenAPI の適切な場所に含めます。 + +例えば、ステータスコード `404` と Pydantic モデル `Message` を持つ別のレスポンスを宣言するには、次のように書けます: + +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} + +/// note | 備考 + +`JSONResponse` を直接返す必要がある点に注意してください。 + +/// + +/// info | 情報 + +`model` キーは OpenAPI の一部ではありません。 + +FastAPI はそこから Pydantic モデルを取得して JSON Schema を生成し、適切な場所に配置します。 + +適切な場所は次のとおりです: + +- `content` キーの中。これは値として別の JSON オブジェクト(`dict`)を持ち、その中に次が含まれます: + - メディアタイプ(例: `application/json`)をキーとし、値としてさらに別の JSON オブジェクトを持ち、その中に次が含まれます: + - `schema` キー。値としてモデル由来の JSON Schema を持ち、ここが正しい配置場所です。 + - FastAPI はここに、スキーマを直接埋め込む代わりに OpenAPI 内のグローバルな JSON Schema への参照を追加します。これにより、他のアプリケーションやクライアントがそれらの JSON Schema を直接利用し、より良いコード生成ツール等を提供できます。 + +/// + +この *path operation* のために OpenAPI に生成されるレスポンスは次のとおりです: + +```JSON hl_lines="3-12" +{ + "responses": { + "404": { + "description": "Additional Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } +} +``` + +スキーマは OpenAPI スキーマ内の別の場所への参照になります: + +```JSON hl_lines="4-16" +{ + "components": { + "schemas": { + "Message": { + "title": "Message", + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "title": "Message", + "type": "string" + } + } + }, + "Item": { + "title": "Item", + "required": [ + "id", + "value" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + } + }, + "ValidationError": { + "title": "ValidationError", + "required": [ + "loc", + "msg", + "type" + ], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } +} +``` + +## メインのレスポンスに追加のメディアタイプ { #additional-media-types-for-the-main-response } + +同じ `responses` パラメータを使って、同一のメインレスポンスに別のメディアタイプを追加できます。 + +例えば、`image/png` の追加メディアタイプを加え、あなたの *path operation* が JSON オブジェクト(メディアタイプ `application/json`)または PNG 画像を返せることを宣言できます: + +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} + +/// note | 備考 + +画像は `FileResponse` を使って直接返す必要がある点に注意してください。 + +/// + +/// info | 情報 + +`responses` パラメータで明示的に別のメディアタイプを指定しない限り、FastAPI はレスポンスがメインのレスポンスクラスと同じメディアタイプ(デフォルトは `application/json`)であるとみなします。 + +ただし、メディアタイプが `None` のカスタムレスポンスクラスを指定している場合、モデルが関連付けられた追加レスポンスには FastAPI は `application/json` を使用します。 + +/// + +## 情報の結合 { #combining-information } + +`response_model`、`status_code`、`responses` パラメータなど、複数の場所からのレスポンス情報を組み合わせることもできます。 + +`response_model` を宣言し、デフォルトのステータスコード `200`(必要なら任意のコード)を使い、その同じレスポンスに対する追加情報を `responses` で OpenAPI スキーマに直接記述できます。 + +FastAPI は `responses` にある追加情報を保持し、モデルの JSON Schema と結合します。 + +例えば、Pydantic モデルを用い、独自の `description` を持つステータスコード `404` のレスポンスを宣言できます。 + +さらに、`response_model` を使うステータスコード `200` のレスポンスに独自の `example` を含めることもできます: + +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} + +これらはすべて結合されて OpenAPI に含まれ、API ドキュメントに表示されます: + + + +## 事前定義レスポンスとカスタムの組み合わせ { #combine-predefined-responses-and-custom-ones } + +多くの *path operations* に適用できる事前定義のレスポンスを用意しつつ、各 *path operation* ごとに必要なカスタムレスポンスと組み合わせたい場合があります。 + +そのような場合、Python の `**dict_to_unpack` による `dict` の「アンパック」テクニックを使えます: + +```Python +old_dict = { + "old key": "old value", + "second old key": "second old value", +} +new_dict = {**old_dict, "new key": "new value"} +``` + +ここでは、`new_dict` には `old_dict` のすべてのキーと値に加え、新しいキーと値が含まれます: + +```Python +{ + "old key": "old value", + "second old key": "second old value", + "new key": "new value", +} +``` + +このテクニックを使うと、*path operations* で事前定義レスポンスを再利用し、さらにカスタムのレスポンスを組み合わせられます。 + +例えば: + +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} + +## OpenAPI レスポンスの詳細 { #more-information-about-openapi-responses } + +レスポンスに正確に何を含められるかは、OpenAPI 仕様の次のセクションを参照してください: + +- OpenAPI の Responses Object。ここには `Response Object` が含まれます。 +- OpenAPI の Response Object。`responses` パラメータ内の各レスポンスに、ここで定義されている要素を直接含められます。`description`、`headers`、`content`(ここで異なるメディアタイプや JSON Schema を宣言します)、`links` など。 diff --git a/docs/ja/docs/advanced/additional-status-codes.md b/docs/ja/docs/advanced/additional-status-codes.md index 14b7e8ba8..fdac52e83 100644 --- a/docs/ja/docs/advanced/additional-status-codes.md +++ b/docs/ja/docs/advanced/additional-status-codes.md @@ -16,7 +16,7 @@ {* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} -/// warning | 注意 +/// warning 上の例のように `Response` を直接返すと、それはそのまま返されます。 @@ -38,4 +38,4 @@ 追加のステータスコードとレスポンスを直接返す場合、それらは OpenAPI スキーマ(API ドキュメント)には含まれません。FastAPI には、事前に何が返されるかを知る方法がないからです。 -しかし、[Additional Responses](additional-responses.md){.internal-link target=_blank} を使ってコード内にドキュメント化できます。 +しかし、[追加のレスポンス](additional-responses.md){.internal-link target=_blank} を使ってコード内にドキュメント化できます。 diff --git a/docs/ja/docs/advanced/advanced-dependencies.md b/docs/ja/docs/advanced/advanced-dependencies.md new file mode 100644 index 000000000..d38ce548d --- /dev/null +++ b/docs/ja/docs/advanced/advanced-dependencies.md @@ -0,0 +1,163 @@ +# 高度な依存関係 { #advanced-dependencies } + +## パラメータ化された依存関係 { #parameterized-dependencies } + +これまで見てきた依存関係は、固定の関数またはクラスでした。 + +しかし、多くの異なる関数やクラスを宣言せずに、その依存関係にパラメータを設定したい場合があります。 + +たとえば、クエリパラメータ `q` に、ある固定の内容が含まれているかを検査する依存関係が欲しいとします。 + +ただし、その固定の内容はパラメータ化できるようにしたいです。 + +## "callable" なインスタンス { #a-callable-instance } + +Python には、クラスのインスタンスを "callable" にする方法があります。 + +クラス自体(これはすでに callable です)ではなく、そのクラスのインスタンスです。 + +そのためには、`__call__` メソッドを宣言します: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} + +この場合、この `__call__` が、**FastAPI** が追加のパラメータやサブ依存関係を確認するために使うものになり、後であなたの *path operation 関数* のパラメータに値を渡すために呼び出されるものになります。 + +## インスタンスのパラメータ化 { #parameterize-the-instance } + +そして、`__init__` を使って、依存関係を「パラメータ化」するために利用できるインスタンスのパラメータを宣言できます: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} + +この場合、**FastAPI** は `__init__` に触れたり気にかけたりすることはありません。私たちがコード内で直接使います。 + +## インスタンスの作成 { #create-an-instance } + +このクラスのインスタンスは次のように作成できます: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} + +このようにして依存関係を「パラメータ化」できます。いまや `"bar"` が属性 `checker.fixed_content` として中に保持されています。 + +## インスタンスを依存関係として使う { #use-the-instance-as-a-dependency } + +その後、`Depends(FixedContentQueryChecker)` の代わりに `Depends(checker)` でこの `checker` を使えます。依存関係はクラスそのものではなく、インスタンスである `checker` だからです。 + +依存関係を解決するとき、**FastAPI** はこの `checker` を次のように呼び出します: + +```Python +checker(q="somequery") +``` + +...そして、その戻り値を *path operation 関数* 内の依存関係の値として、パラメータ `fixed_content_included` に渡します: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} + +/// tip | 豆知識 + +ここまでの内容は回りくどく感じられるかもしれません。まだどのように役立つかが明確でないかもしれません。 + +これらの例は意図的に単純ですが、仕組みを示しています。 + +セキュリティの章では、同じやり方で実装されたユーティリティ関数があります。 + +ここまでを理解できていれば、そうしたセキュリティ用ユーティリティが内部でどのように動いているかも理解できています。 + +/// + +## `yield`、`HTTPException`、`except` とバックグラウンドタスクを伴う依存関係 { #dependencies-with-yield-httpexception-except-and-background-tasks } + +/// warning | 注意 + +これらの技術的詳細は、ほとんどの場合は不要です。 + +主に、0.121.0 より前の FastAPI アプリケーションがあり、`yield` を使う依存関係で問題が発生している場合に有用です。 + +/// + +`yield` を使う依存関係は、さまざまなユースケースに対応し、いくつかの問題を修正するために時間とともに進化してきました。ここでは変更点の概要を説明します。 + +### `yield` と `scope` を伴う依存関係 { #dependencies-with-yield-and-scope } + +バージョン 0.121.0 で、`yield` を使う依存関係に対して `Depends(scope="function")` がサポートされました。 + +`Depends(scope="function")` を使うと、`yield` の後の終了コードは、クライアントへレスポンスが返される前、*path operation 関数* が終了した直後に実行されます。 + +そして、`Depends(scope="request")`(デフォルト)を使う場合、`yield` の後の終了コードはレスポンス送信後に実行されます。 + +詳しくはドキュメント「[`yield` を使う依存関係 - 早期終了と `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope)」を参照してください。 + +### `yield` と `StreamingResponse` を伴う依存関係、技術詳細 { #dependencies-with-yield-and-streamingresponse-technical-details } + +FastAPI 0.118.0 より前では、`yield` を使う依存関係を使用すると、*path operation 関数* が戻ってからレスポンス送信直前に終了コードが実行されていました。 + +これは、レスポンスがネットワーク上を移動するのを待っている間に、不要にリソースを保持しないようにする意図でした。 + +この変更により、`StreamingResponse` を返す場合、`yield` を持つ依存関係の終了コードはすでに実行されていることになりました。 + +たとえば、`yield` を持つ依存関係の中でデータベースセッションを持っていた場合、`StreamingResponse` はデータをストリーミングしている間にそのセッションを使えません。というのも、`yield` の後の終了コードでそのセッションがすでにクローズされているからです。 + +この挙動は 0.118.0 で元に戻され、`yield` の後の終了コードはレスポンス送信後に実行されるようになりました。 + +/// info | 情報 + +以下で見るように、これはバージョン 0.106.0 より前の挙動ととても似ていますが、いくつかのコーナーケースに対する改良とバグ修正が含まれています。 + +/// + +#### 早期終了コードのユースケース { #use-cases-with-early-exit-code } + +特定の条件では、レスポンス送信前に `yield` を持つ依存関係の終了コードを実行する、古い挙動の恩恵を受けられるユースケースがあります。 + +例えば、`yield` を持つ依存関係でデータベースセッションを使ってユーザ検証だけを行い、その後は *path operation 関数* 内ではそのデータベースセッションを一切使わない、かつレスポンス送信に長い時間がかかる(例えばデータをゆっくり送る `StreamingResponse`)が、何らかの理由でデータベースは使わない、というケースです。 + +この場合、レスポンスの送信が終わるまでデータベースセッションが保持されますが、使わないのであれば保持する必要はありません。 + +次のようになります: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py *} + +終了コード、すなわち `Session` の自動クローズは: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +...の部分で定義されており、遅いデータ送信が終わった後に実行されます: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +しかし、`generate_stream()` はデータベースセッションを使わないため、レスポンス送信中にセッションを開いたままにしておく必要は実際にはありません。 + +SQLModel(または SQLAlchemy)でこの特定のユースケースがある場合は、不要になった時点でセッションを明示的にクローズできます: + +{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *} + +このようにすると、セッションはデータベース接続を解放するため、他のリクエストがそれを使えるようになります。 + +`yield` を持つ依存関係で早期終了が必要な別のユースケースがある場合は、あなたの具体的なユースケースと、なぜ `yield` を持つ依存関係の早期クローズが有益かを説明して、GitHub Discussion の質問を作成してください。 + +`yield` を持つ依存関係の早期クローズに納得できるユースケースがある場合は、早期クローズにオプトインする新しい方法を追加することを検討します。 + +### `yield` と `except` を伴う依存関係、技術詳細 { #dependencies-with-yield-and-except-technical-details } + +FastAPI 0.110.0 より前では、`yield` を持つ依存関係を使い、その依存関係内で `except` によって例外を捕捉し、再度その例外を送出しなかった場合でも、その例外は自動的に送出(フォワード)され、任意の例外ハンドラまたは内部サーバエラーハンドラに渡されていました。 + +これは、ハンドラのないフォワードされた例外(内部サーバエラー)による未処理のメモリ消費を修正し、通常の Python コードの挙動と一貫性を持たせるため、バージョン 0.110.0 で変更されました。 + +### バックグラウンドタスクと `yield` を伴う依存関係、技術詳細 { #background-tasks-and-dependencies-with-yield-technical-details } + +FastAPI 0.106.0 より前では、`yield` の後で例外を送出することはできませんでした。`yield` を持つ依存関係の終了コードはレスポンス送信「後」に実行されるため、[例外ハンドラ](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} はすでに実行済みでした。 + +これは主に、依存関係が "yield" した同じオブジェクトをバックグラウンドタスク内で利用できるようにするための設計でした。終了コードはバックグラウンドタスク完了後に実行されるからです。 + +これは、レスポンスがネットワーク上を移動するのを待っている間にリソースを保持しないようにする意図で、FastAPI 0.106.0 で変更されました。 + +/// tip | 豆知識 + +加えて、バックグラウンドタスクは通常、独立したロジックの集合であり、(例えば専用のデータベース接続など)それ自身のリソースで個別に扱うべきです。 + +そのため、このやり方の方がコードはおそらくよりクリーンになります。 + +/// + +この挙動に依存していた場合は、バックグラウンドタスク用のリソースをバックグラウンドタスク内部で作成し、`yield` を持つ依存関係のリソースに依存しないデータだけを内部で使用するようにしてください。 + +例えば、同じデータベースセッションを使うのではなく、バックグラウンドタスク内で新しいデータベースセッションを作成し、この新しいセッションでデータベースからオブジェクトを取得します。そして、バックグラウンドタスク関数の引数としてデータベースのオブジェクト自体を渡すのではなく、そのオブジェクトの ID を渡し、バックグラウンドタスク関数内でもう一度そのオブジェクトを取得します。 diff --git a/docs/ja/docs/advanced/advanced-python-types.md b/docs/ja/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..b4409bcf6 --- /dev/null +++ b/docs/ja/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# 高度な Python の型 { #advanced-python-types } + +Python の型を扱うときに役立つ追加のアイデアをいくつか紹介します。 + +## `Union` または `Optional` の利用 { #using-union-or-optional } + +何らかの理由で `|` が使えない場合、たとえば型アノテーションではなく `response_model=` のような場所では、縦棒(`|`)の代わりに `typing` の `Union` を使えます。 + +例えば、`str` または `None` になり得ることを宣言できます: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +`typing` には、`None` を取り得ることを宣言するための短縮形として `Optional` もあります。 + +ここからは私のとても主観的な提案です: + +- 🚨 `Optional[SomeType]` の使用は避けましょう +- 代わりに ✨ **`Union[SomeType, None]` を使いましょう** ✨。 + +どちらも等価で内部的には同一ですが、「optional(任意)」という語が値が任意だと誤解させやすく、実際の意味は「`None` を取り得る」であり、任意ではなく依然として必須である場合でもそうです。そのため `Optional` より `Union` を勧めます。 + +`Union[SomeType, None]` の方が意味がより明確だと思います。 + +これは用語や名前付けの話に過ぎませんが、その言葉があなたやチームメイトのコードの捉え方に影響します。 + +例として次の関数を見てみましょう: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +パラメータ `name` は `Optional[str]` と定義されていますが、任意ではありません。このパラメータなしで関数を呼び出すことはできません: + +```Python +say_hi() # あっ、これはエラーになります!😱 +``` + +`name` パラメータにはデフォルト値がないため、依然として必須(任意ではない)です。ただし、`name` は値として `None` を受け付けます: + +```Python +say_hi(name=None) # これは動作します。None は有効です 🎉 +``` + +朗報として、多くの場合は単純に `|` を使って型の Union を定義できます: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +したがって、通常は `Optional` や `Union` といった名前を気にする必要はありません。😎 diff --git a/docs/ja/docs/advanced/async-tests.md b/docs/ja/docs/advanced/async-tests.md new file mode 100644 index 000000000..067e9cc9a --- /dev/null +++ b/docs/ja/docs/advanced/async-tests.md @@ -0,0 +1,99 @@ +# 非同期テスト { #async-tests } + +これまでに、提供されている `TestClient` を使って **FastAPI** アプリケーションをテストする方法を見てきました。ここまでは、`async` 関数を使わない同期テストのみでした。 + +テストで非同期関数を使えると、たとえばデータベースへ非同期にクエリする場合などに便利です。非同期データベースライブラリを使いながら、FastAPI アプリにリクエストを送り、その後バックエンドが正しいデータをデータベースに書き込めたかを検証したい、といったケースを想像してください。 + +その方法を見ていきます。 + +## pytest.mark.anyio { #pytest-mark-anyio } + +テスト内で非同期関数を呼び出したい場合、テスト関数自体も非同期である必要があります。AnyIO はこれを実現するための便利なプラグインを提供しており、特定のテスト関数を非同期で呼び出すことを指定できます。 + +## HTTPX { #httpx } + +**FastAPI** アプリケーションが通常の `def` 関数を使っていても、その内側は依然として `async` アプリケーションです。 + +`TestClient` は、標準の pytest を使って通常の `def` のテスト関数から非同期の FastAPI アプリを呼び出すための「おまじない」を内部で行います。しかし、その「おまじない」はテスト関数自体が非同期の場合には機能しません。テストを非同期で実行すると、テスト関数内で `TestClient` は使えなくなります。 + +`TestClient` は HTTPX を基に作られており、幸いなことに API のテストには HTTPX を直接利用できます。 + +## 例 { #example } + +簡単な例として、[大きなアプリケーション](../tutorial/bigger-applications.md){.internal-link target=_blank} と [テスト](../tutorial/testing.md){.internal-link target=_blank} で説明したものに似たファイル構成を考えます: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +`main.py` は次のようになります: + +{* ../../docs_src/async_tests/app_a_py310/main.py *} + +`test_main.py` は `main.py` のテストを持ち、次のようになります: + +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} + +## 実行 { #run-it } + +テストはいつも通り次で実行できます: + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## 詳細 { #in-detail } + +マーカー `@pytest.mark.anyio` は、このテスト関数を非同期で呼び出すべきであることを pytest に伝えます: + +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} + +/// tip | 豆知識 + +`TestClient` を使っていたときと異なり、テスト関数は `def` ではなく `async def` になっている点に注意してください。 + +/// + +次に、アプリを渡して `AsyncClient` を作成し、`await` を使って非同期リクエストを送信できます。 + +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} + +これは次と同等です: + +```Python +response = client.get('/') +``` + +...これまでは `TestClient` でリクエストを送っていました。 + +/// tip | 豆知識 + +新しい `AsyncClient` では async/await を使っている点に注意してください。リクエストは非同期です。 + +/// + +/// warning | 注意 + +アプリケーションが lifespan イベントに依存している場合、`AsyncClient` はそれらのイベントをトリガーしません。確実にトリガーするには、florimondmanca/asgi-lifespan の `LifespanManager` を使用してください。 + +/// + +## その他の非同期関数呼び出し { #other-asynchronous-function-calls } + +テスト関数が非同期になったので、FastAPI アプリへのリクエスト送信以外の `async` 関数も、コードの他の場所と同様に呼び出して(`await` して)使えます。 + +/// tip | 豆知識 + +テストに非同期関数呼び出しを統合した際に(例: MongoDB の MotorClient 使用時)、`RuntimeError: Task attached to a different loop` に遭遇した場合は、イベントループを必要とするオブジェクトは非同期関数内でのみインスタンス化するようにしてください。例えば `@app.on_event("startup")` コールバック内で行います。 + +/// diff --git a/docs/ja/docs/advanced/behind-a-proxy.md b/docs/ja/docs/advanced/behind-a-proxy.md new file mode 100644 index 000000000..67eaa9909 --- /dev/null +++ b/docs/ja/docs/advanced/behind-a-proxy.md @@ -0,0 +1,466 @@ +# プロキシの背後 { #behind-a-proxy } + +多くの状況で、FastAPI アプリの前段に **Traefik** や **Nginx** のような**プロキシ**を置きます。 + +これらのプロキシは HTTPS 証明書などの処理を担います。 + +## プロキシの転送ヘッダー { #proxy-forwarded-headers } + +アプリケーションの前段にある **プロキシ** は通常、リクエストを **サーバー** に送る前に、そのリクエストがプロキシによって転送されたことを知らせるためのヘッダーを動的に付与し、使用中の元の(公開)URL(ドメインを含む)や HTTPS 使用などの情報を伝えます。 + +**サーバー** プログラム(例えば **FastAPI CLI** 経由の **Uvicorn**)はこれらのヘッダーを解釈し、その情報をアプリケーションに渡すことができます。 + +しかしセキュリティ上、サーバーは自分が信頼できるプロキシの背後にあると分からないため、これらのヘッダーを解釈しません。 + +/// note | 技術詳細 + +プロキシのヘッダーは次のとおりです: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +### プロキシ転送ヘッダーを有効化 { #enable-proxy-forwarded-headers } + +FastAPI CLI を *CLI オプション* `--forwarded-allow-ips` 付きで起動し、転送ヘッダーを信頼して読んでよい IP アドレスを指定できます。 + +`--forwarded-allow-ips="*"` とすると、すべての送信元 IP を信頼します。 + +**サーバー** が信頼できる **プロキシ** の背後にあり、そのプロキシからのみ接続される場合、プロキシの IP を受け入れるようになります。 + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### HTTPS を伴うリダイレクト { #redirects-with-https } + +例えば、*path operation* `/items/` を定義しているとします: + +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} + +クライアントが `/items` にアクセスすると、既定では `/items/` にリダイレクトされます。 + +しかし、*CLI オプション* `--forwarded-allow-ips` を設定する前は、`http://localhost:8000/items/` にリダイレクトされる場合があります。 + +ですが、アプリケーションは `https://mysuperapp.com` で公開されており、`https://mysuperapp.com/items/` にリダイレクトされるべきかもしれません。 + +`--proxy-headers` を設定すると、FastAPI は正しい場所にリダイレクトできるようになります。😎 + +``` +https://mysuperapp.com/items/ +``` + +/// tip | 豆知識 + +HTTPS について詳しく知りたい場合は、[HTTPS について](../deployment/https.md){.internal-link target=_blank} を参照してください。 + +/// + +### プロキシ転送ヘッダーの仕組み { #how-proxy-forwarded-headers-work } + +クライアントと **アプリケーションサーバー** の間で、**プロキシ** がどのように転送ヘッダーを追加するかを図示します: + +```mermaid +sequenceDiagram + participant Client + participant Proxy as Proxy/Load Balancer + participant Server as FastAPI Server + + Client->>Proxy: HTTPS Request
Host: mysuperapp.com
Path: /items + + Note over Proxy: Proxy adds forwarded headers + + Proxy->>Server: HTTP Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items + + Note over Server: Server interprets headers
(if --forwarded-allow-ips is set) + + Server->>Proxy: HTTP Response
with correct HTTPS URLs + + Proxy->>Client: HTTPS Response +``` + +**プロキシ** は元のクライアントリクエストを受け取り、**アプリケーションサーバー** に渡す前に特別な「転送」ヘッダー(`X-Forwarded-*`)を追加します。 + +これらのヘッダーは、通常は失われる元のリクエストの情報を保持します: + +* **X-Forwarded-For**: 元のクライアントの IP アドレス +* **X-Forwarded-Proto**: 元のプロトコル(`https`) +* **X-Forwarded-Host**: 元のホスト(`mysuperapp.com`) + +**FastAPI CLI** を `--forwarded-allow-ips` で設定すると、これらのヘッダーを信頼して使用し、たとえばリダイレクトで正しい URL を生成します。 + +## パスプレフィックスを削除するプロキシ { #proxy-with-a-stripped-path-prefix } + +アプリケーションにパスプレフィックスを付与するプロキシを使う場合があります。 + +そのような場合は `root_path` でアプリケーションを設定できます。 + +`root_path` は(FastAPI が Starlette を通して基づいている)ASGI 仕様で提供されている仕組みです。 + +`root_path` はこの種のケースを扱うために使われます。 + +これはサブアプリケーションをマウントする際にも内部的に使用されます。 + +ここでいう「パスプレフィックスを削除するプロキシ」とは、コード上では `/app` というパスを宣言していても、その上にプロキシ層を追加して **FastAPI** アプリケーションを `/api/v1` のようなパスの下に配置することを指します。 + +この場合、元のパス `/app` は実際には `/api/v1/app` で提供されます。 + +すべてのコードは `/app` だけを前提に書かれているにもかかわらず、です。 + +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} + +そしてプロキシは、アプリサーバー(おそらく FastAPI CLI 経由の Uvicorn)に転送する前に、その場で **パスプレフィックス** を**「削除」**し、アプリケーション側には自分が `/app` で提供されているように見せかけます。これにより、コードのすべてを `/api/v1` のプレフィックス付きに書き換える必要がありません。 + +ここまでは通常どおりに動作します。 + +しかし、統合ドキュメント UI(フロントエンド)を開くと、OpenAPI スキーマを `/api/v1/openapi.json` ではなく `/openapi.json` から取得しようとします。 + +そのため、フロントエンド(ブラウザで動作)は `/openapi.json` にアクセスしようとして、OpenAPI スキーマを取得できません。 + +このアプリには `/api/v1` のパスプレフィックスを付与するプロキシがあるため、フロントエンドは `/api/v1/openapi.json` から取得する必要があります。 + +```mermaid +graph LR + +browser("Browser") +proxy["Proxy on http://0.0.0.0:9999/api/v1/app"] +server["Server on http://127.0.0.1:8000/app"] + +browser --> proxy +proxy --> server +``` + +/// tip | 豆知識 + +IP `0.0.0.0` は、そのマシン/サーバーで利用可能なすべての IP で待ち受けることを意味する表現として一般的に使われます。 + +/// + +ドキュメント UI では、この API の `server` が(プロキシの背後で)`/api/v1` にあることを宣言する OpenAPI スキーマも必要です。例えば: + +```JSON hl_lines="4-8" +{ + "openapi": "3.1.0", + // ほかの項目 + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + // ほかの項目 + } +} +``` + +この例では「Proxy」は **Traefik** のようなもの、サーバーは **Uvicorn** と FastAPI CLI で FastAPI アプリケーションを実行しているものを想定しています。 + +### `root_path` の指定 { #providing-the-root-path } + +これを実現するには、次のようにコマンドラインオプション `--root-path` を使用します: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Hypercorn を使う場合も、同様に `--root-path` オプションがあります。 + +/// note | 技術詳細 + +このユースケース向けに、ASGI 仕様は `root_path` を定義しています。 + +そして `--root-path` コマンドラインオプションは、その `root_path` を提供します。 + +/// + +### 現在の `root_path` の確認 { #checking-the-current-root-path } + +各リクエストでアプリケーションが使用している現在の `root_path` は取得できます。これは(ASGI 仕様の一部である)`scope` 辞書に含まれます。 + +ここではデモのため、メッセージに含めています。 + +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} + +そのうえで、次のように Uvicorn を起動すると: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +レスポンスは次のようになります: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +### FastAPI アプリでの `root_path` 設定 { #setting-the-root-path-in-the-fastapi-app } + +あるいは、`--root-path` のようなコマンドラインオプションを渡せない場合は、FastAPI アプリ作成時にパラメータ `root_path` を設定できます: + +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} + +`FastAPI` に `root_path` を渡すのは、Uvicorn や Hypercorn にコマンドラインオプション `--root-path` を渡すのと同等です。 + +### `root_path` について { #about-root-path } + +サーバー(Uvicorn)は、その `root_path` をアプリに渡す以外の用途では使用しない点に注意してください。 + +しかし、ブラウザで http://127.0.0.1:8000/app にアクセスすると、通常どおりのレスポンスが表示されます: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +つまり、`http://127.0.0.1:8000/api/v1/app` でアクセスされることは想定していません。 + +Uvicorn は、プロキシが `http://127.0.0.1:8000/app` にアクセスしてくることを想定しており、その上に追加の `/api/v1` プレフィックスを付けるのはプロキシの責務です。 + +## パスプレフィックスを削除するプロキシについて { #about-proxies-with-a-stripped-path-prefix } + +パスプレフィックスを削除するプロキシは、設定方法の一例にすぎない点に注意してください。 + +多くの場合、プロキシはパスプレフィックスを削除しない設定が既定でしょう。 + +そのような場合(パスプレフィックスを削除しない場合)は、プロキシは `https://myawesomeapp.com` のようなアドレスで待ち受け、ブラウザが `https://myawesomeapp.com/api/v1/app` にアクセスし、サーバー(例: Uvicorn)が `http://127.0.0.1:8000` で待ち受けているなら、プロキシ(プレフィックスを削除しない)は同じパス `http://127.0.0.1:8000/api/v1/app` で Uvicorn にアクセスします。 + +## Traefik を使ったローカル検証 { #testing-locally-with-traefik } + +Traefik を使えば、パスプレフィックスを削除する構成をローカルで簡単に試せます。 + +Traefik をダウンロード してください。単一バイナリなので、圧縮ファイルを展開して端末から直接実行できます。 + +次の内容で `traefik.toml` というファイルを作成します: + +```TOML hl_lines="3" +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +これは Traefik にポート 9999 で待ち受け、別のファイル `routes.toml` を使用するよう指示します。 + +/// tip | 豆知識 + +標準の HTTP ポート 80 ではなく 9999 を使うのは、管理者(`sudo`)権限で実行する必要をなくすためです。 + +/// + +次に、その `routes.toml` ファイルを作成します: + +```TOML hl_lines="5 12 20" +[http] + [http.middlewares] + + [http.middlewares.api-stripprefix.stripPrefix] + prefixes = ["/api/v1"] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + middlewares = ["api-stripprefix"] + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +このファイルは Traefik に `/api/v1` のパスプレフィックスを使うよう設定します。 + +そして Traefik は、`http://127.0.0.1:8000` で動作している Uvicorn へリクエストを転送します。 + +では Traefik を起動します: + +
+ +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml +``` + +
+ +次に、`--root-path` オプションを指定してアプリを起動します: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### レスポンスの確認 { #check-the-responses } + +ここで、Uvicorn のポートの URL http://127.0.0.1:8000/app にアクセスすると、通常どおりのレスポンスが表示されます: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +/// tip | 豆知識 + +`http://127.0.0.1:8000/app` にアクセスしているにもかかわらず、オプション `--root-path` から取得した `root_path` が `/api/v1` と表示されている点に注目してください。 + +/// + +次に、Traefik のポートでプレフィックス付きの URL http://127.0.0.1:9999/api/v1/app を開きます。 + +同じレスポンスが得られます: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +ただし今回は、プロキシが付与したプレフィックス `/api/v1` の付いた URL です。 + +もちろん、ここでの想定は全員がプロキシ経由でアプリにアクセスすることです。したがって、パスプレフィックス `/api/v1` のある版が「正しい」アクセス方法になります。 + +一方、プレフィックスのない版(`http://127.0.0.1:8000/app`。Uvicorn が直接提供)は、_プロキシ_(Traefik)専用の接続先になります。 + +これにより、プロキシ(Traefik)がパスプレフィックスをどのように用い、サーバー(Uvicorn)が `--root-path` の `root_path` をどのように利用するかが分かります。 + +### ドキュメント UI の確認 { #check-the-docs-ui } + +ここがポイントです。✨ + +「公式な」アクセス方法は、定義したパスプレフィックス付きのプロキシ経由です。したがって想定どおり、プレフィックスなしの URL で Uvicorn が直接提供するドキュメント UI にアクセスすると動作しません。プロキシ経由でアクセスされることを前提としているためです。 + +http://127.0.0.1:8000/docs を確認してください: + + + +しかし、プロキシ(ポート `9999`)を使った「公式」URL `/api/v1/docs` でドキュメント UI にアクセスすると、正しく動作します!🎉 + +http://127.0.0.1:9999/api/v1/docs を確認してください: + + + +ねらいどおりです。✔️ + +これは、FastAPI が `root_path` を使って、OpenAPI の既定の `server` を `root_path` の URL で生成するためです。 + +## 追加のサーバー { #additional-servers } + +/// warning | 注意 + +これは高度なユースケースです。読み飛ばしても構いません。 + +/// + +既定では、**FastAPI** は OpenAPI スキーマ内に `root_path` の URL を持つ `server` を作成します。 + +しかし、ステージングと本番の両方と同じドキュメント UI で対話させたい場合など、別の `servers` を指定することもできます。 + +カスタムの `servers` リストを渡していて、かつ `root_path`(API がプロキシの背後にあるため)が設定されている場合、**FastAPI** はこの `root_path` を用いた「server」をリストの先頭に挿入します。 + +例えば: + +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} + +次のような OpenAPI スキーマが生成されます: + +```JSON hl_lines="5-7" +{ + "openapi": "3.1.0", + // ほかの項目 + "servers": [ + { + "url": "/api/v1" + }, + { + "url": "https://stag.example.com", + "description": "Staging environment" + }, + { + "url": "https://prod.example.com", + "description": "Production environment" + } + ], + "paths": { + // ほかの項目 + } +} +``` + +/// tip | 豆知識 + +`root_path` から取得した `url` 値 `/api/v1` を持つ server が自動生成されている点に注目してください。 + +/// + +ドキュメント UI(http://127.0.0.1:9999/api/v1/docs)では次のように表示されます: + + + +/// tip | 豆知識 + +ドキュメント UI は、選択した server と対話します。 + +/// + +/// note | 技術詳細 + +OpenAPI 仕様の `servers` プロパティは任意です。 + +`servers` パラメータを指定せず、かつ `root_path` が `/` の場合、生成される OpenAPI スキーマからは `servers` プロパティが既定で完全に省略されます。これは、`url` が `/` の server が 1 つあるのと同等です。 + +/// + +### `root_path` 由来の自動 server を無効化 { #disable-automatic-server-from-root-path } + +`root_path` を用いた自動的な server を **FastAPI** に含めてほしくない場合は、パラメータ `root_path_in_servers=False` を使用します: + +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} + +すると、OpenAPI スキーマには含まれません。 + +## サブアプリケーションのマウント { #mounting-a-sub-application } + +`root_path` を伴うプロキシを使用しつつサブアプリケーションをマウントする必要がある場合でも([サブアプリケーション - マウント](sub-applications.md){.internal-link target=_blank} 参照)、通常どおりに行えます。 + +FastAPI は内部で `root_path` を適切に扱うため、そのまま動作します。✨ diff --git a/docs/ja/docs/advanced/custom-response.md b/docs/ja/docs/advanced/custom-response.md index 9d881c013..50a789165 100644 --- a/docs/ja/docs/advanced/custom-response.md +++ b/docs/ja/docs/advanced/custom-response.md @@ -30,7 +30,7 @@ しかし、返そうとしているコンテンツが **JSONでシリアライズ可能**であることが確実なら、それを直接レスポンスクラスに渡して、FastAPIがレスポンスクラスへ渡す前に返却コンテンツを `jsonable_encoder` に通すことで発生する追加のオーバーヘッドを回避できます。 -{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} /// info | 情報 @@ -55,7 +55,7 @@ * `HTMLResponse` をインポートする。 * *path operation デコレータ* のパラメータ `response_class` に `HTMLResponse` を渡す。 -{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} /// info | 情報 @@ -73,7 +73,7 @@ 上記と同じ例において、 `HTMLResponse` を返すと、このようになります: -{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} /// warning | 注意 @@ -97,7 +97,7 @@ 例えば、このようになります: -{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} この例では、関数 `generate_html_response()` は、`str` のHTMLを返すのではなく、`Response` を生成して返しています。 @@ -136,7 +136,7 @@ FastAPI(実際にはStarlette)は自動的にContent-Lengthヘッダーを含みます。また、`media_type` に基づいたContent-Typeヘッダーを含み、テキストタイプのためにcharsetを追加します。 -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ### `HTMLResponse` { #htmlresponse } @@ -146,7 +146,7 @@ FastAPI(実際にはStarlette)は自動的にContent-Lengthヘッダーを テキストやバイトを受け取り、プレーンテキストのレスポンスを返します。 -{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} ### `JSONResponse` { #jsonresponse } @@ -180,7 +180,7 @@ FastAPI(実際にはStarlette)は自動的にContent-Lengthヘッダーを /// -{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} /// tip | 豆知識 @@ -194,13 +194,13 @@ HTTPリダイレクトを返します。デフォルトでは307ステータス `RedirectResponse` を直接返せます: -{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} --- または、`response_class` パラメータで使用できます: -{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} その場合、*path operation*関数からURLを直接返せます。 @@ -210,13 +210,13 @@ HTTPリダイレクトを返します。デフォルトでは307ステータス また、`status_code` パラメータを `response_class` パラメータと組み合わせて使うこともできます: -{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} ### `StreamingResponse` { #streamingresponse } 非同期ジェネレータ、または通常のジェネレータ/イテレータを受け取り、レスポンスボディをストリームします。 -{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} #### ファイルライクオブジェクトで `StreamingResponse` を使う { #using-streamingresponse-with-file-like-objects } @@ -226,7 +226,7 @@ HTTPリダイレクトを返します。デフォルトでは307ステータス これにはクラウドストレージとの連携、映像処理など、多くのライブラリが含まれます。 -{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} 1. これはジェネレータ関数です。内部に `yield` 文を含むため「ジェネレータ関数」です。 2. `with` ブロックを使うことで、ジェネレータ関数が終わった後(つまりレスポンスの送信が完了した後)にfile-likeオブジェクトが確実にクローズされるようにします。 @@ -255,11 +255,11 @@ HTTPリダイレクトを返します。デフォルトでは307ステータス ファイルレスポンスには、適切な `Content-Length`、`Last-Modified`、`ETag` ヘッダーが含まれます。 -{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} `response_class` パラメータを使うこともできます: -{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} この場合、*path operation*関数からファイルパスを直接返せます。 @@ -273,7 +273,7 @@ HTTPリダイレクトを返します。デフォルトでは307ステータス `CustomORJSONResponse` を作れます。主に必要なのは、コンテンツを `bytes` として返す `Response.render(content)` メソッドを作ることです: -{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} これまでは次のように返していたものが: @@ -299,7 +299,7 @@ HTTPリダイレクトを返します。デフォルトでは307ステータス 以下の例では、**FastAPI** はすべての*path operation*で、`JSONResponse` の代わりに `ORJSONResponse` をデフォルトとして使います。 -{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} /// tip | 豆知識 diff --git a/docs/ja/docs/advanced/dataclasses.md b/docs/ja/docs/advanced/dataclasses.md new file mode 100644 index 000000000..74f479f07 --- /dev/null +++ b/docs/ja/docs/advanced/dataclasses.md @@ -0,0 +1,95 @@ +# Dataclasses の使用 { #using-dataclasses } + +FastAPI は **Pydantic** の上に構築されており、これまでにリクエストやレスポンスを宣言するために Pydantic モデルを使う方法を紹介してきました。 + +しかし FastAPI は、同様の方法で `dataclasses` もサポートします: + +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} + +これは **Pydantic** によって引き続きサポートされています。Pydantic には `dataclasses` の内部サポート があるためです。 + +そのため、上記のように明示的に Pydantic を使っていないコードでも、FastAPI は標準の dataclass を Pydantic 独自の dataclass に変換するために Pydantic を使用しています。 + +そして当然ながら、次の点も同様にサポートされます: + +- データ検証 +- データのシリアライズ +- データのドキュメント化 など + +これは Pydantic モデルの場合と同じように動作します。内部的にも同様に Pydantic を使って実現されています。 + +/// info | 情報 + +dataclasses は、Pydantic モデルができることをすべては行えない点に留意してください。 + +そのため、Pydantic モデルを使う必要がある場合もあります。 + +しかし既存の dataclass が多数あるなら、FastAPI で Web API を構築する際にそれらを活用するちょっとしたテクニックになります。🤓 + +/// + +## `response_model` での dataclasses { #dataclasses-in-response-model } + +`response_model` パラメータでも `dataclasses` を使用できます: + +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} + +dataclass は自動的に Pydantic の dataclass に変換されます。 + +このため、そのスキーマは API ドキュメントの UI に表示されます: + + + +## ネストしたデータ構造での dataclasses { #dataclasses-in-nested-data-structures } + +`dataclasses` を他の型注釈と組み合わせて、ネストしたデータ構造を作成できます。 + +場合によっては、自動生成された API ドキュメントでエラーが発生するなどの理由で、Pydantic 版の `dataclasses` を使う必要があるかもしれません。 + +その場合は、標準の `dataclasses` を `pydantic.dataclasses` に置き換えるだけで済みます。これはドロップイン置換です: + +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} + +1. 依然として標準の `dataclasses` から `field` をインポートします。 + +2. `pydantic.dataclasses` は `dataclasses` のドロップイン置換です。 + +3. `Author` dataclass は `Item` dataclass のリストを含みます。 + +4. `Author` dataclass を `response_model` パラメータとして使用しています。 + +5. リクエストボディとしての dataclass と併せて、他の標準の型注釈を使用できます。 + + この例では、`Item` dataclass のリストです。 + +6. ここでは、dataclass のリストである `items` を含む辞書を返しています。 + + FastAPI はデータを JSON に シリアライズ できます。 + +7. ここでは `response_model` に `Author` dataclass のリストという型注釈を使用しています。 + + このように、`dataclasses` は標準の型注釈と組み合わせられます。 + +8. この *path operation 関数* は、`async def` ではなく通常の `def` を使用しています。 + + いつもどおり、FastAPI では必要に応じて `def` と `async def` を組み合わせられます。 + + どちらをいつ使うかの復習が必要な場合は、[`async` と `await`](../async.md#in-a-hurry){.internal-link target=_blank} に関するドキュメントの _"In a hurry?"_ セクションを参照してください。 + +9. この *path operation 関数* は(可能ではありますが)dataclass 自体は返さず、内部データを持つ辞書のリストを返しています。 + + FastAPI は dataclass を含む `response_model` パラメータを使ってレスポンスを変換します。 + +`dataclasses` は他の型注釈と多様な組み合わせが可能で、複雑なデータ構造を構成できます。 + +上記のコード内コメントのヒントを参照して、より具体的な詳細を確認してください。 + +## さらに学ぶ { #learn-more } + +`dataclasses` を他の Pydantic モデルと組み合わせたり、継承したり、自分のモデルに含めたりもできます。 + +詳しくは、dataclasses に関する Pydantic ドキュメント を参照してください。 + +## バージョン { #version } + +これは FastAPI バージョン `0.67.0` 以降で利用可能です。🔖 diff --git a/docs/ja/docs/advanced/events.md b/docs/ja/docs/advanced/events.md new file mode 100644 index 000000000..fb79062fa --- /dev/null +++ b/docs/ja/docs/advanced/events.md @@ -0,0 +1,165 @@ +# Lifespan イベント { #lifespan-events } + +アプリケーションが起動する前に一度だけ実行すべきロジック(コード)を定義できます。これは、アプリケーションがリクエストを受け取り始める前に、そのコードが一度だけ実行される、という意味です。 + +同様に、アプリケーションがシャットダウンするときに実行すべきロジック(コード)も定義できます。この場合、そのコードは、(多くのリクエストを処理した)後に一度だけ実行されます。 + +このコードは、アプリケーションがリクエストの受け付けを「開始」する前、そして処理を「終了」した直後に実行されるため、アプリケーションの全体の「Lifespan」(この「lifespan」という言葉はすぐ後で重要になります 😉)をカバーします。 + +これは、アプリ全体で使用し、リクエスト間で「共有」し、かつ後で「クリーンアップ」する必要があるような「リソース」をセットアップするのにとても便利です。たとえば、データベース接続プールや、共有の機械学習モデルの読み込みなどです。 + +## ユースケース { #use-case } + +まずはユースケースの例から始めて、これをどのように解決するかを見ていきます。 + +リクエストを処理するために使用したい「機械学習モデル」がいくつかあると想像してください。🤖 + +同じモデルをリクエスト間で共有するので、リクエストごとやユーザーごとに別々のモデルを使うわけではありません。 + +モデルの読み込みにはディスクから大量のデータを読む必要があり、かなり時間がかかるかもしれません。したがって、リクエストごとに読み込みたくはありません。 + +モジュール/ファイルのトップレベルで読み込むこともできますが、その場合は、たとえ簡単な自動テストを実行するだけでも「モデルを読み込む」ことになり、そのモデルの読み込みを待つ必要があるため、独立したコード部分を走らせるだけのテストでも「遅く」なってしまいます。 + +これを解決しましょう。リクエストを処理する前にモデルを読み込みますが、コードがロードされている最中ではなく、アプリケーションがリクエストの受け付けを開始する直前だけにします。 + +## Lifespan { #lifespan } + +この「起動時」と「シャットダウン時」のロジックは、`FastAPI` アプリの `lifespan` パラメータと「コンテキストマネージャ」(これが何かはすぐに示します)を使って定義できます。 + +まずは例を見てから、詳細を説明します。 + +次のように、`yield` を使う非同期関数 `lifespan()` を作成します: + +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} + +ここでは、`yield` の前で機械学習モデルの辞書に(ダミーの)モデル関数を入れることで、高コストな「起動時」のモデル読み込みをシミュレーションしています。このコードは、アプリケーションがリクエストを「受け付け始める前」に、すなわち起動時に実行されます。 + +そして `yield` の直後でモデルをアンロードします。このコードは、アプリケーションがリクエスト処理を「終了」した後、シャットダウン直前に実行されます。たとえばメモリや GPU のようなリソースを解放できます。 + +/// tip | 豆知識 + +`shutdown` は、アプリケーションを「停止」するときに発生します。 + +新しいバージョンを開始する必要があるか、単に実行をやめたくなったのかもしれません。🤷 + +/// + +### Lifespan 関数 { #lifespan-function } + +まず注目すべきは、`yield` を使う非同期関数を定義していることです。これは「yield を使う依存関係(Dependencies)」にとてもよく似ています。 + +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} + +`yield` の前の前半は、アプリケーションが開始される「前」に実行されます。 + +`yield` の後半は、アプリケーションの処理が「終了」した「後」に実行されます。 + +### 非同期コンテキストマネージャ { #async-context-manager } + +この関数には `@asynccontextmanager` がデコレートされています。 + +これにより、この関数は「非同期コンテキストマネージャ」になります。 + +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} + +Python の「コンテキストマネージャ」は、`with` 文で使えるものです。たとえば、`open()` はコンテキストマネージャとして使えます: + +```Python +with open("file.txt") as file: + file.read() +``` + +最近の Python には「非同期コンテキストマネージャ」もあります。`async with` で使います: + +```Python +async with lifespan(app): + await do_stuff() +``` + +このようにコンテキストマネージャ(または非同期コンテキストマネージャ)を作ると、`with` ブロックに入る前に `yield` より前のコードが実行され、`with` ブロックを出た後に `yield` より後ろのコードが実行されます。 + +上のコード例では直接それを使ってはいませんが、FastAPI に渡して内部で使ってもらいます。 + +`FastAPI` アプリの `lifespan` パラメータは「非同期コンテキストマネージャ」を受け取るので、新しく作った `lifespan` 非同期コンテキストマネージャを渡せます。 + +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} + +## 代替のイベント(非推奨) { #alternative-events-deprecated } + +/// warning | 注意 + +推奨される方法は、上で説明したとおり `FastAPI` アプリの `lifespan` パラメータを使って「起動」と「シャットダウン」を扱うことです。`lifespan` パラメータを指定すると、`startup` と `shutdown` のイベントハンドラは呼び出されなくなります。`lifespan` かイベントか、どちらか一方であり、両方同時ではありません。 + +この節は読み飛ばしてもかまいません。 + +/// + +起動時とシャットダウン時に実行されるロジックを定義する別の方法もあります。 + +アプリケーションが起動する前、またはシャットダウンするときに実行する必要があるイベントハンドラ(関数)を定義できます。 + +これらの関数は `async def` でも、通常の `def` でも構いません。 + +### `startup` イベント { #startup-event } + +アプリケーションが開始される前に実行すべき関数を追加するには、イベント `"startup"` で宣言します: + +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} + +この場合、`startup` のイベントハンドラ関数は items の「データベース」(単なる `dict`)をいくつかの値で初期化します。 + +イベントハンドラ関数は複数追加できます。 + +すべての `startup` イベントハンドラが完了するまで、アプリケーションはリクエストの受け付けを開始しません。 + +### `shutdown` イベント { #shutdown-event } + +アプリケーションがシャットダウンするときに実行すべき関数を追加するには、イベント `"shutdown"` で宣言します: + +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} + +ここでは、`shutdown` のイベントハンドラ関数が、テキスト行 `"Application shutdown"` をファイル `log.txt` に書き込みます。 + +/// info | 情報 + +`open()` 関数の `mode="a"` は「追加」(append)を意味します。つまり、そのファイルに既にある内容を上書きせず、行が後ろに追記されます。 + +/// + +/// tip | 豆知識 + +この例では、ファイルを扱う標準の Python 関数 `open()` を使っています。 + +そのため、ディスクへの書き込みを「待つ」必要がある I/O(入力/出力)が関わります。 + +しかし `open()` 自体は `async` や `await` を使いません。 + +したがって、イベントハンドラ関数は `async def` ではなく通常の `def` で宣言しています。 + +/// + +### `startup` と `shutdown` をまとめて { #startup-and-shutdown-together } + +起動時とシャットダウン時のロジックは関連していることが多いです。何かを開始してから終了したい、リソースを獲得してから解放したい、などです. + +共有するロジックや変数のない別々の関数でそれを行うのは難しく、グローバル変数などに値を保存する必要が出てきます。 + +そのため、現在は上で説明したとおり `lifespan` を使うことが推奨されています。 + +## 技術詳細 { #technical-details } + +技術が気になる方への細かな詳細です。🤓 + +内部的には、ASGI の技術仕様において、これは Lifespan プロトコル の一部であり、`startup` と `shutdown` というイベントが定義されています。 + +/// info | 情報 + +Starlette の `lifespan` ハンドラについては、Starlette の Lifespan ドキュメントで詳しく読むことができます。 + +コードの他の領域で使える lifespan の状態をどのように扱うかも含まれています。 + +/// + +## サブアプリケーション { #sub-applications } + +🚨 これらの lifespan イベント(startup と shutdown)はメインのアプリケーションに対してのみ実行され、[サブアプリケーション - マウント](sub-applications.md){.internal-link target=_blank} には実行されないことに注意してください。 diff --git a/docs/ja/docs/advanced/generate-clients.md b/docs/ja/docs/advanced/generate-clients.md new file mode 100644 index 000000000..7b9f82054 --- /dev/null +++ b/docs/ja/docs/advanced/generate-clients.md @@ -0,0 +1,208 @@ +# SDK の生成 { #generating-sdks } + +**FastAPI** は **OpenAPI** 仕様に基づいているため、その API は多くのツールが理解できる標準形式で記述できます。 + +これにより、最新の**ドキュメント**、複数言語のクライアントライブラリ(**SDKs**)、そしてコードと同期し続ける**テスト**や**自動化ワークフロー**を容易に生成できます。 + +本ガイドでは、FastAPI バックエンド向けの **TypeScript SDK** を生成する方法を説明します。 + +## オープソースの SDK ジェネレータ { #open-source-sdk-generators } + +多用途な選択肢として OpenAPI Generator があります。これは**多数のプログラミング言語**をサポートし、OpenAPI 仕様から SDK を生成できます。 + +**TypeScript クライアント**向けには、Hey API が目的特化のソリューションで、TypeScript エコシステムに最適化された体験を提供します。 + +他の SDK ジェネレータは OpenAPI.Tools でも見つけられます。 + +/// tip | 豆知識 + +FastAPI は自動的に **OpenAPI 3.1** の仕様を生成します。したがって、使用するツールはこのバージョンをサポートしている必要があります。 + +/// + +## FastAPI スポンサーによる SDK ジェネレータ { #sdk-generators-from-fastapi-sponsors } + +このセクションでは、FastAPI をスポンサーしている企業による、**ベンチャー支援**および**企業支援**のソリューションを紹介します。これらの製品は、高品質な生成 SDK に加えて、**追加機能**や**統合**を提供します。 + +✨ [**FastAPI をスポンサーする**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ ことで、これらの企業はフレームワークとその**エコシステム**の健全性と**持続可能性**を支援しています。 + +この支援は、FastAPI の**コミュニティ**(皆さん)への強いコミットメントの表明でもあり、**優れたサービス**の提供だけでなく、堅牢で発展するフレームワーク FastAPI を支える姿勢を示しています。🙇 + +例えば、次のようなものがあります: + +* Speakeasy +* Stainless +* liblab + +これらのソリューションの中にはオープンソースや無料枠を提供するものもあり、金銭的コミットメントなしで試すことができます。他の商用 SDK ジェネレータも存在し、オンラインで見つけられます。🤓 + +## TypeScript SDK を作成する { #create-a-typescript-sdk } + +まずは簡単な FastAPI アプリから始めます: + +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} + +ここで、*path operation* はリクエストとレスポンスのペイロードに使用するモデルを定義しており、`Item` と `ResponseMessage` を使っています。 + +### API ドキュメント { #api-docs } + +`/docs` に移動すると、リクエストで送信・レスポンスで受信するデータの**スキーマ**が表示されます: + + + +これらのスキーマは、アプリ内でモデルとして宣言されているため表示されます。 + +その情報はアプリの **OpenAPI スキーマ**に含まれ、API ドキュメントに表示されます。 + +OpenAPI に含まれるこれらのモデル情報を使って、**クライアントコードを生成**できます。 + +### Hey API { #hey-api } + +モデルを備えた FastAPI アプリがあれば、Hey API で TypeScript クライアントを生成できます。最も手早い方法は npx を使うことです。 + +```sh +npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client +``` + +これで TypeScript SDK が `./src/client` に生成されます。 + +`@hey-api/openapi-ts` のインストール方法や、生成物の詳細は公式サイトを参照してください。 + +### SDK の利用 { #using-the-sdk } + +これでクライアントコードを import して利用できます。例えば次のようになり、メソッドに対して補完が効きます: + + + +送信するペイロードにも補完が適用されます: + + + +/// tip | 豆知識 + +FastAPI アプリの `Item` モデルで定義した `name` と `price` に補完が効いている点に注目してください。 + +/// + +送信データに対するインラインエラーも表示されます: + + + +レスポンスオブジェクトにも補完があります: + + + +## タグ付きの FastAPI アプリ { #fastapi-app-with-tags } + +実運用ではアプリは大きくなり、*path operation* のグループ分けにタグを使うことが多いでしょう。 + +例えば **items** 用と **users** 用のセクションがあり、タグで分けられます: + +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} + +### タグ付き TypeScript クライアントの生成 { #generate-a-typescript-client-with-tags } + +タグを用いた FastAPI アプリからクライアントを生成すると、通常クライアント側のコードもタグごとに分割されます。 + +これにより、クライアントコードも正しく整理・グルーピングされます: + + + +この例では次のようになります: + +* `ItemsService` +* `UsersService` + +### クライアントのメソッド名 { #client-method-names } + +現状では、生成されるメソッド名(`createItemItemsPost` など)はあまりきれいではありません: + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +これは、クライアントジェネレータが各 *path operation* の OpenAPI 内部の **operation ID** を用いるためです。 + +OpenAPI では operation ID は全ての *path operation* を通して一意である必要があります。そのため FastAPI は**関数名**、**パス**、**HTTP メソッド/オペレーション**を組み合わせて operation ID を生成し、一意性を保証します。 + +次にこれを改善する方法を示します。🤓 + +## カスタム operation ID とより良いメソッド名 { #custom-operation-ids-and-better-method-names } + +operation ID の**生成方法**を**変更**して簡潔にし、クライアント側の**メソッド名をシンプル**にできます。 + +この場合でも各 operation ID が**一意**であることは別の方法で保証する必要があります。 + +例えば、各 *path operation* にタグを付け、**タグ**と *path operation* の**名前**(関数名)から operation ID を生成できます。 + +### 一意 ID 生成関数のカスタマイズ { #custom-generate-unique-id-function } + +FastAPI は各 *path operation* に**一意 ID**を用いており、これは **operation ID** のほか、必要に応じてリクエストやレスポンスのカスタムモデル名にも使われます。 + +この関数はカスタマイズ可能です。`APIRoute` を受け取り、文字列を返します。 + +例えばここでは、最初のタグ(通常は 1 つ)と *path operation* 名(関数名)を使います。 + +そのカスタム関数を **FastAPI** の `generate_unique_id_function` パラメータに渡します: + +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} + +### カスタム operation ID で TypeScript クライアントを生成 { #generate-a-typescript-client-with-custom-operation-ids } + +この状態でクライアントを再生成すると、メソッド名が改善されています: + + + +ご覧のとおり、メソッド名はタグ名と関数名のみになり、URL パスや HTTP オペレーションの情報は含まれません。 + +### クライアント生成向けの OpenAPI 仕様の前処理 { #preprocess-the-openapi-specification-for-the-client-generator } + +それでも生成コードには**重複情報**が残っています。 + +`ItemsService`(タグ由来)から items 関連であることはすでに分かるのに、メソッド名にもタグ名が前置されています。😕 + +OpenAPI 全体としては operation ID の**一意性**のために、このプレフィックスを維持したい場合があるでしょう。 + +しかし生成クライアント用には、クライアントを生成する直前に OpenAPI の operation ID を**加工**して、メソッド名をより**見やすく**、**クリーン**にできます。 + +OpenAPI の JSON を `openapi.json` として保存し、次のようなスクリプトで**そのタグのプレフィックスを除去**できます: + +{* ../../docs_src/generate_clients/tutorial004_py310.py *} + +//// tab | Node.js + +```Javascript +{!> ../../docs_src/generate_clients/tutorial004.js!} +``` + +//// + +これにより operation ID は `items-get_items` のような形から単なる `get_items` に置き換わり、クライアントジェネレータはより簡潔なメソッド名を生成できます。 + +### 前処理済み OpenAPI から TypeScript クライアントを生成 { #generate-a-typescript-client-with-the-preprocessed-openapi } + +生成元が `openapi.json` になったので、入力の場所を更新します: + +```sh +npx @hey-api/openapi-ts -i ./openapi.json -o src/client +``` + +新しいクライアントを生成すると、**クリーンなメソッド名**になり、**補完**や**インラインエラー**などもそのまま利用できます: + + + +## 利点 { #benefits } + +自動生成されたクライアントを使うと、次のような対象で**補完**が得られます: + +* メソッド +* 本体のリクエストペイロード、クエリパラメータ等 +* レスポンスのペイロード + +また、あらゆる箇所で**インラインエラー**も得られます。 + +バックエンドコードを更新してフロントエンドを**再生成**すれば、新しい *path operation* はメソッドとして追加され、古いものは削除され、その他の変更も生成コードに反映されます。🤓 + +つまり、変更があれば自動的にクライアントコードに**反映**されます。クライアントを**ビルド**すれば、使用データに**不整合**があればエラーになります。 + +その結果、多くのエラーを開発の初期段階で**早期発見**でき、本番で最終ユーザーに不具合が現れてから原因をデバッグする必要がなくなります。✨ diff --git a/docs/ja/docs/advanced/middleware.md b/docs/ja/docs/advanced/middleware.md new file mode 100644 index 000000000..2883d53d8 --- /dev/null +++ b/docs/ja/docs/advanced/middleware.md @@ -0,0 +1,97 @@ +# 高度なミドルウェア { #advanced-middleware } + +メインのチュートリアルでは、アプリケーションに[カスタムミドルウェア](../tutorial/middleware.md){.internal-link target=_blank}を追加する方法を学びました。 + +そして、[`CORSMiddleware` を使った CORS の扱い方](../tutorial/cors.md){.internal-link target=_blank}も学びました。 + +このセクションでは、その他のミドルウェアの使い方を見ていきます。 + +## ASGI ミドルウェアの追加 { #adding-asgi-middlewares } + +**FastAPI** は Starlette を基盤としており、ASGI 仕様を実装しているため、任意の ASGI ミドルウェアを利用できます。 + +ミドルウェアは ASGI 仕様に従っていれば、FastAPI や Starlette 専用に作られていなくても動作します。 + +一般に、ASGI ミドルウェアは最初の引数として ASGI アプリを受け取るクラスです。 + +そのため、サードパーティの ASGI ミドルウェアのドキュメントでは、おそらく次のように書かれているでしょう: + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +しかし FastAPI(正確には Starlette)は、内部ミドルウェアがサーバーエラーを処理し、カスタム例外ハンドラが正しく動作することを保証する、より簡単な方法を提供しています。 + +そのためには(CORS の例と同様に)`app.add_middleware()` を使います。 + +```Python +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +`app.add_middleware()` は、最初の引数にミドルウェアのクラスを取り、それ以外の追加引数はミドルウェアに渡されます。 + +## 組み込みミドルウェア { #integrated-middlewares } + +**FastAPI** は一般的なユースケースに対応するいくつかのミドルウェアを含んでいます。以下でその使い方を見ていきます。 + +/// note | 技術詳細 + +以下の例では、`from starlette.middleware.something import SomethingMiddleware` を使うこともできます。 + +**FastAPI** は開発者であるあなたの便宜のために `fastapi.middleware` にいくつかのミドルウェアを提供しています。しかし、利用可能なミドルウェアの多くは Starlette から直接提供されています。 + +/// + +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } + +すべての受信リクエストが `https` または `wss` でなければならないように強制します。 + +`http` または `ws` への受信リクエストは、安全なスキームにリダイレクトされます。 + +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} + +## `TrustedHostMiddleware` { #trustedhostmiddleware } + +HTTP Host Header 攻撃を防ぐため、すべての受信リクエストに正しく設定された `Host` ヘッダーを強制します。 + +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} + +サポートされる引数は次のとおりです: + +- `allowed_hosts` - 許可するホスト名のドメイン名リスト。`*.example.com` のようなワイルドカードドメインでサブドメインのマッチングもサポートします。任意のホスト名を許可するには、`allowed_hosts=["*"]` を使うか、このミドルウェアを省略します。 +- `www_redirect` - True に設定すると、許可されたホストの非 www 版へのリクエストを www 版へリダイレクトします。デフォルトは `True` です。 + +受信リクエストが正しく検証されない場合、`400` のレスポンスが返されます。 + +## `GZipMiddleware` { #gzipmiddleware } + +`Accept-Encoding` ヘッダーに "gzip" を含むリクエストに対して GZip レスポンスを処理します。 + +このミドルウェアは、通常のレスポンスとストリーミングレスポンスの両方を処理します。 + +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} + +サポートされる引数は次のとおりです: + +- `minimum_size` - このバイト数の最小サイズ未満のレスポンスは GZip 圧縮しません。デフォルトは `500` です。 +- `compresslevel` - GZip 圧縮時に使用します。1 から 9 までの整数です。デフォルトは `9`。値が小さいほど圧縮は速くなりますがファイルサイズは大きくなり、値が大きいほど圧縮は遅くなりますがファイルサイズは小さくなります。 + +## その他のミドルウェア { #other-middlewares } + +他にも多くの ASGI ミドルウェアがあります。 + +例えば: + +- Uvicorn の `ProxyHeadersMiddleware` +- MessagePack + +他に利用可能なミドルウェアについては、Starlette のミドルウェアドキュメントASGI Awesome List を参照してください。 diff --git a/docs/ja/docs/advanced/openapi-callbacks.md b/docs/ja/docs/advanced/openapi-callbacks.md new file mode 100644 index 000000000..283a80b21 --- /dev/null +++ b/docs/ja/docs/advanced/openapi-callbacks.md @@ -0,0 +1,186 @@ +# OpenAPI コールバック { #openapi-callbacks } + +あなたは、*path operation* を持つ API を作成し、他者(多くの場合、あなたの API を「利用する」同一の開発者)が作成した *外部 API* へリクエストをトリガーできるようにできます。 + +あなたの API アプリが *外部 API* を呼び出すときに起きる処理は「コールバック」と呼ばれます。なぜなら、外部開発者が作成したソフトウェアがあなたの API にリクエストを送り、その後であなたの API が「呼び返し」、*外部 API*(おそらく同じ開発者が作成)へリクエストを送るためです。 + +この場合、その *外部 API* がどのようである「べき」かをドキュメント化したくなるでしょう。どんな *path operation* を持ち、どんなボディを受け取り、どんなレスポンスを返すか、などです。 + +## コールバックのあるアプリ { #an-app-with-callbacks } + +例で見ていきます。 + +あなたが請求書を作成できるアプリを開発していると想像してください。 + +これらの請求書は `id`、`title`(任意)、`customer`、`total` を持ちます。 + +あなたの API の利用者(外部開発者)は、POST リクエストであなたの API に請求書を作成します。 + +その後、あなたの API は(仮にこうしましょう): + +* 外部開発者の顧客に請求書を送ります。 +* 代金を回収します。 +* API 利用者(外部開発者)に通知を送り返します。 + * これは(あなたの API から)外部開発者が提供する *外部 API* に POST リクエストを送ることで行われます(これが「コールバック」です)。 + +## 通常の FastAPI アプリ { #the-normal-fastapi-app } + +まず、コールバックを追加する前の通常の API アプリがどうなるか見てみましょう。 + +`Invoice` ボディを受け取り、クエリパラメータ `callback_url` にコールバック用の URL を含める *path operation* を持ちます。 + +この部分はとても普通で、ほとんどのコードはすでに見覚えがあるはずです: + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} + +/// tip | 豆知識 + +`callback_url` クエリパラメータは、Pydantic の Url 型を使用します。 + +/// + +唯一の新しい点は、*path operation デコレータ*の引数として `callbacks=invoices_callback_router.routes` を渡すことです。これが何かは次で見ます。 + +## コールバックのドキュメント化 { #documenting-the-callback } + +実際のコールバックのコードは、あなた自身の API アプリに大きく依存します。 + +そしてアプリごとに大きく異なるでしょう。 + +それは次のように 1、2 行のコードかもしれません: + +```Python +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +しかし、おそらくコールバックで最も重要な点は、あなたの API 利用者(外部開発者)が、*あなたの API* がコールバックのリクエストボディなどで送るデータに従って、*外部 API* を正しく実装することを確実にすることです。 + +そこで次に行うのは、*あなたの API* からのコールバックを受け取るために、その *外部 API* がどうあるべきかをドキュメント化するコードを追加することです。 + +そのドキュメントはあなたの API の `/docs` の Swagger UI に表示され、外部開発者に *外部 API* の作り方を知らせます。 + +この例ではコールバック自体は実装しません(それは 1 行のコードでもよいでしょう)。ドキュメント部分のみです。 + +/// tip | 豆知識 + +実際のコールバックは単なる HTTP リクエストです。 + +自分でコールバックを実装する場合は、HTTPXRequests のようなものを使えます。 + +/// + +## コールバックのドキュメント用コードを書く { #write-the-callback-documentation-code } + +このコードはあなたのアプリで実行されません。*外部 API* がどうあるべきかをドキュメント化するためだけに必要です。 + +しかし、あなたはすでに **FastAPI** で API の自動ドキュメントを簡単に作る方法を知っています。 + +その知識を使って、*外部 API* がどうあるべきかをドキュメント化します……つまり、外部 API が実装すべき *path operation(s)*(あなたの API が呼び出すもの)を作成します。 + +/// tip | 豆知識 + +コールバックをドキュメント化するコードを書くときは、あなたがその「外部開発者」だと想像するのが役に立つかもしれません。いま実装しているのは「あなたの API」ではなく、*外部 API* です。 + +この(外部開発者の)視点を一時的に採用すると、その *外部 API* に対してパラメータ、ボディ用の Pydantic モデル、レスポンスなどをどこに置くのが自然かがより明確に感じられるでしょう。 + +/// + +### コールバック用 APIRouter を作成 { #create-a-callback-apirouter } + +まず、1 つ以上のコールバックを含む新しい `APIRouter` を作成します。 + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *} + +### コールバックの path operation を作成 { #create-the-callback-path-operation } + +上で作成したのと同じ `APIRouter` を使って、コールバックの *path operation* を作成します。 + +見た目は通常の FastAPI の *path operation* と同じです: + +* 受け取るボディの宣言(例: `body: InvoiceEvent`)が必要でしょう。 +* 返すレスポンスの宣言(例: `response_model=InvoiceEventReceived`)も持てます。 + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} + +通常の *path operation* と異なる主な点が 2 つあります: + +* 実際のコードは不要です。あなたのアプリはこのコードを決して呼びません。これは *外部 API* をドキュメント化するためだけに使われます。したがって、関数本体は `pass` で構いません。 +* *パス* には、*あなたの API* に送られた元のリクエストのパラメータや一部を変数として使える OpenAPI 3 の式(後述)を含められます。 + +### コールバックのパス式 { #the-callback-path-expression } + +コールバックの *パス* には、*あなたの API* に送られた元のリクエストの一部を含められる OpenAPI 3 の式を使用できます。 + +この例では、`str` は次のとおりです: + +```Python +"{$callback_url}/invoices/{$request.body.id}" +``` + +つまり、あなたの API 利用者(外部開発者)が *あなたの API* に次のようにリクエストを送った場合: + +``` +https://yourapi.com/invoices/?callback_url=https://www.external.org/events +``` + +JSON ボディは: + +```JSON +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +その後 *あなたの API* は請求書を処理し、のちほど `callback_url`(*外部 API*)へコールバックのリクエストを送ります: + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +JSON ボディは次のような内容です: + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +そして *外部 API* からは次のような JSON ボディのレスポンスを期待します: + +```JSON +{ + "ok": true +} +``` + +/// tip | 豆知識 + +使用されるコールバック URL には、クエリパラメータ `callback_url`(`https://www.external.org/events`)で受け取った URL と、JSON ボディ内の請求書 `id`(`2expen51ve`)が含まれている点に注目してください。 + +/// + +### コールバック用ルーターを追加 { #add-the-callback-router } + +これで、上で作成したコールバック用ルーター内に、必要なコールバックの *path operation(s)*(*外部開発者* が *外部 API* に実装すべきもの)が用意できました。 + +次に、*あなたの API の path operation デコレータ*の `callbacks` パラメータに、そのコールバック用ルーターの属性 `.routes`(実体はルート/*path operations* の `list`)を渡します: + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} + +/// tip | 豆知識 + +`callback=` に渡すのはルーター本体(`invoices_callback_router`)ではなく、属性 `.routes`(`invoices_callback_router.routes`)である点に注意してください。 + +/// + +### ドキュメントを確認 { #check-the-docs } + +アプリを起動して http://127.0.0.1:8000/docs にアクセスします。 + +あなたの *path operation* に「Callbacks」セクションが含まれ、*外部 API* がどうあるべきかが表示されているのが確認できます: + + diff --git a/docs/ja/docs/advanced/openapi-webhooks.md b/docs/ja/docs/advanced/openapi-webhooks.md new file mode 100644 index 000000000..368cfddd8 --- /dev/null +++ b/docs/ja/docs/advanced/openapi-webhooks.md @@ -0,0 +1,55 @@ +# OpenAPI の Webhook { #openapi-webhooks } + +アプリがある種の**イベント**を**通知**するために、データ付きで相手のアプリ(リクエスト送信)を呼び出す可能性があることを、API の**ユーザー**に伝えたい場合があります。 + +これは、通常のようにユーザーがあなたの API にリクエストを送るのではなく、**あなたの API(あなたのアプリ)**が**相手のシステム**(相手の API、アプリ)にリクエストを送る、ということです。 + +これは一般に**Webhook**と呼ばれます。 + +## Webhook の手順 { #webhooks-steps } + +通常の流れとして、まずあなたのコード内で、送信するメッセージ、すなわちリクエストの**本文(ボディ)**を**定義**します。 + +加えて、アプリがそれらのリクエスト(イベント)を送信する**タイミング**も何らかの形で定義します。 + +そして**ユーザー**は、アプリがそのリクエストを送るべき**URL**を(たとえばどこかの Web ダッシュボードで)定義します。 + +Webhook の URL を登録する方法や実際にリクエストを送るコードなど、これらの**ロジック**はすべてあなた次第です。**あなた自身のコード**で好きなように実装します。 + +## FastAPI と OpenAPI による Webhook のドキュメント化 { #documenting-webhooks-with-fastapi-and-openapi } + +**FastAPI** と OpenAPI を使うと、Webhook の名前、アプリが送信できる HTTP の操作(例: `POST`, `PUT` など)、アプリが送るリクエストの**ボディ**を定義できます。 + +これにより、ユーザーがあなたの **Webhook** リクエストを受け取るための**API を実装**するのが大幅に簡単になります。場合によっては、ユーザーが自分たちの API コードを自動生成できるかもしれません。 + +/// info | 情報 + +Webhook は OpenAPI 3.1.0 以上で利用可能で、FastAPI `0.99.0` 以上が対応しています。 + +/// + +## Webhook を持つアプリ { #an-app-with-webhooks } + +**FastAPI** アプリケーションを作成すると、`webhooks` という属性があり、ここで *path operations* と同様に(例: `@app.webhooks.post()`)*webhook* を定義できます。 + +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} + +定義した webhook は **OpenAPI** スキーマおよび自動生成される **ドキュメント UI** に反映されます。 + +/// info | 情報 + +`app.webhooks` オブジェクトは実際には単なる `APIRouter` で、複数ファイルでアプリを構成する際に使うものと同じ型です。 + +/// + +Webhook では(`/items/` のような)*パス*を宣言しているわけではない点に注意してください。ここで渡す文字列は webhook の**識別子**(イベント名)です。たとえば `@app.webhooks.post("new-subscription")` での webhook 名は `new-subscription` です。 + +これは、**ユーザー**が実際に Webhook リクエストを受け取りたい**URL パス**を、別の方法(例: Web ダッシュボード)で定義することを想定しているためです。 + +### ドキュメントの確認 { #check-the-docs } + +アプリを起動し、http://127.0.0.1:8000/docs にアクセスします。 + +ドキュメントには通常の *path operations* に加えて、**webhooks** も表示されます: + + diff --git a/docs/ja/docs/advanced/path-operation-advanced-configuration.md b/docs/ja/docs/advanced/path-operation-advanced-configuration.md index a78c3cb02..f7e340617 100644 --- a/docs/ja/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/ja/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ OpenAPIの「エキスパート」でなければ、これはおそらく必要 各オペレーションで一意になるようにする必要があります。 -{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} ### *path operation関数* の名前をoperationIdとして使用する { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ APIの関数名を `operationId` として利用したい場合、すべてのAP すべての *path operation* を追加した後に行うべきです。 -{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} /// tip | 豆知識 @@ -40,7 +40,7 @@ APIの関数名を `operationId` として利用したい場合、すべてのAP 生成されるOpenAPIスキーマ(つまり、自動ドキュメント生成の仕組み)から *path operation* を除外するには、`include_in_schema` パラメータを使用して `False` に設定します。 -{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} ## docstringによる説明の高度な設定 { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ OpenAPI仕様では parsed されず、直接 `bytes` として読み取られます。そして `magic_data_reader()` 関数が、何らかの方法でそれをパースする責務を担います。 +この例では、Pydanticモデルを一切宣言していません。実際、リクエストボディはJSONとして パース されず、直接 `bytes` として読み取られます。そして `magic_data_reader()` 関数が、何らかの方法でそれをパースする責務を担います。 それでも、リクエストボディに期待されるスキーマを宣言できます。 @@ -153,7 +153,7 @@ OpenAPI仕様では Starlette のドキュメントを参照してください。 diff --git a/docs/ja/docs/advanced/response-directly.md b/docs/ja/docs/advanced/response-directly.md index 7e83b9ffb..103e84c38 100644 --- a/docs/ja/docs/advanced/response-directly.md +++ b/docs/ja/docs/advanced/response-directly.md @@ -54,7 +54,7 @@ XMLを文字列にし、`Response` に含め、それを返します。 -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ## 備考 { #notes } diff --git a/docs/ja/docs/advanced/response-headers.md b/docs/ja/docs/advanced/response-headers.md new file mode 100644 index 000000000..10aec7845 --- /dev/null +++ b/docs/ja/docs/advanced/response-headers.md @@ -0,0 +1,41 @@ +# レスポンスヘッダー { #response-headers } + +## `Response` パラメータを使う { #use-a-response-parameter } + +(Cookie と同様に)*path operation 関数*で `Response` 型のパラメータを宣言できます。 + +そして、その*一時的*なレスポンスオブジェクトにヘッダーを設定できます。 + +{* ../../docs_src/response_headers/tutorial002_py310.py hl[1, 7:8] *} + +その後は通常どおり、必要な任意のオブジェクト(`dict`、データベースモデルなど)を返せます。 + +`response_model` を宣言している場合は、返したオブジェクトのフィルタと変換に引き続き使用されます。 + +**FastAPI** はその*一時的*なレスポンスからヘッダー(Cookie やステータスコードも含む)を取り出し、`response_model` によってフィルタされた返却値を含む最終的なレスポンスに反映します。 + +また、依存関係の中で `Response` パラメータを宣言し、その中でヘッダー(や Cookie)を設定することもできます。 + +## `Response` を直接返す { #return-a-response-directly } + +`Response` を直接返す場合にもヘッダーを追加できます。 + +[Response を直接返す](response-directly.md){.internal-link target=_blank} で説明したようにレスポンスを作成し、ヘッダーを追加のパラメータとして渡します: + +{* ../../docs_src/response_headers/tutorial001_py310.py hl[10:12] *} + +/// note | 技術詳細 + +`from starlette.responses import Response` や `from starlette.responses import JSONResponse` を使うこともできます。 + +**FastAPI** は、開発者であるあなたへの便宜として、`starlette.responses` と同じものを `fastapi.responses` として提供しています。しかし、利用可能なレスポンスの大半は直接 Starlette から来ています。 + +また、`Response` はヘッダーや Cookie を設定するのによく使われるため、**FastAPI** は `fastapi.Response` でも提供しています。 + +/// + +## カスタムヘッダー { #custom-headers } + +独自のカスタムヘッダーは、`X-` プレフィックスを使って追加できることに注意してください。 + +ただし、ブラウザのクライアントに見えるようにしたいカスタムヘッダーがある場合は、CORS 設定にそれらを追加する必要があります([CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank} を参照)。このとき、Starlette の CORS ドキュメントに記載の `expose_headers` パラメータを使用します。 diff --git a/docs/ja/docs/advanced/security/http-basic-auth.md b/docs/ja/docs/advanced/security/http-basic-auth.md new file mode 100644 index 000000000..7ee56a039 --- /dev/null +++ b/docs/ja/docs/advanced/security/http-basic-auth.md @@ -0,0 +1,107 @@ +# HTTP Basic 認証 { #http-basic-auth } + +最もシンプルなケースでは、HTTP Basic 認証を利用できます。 + +HTTP Basic 認証では、アプリケーションはユーザー名とパスワードを含むヘッダーを期待します。 + +それを受け取れない場合、HTTP 401 "Unauthorized" エラーを返します。 + +そして、値が `Basic` のヘッダー `WWW-Authenticate` を、任意の `realm` パラメータとともに返します。 + +これにより、ブラウザは組み込みのユーザー名とパスワード入力プロンプトを表示します。 + +その後、そのユーザー名とパスワードを入力すると、ブラウザはそれらをヘッダーに自動的に付与して送信します。 + +## シンプルな HTTP Basic 認証 { #simple-http-basic-auth } + +- `HTTPBasic` と `HTTPBasicCredentials` をインポートします。 +- `HTTPBasic` を使って「`security` スキーム」を作成します。 +- その `security` を依存関係として path operation に使用します。 +- `HTTPBasicCredentials` 型のオブジェクトが返ります: + - 送信された `username` と `password` を含みます。 + +{* ../../docs_src/security/tutorial006_an_py310.py hl[4,8,12] *} + +URL を最初に開こうとしたとき(またはドキュメントで「Execute」ボタンをクリックしたとき)、ブラウザはユーザー名とパスワードの入力を求めます: + + + +## ユーザー名の確認 { #check-the-username } + +より完全な例です。 + +依存関係を使ってユーザー名とパスワードが正しいかを確認します。 + +これには、Python 標準モジュール `secrets` を用いてユーザー名とパスワードを検証します。 + +`secrets.compare_digest()` は `bytes` か、ASCII 文字(英語の文字)のみを含む `str` を受け取る必要があります。つまり、`Sebastián` のように `á` を含む文字ではそのままでは動作しません。 + +これに対処するため、まず `username` と `password` を UTF-8 でエンコードして `bytes` に変換します。 + +そのうえで、`secrets.compare_digest()` を使って、`credentials.username` が `"stanleyjobson"` であり、`credentials.password` が `"swordfish"` であることを確認します。 + +{* ../../docs_src/security/tutorial007_an_py310.py hl[1,12:24] *} + +これは次のようなコードに相当します: + +```Python +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # Return some error + ... +``` + +しかし `secrets.compare_digest()` を使うことで、「タイミング攻撃」と呼ばれる種類の攻撃に対して安全になります。 + +### タイミング攻撃 { #timing-attacks } + +「タイミング攻撃」とは何でしょうか? + +攻撃者がユーザー名とパスワードを推測しようとしていると想像してください。 + +そして、ユーザー名 `johndoe`、パスワード `love123` を使ってリクエストを送ります。 + +その場合、アプリケーション内の Python コードは次のようなものと等価になります: + +```Python +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +しかし、Python は `johndoe` の最初の `j` と `stanleyjobson` の最初の `s` を比較した時点で、両者の文字列が同じでないと判断してすぐに `False` を返します。つまり「残りの文字を比較して計算資源を無駄にする必要はない」と考えるわけです。そしてアプリケーションは「ユーザー名またはパスワードが正しくありません」と返します。 + +次に、攻撃者がユーザー名 `stanleyjobsox`、パスワード `love123` で試すとします。 + +アプリケーションのコードは次のようになります: + +```Python +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +この場合、Python は `stanleyjobsox` と `stanleyjobson` の両方で `stanleyjobso` 全体を比較してから、文字列が同じでないと気づきます。したがって、「ユーザー名またはパスワードが正しくありません」と応答するまでに余分に数マイクロ秒かかります。 + +#### 応答時間が攻撃者を助ける { #the-time-to-answer-helps-the-attackers } + +ここで、サーバーが「ユーザー名またはパスワードが正しくありません」というレスポンスを返すまでに、わずかに長い時間がかかったことに気づけば、攻撃者は何かしら正解に近づいた、すなわち先頭のいくつかの文字が正しかったことを知ることができます。 + +すると、`johndoe` よりも `stanleyjobsox` に近いものを狙って再試行できます。 + +#### 「プロ」レベルの攻撃 { #a-professional-attack } + +もちろん、攻撃者はこれらを手作業では行わず、プログラムを書いて、1 秒間に数千〜数百万回のテストを行うでしょう。そして 1 回に 1 文字ずつ正しい文字を見つけていきます。 + +そうすることで、数分から数時間のうちに、攻撃者は私たちのアプリケーションの「助け」(応答にかかった時間)だけを利用して、正しいユーザー名とパスワードを推測できてしまいます。 + +#### `secrets.compare_digest()` で対策 { #fix-it-with-secrets-compare-digest } + +しかし、私たちのコードでは実際に `secrets.compare_digest()` を使用しています。 + +要するに、`stanleyjobsox` と `stanleyjobson` を比較するのにかかる時間は、`johndoe` と `stanleyjobson` を比較するのにかかる時間と同じになります。パスワードでも同様です。 + +このように、アプリケーションコードで `secrets.compare_digest()` を使うと、この種の一連のセキュリティ攻撃に対して安全になります。 + +### エラーを返す { #return-the-error } + +認証情報が不正であることを検出したら、ステータスコード 401(認証情報が提供されない場合と同じ)で `HTTPException` を返し、ブラウザに再度ログインプロンプトを表示させるためにヘッダー `WWW-Authenticate` を追加します: + +{* ../../docs_src/security/tutorial007_an_py310.py hl[26:30] *} diff --git a/docs/ja/docs/advanced/security/index.md b/docs/ja/docs/advanced/security/index.md new file mode 100644 index 000000000..069e2686c --- /dev/null +++ b/docs/ja/docs/advanced/security/index.md @@ -0,0 +1,19 @@ +# 高度なセキュリティ { #advanced-security } + +## 追加機能 { #additional-features } + +[チュートリアル - ユーザーガイド: セキュリティ](../../tutorial/security/index.md){.internal-link target=_blank}で扱ったもの以外にも、セキュリティを扱うための追加機能がいくつかあります。 + +/// tip | 豆知識 + +次の節は必ずしも「高度」ではありません。 + +あなたのユースケースでは、その中のいずれかに解決策があるかもしれません。 + +/// + +## まずチュートリアルを読む { #read-the-tutorial-first } + +以下の節は、すでにメインの[チュートリアル - ユーザーガイド: セキュリティ](../../tutorial/security/index.md){.internal-link target=_blank}を読んでいることを前提とします。 + +いずれも同じ概念に基づいていますが、いくつかの追加機能を利用できます。 diff --git a/docs/ja/docs/advanced/security/oauth2-scopes.md b/docs/ja/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 000000000..7c6cfdbf0 --- /dev/null +++ b/docs/ja/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,274 @@ +# OAuth2 のスコープ { #oauth2-scopes } + +OAuth2 のスコープは **FastAPI** で直接利用でき、シームレスに統合されています。 + +これにより、OAuth2 標準に従った、よりきめ細かな権限システムを、OpenAPI 対応アプリケーション(および API ドキュメント)に統合できます。 + +スコープ付きの OAuth2 は、Facebook、Google、GitHub、Microsoft、X (Twitter) など、多くの大手認証プロバイダで使われている仕組みです。ユーザーやアプリケーションに特定の権限を付与するために利用されます。 + +「Facebook でログイン」「Google でログイン」「GitHub でログイン」「Microsoft でログイン」「X (Twitter) でログイン」するたびに、そのアプリケーションはスコープ付きの OAuth2 を使っています。 + +この節では、同じスコープ付き OAuth2 を使って、**FastAPI** アプリケーションで認証と認可を管理する方法を見ていきます。 + +/// warning | 注意 + +これはやや高度な内容です。はじめたばかりであれば読み飛ばしても構いません。 + +OAuth2 のスコープは必ずしも必要ではなく、認証と認可は好きなやり方で実装できます。 + +ただし、スコープ付きの OAuth2 は、API(OpenAPI)や API ドキュメントにきれいに統合できます。 + +とはいえ、これらのスコープやその他のセキュリティ/認可要件の適用は、必要に応じてコードの中で行う必要があります。 + +多くの場合、スコープ付き OAuth2 はオーバースペックになりえます。 + +それでも必要だと分かっている場合や、興味がある場合は、このまま読み進めてください。 + +/// + +## OAuth2 のスコープと OpenAPI { #oauth2-scopes-and-openapi } + +OAuth2 仕様では、「スコープ」は空白で区切られた文字列の一覧として定義されています。 + +各文字列の内容は任意ですが、空白は含められません。 + +これらのスコープは「権限」を表します。 + +OpenAPI(例: API ドキュメント)では、「セキュリティスキーム」を定義できます。 + +これらのセキュリティスキームの一つが OAuth2 を使う場合、スコープを宣言して利用できます。 + +各「スコープ」は、ただの文字列(空白なし)です。 + +通常、特定のセキュリティ権限を宣言するために使われます。例えば: + +- `users:read` や `users:write` は一般的な例です。 +- `instagram_basic` は Facebook / Instagram で使われています。 +- `https://www.googleapis.com/auth/drive` は Google で使われています。 + +/// info | 情報 + +OAuth2 において「スコープ」は、必要な特定の権限を宣言する単なる文字列です。 + +`:` のような他の文字が含まれていても、URL であっても問題ありません。 + +それらの詳細は実装依存です。 + +OAuth2 にとっては、単に文字列に過ぎません。 + +/// + +## 全体像 { #global-view } + +まず、メインの**チュートリアル - ユーザーガイド**にある [OAuth2(パスワード[ハッシュ化あり])、Bearer と JWT トークン](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank} の例から変更される部分を、スコープ付き OAuth2 を使って手早く見てみましょう。 + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} + +では、これらの変更を一つずつ確認していきます。 + +## OAuth2 のセキュリティスキーム { #oauth2-security-scheme } + +最初の変更点は、`me` と `items` の 2 つのスコープを持つ OAuth2 セキュリティスキームを宣言していることです。 + +`scopes` パラメータは、各スコープをキー、その説明を値とする `dict` を受け取ります: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} + +これらのスコープを宣言しているため、ログイン/認可時に API ドキュメントに表示されます。 + +そして、付与するスコープ(`me`、`items`)を選択できます。 + +これは、Facebook、Google、GitHub などでログイン時に権限を付与する際と同じ仕組みです: + + + +## スコープ付きの JWT トークン { #jwt-token-with-scopes } + +次に、トークンの path operation を修正して、要求されたスコープを返すようにします。 + +引き続き同じ `OAuth2PasswordRequestForm` を使用します。これには、リクエストで受け取った各スコープを含む、`str` の `list` である `scopes` プロパティが含まれます。 + +そして、そのスコープを JWT トークンの一部として返します。 + +/// danger | 警告 + +簡単のため、ここでは受け取ったスコープをそのままトークンに追加しています。 + +しかし、本番アプリケーションではセキュリティのため、ユーザーが実際に持つことができるスコープ、または事前に定義したスコープだけを追加するようにしてください。 + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} + +## path operation と依存関係でスコープを宣言 { #declare-scopes-in-path-operations-and-dependencies } + +ここでは、`/users/me/items/` の path operation が `items` スコープを必要とするように宣言します。 + +そのために、`fastapi` から `Security` をインポートして使います。 + +`Security` は(`Depends` と同様に)依存関係を宣言できますが、さらにスコープ(文字列)のリストを受け取る `scopes` パラメータも持ちます。 + +この場合、`Security` に依存関数 `get_current_active_user` を渡します(`Depends` と同様です)。 + +加えて、`items` という 1 つのスコープ(複数でも可)を含む `list` も渡します。 + +依存関数 `get_current_active_user` は、`Depends` だけでなく `Security` でもサブ依存関係を宣言できます。自身のサブ依存関数(`get_current_user`)を宣言し、さらにスコープ要件を追加します。 + +この場合、`me` スコープを要求します(複数のスコープも可)。 + +/// note | 備考 + +異なる場所で異なるスコープを追加する必要は必ずしもありません。 + +ここでは、**FastAPI** が異なるレベルで宣言されたスコープをどのように扱うかを示すためにそうしています。 + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} + +/// info | 技術詳細 + +`Security` は実際には `Depends` のサブクラスで、後述する追加パラメータが 1 つあるだけです。 + +しかし `Depends` の代わりに `Security` を使うことで、**FastAPI** はセキュリティスコープを宣言・内部利用でき、OpenAPI で API をドキュメント化できると判断します。 + +なお、`fastapi` から `Query`、`Path`、`Depends`、`Security` などをインポートする際、それらは実際には特殊なクラスを返す関数です。 + +/// + +## `SecurityScopes` を使う { #use-securityscopes } + +次に、依存関数 `get_current_user` を更新します。 + +これは上記の依存関係から使用されます。 + +ここで、先ほど作成した同じ OAuth2 スキームを依存関係(`oauth2_scheme`)として宣言して使います。 + +この依存関数自体はスコープ要件を持たないため、`oauth2_scheme` には `Depends` を使えます。セキュリティスコープを指定する必要がない場合は `Security` を使う必要はありません。 + +さらに、`fastapi.security` からインポートする特別な型 `SecurityScopes` のパラメータを宣言します。 + +この `SecurityScopes` クラスは `Request` に似ています(`Request` はリクエストオブジェクトを直接取得するために使いました)。 + +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} + +## `scopes` を使う { #use-the-scopes } + +パラメータ `security_scopes` は `SecurityScopes` 型になります。 + +このオブジェクトは、自身およびこれをサブ依存として使うすべての依存関係で要求されるスコープを含む `scopes` プロパティ(リスト)を持ちます。つまり、すべての「依存元」... 少し分かりにくいかもしれませんが、後で再度説明します。 + +`security_scopes`(`SecurityScopes` クラスのインスタンス)は、要求されたスコープを空白で連結した 1 つの文字列を返す `scope_str` も提供します(これを使います)。 + +後で複数箇所で再利用(raise)できるように、`HTTPException` を 1 つ作成します。 + +この例外には、要求されたスコープがあればそれらを空白区切りの文字列(`scope_str` を使用)として含めます。このスコープ文字列は `WWW-Authenticate` ヘッダに入れます(仕様の一部です)。 + +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} + +## `username` とデータ構造の検証 { #verify-the-username-and-data-shape } + +`username` を取得できていることを確認し、スコープを取り出します。 + +そして、そのデータを Pydantic モデルで検証します(`ValidationError` 例外を捕捉)。JWT トークンの読み取りや Pydantic によるデータ検証でエラーが発生した場合は、先ほど作成した `HTTPException` を送出します。 + +そのために、Pydantic モデル `TokenData` に新しいプロパティ `scopes` を追加します。 + +Pydantic でデータを検証することで、例えばスコープは `str` の `list`、`username` は `str` といった、正確な型になっていることを保証できます。 + +そうしておけば、例えば誤って `dict` などが入って後でアプリケーションを破壊してしまい、セキュリティリスクになる、といった事態を避けられます。 + +また、その `username` を持つユーザーが存在することも確認し、存在しなければ、やはり先ほどの例外を送出します。 + +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} + +## `scopes` の検証 { #verify-the-scopes } + +この依存関数およびすべての依存元(path operation を含む)が要求するすべてのスコープが、受け取ったトークンに含まれていることを検証し、含まれていなければ `HTTPException` を送出します。 + +そのために、これらすべてのスコープを `str` の `list` として含む `security_scopes.scopes` を使います。 + +{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} + +## 依存関係ツリーとスコープ { #dependency-tree-and-scopes } + +依存関係ツリーとスコープをもう一度見てみましょう。 + +`get_current_active_user` 依存関係は `get_current_user` をサブ依存として持つため、`get_current_active_user` で宣言された `"me"` スコープは、`get_current_user` に渡される `security_scopes.scopes` の必須スコープ一覧に含まれます。 + +path operation 自体も `"items"` スコープを宣言するため、これも `get_current_user` に渡される `security_scopes.scopes` に含まれます。 + +依存関係とスコープの階層は次のようになります: + +- *path operation* `read_own_items` には: + - 依存関係に対して必須スコープ `["items"]` がある: + - `get_current_active_user`: + - 依存関数 `get_current_active_user` には: + - 依存関係に対して必須スコープ `["me"]` がある: + - `get_current_user`: + - 依存関数 `get_current_user` には: + - 自身に必須スコープはない。 + - `oauth2_scheme` を使う依存関係がある。 + - `SecurityScopes` 型の `security_scopes` パラメータがある: + - この `security_scopes` パラメータは、上で宣言されたすべてのスコープを含む `list` を持つ `scopes` プロパティを持つ。したがって: + - *path operation* `read_own_items` では、`security_scopes.scopes` は `["me", "items"]` を含む。 + - *path operation* `read_users_me` では、`security_scopes.scopes` は `["me"]` を含む。これは依存関係 `get_current_active_user` に宣言されているため。 + - *path operation* `read_system_status` では、`security_scopes.scopes` は `[]`(空)になる。`scopes` を持つ `Security` を宣言しておらず、その依存関係 `get_current_user` も `scopes` を宣言していないため。 + +/// tip | 豆知識 + +重要で「魔法のよう」な点は、`get_current_user` が path operation ごとに異なる `scopes` のリストをチェックすることになる、ということです。 + +それは、それぞれの path operation と、その path operation の依存関係ツリー内の各依存関係で宣言された `scopes` によって決まります。 + +/// + +## `SecurityScopes` の詳細 { #more-details-about-securityscopes } + +`SecurityScopes` はどの地点でも、複数箇所でも使えます。「ルート」の依存関係である必要はありません。 + +常に、その時点の `Security` 依存関係と、**その特定の** path operation と **その特定の** 依存関係ツリーにおける、すべての依存元で宣言されたセキュリティスコープを持ちます。 + +`SecurityScopes` には依存元で宣言されたすべてのスコープが入るため、トークンが必要なスコープを持っているかどうかを中央の依存関数で検証し、path operation ごとに異なるスコープ要件を宣言する、といった使い方ができます。 + +これらは path operation ごとに独立して検証されます。 + +## チェック { #check-it } + +API ドキュメントを開くと、認証して、許可するスコープを指定できます。 + + + +どのスコープも選択しない場合は「認証済み」にはなりますが、`/users/me/` や `/users/me/items/` にアクセスしようとすると、権限が不足しているというエラーになります。`/status/` には引き続きアクセスできます。 + +`me` スコープだけを選択し、`items` スコープを選択しない場合は、`/users/me/` にはアクセスできますが、`/users/me/items/` にはアクセスできません。 + +これは、ユーザーがアプリケーションに与えた権限の範囲に応じて、サードパーティアプリケーションがこれらの path operation のいずれかに、ユーザーから提供されたトークンでアクセスしようとしたときに起こる動作です。 + +## サードパーティ統合について { #about-third-party-integrations } + +この例では、OAuth2 の「password」フローを使用しています。 + +これは、(おそらく自前のフロントエンドで)自分たちのアプリケーションにログインする場合に適しています。 + +自分たちで管理しているため、`username` と `password` を受け取る相手を信頼できるからです。 + +しかし、他者が接続する OAuth2 アプリケーション(Facebook、Google、GitHub などに相当する認証プロバイダ)を構築する場合は、他のいずれかのフローを使用すべきです。 + +最も一般的なのは implicit フローです。 + +最も安全なのは code フローですが、手順が多く実装がより複雑です。複雑なため、多くのプロバイダは結局 implicit フローを推奨することがあります。 + +/// note | 備考 + +各認証プロバイダがフローに独自の名称を付け、自社のブランドの一部にするのは一般的です。 + +しかし、最終的には同じ OAuth2 標準を実装しています。 + +/// + +**FastAPI** には、これらすべての OAuth2 認証フロー向けのユーティリティが `fastapi.security.oauth2` に含まれています。 + +## デコレータ `dependencies` での `Security` { #security-in-decorator-dependencies } + +デコレータの `dependencies` パラメータに `Depends` の `list` を定義できるのと同様([path operation デコレータでの依存関係](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 参照)、ここで `scopes` を指定した `Security` も使用できます。 diff --git a/docs/ja/docs/advanced/settings.md b/docs/ja/docs/advanced/settings.md new file mode 100644 index 000000000..5508ad6d9 --- /dev/null +++ b/docs/ja/docs/advanced/settings.md @@ -0,0 +1,302 @@ +# 設定と環境変数 { #settings-and-environment-variables } + +多くの場合、アプリケーションは外部の設定や構成を必要とします。たとえば、シークレットキー、データベース認証情報、メールサービスの認証情報などです。 + +これらの設定の多くは可変(変更されうる)で、データベースのURLのようなものがあります。また、多くはシークレットのように機微な情報です。 + +そのため、アプリケーションが読み取る環境変数で提供するのが一般的です。 + +/// tip | 豆知識 + +環境変数について理解するには、[環境変数](../environment-variables.md){.internal-link target=_blank}を参照してください。 + +/// + +## 型とバリデーション { #types-and-validation } + +これらの環境変数は Python の外部にあり、他のプログラムやシステム全体(Linux、Windows、macOS といった異なるOSを含む)と互換性が必要なため、文字列テキストのみを扱えます。 + +つまり、Python で環境変数から読み取られる値はすべて `str` になり、他の型への変換やバリデーションはコードで行う必要があります。 + +## Pydantic の `Settings` { #pydantic-settings } + +幸いなことに、Pydantic には環境変数から来る設定を扱うための優れたユーティリティがあり、Pydantic: Settings management で提供されています。 + +### `pydantic-settings` のインストール { #install-pydantic-settings } + +まず、[仮想環境](../virtual-environments.md){.internal-link target=_blank}を作成して有効化し、`pydantic-settings` パッケージをインストールします: + +
+ +```console +$ pip install pydantic-settings +---> 100% +``` + +
+ +また、次のように `all` エクストラをインストールすると付属します: + +
+ +```console +$ pip install "fastapi[all]" +---> 100% +``` + +
+ +### `Settings` オブジェクトを作成 { #create-the-settings-object } + +Pydantic から `BaseSettings` をインポートして、そのサブクラスを作成します。これは Pydantic モデルとほぼ同じです。 + +Pydantic モデルと同様に、型アノテーションと(必要なら)デフォルト値を持つクラス属性を宣言します。 + +`Field()` による追加バリデーションなど、Pydantic モデルで使えるのと同じバリデーション機能をすべて利用できます。 + +{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *} + +/// tip | 豆知識 + +コピペ用に手早く使いたい場合は、この例ではなく、下の最後の例を使ってください。 + +/// + +その後、その `Settings` クラスのインスタンス(この例では `settings` オブジェクト)を作成すると、Pydantic は環境変数を大文字小文字を区別せずに読み取ります。つまり、大文字の `APP_NAME` という変数は、属性 `app_name` に対しても読み取られます。 + +次に、データが変換・バリデーションされます。したがって、`settings` オブジェクトを使うと、宣言した型のデータ(例: `items_per_user` は `int`)が得られます。 + +### `settings` の使用 { #use-the-settings } + +次に、アプリケーションで新しい `settings` オブジェクトを使用できます: + +{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *} + +### サーバーを実行 { #run-the-server } + +次に、設定を環境変数として渡してサーバーを実行します。たとえば、`ADMIN_EMAIL` と `APP_NAME` を次のように設定できます: + +
+ +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +/// tip | 豆知識 + +1つのコマンドに複数の環境変数を設定するには、スペースで区切ってコマンドの前に並べます。 + +/// + +すると、`admin_email` の設定は `"deadpool@example.com"` に設定されます。 + +`app_name` は `"ChimichangApp"` になります。 + +`items_per_user` はデフォルト値の `50` のままです。 + +## 別モジュールでの設定 { #settings-in-another-module } + +[大規模アプリケーション - 複数ファイル](../tutorial/bigger-applications.md){.internal-link target=_blank} で見たように、これらの設定を別のモジュールファイルに置くこともできます。 + +たとえば、`config.py` というファイルに次のように書けます: + +{* ../../docs_src/settings/app01_py310/config.py *} + +そして、`main.py` というファイルでそれを使います: + +{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *} + +/// tip | 豆知識 + +[大規模アプリケーション - 複数ファイル](../tutorial/bigger-applications.md){.internal-link target=_blank} で見たように、`__init__.py` ファイルも必要です。 + +/// + +## 依存関係での設定 { #settings-in-a-dependency } + +場合によっては、どこでも使うグローバルな `settings` オブジェクトを持つ代わりに、依存関係から設定を提供すると便利なことがあります。 + +これは特にテスト時に有用で、依存関係を独自のカスタム設定で簡単にオーバーライドできるからです。 + +### 設定ファイル { #the-config-file } + +前の例から続けると、`config.py` ファイルは次のようになります: + +{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *} + +ここでは、デフォルトのインスタンス `settings = Settings()` を作成していないことに注意してください。 + +### メインアプリファイル { #the-main-app-file } + +ここでは、新しい `config.Settings()` を返す依存関係を作成します。 + +{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *} + +/// tip | 豆知識 + +`@lru_cache` については後で説明します。 + +今は `get_settings()` が普通の関数だと考えてください。 + +/// + +そして、*path operation 関数*から依存関係として要求し、必要な場所でどこでも使えます。 + +{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *} + +### 設定とテスト { #settings-and-testing } + +次に、`get_settings` の依存関係オーバーライドを作ることで、テスト中に別の設定オブジェクトを提供するのがとても簡単になります: + +{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *} + +依存関係オーバーライドでは、新しい `Settings` オブジェクトを作る際に `admin_email` に新しい値を設定し、その新しいオブジェクトを返します。 + +そして、それが使用されていることをテストできます。 + +## `.env` ファイルの読み込み { #reading-a-env-file } + +変更が多くなりそうな設定が多数ある場合、環境ごとにファイルに入れて、環境変数としてそこから読み込むと便利なことがあります。 + +このプラクティスは十分に一般的で名前もあり、これらの環境変数は通常 `.env` というファイルに置かれ、そのファイルは「dotenv」と呼ばれます。 + +/// tip | 豆知識 + +ドット(`.`)で始まるファイルは、Linux や macOS のような Unix 系システムでは隠しファイルです。 + +ただし、dotenv ファイルは必ずしもその正確なファイル名である必要はありません。 + +/// + +Pydantic は外部ライブラリを使ってこの種のファイルからの読み込みをサポートしています。詳細は Pydantic Settings: Dotenv (.env) support を参照してください。 + +/// tip | 豆知識 + +これを機能させるには、`pip install python-dotenv` が必要です。 + +/// + +### `.env` ファイル { #the-env-file } + +次のような `.env` ファイルを用意できます: + +```bash +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +### `.env` から設定を読む { #read-settings-from-env } + +そして、`config.py` を次のように更新します: + +{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *} + +/// tip | 豆知識 + +`model_config` 属性は Pydantic の設定専用です。詳しくは Pydantic: Concepts: Configuration を参照してください。 + +/// + +ここでは、Pydantic の `Settings` クラス内で設定 `env_file` を定義し、使用したい dotenv ファイルのファイル名を指定しています。 + +### `lru_cache` で `Settings` を一度だけ作成 { #creating-the-settings-only-once-with-lru-cache } + +ディスクからファイルを読むのは通常コスト(遅延)が高い処理なので、1回だけ実行して同じ設定オブジェクトを再利用し、各リクエストごとに読み直さないのが望ましいです。 + +しかし、次のようにするたびに: + +```Python +Settings() +``` + +新しい `Settings` オブジェクトが作成され、その作成時に `.env` ファイルが再度読み込まれます。 + +依存関数が次のようであれば: + +```Python +def get_settings(): + return Settings() +``` + +各リクエストごとにそのオブジェクトを作成し、各リクエストごとに `.env` ファイルを読み込むことになります。⚠️ + +しかし、上に `@lru_cache` デコレータを使っているので、`Settings` オブジェクトは最初に呼び出されたときに一度だけ作成されます。✔️ + +{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *} + +その後のリクエスト用の依存関係で `get_settings()` が呼ばれるたびに、`get_settings()` の内部コードを実行して新しい `Settings` オブジェクトを作るのではなく、最初の呼び出しで返されたのと同じオブジェクトを何度でも返します。 + +#### `lru_cache` の技術詳細 { #lru-cache-technical-details } + +`@lru_cache` は、毎回関数のコードを実行して再計算するのではなく、最初に返した値を返すように、修飾する関数を変更します。 + +したがって、その下の関数は引数の組み合わせごとに一度だけ実行されます。そして、その各引数の組み合わせで返された値は、まったく同じ引数の組み合わせで呼び出されたときに何度でも再利用されます。 + +たとえば、次のような関数があるとします: + +```Python +@lru_cache +def say_hi(name: str, salutation: str = "Ms."): + return f"Hello {salutation} {name}" +``` + +プログラムは次のように実行されます: + +```mermaid +sequenceDiagram + +participant code as Code +participant function as say_hi() +participant execute as Execute function + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Camila") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick", salutation="Mr.") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: return stored result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end +``` + +今回の依存関数 `get_settings()` の場合、関数は引数を一切取りません。そのため、常に同じ値を返します。 + +この方法は、ほとんどグローバル変数のように振る舞います。しかし、依存関数を使っているので、テストのために簡単にオーバーライドできます。 + +`@lru_cache` は Python 標準ライブラリの `functools` の一部です。詳細は Python の `@lru_cache` ドキュメントを参照してください。 + +## まとめ { #recap } + +Pydantic Settings を使うことで、アプリケーションの設定や構成を、Pydantic モデルの力を活かして扱えます。 + +* 依存関係を使うことで、テストを簡素化できます。 +* `.env` ファイルを利用できます。 +* `@lru_cache` を使うと、各リクエストごとに dotenv ファイルを繰り返し読み込むのを避けつつ、テスト時にはオーバーライドできます。 diff --git a/docs/ja/docs/advanced/sub-applications.md b/docs/ja/docs/advanced/sub-applications.md new file mode 100644 index 000000000..f38da1423 --- /dev/null +++ b/docs/ja/docs/advanced/sub-applications.md @@ -0,0 +1,67 @@ +# サブアプリケーション - マウント { #sub-applications-mounts } + +それぞれ独立した OpenAPI とドキュメント UI を持つ2つの独立した FastAPI アプリケーションが必要な場合、メインアプリに1つ(以上)のサブアプリケーションを「マウント」できます。 + +## FastAPI アプリケーションのマウント { #mounting-a-fastapi-application } + +「マウント」とは、特定のパスに完全に「独立した」アプリケーションを追加し、そのサブアプリケーションで宣言された path operation によって、そのパス以下のすべてを処理させることを意味します。 + +### トップレベルアプリケーション { #top-level-application } + +まず、メインのトップレベル **FastAPI** アプリケーションと、その path operation を作成します: + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *} + +### サブアプリケーション { #sub-application } + +次に、サブアプリケーションとその path operation を作成します。 + +このサブアプリケーションは通常の FastAPI アプリケーションですが、これを「マウント」します: + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *} + +### サブアプリケーションをマウント { #mount-the-sub-application } + +トップレベルのアプリケーション `app` に、サブアプリケーション `subapi` をマウントします。 + +この例では、パス `/subapi` にマウントされます: + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *} + +### 自動 API ドキュメントの確認 { #check-the-automatic-api-docs } + +では、`fastapi` コマンドでこのファイルを実行します: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +そして、http://127.0.0.1:8000/docs を開きます。 + +メインアプリ用の自動 API ドキュメントが表示され、そのアプリ自身の path operation のみが含まれます: + + + +次に、サブアプリケーションのドキュメント http://127.0.0.1:8000/subapi/docs を開きます。 + +サブアプリケーション用の自動 API ドキュメントが表示され、そのアプリ自身の path operation のみが、正しいサブパス接頭辞 `/subapi` の下で表示されます: + + + +どちらの UI でも操作すれば正しく動作します。ブラウザがそれぞれのアプリ/サブアプリと通信できるためです。 + +### 技術詳細: `root_path` { #technical-details-root-path } + +上記のようにサブアプリケーションをマウントすると、FastAPI は ASGI 仕様の `root_path` と呼ばれる仕組みを使って、そのサブアプリケーションへのマウントパスを伝播します。 + +このため、サブアプリケーションはドキュメント UI でそのパス接頭辞を使用すべきことを認識できます。 + +さらに、サブアプリケーション自身が別のサブアプリケーションをマウントしていても問題ありません。FastAPI がこれらの `root_path` をすべて自動的に処理するためです。 + +`root_path` の詳細や明示的な指定方法については、[プロキシの背後で](behind-a-proxy.md){.internal-link target=_blank} の節で学べます。 diff --git a/docs/ja/docs/advanced/templates.md b/docs/ja/docs/advanced/templates.md new file mode 100644 index 000000000..3c4827b88 --- /dev/null +++ b/docs/ja/docs/advanced/templates.md @@ -0,0 +1,126 @@ +# テンプレート { #templates } + +**FastAPI** では任意のテンプレートエンジンを使用できます。 + +Flask などでも使われている Jinja2 が一般的な選択肢です。 + +Starlette によって提供され、**FastAPI** アプリで直接使える、簡単に設定できるユーティリティがあります。 + +## 依存関係のインストール { #install-dependencies } + +[仮想環境](../virtual-environments.md){.internal-link target=_blank} を作成して有効化し、`jinja2` をインストールします: + +
+ +```console +$ pip install jinja2 + +---> 100% +``` + +
+ +## `Jinja2Templates` の使用 { #using-jinja2templates } + +* `Jinja2Templates` をインポートします。 +* 後で再利用できる `templates` オブジェクトを作成します。 +* テンプレートを返す path operation に `Request` パラメータを宣言します。 +* 作成した `templates` を使って `TemplateResponse` をレンダリングして返します。テンプレート名、リクエストオブジェクト、Jinja2 テンプレート内で使用するキーと値のペアからなる "context" の辞書を渡します。 + +{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *} + +/// note | 備考 + +FastAPI 0.108.0、Starlette 0.29.0 以前では、`name` は最初のパラメータでした。 + +またそれ以前のバージョンでは、`request` オブジェクトは Jinja2 用のコンテキスト内のキーと値のペアの一部として渡されていました。 + +/// + +/// tip | 豆知識 + +`response_class=HTMLResponse` を宣言すると、ドキュメント UI がレスポンスが HTML であることを認識できます。 + +/// + +/// note | 技術詳細 + +`from starlette.templating import Jinja2Templates` を使うこともできます。 + +**FastAPI** は、開発者であるあなたの利便性のために、`starlette.templating` と同じものを `fastapi.templating` として提供しています。しかし、利用可能なレスポンスのほとんどは Starlette から直接提供されています。`Request` や `StaticFiles` も同様です。 + +/// + +## テンプレートの作成 { #writing-templates } + +例えば、`templates/item.html` に次のようなテンプレートを書きます: + +```jinja hl_lines="7" +{!../../docs_src/templates/templates/item.html!} +``` + +### テンプレートのコンテキスト値 { #template-context-values } + +次のような HTML 内で: + +{% raw %} + +```jinja +Item ID: {{ id }} +``` + +{% endraw %} + +...渡した "context" の `dict` から取得した `id` が表示されます: + +```Python +{"id": id} +``` + +例えば、ID が `42` の場合は次のようにレンダリングされます: + +```html +Item ID: 42 +``` + +### テンプレートの `url_for` の引数 { #template-url-for-arguments } + +テンプレート内でも `url_for()` を使用できます。引数には、対応する path operation 関数で使われるのと同じ引数を取ります。 + +したがって、次の部分は: + +{% raw %} + +```jinja + +``` + +{% endraw %} + +...path operation 関数 `read_item(id=id)` が処理するのと同じ URL へのリンクを生成します。 + +例えば、ID が `42` の場合は次のようにレンダリングされます: + +```html + +``` + +## テンプレートと静的ファイル { #templates-and-static-files } + +テンプレート内で `url_for()` を使用し、例えば `name="static"` でマウントした `StaticFiles` に対して利用できます。 + +```jinja hl_lines="4" +{!../../docs_src/templates/templates/item.html!} +``` + +この例では、`static/styles.css` の CSS ファイルにリンクします: + +```CSS hl_lines="4" +{!../../docs_src/templates/static/styles.css!} +``` + +また、`StaticFiles` を使用しているため、その CSS ファイルは **FastAPI** アプリケーションから URL `/static/styles.css` で自動的に配信されます。 + +## さらに詳しく { #more-details } + +より詳しい内容(テンプレートのテスト方法など)については、Starlette のテンプレートに関するドキュメントを参照してください。 diff --git a/docs/ja/docs/advanced/testing-dependencies.md b/docs/ja/docs/advanced/testing-dependencies.md new file mode 100644 index 000000000..78478a775 --- /dev/null +++ b/docs/ja/docs/advanced/testing-dependencies.md @@ -0,0 +1,53 @@ +# 依存関係のオーバーライドによるテスト { #testing-dependencies-with-overrides } + +## テスト時の依存関係のオーバーライド { #overriding-dependencies-during-testing } + +テスト中に依存関係をオーバーライドしたい場面がいくつかあります。 + +元の依存関係(およびそれにぶら下がるサブ依存関係)を実行したくない場合です。 + +代わりに、テストの間だけ(特定のテストだけでも)使われる別の依存関係を提供し、元の依存関係の値が使われていた箇所で利用できる値を返したいのです。 + +### ユースケース: 外部サービス { #use-cases-external-service } + +例として、呼び出す必要がある外部の認証プロバイダがあるとします。 + +トークンを送ると、認証済みユーザーが返ってきます。 + +このプロバイダはリクエストごとに課金されるかもしれず、テスト用に固定のモックユーザーを使う場合に比べて呼び出しに余分な時間がかかるかもしれません。 + +外部プロバイダ自体の動作は一度はテストしたいでしょうが、実行されるすべてのテストで毎回呼び出す必要はありません。 + +この場合、そのプロバイダを呼び出す依存関係をオーバーライドし、テストのときだけモックユーザーを返すカスタムの依存関係を使えます。 + +### app.dependency_overrides 属性を使う { #use-the-app-dependency-overrides-attribute } + +このような場合のために、**FastAPI** アプリケーションには `app.dependency_overrides` という属性があり、これは単純な `dict` です。 + +テスト用に依存関係をオーバーライドするには、キーに元の依存関係(関数)を、値にオーバーライドする依存関係(別の関数)を設定します。 + +すると **FastAPI** は元の依存関係の代わりにそのオーバーライドを呼び出します。 + +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} + +/// tip | 豆知識 + +アプリケーション内のどこで使われている依存関係に対しても、依存関係のオーバーライドを設定できます。 + +元の依存関係は、*path operation 関数*、*path operation デコレータ*(戻り値を使わない場合)、`.include_router()` の呼び出しなど、さまざまな場所で使われていてもかまいません。 + +FastAPI はそれでもオーバーライドできます。 + +/// + +その後、`app.dependency_overrides` を空の `dict` に設定することで、オーバーライドをリセット(削除)できます: + +```Python +app.dependency_overrides = {} +``` + +/// tip | 豆知識 + +一部のテストの間だけ依存関係をオーバーライドしたい場合は、テストの開始時(テスト関数内)にオーバーライドを設定し、終了時(テスト関数の末尾)にリセットするとよいです。 + +/// diff --git a/docs/ja/docs/advanced/testing-events.md b/docs/ja/docs/advanced/testing-events.md new file mode 100644 index 000000000..98e97fed8 --- /dev/null +++ b/docs/ja/docs/advanced/testing-events.md @@ -0,0 +1,11 @@ +# イベントのテスト: lifespan と startup - shutdown { #testing-events-lifespan-and-startup-shutdown } + +テストで `lifespan` を実行する必要がある場合は、`with` 文と併用して `TestClient` を使用できます: + +{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *} + +より詳しい内容は、[公式 Starlette ドキュメントの「テストでの lifespan の実行」](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) を参照してください。 + +非推奨の `startup` および `shutdown` イベントについては、次のように `TestClient` を使用できます: + +{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *} diff --git a/docs/ja/docs/advanced/testing-websockets.md b/docs/ja/docs/advanced/testing-websockets.md new file mode 100644 index 000000000..7f708ea1a --- /dev/null +++ b/docs/ja/docs/advanced/testing-websockets.md @@ -0,0 +1,13 @@ +# WebSocket のテスト { #testing-websockets } + +WebSocket をテストするのにも同じ `TestClient` を使用できます。 + +そのために、`with` 文の中で `TestClient` を使用し、WebSocket に接続します: + +{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *} + +/// note | 備考 + +詳細については、Starlette のドキュメント「WebSocket のテスト」を参照してください。 + +/// diff --git a/docs/ja/docs/advanced/using-request-directly.md b/docs/ja/docs/advanced/using-request-directly.md new file mode 100644 index 000000000..1e564f594 --- /dev/null +++ b/docs/ja/docs/advanced/using-request-directly.md @@ -0,0 +1,56 @@ +# Request を直接使う { #using-the-request-directly } + +これまで、必要なリクエストの各部分を、その型とともに宣言してきました。 + +次の場所からデータを取得します: + +- パスのパラメータ +- ヘッダー +- クッキー +- など + +こうすることで、**FastAPI** はそのデータを検証し、変換し、API のドキュメントを自動生成します。 + +しかし、`Request` オブジェクトに直接アクセスする必要がある場面もあります。 + +## `Request` オブジェクトの詳細 { #details-about-the-request-object } + +**FastAPI** は内部的には **Starlette** の上にいくつかのツール層を載せたものなので、必要に応じて Starlette の `Request` オブジェクトを直接使えます。 + +また、`Request` オブジェクトから直接データ(例: ボディ)を取得する場合、そのデータは FastAPI によって検証・変換・ドキュメント化(OpenAPI による自動 API ユーザーインターフェース向け)されません。 + +ただし、通常どおりに宣言された他のパラメータ(例: Pydantic モデルのボディ)は引き続き検証・変換・注釈付けなどが行われます。 + +それでも、`Request` オブジェクトを取得するのが有用な特定のケースがあります。 + +## `Request` オブジェクトを直接使う { #use-the-request-object-directly } + +たとえば、path operation 関数内でクライアントの IP アドレス/ホストを取得したいとします。 + +そのためには、リクエストに直接アクセスする必要があります。 + +{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *} + +path operation 関数の引数として `Request` 型のパラメータを宣言すると、**FastAPI** はその引数に `Request` を渡します。 + +/// tip | 豆知識 + +この例では、`Request` 型の引数に加えて、パスパラメータも宣言しています。 + +そのため、パスパラメータは取り出され、検証され、指定した型に変換され、OpenAPI で注釈(ドキュメント化)されます。 + +同様に、通常どおり任意の他のパラメータを宣言しつつ、追加で `Request` も受け取れます。 + +/// + +## `Request` のドキュメント { #request-documentation } + +より詳しくは、公式 Starlette ドキュメントサイトの `Request` オブジェクトを参照してください。 + +/// note | 技術詳細 + +`from starlette.requests import Request` を使うこともできます。 + +**FastAPI** は開発者である皆さんの便宜のために直接提供していますが、これは Starlette からそのまま提供されているものです。 + +/// diff --git a/docs/ja/docs/advanced/websockets.md b/docs/ja/docs/advanced/websockets.md index 6c68c9f0b..cb5e376de 100644 --- a/docs/ja/docs/advanced/websockets.md +++ b/docs/ja/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets しかし、これはWebSocketsのサーバーサイドに焦点を当て、動作する例を示す最も簡単な方法です。 -{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} ## `websocket` を作成する { #create-a-websocket } **FastAPI** アプリケーションで、`websocket` を作成します。 -{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} /// note | 技術詳細 @@ -58,7 +58,7 @@ $ pip install websockets WebSocketルートでは、メッセージを待機して送信するために `await` を使用できます。 -{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} バイナリやテキストデータ、JSONデータを送受信できます。 @@ -154,7 +154,7 @@ $ fastapi dev main.py WebSocket接続が閉じられると、 `await websocket.receive_text()` は例外 `WebSocketDisconnect` を発生させ、この例のようにキャッチして処理することができます。 -{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} +{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} 試してみるには、 diff --git a/docs/ja/docs/advanced/wsgi.md b/docs/ja/docs/advanced/wsgi.md new file mode 100644 index 000000000..b06b4a329 --- /dev/null +++ b/docs/ja/docs/advanced/wsgi.md @@ -0,0 +1,51 @@ +# WSGI の組み込み - Flask、Django など { #including-wsgi-flask-django-others } + +[サブアプリケーション - マウント](sub-applications.md){.internal-link target=_blank}、[プロキシの背後](behind-a-proxy.md){.internal-link target=_blank} で見たように、WSGI アプリケーションをマウントできます。 + +そのために `WSGIMiddleware` を使用して、Flask や Django などの WSGI アプリをラップできます。 + +## `WSGIMiddleware` の使用 { #using-wsgimiddleware } + +/// info | 情報 + +これには `a2wsgi` のインストールが必要です。例: `pip install a2wsgi`。 + +/// + +`a2wsgi` から `WSGIMiddleware` をインポートします。 + +次に、そのミドルウェアで WSGI(例: Flask)アプリをラップします。 + +そして、それをあるパスの下にマウントします。 + +{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *} + +/// note | 備考 + +以前は `fastapi.middleware.wsgi` の `WSGIMiddleware` を使用することが推奨されていましたが、現在は非推奨です。 + +代わりに `a2wsgi` パッケージを使用することを推奨します。使い方は同じです。 + +`a2wsgi` パッケージがインストールされていることを確認し、`a2wsgi` から `WSGIMiddleware` を正しくインポートしてください。 + +/// + +## チェック { #check-it } + +これで、パス `/v1/` 配下へのすべてのリクエストは Flask アプリケーションが処理します。 + +それ以外は **FastAPI** が処理します。 + +実行して http://localhost:8000/v1/ にアクセスすると、Flask からのレスポンスが表示されます: + +```txt +Hello, World from Flask! +``` + +さらに http://localhost:8000/v2 にアクセスすると、FastAPI からのレスポンスが表示されます: + +```JSON +{ + "message": "Hello World" +} +``` diff --git a/docs/ja/docs/alternatives.md b/docs/ja/docs/alternatives.md index 9f5152c08..8b1ec072d 100644 --- a/docs/ja/docs/alternatives.md +++ b/docs/ja/docs/alternatives.md @@ -1,8 +1,8 @@ -# 代替ツールから受けたインスピレーションと比較 +# 代替ツールから受けたインスピレーションと比較 { #alternatives-inspiration-and-comparisons } 何が**FastAPI**にインスピレーションを与えたのか、他の代替ツールと比較してどうか、そしてそこから何を学んだのかについて。 -## はじめに +## はじめに { #intro } **FastAPI**は、代替ツールのこれまでの働きがなければ存在しなかったでしょう。 @@ -12,17 +12,17 @@ しかし、その時点では、これらの機能をすべて提供し、以前のツールから優れたアイデアを取り入れ、可能な限り最高の方法でそれらを組み合わせ、それまで利用できなかった言語機能 (Python 3.6以降の型ヒント) を利用したものを作る以外に選択肢はありませんでした。 -## 以前のツール +## 以前のツール { #previous-tools } -### Django +### Django { #django } Pythonのフレームワークの中で最もポピュラーで、広く信頼されています。Instagramのようなシステムの構築に使われています。 リレーショナルデータベース (MySQLやPostgreSQLなど) と比較的強固に結合されているので、NoSQLデータベース (Couchbase、MongoDB、Cassandraなど) をメインに利用することは簡単ではありません。 -バックエンドでHTMLを生成するために作られたものであり、現代的なフロントエンド (ReactやVue.js、Angularなど) や、他のシステム (IoTデバイスなど) と通信するAPIを構築するために作られたものではありません。 +バックエンドでHTMLを生成するために作られたものであり、現代的なフロントエンド (ReactやVue.js、Angularなど) や、他のシステム (IoTデバイスなど) と通信するAPIを構築するために作られたものではありません。 -### Django REST Framework +### Django REST Framework { #django-rest-framework } Django REST Frameworkは、Djangoを下敷きにしてWeb APIを構築する柔軟なツールキットとして、APIの機能を向上させるために作られました。 @@ -42,7 +42,7 @@ Django REST Framework は Tom Christie によって作成されました。Starl /// -### Flask +### Flask { #flask } Flask は「マイクロフレームワーク」であり、データベースとの統合のようなDjangoがデフォルトで持つ多くの機能は含まれていません。 @@ -64,7 +64,7 @@ Flaskのシンプルさを考えると、APIを構築するのに適している /// -### Requests +### Requests { #requests } **FastAPI**は実際には**Requests**の代替ではありません。それらのスコープは大きく異なります。 @@ -80,7 +80,7 @@ Requestsは非常にシンプルかつ直感的なデザインで使いやすく 公式サイトで以下のように言われているのは、それが理由です。 -> Requestsは今までで最もダウンロードされたPythonパッケージである +> Requestsは史上最もダウンロードされたPythonパッケージのひとつです 使い方はとても簡単です。例えば、`GET`リクエストを実行するには、このように書けば良いです: @@ -88,7 +88,7 @@ Requestsは非常にシンプルかつ直感的なデザインで使いやすく response = requests.get("http://example.com/some/url") ``` -対応するFastAPIのパスオペレーションはこのようになります: +対応するFastAPIのAPIのpath operationはこのようになります: ```Python hl_lines="1" @app.get("/some/url") @@ -106,7 +106,7 @@ def read_url(): /// -### Swagger / OpenAPI +### Swagger / OpenAPI { #swagger-openapi } 私がDjango REST Frameworkに求めていた主な機能は、APIの自動的なドキュメント生成でした。 @@ -131,13 +131,13 @@ def read_url(): /// -### Flask REST フレームワーク +### Flask REST フレームワーク { #flask-rest-frameworks } いくつかのFlask RESTフレームワークがありますが、それらを調査してみたところ、多くのものが不適切な問題が残ったまま、中断されたり放置されていることがわかりました。 -### Marshmallow +### Marshmallow { #marshmallow } -APIシステムで必要とされる主な機能の一つに、コード (Python) からデータを取り出して、ネットワークを介して送れるものに変換するデータの「シリアライゼーション」があります。例えば、データベースのデータを含むオブジェクトをJSONオブジェクトに変換したり、`datetime` オブジェクトを文字列に変換するなどです。 +APIシステムで必要とされる主な機能の一つに、コード (Python) からデータを取り出して、ネットワークを介して送れるものに変換するデータの「シリアライゼーション」があります。例えば、データベースのデータを含むオブジェクトをJSONオブジェクトに変換したり、`datetime` オブジェクトを文字列に変換するなどです。 APIが必要とするもう一つの大きな機能はデータのバリデーションであり、特定のパラメータが与えられた場合にデータが有効であることを確認することです。例えば、あるフィールドがランダムな文字列ではなく `int` であることなどです。これは特に受信するデータに対して便利です。 @@ -145,7 +145,7 @@ APIが必要とするもう一つの大きな機能はデータのバリデー これらの機能は、Marshmallowが提供するものです。Marshmallowは素晴らしいライブラリで、私も以前に何度も使ったことがあります。 -しかし、それはPythonの型ヒントが存在する前に作られたものです。そのため、すべてのスキーマを定義するためには、Marshmallowが提供する特定のユーティリティやクラスを使用する必要があります。 +しかし、それはPythonの型ヒントが存在する前に作られたものです。そのため、すべてのスキーマを定義するためには、Marshmallowが提供する特定のユーティリティやクラスを使用する必要があります。 /// check | **FastAPI**へ与えたインスピレーション @@ -153,9 +153,9 @@ APIが必要とするもう一つの大きな機能はデータのバリデー /// -### Webargs +### Webargs { #webargs } -APIに求められる他の大きな機能として、受信したリクエストデータのパースがあります。 +APIに求められる他の大きな機能として、受信したリクエストデータのパースがあります。 WebargsはFlaskをはじめとするいくつかのフレームワークの上にそれを提供するために作られたツールです。 @@ -175,7 +175,7 @@ Webargsは、Marshmallowと同じ開発者により作られました。 /// -### APISpec +### APISpec { #apispec } MarshmallowとWebargsはバリデーション、パース、シリアライゼーションをプラグインとして提供しています。 @@ -205,7 +205,7 @@ OpenAPIという、APIについてのオープンな標準をサポートして /// -### Flask-apispec +### Flask-apispec { #flask-apispec } Webargs、Marshmallow、APISpecを連携させたFlaskプラグインです。 @@ -237,7 +237,7 @@ Flask-apispecはMarshmallowと同じ開発者により作成されました。 /// -### NestJS (とAngular) +### NestJS (とAngular) { #nestjs-and-angular } NestJSはAngularにインスパイアされたJavaScript (TypeScript) NodeJSフレームワークで、Pythonですらありません。 @@ -259,13 +259,13 @@ Angular 2にインスピレーションを受けた、統合された依存性 /// -### Sanic +### Sanic { #sanic } `asyncio`に基づいた、Pythonのフレームワークの中でも非常に高速なものの一つです。Flaskと非常に似た作りになっています。 /// note | 技術詳細 -Pythonの`asyncio`ループの代わりに、`uvloop`が利用されています。それにより、非常に高速です。 +Pythonの`asyncio`ループの代わりに、`uvloop`が利用されています。それにより、非常に高速です。 `Uvicorn`と`Starlette`に明らかなインスピレーションを与えており、それらは現在オープンなベンチマークにおいてSanicより高速です。 @@ -279,12 +279,10 @@ Pythonの`asyncio`ループの代わりに、`uvloop`が利用されています /// -### Falcon +### Falcon { #falcon } Falconはもう一つの高性能Pythonフレームワークで、ミニマムに設計されており、Hugのような他のフレームワークの基盤として動作します。 -Pythonのウェブフレームワーク標準規格 (WSGI) を使用していますが、それは同期的であるためWebSocketなどの利用には対応していません。とはいえ、それでも非常に高い性能を持っています。 - これは、「リクエスト」と「レスポンス」の2つのパラメータを受け取る関数を持つように設計されています。そして、リクエストからデータを「読み込み」、レスポンスにデータを「書き込み」ます。この設計のため、Python標準の型ヒントでリクエストのパラメータやボディを関数の引数として宣言することはできません。 そのため、データのバリデーション、シリアライゼーション、ドキュメント化は、自動的にできずコードの中で行わなければなりません。あるいは、HugのようにFalconの上にフレームワークとして実装されなければなりません。このような分断は、パラメータとして1つのリクエストオブジェクトと1つのレスポンスオブジェクトを持つというFalconのデザインにインスピレーションを受けた他のフレームワークでも起こります。 @@ -299,7 +297,7 @@ Hug (HugはFalconをベースにしています) と一緒に、**FastAPI**が`r /// -### Molten +### Molten { #molten } **FastAPI**を構築する最初の段階でMoltenを発見しました。そして、それは非常に似たようなアイデアを持っています。 @@ -323,7 +321,7 @@ Pydanticのようなデータのバリデーション、シリアライゼーシ /// -### Hug +### Hug { #hug } Hugは、Pythonの型ヒントを利用してAPIパラメータの型宣言を実装した最初のフレームワークの1つです。これは素晴らしいアイデアで、他のツールが同じことをするきっかけとなりました。 @@ -353,7 +351,7 @@ Hugは、**FastAPI**がヘッダーやクッキーを設定するために関数 /// -### APIStar (<= 0.5) +### APIStar (<= 0.5) { #apistar-0-5 } **FastAPI**を構築することを決める直前に、**APIStar**サーバーを見つけました。それは私が探していたものがほぼすべて含まれており、素晴らしいデザインでした。 @@ -401,9 +399,9 @@ APIStarはTom Christieにより開発されました。以下の開発者でも /// -## **FastAPI**が利用しているもの +## **FastAPI**が利用しているもの { #used-by-fastapi } -### Pydantic +### Pydantic { #pydantic } Pydanticは、Pythonの型ヒントを元にデータのバリデーション、シリアライゼーション、 (JSON Schemaを使用した) ドキュメントを定義するライブラリです。 @@ -419,9 +417,9 @@ Marshmallowに匹敵しますが、ベンチマークではMarshmallowよりも /// -### Starlette +### Starlette { #starlette } -Starletteは、軽量なASGIフレームワーク/ツールキットで、高性能な非同期サービスの構築に最適です。 +Starletteは、軽量なASGIフレームワーク/ツールキットで、高性能な非同期サービスの構築に最適です。 非常にシンプルで直感的です。簡単に拡張できるように設計されており、モジュール化されたコンポーネントを持っています。 @@ -429,15 +427,14 @@ Starletteは、軽量なUvicorn +### Uvicorn { #uvicorn } Uvicornは非常に高速なASGIサーバーで、uvloopとhttptoolsにより構成されています。 @@ -477,12 +474,12 @@ Starletteや**FastAPI**のサーバーとして推奨されています。 **FastAPI**アプリケーションを実行するメインのウェブサーバーである点。 -Gunicornと組み合わせることで、非同期でマルチプロセスなサーバーを持つことがきます。 +コマンドラインオプション `--workers` を使って、非同期のマルチプロセスサーバーにできます。 詳細は[デプロイ](deployment/index.md){.internal-link target=_blank}の項目で確認してください。 /// -## ベンチマーク と スピード +## ベンチマーク と スピード { #benchmarks-and-speed } Uvicorn、Starlette、FastAPIの違いを理解、比較、確認するには、[ベンチマーク](benchmarks.md){.internal-link target=_blank}を確認してください。 diff --git a/docs/ja/docs/async.md b/docs/ja/docs/async.md index 90a2e2ee5..bf4acda3f 100644 --- a/docs/ja/docs/async.md +++ b/docs/ja/docs/async.md @@ -1,18 +1,18 @@ -# 並行処理と async / await +# 並行処理と async / await { #concurrency-and-async-await } -*path operation 関数*のための `async def` に関する詳細と非同期 (asynchronous) コード、並行処理 (Concurrency)、そして、並列処理 (Parallelism) の背景について。 +*path operation 関数*のための `async def` 構文に関する詳細と、非同期コード、並行処理、並列処理の背景についてです。 -## 急いでいますか? +## 急いでいますか? { #in-a-hurry } -TL;DR: +TL;DR: -次のような、`await` を使用して呼び出すべきサードパーティライブラリを使用している場合: +次のように `await` で呼び出すよう指示されているサードパーティライブラリを使っているなら: ```Python results = await some_library() ``` -以下の様に `async def` を使用して*path operation 関数*を宣言します。 +*path operation 関数*は次のように `async def` で宣言します: ```Python hl_lines="2" @app.get('/') @@ -23,13 +23,13 @@ async def read_results(): /// note | 備考 -`async def` を使用して作成された関数の内部でしか `await` は使用できません。 +`await` は `async def` で作られた関数の内部でしか使えません。 /// --- -データベース、API、ファイルシステムなどと通信し、`await` の使用をサポートしていないサードパーティライブラリ (現在のほとんどのデータベースライブラリに当てはまります) を使用している場合、次の様に、単に `def` を使用して通常通り *path operation 関数* を宣言してください: +データベース、API、ファイルシステムなどと通信しつつ `await` の使用をサポートしていないサードパーティライブラリ (現在のところ多くのデータベースライブラリが該当します) を使っている場合、*path operation 関数*は通常どおり `def` で宣言してください: ```Python hl_lines="2" @app.get('/') @@ -40,272 +40,307 @@ def results(): --- -アプリケーションが (どういうわけか) 他の何とも通信せず、応答を待つ必要がない場合は、`async def` を使用して下さい。 +アプリケーションが (何らかの理由で) ほかの何とも通信せず応答を待つ必要がないなら、`await` を内部で使わなくても `async def` を使ってください。 --- -よく分からない場合は、通常の `def` を使用して下さい。 +よく分からない場合は、通常の `def` を使ってください。 --- -**備考**: *path operation 関数*に必要なだけ `def` と `async def` を混在させ、それぞれに最適なオプションを使用して定義できます。それに応じてFastAPIは正しい処理を行います。 +**備考**: 必要に応じて *path operation 関数* では `def` と `async def` を混在させ、それぞれに最適な選択肢で定義できます。FastAPI は適切に処理します。 -とにかく、上記のいずれの場合でもFastAPIは非同期で動作し、非常に高速です。 +いずれの場合でも、FastAPI は非同期で動作し非常に高速です。 -しかし、上記のステップに従うことで、パフォーマンスの最適化を行えます。 +ただし上記の手順に従うことで、さらにパフォーマンス最適化が可能になります。 -## 技術詳細 +## 技術詳細 { #technical-details } -現代版のPythonは「**非同期コード**」を、「**コルーチン**」と称されるものを利用してサポートしています。これは **`async` と `await`** 構文を用います。 +モダンな Python は **「非同期コード」** を **「コルーチン」** と呼ばれる仕組みでサポートしており、構文は **`async` と `await`** です。 -次のセクションで、フレーズ内のパーツを順に見ていきましょう: +以下のセクションで、このフレーズをパーツごとに見ていきます: * **非同期コード** * **`async` と `await`** * **コルーチン** -## 非同期コード +## 非同期コード { #asynchronous-code } -非同期コードとは、言語💬がコード内のどこかで、コンピュータ/プログラム🤖に *他の何か* がどこか別の箇所で終了するのを待つように伝える手段を持っていることを意味します。*他の何か* は「遅いファイル📝」と呼ばれているとしましょう. +非同期コードとは、言語 💬 がコードのどこかの時点で、コンピュータ/プログラム 🤖 に「どこか別のところで終わるまで、別の何か」を待つ必要があると伝える手段を持っている、ということです。その「別の何か」を「遅いファイル」📝 と呼ぶことにしましょう。 -したがって、コンピュータは「遅いファイル📝」が終了するまで、他の処理ができます。 +その間、コンピュータは「遅いファイル」📝 が終わるまで、他の作業を進められます。 -コンピュータ/プログラム🤖は再び待機する機会があるときや、その時点で行っていたすべての作業が完了するたびに戻ってきます。そして、必要な処理をしながら、コンピュータ/プログラム🤖が待っていた処理のどれかが終わっているかどうか確認します。 +その後、コンピュータ/プログラム 🤖 は、また待つ機会が来たときや、その時点で抱えていた作業をすべて終えたときに戻ってきます。そして、待っていたタスクのどれかが終わっていないか確認し、必要な処理を実行します。 -次に、それ🤖が最初のタスク (要するに、先程の「遅いファイル📝」)を終わらせて、そのタスクの結果を使う必要がある処理を続けます。 +次に、最初に終わったタスク (たとえば「遅いファイル」📝) を取り、続きの処理を行います。 -この「他の何かを待つ」とは、通常以下の様なものを待つような (プロセッサとRAMメモリの速度に比べて) 相対的に「遅い」I/O 操作を指します: +この「別の何かを待つ」は、通常 I/O 操作を指し、(プロセッサや RAM の速度に比べて) 相対的に「遅い」待機を伴います。例えば次のようなものです: -* ネットワーク経由でクライアントから送信されるデータ -* ネットワーク経由でクライアントが受信する、プログラムから送信されたデータ -* システムによって読み取られ、プログラムに渡されるディスク内のファイル内容 -* プログラムがシステムに渡して、ディスクに書き込む内容 -* リモートAPI操作 +* クライアントからネットワーク経由でデータが送られてくるのを待つ +* プログラムが送信したデータをクライアントがネットワーク経由で受け取るのを待つ +* ディスク上のファイル内容がシステムにより読み取られ、プログラムに渡されるのを待つ +* プログラムがシステムに渡した内容がディスクに書き込まれるのを待つ +* リモート API 操作 * データベース操作の完了 -* データベースクエリが結果を返すこと -* など。 +* データベースクエリが結果を返すのを待つ +* など -実行時間のほとんどがI/O 操作の待ち時間が占めるため、このような操作を「I/O バウンド」操作と言います。 +実行時間の大半が I/O 操作の待ち時間に費やされるため、これらは「I/O バウンド」な操作と呼ばれます。 -コンピュータ/プログラムがこのような遅いタスクと「同期 (タスクの結果を取得して作業を続行するために、何もせずに、タスクが完了する瞬間を正確に待つ)」する必要がないため、「非同期」と呼ばれます。 +「非同期」と呼ばれるのは、コンピュータ/プログラムがその遅いタスクと「同期」(タスクがちょうど終わる瞬間を、何もせずに待つ) する必要がないからです。結果を受け取って処理を続けるために、空待ちする必要がありません。 -その代わりに、「非同期」システムであることにより、いったん終了すると、タスクは、コンピュータ/プログラムが既に開始した処理がすべて完了するのをほんの少し (数マイクロ秒) 待って、結果を受け取りに戻ってきます。そして、処理を継続します。 +代わりに「非同期」システムでは、タスクが終わったら、コンピュータ/プログラムが取りかかっている作業が終わるまで (数マイクロ秒ほど) 少し待ち、結果を受け取りに戻って処理を続けられます。 -「同期」の場合 (「非同期」とは異なり)、「シーケンシャル」という用語もよく使用されます。これは、コンピュータ/プログラムがすべてのステップを (待機が伴う場合でも別のタスクに切り替えることなく) 順番に実行するためです。 +「非同期」と対になる「同期」は、「シーケンシャル」と呼ばれることもあります。待機が含まれていても、別のタスクに切り替える前にコンピュータ/プログラムが手順を順番に実行するためです。 -### 並行処理とハンバーガー +### 並行処理とハンバーガー { #concurrency-and-burgers } -上記の**非同期**コードのアイデアは、**「並行処理」**と呼ばれることもあります。 **「並列処理」**とは異なります。 +上で説明した**非同期**コードの考え方は、**「並行処理」** と呼ばれることもあります。これは **「並列処理」** とは異なります。 -**並行処理**と**並列処理**はどちらも「多かれ少なかれ同時に発生するさまざまなこと」に関連しています。 +**並行処理** も **並列処理** も、「複数のことがだいたい同時に起きる」ことに関係します。 -ただし、*並行処理*と*並列処理*の詳細はまったく異なります。 +ただし、*並行処理* と *並列処理* の詳細はかなり異なります。 -違いを確認するには、ハンバーガーに関する次の物語を想像してみてください: +違いを見るために、ハンバーガーに関する次の物語を想像してみてください。 -### 並行ハンバーガー +### 並行ハンバーガー { #concurrent-burgers } -ファストフード🍔を食べようと、好きな人😍とレジに並んでおり、レジ係💁があなたの前にいる人達の注文を受けつけています。 +あなたは好きな人とファストフードを買いに行き、前の人たちの注文をレジ係が受ける間、列に並びます。😍 -それからあなたの番になり、好きな人😍と自分のために、2つの非常に豪華なハンバーガー🍔を注文します。 + -料金を支払います💸。 +やがてあなたの番になり、好きな人と自分のために、とても豪華なハンバーガーを2つ注文します。🍔🍔 -レジ係💁はキッチンの男👨‍🍳に向かって、あなたのハンバーガー🍔を準備しなければならないと伝えるために何か言いました (彼は現在、前のお客さんの商品を準備していますが)。 + -レジ係💁はあなたに番号札を渡します。 +レジ係はキッチンの料理人に、あなたのハンバーガーを用意するよう声をかけます (料理人はいま前のお客さんの分を作っています)。 -待っている間、好きな人😍と一緒にテーブルを選んで座り、好きな人😍と長い間話をします (注文したハンバーガーは非常に豪華で、準備に少し時間がかかるので✨🍔✨)。 + -ハンバーガー🍔を待ちながら好きな人😍とテーブルに座っている間、あなたの好きな人がなんて素晴らしく、かわいくて頭がいいんだと✨😍✨惚れ惚れしながら時間を費やすことができます。 +支払いをします。💸 -好きな人😍と話しながら待っている間、ときどき、カウンターに表示されている番号をチェックして、自分の番かどうかを確認します。 +レジ係はあなたに番号札を渡します。 -その後、ついにあなたの番になりました。カウンターに行き、ハンバーガー🍔を手に入れてテーブルに戻ります。 + -あなたとあなたの好きな人😍はハンバーガー🍔を食べて、楽しい時間を過ごします✨。 +待っている間、好きな人とテーブルに移動して座り、(豪華なハンバーガーは時間がかかるので) しばらく話します。 + +テーブルで待っている間、好きな人がどれだけ素敵で、かわいくて、頭が良いかを眺めて時間を過ごせます ✨😍✨。 + + + +時々カウンターの表示を見て、自分の番号になっているか確認します。 + +やがてあなたの番になります。カウンターに行き、ハンバーガーを受け取り、テーブルに戻ります。 + + + +あなたと好きな人はハンバーガーを食べて、楽しい時間を過ごします。✨ + + + +/// info | 情報 + +美しいイラストは Ketrina Thompson によるものです。🎨 + +/// --- -上記のストーリーで、あなたがコンピュータ/プログラム🤖だと想像してみてください。 +この物語で、あなた自身がコンピュータ/プログラム 🤖 だと想像してみてください。 -列にいる間、あなたはアイドル状態です😴。何も「生産的」なことをせず、ただ自分の番を待っています。しかし、レジ係💁は注文を受け取るだけなので (商品の準備をしているわけではない)、列は高速です。したがって、何も問題ありません。 +列にいる間は、何も「生産的」なことをせず、自分の番を待つだけのアイドル状態 😴 です。ただしレジ係は注文を取るだけ (作りはしない) なので列は速く進み、問題ありません。 -それから、あなたの番になったら、実に「生産的な」作業を行います🤓、メニューを確認し、欲しいものを決め、好きな人😍の欲しいものを聞き、料金を支払い💸、現金またはカードを正しく渡したか確認し、正しく清算されたことを確認し、注文が正しく通っているかなどを確認します。 +あなたの番になると、実際に「生産的」な作業をします。メニューを見て注文を決め、好きな人の分も確認し、支払い、正しい紙幣/カードを渡したか、正しく決済されたか、注文内容が正しいかなどを確認します。 -しかし、ハンバーガー🍔をまだできていないので、ハンバーガーの準備ができるまで待機🕙する必要があるため、レジ係💁との作業は「一時停止⏸」になります。 +しかし、ハンバーガーはまだ出来上がっていないので、レジ係とのやり取りは「一時停止」⏸ になります。ハンバーガーができるまで待つ 🕙 必要があるからです。 -しかし、カウンターから離れて、番号札を持ってテーブルに座っているときは、注意を好きな人😍に切り替えて🔀、その上で「仕事⏯🤓」を行なえます。その後、好きな人😍といちゃつくかのような、非常に「生産的な🤓」ことを再び行います。 +ただし、番号札を持ってカウンターから離れテーブルに座れば、注意を好きな人に切り替え 🔀、「その作業」⏯ 🤓 に取り組めます。好きな人といちゃつくという、とても「生産的」🤓 なことがまたできます。 -次に、レジ係💁は、「ハンバーガーの準備ができました🍔」と言って、カウンターのディスプレイに番号を表示しますが、表示番号があなたの番号に変わっても、すぐに狂ったように飛んで行くようなことはありません。あなたは自分の番号札を持っていって、他の人も自分の番号札があるので、あなたのハンバーガー🍔を盗む人がいないことは知っています。 +レジ係 💁 がカウンターの表示にあなたの番号を出して「ハンバーガーができました」と知らせても、あなたは表示が切り替わった瞬間に飛び跳ねたりしません。自分の番号札があり、他の人にもそれぞれ番号札があるので、ハンバーガーを盗られることはないと知っているからです。 -なので、あなたは好きな人😍が話し終えるのを待って (現在の仕事⏯ / 処理中のタスクを終了します🤓)、優しく微笑んで、ハンバーガーを貰ってくるねと言います⏸。 +だから、好きな人の話が終わるのを待ち (現在の作業 ⏯ / 処理中のタスクを完了し 🤓)、微笑んで「ハンバーガー取ってくるね」と言います ⏸。 -次に、カウンターへ、いまから完了する最初のタスク⏯へ向かい、ハンバーガー🍔を受け取り、感謝の意を表して、テーブルに持っていきます。これで、カウンターとのやり取りのステップ/タスクが完了しました⏹。これにより、「ハンバーガーを食べる🔀⏯」という新しいタスクが作成されます。しかし、前の「ハンバーガーを取得する」というタスクは終了しました⏹。 +それからカウンターへ行き 🔀、いま完了した初期のタスク ⏯ に戻って、ハンバーガーを受け取り、礼を言ってテーブルに持っていきます。これでカウンターとのやり取りというステップ/タスクは完了 ⏹ です。その結果として「ハンバーガーを食べる」🔀 ⏯ という新しいタスクが生まれますが、先の「ハンバーガーを受け取る」タスクは完了 ⏹ しています。 -### 並列ハンバーガー +### 並列ハンバーガー { #parallel-burgers } -これらが「並行ハンバーガー」ではなく、「並列ハンバーガー」であるとしましょう。 +今度は、これが「並行ハンバーガー」ではなく「並列ハンバーガー」だと想像しましょう。 -あなたは好きな人😍と並列ファストフード🍔を買おうとしています。 +あなたは好きな人と「並列」ファストフードを買いに行きます。 -列に並んでいますが、何人かの料理人兼、レジ係 (8人としましょう) 👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳があなたの前にいる人達の注文を受けつけています。 +複数のレジ係 (例えば 8 人) が同時に料理人でもあり、前の人たちの注文を受けています。 -8人のレジ係がそれぞれ自分で注文を受けるや否や、次の注文を受ける前にハンバーガーを準備するので、あなたの前の人達はカウンターを離れずに、ハンバーガー🍔ができるのを待っています🕙。 +8 人のレジ係はそれぞれ、次の注文を取る前にすぐに調理に取りかかるため、あなたの前の人たちはカウンターを離れず、ハンバーガーができるのを待っています。 -それからいよいよあなたの番になり、好きな人😍と自分のために、2つの非常に豪華なハンバーガー🍔を注文します。 + -料金を支払います💸。 +ようやくあなたの番になり、好きな人と自分のために豪華なハンバーガーを 2 つ注文します。 -レジ係はキッチンに行きます👨‍🍳。 +支払いをします 💸。 -あなたはカウンターの前に立って待ちます🕙。番号札がないので誰もあなたよりも先にハンバーガー🍔を取らないようにします。 + -あなたと好きな人😍は忙しいので、誰もあなたの前に来させませんし、あなたのハンバーガーが到着したとき🕙に誰にも取ることを許しません。あなたは好きな人に注意を払えません😞。 +レジ係はキッチンに向かいます。 -これは「同期」作業であり、レジ係/料理人👨‍🍳と「同期」します。レジ係/料理人👨‍🍳がハンバーガー🍔を完成させてあなたに渡すまで待つ🕙必要があり、ちょうどその完成の瞬間にそこにいる必要があります。そうでなければ、他の誰かに取られるかもしれません。 +番号札がないため、他の誰かに先に取られないよう、カウンターの前で立って待ちます 🕙。 -その後、カウンターの前で長い時間待ってから🕙、ついにレジ係/料理人👨‍🍳がハンバーガー🍔を渡しに戻ってきます。 + -ハンバーガー🍔を取り、好きな人😍とテーブルに行きます。 +あなたと好きな人は、誰にも割り込まれずハンバーガーが来たらすぐ受け取れるよう見張っているので、好きな人に注意を向けられません。😞 -ただ食べるだけ、それでおしまいです。🍔⏹。 +これは「同期」的な作業です。レジ係/料理人 👨‍🍳 と「同期」しています。レジ係/料理人 👨‍🍳 がハンバーガーを作り終えて手渡すその瞬間に、待って 🕙 その場にいなければなりません。そうでないと他の誰かに取られるかもしれません。 -ほとんどの時間、カウンターの前で待つのに費やされていたので🕙、あまり話したりいちゃつくことはありませんでした😞。 + + +長い時間 🕙 カウンター前で待った後、ようやくレジ係/料理人 👨‍🍳 がハンバーガーを持って戻ってきます。 + + + +ハンバーガーを受け取り、好きな人とテーブルに行きます。 + +食べて、おしまいです。⏹ + + + +ほとんどの時間をカウンター前で待つ 🕙 のに費やしたため、あまり話したり、いちゃついたりできませんでした。😞 + +/// info | 情報 + +美しいイラストは Ketrina Thompson によるものです。🎨 + +/// --- -この並列ハンバーガーのシナリオでは、あなたは2つのプロセッサを備えたコンピュータ/プログラム🤖 (あなたとあなたの好きな人😍) であり、両方とも待機🕙していて、彼らは「カウンターで待機🕙」することに専念しています⏯。 +この「並列ハンバーガー」のシナリオでは、あなたは 2 つのプロセッサ (あなたと好きな人) を持つコンピュータ/プログラム 🤖 で、どちらも長い間 🕙「カウンターでの待機」に注意 ⏯ を専念しています。 -ファストフード店には8つのプロセッサ (レジ係/料理人) 👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳があります。一方、並行ハンバーガー店には2人 (レジ係と料理人) 💁👨‍🍳しかいなかったかもしれません。 +ファストフード店には 8 個のプロセッサ (レジ係/料理人) があります。一方、並行ハンバーガーの店には (レジ係 1、人、料理人 1 人の) 2 個しかなかったかもしれません。 -しかし、それでも、最終的な体験は最高ではありません😞。 +それでも、最終的な体験は最良とは言えません。😞 --- -これは、ハンバーガー🍔の話と同等な話になります。 +これはハンバーガーにおける並列版の物語です。🍔 より「現実的な」例として、銀行を想像してみてください。 -最近まで、ほとんどの銀行は複数の窓口👨‍💼👨‍💼👨‍💼👨‍💼に、行列🕙🕙🕙🕙🕙🕙🕙🕙ができていました。 +つい最近まで、ほとんどの銀行には複数の窓口係 👨‍💼👨‍💼👨‍💼👨‍💼 と長い行列 🕙🕙🕙🕙🕙🕙🕙🕙 がありました。 -すべての窓口で、次々と、一人の客とすべての作業を行います👨‍💼⏯. +各窓口係が、一人ずつ、すべての作業を順番に行います 👨‍💼⏯。 -その上、長時間、列に並ばなければいけません🕙。そうしないと、順番が回ってきません。 +そして、長時間 🕙 行列で待たなければ順番を失います。 -銀行🏦での用事にあなたの好きな人😍を連れて行きたくはないでしょう。 +銀行の用事 🏦 に、好きな人 😍 を連れて行きたいとは思わないでしょう。 -### ハンバーガーのまとめ +### ハンバーガーのまとめ { #burger-conclusion } -この「好きな人とのファストフードハンバーガー」のシナリオでは、待機🕙が多いため、並行システム⏸🔀⏯を使用する方がはるかに理にかなっています。 +この「好きな人とファストフード」のシナリオでは、待ち時間 🕙 が多いため、並行システム ⏸🔀⏯ を使う方がはるかに理にかなっています。 -これは、ほとんどのWebアプリケーションに当てはまります。 +これは、ほとんどの Web アプリケーションにも当てはまります。 -多くのユーザーがいますが、サーバーは、あまり強くない回線でのリクエストの送信を待機🕙しています。 +とても多くのユーザーがいますが、サーバは彼らのあまり速くない回線からリクエストが届くのを待ち 🕙、 -そして、レスポンスが返ってくるのをもう一度待機🕙します。 +その後、レスポンスが戻ってくるのをまた待ちます 🕙。 -この「待機🕙」はマイクロ秒単位ですが、それでも、すべて合算すると、最終的にはかなり待機することになります。 +この「待ち」🕙 はマイクロ秒単位で測られますが、すべてを合計すると、結局かなりの待ちになります。 -これが、Web APIへの非同期⏸🔀⏯コードの利用が理にかなっている理由です。 +だからこそ、Web API には非同期 ⏸🔀⏯ コードを使うのが理にかなっています。 -ほとんどの既存の人気のあるPythonフレームワーク (FlaskやDjangoを含む) は、Pythonの新しい非同期機能ができる前に作成されました。したがって、それらをデプロイする方法は、並列実行と、新機能ほど強力ではない古い形式の非同期実行をサポートします。 +これが、NodeJS を人気にした要因 (NodeJS 自体は並列ではありません) であり、プログラミング言語としての Go の強みでもあります。 -しかし、WebSocketのサポートを追加するために、非同期Web Python (ASGI) の主な仕様はDjangoで開発されました。 +そして、それが **FastAPI** で得られるパフォーマンスの水準です。 -そのような非同期性がNodeJSを人気にした理由です (NodeJSは並列ではありませんが)。そして、プログラミング言語としてのGoの強みでもあります。 +さらに、並列性と非同期性を同時に活用できるため、テストされた多くの NodeJS フレームワークより高い性能を発揮し、C に近いコンパイル言語である Go と同等の性能になります (すべて Starlette のおかげです)。 -そして、それは**FastAPI**で得られるパフォーマンスと同じレベルです。 +### 並行処理は並列処理より優れている? { #is-concurrency-better-than-parallelism } -また、並列処理と非同期処理を同時に実行できるため、テスト済みのほとんどのNodeJSフレームワークよりも高く、Goと同等のパフォーマンスが得られます。Goは、Cに近いコンパイル言語です (Starletteに感謝します)。 +いいえ!それがこの話の教訓ではありません。 -### 並行は並列よりも優れていますか? +並行処理は並列処理とは異なります。そして多くの待ち時間を伴う**特定の**シナリオでは優れています。そのため、一般に Web アプリ開発では並列処理よりはるかに適しています。しかし、すべてに対して最良というわけではありません。 -いや!それはこの話の教訓ではありません。 +バランスを取るために、次の短い物語を想像してください。 -並行処理は並列処理とは異なります。多くの待機を伴う**特定の**シナリオに適しています。そのため、一般に、Webアプリケーション開発では並列処理よりもはるかに優れています。しかし、すべてに対してより良いというわけではありません。 +> 大きくて汚れた家を掃除しなければならない。 -なので、バランスをとるために、次の物語を想像して下さい: - -> あなたは大きくて汚れた家を掃除する必要があります。 - -*はい、以上です*。 +*はい、これで物語は全部です*。 --- -待機🕙せず、家の中の複数の場所でたくさんの仕事をするだけです。 +どこにも待ち 🕙 はなく、家の複数箇所で大量の作業があるだけです。 -あなたはハンバーガーの例のように、最初はリビングルーム、次にキッチンのように順番にやっていくことができますが、何かを待機🕙しているわけではなく、ただひたすらに掃除をするだけで、順番は何にも影響しません。 +ハンバーガーの例のように順番を決めて、まずリビング、次にキッチン、と進めてもよいのですが、何かを待つ 🕙 わけではなく、ひたすら掃除するだけなので、順番は何も影響しません。 -順番の有無に関係なく (並行に) 同じ時間がかかり、同じ量の作業が行われることになるでしょう。 +順番の有無 (並行性の有無) に関係なく、終了までに同じ時間がかかり、同じ作業量をこなすことになります。 -しかし、この場合、8人の元レジ係/料理人/現役清掃員👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳👨‍🍳を手配できて、それぞれ (さらにあなたも) が家の別々の場所を掃除できれば、追加の助けを借りて、すべての作業を**並列**に行い、はるかに早く終了できるでしょう。 +しかしこの場合、8 人の元レジ係/料理人/現清掃員を連れてきて、それぞれ (あなたも加えて) 家の別々のエリアを掃除できれば、**並列** に作業でき、より早く終えられます。 -このシナリオでは、清掃員 (あなたを含む) のそれぞれがプロセッサとなり、それぞれの役割を果たします。 +このシナリオでは、各清掃員 (あなたを含む) がプロセッサであり、それぞれが自分の役割を果たします。 -また、実行時間のほとんどは (待機ではなく) 実際の作業に費やされ、コンピュータでの作業はCPUによって行われます。これらの問題は「CPUバウンド」と言います。 +そして実行時間の大半は (待ちではなく) 実作業が占め、コンピュータでの作業は CPU によって行われます。これらの問題は「CPU バウンド」と呼ばれます。 --- -CPUバウンド操作の一般的な例は、複雑な数学処理が必要なものです。 +CPU バウンドな操作の一般的な例は、複雑な数値処理が必要なものです。 例えば: * **オーディオ** や **画像処理**。 -* **コンピュータビジョン**: 画像は数百万のピクセルで構成され、各ピクセルには3つの値/色があり、通常、これらのピクセルで何かを同時に計算する必要がある処理。 -* **機械学習**: 通常、多くの「行列」と「ベクトル」の乗算が必要です。巨大なスプレッドシートに数字を入れて、それを同時に全部掛け合わせることを考えてみてください。 -* **ディープラーニング**: これは機械学習のサブフィールドであるため、同じことが当てはまります。乗算する数字がある単一のスプレッドシートではなく、それらの膨大な集合で、多くの場合、それらのモデルを構築および/または使用するために特別なプロセッサを使用します。 +* **コンピュータビジョン**: 画像は数百万のピクセルで構成され、各ピクセルには 3 つの値/色があり、通常、それらのピクセル上で同時に何かを計算する必要があります。 +* **機械学習**: 多くの「行列」や「ベクトル」の乗算が必要になります。巨大なスプレッドシートに数字が入っていて、それらを同時にすべて掛け合わせることを想像してください。 +* **ディープラーニング**: 機械学習のサブフィールドなので同様です。掛け合わせる数字が 1 つのスプレッドシートではなく膨大な集合であり、多くの場合、それらのモデルを構築/利用するための特別なプロセッサを使います。 -### 並行処理 + 並列処理: Web + 機械学習 +### 並行処理 + 並列処理: Web + 機械学習 { #concurrency-parallelism-web-machine-learning } -**FastAPI**を使用すると、Web開発で非常に一般的な並行処理 (NodeJSの主な魅力と同じもの) を利用できます。 +**FastAPI** では、Web 開発で非常に一般的な並行処理 (NodeJS の主な魅力と同じ) を活用できます。 -ただし、機械学習システムのような **CPUバウンド** ワークロードに対して、並列処理とマルチプロセッシング (複数のプロセスが並列で実行される) の利点を活用することもできます。 +同時に、機械学習システムのような **CPU バウンド** なワークロードに対して、並列処理やマルチプロセッシング (複数プロセスの並列実行) の利点も活用できます。 -さらに、Pythonが**データサイエンス**、機械学習、特にディープラーニングの主要言語であるという単純な事実により、FastAPIはデータサイエンス/機械学習のWeb APIおよびアプリケーション (他の多くのアプリケーションとの) に非常によく適合しています。 +さらに、Python が **データサイエンス**、機械学習、特にディープラーニングの主要言語であるという事実も相まって、FastAPI はデータサイエンス/機械学習の Web API やアプリケーション (ほか多数) に非常に適しています。 -本番環境でこの並列処理を実現する方法については、[デプロイ](deployment/index.md){.internal-link target=_blank}に関するセクションを参照してください。 +本番環境でこの並列性を実現する方法は、[デプロイ](deployment/index.md){.internal-link target=_blank} のセクションを参照してください。 -## `async` と `await` +## `async` と `await` { #async-and-await } -現代的なバージョンのPythonには、非同期コードを定義する非常に直感的な方法があります。これにより、通常の「シーケンシャル」コードのように見え、適切なタイミングで「待機」します。 +モダンな Python には、非同期コードをとても直感的に定義する方法があります。これにより、通常の「シーケンシャル」なコードのように書けて、適切なタイミングで「待ち」を行ってくれます。 -結果を返す前に待機する必要があり、これらの新しいPython機能をサポートする操作がある場合は、次のようにコーディングできます。 +結果を返す前に待ちが必要で、これらの新しい Python 機能をサポートしている操作がある場合、次のように書けます。 ```Python burgers = await get_burgers(2) ``` -カギは `await` です。結果を `burgers`に保存する前に、`get_burgers(2)`の処理🕙の完了を待つ⏸必要があることをPythonに伝えます。これでPythonは、その間に (別のリクエストを受信するなど) 何か他のことができる🔀⏯ことを知ります。 +ここでの鍵は `await` です。`burgers` に結果を保存する前に、`get_burgers(2)` がやるべきことを終えるのを ⏸ 待つ 🕙 ように Python に伝えます。これにより Python は、その間に (別のリクエストを受け取るなど) ほかのことを 🔀 ⏯ できると分かります。 -`await` が機能するためには、非同期処理をサポートする関数内にある必要があります。これは、`async def` で関数を宣言するだけでよいです: +`await` が機能するには、この非同期性をサポートする関数の内部でなければなりません。そのためには `async def` で宣言します: ```Python hl_lines="1" async def get_burgers(number: int): - # ハンバーガーを作成するために非同期処理を実行 + # ハンバーガーを作るために非同期の処理を行う return burgers ``` -...`def` のかわりに: +...`def` の代わりに: ```Python hl_lines="2" -# 非同期ではない +# これは非同期ではない def get_sequential_burgers(number: int): - # ハンバーガーを作成するためにシーケンシャルな処理を実行 + # ハンバーガーを作るためにシーケンシャルな処理を行う return burgers ``` -`async def` を使用すると、Pythonにその関数内で `await` 式 (その関数の実行を「一時停止⏸」し、結果が戻るまで他の何かを実行🔀する) を認識しなければならないと伝えることができます。 -`async def` 関数を呼び出すときは、「await」しなければなりません。したがって、これは機能しません: +`async def` を使うと、Python はその関数内で `await` 式に注意し、関数の実行を「一時停止」⏸ してほかのことをしに行き 🔀、戻ってくることができると分かります。 + +`async def` な関数を呼ぶときは「await」しなければなりません。したがって、次は動きません: ```Python -# get_burgersはasync defで定義されているので動作しない +# 動きません。get_burgers は async def で定義されています burgers = get_burgers(2) ``` --- -したがって、 `await` で呼び出すことができるライブラリを使用している場合は、次のように `async def` を使用して、それを使用する*path operation 関数*を作成する必要があります: +そのため、`await` で呼べると謳っているライブラリを使っている場合は、それを使う *path operation 関数* を `async def` で作る必要があります。例えば: ```Python hl_lines="2-3" @app.get('/burgers') @@ -314,86 +349,96 @@ async def read_burgers(): return burgers ``` -### より発展的な技術詳細 +### より発展的な技術詳細 { #more-technical-details } -`await` は `async def` で定義された関数内でのみ使用できることがわかったかと思います。 +`await` は `async def` で定義された関数の内部でしか使えないことに気づいたかもしれません。 -しかし同時に、`async def` で定義された関数は「awaitされる」必要があります。なので、`async def` を持つ関数は、`async def` で定義された関数内でのみ呼び出せます。 +同時に、`async def` で定義された関数は「await」される必要があります。つまり、`async def` を持つ関数は、やはり `async def` で定義された関数の内部からしか呼べません。 -では、このニワトリと卵の問題について、最初の `async` 関数をどのように呼び出すのでしょうか? +では、ニワトリと卵の話のように、最初の `async` 関数はどう呼ぶのでしょうか? -**FastAPI**を使用している場合、その「最初の」関数が*path operation 関数*であり、FastAPIが正しく実行する方法を知っているので、心配する必要はありません。 +**FastAPI** を使っている場合は心配ありません。その「最初の」関数は *path operation 関数* で、FastAPI が適切に実行してくれます。 -しかし、FastAPI以外で `async` / `await` を使用したい場合は、公式Pythonドキュメントを参照して下さい。 +しかし、FastAPI を使わずに `async` / `await` を使いたい場合もあります。 -### 非同期コードの他の形式 +### 自分で async コードを書く { #write-your-own-async-code } -`async` と `await` を使用するスタイルは、この言語では比較的新しいものです。 +Starlette (**FastAPI** も) は AnyIO の上に構築されており、標準ライブラリの asyncioTrio の両方に対応しています。 -非同期コードの操作がはるかに簡単になります。 +特に、あなた自身のコード内で、より高度なパターンを必要とする発展的な並行処理のユースケースに対して、AnyIO を直接使えます。 -等価な (またはほとんど同一の) 構文が、最近のバージョンのJavaScript (ブラウザおよびNodeJS) にも最近組み込まれました。 +仮に FastAPI を使っていなくても、AnyIO で独自の async アプリケーションを書けば、高い互換性と利点 (例: 構造化並行性) を得られます。 -しかし、その前は、非同期コードの処理はかなり複雑で難解でした。 +私は AnyIO の上に薄い層として、型注釈を少し改善し、より良い**補完**や**インラインエラー**などを得るための別ライブラリも作りました。また、**理解**して**自分で async コードを書く**のに役立つフレンドリーなイントロ/チュートリアルもあります: Asyncer。特に、**async コードと通常の** (ブロッキング/同期) **コードを組み合わせる**必要がある場合に有用です。 -以前のバージョンのPythonでは、スレッドやGeventが利用できました。しかし、コードは理解、デバック、そして、考察がはるかに複雑です。 +### 非同期コードの他の形式 { #other-forms-of-asynchronous-code } -以前のバージョンのNodeJS / ブラウザJavaScriptでは、「コールバック」を使用していました。これは、「コールバック地獄」につながります。 +`async` と `await` を使うこのスタイルは、言語としては比較的新しいものです。 -## コルーチン +しかし、これにより非同期コードの取り扱いは大幅に簡単になります。 -**コルーチン**は、`async def` 関数によって返されるものを指す非常に洒落た用語です。これは、開始できて、いつか終了する関数のようなものであるが、内部に `await` があるときは内部的に一時停止⏸されることもあるものだとPythonは認識しています。 +同等 (ほぼ同一) の構文が最近の JavaScript (ブラウザと NodeJS) にも導入されました。 -`async` と `await` を用いた非同期コードを使用するすべての機能は、「コルーチン」を使用するものとして何度もまとめられています。Goの主要機能である「ゴルーチン」に相当します。 +それ以前は、非同期コードの扱いはかなり複雑で難解でした。 -## まとめ +以前の Python ではスレッドや Gevent を使えましたが、コードの理解・デバッグ・思考がはるかに難しくなります。 -上述したフレーズを見てみましょう: +以前の NodeJS / ブラウザ JavaScript では「コールバック」を使っており、「コールバック地獄」を招きました。 -> 現代版のPythonは「**非同期コード**」を、「**コルーチン**」と称されるものを利用してサポートしています。これは **`async` と `await`** 構文を用います。 +## コルーチン { #coroutines } -今では、この意味がより理解できるはずです。✨ +**コルーチン**は、`async def` 関数が返すものを指す、ちょっと洒落た用語です。Python はそれを、開始できていつか終了する関数のようなものとして扱いますが、内部に `await` があるたびに内部的に一時停止 ⏸ するかもしれないものとして認識します。 -(Starletteを介して) FastAPIに力を与えて、印象的なパフォーマンスを実現しているものはこれがすべてです。 +`async` と `await` を用いた非同期コードの機能全体は、しばしば「コルーチンを使う」と要約されます。これは Go の主要機能「Goroutines」に相当します。 -## 非常に発展的な技術的詳細 +## まとめ { #conclusion } + +上のフレーズをもう一度見てみましょう: + +> モダンな Python は **「非同期コード」** を **「コルーチン」** と呼ばれる仕組みでサポートしており、構文は **`async` と `await`** です。 + +今なら、より意味が分かるはずです。✨ + +これらすべてが (Starlette を通じて) FastAPI を支え、印象的なパフォーマンスを実現しています。 + +## 非常に発展的な技術的詳細 { #very-technical-details } /// warning | 注意 -恐らくスキップしても良いでしょう。 +おそらく読み飛ばしても大丈夫です。 -この部分は**FastAPI**の仕組みに関する非常に技術的な詳細です。 +これは **FastAPI** の内部動作に関する、とても技術的な詳細です。 -かなりの技術知識 (コルーチン、スレッド、ブロッキングなど) があり、FastAPIが `async def` と通常の `def` をどのように処理するか知りたい場合は、先に進んでください。 +(コルーチン、スレッド、ブロッキング等の) 技術知識があり、FastAPI が `async def` と通常の `def` をどう扱うかに興味がある場合は、読み進めてください。 /// -### Path operation 関数 +### Path operation 関数 { #path-operation-functions } -*path operation 関数*を `async def` の代わりに通常の `def` で宣言すると、(サーバーをブロックするので) 直接呼び出す代わりに外部スレッドプール (awaitされる) で実行されます。 +*path operation 関数* を `async def` ではなく通常の `def` で宣言した場合、(サーバをブロックしてしまうため) 直接呼び出されるのではなく、外部のスレッドプールで実行され、それを待機します。 -上記の方法と違った方法の別の非同期フレームワークから来ており、小さなパフォーマンス向上 (約100ナノ秒) のために通常の `def` を使用して些細な演算のみ行う *path operation 関数* を定義するのに慣れている場合は、**FastAPI**ではまったく逆の効果になることに注意してください。このような場合、*path operation 関数* がブロッキングI/Oを実行しないのであれば、`async def` の使用をお勧めします。 +上記とは異なる動作の別の非同期フレームワークから来ており、ほんのわずかなパフォーマンス向上 (約 100 ナノ秒) を狙って、計算のみの些細な *path operation 関数* を素の `def` で定義することに慣れている場合、**FastAPI** では効果がまったく逆になる点に注意してください。これらの場合、*path operation 関数* がブロッキングな I/O を行うコードを使っていない限り、`async def` を使った方が良いです。 -それでも、どちらの状況でも、**FastAPI**が過去のフレームワークよりも (またはそれに匹敵するほど) [高速になる](index.md#_10){.internal-link target=_blank}可能性があります。 +それでも、どちらの状況でも、**FastAPI** はあなたが以前使っていたフレームワークよりも (少なくとも同等に) [高速である](index.md#performance){.internal-link target=_blank} 可能性が高いです。 -### 依存関係 +### 依存関係 { #dependencies } -依存関係についても同様です。依存関係が `async def` ではなく標準の `def` 関数である場合、外部スレッドプールで実行されます。 +[依存関係](tutorial/dependencies/index.md){.internal-link target=_blank} についても同様です。依存関係が `async def` ではなく標準の `def` 関数である場合、外部のスレッドプールで実行されます。 -### サブ依存関係 +### サブ依存関係 { #sub-dependencies } -(関数定義のパラメーターとして) 相互に必要な複数の依存関係とサブ依存関係を設定できます。一部は `async def` で作成され、他の一部は通常の `def` で作成されます。それでも動作し、通常の `def`で作成されたものは、「awaitされる」代わりに (スレッドプールから) 外部スレッドで呼び出されます。 +複数の依存関係や [サブ依存関係](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} を (関数定義のパラメータとして) 相互に要求させられます。その一部は `async def`、他は通常の `def` で作られていても動作します。通常の `def` で作られたものは「await」される代わりに、外部スレッドプールからスレッド上で呼び出されます。 -### その他のユーティリティ関数 +### その他のユーティリティ関数 { #other-utility-functions } -あなたが直接呼び出すユーティリティ関数は通常の `def` または `async def` で作成でき、FastAPIは呼び出す方法に影響を与えません。 +あなたが直接呼び出すユーティリティ関数は、通常の `def` でも `async def` でも構いません。FastAPI はその呼び出し方に影響を与えません。 -これは、FastAPIが呼び出す関数と対照的です: *path operation 関数*と依存関係。 +これは、FastAPI があなたの代わりに呼び出す関数 (すなわち *path operation 関数* と依存関係) とは対照的です。 -ユーティリティ関数が `def` を使用した通常の関数である場合、スレッドプールではなく直接 (コードで記述したとおりに) 呼び出されます。関数が `async def` を使用して作成されている場合は、呼び出す際に `await` する必要があります。 +ユーティリティ関数が `def` の通常関数であれば、(あなたのコードに書いたとおりに) 直接呼び出され、スレッドプールでは実行されません。関数が `async def` で作られている場合は、その関数を呼ぶときに `await` すべきです。 --- -繰り返しになりますが、これらは非常に技術的な詳細であり、検索して辿り着いた場合は役立つでしょう。 +繰り返しになりますが、これらは非常に技術的な詳細で、該当事項を検索してここにたどり着いた場合には役立つでしょう。 -それ以外の場合は、上記のセクションのガイドラインで問題ないはずです: 急いでいますか?。 +それ以外の場合は、上のセクションのガイドラインに従えば十分です: 急いでいますか?。 diff --git a/docs/ja/docs/deployment/cloud.md b/docs/ja/docs/deployment/cloud.md new file mode 100644 index 000000000..17699cdca --- /dev/null +++ b/docs/ja/docs/deployment/cloud.md @@ -0,0 +1,24 @@ +# クラウドプロバイダへの FastAPI デプロイ { #deploy-fastapi-on-cloud-providers } + +FastAPI アプリケーションは、実質的にどのようなクラウドプロバイダでもデプロイできます。 + +多くの場合、主要なクラウドプロバイダは FastAPI をデプロイするためのガイドを提供しています。 + +## FastAPI Cloud { #fastapi-cloud } + +**FastAPI Cloud** は、**FastAPI** の作者と同じチームによって作られています。 + +API の**構築**、**デプロイ**、**アクセス**までのプロセスを、最小限の手間で効率化します。 + +FastAPI でアプリを開発するときと同じ**開発者体験**を、クラウドへの**デプロイ**にももたらします。🎉 + +FastAPI Cloud は、*FastAPI and friends* オープンソースプロジェクトの主要なスポンサーかつ資金提供元です。✨ + +## クラウドプロバイダ - スポンサー { #cloud-providers-sponsors } + +他にもいくつかのクラウドプロバイダが ✨ [**FastAPI をスポンサーしています**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨。🙇 + +それらのガイドを参考にし、サービスを試してみるのもよいでしょう: + +* Render +* Railway diff --git a/docs/ja/docs/deployment/concepts.md b/docs/ja/docs/deployment/concepts.md index 787eb2e73..501a90bc9 100644 --- a/docs/ja/docs/deployment/concepts.md +++ b/docs/ja/docs/deployment/concepts.md @@ -29,7 +29,6 @@ ## セキュリティ - HTTPS { #security-https } - [前チャプターのHTTPSについて](https.md){.internal-link target=_blank}では、HTTPSがどのようにAPIを暗号化するのかについて学びました。 通常、アプリケーションサーバにとって**外部の**コンポーネントである**TLS Termination Proxy**によって提供されることが一般的です。このプロキシは通信の暗号化を担当します。 @@ -193,7 +192,6 @@ FastAPI アプリケーションでは、Uvicorn を実行する `fastapi` コ 同じAPIプログラムの**複数のプロセス**を実行する場合、それらは一般的に**Worker/ワーカー**と呼ばれます。 ### ワーカー・プロセス と ポート { #worker-processes-and-ports } - [HTTPSについて](https.md){.internal-link target=_blank}のドキュメントで、1つのサーバーで1つのポートとIPアドレスの組み合わせでリッスンできるのは1つのプロセスだけであることを覚えていますでしょうか? diff --git a/docs/ja/docs/deployment/docker.md b/docs/ja/docs/deployment/docker.md index 6c182448c..883f98c98 100644 --- a/docs/ja/docs/deployment/docker.md +++ b/docs/ja/docs/deployment/docker.md @@ -14,7 +14,7 @@ Linuxコンテナの使用には、**セキュリティ**、**反復可能性( Dockerfile Preview 👀 ```Dockerfile -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -166,7 +166,7 @@ def read_item(item_id: int, q: str | None = None): ```{ .dockerfile .annotate } # (1)! -FROM python:3.9 +FROM python:3.14 # (2)! WORKDIR /code @@ -392,7 +392,7 @@ FastAPI が単一のファイル、例えば `./app` ディレクトリのない そうすれば、`Dockerfile`の中にファイルをコピーするために、対応するパスを変更するだけでよいです: ```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -456,7 +456,7 @@ TraefikはDockerやKubernetesなどと統合されているので、コンテナ ## レプリケーション - プロセス数 { #replication-number-of-processes } -**Kubernetes** や Docker Swarm モード、Nomad、あるいは複数のマシン上で分散コンテナを管理するための同様の複雑なシステムを使ってマシンのclusterを構成している場合、 各コンテナで(Workerを持つUvicornのような)**プロセスマネージャ**を使用する代わりに、**クラスター・レベル**で**レプリケーション**を処理したいと思うでしょう。 +**Kubernetes** や Docker Swarm モード、Nomad、あるいは複数のマシン上で分散コンテナを管理するための同様の複雑なシステムを使ってマシンのクラスタを構成している場合、 各コンテナで(Workerを持つUvicornのような)**プロセスマネージャ**を使用する代わりに、**クラスター・レベル**で**レプリケーション**を処理したいと思うでしょう。 Kubernetesのような分散コンテナ管理システムの1つは通常、入ってくるリクエストの**ロードバランシング**をサポートしながら、**コンテナのレプリケーション**を処理する統合された方法を持っています。このことはすべて**クラスタレベル**にてです。 @@ -501,7 +501,7 @@ HTTPSに使われるものと同じ**TLS Termination Proxy**コンポーネン そのような場合、`--workers` コマンドラインオプションを使って、実行したいワーカー数を設定できます: ```{ .dockerfile .annotate } -FROM python:3.9 +FROM python:3.14 WORKDIR /code diff --git a/docs/ja/docs/deployment/fastapicloud.md b/docs/ja/docs/deployment/fastapicloud.md new file mode 100644 index 000000000..43d9ce0d0 --- /dev/null +++ b/docs/ja/docs/deployment/fastapicloud.md @@ -0,0 +1,65 @@ +# FastAPI Cloud { #fastapi-cloud } + +FastAPI Cloud に **コマンド1つ** でデプロイできます。まだならウェイティングリストにご登録ください。🚀 + +## ログイン { #login } + +すでに **FastAPI Cloud** アカウントをお持ちであることを確認してください(ウェイティングリストからご招待しています 😉)。 + +次にログインします: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +## デプロイ { #deploy } + +では、**コマンド1つ** でアプリをデプロイします: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +以上です!その URL からアプリにアクセスできます。✨ + +## FastAPI Cloud について { #about-fastapi-cloud } + +**FastAPI Cloud** は、**FastAPI** の作者とチームによって開発されています。 + +最小限の手間で API を**構築**・**デプロイ**・**利用**できるように工程を簡素化します。 + +FastAPI での開発と同じ**開発者体験**を、クラウドへの**デプロイ**にももたらします。🎉 + +さらに、アプリのデプロイ時に必要となる多くの事項も任せられます。例えば: + +* HTTPS +* レプリケーション(リクエストに基づくオートスケーリング付き) +* など + +FastAPI Cloud は、*FastAPI and friends* のオープンソースプロジェクトに対する主要スポンサーかつ資金提供元です。✨ + +## 他のクラウドプロバイダーへのデプロイ { #deploy-to-other-cloud-providers } + +FastAPI はオープンソースで標準に基づいています。お好みの任意のクラウドプロバイダーに FastAPI アプリをデプロイできます。 + +各クラウドプロバイダーのガイドに従って FastAPI アプリをデプロイしてください。🤓 + +## 自分のサーバーへのデプロイ { #deploy-your-own-server } + +この**デプロイ**ガイドの後半では詳細も解説します。何が起きているのか、何が必要なのか、そして自分のサーバーを含めて FastAPI アプリを自力でデプロイする方法が分かるようになります。🤓 diff --git a/docs/ja/docs/deployment/https.md b/docs/ja/docs/deployment/https.md index d5a6daf0c..8e588aadc 100644 --- a/docs/ja/docs/deployment/https.md +++ b/docs/ja/docs/deployment/https.md @@ -28,7 +28,7 @@ HTTPSは単に「有効」か「無効」かで決まるものだと思いがち * **デフォルトでは**、**IPアドレスごとに1つのHTTPS証明書**しか持てないことになります。 * これは、サーバーの規模やアプリケーションの規模に寄りません。 * しかし、これには**解決策**があります。 -* **TLS**プロトコル(HTTPの前に、TCPレベルで暗号化を処理するもの)には、**SNI**と呼ばれる**拡張**があります。 +* **TLS**プロトコル(HTTPの前に、TCPレベルで暗号化を処理するもの)には、**SNI**と呼ばれる**拡張**があります。 * このSNI拡張機能により、1つのサーバー(**単一のIPアドレス**を持つ)が**複数のHTTPS証明書**を持ち、**複数のHTTPSドメイン/アプリケーション**にサービスを提供できるようになります。 * これが機能するためには、**パブリックIPアドレス**でリッスンしている、サーバー上で動作している**単一の**コンポーネント(プログラム)が、サーバー内の**すべてのHTTPS証明書**を持っている必要があります。 * セキュアな接続を取得した**後**でも、通信プロトコルは**HTTPのまま**です。 @@ -66,7 +66,7 @@ Let's Encrypt以前は、これらの**HTTPS証明書**は信頼できる第三 ステップの初めは、**ドメイン名**を**取得すること**から始まるでしょう。その後、DNSサーバー(おそらく同じクラウドプロバイダー)に設定します。 -おそらくクラウドサーバー(仮想マシン)かそれに類するものを手に入れ、fixed **パブリックIPアドレス**を持つことになるでしょう。 +おそらくクラウドサーバー(仮想マシン)かそれに類するものを手に入れ、固定の **パブリックIPアドレス**を持つことになるでしょう。 DNSサーバーでは、**取得したドメイン**をあなたのサーバーのパプリック**IPアドレス**に向けるレコード(「`A record`」)を設定します。 diff --git a/docs/ja/docs/deployment/manually.md b/docs/ja/docs/deployment/manually.md index da382a9c5..1931cd87e 100644 --- a/docs/ja/docs/deployment/manually.md +++ b/docs/ja/docs/deployment/manually.md @@ -1,12 +1,82 @@ -# 手動デプロイ +# サーバーを手動で実行する { #run-a-server-manually } -**FastAPI** を手動でデプロイすることもできます。 +## fastapi run コマンドを使う { #use-the-fastapi-run-command } -以下の様なASGI対応のサーバをインストールする必要があります: +結論として、FastAPI アプリケーションを提供するには `fastapi run` を使います: -//// tab | Uvicorn +
-* Uvicorn, uvloopとhttptoolsを基にした高速なASGIサーバ。 +```console +$ fastapi run main.py + + FastAPI Starting production 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://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) +``` + +
+ +これでほとんどのケースは動作します。😎 + +このコマンドは、たとえばコンテナやサーバー内で **FastAPI** アプリを起動するのに使えます。 + +## ASGIサーバー { #asgi-servers } + +少し詳しく見ていきます。 + +FastAPI は、Python の Web フレームワークとサーバーのための標準である ASGI を使います。FastAPI は ASGI Web フレームワークです。 + +リモートのサーバーマシンで **FastAPI** アプリケーション(や他の ASGI アプリケーション)を実行するのに主に必要なのは **Uvicorn** のような ASGI サーバープログラムです。これは `fastapi` コマンドにデフォルトで含まれています。 + +他にもいくつかの選択肢があります: + +* Uvicorn: 高性能な ASGI サーバー。 +* Hypercorn: HTTP/2 や Trio に対応する ASGI サーバーなど。 +* Daphne: Django Channels のために作られた ASGI サーバー。 +* Granian: Python アプリケーション向けの Rust 製 HTTP サーバー。 +* NGINX Unit: 軽量で多用途な Web アプリケーションランタイム。 + +## サーバーマシンとサーバープログラム { #server-machine-and-server-program } + +名称に関する小さな注意点があります。💡 + +「サーバー」という言葉は、リモート/クラウド上のコンピュータ(物理/仮想マシン)と、そのマシン上で動作しているプログラム(例: Uvicorn)の両方を指すのに一般的に使われます。 + +一般に「サーバー」と書かれているときは、そのどちらかを指している可能性があることを覚えておいてください。 + +リモートマシンを指す場合、「サーバー」のほか「マシン」「VM(仮想マシン)」「ノード」などとも呼ばれます。いずれも通常 Linux を実行し、そこでプログラムを動かすリモートマシンを指します。 + +## サーバープログラムをインストール { #install-the-server-program } + +FastAPI をインストールすると、本番サーバーの Uvicorn が同梱されており、`fastapi run` コマンドで起動できます。 + +ただし、ASGI サーバーを手動でインストールすることもできます。 + +[仮想環境](../virtual-environments.md){.internal-link target=_blank}を作成して有効化し、サーバーアプリケーションをインストールしてください。 + +例として、Uvicorn をインストールするには:
@@ -18,37 +88,21 @@ $ pip install "uvicorn[standard]"
-//// +他の ASGI サーバープログラムでも同様の手順です。 /// tip | 豆知識 -`standard` を加えることで、Uvicornがインストールされ、いくつかの推奨される依存関係を利用するようになります。 +`standard` を付けると、Uvicorn は推奨の追加依存関係もインストールして使用します。 -これには、`asyncio` の高性能な完全互換品である `uvloop` が含まれ、並行処理のパフォーマンスが大幅に向上します。 +その中には、`asyncio` の高性能なドロップイン代替であり、大きな並行実行性能の向上をもたらす `uvloop` も含まれます。 + +`pip install "fastapi[standard]"` のように FastAPI をインストールした場合は、すでに `uvicorn[standard]` も含まれます。 /// -//// tab | Hypercorn +## サーバープログラムを起動 { #run-the-server-program } -* Hypercorn, HTTP/2にも対応しているASGIサーバ。 - -
- -```console -$ pip install hypercorn - ----> 100% -``` - -
- -...または、これら以外のASGIサーバ。 - -//// - -そして、チュートリアルと同様な方法でアプリケーションを起動して下さい。ただし、以下の様に`--reload` オプションは使用しないで下さい: - -//// tab | Uvicorn +ASGI サーバーを手動でインストールした場合、通常は FastAPI アプリケーションをインポートさせるために、特別な形式のインポート文字列を渡す必要があります:
@@ -60,26 +114,44 @@ $ uvicorn main:app --host 0.0.0.0 --port 80
-//// +/// note | 備考 -//// tab | Hypercorn +`uvicorn main:app` というコマンドは次を指します: -
+* `main`: ファイル `main.py`(Python の「モジュール」)。 +* `app`: `main.py` 内で `app = FastAPI()` により作成されたオブジェクト。 -```console -$ hypercorn main:app --bind 0.0.0.0:80 +これは次と等価です: -Running on 0.0.0.0:8080 over http (CTRL + C to quit) +```Python +from main import app ``` -
+/// -//// +他の ASGI サーバープログラムでも同様のコマンドがあり、詳細はそれぞれのドキュメントを参照してください。 -停止した場合に自動的に再起動させるツールを設定したいかもしれません。 +/// warning | 注意 -さらに、GunicornをインストールしてUvicornのマネージャーとして使用したり、複数のワーカーでHypercornを使用したいかもしれません。 +Uvicorn などのサーバーは、開発時に便利な `--reload` オプションをサポートしています。 -ワーカー数などの微調整も行いたいかもしれません。 +しかし `--reload` は多くのリソースを消費し、不安定になるなどの性質があります。 -しかしこれら全てをやろうとすると、自動的にこれらを行うDockerイメージを使う方が楽かもしれません。 +開発中には非常に役立ちますが、 本番環境では使用すべきではありません。 + +/// + +## デプロイの概念 { #deployment-concepts } + +これらの例は、サーバープログラム(例: Uvicorn)を実行し、事前に決めたポート(例: `80`)で、すべての IP(`0.0.0.0`)をリッスンする「単一プロセス」を起動します。 + +これが基本的な考え方です。ただし、次のような追加事項にも対応したくなるでしょう: + +* セキュリティ - HTTPS +* 起動時に実行 +* 再起動 +* レプリケーション(実行プロセス数) +* メモリ +* 起動前の事前ステップ + +これらの各概念についての考え方や、対処するための具体例・戦略を次の章で説明します。🚀 diff --git a/docs/ja/docs/environment-variables.md b/docs/ja/docs/environment-variables.md index 45dbfc71f..4deae7044 100644 --- a/docs/ja/docs/environment-variables.md +++ b/docs/ja/docs/environment-variables.md @@ -153,7 +153,7 @@ Hello World from Python /// tip | 豆知識 -詳しくは The Twelve-Factor App: Config を参照してください。 +詳しくは The Twelve-Factor App: 設定 を参照してください。 /// @@ -291,7 +291,7 @@ $ C:\opt\custompython\bin\python これで、**環境変数**とは何か、Pythonでどのように使用するかについて、基本的な理解が得られたはずです。 -環境変数についての詳細は、Wikipedia for Environment Variable も参照してください。 +環境変数についての詳細は、Wikipedia の環境変数 も参照してください。 多くの場合、環境変数がどのように役立ち、すぐに適用できるのかはあまり明確ではありません。しかし、開発中のさまざまなシナリオで何度も登場するため、知っておくとよいでしょう。 diff --git a/docs/ja/docs/fastapi-cli.md b/docs/ja/docs/fastapi-cli.md new file mode 100644 index 000000000..20cc9c2fb --- /dev/null +++ b/docs/ja/docs/fastapi-cli.md @@ -0,0 +1,75 @@ +# FastAPI CLI { #fastapi-cli } + +**FastAPI CLI** は、FastAPI アプリの提供、FastAPI プロジェクトの管理などに使用できるコマンドラインプログラムです。 + +FastAPI をインストールすると(例: `pip install "fastapi[standard]"`)、`fastapi-cli` というパッケージが含まれます。このパッケージがターミナルで使用する `fastapi` コマンドを提供します。 + +開発用に FastAPI アプリを起動するには、`fastapi dev` コマンドを使用できます: + +
+ +```console +$ 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 CLI は、Python プログラムへのパス(例: `main.py`)を受け取り、`FastAPI` インスタンス(通常は `app`)を自動検出し、適切な import 方法を判断して提供します。 + +本番環境では代わりに `fastapi run` を使用します。🚀 + +内部的には、**FastAPI CLI** は Uvicorn(高性能で本番運用向けの ASGI サーバー)を使用します。😎 + +## `fastapi dev` { #fastapi-dev } + +`fastapi dev` を実行すると、開発モードが有効になります。 + +デフォルトでは、**auto-reload** が有効です。コードを変更するとサーバーが自動で再読み込みされます。これはリソースを多く消費し、無効時より安定性が低くなる可能性があります。開発時のみに使用してください。また、IP アドレス `127.0.0.1`(マシン自身のみと通信するための IP、`localhost`)で待ち受けます。 + +## `fastapi run` { #fastapi-run } + +`fastapi run` を実行すると、デフォルトで本番モードで起動します。 + +デフォルトでは、**auto-reload** は無効です。また、IP アドレス `0.0.0.0`(利用可能なすべての IP アドレスを意味します)で待ち受けるため、そのマシンと通信できる任意のクライアントから公開アクセスが可能になります。これは、たとえばコンテナ内など、本番環境で一般的な実行方法です。 + +多くの場合(そして推奨されるのは)、上位に HTTPS を終端する「termination proxy」を置きます。これはアプリのデプロイ方法に依存し、プロバイダが代行する場合もあれば、自分で設定する必要がある場合もあります。 + +/// tip | 豆知識 + +詳しくは、[デプロイのドキュメント](deployment/index.md){.internal-link target=_blank}を参照してください。 + +/// diff --git a/docs/ja/docs/features.md b/docs/ja/docs/features.md index f78eab430..40a1d2e60 100644 --- a/docs/ja/docs/features.md +++ b/docs/ja/docs/features.md @@ -1,54 +1,55 @@ -# 機能 +# 機能 { #features } -## FastAPIの機能 +## FastAPIの機能 { #fastapi-features } -**FastAPI** は以下の機能をもちます: +**FastAPI** は次のものを提供します: -### オープンスタンダード準拠 +### オープンスタンダード準拠 { #based-on-open-standards } -* API作成のためのOpenAPI。これは、path operationsの宣言、パラメータ、ボディリクエスト、セキュリティなどを含んでいます。 -* JSONスキーマを使用したデータモデルのドキュメント自動生成(OpenAPIはJSONスキーマに基づいている)。 -* 綿密な調査の結果、上層に後付けするのではなく、これらの基準に基づいて設計されました。 +* API 作成のための OpenAPIpath operations、パラメータ、リクエストボディ、セキュリティなどの宣言を含みます。 +* JSON Schema によるデータモデルの自動ドキュメント化(OpenAPI 自体が JSON Schema に基づいています)。 +* 入念な調査のうえ、これらの標準を中心に設計されています。後付けのレイヤーではありません。 * これにより、多くの言語で自動 **クライアントコード生成** が可能です。 -### 自動ドキュメント生成 -対話的なAPIドキュメントと探索的なwebユーザーインターフェースを提供します。フレームワークはOpenAPIを基にしているため、いくつかのオプションがあり、デフォルトで2つ含まれています。 +### 自動ドキュメント { #automatic-docs } -* Swagger UIで、インタラクティブな探索をしながら、ブラウザから直接APIを呼び出してテストが行えます。 +対話的な API ドキュメントと探索的な Web ユーザーインターフェース。フレームワークは OpenAPI に基づいているため、複数のオプションがあり、デフォルトで 2 つ含まれます。 + +* Swagger UI。インタラクティブに探索しつつ、ブラウザから直接 API を呼び出してテストできます。 ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* ReDocを使用したもう一つのAPIドキュメント生成。 +* ReDoc による代替の API ドキュメント。 ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### 現代的なPython +### 現代的なPythonのみ { #just-modern-python } -FastAPIの機能はすべて、標準のPython 3.8型宣言に基づいています(Pydanticの功績)。新しい構文はありません。ただの現代的な標準のPythonです。 +すべて標準の **Python の型** 宣言(Pydantic に感謝)に基づいています。新しい構文を学ぶ必要はありません。標準的でモダンな Python だけです。 -(FastAPIを使用しない場合でも)Pythonの型の使用方法について簡単な復習が必要な場合は、短いチュートリアル([Python Types](python-types.md){.internal-link target=_blank})を参照してください。 +(FastAPI を使わない場合でも)Python の型の使い方を 2 分で復習したい場合は、短いチュートリアル [Python Types](python-types.md){.internal-link target=_blank} を参照してください。 -型を使用した標準的なPythonを記述します: +型を使った標準的な Python を記述します: ```Python from datetime import date from pydantic import BaseModel -# Declare a variable as a str -# and get editor support inside the function +# 変数を str として宣言 +# そして関数内でエディタ支援を受ける def main(user_id: str): return user_id -# A Pydantic model +# Pydantic モデル class User(BaseModel): id: int name: str joined: date ``` -これは以下のように用いられます: +これは次のように使えます: ```Python my_user: User = User(id=3, name="John Doe", joined="2018-07-19") @@ -62,143 +63,139 @@ second_user_data = { my_second_user: User = User(**second_user_data) ``` -/// info | 情報 +/// info -`**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")` と同等です。 /// -### エディタのサポート +### エディタのサポート { #editor-support } -すべてのフレームワークは使いやすく直感的に使用できるように設計されており、すべての決定は開発を開始する前でも複数のエディターでテストされ、最高の開発体験が保証されます。 +フレームワーク全体が使いやすく直感的になるよう設計されており、最高の開発体験を確保するため、開発開始前から複数のエディタであらゆる判断が検証されています。 -前回のPython開発者調査では、最も使用されている機能が「オートコンプリート」であることが明らかになりました。 +Python 開発者調査では、最もよく使われる機能の 1 つが「オートコンプリート」であることが明らかです。 -**FastAPI** フレームワークは、この要求を満たすことを基本としています。オートコンプリートはどこでも機能します。 +**FastAPI** はその要求を満たすことを基盤にしています。オートコンプリートはどこでも機能します。 ドキュメントに戻る必要はほとんどありません。 -エディターがどのように役立つかを以下に示します: +エディタがどのように役立つかの例です: -* Visual Studio Codeの場合: +* Visual Studio Code の場合: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -* PyCharmの場合: +* PyCharm の場合: ![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) -以前は不可能だと考えていたコードでさえ補完されます。例えば、リクエストからのJSONボディ(ネストされている可能性がある)内の `price`キーです。 +以前は不可能だと思っていたコードでも補完が得られます。例えば、リクエストから届く(ネストされている可能性のある)JSON ボディ内の `price` キーなどです。 -間違ったキー名を入力したり、ドキュメント間を行き来したり、上下にスクロールして`username`と`user_name`のどちらを使用したか調べたりする必要はもうありません。 +もう間違ったキー名を入力したり、ドキュメントを行き来したり、上下にスクロールして最終的に `username` と `user_name` のどちらを使ったのか探す必要はありません。 -### 簡潔 +### 簡潔 { #short } -すべてに適切な**デフォルト**があり、オプションの構成ができます。必要なことを実行し、必要なAPIを定義するためにすべてのパラメーターを調整できます。 +すべてに妥当な **デフォルト** があり、どこでもオプションで構成できます。必要に応じてすべてのパラメータを微調整して、求める API を定義できます。 -ただし、デフォルトでもすべて **うまくいきます**。 +しかしデフォルトのままでも、すべて **うまく動きます**。 -### 検証 +### 検証 { #validation } -* 以下の様な、ほとんどの(すべての?)Python **データ型**の検証: - * JSONオブジェクト(`dict`) - * 項目の型を定義するJSON配列(`list`) - * 最小長と最大長のある文字列(`str`)フィールド - * 最小値と最大値のある数値(`int`、` float`) +* ほとんど(あるいはすべて?)の Python の **データ型** に対する検証: + * JSON オブジェクト(`dict`)。 + * 項目の型を定義する JSON 配列(`list`)。 + * 文字列(`str`)フィールドの最小/最大長。 + * 数値(`int`、`float`)の最小/最大値、など。 -* よりエキゾチックな型の検証: - * URL - * Eメール - * UUID - * ...その他 +* よりエキゾチックな型の検証: + * URL。 + * Email。 + * UUID。 + * ...その他。 -すべての検証は、確立された堅牢な **Pydantic** によって処理されます。 +すべての検証は、確立され堅牢な **Pydantic** によって処理されます。 -### セキュリティと認証 +### セキュリティと認証 { #security-and-authentication } -セキュリティと認証が統合されています。 データベースまたはデータモデルについても妥協していません。 +セキュリティと認証が統合されています。データベースやデータモデルとの妥協はありません。 -以下のOpenAPIで定義されているすべてのセキュリティスキームを含む: +OpenAPI で定義されたすべてのセキュリティスキームをサポートします: -* HTTPベーシック -* **OAuth2**(**JWTトークン**も使用)。 JWTを使用したOAuth2のチュートリアル([OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank})を確認してください。 -* APIキー: - * ヘッダー - * クエリパラメータ - * クッキー、等 +* HTTP Basic。 +* **OAuth2**(**JWT トークン** も可)。チュートリアル [JWT を用いた OAuth2](tutorial/security/oauth2-jwt.md){.internal-link target=_blank} を確認してください。 +* API キー(以下の場所): + * ヘッダー。 + * クエリパラメータ。 + * クッキー、など。 -さらに、Starletteのすべてのセキュリティ機能も含みます(**セッションCookie**を含む)。 +さらに、Starlette のすべてのセキュリティ機能(**セッション Cookie** を含む)も利用できます。 -これらは、システム、データストア、リレーショナルデータベース、NoSQLデータベースなどと簡単に統合できる再利用可能なツールとコンポーネントとして構築されています。 +これらはすべて再利用可能なツールやコンポーネントとして構築されており、システム、データストア、リレーショナル/NoSQL データベース等と容易に統合できます。 -### 依存性の注入(Dependency Injection) +### 依存性の注入 { #dependency-injection } -FastAPIには非常に使いやすく、非常に強力な依存性の注入システムを備えています。 +FastAPI には、非常に使いやすく、かつ非常に強力な 依存性の注入 システムがあります。 -* 依存関係でさえも依存関係を持つことができ、階層または **依存関係の"グラフ"** を作成することができます。 +* 依存関係は依存関係を持つこともでき、階層または **依存関係の「グラフ」** を作成できます。 +* すべてフレームワークによって**自動的に処理**されます。 +* すべての依存関係はリクエストからデータを要求でき、*path operation* の制約と自動ドキュメントを**拡張**できます。 +* 依存関係で定義された *path operation* のパラメータについても**自動検証**されます。 +* 複雑なユーザー認証システム、**データベース接続** などのサポート。 +* **データベースやフロントエンド等との妥協は不要**。すべてと簡単に統合できます。 -* フレームワークによってすべて**自動的に処理**されます。 -* すべての依存関係はリクエストからのデータを要請できて、**path operationsの制約と自動ドキュメンテーションを拡張できます**。 -* 依存関係で定義された *path operation* パラメータも**自動検証**が可能です。 -* 複雑なユーザー認証システム、**データベース接続**などのサポート -* **データベース、フロントエンドなどに対する妥協はありません**。それらすべてと簡単に統合できます。 +### 無制限の「プラグイン」 { #unlimited-plug-ins } -### 無制限の「プラグイン」 +別の言い方をすれば、プラグインは不要で、必要なコードをインポートして使うだけです。 -他の方法では、それらを必要とせず、必要なコードをインポートして使用します。 +あらゆる統合は(依存関係を用いて)非常に簡単に使えるよう設計されており、*path operation* で使うのと同じ構造と構文で、2 行のコードでアプリケーション用の「プラグイン」を作れます。 -統合は非常に簡単に使用できるように設計されており(依存関係を用いて)、*path operations* で使用されているのと同じ構造と構文を使用して、2行のコードでアプリケーションの「プラグイン」を作成できます。 +### テスト済み { #tested } +* 100% の テストカバレッジ。 +* 100% 型アノテーション付きのコードベース。 +* 本番アプリケーションで使用されています。 -### テスト +## Starletteの機能 { #starlette-features } -* テストカバレッジ 100% -* 型アノテーション100%のコードベース -* 本番アプリケーションで使用されます +**FastAPI** は Starlette と完全に互換性があり(かつそれに基づいています)。そのため、手元の Starlette の追加コードも動作します。 -## Starletteの機能 +`FastAPI` は実際には `Starlette` のサブクラスです。すでに Starlette を知っている、あるいは使っているなら、ほとんどの機能は同じように動作します。 -**FastAPI**は、Starlette と完全に互換性があります(そしてベースになっています)。したがって、追加のStarletteコードがあれば、それも機能します。 +**FastAPI** では **Starlette** のすべての機能が利用できます(FastAPI は強化された Starlette にすぎません): -`FastAPI`は実際には`Starlette`のサブクラスです。したがって、Starletteをすでに知っているか使用している場合は、ほとんどの機能が同じように機能します。 - -**FastAPI**を使用すると、以下のような、**Starlette**のすべての機能を利用できます(FastAPIはStarletteを強化したものにすぎないため): - -* 見事なパフォーマンス。 **NodeJS**および**Go**に匹敵する、最速のPythonフレームワークの1つです。 - -* **WebSocket**のサポート -* **GraphQL**のサポート -* プロセス内バックグラウンドタスク -* 起動およびシャットダウンイベント -* `httpx`に基づいて構築されたテストクライアント -* **CORS**、GZip、静的ファイル、ストリーミング応答 -* **セッションとCookie**のサポート -* テストカバレッジ100% -* 型アノテーション100%のコードベース - -## Pydanticの特徴 - -**FastAPI**はPydantic と完全に互換性があります(そしてベースになっています)。したがって、追加のPydanticコードがあれば、それも機能します。 - -データベースのためにORMsや、ODMsなどの、Pydanticに基づく外部ライブラリを備えています。 - -これは、すべてが自動的に検証されるため、多くの場合、リクエストから取得したオブジェクトを**データベースに直接**渡すことができるということを意味しています。 - -同じことがその逆にも当てはまり、多くの場合、データベースから取得したオブジェクトを**クライアントに直接**渡すことができます。 - -**FastAPI**を使用すると、**Pydantic**のすべての機能を利用できます(FastAPIがPydanticに基づいてすべてのデータ処理を行っているため)。 - -* **brainfuckなし**: - * スキーマ定義のためのマイクロ言語を新たに学習する必要はありません。 - * Pythonの型を知っている場合は、既にPydanticの使用方法を知っているに等しいです。 -* ユーザーの **IDE/リンター/思考 とうまく連携します**: - * Pydanticのデータ構造は、ユーザーが定義するクラスの単なるインスタンスであるため、オートコンプリート、リンティング、mypy、およびユーザーの直感はすべて、検証済みのデータで適切に機能するはずです。 -* **複雑な構造**を検証: - * 階層的なPydanticモデルや、Pythonの「`typing`」の「`list`」と「`dict`」などの利用。 - * バリデーターにより、複雑なデータスキーマを明確かつ簡単に定義、チェックし、JSONスキーマとして文書化できます。 - * 深く**ネストされたJSON**オブジェクトを作成し、それらすべてを検証してアノテーションを付けることができます。 -* **拡張可能**: - * Pydanticでは、カスタムデータ型を定義できます。または、バリデーターデコレーターで装飾されたモデルのメソッドを使用して検証を拡張できます。 +* 圧倒的なパフォーマンス。利用可能な最速クラスの Python フレームワークの 1 つで、**NodeJS** や **Go** と同等です。 +* **WebSocket** のサポート。 +* プロセス内バックグラウンドタスク。 +* 起動およびシャットダウンイベント。 +* HTTPX に基づくテストクライアント。 +* **CORS**、GZip、静的ファイル、ストリーミングレスポンス。 +* **セッションと Cookie** のサポート。 +* テストカバレッジ 100%。 +* 型アノテーション 100% のコードベース。 + +## Pydanticの機能 { #pydantic-features } + +**FastAPI** は Pydantic と完全に互換性があり(かつそれに基づいています)。そのため、手元の Pydantic の追加コードも動作します。 + +Pydantic に基づく外部ライブラリ(データベース用の ORMODM など)も含まれます。 + +これは、すべてが自動的に検証されるため、多くの場合、リクエストから取得したオブジェクトを **そのままデータベースに** 渡せることを意味します。 + +逆方向も同様で、多くの場合、データベースから取得したオブジェクトを **そのままクライアントに** 渡せます。 + +**FastAPI** では **Pydantic** のすべての機能が利用できます(FastAPI はデータ処理のすべてで Pydantic に基づいています): + +* **brainfuck なし**: + * スキーマ定義のための新しいマイクロ言語を学ぶ必要はありません。 + * Python の型を知っていれば、Pydantic の使い方もわかります。 +* **IDE/リンター/思考** と気持ちよく連携します: + * Pydantic のデータ構造は、あなたが定義するクラスの単なるインスタンスなので、オートコンプリート、リンティング、mypy、そしてあなたの直感が、検証済みデータに対して適切に機能します。 +* **複雑な構造** を検証: + * 階層的な Pydantic モデルや、Python の `typing` にある `List` や `Dict` などを利用できます。 + * さらにバリデータにより、複雑なデータスキーマを明確かつ容易に定義・検査でき、JSON Schema として文書化できます。 + * 深く **ネストされた JSON** オブジェクトを扱え、それらすべてを検証してアノテーションを付与できます。 +* **拡張可能**: + * Pydantic ではカスタムデータ型を定義できますし、バリデータデコレーターで装飾したモデルメソッドで検証を拡張できます。 * テストカバレッジ 100%。 diff --git a/docs/ja/docs/help-fastapi.md b/docs/ja/docs/help-fastapi.md index 8cf0c2163..ed91bb19f 100644 --- a/docs/ja/docs/help-fastapi.md +++ b/docs/ja/docs/help-fastapi.md @@ -1,101 +1,255 @@ -# FastAPIを応援 - ヘルプの入手 +# FastAPIを応援 - ヘルプの入手 { #help-fastapi-get-help } **FastAPI** は気に入りましたか? -FastAPIやユーザーや開発者を応援したいですか? +FastAPIや他のユーザー、作者を応援したいですか? -もしくは、 **FastAPI** についてヘルプが必要ですか? +それとも **FastAPI** についてヘルプが必要ですか? -とても簡単に応援できます (ただ1、2回クリックするだけのものもあります)。 +とても簡単に応援できる方法があります(1、2回クリックするだけのものもあります)。 -また、ヘルプを入手する手段がいくつかあります。 +ヘルプを得る方法もいくつかあります。 -## GitHubで **FastAPI** にStar +## ニュースレターを購読 { #subscribe-to-the-newsletter } -GitHubでFastAPIに「Star」をつけることができます (右上部のStarボタンをクリック): https://github.com/fastapi/fastapi. ⭐️ +[**FastAPI and friends** ニュースレター](newsletter.md){.internal-link target=_blank}(配信はまれです)を購読すると、次の情報をキャッチアップできます: -スターを増やすことで、他のユーザーの目につきやすくなり、多くの人にとって便利なものであることを示せます。 +* FastAPI と関連プロジェクトのニュース 🚀 +* ガイド 📝 +* 機能 ✨ +* 互換性に影響する変更 🚨 +* ヒントやコツ ✅ -## GitHubレポジトリのリリースをWatch +## X (Twitter) で FastAPI をフォロー { #follow-fastapi-on-x-twitter } -GitHubでFastAPIを「Watch」できます (右上部のWatchボタンをクリック): https://github.com/fastapi/fastapi. 👀 +**X (Twitter)** で @fastapi をフォローして、**FastAPI** の最新情報を受け取りましょう。🐦 + +## GitHubで **FastAPI** にStar { #star-fastapi-in-github } + +GitHubでFastAPIに「Star」をつけることができます(右上部のStarボタンをクリック): https://github.com/fastapi/fastapi。⭐️ + +スターを増やすことで、他のユーザーの目につきやすくなり、すでに多くの人の役に立っていることが伝わります。 + +## GitHubレポジトリのリリースをWatch { #watch-the-github-repository-for-releases } + +GitHubでFastAPIを「Watch」できます(右上部の「Watch」ボタンをクリック): https://github.com/fastapi/fastapi。👀 そこで「Releases only」を選択できます。 -これを行うと、**FastAPI** バグ修正や新機能の実装などの新しいリリース (新しいバージョン) があるたびに (メールで) 通知を受け取れます。 +これを行うと、バグ修正や新機能を含む **FastAPI** の新しいリリース(新バージョン)があるたびに、(メールで)通知を受け取れます。 -## 開発者とつながる +## 開発者とつながる { #connect-with-the-author } -以下で、開発者 (Sebastián Ramírez / `tiangolo`) とコンタクトをとれます: +作者である私(Sebastián Ramírez / `tiangolo`)とつながれます。 + +できること: * **GitHub** でフォロー。 - * 他のオープンソースプロジェクトを確認できます。何かの助けになるものが見つかるかもしれません。 - * 新たなオープンソースプロジェクトを作成したときに通知されます。 -* **X (Twitter)** でフォロー。 - * FastAPIの使用用途を教えてください (聞いてみたいです)。 - * 新たなツールの発表やリリースが聞けます。 -* **Linkedin** でつながる。 - * 新たなツールの発表やリリースが聞けます (ただしX (Twitter)の方が利用頻度が高いですが 🤷‍♂)。 -* **Dev.to****Medium** で著作物を読む (またはフォロー)。 - * アイデアや作成ツールについての記事が読めます。 - * 新規記事の執筆を通知してくれます。 + * 役に立つかもしれない、私が作成した他のオープンソースプロジェクトを見られます。 + * 新しいオープンソースプロジェクトを作成したときにわかります。 +* **X (Twitter)** でフォロー または Mastodon。 + * あなたがどのようにFastAPIを使っているか教えてください(聞けると嬉しいです)。 + * 新しいツールの告知やリリースを聞けます。 + * さらに、X (Twitter) の @fastapi(別アカウント)もフォローできます。 +* **LinkedIn** でフォロー。 + * 新しいツールの告知やリリースを聞けます(ただしX (Twitter) の方をよく使っています 🤷‍♂)。 +* **Dev.to****Medium** で執筆内容を読む(またはフォロー)。 + * 私のアイデアや、作成したツールに関する記事を読めます。 + * 新しい記事を公開したときに読めます。 -## **FastAPI** に関するツイート +## **FastAPI** についてツイート { #tweet-about-fastapi } -**FastAPI** についてツイートし、開発者や他の人にどこが気に入ったのか教えてください。🎉 +**FastAPI** についてツイートして、なぜ気に入っているのかを私や他の人に教えてください。🎉 -**FastAPI** がどのように使われ、どこが気に入られ、どんなプロジェクト/会社で使われているかなどについて知りたいです。 +**FastAPI** がどのように使われているか、どこを気に入っているか、どのプロジェクト/会社で使っているか等、聞けると嬉しいです。 -## FastAPIに投票 +## FastAPIに投票 { #vote-for-fastapi } -* Slantで **FastAPI** に投票 -* AlternativeToで **FastAPI** に投票 -* awesome-restで **FastAPI** に投票 +* Slantで **FastAPI** に投票。 +* AlternativeToで **FastAPI** に投票。 +* StackShare で **FastAPI** を使っていると宣言。 -## GitHub issuesで他の人を助ける +## GitHubで質問に困っている人を助ける { #help-others-with-questions-in-github } -既存のissuesを確認して、他の人を助けてみてください。皆さんが回答を知っているかもしれない質問がほとんどです。🤓 +次の場所で、他の人の質問を手助けできます: -## GitHubレポジトリをWatch +* GitHub Discussions +* GitHub Issues -GitHubでFastAPIを「watch」できます (右上部の「watch」ボタンをクリック): https://github.com/fastapi/fastapi. 👀 +多くの場合、その質問の答えをすでに知っているかもしれません。🤓 -「Releases only」ではなく「Watching」を選択すると、新たなissueが立てられた際に通知されます。 +もし多くの人の質問に答えて助けてくれたなら、あなたは公式の[FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank}になります。🎉 -そして、issueを解決し他の人を助けることができます。 +最も大事なポイントは「親切であること」を心がけることです。人はフラストレーションを抱えてやって来るので、必ずしも最良の聞き方をしているとは限りませんが、できる限り親切に対応しましょう。🤗 -## issuesを立てる +**FastAPI** コミュニティは親切で歓迎的であることを目指しています。同時に、いじめや他者への無礼な振る舞いは受け入れないでください。お互いを大事にしましょう。 -GitHubレポジトリで新たなissueを立てられます。例えば: +--- -* 質問、または、問題の報告 -* 新機能の提案 +以下は(Discussions や Issues で)他の人の質問を手助けする方法です: -**Note**: issueを立てた人は、他の人の手助けもお願いします。😉 +### 質問を理解する { #understand-the-question } -## プルリクエストをする +* 質問者の「目的」やユースケースを理解できるか確認します。 -以下の様なプルリクエストを作成できます: +* 次に、質問(大半は質問です)が「明確」か確認します。 -* ドキュメントのタイプミスを修正。 -* 新たなドキュメントセクションを提案。 -* 既存のissue/バグを修正。 -* 新機能を追加。 +* 多くの場合、ユーザーが想像した解決策についての質問になっていますが、もっと「良い」方法があるかもしれません。問題やユースケースをよりよく理解できれば、より良い「代替解決策」を提案できるかもしれません。 -## 開発者のスポンサーになる +* 質問が理解できない場合は、さらに「詳細」を尋ねます。 -GitHub sponsorsを通して開発者を経済的にサポートできます。 +### 問題を再現する { #reproduce-the-problem } -そこで、感謝の気持ちを伝えるためにコーヒー☕️を買うことができます 😄。 +多くのケースや質問は、その人の「元のコード」に関係しています。 -## FastAPIを強化するツールのスポンサーになる +しばしばコードの断片だけが共有されますが、それでは問題を「再現」するには不十分です。 -ドキュメントで見たように、FastAPIはStarletteとPydanticという巨人の肩に乗っています。 +* ローカルで同じエラーや挙動を確認できるように、またはユースケースをよりよく理解できるように、**コピー&ペースト**して実行できる最小の再現可能な例の提供を依頼できます。 -以下のスポンサーになることもできます: +* とても寛大な気分なら、問題の説明だけをもとに、あなた自身でそのような**例を作成**してみることもできます。ただし時間がかかる可能性が高いので、まずは問題の明確化を依頼した方が良い場合もあります。 -* Samuel Colvin (Pydantic) -* Encode (Starlette, Uvicorn) +### 解決策を提案する { #suggest-solutions } + +* 質問を理解できたら、可能な**回答**を提示できます。 + +* 多くの場合、相手の「根本的な問題やユースケース」を理解することが重要です。相手が試している方法より良い解決方法があるかもしれないからです。 + +### クローズを依頼する { #ask-to-close } + +もし相手が返信してきて、あなたが問題を解決できたなら、おめでとう、**あなたはヒーロー**です!🦸 + +* その場合、次のように依頼できます: + + * GitHub Discussions: コメントを**回答**としてマークしてもらう。 + * GitHub Issues: issue を**クローズ**してもらう。 + +## GitHubレポジトリをWatch { #watch-the-github-repository } + +GitHubでFastAPIを「Watch」できます(右上部の「Watch」ボタンをクリック): https://github.com/fastapi/fastapi。👀 + +「Releases only」ではなく「Watching」を選択すると、新しい issue や質問が作成されたときに通知を受け取れます。新しい issue のみ、Discussions のみ、PR のみ、など通知対象を絞ることもできます。 + +その上で、そうした質問の解決を手助けできます。 + +## 質問する { #ask-questions } + +GitHubレポジトリで新しい質問を作成できます。例えば: + +* **質問**をする、または**問題**について尋ねる。 +* 新しい**機能**を提案する。 + +**Note**: もしそうするなら、他の人の手助けもお願いします。😉 + +## プルリクエストをレビュー { #review-pull-requests } + +他の人からのプルリクエストのレビューを手伝ってもらえます。 + +ここでも、できる限り親切であるようにしてください。🤗 + +--- + +プルリクエストをレビューするときのポイントです: + +### 問題を理解する { #understand-the-problem } + +* まず、そのプルリクエストが解決しようとしている**問題を理解**してください。長めの議論が GitHub Discussion や issue にあるかもしれません。 + +* その問題は実は**別の方法**で解決でき、プルリクエスト自体が不要な場合もあります。その場合は、その提案や質問をしても良いでしょう。 + +### スタイルは気にしすぎない { #dont-worry-about-style } + +* コミットメッセージのスタイルなどはあまり気にしなくて大丈夫です。私は squash and merge を使い、コミットを手動で調整します。 + +* スタイルルールについても心配無用です。自動化ツールがすでにチェックしています。 + +ほかにスタイルや一貫性の要件があれば、私から直接依頼しますし、必要な変更を上に積む形でコミットを追加します。 + +### コードを確認 { #check-the-code } + +* コードを確認して読み、妥当かどうかを見て、**ローカルで実行**し、本当に問題を解決しているか確かめてください。 + +* そのうえで、それを行ったことを**コメント**で伝えてください。そうすれば、実際に確認してくれたとわかります。 + +/// info | 情報 + +残念ながら、承認が複数ついただけのPRを、そのまま信頼することはできません。 + +説明が魅力的なためか、3件、5件以上の承認がついていても、実際にPRを確認すると壊れていたり、バグがあったり、主張する問題を解決していなかったりすることが何度もありました。😅 + +ですので、実際にコードを読み、実行して確認し、それをコメントで知らせてもらえることが本当に重要です。🤓 + +/// + +* もしPRを簡素化できそうなら、その依頼をしても構いませんが、細かい点にこだわり過ぎる必要はありません。主観的な見方が多く(私にもあります 🙈)、基本的な点に集中できるとより良いでしょう。 + +### テスト { #tests } + +* PRに**テスト**があるか確認を手伝ってください。 + +* PR前はテストが**失敗**することを確認します。🚨 + +* そしてPR後にテストが**成功**することを確認します。✅ + +* 多くのPRにはテストがありません。テストの追加を**リマインド**したり、テストを**提案**したりできます。これは最も時間を消費する部分の一つで、大いに助けになります。 + +* 何を試したかもコメントしてください。そうすれば、確認してくれたことがわかります。🤓 + +## プルリクエストを作成 { #create-a-pull-request } + +[貢献](contributing.md){.internal-link target=_blank}として、次のようにプルリクエストでソースコードに貢献できます: + +* ドキュメントで見つけたタイポの修正。 +* 自分が作成/発見した FastAPI に関する記事・動画・ポッドキャストを、このファイルを編集して共有。 + * 該当セクションの先頭にリンクを追加してください。 +* 自分の言語への[ドキュメント翻訳を手伝う](contributing.md#translations){.internal-link target=_blank}。 + * 他の人が作成した翻訳のレビューも手伝えます。 +* 新しいドキュメントセクションの提案。 +* 既存のissue/バグの修正。 + * テストを追加してください。 +* 新機能の追加。 + * テストを追加してください。 + * 関連があればドキュメントも追加してください。 + +## FastAPIのメンテナンスを手伝う { #help-maintain-fastapi } + +**FastAPI** のメンテナンスを手伝ってください!🤓 + +やることはたくさんあり、その多くは**あなた**にもできます。 + +今すぐできる主なタスクは次のとおりです: + +* [GitHubで質問に困っている人を助ける](#help-others-with-questions-in-github){.internal-link target=_blank}(上のセクションを参照)。 +* [プルリクエストをレビュー](#review-pull-requests){.internal-link target=_blank}(上のセクションを参照)。 + +この2つが**最も時間を消費**します。FastAPI のメンテナンス作業の中心です。 + +これを手伝ってもらえると、**FastAPIのメンテナンスに貢献**し、**より速く・より良く前進**できるようになります。🚀 + +## チャットに参加 { #join-the-chat } + +👥 Discord チャットサーバー 👥 に参加し、FastAPI コミュニティのみんなと交流しましょう。 + +/// tip | 豆知識 + +質問は GitHub Discussions に投稿してください。そこなら[FastAPI Experts](fastapi-people.md#fastapi-experts){.internal-link target=_blank}から助けてもらえる可能性がずっと高いです。 + +チャットは一般的な会話のみに使いましょう。 + +/// + +### 質問でチャットを使わない { #dont-use-the-chat-for-questions } + +チャットは「自由な会話」がしやすいため、一般的すぎて答えにくい質問になりがちです。そのため、回答が得られない可能性があります。 + +GitHub では、テンプレートが正しい形で質問を書くのを助けてくれるため、良い回答を得やすくなりますし、質問する前に自分で問題を解決できることもあります。さらにGitHubなら、時間がかかっても私が必ずすべてに回答できるようにできます。チャットでは私個人にはそれができません。😅 + +チャットでの会話はGitHubほど検索しやすくないため、質問と回答が会話に埋もれがちです。そして、[FastAPI Expert](fastapi-people.md#fastapi-experts){.internal-link target=_blank}になるためにカウントされるのはGitHub上の活動だけです。ですから、GitHubの方が注目を集めやすいでしょう。 + +一方で、チャットには数千人のユーザーがいるため、ほぼ常に誰かと会話できる可能性が高いです。😄 + +## 作者をスポンサー { #sponsor-the-author } + +あなたの**製品/会社**が **FastAPI** に依存している、または関連しており、そのユーザーにリーチしたい場合は、GitHub sponsors を通じて作者(私)を支援できます。プランに応じて、ドキュメントにバッジが表示されるなどの特典がある場合があります。🎁 --- diff --git a/docs/ja/docs/history-design-future.md b/docs/ja/docs/history-design-future.md index 6cfd1894d..146555708 100644 --- a/docs/ja/docs/history-design-future.md +++ b/docs/ja/docs/history-design-future.md @@ -1,13 +1,12 @@ -# 歴史、設計、そしてこれから +# 歴史、設計、そしてこれから { #history-design-and-future } -少し前に、**FastAPI** -のユーザーに以下の様に尋ねられました: +少し前に、**FastAPI**のユーザーに以下の様に尋ねられました: > このプロジェクトの歴史は?何もないところから、数週間ですごいものができているようです。 [...] これがその歴史のほんの一部です。 -## 代替手段 +## 代替手段 { #alternatives } 数年前から、私は複雑な要件を持つAPI (機械学習、分散システム、非同期ジョブ、NoSQLデータベースなど) を作成しており、いくつかの開発者チームを率いています。 @@ -19,7 +18,7 @@
-**FastAPI**は、代替ツールのこれまでの働きがなければ存在しなかったでしょう。 +**FastAPI**は、他の人々のこれまでの働きがなければ存在しなかったでしょう。 以前に作られた多くのツールが、作成における刺激として役立ってきました。 @@ -29,7 +28,7 @@
-## 調査 +## 調査 { #investigation } すべて既存の代替手段を使うことで、そのすべてを学び、アイデアを得て、自分や一緒に仕事をしてきた開発者のチームにとって最良の方法で組み合わせる機会を得ました。 @@ -39,7 +38,7 @@ そこで、**FastAPI**のコードを書き始める前に、OpenAPI、JSON Schema、OAuth2などの仕様を数ヶ月かけて勉強し、それらの関係、重複する箇所、相違点を理解しました。 -## 設計 +## 設計 { #design } その後、 (FastAPIを使う開発者として) ユーザーが欲しい「API」の設計に時間を費やしました。 @@ -53,19 +52,19 @@ すべての箇所で、すべての開発者に最高の開発体験を提供しました。 -## 要件 +## 要件 { #requirements } いくつかの代替手法を試したあと、私は**Pydantic**の強みを利用することを決めました。 そして、JSON Schemaに完全に準拠するようにしたり、制約宣言を定義するさまざまな方法をサポートしたり、いくつかのエディターでのテストに基づいてエディターのサポート (型チェック、自動補完) を改善するために貢献しました。 -開発中、もう1つの重要な鍵となる**Starlette**、にも貢献しました。 +開発中、もう1つの重要な鍵となる**Starlette**にも貢献しました。 -## 開発 +## 開発 { #development } 私が**FastAPI**自体の作成を開始した時には、ほとんどの部分がすでに準備されており、設計が定義され、必要な条件とツールの準備ができていました。そして規格や仕様に関する知識が、明確になり、更新されていました。 -## これから +## これから { #future } この時点ですでに、これらのアイデアを持った**FastAPI**が多くの人の役に立っていることは明らかです。 diff --git a/docs/ja/docs/how-to/authentication-error-status-code.md b/docs/ja/docs/how-to/authentication-error-status-code.md new file mode 100644 index 000000000..9bef47644 --- /dev/null +++ b/docs/ja/docs/how-to/authentication-error-status-code.md @@ -0,0 +1,17 @@ +# 古い 403 認証エラーのステータスコードを使う { #use-old-403-authentication-error-status-codes } + +FastAPI バージョン `0.122.0` より前は、統合されたセキュリティユーティリティが認証に失敗してクライアントへエラーを返す際、HTTP ステータスコード `403 Forbidden` を使用していました。 + +FastAPI バージョン `0.122.0` 以降では、より適切な HTTP ステータスコード `401 Unauthorized` を使用し、HTTP 仕様に従ってレスポンスに妥当な `WWW-Authenticate` ヘッダーを含めます。RFC 7235RFC 9110。 + +しかし、何らかの理由でクライアントが従来の挙動に依存している場合は、セキュリティクラスでメソッド `make_not_authenticated_error` をオーバーライドすることで、その挙動に戻せます。 + +たとえば、既定の `401 Unauthorized` エラーの代わりに `403 Forbidden` エラーを返す `HTTPBearer` のサブクラスを作成できます: + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} + +/// tip | 豆知識 + +この関数は例外インスタンスを返す点に注意してください。ここでは例外を送出しません。送出は内部の他のコードで行われます。 + +/// diff --git a/docs/ja/docs/how-to/conditional-openapi.md b/docs/ja/docs/how-to/conditional-openapi.md index 9478f5c03..0febe1ef6 100644 --- a/docs/ja/docs/how-to/conditional-openapi.md +++ b/docs/ja/docs/how-to/conditional-openapi.md @@ -10,7 +10,7 @@ もしセキュリティ上の欠陥がソースコードにあるならば、それは存在したままです。 -ドキュメンテーションを非表示にするのは、単にあなたのAPIへのアクセス方法を難解にするだけでなく、同時にあなた自身の本番環境でのAPIのデバッグを困難にしてしまう可能性があります。単純に、 Security through obscurity の一つの形態として考えられるでしょう。 +ドキュメンテーションを非表示にするのは、単にあなたのAPIへのアクセス方法を難解にするだけでなく、同時にあなた自身の本番環境でのAPIのデバッグを困難にしてしまう可能性があります。単純に、 秘匿によるセキュリティ の一つの形態として考えられるでしょう。 もしあなたのAPIのセキュリティを強化したいなら、いくつかのよりよい方法があります。例を示すと、 @@ -29,7 +29,7 @@ 例えば、 -{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} ここでは `openapi_url` の設定を、デフォルトの `"/openapi.json"` のまま宣言しています。 diff --git a/docs/ja/docs/how-to/configure-swagger-ui.md b/docs/ja/docs/how-to/configure-swagger-ui.md new file mode 100644 index 000000000..8be7adc84 --- /dev/null +++ b/docs/ja/docs/how-to/configure-swagger-ui.md @@ -0,0 +1,70 @@ +# Swagger UI の設定 { #configure-swagger-ui } + +いくつかの追加の Swagger UI パラメータを設定できます。 + +設定するには、`FastAPI()` のアプリオブジェクトを作成するとき、または `get_swagger_ui_html()` 関数に `swagger_ui_parameters` 引数を渡します。 + +`swagger_ui_parameters` は、Swagger UI に直接渡される設定を含む辞書を受け取ります。 + +FastAPI はそれらの設定を **JSON** に変換し、JavaScript と互換にします。Swagger UI が必要とするのはこの形式です。 + +## シンタックスハイライトを無効化 { #disable-syntax-highlighting } + +例えば、Swagger UI のシンタックスハイライトを無効化できます。 + +設定を変更しなければ、シンタックスハイライトはデフォルトで有効です: + + + +しかし、`syntaxHighlight` を `False` に設定すると無効化できます: + +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} + +...その場合、Swagger UI ではシンタックスハイライトが表示されなくなります: + + + +## テーマの変更 { #change-the-theme } + +同様に、キー `"syntaxHighlight.theme"`(途中にドットが含まれている点に注意)でシンタックスハイライトのテーマを設定できます: + +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} + +この設定により、シンタックスハイライトの配色テーマが変わります: + + + +## 既定の Swagger UI パラメータの変更 { #change-default-swagger-ui-parameters } + +FastAPI には、多くのユースケースに適した既定の設定パラメータが含まれています。 + +既定では次の設定が含まれます: + +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} + +引数 `swagger_ui_parameters` に別の値を指定することで、これらを上書きできます。 + +例えば、`deepLinking` を無効化するには、次の設定を `swagger_ui_parameters` に渡します: + +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} + +## その他の Swagger UI パラメータ { #other-swagger-ui-parameters } + +利用可能な他のすべての設定については、公式の Swagger UI パラメータのドキュメントを参照してください。 + +## JavaScript 専用の設定 { #javascript-only-settings } + +Swagger UI では、他にも **JavaScript 専用** のオブジェクト(例: JavaScript の関数)による設定が可能です。 + +FastAPI には、次の JavaScript 専用の `presets` 設定も含まれています: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +これらは文字列ではなく **JavaScript** のオブジェクトであるため、Python のコードから直接渡すことはできません。 + +そのような JavaScript 専用の設定を使う必要がある場合は、上記のいずれかの方法を使用し、Swagger UI の path operation をオーバーライドして、必要な JavaScript を手動で記述してください。 diff --git a/docs/ja/docs/how-to/custom-docs-ui-assets.md b/docs/ja/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 000000000..c0c9745b2 --- /dev/null +++ b/docs/ja/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,185 @@ +# カスタムドキュメント UI の静的アセット(セルフホスティング) { #custom-docs-ui-static-assets-self-hosting } + +API ドキュメントは **Swagger UI** と **ReDoc** を使用しており、それぞれにいくつかの JavaScript と CSS ファイルが必要です。 + +既定では、これらのファイルは CDN から配信されます。 + +しかし、カスタマイズすることも可能で、特定の CDN を指定したり、自分でファイルを配信したりできます。 + +## JavaScript と CSS のカスタム CDN { #custom-cdn-for-javascript-and-css } + +別の CDN を使いたいとします。例えば `https://unpkg.com/` を使いたい場合です。 + +例えば、一部の URL が制限されている国に住んでいる場合に役立ちます。 + +### 自動ドキュメントの無効化 { #disable-the-automatic-docs } + +最初の手順は自動ドキュメントを無効化することです。デフォルトではそれらは既定の CDN を使用します。 + +無効化するには、`FastAPI` アプリ作成時にそれらの URL を `None` に設定します: + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} + +### カスタムドキュメントの追加 { #include-the-custom-docs } + +これで、カスタムドキュメント用の *path operations* を作成できます。 + +FastAPI の内部関数を再利用してドキュメント用の HTML ページを生成し、必要な引数を渡せます: + +- `openapi_url`: ドキュメントの HTML ページが API の OpenAPI スキーマを取得する URL。ここでは属性 `app.openapi_url` を使用できます。 +- `title`: API のタイトル。 +- `oauth2_redirect_url`: 既定値を使うにはここで `app.swagger_ui_oauth2_redirect_url` を使用できます。 +- `swagger_js_url`: Swagger UI ドキュメント用の HTML が取得する JavaScript ファイルの URL。これはカスタム CDN の URL です。 +- `swagger_css_url`: Swagger UI ドキュメント用の HTML が取得する CSS ファイルの URL。これはカスタム CDN の URL です。 + +ReDoc についても同様です... + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} + +/// tip | 豆知識 + +`swagger_ui_redirect` 用の *path operation* は、OAuth2 を使用する場合の補助です。 + +API を OAuth2 プロバイダと統合すると、認証を実行して取得したクレデンシャルを持った状態で API ドキュメントに戻れます。そして実際の OAuth2 認証を用いてドキュメント上から API と対話できます。 + +Swagger UI がこの処理を裏側で行いますが、そのためにこの「redirect」の補助が必要です。 + +/// + +### テスト用の *path operation* を作成 { #create-a-path-operation-to-test-it } + +すべてが動作するかをテストできるように、*path operation* を作成します: + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} + +### テスト { #test-it } + +これで、http://127.0.0.1:8000/docs にアクセスしてページを再読み込みすると、新しい CDN からそれらのアセットが読み込まれるはずです。 + +## ドキュメント用 JavaScript と CSS のセルフホスティング { #self-hosting-javascript-and-css-for-docs } + +オフライン(インターネット非接続)でも、あるいはローカルネットワークで、アプリを動作させたい場合などには、JavaScript と CSS をセルフホストするのが有用です。 + +ここでは、同じ FastAPI アプリ内でそれらのファイルを配信し、ドキュメントでそれらを使用するように設定する方法を示します。 + +### プロジェクトのファイル構成 { #project-file-structure } + +プロジェクトのファイル構成が次のようになっているとします: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +これらの静的ファイルを保存するためのディレクトリを作成します。 + +新しいファイル構成は次のようになります: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### ファイルのダウンロード { #download-the-files } + +ドキュメントに必要な静的ファイルをダウンロードし、`static/` ディレクトリに配置します。 + +各リンクを右クリックして「リンク先を別名で保存...」のようなオプションを選べます。 + +**Swagger UI** では次のファイルを使用します: + +- `swagger-ui-bundle.js` +- `swagger-ui.css` + +そして **ReDoc** では次のファイルを使用します: + +- `redoc.standalone.js` + +その後、ファイル構成は次のようになります: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### 静的ファイルの配信 { #serve-the-static-files } + +- `StaticFiles` をインポートします。 +- 特定のパスに `StaticFiles()` インスタンスを「マウント」します。 + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} + +### 静的ファイルのテスト { #test-the-static-files } + +アプリケーションを起動し、http://127.0.0.1:8000/static/redoc.standalone.js にアクセスします。 + +**ReDoc** 用の非常に長い JavaScript ファイルが表示されるはずです。 + +先頭は次のようになっているかもしれません: + +```JavaScript +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): +... +``` + +これで、アプリから静的ファイルを配信できていること、そしてドキュメント用の静的ファイルを正しい場所に配置できていることが確認できます。 + +次に、ドキュメントでそれらの静的ファイルを使用するようにアプリを設定します。 + +### 静的ファイル用に自動ドキュメントを無効化 { #disable-the-automatic-docs-for-static-files } + +カスタム CDN を使う場合と同様、最初の手順は自動ドキュメントを無効化することです。既定では CDN を使用します。 + +無効化するには、`FastAPI` アプリ作成時にそれらの URL を `None` に設定します: + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} + +### 静的ファイル用のカスタムドキュメントを追加 { #include-the-custom-docs-for-static-files } + +カスタム CDN と同様の方法で、カスタムドキュメント用の *path operations* を作成できます。 + +再び、FastAPI の内部関数を再利用してドキュメント用の HTML ページを生成し、必要な引数を渡します: + +- `openapi_url`: ドキュメントの HTML ページが API の OpenAPI スキーマを取得する URL。ここでは属性 `app.openapi_url` を使用できます。 +- `title`: API のタイトル。 +- `oauth2_redirect_url`: 既定値を使うにはここで `app.swagger_ui_oauth2_redirect_url` を使用できます。 +- `swagger_js_url`: Swagger UI ドキュメント用の HTML が取得する **JavaScript** ファイルの URL。**これはあなたのアプリ自身がいま配信しているものです**。 +- `swagger_css_url`: Swagger UI ドキュメント用の HTML が取得する **CSS** ファイルの URL。**これはあなたのアプリ自身がいま配信しているものです**。 + +ReDoc についても同様です... + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} + +/// tip | 豆知識 + +`swagger_ui_redirect` 用の *path operation* は、OAuth2 を使用する場合の補助です。 + +API を OAuth2 プロバイダと統合すると、認証を実行して取得したクレデンシャルを持った状態で API ドキュメントに戻れます。そして実際の OAuth2 認証を用いてドキュメント上から API と対話できます。 + +Swagger UI がこの処理を裏側で行いますが、そのためにこの「redirect」の補助が必要です。 + +/// + +### 静的ファイルをテストするための *path operation* を作成 { #create-a-path-operation-to-test-static-files } + +すべてが動作するかをテストできるように、*path operation* を作成します: + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} + +### 静的ファイル UI のテスト { #test-static-files-ui } + +これで、WiFi を切断して http://127.0.0.1:8000/docs にアクセスし、ページを再読み込みできるはずです。 + +インターネットに接続していなくても、API のドキュメントを表示し、API と対話できます。 diff --git a/docs/ja/docs/how-to/custom-request-and-route.md b/docs/ja/docs/how-to/custom-request-and-route.md new file mode 100644 index 000000000..ae64f3109 --- /dev/null +++ b/docs/ja/docs/how-to/custom-request-and-route.md @@ -0,0 +1,109 @@ +# カスタム Request と APIRoute クラス { #custom-request-and-apiroute-class } + +場合によっては、`Request` や `APIRoute` クラスで使われるロジックを上書きしたいことがあります。 + +特に、ミドルウェアでのロジックの代替として有効な場合があります。 + +たとえば、アプリケーションで処理される前にリクエストボディを読み取ったり操作したりしたい場合です。 + +/// danger | 警告 + +これは「上級」機能です。 + +FastAPI を始めたばかりの場合は、このセクションは読み飛ばしてもよいでしょう。 + +/// + +## ユースケース { #use-cases } + +ユースケースの例: + +* JSON ではないリクエストボディを JSON に変換する(例: `msgpack`)。 +* gzip 圧縮されたリクエストボディの解凍。 +* すべてのリクエストボディの自動ロギング。 + +## カスタムリクエストボディのエンコーディングの処理 { #handling-custom-request-body-encodings } + +gzip のリクエストを解凍するために、カスタムの `Request` サブクラスを使う方法を見ていきます。 + +そして、そのカスタムリクエストクラスを使うための `APIRoute` サブクラスを用意します。 + +### カスタム `GzipRequest` クラスの作成 { #create-a-custom-gziprequest-class } + +/// tip | 豆知識 + +これは仕組みを示すためのサンプルです。Gzip 対応が必要な場合は、用意されている [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank} を使用できます。 + +/// + +まず、`GzipRequest` クラスを作成します。これは適切なヘッダーがある場合に本体を解凍するよう、`Request.body()` メソッドを上書きします。 + +ヘッダーに `gzip` がなければ、解凍は試みません。 + +この方法により、同じルートクラスで gzip 圧縮済み/未圧縮のリクエストの両方を扱えます。 + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} + +### カスタム `GzipRoute` クラスの作成 { #create-a-custom-gziproute-class } + +次に、`GzipRequest` を利用する `fastapi.routing.APIRoute` のカスタムサブクラスを作成します。 + +ここでは `APIRoute.get_route_handler()` メソッドを上書きします。 + +このメソッドは関数を返します。そしてその関数がリクエストを受け取り、レスポンスを返します。 + +ここでは、元のリクエストから `GzipRequest` を作成するために利用します。 + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} + +/// note | 技術詳細 + +`Request` には `request.scope` 属性があり、これはリクエストに関するメタデータを含む Python の `dict` です。 + +`Request` には `request.receive` もあり、これはリクエストの本体を「受信」するための関数です。 + +`scope` の `dict` と `receive` 関数はいずれも ASGI 仕様の一部です。 + +そしてこの 2 つ(`scope` と `receive`)が、新しい `Request` インスタンスを作成するために必要なものです。 + +`Request` について詳しくは、Starlette の Requests に関するドキュメント を参照してください。 + +/// + +`GzipRequest.get_route_handler` が返す関数が異なるのは、`Request` を `GzipRequest` に変換する点だけです。 + +これにより、`GzipRequest` は必要に応じてデータを解凍してから *path operations* に渡します。 + +それ以降の処理ロジックはすべて同じです。 + +ただし、`GzipRequest.body` を変更しているため、必要に応じて **FastAPI** によって読み込まれる際にリクエストボディが自動的に解凍されます。 + +## 例外ハンドラでのリクエストボディへのアクセス { #accessing-the-request-body-in-an-exception-handler } + +/// tip | 豆知識 + +同じ問題を解決するには、`RequestValidationError` 用のカスタムハンドラで `body` を使う方がずっと簡単でしょう([エラー処理](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank})。 + +ただし、この例も有効で、内部コンポーネントとどのようにやり取りするかを示しています。 + +/// + +同じアプローチを使って、例外ハンドラ内でリクエストボディにアクセスすることもできます。 + +やることは、`try`/`except` ブロックの中でリクエストを処理するだけです: + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} + +例外が発生しても、`Request` インスタンスはスコープ内に残るため、エラー処理時にリクエストボディを読み取り、活用できます: + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} + +## ルーターでのカスタム `APIRoute` クラス { #custom-apiroute-class-in-a-router } + +`APIRouter` の `route_class` パラメータを設定することもできます: + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} + +この例では、`router` 配下の *path operations* はカスタムの `TimedRoute` クラスを使用し、レスポンスの生成にかかった時間を示す追加の `X-Response-Time` ヘッダーがレスポンスに含まれます: + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} diff --git a/docs/ja/docs/how-to/extending-openapi.md b/docs/ja/docs/how-to/extending-openapi.md new file mode 100644 index 000000000..df5b3cd35 --- /dev/null +++ b/docs/ja/docs/how-to/extending-openapi.md @@ -0,0 +1,80 @@ +# OpenAPI の拡張 { #extending-openapi } + +生成された OpenAPI スキーマを変更する必要がある場合があります。 + +このセクションではその方法を説明します。 + +## 通常のプロセス { #the-normal-process } + +通常(デフォルト)のプロセスは次のとおりです。 + +`FastAPI` アプリケーション(インスタンス)には、OpenAPI スキーマを返すことが期待される `.openapi()` メソッドがあります。 + +アプリケーションオブジェクトの作成時に、`/openapi.json`(または `openapi_url` に設定したパス)への path operation が登録されます。 + +これは単に、アプリケーションの `.openapi()` メソッドの結果を含む JSON レスポンスを返します。 + +デフォルトでは、`.openapi()` メソッドはプロパティ `.openapi_schema` に内容があるかを確認し、あればそれを返します。 + +なければ、`fastapi.openapi.utils.get_openapi` にあるユーティリティ関数を使って生成します。 + +この関数 `get_openapi()` は次の引数を受け取ります: + +- `title`: ドキュメントに表示される OpenAPI のタイトル。 +- `version`: API のバージョン。例: `2.5.0`。 +- `openapi_version`: 使用する OpenAPI 仕様のバージョン。デフォルトは最新の `3.1.0`。 +- `summary`: API の短い概要。 +- `description`: API の説明。Markdown を含めることができ、ドキュメントに表示されます。 +- `routes`: ルートのリスト。登録済みの各 path operation です。`app.routes` から取得されます。 + +/// info | 情報 + +パラメータ `summary` は OpenAPI 3.1.0 以降で利用可能で、FastAPI 0.99.0 以降が対応しています。 + +/// + +## デフォルトの上書き { #overriding-the-defaults } + +上記の情報を使って、同じユーティリティ関数で OpenAPI スキーマを生成し、必要な部分を上書きできます。 + +たとえば、カスタムロゴを含めるための ReDoc の OpenAPI 拡張を追加してみましょう。 + +### 通常の **FastAPI** { #normal-fastapi } + +まず、通常どおりに **FastAPI** アプリケーションを実装します: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *} + +### OpenAPI スキーマの生成 { #generate-the-openapi-schema } + +次に、`custom_openapi()` 関数内で同じユーティリティ関数を使って OpenAPI スキーマを生成します: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *} + +### OpenAPI スキーマの変更 { #modify-the-openapi-schema } + +OpenAPI スキーマの `info`「オブジェクト」にカスタムの `x-logo` を追加して、ReDoc 拡張を加えます: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *} + +### OpenAPI スキーマのキャッシュ { #cache-the-openapi-schema } + +生成したスキーマを保持する「キャッシュ」として `.openapi_schema` プロパティを利用できます。 + +こうすることで、ユーザーが API ドキュメントを開くたびにスキーマを生成する必要がなくなります。 + +最初の1回だけ生成され、その後は同じキャッシュ済みスキーマが以降のリクエストで使われます。 + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *} + +### メソッドの上書き { #override-the-method } + +これで、`.openapi()` メソッドを新しい関数に置き換えられます。 + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *} + +### 確認 { #check-it } + +http://127.0.0.1:8000/redoc にアクセスすると、カスタムロゴ(この例では **FastAPI** のロゴ)が使われていることが確認できます: + + diff --git a/docs/ja/docs/how-to/general.md b/docs/ja/docs/how-to/general.md new file mode 100644 index 000000000..8879c68eb --- /dev/null +++ b/docs/ja/docs/how-to/general.md @@ -0,0 +1,39 @@ +# 一般 - ハウツー - レシピ { #general-how-to-recipes } + +ここでは、一般的またはよくある質問に対して、ドキュメント内の他の箇所への参照をいくつか示します。 + +## データのフィルタリング - セキュリティ { #filter-data-security } + +返すべき以上のデータを返さないようにするには、[チュートリアル - レスポンスモデル - 戻り値の型](../tutorial/response-model.md){.internal-link target=_blank} を参照してください。 + +## ドキュメントのタグ - OpenAPI { #documentation-tags-openapi } + +*path operations* にタグを追加し、ドキュメント UI でグループ化するには、[チュートリアル - path operation の設定 - タグ](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank} を参照してください。 + +## ドキュメントの概要と説明 - OpenAPI { #documentation-summary-and-description-openapi } + +*path operations* に概要と説明を追加し、ドキュメント UI に表示するには、[チュートリアル - path operation の設定 - 概要と説明](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank} を参照してください。 + +## ドキュメントのレスポンス説明 - OpenAPI { #documentation-response-description-openapi } + +ドキュメント UI に表示されるレスポンスの説明を定義するには、[チュートリアル - path operation の設定 - レスポンスの説明](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank} を参照してください。 + +## *Path Operation* の非推奨化 - OpenAPI { #documentation-deprecate-a-path-operation-openapi } + +*path operation* を非推奨にし、ドキュメント UI に表示するには、[チュートリアル - path operation の設定 - 非推奨](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank} を参照してください。 + +## 任意のデータを JSON 互換に変換 { #convert-any-data-to-json-compatible } + +任意のデータを JSON 互換に変換するには、[チュートリアル - JSON 互換エンコーダ](../tutorial/encoder.md){.internal-link target=_blank} を参照してください。 + +## OpenAPI メタデータ - ドキュメント { #openapi-metadata-docs } + +ライセンス、バージョン、連絡先などを含むメタデータを OpenAPI スキーマに追加するには、[チュートリアル - メタデータとドキュメントの URL](../tutorial/metadata.md){.internal-link target=_blank} を参照してください。 + +## OpenAPI のカスタム URL { #openapi-custom-url } + +OpenAPI の URL をカスタマイズ(または削除)するには、[チュートリアル - メタデータとドキュメントの URL](../tutorial/metadata.md#openapi-url){.internal-link target=_blank} を参照してください。 + +## OpenAPI ドキュメントの URL { #openapi-docs-urls } + +自動生成されるドキュメント UI が使用する URL を変更するには、[チュートリアル - メタデータとドキュメントの URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank} を参照してください。 diff --git a/docs/ja/docs/how-to/graphql.md b/docs/ja/docs/how-to/graphql.md new file mode 100644 index 000000000..bd0d22349 --- /dev/null +++ b/docs/ja/docs/how-to/graphql.md @@ -0,0 +1,60 @@ +# GraphQL { #graphql } + +**FastAPI** は **ASGI** 標準に基づいているため、ASGI に対応した任意の **GraphQL** ライブラリを簡単に統合できます。 + +同じアプリケーション内で通常の FastAPI の *path operation* と GraphQL を組み合わせて使えます。 + +/// tip | 豆知識 + +**GraphQL** は非常に特定のユースケースを解決します。 + +一般的な **Web API** と比べると、**長所** と **短所** があります。 + +ご自身のユースケースで得られる **利点** が **欠点** を補うかどうかを評価してください。 🤓 + +/// + +## GraphQL ライブラリ { #graphql-libraries } + +**ASGI** をサポートする **GraphQL** ライブラリの一部を以下に示します。**FastAPI** と組み合わせて使用できます: + +* Strawberry 🍓 + * FastAPI 向けドキュメントあり +* Ariadne + * FastAPI 向けドキュメントあり +* Tartiflette + * ASGI 連携用の Tartiflette ASGI あり +* Graphene + * starlette-graphene3 あり + +## Strawberry で GraphQL { #graphql-with-strawberry } + +**GraphQL** が必要、または利用したい場合は、**Strawberry** を**推奨**します。**FastAPI** の設計に最も近く、すべてが**型アノテーション**に基づいています。 + +ユースケースによっては他のライブラリを選ぶ方がよい場合もありますが、私に尋ねられれば、おそらく **Strawberry** を試すことを勧めるでしょう。 + +FastAPI と Strawberry を統合する方法の簡単なプレビューです: + +{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *} + +詳細は Strawberry のドキュメントをご覧ください。 + +また、Strawberry と FastAPI の連携に関するドキュメントもあります。 + +## Starlette の旧 `GraphQLApp` { #older-graphqlapp-from-starlette } + +以前の Starlette には、Graphene と統合するための `GraphQLApp` クラスが含まれていました。 + +これは Starlette からは非推奨になりましたが、もしそれを使用しているコードがある場合は、同じユースケースをカバーし、**ほぼ同一のインターフェース**を持つ starlette-graphene3 へ容易に**移行**できます。 + +/// tip | 豆知識 + +GraphQL が必要であれば、依然として Strawberry の利用を推奨します。独自のクラスや型ではなく、型アノテーションに基づいているためです。 + +/// + +## さらに学ぶ { #learn-more } + +**GraphQL** については、公式 GraphQL ドキュメントでさらに学べます。 + +上記の各ライブラリについては、リンク先のドキュメントをご参照ください。 diff --git a/docs/ja/docs/how-to/index.md b/docs/ja/docs/how-to/index.md new file mode 100644 index 000000000..b1cd17785 --- /dev/null +++ b/docs/ja/docs/how-to/index.md @@ -0,0 +1,13 @@ +# ハウツー - レシピ { #how-to-recipes } + +ここでは、**複数のトピック**に関するさまざまなレシピや「ハウツー」ガイドを紹介します。 + +これらのアイデアの多くはおおむね**独立**しており、ほとんどの場合、**あなたのプロジェクト**に直接当てはまるものだけを読めば十分です。 + +プロジェクトにとって興味深く有用だと思うものがあれば、ぜひ確認してください。そうでなければ、読み飛ばしても問題ありません。 + +/// tip | 豆知識 + +**FastAPI を学ぶ**ことを体系的に進めたい場合(推奨)、代わりに [チュートリアル - ユーザーガイド](../tutorial/index.md){.internal-link target=_blank} を章ごとに読んでください。 + +/// diff --git a/docs/ja/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/ja/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md new file mode 100644 index 000000000..f1414ac28 --- /dev/null +++ b/docs/ja/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -0,0 +1,135 @@ +# Pydantic v1 から Pydantic v2 への移行 { #migrate-from-pydantic-v1-to-pydantic-v2 } + +古い FastAPI アプリがある場合、Pydantic v1 を使っているかもしれません。 + +FastAPI 0.100.0 は Pydantic v1 / v2 のどちらにも対応しており、インストールされている方を使用しました。 + +FastAPI 0.119.0 では、Pydantic v2 内からの Pydantic v1 の部分的サポート(`pydantic.v1`)が導入され、v2 への移行が容易になりました。 + +FastAPI 0.126.0 で Pydantic v1 のサポートは終了しましたが、しばらくの間は `pydantic.v1` は利用可能でした。 + +/// warning | 注意 + +Pydantic チームは Python の最新バージョン、つまり **Python 3.14** から、Pydantic v1 のサポートを終了しました。 + +これには `pydantic.v1` も含まれ、Python 3.14 以上ではサポートされません。 + +Python の最新機能を使いたい場合は、Pydantic v2 を使用していることを確認する必要があります。 + +/// + +古い FastAPI アプリで Pydantic v1 を使っている場合、ここでは Pydantic v2 への移行方法と、段階的移行を助ける **FastAPI 0.119.0 の機能** を紹介します。 + +## 公式ガイド { #official-guide } + +Pydantic には v1 から v2 への公式の 移行ガイド があります。 + +変更点、検証がより正確で厳密になった点、注意事項などが含まれます。 + +何が変わったかをよりよく理解するために参照してください。 + +## テスト { #tests } + +アプリに対する[テスト](../tutorial/testing.md){.internal-link target=_blank}を用意し、継続的インテグレーション(CI)で実行するようにしてください。 + +これにより、アップグレード後も期待どおり動作していることを確認できます。 + +## `bump-pydantic` { #bump-pydantic } + +多くの場合、カスタマイズのない通常の Pydantic モデルを使っていれば、v1 から v2 への移行作業の大半を自動化できます。 + +同じ Pydantic チームが提供する `bump-pydantic` を使用できます。 + +このツールは必要なコード変更のほとんどを自動で行います。 + +その後テストを実行し、問題なければ完了です。😎 + +## v2 における Pydantic v1 { #pydantic-v1-in-v2 } + +Pydantic v2 には、Pydantic v1 がサブモジュール `pydantic.v1` として同梱されています。ただし、これは Python 3.13 を超えるバージョンではサポートされません。 + +つまり、Pydantic v2 の最新バージョンをインストールし、このサブモジュールから旧 Pydantic v1 のコンポーネントをインポートして、あたかも v1 をインストールしているかのように使用できます。 + +{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} + +### v2 内の Pydantic v1 に対する FastAPI のサポート { #fastapi-support-for-pydantic-v1-in-v2 } + +FastAPI 0.119.0 以降では、移行を容易にするため、Pydantic v2 内の Pydantic v1 に対する部分的サポートもあります。 + +そのため、Pydantic を v2 の最新に上げ、インポートを `pydantic.v1` サブモジュールに切り替えるだけで、多くの場合そのまま動作します。 + +{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} + +/// warning | 注意 + +前述のとおり、Python の最近のバージョン(Python 3.14 以降)では Pydantic v1 がサポートされないため、`pydantic.v1` の使用も Python 3.14 以上ではサポートされません。 + +/// + +### 同一アプリでの Pydantic v1 と v2 { #pydantic-v1-and-v2-on-the-same-app } + +Pydantic v2 のモデルのフィールドに Pydantic v1 のモデルを(またはその逆を)埋め込むことは、Pydantic では「サポートされていません」。 + +```mermaid +graph TB + subgraph "❌ Not Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V1Field["Pydantic v1 Model"] + end + subgraph V1["Pydantic v1 Model"] + V2Field["Pydantic v2 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +...but, you can have separated models using Pydantic v1 and v2 in the same app. + +```mermaid +graph TB + subgraph "✅ Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V2Field["Pydantic v2 Model"] + end + subgraph V1["Pydantic v1 Model"] + V1Field["Pydantic v1 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +場合によっては、同じ FastAPI の path operation 内で、Pydantic v1 と v2 の両方のモデルを扱うことも可能です: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} + +上の例では、入力モデルは Pydantic v1、出力モデル(`response_model=ItemV2` で定義)は Pydantic v2 です。 + +### Pydantic v1 のパラメータ { #pydantic-v1-parameters } + +Pydantic v1 のモデルで `Body`、`Query`、`Form` などの FastAPI 固有のパラメータユーティリティを使う必要がある場合、v2 への移行が完了するまでの間は `fastapi.temp_pydantic_v1_params` からインポートできます: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} + +### 段階的に移行 { #migrate-in-steps } + +/// tip | 豆知識 + +まずは `bump-pydantic` を試してください。テストが通り、問題なければコマンド一発で完了です。✨ + +/// + +`bump-pydantic` が適用できない場合は、同一アプリで v1 と v2 のモデルを併用できるサポートを利用して、徐々に v2 へ移行できます。 + +まず Pydantic を v2 の最新にアップグレードし、すべてのモデルのインポートを `pydantic.v1` に切り替えます。 + +その後、モデルをグループごとに少しずつ Pydantic v1 から v2 へ移行していきます。🚶 diff --git a/docs/ja/docs/how-to/separate-openapi-schemas.md b/docs/ja/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 000000000..46df2aafb --- /dev/null +++ b/docs/ja/docs/how-to/separate-openapi-schemas.md @@ -0,0 +1,102 @@ +# 入力と出力でOpenAPIのスキーマを分けるかどうか { #separate-openapi-schemas-for-input-and-output-or-not } + +**Pydantic v2** のリリース以降、生成される OpenAPI は以前より少し正確で、より正しいものになりました。😎 + +実際には、場合によっては同じ Pydantic モデルに対して、入力用と出力用で OpenAPI に **2 つの JSON Schema** が含まれることがあります。これは **デフォルト値** の有無に依存します。 + +その動作と、必要に応じての変更方法を見ていきます。 + +## 入出力のPydanticモデル { #pydantic-models-for-input-and-output } + +次のようにデフォルト値を持つ Pydantic モデルがあるとします。 + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} + +### 入力用モデル { #model-for-input } + +このモデルを次のように入力として使うと: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} + +...`description` フィールドは **必須ではありません**。デフォルト値が `None` だからです。 + +### ドキュメントでの入力モデル { #input-model-in-docs } + +ドキュメントで確認すると、`description` フィールドには **赤いアスタリスク** が付いておらず、必須としてはマークされていません: + +
+ +
+ +### 出力用モデル { #model-for-output } + +しかし同じモデルを次のように出力として使う場合: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} + +...`description` にデフォルト値があるため、そのフィールドに何も返さなくても、その **デフォルト値** が入ります。 + +### 出力のレスポンスデータ { #model-for-output-response-data } + +ドキュメントから試してレスポンスを確認すると、コードでは一方の `description` フィールドに何も追加していないにもかかわらず、JSON レスポンスにはデフォルト値(`null`)が含まれています: + +
+ +
+ +つまりそのフィールドには **常に値があります**。値が `None`(JSON では `null`)になることがあるだけです。 + +したがって、この API を使うクライアントは値の有無を確認する必要がなく、フィールドが **常に存在する** と仮定できます。場合によってはデフォルト値の `None` になるだけです。 + +これを OpenAPI で表現するには、そのフィールドを **必須** としてマークします。常に存在するためです。 + +このため、モデルの JSON Schema は、**入力か出力か** によって異なる場合があります: + +- **入力** では `description` は **必須ではない** +- **出力** では **必須**(値は `None`、JSON では `null` の可能性あり) + +### ドキュメントでの出力モデル { #model-for-output-in-docs } + +ドキュメントで出力モデルを見ると、`name` と `description` の **両方** が **赤いアスタリスク** で **必須** としてマークされています: + +
+ +
+ +### ドキュメントでの入力・出力モデル { #model-for-input-and-output-in-docs } + +さらに、OpenAPI に含まれる利用可能なスキーマ(JSON Schema)を確認すると、`Item-Input` と `Item-Output` の 2 つがあることが分かります。 + +`Item-Input` では、`description` は **必須ではありません**(赤いアスタリスクなし)。 + +一方、`Item-Output` では、`description` は **必須**(赤いアスタリスクあり)です。 + +
+ +
+ +この **Pydantic v2** の機能により、API ドキュメントはより **正確** になり、自動生成されたクライアントや SDK もより正確になります。これにより、より良い **開発者エクスペリエンス** と一貫性が得られます。🎉 + +## スキーマを分けない { #do-not-separate-schemas } + +一方で、**入力と出力で同じスキーマ** にしたい場合もあります。 + +主なユースケースは、すでに自動生成されたクライアントコードや SDK があり、まだそれらをすべて更新したくない場合です。いずれは更新したいとしても、今ではないかもしれません。 + +その場合は、**FastAPI** のパラメータ `separate_input_output_schemas=False` でこの機能を無効化できます。 + +/// info | 情報 + +`separate_input_output_schemas` のサポートは FastAPI `0.102.0` で追加されました。🤓 + +/// + +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} + +### ドキュメントで入力・出力に同一スキーマを使用 { #same-schema-for-input-and-output-models-in-docs } + +これでモデルの入力と出力は単一のスキーマ、`Item` のみになり、`description` は **必須ではありません**: + +
+ +
diff --git a/docs/ja/docs/how-to/testing-database.md b/docs/ja/docs/how-to/testing-database.md new file mode 100644 index 000000000..af9ad7509 --- /dev/null +++ b/docs/ja/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# データベースのテスト { #testing-a-database } + +データベース、SQL、SQLModel については、SQLModel ドキュメントで学べます。🤓 + +FastAPI と一緒に SQLModel を使うためのミニ チュートリアルがあります。✨ + +そのチュートリアルには、SQL データベースのテストに関するセクションも含まれています。😎 diff --git a/docs/ja/docs/index.md b/docs/ja/docs/index.md index 67e01ed53..90368b4ae 100644 --- a/docs/ja/docs/index.md +++ b/docs/ja/docs/index.md @@ -40,7 +40,7 @@ FastAPI は、Python の標準である型ヒントに基づいて Python で AP * **高速**: **NodeJS** や **Go** 並みのとても高いパフォーマンス(Starlette と Pydantic のおかげです)。 [利用可能な最も高速な Python フレームワークの一つです](#performance)。 * **高速なコーディング**: 開発速度を約 200%〜300% 向上させます。* * **少ないバグ**: 開発者起因のヒューマンエラーを約 40% 削減します。* -* **直感的**: 素晴らしいエディタサポート。あらゆる場所で 補完 が使えます。デバッグ時間を削減します。 +* **直感的**: 素晴らしいエディタサポート。補完 があらゆる場所で使えます。デバッグ時間を削減します。 * **簡単**: 簡単に利用・習得できるようにデザインされています。ドキュメントを読む時間を削減します。 * **短い**: コードの重複を最小限にします。各パラメータ宣言から複数の機能を得られます。バグも減ります。 * **堅牢性**: 自動対話型ドキュメントにより、本番環境向けのコードが得られます。 @@ -127,7 +127,7 @@ FastAPI は、Python の標準である型ヒントに基づいて Python で AP -Web API の代わりにターミナルで使用する CLI アプリを構築する場合は、**Typer** を確認してください。 +Web API の代わりにターミナルで使用する CLI アプリを構築する場合は、**Typer** を確認してください。 **Typer** は FastAPI の弟分です。そして、**CLI 版 FastAPI** を意図しています。 ⌨️ 🚀 @@ -368,7 +368,7 @@ item: Item * データの検証: * データが無効な場合に自動で明確なエラーを返します。 * 深い入れ子になった JSON オブジェクトでも検証が可能です。 -* 入力データの 変換: ネットワークから Python のデータや型へ。以下から読み取ります: +* 入力データの 変換: ネットワークから Python のデータや型へ。以下から読み取ります: * JSON。 * パスパラメータ。 * クエリパラメータ。 @@ -376,7 +376,7 @@ item: Item * ヘッダー。 * フォーム。 * ファイル。 -* 出力データの 変換: Python のデータや型からネットワークデータへ(JSON として)変換します: +* 出力データの 変換: Python のデータや型からネットワークデータへ(JSON として)変換します: * Python の型(`str`、`int`、`float`、`bool`、`list` など)の変換。 * `datetime` オブジェクト。 * `UUID` オブジェクト。 @@ -439,7 +439,7 @@ item: Item * **ヘッダー**、**Cookie**、**フォームフィールド**、**ファイル**など、他のさまざまな場所からの **パラメータ** 宣言。 * `maximum_length` や `regex` のような **検証制約** を設定する方法。 -* 非常に強力で使いやすい **依存性注入** システム。 +* 非常に強力で使いやすい **依存性注入** システム。 * **JWT トークン**を用いた **OAuth2** や **HTTP Basic** 認証のサポートを含む、セキュリティと認証。 * **深くネストされた JSON モデル**を宣言するための、より高度な(しかし同様に簡単な)手法(Pydantic のおかげです)。 * Strawberry および他のライブラリによる **GraphQL** 統合。 @@ -524,7 +524,7 @@ Starlette によって使用されるもの: * httpx - `TestClient` を使用したい場合に必要です。 * jinja2 - デフォルトのテンプレート設定を使用したい場合に必要です。 -* python-multipart - `request.form()` とともに、フォームの 「parsing」 をサポートしたい場合に必要です。 +* python-multipart - `request.form()` とともに、フォームの 「parsing」 をサポートしたい場合に必要です。 FastAPI によって使用されるもの: diff --git a/docs/ja/docs/python-types.md b/docs/ja/docs/python-types.md index 26a9e2193..a6b46c256 100644 --- a/docs/ja/docs/python-types.md +++ b/docs/ja/docs/python-types.md @@ -1,12 +1,12 @@ # Pythonの型の紹介 { #python-types-intro } -Pythonではオプションの「型ヒント」(「型アノテーション」とも呼ばれます)がサポートされています。 +Python にはオプションの「型ヒント」(「型アノテーション」とも呼ばれます)がサポートされています。 -これらの **「型ヒント」** またはアノテーションは、変数のを宣言できる特別な構文です。 +これらの **「型ヒント」** やアノテーションは、変数のを宣言できる特別な構文です。 変数に型を宣言することで、エディターやツールがより良いサポートを提供できます。 -これはPythonの型ヒントについての **クイックチュートリアル/リフレッシュ** にすぎません。**FastAPI** で使用するために必要な最低限のことだけをカバーしています。...実際には本当に少ないです。 +これは Python の型ヒントについての **クイックチュートリアル/リフレッシュ** にすぎません。**FastAPI** で使うために必要な最低限のことだけをカバーしています。...実際には本当に少ないです。 **FastAPI** はすべてこれらの型ヒントに基づいており、多くの強みと利点を与えてくれます。 @@ -14,7 +14,7 @@ Pythonではオプションの「型ヒント」(「型アノテーション /// note | 備考 -もしあなたがPythonの専門家で、すでに型ヒントについてすべて知っているのであれば、次の章まで読み飛ばしてください。 +もしあなたが Python の専門家で、すでに型ヒントについてすべて知っているのであれば、次の章まで読み飛ばしてください。 /// @@ -22,7 +22,7 @@ Pythonではオプションの「型ヒント」(「型アノテーション 簡単な例から始めてみましょう: -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} このプログラムを呼び出すと、以下が出力されます: @@ -32,11 +32,11 @@ John Doe この関数は以下のようなことを行います: -* `first_name`と`last_name`を取得します。 -* `title()`を用いて、それぞれの最初の文字を大文字に変換します。 -* 真ん中にスペースを入れて連結します。 +* `first_name` と `last_name` を取得します。 +* `title()` を用いて、それぞれの最初の文字を大文字に変換します。 +* 真ん中にスペースを入れて連結します。 -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### 編集 { #edit-it } @@ -48,11 +48,11 @@ John Doe しかし、そうすると「最初の文字を大文字に変換するあのメソッド」を呼び出す必要があります。 -それは`upper`でしたか?`uppercase`でしたか?`first_uppercase`?`capitalize`? +それは `upper` でしたか?`uppercase` でしたか?`first_uppercase`?`capitalize`? そして、古くからプログラマーの友人であるエディタで自動補完を試してみます。 -関数の最初のパラメータ`first_name`を入力し、ドット(`.`)を入力してから、`Ctrl+Space`を押すと補完が実行されます。 +関数の最初のパラメータ `first_name` を入力し、ドット(`.`)を入力してから、`Ctrl+Space` を押すと補完が実行されます。 しかし、悲しいことに、これはなんの役にも立ちません: @@ -78,7 +78,7 @@ John Doe それが「型ヒント」です: -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} これは、以下のようにデフォルト値を宣言するのと同じではありません: @@ -94,7 +94,7 @@ John Doe しかし今、あなたが再びその関数を作成している最中に、型ヒントを使っていると想像してみてください。 -同じタイミングで`Ctrl+Space`で自動補完を実行すると、以下のようになります: +同じタイミングで `Ctrl+Space` で自動補完を実行すると、以下のようになります: @@ -106,15 +106,15 @@ John Doe この関数を見てください。すでに型ヒントを持っています: -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} エディタは変数の型を知っているので、補完だけでなく、エラーチェックをすることもできます: -これで`age`を`str(age)`で文字列に変換して修正する必要があることがわかります: +これで `age` を `str(age)` で文字列に変換して修正する必要があることがわかります: -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} ## 型の宣言 { #declaring-types } @@ -124,7 +124,7 @@ John Doe ### 単純な型 { #simple-types } -`str`だけでなく、Pythonの標準的な型すべてを宣言できます。 +`str` だけでなく、Python の標準的な型すべてを宣言できます。 例えば、以下を使用可能です: @@ -133,51 +133,54 @@ John Doe * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} -### 型パラメータを持つジェネリック型 { #generic-types-with-type-parameters } +### `typing` モジュール { #typing-module } -データ構造の中には、`dict`、`list`、`set`、`tuple`のように他の値を含むことができるものがあります。また内部の値も独自の型を持つことができます。 +いくつかの追加のユースケースでは、標準ライブラリの `typing` モジュールから何かをインポートする必要があるかもしれません。例えば「任意の型」を受け付けることを宣言したい場合、`typing` の `Any` を使えます: -内部の型を持つこれらの型は「**generic**」型と呼ばれます。そして、内部の型も含めて宣言することが可能です。 +```python +from typing import Any -これらの型や内部の型を宣言するには、Pythonの標準モジュール`typing`を使用できます。これらの型ヒントをサポートするために特別に存在しています。 -#### 新しいPythonバージョン { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -`typing`を使う構文は、Python 3.6から最新バージョンまで(Python 3.9、Python 3.10などを含む)すべてのバージョンと **互換性** があります。 +### ジェネリック型 { #generic-types } -Pythonが進化するにつれ、**新しいバージョン** ではこれらの型アノテーションへのサポートが改善され、多くの場合、型アノテーションを宣言するために`typing`モジュールをインポートして使う必要すらなくなります。 +一部の型は、角括弧内で「型パラメータ」を受け取り、内部の型を定義できます。例えば「文字列のリスト」は `list[str]` として宣言します。 -プロジェクトでより新しいPythonバージョンを選べるなら、その追加のシンプルさを活用できます。 +このように型パラメータを取れる型は **Generic types**(ジェネリクス)と呼ばれます。 -ドキュメント全体で、Pythonの各バージョンと互換性のある例(差分がある場合)を示しています。 +次の組み込み型をジェネリクスとして(角括弧と内部の型で)使えます: -例えば「**Python 3.6+**」はPython 3.6以上(3.7、3.8、3.9、3.10などを含む)と互換性があることを意味します。また「**Python 3.9+**」はPython 3.9以上(3.10などを含む)と互換性があることを意味します。 - -**最新のPythonバージョン** を使えるなら、最新バージョン向けの例を使ってください。例えば「**Python 3.10+**」のように、それらは **最良かつ最もシンプルな構文** になります。 +* `list` +* `tuple` +* `set` +* `dict` #### List { #list } -例えば、`str`の`list`の変数を定義してみましょう。 +例えば、`str` の `list` の変数を定義してみましょう。 同じコロン(`:`)の構文で変数を宣言します。 -型として、`list`を指定します。 +型として、`list` を指定します。 リストはいくつかの内部の型を含む型なので、それらを角括弧で囲みます: -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} /// info | 情報 角括弧内の内部の型は「型パラメータ」と呼ばれています。 -この場合、`str`は`list`に渡される型パラメータです。 +この場合、`str` は `list` に渡される型パラメータです。 /// -つまり: 変数`items`は`list`であり、このリストの各項目は`str`です。 +つまり: 変数 `items` は `list` であり、このリストの各項目は `str` です。 そうすることで、エディタはリストの項目を処理している間にもサポートを提供できます。 @@ -185,78 +188,54 @@ Pythonが進化するにつれ、**新しいバージョン** ではこれらの 型がなければ、それはほぼ不可能です。 -変数`item`はリスト`items`の要素の一つであることに注意してください。 +変数 `item` はリスト `items` の要素の一つであることに注意してください。 -それでも、エディタはそれが`str`であることを知っていて、そのためのサポートを提供しています。 +それでも、エディタはそれが `str` であることを知っていて、そのためのサポートを提供しています。 #### Tuple と Set { #tuple-and-set } -`tuple`と`set`の宣言も同様です: +`tuple` と `set` の宣言も同様です: -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} つまり: -* 変数`items_t`は`int`、別の`int`、`str`の3つの項目を持つ`tuple`です。 -* 変数`items_s`は`set`であり、その各項目は`bytes`型です。 +* 変数 `items_t` は `int`、別の `int`、`str` の 3 つの項目を持つ `tuple` です。 +* 変数 `items_s` は `set` であり、その各項目は `bytes` 型です。 #### Dict { #dict } -`dict`を定義するには、カンマ区切りで2つの型パラメータを渡します。 +`dict` を定義するには、カンマ区切りで 2 つの型パラメータを渡します。 -最初の型パラメータは`dict`のキーです。 +最初の型パラメータは `dict` のキーです。 -2番目の型パラメータは`dict`の値です: +2 番目の型パラメータは `dict` の値です: -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} つまり: -* 変数`prices`は`dict`です: - * この`dict`のキーは`str`型です(例えば、各項目の名前)。 - * この`dict`の値は`float`型です(例えば、各項目の価格)。 +* 変数 `prices` は `dict` です: + * この `dict` のキーは `str` 型です(例えば、各項目の名前)。 + * この `dict` の値は `float` 型です(例えば、各項目の価格)。 #### Union { #union } -変数が**複数の型のいずれか**になり得ることを宣言できます。例えば、`int`または`str`です。 +変数が **複数の型のいずれか** になり得ることを宣言できます。例えば、`int` または `str` です。 -Python 3.6以上(Python 3.10を含む)では、`typing`の`Union`型を使い、角括弧の中に受け付ける可能性のある型を入れられます。 +それを定義するには、両方の型を区切るために 縦棒(`|`) を使います。 -Python 3.10では、受け付ける可能性のある型を縦棒(`|`)で区切って書ける **新しい構文** もあります。 - -//// tab | Python 3.10+ +これは「ユニオン(union)」と呼ばれます。変数がそれら 2 つの型の集合の和集合のいずれかになり得るからです。 ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// +これは `item` が `int` または `str` になり得ることを意味します. -//// tab | Python 3.9+ +#### `None` の可能性 { #possibly-none } -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -どちらの場合も、`item`は`int`または`str`になり得ることを意味します。 - -#### `None`の可能性 { #possibly-none } - -値が`str`のような型を持つ可能性がある一方で、`None`にもなり得ることを宣言できます。 - -Python 3.6以上(Python 3.10を含む)では、`typing`モジュールから`Optional`をインポートして使うことで宣言できます。 - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -ただの`str`の代わりに`Optional[str]`を使用することで、値が常に`str`であると仮定しているときに、実際には`None`である可能性もあるというエラーをエディタが検出するのに役立ちます。 - -`Optional[Something]`は実際には`Union[Something, None]`のショートカットで、両者は等価です。 - -これは、Python 3.10では`Something | None`も使えることを意味します: +値が `str` のような型を持つ可能性がある一方で、`None` にもなり得ることを宣言できます。 //// tab | Python 3.10+ @@ -266,120 +245,31 @@ Python 3.6以上(Python 3.10を含む)では、`typing`モジュールから //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ alternative - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### `Union`または`Optional`の使用 { #using-union-or-optional } - -Python 3.10未満のバージョンを使っている場合、これは私のとても **主観的** な観点からのヒントです: - -* 🚨 `Optional[SomeType]`は避けてください -* 代わりに ✨ **`Union[SomeType, None]`を使ってください** ✨ - -どちらも等価で、内部的には同じですが、`Optional`より`Union`をおすすめします。というのも「**optional**」という単語は値がオプションであることを示唆するように見えますが、実際には「`None`になり得る」という意味であり、オプションではなく必須である場合でもそうだからです。 - -`Union[SomeType, None]`のほうが意味がより明示的だと思います。 - -これは言葉や名前の話にすぎません。しかし、その言葉はあなたやチームメイトがコードをどう考えるかに影響し得ます。 - -例として、この関数を見てみましょう: - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -パラメータ`name`は`Optional[str]`として定義されていますが、**オプションではありません**。そのパラメータなしで関数を呼び出せません: - -```Python -say_hi() # Oh, no, this throws an error! 😱 -``` - -`name`パラメータはデフォルト値がないため、**依然として必須**(*optional*ではない)です。それでも、`name`は値として`None`を受け付けます: - -```Python -say_hi(name=None) # This works, None is valid 🎉 -``` - -良い知らせとして、Python 3.10になればその心配は不要です。型のユニオンを定義するために`|`を単純に使えるからです: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -そして、`Optional`や`Union`のような名前について心配する必要もなくなります。😎 - -#### ジェネリック型 { #generic-types } - -角括弧で型パラメータを取るこれらの型は、例えば次のように **Generic types** または **Generics** と呼ばれます: - -//// tab | Python 3.10+ - -同じ組み込み型をジェネリクスとして(角括弧と内部の型で)使えます: - -* `list` -* `tuple` -* `set` -* `dict` - -また、これまでのPythonバージョンと同様に、`typing`モジュールから: - -* `Union` -* `Optional` -* ...and others. - -Python 3.10では、ジェネリクスの`Union`や`Optional`を使う代替として、型のユニオンを宣言するために縦棒(`|`)を使えます。これはずっと良く、よりシンプルです。 - -//// - -//// tab | Python 3.9+ - -同じ組み込み型をジェネリクスとして(角括弧と内部の型で)使えます: - -* `list` -* `tuple` -* `set` -* `dict` - -そして`typing`モジュールのジェネリクス: - -* `Union` -* `Optional` -* ...and others. - -//// +ただの `str` の代わりに `str | None` を使用することで、値が常に `str` であると仮定しているときに、実際には `None` である可能性もあるというエラーをエディタが検出するのに役立ちます。 ### 型としてのクラス { #classes-as-types } 変数の型としてクラスを宣言することもできます。 -名前を持つ`Person`クラスがあるとしましょう: +名前を持つ `Person` クラスがあるとしましょう: -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} -変数を`Person`型として宣言できます: +変数を `Person` 型として宣言できます: -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} そして、再び、すべてのエディタのサポートを得ることができます: -これは「`one_person`はクラス`Person`の**インスタンス**である」ことを意味します。 +これは「`one_person` はクラス `Person` の **インスタンス** である」ことを意味します。 -「`one_person`は`Person`という名前の**クラス**である」という意味ではありません。 +「`one_person` は `Person` という名前の **クラス** である」という意味ではありません。 -## Pydanticのモデル { #pydantic-models } +## Pydantic のモデル { #pydantic-models } -Pydantic はデータ検証を行うためのPythonライブラリです。 +Pydantic はデータ検証を行うための Python ライブラリです。 データの「形」を属性付きのクラスとして宣言します。 @@ -389,53 +279,47 @@ Python 3.10では、ジェネリクスの`Union`や`Optional`を使う代替と また、その結果のオブジェクトですべてのエディタのサポートを受けることができます。 -Pydanticの公式ドキュメントからの例: +Pydantic の公式ドキュメントからの例: {* ../../docs_src/python_types/tutorial011_py310.py *} /// info | 情報 -Pydanticの詳細はドキュメントを参照してください。 +Pydantic の詳細はドキュメントを参照してください。 /// -**FastAPI** はすべてPydanticをベースにしています。 +**FastAPI** はすべて Pydantic をベースにしています。 -すべてのことは[チュートリアル - ユーザーガイド](tutorial/index.md){.internal-link target=_blank}で実際に見ることができます。 - -/// tip | 豆知識 - -Pydanticには、デフォルト値なしで`Optional`または`Union[Something, None]`を使った場合の特別な挙動があります。詳細はPydanticドキュメントのRequired Optional fieldsを参照してください。 - -/// +すべてのことは [チュートリアル - ユーザーガイド](tutorial/index.md){.internal-link target=_blank} で実際に見ることができます。 ## メタデータアノテーション付き型ヒント { #type-hints-with-metadata-annotations } -Pythonには、`Annotated`を使って型ヒントに**追加のメタデータ**を付与できる機能もあります。 +Python には、`Annotated` を使って型ヒントに **追加の メタデータ** を付与できる機能もあります。 -Python 3.9以降、`Annotated`は標準ライブラリの一部なので、`typing`からインポートできます。 +`Annotated` は `typing` からインポートできます。 -{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} -Python自体は、この`Annotated`で何かをするわけではありません。また、エディタや他のツールにとっても、型は依然として`str`です。 +Python 自体は、この `Annotated` で何かをするわけではありません。また、エディタや他のツールにとっても、型は依然として `str` です。 -しかし、`Annotated`内のこのスペースを使って、アプリケーションをどのように動作させたいかについての追加メタデータを **FastAPI** に提供できます。 +しかし、`Annotated` 内のこのスペースを使って、アプリケーションをどのように動作させたいかについての追加メタデータを **FastAPI** に提供できます。 -覚えておくべき重要な点は、`Annotated`に渡す**最初の*型パラメータ***が**実際の型**であることです。残りは、他のツール向けのメタデータにすぎません。 +覚えておくべき重要な点は、`Annotated` に渡す **最初の「型パラメータ」** が **実際の型** であることです。残りは、他のツール向けのメタデータにすぎません。 -今のところは、`Annotated`が存在し、それが標準のPythonであることを知っておけば十分です。😎 +今のところは、`Annotated` が存在し、それが標準の Python であることを知っておけば十分です。😎 -後で、これがどれほど**強力**になり得るかを見ることになります。 +後で、これがどれほど **強力** になり得るかを見ることになります。 /// tip | 豆知識 -これが **標準のPython** であるという事実は、エディタで、使用しているツール(コードの解析やリファクタリングなど)とともに、**可能な限り最高の開発体験**が得られることを意味します。 ✨ +これが **標準の Python** であるという事実は、エディタで、使用しているツール(コードの解析やリファクタリングなど)とともに、**可能な限り最高の開発体験** が得られることを意味します。 ✨ -また、あなたのコードが他の多くのPythonツールやライブラリとも非常に互換性が高いことも意味します。 🚀 +また、あなたのコードが他の多くの Python ツールやライブラリとも非常に互換性が高いことも意味します。 🚀 /// -## **FastAPI**での型ヒント { #type-hints-in-fastapi } +## **FastAPI** での型ヒント { #type-hints-in-fastapi } **FastAPI** はこれらの型ヒントを利用していくつかのことを行います。 @@ -450,15 +334,15 @@ Python自体は、この`Annotated`で何かをするわけではありません * **データの変換**: リクエストから必要な型にデータを変換します。 * **データの検証**: 各リクエストから来るデータについて: * データが無効な場合にクライアントに返される **自動エラー** を生成します。 -* OpenAPIを使用してAPIを**ドキュメント化**します: +* OpenAPI を使用して API を **ドキュメント化** します: * これは自動の対話型ドキュメントのユーザーインターフェイスで使われます。 -すべてが抽象的に聞こえるかもしれません。心配しないでください。 この全ての動作は [チュートリアル - ユーザーガイド](tutorial/index.md){.internal-link target=_blank}で見ることができます。 +すべてが抽象的に聞こえるかもしれません。心配しないでください。 この全ての動作は [チュートリアル - ユーザーガイド](tutorial/index.md){.internal-link target=_blank} で見ることができます。 -重要なのは、Pythonの標準的な型を使うことで、(クラスやデコレータなどを追加するのではなく)1つの場所で **FastAPI** が多くの作業を代わりにやってくれているということです。 +重要なのは、Python の標準的な型を使うことで、(クラスやデコレータなどを追加するのではなく)1 つの場所で **FastAPI** が多くの作業を代わりにやってくれているということです。 /// info | 情報 -すでにすべてのチュートリアルを終えて、型についての詳細を見るためにこのページに戻ってきた場合は、良いリソースとして`mypy`の「チートシート」があります。 +すでにすべてのチュートリアルを終えて、型についての詳細を見るためにこのページに戻ってきた場合は、良いリソースとして `mypy` の「チートシート」 があります。 /// diff --git a/docs/ja/docs/resources/index.md b/docs/ja/docs/resources/index.md new file mode 100644 index 000000000..15a949e01 --- /dev/null +++ b/docs/ja/docs/resources/index.md @@ -0,0 +1,3 @@ +# リソース { #resources } + +追加リソース、外部リンクなど。✈️ diff --git a/docs/ja/docs/translation-banner.md b/docs/ja/docs/translation-banner.md new file mode 100644 index 000000000..351a82a35 --- /dev/null +++ b/docs/ja/docs/translation-banner.md @@ -0,0 +1,11 @@ +/// details | 🌐 AI と人間による翻訳 + +この翻訳は、人間のガイドに基づいて AI によって作成されました。🤝 + +原文の意図を取り違えていたり、不自然な表現になっている可能性があります。🤖 + +[AI LLM をより適切に誘導するのを手伝う](https://fastapi.tiangolo.com/ja/contributing/#translations) ことで、この翻訳を改善できます。 + +[英語版](ENGLISH_VERSION_URL) + +/// diff --git a/docs/ja/docs/tutorial/background-tasks.md b/docs/ja/docs/tutorial/background-tasks.md index 0ed41ce11..d32c141b5 100644 --- a/docs/ja/docs/tutorial/background-tasks.md +++ b/docs/ja/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ まず初めに、`BackgroundTasks` をインポートし、`BackgroundTasks` の型宣言と共に、*path operation function* のパラメーターを定義します: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} **FastAPI** は、`BackgroundTasks` 型のオブジェクトを作成し、そのパラメーターに渡します。 @@ -31,13 +31,13 @@ また、書き込み操作では `async` と `await` を使用しないため、通常の `def` で関数を定義します。 -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} ## バックグラウンドタスクの追加 { #add-the-background-task } *path operation function* 内で、`.add_task()` メソッドを使用してタスク関数を *background tasks* オブジェクトに渡します。 -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} `.add_task()` は以下の引数を受け取ります: diff --git a/docs/ja/docs/tutorial/bigger-applications.md b/docs/ja/docs/tutorial/bigger-applications.md new file mode 100644 index 000000000..9c1cc0fe6 --- /dev/null +++ b/docs/ja/docs/tutorial/bigger-applications.md @@ -0,0 +1,504 @@ +# 大規模アプリケーション - 複数ファイル { #bigger-applications-multiple-files } + +アプリケーションや Web API を作る場合、すべてを1つのファイルに収められることはほとんどありません。 + +**FastAPI** は、柔軟性を保ったままアプリケーションを構造化できる便利なツールを提供します。 + +/// info | 情報 + +Flask 出身であれば、Flask の Blueprint に相当します。 + +/// + +## 例のファイル構成 { #an-example-file-structure } + +次のようなファイル構成があるとします: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   ├── dependencies.py +│   └── routers +│   │ ├── __init__.py +│   │ ├── items.py +│   │ └── users.py +│   └── internal +│   ├── __init__.py +│   └── admin.py +``` + +/// tip | 豆知識 + +複数の `__init__.py` ファイルがあります: 各ディレクトリやサブディレクトリに1つずつです。 + +これにより、あるファイルから別のファイルへコードをインポートできます。 + +例えば、`app/main.py` では次のように書けます: + +``` +from app.routers import items +``` + +/// + +* `app` ディレクトリはすべてを含みます。そして空のファイル `app/__init__.py` があり、「Python パッケージ」(「Python モジュール」の集合): `app` です。 +* `app/main.py` ファイルがあります。Python パッケージ(`__init__.py` のあるディレクトリ)の中にあるため、そのパッケージの「モジュール」: `app.main` です。 +* `app/dependencies.py` ファイルもあり、`app/main.py` と同様に「モジュール」: `app.dependencies` です。 +* `app/routers/` サブディレクトリに別の `__init__.py` があるので、「Python サブパッケージ」: `app.routers` です。 +* `app/routers/items.py` はパッケージ `app/routers/` 内のファイルなので、サブモジュール: `app.routers.items` です。 +* `app/routers/users.py` も同様で、別のサブモジュール: `app.routers.users` です。 +* `app/internal/` サブディレクトリにも `__init__.py` があるので、別の「Python サブパッケージ」: `app.internal` です。 +* `app/internal/admin.py` は別のサブモジュール: `app.internal.admin` です。 + + + +同じファイル構成にコメントを付けると次のとおりです: + +```bash +. +├── app # "app" is a Python package +│   ├── __init__.py # this file makes "app" a "Python package" +│   ├── main.py # "main" module, e.g. import app.main +│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies +│   └── routers # "routers" is a "Python subpackage" +│   │ ├── __init__.py # makes "routers" a "Python subpackage" +│   │ ├── items.py # "items" submodule, e.g. import app.routers.items +│   │ └── users.py # "users" submodule, e.g. import app.routers.users +│   └── internal # "internal" is a "Python subpackage" +│   ├── __init__.py # makes "internal" a "Python subpackage" +│   └── admin.py # "admin" submodule, e.g. import app.internal.admin +``` + +## `APIRouter` { #apirouter } + +ユーザーだけを扱うファイルが `/app/routers/users.py` のサブモジュールだとします。 + +ユーザーに関連する *path operations* をほかのコードから分離して整理したいはずです。 + +ただし、同じ **FastAPI** アプリケーション / Web API(同じ「Python パッケージ」の一部)である点は変わりません。 + +そのモジュールで `APIRouter` を使って *path operations* を作成できます。 + +### `APIRouter` のインポート { #import-apirouter } + +クラス `FastAPI` と同様にインポートし、「インスタンス」を作成します: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} + +### `APIRouter` での *path operations* { #path-operations-with-apirouter } + +これを使って *path operations* を宣言します。 + +使い方は `FastAPI` クラスと同じです: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} + +`APIRouter` は「ミニ `FastAPI`」のようなクラスと考えられます。 + +同じオプションがすべてサポートされています。 + +同じ `parameters`、`responses`、`dependencies`、`tags` などが使えます。 + +/// tip | 豆知識 + +この例では変数名は `router` ですが、任意の名前を付けられます。 + +/// + +この `APIRouter` をメインの `FastAPI` アプリに取り込みますが、その前に依存関係と別の `APIRouter` を確認します。 + +## 依存関係 { #dependencies } + +アプリケーションの複数箇所で使う依存関係が必要になります。 + +そのため、専用の `dependencies` モジュール(`app/dependencies.py`)に置きます。 + +ここではカスタムヘッダー `X-Token` を読む簡単な依存関係を使います: + +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} + +/// tip | 豆知識 + +この例を簡単にするために架空のヘッダーを使っています。 + +しかし実際には、組み込みの [Security utilities](security/index.md){.internal-link target=_blank} を使う方が良い結果になります。 + +/// + +## 別モジュールでの `APIRouter` { #another-module-with-apirouter } + +アプリケーションの「items」を扱うエンドポイントが `app/routers/items.py` のモジュールにあるとします。 + +次の *path operations* があります: + +* `/items/` +* `/items/{item_id}` + +構造は `app/routers/users.py` と同じです。 + +しかし、もう少し賢くしてコードを少し簡潔にしたいところです。 + +このモジュールのすべての *path operations* には同じものがあると分かっています: + +* パスの `prefix`: `/items` +* `tags`(1つのタグ: `items`) +* 追加の `responses` +* `dependencies`: 先ほど作成した `X-Token` の依存関係が必要 + +そこで、各 *path operation* に個別に追加する代わりに、これらを `APIRouter` に追加できます。 + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} + +各 *path operation* のパスは次のように `/` で始める必要があるため: + +```Python hl_lines="1" +@router.get("/{item_id}") +async def read_item(item_id: str): + ... +``` + +...prefix の末尾に `/` を含めてはいけません。 + +この場合の prefix は `/items` です。 + +また、`tags` のリストや追加の `responses` を、このルーターに含まれるすべての *path operations* に適用するよう追加できます。 + +さらに `dependencies` のリストを追加できます。これはこのルーター内のすべての *path operations* に追加され、それらへの各リクエストごとに実行・解決されます。 + +/// tip | 豆知識 + +[*path operation デコレータ*の依存関係](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank} と同様に、*path operation 関数*には値は渡されない点に注意してください。 + +/// + +最終的に、item のパスは次のとおりになります: + +* `/items/` +* `/items/{item_id}` + +...意図したとおりです。 + +* これらには、文字列 `"items"` を1つ含むタグのリストが付きます。 + * これらの「タグ」は、(OpenAPI を使う)自動インタラクティブドキュメントで特に有用です。 +* すべてに事前定義した `responses` が含まれます。 +* これらすべての *path operations* では、実行前に `dependencies` のリストが評価・実行されます。 + * 特定の *path operation* に依存関係を宣言した場合は、**それらも実行されます**。 + * ルーターの依存関係が先に実行され、その後に[デコレータ内の `dependencies`](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}、次に通常のパラメータ依存関係が続きます。 + * [`scopes` を伴う `Security` 依存関係](../advanced/security/oauth2-scopes.md){.internal-link target=_blank} を追加することもできます。 + +/// tip | 豆知識 + +`APIRouter` に `dependencies` を置くことで、*path operations* のグループ全体に認証を要求する、といった用途に使えます。個々の *path operation* に依存関係を追加していなくても構いません。 + +/// + +/// check | 確認 + +`prefix`、`tags`、`responses`、`dependencies` の各パラメータは(ほかの多くのケースと同様に)コード重複を避けるための **FastAPI** の機能です。 + +/// + +### 依存関係をインポート { #import-the-dependencies } + +このコードはモジュール `app.routers.items`(ファイル `app/routers/items.py`)内にあります。 + +そして依存関係の関数はモジュール `app.dependencies`(ファイル `app/dependencies.py`)から取得する必要があります。 + +そこで、依存関係には `..` を使った相対インポートを使います: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} + +#### 相対インポートの仕組み { #how-relative-imports-work } + +/// tip | 豆知識 + +インポートの仕組みを十分理解している場合は、次の節に進んでください。 + +/// + +ドット1つ `.` を使うと、次のような意味になります: + +```Python +from .dependencies import get_token_header +``` + +意味: + +* このモジュール(`app/routers/items.py`)が存在する同じパッケージ(ディレクトリ `app/routers/`)から開始し... +* モジュール `dependencies`(仮想的には `app/routers/dependencies.py`)を探し... +* そこから関数 `get_token_header` をインポートする。 + +しかしそのファイルは存在せず、実際の依存関係は `app/dependencies.py` にあります。 + +アプリ/ファイル構成がどうなっていたかを思い出してください: + + + +--- + +ドット2つ `..` を使うと、次のようになります: + +```Python +from ..dependencies import get_token_header +``` + +意味: + +* このモジュール(`app/routers/items.py`)が存在する同じパッケージ(ディレクトリ `app/routers/`)から開始し... +* 親パッケージ(ディレクトリ `app/`)に移動し... +* そこでモジュール `dependencies`(ファイル `app/dependencies.py`)を探し... +* そこから関数 `get_token_header` をインポートする。 + +これは正しく動作します! 🎉 + +--- + +同様に、ドット3つ `...` を使うと: + +```Python +from ...dependencies import get_token_header +``` + +意味: + +* このモジュール(`app/routers/items.py`)が存在する同じパッケージ(ディレクトリ `app/routers/`)から開始し... +* 親パッケージ(ディレクトリ `app/`)に移動し... +* さらにその親パッケージに移動しようとします(`app` は最上位なので親パッケージはありません 😱)... +* そこでモジュール `dependencies`(ファイル `app/dependencies.py`)を探し... +* そこから関数 `get_token_header` をインポートする。 + +これは `app/` より上位のパッケージ(独自の `__init__.py` を持つ)を参照することになります。しかしそのようなものはありません。そのため、この例ではエラーになります。🚨 + +これで仕組みが分かったので、どれほど複雑でも自分のアプリで相対インポートを使えます。🤓 + +### カスタムの `tags`、`responses`、`dependencies` を追加 { #add-some-custom-tags-responses-and-dependencies } + +`APIRouter` に追加済みなので、各 *path operation* に `/items` の prefix や `tags=["items"]` を付けていません。 + +しかし、特定の *path operation* に適用される _追加の_ `tags` や、その *path operation* 固有の追加の `responses` を加えることはできます: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} + +/// tip | 豆知識 + +この最後の *path operation* は、`["items", "custom"]` のタグの組み合わせを持ちます。 + +またドキュメントには `404` と `403` の両方のレスポンスが表示されます。 + +/// + +## メインの `FastAPI` { #the-main-fastapi } + +次に、`app/main.py` のモジュールを見ていきます。 + +ここでクラス `FastAPI` をインポートして使用します。 + +これはすべてをまとめるアプリケーションのメインファイルになります。 + +そして大部分のロジックはそれぞれの専用モジュールに置かれるため、メインファイルはかなりシンプルになります。 + +### `FastAPI` のインポート { #import-fastapi } + +通常どおり `FastAPI` クラスをインポートして作成します。 + +さらに、各 `APIRouter` の依存関係と組み合わされる[グローバル依存関係](dependencies/global-dependencies.md){.internal-link target=_blank}も宣言できます: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} + +### `APIRouter` のインポート { #import-the-apirouter } + +次に、`APIRouter` を持つ他のサブモジュールをインポートします: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} + +`app/routers/users.py` と `app/routers/items.py` は同じ Python パッケージ `app` のサブモジュールなので、1つのドット `.` を使った「相対インポート」でインポートできます。 + +### インポートの動作 { #how-the-importing-works } + +次の部分: + +```Python +from .routers import items, users +``` + +は次の意味です: + +* このモジュール(`app/main.py`)が存在する同じパッケージ(ディレクトリ `app/`)から開始し... +* サブパッケージ `routers`(ディレクトリ `app/routers/`)を探し... +* そこからサブモジュール `items`(ファイル `app/routers/items.py`)と `users`(ファイル `app/routers/users.py`)をインポートする... + +モジュール `items` には変数 `router`(`items.router`)があります。これは `app/routers/items.py` で作成した `APIRouter` オブジェクトと同じものです。 + +モジュール `users` についても同様です。 + +次のようにインポートすることもできます: + +```Python +from app.routers import items, users +``` + +/// info | 情報 + +最初のバージョンは「相対インポート」です: + +```Python +from .routers import items, users +``` + +2つ目のバージョンは「絶対インポート」です: + +```Python +from app.routers import items, users +``` + +Python のパッケージとモジュールについて詳しくは、公式の Python モジュールに関するドキュメントをご覧ください。 + +/// + +### 名前衝突の回避 { #avoid-name-collisions } + +サブモジュール `items` の変数 `router` だけをインポートするのではなく、サブモジュール自体を直接インポートしています。 + +これは、サブモジュール `users` にも `router` という変数があるためです。 + +もし次のように続けてインポートした場合: + +```Python +from .routers.items import router +from .routers.users import router +``` + +`users` の `router` が `items` のものを上書きしてしまい、同時に両方を使えなくなります。 + +同じファイルで両方を使えるようにするため、サブモジュールを直接インポートします: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} + +### `users` と `items` の `APIRouter` を取り込む { #include-the-apirouters-for-users-and-items } + +では、サブモジュール `users` と `items` から `router` を取り込みます: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} + +/// info | 情報 + +`users.router` は、ファイル `app/routers/users.py` 内の `APIRouter` を含みます。 + +`items.router` は、ファイル `app/routers/items.py` 内の `APIRouter` を含みます。 + +/// + +`app.include_router()` を使って、各 `APIRouter` をメインの `FastAPI` アプリケーションに追加できます。 + +そのルーターのすべてのルートがアプリに含まれます。 + +/// note | 技術詳細 + +実際には、`APIRouter` で宣言された各 *path operation* ごとに内部的に *path operation* が作成されます。 + +つまり裏側では、すべてが同じ単一のアプリであるかのように動作します。 + +/// + +/// check | 確認 + +ルーターを取り込んでもパフォーマンスを心配する必要はありません。 + +これは起動時にマイクロ秒で行われます。 + +したがってパフォーマンスには影響しません。⚡ + +/// + +### カスタムの `prefix`、`tags`、`responses`、`dependencies` 付きで `APIRouter` を取り込む { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies } + +あなたの組織から `app/internal/admin.py` ファイルが提供されたとしましょう。 + +そこには、組織が複数プロジェクトで共有している管理用の *path operations* を持つ `APIRouter` が含まれています。 + +この例ではとてもシンプルですが、組織内の他プロジェクトと共有しているため、`APIRouter` 自体を直接変更して `prefix`、`dependencies`、`tags` などを追加できないとします: + +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} + +それでも、`APIRouter` を取り込む際にカスタムの `prefix` を設定してすべての *path operations* を `/admin` で始めたい、既存の `dependencies` で保護したい、さらに `tags` と `responses` も含めたいとします。 + +元の `APIRouter` を変更することなく、`app.include_router()` にこれらのパラメータを渡すことで宣言できます: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} + +このようにすると、元の `APIRouter` は未変更のままなので、同じ `app/internal/admin.py` ファイルを組織内の他プロジェクトとも引き続き共有できます。 + +結果として、このアプリ内では `admin` モジュールの各 *path operation* が次のようになります: + +* prefix は `/admin` +* タグは `admin` +* 依存関係は `get_token_header` +* レスポンスは `418` 🍵 + +ただし、これはこのアプリ内のその `APIRouter` にのみ影響し、それを使用する他のコードには影響しません。 + +例えば、他のプロジェクトでは同じ `APIRouter` を別の認証方式で使うこともできます。 + +### *path operation* を追加 { #include-a-path-operation } + +`FastAPI` アプリに *path operations* を直接追加することもできます。 + +ここでは(できることを示すためだけに)追加します 🤷: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} + +そして、`app.include_router()` で追加したほかの *path operations* と一緒に正しく動作します。 + +/// info | 非常に技術的な詳細 + +注記: これは非常に技術的な詳細で、**読み飛ばして構いません**。 + +--- + +`APIRouter` は「マウント」されておらず、アプリケーションの他部分から分離されていません。 + +これは、それらの *path operations* を OpenAPI スキーマやユーザーインターフェースに含めたいからです。 + +完全に分離して独立に「マウント」できないため、*path operations* は直接取り込まれるのではなく「クローン(再作成)」されます。 + +/// + +## 自動APIドキュメントの確認 { #check-the-automatic-api-docs } + +アプリを実行します: + +
+ +```console +$ fastapi dev app/main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +そして http://127.0.0.1:8000/docs を開きます。 + +すべてのサブモジュール由来のパスを含む自動 API ドキュメントが表示され、正しいパス(および prefix)と正しいタグが使われているのが分かります: + + + +## 同じルーターを異なる `prefix` で複数回取り込む { #include-the-same-router-multiple-times-with-different-prefix } + +同じルーターに対して、異なる prefix で `.include_router()` を複数回使うこともできます。 + +例えば、同じ API を `/api/v1` と `/api/latest` のように異なる prefix で公開する場合に役立ちます。 + +高度な使い方なので不要かもしれませんが、必要な場合に備えて用意されています。 + +## `APIRouter` を別の `APIRouter` に取り込む { #include-an-apirouter-in-another } + +`APIRouter` を `FastAPI` アプリケーションに取り込めるのと同じように、`APIRouter` を別の `APIRouter` に取り込むこともできます: + +```Python +router.include_router(other_router) +``` + +`router` を `FastAPI` アプリに取り込む前にこれを実行して、`other_router` の *path operations* も含まれるようにしてください。 diff --git a/docs/ja/docs/tutorial/body-multiple-params.md b/docs/ja/docs/tutorial/body-multiple-params.md index 4ce77cc0d..0f81f4c46 100644 --- a/docs/ja/docs/tutorial/body-multiple-params.md +++ b/docs/ja/docs/tutorial/body-multiple-params.md @@ -106,12 +106,6 @@ q: str | None = None ``` -またはPython 3.10以上では: - -```Python -q: Union[str, None] = None -``` - 例えば: {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/ja/docs/tutorial/body-nested-models.md b/docs/ja/docs/tutorial/body-nested-models.md index 24eb30208..ab78b8f86 100644 --- a/docs/ja/docs/tutorial/body-nested-models.md +++ b/docs/ja/docs/tutorial/body-nested-models.md @@ -164,7 +164,7 @@ images: list[Image] 以下のように: -{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} ## あらゆる場所でのエディタサポート { #editor-support-everywhere } @@ -194,7 +194,7 @@ Pydanticモデルではなく、`dict`を直接使用している場合はこの この場合、`int`のキーと`float`の値を持つものであれば、どんな`dict`でも受け入れることができます: -{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} /// tip | 豆知識 diff --git a/docs/ja/docs/tutorial/body-updates.md b/docs/ja/docs/tutorial/body-updates.md index e888d5a0d..310530c69 100644 --- a/docs/ja/docs/tutorial/body-updates.md +++ b/docs/ja/docs/tutorial/body-updates.md @@ -68,7 +68,7 @@ まとめると、部分的な更新を適用するには、次のようにします: -* (オプションで)`PUT`の代わりに`PATCH`を使用します。 +* (オプションで)`PATCH`の代わりに`PUT`を使用します。 * 保存されているデータを取得します。 * そのデータをPydanticモデルにいれます。 * 入力モデルからデフォルト値を含まない`dict`を生成します(`exclude_unset`を使用します)。 diff --git a/docs/ja/docs/tutorial/body.md b/docs/ja/docs/tutorial/body.md index a219faed0..7c939bdfa 100644 --- a/docs/ja/docs/tutorial/body.md +++ b/docs/ja/docs/tutorial/body.md @@ -74,7 +74,7 @@ APIはほとんどの場合 **レスポンス** ボディを送信する必要 * 受け取ったデータをパラメータ `item` に渡します。 * 関数内で `Item` 型として宣言したため、すべての属性とその型について、エディタサポート(補完など)も利用できます。 * モデル向けの JSON Schema 定義を生成します。プロジェクトにとって意味があるなら、他の場所でも好きなように利用できます。 -* それらのスキーマは生成されるOpenAPIスキーマの一部となり、自動ドキュメントの UIs で使用されます。 +* それらのスキーマは生成されるOpenAPIスキーマの一部となり、自動ドキュメントの UIs で使用されます。 ## 自動ドキュメント { #automatic-docs } @@ -155,7 +155,7 @@ APIはほとんどの場合 **レスポンス** ボディを送信する必要 FastAPIは、デフォルト値 `= None` があるため、`q` の値が必須ではないことを認識します。 -`str | None`(Python 3.10+)や `Union[str, None]`(Python 3.9+)の `Union` は、値が必須ではないことを判断するためにFastAPIでは使用されません。`= None` というデフォルト値があるため、必須ではないことを認識します。 +`str | None` は、値が必須ではないことを判断するためにFastAPIでは使用されません。`= None` というデフォルト値があるため、必須ではないことを認識します。 しかし、型アノテーションを追加すると、エディタがより良いサポートを提供し、エラーを検出できるようになります。 diff --git a/docs/ja/docs/tutorial/cookie-param-models.md b/docs/ja/docs/tutorial/cookie-param-models.md index 10ffb2566..89ae42438 100644 --- a/docs/ja/docs/tutorial/cookie-param-models.md +++ b/docs/ja/docs/tutorial/cookie-param-models.md @@ -46,7 +46,7 @@ 特定の(あまり一般的ではないかもしれない)ケースで、受け付けるクッキーを**制限**する必要があるかもしれません。 -あなたのAPIは独自の クッキー同意 を管理する能力を持っています。 🤪🍪 +あなたのAPIは独自の クッキー同意 を管理する能力を持っています。 🤪🍪 Pydanticのモデルの Configuration を利用して、 `extra` フィールドを `forbid` とすることができます。 @@ -54,9 +54,9 @@ Pydanticのモデルの Configuration を利用して、 `extra` フィールド もしクライアントが**余分なクッキー**を送ろうとすると、**エラー**レスポンスが返されます。 -どうせAPIに拒否されるのにあなたの同意を得ようと精一杯努力する可哀想なクッキーバナーたち... 🍪 +どうせAPIに拒否されるのにあなたの同意を得ようと精一杯努力する可哀想なクッキーバナーたち... 🍪 -例えば、クライアントがクッキー `santa_tracker` を `good-list-please` という値で送ろうとすると、`santa_tracker` という クッキーが許可されていない ことを通知する**エラー**レスポンスが返されます: +例えば、クライアントがクッキー `santa_tracker` を `good-list-please` という値で送ろうとすると、`santa_tracker` という クッキーが許可されていない ことを通知する**エラー**レスポンスが返されます: ```json { @@ -73,4 +73,4 @@ Pydanticのモデルの Configuration を利用して、 `extra` フィールド ## まとめ { #summary } -**FastAPI**では、**クッキー**を宣言するために、**Pydanticモデル**を使用できます。😎 +**FastAPI**では、**クッキー**を宣言するために、**Pydanticモデル**を使用できます。😎 diff --git a/docs/ja/docs/tutorial/cors.md b/docs/ja/docs/tutorial/cors.md index a1dfe8e62..5136a7fd5 100644 --- a/docs/ja/docs/tutorial/cors.md +++ b/docs/ja/docs/tutorial/cors.md @@ -46,7 +46,7 @@ * 特定のHTTPメソッド (`POST`、`PUT`) またはワイルドカード `"*"` を使用してすべて許可。 * 特定のHTTPヘッダー、またはワイルドカード `"*"`を使用してすべて許可。 -{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} `CORSMiddleware` 実装で使用されるデフォルトのパラメータはデフォルトで制限が厳しいため、ブラウザがクロスドメインのコンテキストでそれらを使用できるようにするには、特定のオリジン、メソッド、またはヘッダーを明示的に有効にする必要があります。 diff --git a/docs/ja/docs/tutorial/debugging.md b/docs/ja/docs/tutorial/debugging.md index 8fe5b2d5d..9d88ba42b 100644 --- a/docs/ja/docs/tutorial/debugging.md +++ b/docs/ja/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ Visual Studio CodeやPyCharmなどを使用して、エディター上でデバ FastAPIアプリケーション上で、`uvicorn` を直接インポートして実行します: -{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} ### `__name__ == "__main__"` について { #about-name-main } diff --git a/docs/ja/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ja/docs/tutorial/dependencies/classes-as-dependencies.md index 3cb1fe73d..21de5b978 100644 --- a/docs/ja/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/ja/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ FastAPIが実際にチェックしているのは、それが「呼び出し可 上のコードでは`CommonQueryParams`を2回書いていることに注目してください: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ 注釈なし +//// tab | Python 3.10+ 注釈なし /// tip | 豆知識 @@ -137,7 +137,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) この場合、以下にある最初の`CommonQueryParams`: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.9+ 注釈なし +//// tab | Python 3.10+ 注釈なし /// tip | 豆知識 @@ -163,7 +163,7 @@ commons: CommonQueryParams ... 実際には以下のように書けばいいだけです: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ 注釈なし +//// tab | Python 3.10+ 注釈なし /// tip | 豆知識 @@ -197,7 +197,7 @@ commons = Depends(CommonQueryParams) しかし、ここでは`CommonQueryParams`を2回書くというコードの繰り返しが発生していることがわかります: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ 注釈なし +//// tab | Python 3.10+ 注釈なし /// tip | 豆知識 @@ -225,7 +225,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) 以下のように書く代わりに: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ 注釈なし +//// tab | Python 3.10+ 注釈なし /// tip | 豆知識 @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...以下のように書きます: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.9+ 注釈なし +//// tab | Python 3.10+ 注釈なし /// tip | 豆知識 diff --git a/docs/ja/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/ja/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 2051afc05..d0a2b1672 100644 --- a/docs/ja/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/ja/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -14,7 +14,7 @@ それは`Depends()`の`list`であるべきです: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} これらの依存関係は、通常の依存関係と同様に実行・解決されます。しかし、それらの値(何かを返す場合)は*path operation 関数*には渡されません。 @@ -44,13 +44,13 @@ これらはリクエストの要件(ヘッダーのようなもの)やその他のサブ依存関係を宣言できます: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} ### 例外の発生 { #raise-exceptions } これらの依存関係は、通常の依存関係と同じように例外を`raise`できます: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} ### 戻り値 { #return-values } @@ -58,7 +58,7 @@ つまり、すでにどこかで使っている通常の依存関係(値を返すもの)を再利用でき、値は使われなくても依存関係は実行されます: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} ## *path operation*のグループに対する依存関係 { #dependencies-for-a-group-of-path-operations } diff --git a/docs/ja/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ja/docs/tutorial/dependencies/dependencies-with-yield.md index 8095114c3..380dcb536 100644 --- a/docs/ja/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/ja/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,6 +1,6 @@ # `yield`を持つ依存関係 { #dependencies-with-yield } -FastAPIは、いくつかの終了後の追加のステップを行う依存関係をサポートしています。 +FastAPIは、いくつかの終了後の追加のステップを行う依存関係をサポートしています。 これを行うには、`return`の代わりに`yield`を使い、その後に追加のステップ(コード)を書きます。 @@ -29,15 +29,15 @@ FastAPIは、いくつかの + +
+ +## 余分なヘッダーを禁止 { #forbid-extra-headers } + +特殊なユースケース(あまり一般的ではありません)では、受け付けるヘッダーを**制限**したい場合があります。 + +Pydantic のモデル設定で `extra` フィールドを `forbid` にして禁止できます: + +{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *} + +クライアントが**余分なヘッダー**を送信しようとすると、**エラー**レスポンスが返されます。 + +例えば、クライアントが値 `plumbus` の `tool` ヘッダーを送ろうとすると、ヘッダーパラメータ `tool` は許可されていない旨の**エラー**レスポンスが返されます: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["header", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ] +} +``` + +## アンダースコア変換の無効化 { #disable-convert-underscores } + +通常のヘッダーパラメータと同様に、パラメータ名にアンダースコアがある場合は**自動的にハイフンに変換**されます。 + +例えば、コード上でヘッダーパラメータ `save_data` を定義すると、想定される HTTP ヘッダーは `save-data` となり、ドキュメント上にもそのように表示されます。 + +何らかの理由でこの自動変換を無効化する必要がある場合、ヘッダーパラメータ用の Pydantic モデルでも無効化できます。 + +{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *} + +/// warning | 注意 + +`convert_underscores` を `False` に設定する前に、アンダースコアを含むヘッダーの使用を禁止している HTTP プロキシやサーバーがあることに留意してください。 + +/// + +## まとめ { #summary } + +**Pydantic モデル**を使って **FastAPI** で **ヘッダー**を宣言できます。😎 diff --git a/docs/ja/docs/tutorial/metadata.md b/docs/ja/docs/tutorial/metadata.md index 0ffb8f350..3b70bf2f4 100644 --- a/docs/ja/docs/tutorial/metadata.md +++ b/docs/ja/docs/tutorial/metadata.md @@ -18,7 +18,7 @@ OpenAPI仕様および自動APIドキュメントUIで使用される次のフ 以下のように設定できます: -{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} +{* ../../docs_src/metadata/tutorial001_py310.py hl[3:16, 19:32] *} /// tip | 豆知識 @@ -36,7 +36,7 @@ OpenAPI 3.1.0 および FastAPI 0.99.0 以降では、`license_info` を `url` 例: -{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} +{* ../../docs_src/metadata/tutorial001_1_py310.py hl[31] *} ## タグのメタデータ { #metadata-for-tags } @@ -58,7 +58,7 @@ OpenAPI 3.1.0 および FastAPI 0.99.0 以降では、`license_info` を `url` タグのメタデータを作成し、それを `openapi_tags` パラメータに渡します: -{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[3:16,18] *} 説明の中でMarkdownを使用できることに注意してください。たとえば「login」は太字 (**login**) で表示され、「fancy」は斜体 (_fancy_) で表示されます。 @@ -72,7 +72,7 @@ OpenAPI 3.1.0 および FastAPI 0.99.0 以降では、`license_info` を `url` *path operation*(および `APIRouter`)の `tags` パラメータを使用して、それらを異なるタグに割り当てます: -{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[21,26] *} /// info | 情報 @@ -100,7 +100,7 @@ OpenAPI 3.1.0 および FastAPI 0.99.0 以降では、`license_info` を `url` たとえば、`/api/v1/openapi.json` で提供されるように設定するには: -{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py310.py hl[3] *} OpenAPIスキーマを完全に無効にする場合は、`openapi_url=None` を設定できます。これにより、それを使用するドキュメントUIも無効になります。 @@ -117,4 +117,4 @@ OpenAPIスキーマを完全に無効にする場合は、`openapi_url=None` を たとえば、`/documentation` でSwagger UIが提供されるように設定し、ReDocを無効にするには: -{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial003_py310.py hl[3] *} diff --git a/docs/ja/docs/tutorial/middleware.md b/docs/ja/docs/tutorial/middleware.md index 12fb57a64..103d6e2c0 100644 --- a/docs/ja/docs/tutorial/middleware.md +++ b/docs/ja/docs/tutorial/middleware.md @@ -31,7 +31,7 @@ * 次に、対応する*path operation*によって生成された `response` を返します。 * その後、`response` を返す前にさらに `response` を変更することもできます。 -{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[8:9,11,14] *} /// tip | 豆知識 @@ -57,7 +57,7 @@ 例えば、リクエストの処理とレスポンスの生成にかかった秒数を含むカスタムヘッダー `X-Process-Time` を追加できます: -{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[10,12:13] *} /// tip | 豆知識 @@ -92,4 +92,4 @@ app.add_middleware(MiddlewareB) 他のミドルウェアの詳細については、[高度なユーザーガイド: 高度なミドルウェア](../advanced/middleware.md){.internal-link target=_blank}を参照してください。 -次のセクションでは、ミドルウェアを使用して CORS を処理する方法について説明します。 +次のセクションでは、ミドルウェアを使用して CORS を処理する方法について説明します。 diff --git a/docs/ja/docs/tutorial/path-operation-configuration.md b/docs/ja/docs/tutorial/path-operation-configuration.md index eb6b6b11a..556cc6b14 100644 --- a/docs/ja/docs/tutorial/path-operation-configuration.md +++ b/docs/ja/docs/tutorial/path-operation-configuration.md @@ -46,7 +46,7 @@ **FastAPI** は、プレーンな文字列の場合と同じ方法でそれをサポートしています: -{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *} +{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *} ## 概要と説明 { #summary-and-description } @@ -56,7 +56,7 @@ ## docstringを用いた説明 { #description-from-docstring } -説明文は長くて複数行におよぶ傾向があるので、関数docstring内に*path operation*の説明文を宣言できます。すると、**FastAPI** は説明文を読み込んでくれます。 +説明文は長くて複数行におよぶ傾向があるので、関数docstring内に*path operation*の説明文を宣言できます。すると、**FastAPI** は説明文を読み込んでくれます。 docstringにMarkdownを記述すれば、正しく解釈されて表示されます。(docstringのインデントを考慮して) @@ -90,9 +90,9 @@ OpenAPIは*path operation*ごとにレスポンスの説明を必要としてい ## *path operation*を非推奨にする { #deprecate-a-path-operation } -*path operation*をdeprecatedとしてマークする必要があるが、それを削除しない場合は、`deprecated`パラメータを渡します: +*path operation*をdeprecatedとしてマークする必要があるが、それを削除しない場合は、`deprecated`パラメータを渡します: -{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} 対話的ドキュメントでは非推奨と明記されます: diff --git a/docs/ja/docs/tutorial/path-params-numeric-validations.md b/docs/ja/docs/tutorial/path-params-numeric-validations.md index 6a9ecc4e7..ab3240f04 100644 --- a/docs/ja/docs/tutorial/path-params-numeric-validations.md +++ b/docs/ja/docs/tutorial/path-params-numeric-validations.md @@ -54,11 +54,11 @@ Pythonは「デフォルト」を持つ値を「デフォルト」を持たな そのため、以下のように関数を宣言することができます: -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} ただし、`Annotated`を使う場合はこの問題は起きないことを覚えておいてください。`Query()`や`Path()`に関数パラメータのデフォルト値を使わないためです。 -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## 必要に応じてパラメータを並び替えるトリック { #order-the-parameters-as-you-need-tricks } @@ -81,15 +81,15 @@ Pythonは「デフォルト」を持つ値を「デフォルト」を持たな 関数の最初のパラメータとして`*`を渡します。 -Pythonはその`*`で何かをすることはありませんが、それ以降のすべてのパラメータがキーワード引数(キーと値のペア)として呼ばれるべきものであると知っているでしょう。それはkwargsとしても知られています。たとえデフォルト値がなくても。 +Pythonはその`*`で何かをすることはありませんが、それ以降のすべてのパラメータがキーワード引数(キーと値のペア)として呼ばれるべきものであると知っているでしょう。それはkwargsとしても知られています。たとえデフォルト値がなくても。 -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### `Annotated`のほうがよい { #better-with-annotated } `Annotated`を使う場合は、関数パラメータのデフォルト値を使わないため、この問題は起きず、おそらく`*`を使う必要もありません。 -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## 数値の検証: 以上 { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ Pythonはその`*`で何かをすることはありませんが、それ以降 ここで、`ge=1`の場合、`item_id`は`1`「より大きい`g`か、同じ`e`」整数でなければなりません。 -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## 数値の検証: より大きいと小なりイコール { #number-validations-greater-than-and-less-than-or-equal } @@ -106,7 +106,7 @@ Pythonはその`*`で何かをすることはありませんが、それ以降 * `gt`: `g`reater `t`han * `le`: `l`ess than or `e`qual -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## 数値の検証: 浮動小数点、 大なり小なり { #number-validations-floats-greater-than-and-less-than } @@ -118,7 +118,7 @@ Pythonはその`*`で何かをすることはありませんが、それ以降 これはltも同じです。 -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## まとめ { #recap } diff --git a/docs/ja/docs/tutorial/path-params.md b/docs/ja/docs/tutorial/path-params.md index 96a1fe9d1..5b78eb7b1 100644 --- a/docs/ja/docs/tutorial/path-params.md +++ b/docs/ja/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメータ」や「パス変数」を宣言できます: -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} パスパラメータ `item_id` の値は、引数 `item_id` として関数に渡されます。 @@ -16,7 +16,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー 標準のPythonの型アノテーションを使用して、関数内のパスパラメータの型を宣言できます: -{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params/tutorial002_py310.py hl[7] *} ここでは、 `item_id` は `int` として宣言されています。 @@ -26,7 +26,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー /// -## データ変換 { #data-conversion } +## データ変換 { #data-conversion } この例を実行し、ブラウザで http://127.0.0.1:8000/items/3 を開くと、次のレスポンスが表示されます: @@ -38,7 +38,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー 関数が受け取った(および返した)値は、文字列の `"3"` ではなく、Pythonの `int` としての `3` であることに注意してください。 -したがって、その型宣言を使うと、**FastAPI**は自動リクエスト "解析" を行います。 +したがって、その型宣言を使うと、**FastAPI**は自動リクエスト "解析" を行います。 /// @@ -118,13 +118,13 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー *path operations* は順に評価されるので、 `/users/me` が `/users/{user_id}` よりも先に宣言されているか確認する必要があります: -{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *} それ以外の場合、 `/users/{user_id}` は `/users/me` としてもマッチします。値が `"me"` であるパラメータ `user_id` を受け取ると「考え」ます。 同様に、path operation を再定義することはできません: -{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *} パスは最初にマッチしたものが常に使われるため、最初のものが常に使用されます。 @@ -140,11 +140,11 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー そして、固定値のクラス属性を作ります。すると、その値が使用可能な値となります: -{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *} /// tip | 豆知識 -"AlexNet"、"ResNet"そして"LeNet"は機械学習モデルの名前です。 +"AlexNet"、"ResNet"そして"LeNet"は機械学習モデルの名前です。 /// @@ -152,7 +152,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー 次に、作成したenumクラスである`ModelName`を使用した型アノテーションをもつ*パスパラメータ*を作成します: -{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[16] *} ### ドキュメントの確認 { #check-the-docs } @@ -168,13 +168,13 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー これは、作成した列挙型 `ModelName` の*列挙型メンバ*と比較できます: -{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[17] *} #### *列挙値*の取得 { #get-the-enumeration-value } `model_name.value` 、もしくは一般に、 `your_enum_member.value` を使用して実際の値 (この場合は `str`) を取得できます。 -{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[20] *} /// tip | 豆知識 @@ -188,7 +188,7 @@ Pythonのformat文字列と同様のシンタックスで「パスパラメー それらはクライアントに返される前に適切な値 (この場合は文字列) に変換されます。 -{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[18,21,23] *} クライアントは以下の様なJSONレスポンスを得ます: @@ -227,7 +227,7 @@ Starletteのオプションを直接使用することで、以下のURLの様 したがって、以下の様に使用できます: -{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py310.py hl[6] *} /// tip | 豆知識 @@ -242,7 +242,7 @@ Starletteのオプションを直接使用することで、以下のURLの様 簡潔で、本質的で、標準的なPythonの型宣言を使用することで、**FastAPI**は以下を行います: * エディターサポート: エラーチェック、自動補完、など -* データ「解析」 +* データ「解析」 * データバリデーション * APIアノテーションと自動ドキュメント生成 diff --git a/docs/ja/docs/tutorial/query-params-str-validations.md b/docs/ja/docs/tutorial/query-params-str-validations.md index e230ef29a..dda4e120b 100644 --- a/docs/ja/docs/tutorial/query-params-str-validations.md +++ b/docs/ja/docs/tutorial/query-params-str-validations.md @@ -47,40 +47,16 @@ FastAPI はバージョン 0.95.0 で `Annotated` のサポートを追加し( 次の型アノテーションがありました: -//// tab | Python 3.10+ - ```Python q: str | None = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Union[str, None] = None -``` - -//// - これを `Annotated` で包んで、次のようにします: -//// tab | Python 3.10+ - ```Python q: Annotated[str | None] = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Annotated[Union[str, None]] = None -``` - -//// - どちらも同じ意味で、`q` は `str` または `None` になり得るパラメータで、デフォルトでは `None` です。 では、面白いところに進みましょう。 🎉 @@ -109,7 +85,7 @@ FastAPI は次を行います: ## 代替(古い方法): デフォルト値としての `Query` { #alternative-old-query-as-the-default-value } -FastAPI の以前のバージョン(0.95.0 より前)では、パラメータのデフォルト値として `Query` を使う必要があり、`Annotated` の中に入れるのではありませんでした。これを使ったコードを見かける可能性が高いので、説明します。 +FastAPI の以前のバージョン(0.95.0 より前)では、パラメータのデフォルト値として `Query` を使う必要があり、`Annotated` の中に入れるのではありませんでした。これを使ったコードを見かける可能性が高いので、説明します。 /// tip | 豆知識 @@ -192,7 +168,7 @@ FastAPI なしで同じ関数を **別の場所** から **呼び出しても** ## 正規表現の追加 { #add-regular-expressions } -パラメータが一致するべき 正規表現 `pattern` を定義することができます: +パラメータが一致するべき 正規表現 `pattern` を定義することができます: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -212,7 +188,7 @@ FastAPI なしで同じ関数を **別の場所** から **呼び出しても** クエリパラメータ `q` の `min_length` を `3` とし、デフォルト値を `"fixedquery"` として宣言したいとします: -{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} /// note | 備考 @@ -242,7 +218,7 @@ q: Annotated[str | None, Query(min_length=3)] = None そのため、`Query` を使いながら値を必須として宣言したい場合は、単にデフォルト値を宣言しません: -{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} ### 必須、`None` にできる { #required-can-be-none } @@ -293,7 +269,7 @@ http://localhost:8000/items/?q=foo&q=bar また、値が指定されていない場合はデフォルトの `list` を定義することもできます。 -{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} 以下にアクセスすると: @@ -316,7 +292,7 @@ http://localhost:8000/items/ `list[str]` の代わりに直接 `list` を使うこともできます: -{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} /// note | 備考 @@ -372,7 +348,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems さて、このパラメータが気に入らなくなったとしましょう。 -それを使っているクライアントがいるので、しばらくは残しておく必要がありますが、ドキュメントにはdeprecatedと明記しておきたいです。 +それを使っているクライアントがいるので、しばらくは残しておく必要がありますが、ドキュメントにはdeprecatedと明記しておきたいです。 その場合、`Query`にパラメータ`deprecated=True`を渡します: @@ -402,7 +378,7 @@ Pydantic には ISBN の書籍番号なら item ID が `isbn-` で始まること、IMDB の movie URL ID なら `imdb-` で始まることをチェックします: +例えば、このカスタムバリデータは、ISBN の書籍番号なら item ID が `isbn-` で始まること、IMDB の movie URL ID なら `imdb-` で始まることをチェックします: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -436,7 +412,7 @@ Pydantic には 反復可能オブジェクト を取得します。 +`data.items()` で、辞書の各アイテムのキーと値を含むタプルを持つ 反復可能オブジェクト を取得します。 この反復可能オブジェクトを `list(data.items())` で適切な `list` に変換します。 diff --git a/docs/ja/docs/tutorial/query-params.md b/docs/ja/docs/tutorial/query-params.md index 41e51ed78..d32c9822b 100644 --- a/docs/ja/docs/tutorial/query-params.md +++ b/docs/ja/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ パスパラメータではない関数パラメータを宣言すると、それらは自動的に「クエリ」パラメータとして解釈されます。 -{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} クエリはURL内で `?` の後に続くキーとバリューの組で、 `&` で区切られています。 @@ -24,7 +24,7 @@ http://127.0.0.1:8000/items/?skip=0&limit=10 パスパラメータに適用される処理と完全に同様な処理がクエリパラメータにも施されます: * エディターサポート (明らかに) -* データ 「解析」 +* データ 「解析」 * データバリデーション * 自動ドキュメント生成 @@ -128,7 +128,7 @@ http://127.0.0.1:8000/items/foo?short=yes しかしクエリパラメータを必須にしたい場合は、ただデフォルト値を宣言しなければよいです: -{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} ここで、クエリパラメータ `needy` は `str` 型の必須のクエリパラメータです。 diff --git a/docs/ja/docs/tutorial/request-files.md b/docs/ja/docs/tutorial/request-files.md new file mode 100644 index 000000000..538cf6474 --- /dev/null +++ b/docs/ja/docs/tutorial/request-files.md @@ -0,0 +1,176 @@ +# リクエストファイル { #request-files } + +`File` を使って、クライアントがアップロードするファイルを定義できます。 + +/// info | 情報 + +アップロードされたファイルを受け取るには、まず `python-multipart` をインストールします。 + +[仮想環境](../virtual-environments.md){.internal-link target=_blank}を作成して有効化し、次のようにインストールしてください: + +```console +$ pip install python-multipart +``` + +アップロードされたファイルは「form data」として送信されるためです。 + +/// + +## `File` をインポート { #import-file } + +`fastapi` から `File` と `UploadFile` をインポートします: + +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} + +## `File` パラメータの定義 { #define-file-parameters } + +`Body` や `Form` と同様の方法でファイルのパラメータを作成します: + +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} + +/// info | 情報 + +`File` は `Form` を直接継承したクラスです。 + +ただし、`fastapi` から `Query`、`Path`、`File` などをインポートするとき、それらは実際には特殊なクラスを返す関数であることに注意してください。 + +/// + +/// tip | 豆知識 + +ファイルのボディを宣言するには `File` を使う必要があります。そうしないと、パラメータはクエリパラメータやボディ(JSON)パラメータとして解釈されます。 + +/// + +ファイルは「form data」としてアップロードされます。 + +*path operation 関数* のパラメータの型を `bytes` として宣言すると、**FastAPI** がファイルを読み取り、内容を `bytes` として受け取ります。 + +これは内容全体がメモリに保持されることを意味します。小さなファイルには有効です。 + +しかし、多くの場合は `UploadFile` を使う方が有利です。 + +## `UploadFile` によるファイルパラメータ { #file-parameters-with-uploadfile } + +型を `UploadFile` にしてファイルパラメータを定義します: + +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} + +`UploadFile` を使うことには `bytes` に対する次の利点があります: + +- パラメータのデフォルト値に `File()` を使う必要がありません。 +- 「spooled」ファイルを使います: + - 最大サイズまではメモリに保持し、それを超えるとディスクに格納されるファイルです。 +- そのため、画像・動画・大きなバイナリなどの大きなファイルでも、メモリを使い果たすことなくうまく動作します。 +- アップロードされたファイルからメタデータを取得できます。 +- file-like な `async` インターフェースを持ちます。 +- 実際の Python の `SpooledTemporaryFile` オブジェクトを公開しており、file-like オブジェクトを期待する他のライブラリにそのまま渡せます。 + +### `UploadFile` { #uploadfile } + +`UploadFile` には次の属性があります: + +- `filename`: アップロード時の元のファイル名を表す `str`(例: `myimage.jpg`) +- `content_type`: コンテントタイプ(MIME タイプ / メディアタイプ)を表す `str`(例: `image/jpeg`) +- `file`: `SpooledTemporaryFile`file-like なオブジェクト)。これは実際の Python のファイルオブジェクトで、「file-like」オブジェクトを期待する関数やライブラリに直接渡せます。 + +`UploadFile` には次の `async` メソッドがあります。いずれも内部で対応するファイルメソッド(内部の `SpooledTemporaryFile`)を呼び出します。 + +- `write(data)`: `data`(`str` または `bytes`)を書き込みます。 +- `read(size)`: `size`(`int`)バイト/文字を読み込みます。 +- `seek(offset)`: ファイル内のバイト位置 `offset`(`int`)に移動します。 + - 例: `await myfile.seek(0)` はファイルの先頭に移動します。 + - 一度 `await myfile.read()` を実行して、もう一度内容を読みたい場合に特に便利です。 +- `close()`: ファイルを閉じます。 + +これらはすべて `async` メソッドなので、`await` する必要があります。 + +例えば、`async` の *path operation 関数* 内では次のように内容を取得できます: + +```Python +contents = await myfile.read() +``` + +通常の `def` の *path operation 関数* 内にいる場合は、`UploadFile.file` に直接アクセスできます。例えば: + +```Python +contents = myfile.file.read() +``` + +/// note | `async` の技術詳細 + +`async` メソッドを使うと、**FastAPI** はファイルメソッドをスレッドプールで実行し、その完了を待機します。 + +/// + +/// note | Starlette の技術詳細 + +**FastAPI** の `UploadFile` は **Starlette** の `UploadFile` を直接継承していますが、**Pydantic** や FastAPI の他の部分と互換にするために必要な要素を追加しています。 + +/// + +## 「Form Data」とは { #what-is-form-data } + +HTML フォーム(`
`)がサーバーにデータを送る方法は、そのデータに対して通常「特別な」エンコーディングを用い、JSON とは異なります。 + +**FastAPI** は JSON ではなく、適切な場所からそのデータを読み取るようにします。 + +/// note | 技術詳細 + +ファイルを含まない場合、フォームからのデータは通常「メディアタイプ」`application/x-www-form-urlencoded` でエンコードされます。 + +ただしフォームにファイルが含まれる場合は、`multipart/form-data` としてエンコードされます。`File` を使うと、**FastAPI** はボディ内の正しい部分からファイルを取得すべきであると認識します。 + +これらのエンコーディングやフォームフィールドの詳細は、MDN Web Docs の POST を参照してください。 + +/// + +/// warning | 注意 + +1 つの *path operation* に複数の `File` および `Form` パラメータを宣言できますが、同時に JSON として受け取ることを期待する `Body` フィールドを宣言することはできません。リクエストのボディは `application/json` ではなく `multipart/form-data` でエンコードされるためです。 + +これは **FastAPI** の制限ではなく、HTTP プロトコルの仕様です。 + +/// + +## 任意のファイルアップロード { #optional-file-upload } + +標準の型アノテーションを使い、デフォルト値を `None` に設定することで、ファイルを任意にできます: + +{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} + +## 追加メタデータつきの `UploadFile` { #uploadfile-with-additional-metadata } + +例えば追加のメタデータを設定するために、`UploadFile` と併せて `File()` を使うこともできます: + +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} + +## 複数ファイルのアップロード { #multiple-file-uploads } + +同時に複数のファイルをアップロードできます。 + +それらは「form data」で送信される同じ「フォームフィールド」に関連付けられます。 + +そのためには、`bytes` または `UploadFile` のリストを宣言します: + +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} + +宣言どおり、`bytes` または `UploadFile` の `list` を受け取ります。 + +/// note | 技術詳細 + +`from starlette.responses import HTMLResponse` を使うこともできます。 + +**FastAPI** は利便性のため、`starlette.responses` と同じものを `fastapi.responses` として提供しています。ただし、利用可能なレスポンスの多くは Starlette から直接提供されています。 + +/// + +### 追加メタデータつきの複数ファイルアップロード { #multiple-file-uploads-with-additional-metadata } + +先ほどと同様に、`UploadFile` に対しても `File()` を使って追加のパラメータを設定できます: + +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} + +## まとめ { #recap } + +リクエストでフォームデータとして送信されるアップロードファイルを宣言するには、`File`、`bytes`、`UploadFile` を使います。 diff --git a/docs/ja/docs/tutorial/request-form-models.md b/docs/ja/docs/tutorial/request-form-models.md new file mode 100644 index 000000000..071867964 --- /dev/null +++ b/docs/ja/docs/tutorial/request-form-models.md @@ -0,0 +1,78 @@ +# フォームモデル { #form-models } + +FastAPI では、フォームフィールドを宣言するために Pydantic モデルを使用できます。 + +/// info | 情報 + +フォームを使うには、まず `python-multipart` をインストールします。 + +まず [仮想環境](../virtual-environments.md){.internal-link target=_blank} を作成して有効化し、そのうえでインストールしてください。例えば: + +```console +$ pip install python-multipart +``` + +/// + +/// note | 備考 + +これは FastAPI バージョン `0.113.0` 以降でサポートされています。🤓 + +/// + +## フォーム用の Pydantic モデル { #pydantic-models-for-forms } + +受け取りたいフィールドを **フォームフィールド** として持つ **Pydantic モデル** を宣言し、パラメータを `Form` として宣言するだけです: + +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} + +**FastAPI** はリクエストの **フォームデータ** から **各フィールド** のデータを **抽出** し、定義した Pydantic モデルとして渡します。 + +## ドキュメントで確認 { #check-the-docs } + +`/docs` のドキュメント UI で確認できます: + +
+ +
+ +## 追加のフォームフィールドを禁止 { #forbid-extra-form-fields } + +一部の特殊なユースケース(おそらくあまり一般的ではありません)では、フォームフィールドを Pydantic モデルで宣言したもののみに**制限**し、**追加**のフィールドを**禁止**したい場合があります。 + +/// note | 備考 + +これは FastAPI バージョン `0.114.0` 以降でサポートされています。🤓 + +/// + +Pydantic のモデル設定で、`extra` フィールドを `forbid` にできます: + +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} + +クライアントが余分なデータを送信しようとすると、**エラー**のレスポンスを受け取ります。 + +例えば、クライアントが次のフォームフィールドを送ろうとした場合: + +- `username`: `Rick` +- `password`: `Portal Gun` +- `extra`: `Mr. Poopybutthole` + +フィールド `extra` は許可されていない旨のエラーレスポンスが返されます: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["body", "extra"], + "msg": "Extra inputs are not permitted", + "input": "Mr. Poopybutthole" + } + ] +} +``` + +## まとめ { #summary } + +FastAPI でフォームフィールドを宣言するために Pydantic モデルを使用できます。😎 diff --git a/docs/ja/docs/tutorial/request-forms-and-files.md b/docs/ja/docs/tutorial/request-forms-and-files.md index 09e1277c8..9a4e299e9 100644 --- a/docs/ja/docs/tutorial/request-forms-and-files.md +++ b/docs/ja/docs/tutorial/request-forms-and-files.md @@ -16,13 +16,13 @@ $ pip install python-multipart ## `File`と`Form`のインポート { #import-file-and-form } -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} ## `File`と`Form`のパラメータの定義 { #define-file-and-form-parameters } ファイルやフォームのパラメータは`Body`や`Query`の場合と同じように作成します: -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} ファイルとフォームフィールドがフォームデータとしてアップロードされ、ファイルとフォームフィールドを受け取ります。 diff --git a/docs/ja/docs/tutorial/request-forms.md b/docs/ja/docs/tutorial/request-forms.md index 1bdc28670..dda2a4bf7 100644 --- a/docs/ja/docs/tutorial/request-forms.md +++ b/docs/ja/docs/tutorial/request-forms.md @@ -18,17 +18,17 @@ $ pip install python-multipart `fastapi`から`Form`をインポートします: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} ## `Form`のパラメータの定義 { #define-form-parameters } `Body`や`Query`の場合と同じようにフォームパラメータを作成します: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} 例えば、OAuth2仕様が使用できる方法の1つ(「パスワードフロー」と呼ばれる)では、フォームフィールドとして`username`と`password`を送信する必要があります。 -specでは、フィールドの名前が`username`と`password`であることと、JSONではなくフォームフィールドとして送信されることを要求しています。 +仕様では、フィールドの名前が正確に`username`と`password`であることと、JSONではなくフォームフィールドとして送信されることを要求しています。 `Form`では`Body`(および`Query`や`Path`、`Cookie`)と同じ設定を宣言することができます。これには、バリデーション、例、エイリアス(例えば`username`の代わりに`user-name`)などが含まれます。 @@ -56,13 +56,13 @@ HTMLフォーム(`
`)がサーバにデータを送信する方 しかし、フォームがファイルを含む場合は、`multipart/form-data`としてエンコードされます。ファイルの扱いについては次の章で説明します。 -これらのエンコーディングやフォームフィールドの詳細については、MDNPOSTのウェブドキュメントを参照してください。 +これらのエンコーディングやフォームフィールドの詳細については、MDNPOSTのウェブドキュメントを参照してください。 /// /// warning | 注意 -*path operation*で複数の`Form`パラメータを宣言することができますが、JSONとして受け取ることを期待している`Body`フィールドを宣言することはできません。なぜなら、リクエストは`application/json`の代わりに`application/x-www-form-urlencoded`を使ってボディをエンコードするからです。 +*path operation*で複数の`Form`パラメータを宣言することができますが、JSONとして受け取ることを期待している`Body`フィールドを宣言することはできません。なぜなら、リクエストは`application/x-www-form-urlencoded`の代わりに`application/json`を使ってボディをエンコードするからです。 これは **FastAPI**の制限ではなく、HTTPプロトコルの一部です。 diff --git a/docs/ja/docs/tutorial/response-model.md b/docs/ja/docs/tutorial/response-model.md index 258eac8e6..07dc20123 100644 --- a/docs/ja/docs/tutorial/response-model.md +++ b/docs/ja/docs/tutorial/response-model.md @@ -41,7 +41,7 @@ FastAPIはこの戻り値の型を使って以下を行います: /// note | 備考 -`response_model`は「デコレータ」メソッド(`get`、`post`など)のパラメータであることに注意してください。すべてのパラメータやボディのように、*path operation 関数* のパラメータではありません。 +`response_model`は「デコレータ」メソッド(`get`、`post`など)のパラメータです。関数のパラメータやボディなどとは違い、*path operation 関数*のパラメータではありません。 /// @@ -183,7 +183,7 @@ Pydanticフィールドとして有効ではないものを返し、ツール( 最も一般的なケースは、[高度なドキュメントで後述する「Responseを直接返す」](../advanced/response-directly.md){.internal-link target=_blank}場合です。 -{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} このシンプルなケースは、戻り値の型アノテーションが `Response` のクラス(またはサブクラス)であるため、FastAPIが自動的に処理します。 @@ -193,7 +193,7 @@ Pydanticフィールドとして有効ではないものを返し、ツール( 型アノテーションで `Response` のサブクラスを使うこともできます: -{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} これは `RedirectResponse` が `Response` のサブクラスであり、FastAPIがこのシンプルなケースを自動処理するため、同様に動作します。 @@ -201,7 +201,7 @@ Pydanticフィールドとして有効ではないものを返し、ツール( しかし、Pydantic型として有効ではない別の任意のオブジェクト(例: データベースオブジェクト)を返し、関数でそのようにアノテーションすると、FastAPIはその型アノテーションからPydanticレスポンスモデルを作成しようとして失敗します。 -同様に、unionのように、複数の型のうち1つ以上がPydantic型として有効でないものを含む場合も起こります。例えば次は失敗します 💥: +同様に、ユニオンのように、複数の型のうち1つ以上がPydantic型として有効でないものを含む場合も起こります。例えば次は失敗します 💥: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} diff --git a/docs/ja/docs/tutorial/response-status-code.md b/docs/ja/docs/tutorial/response-status-code.md index d3c219416..d4ac45da6 100644 --- a/docs/ja/docs/tutorial/response-status-code.md +++ b/docs/ja/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ * `@app.delete()` * etc. -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} /// note | 備考 @@ -74,7 +74,7 @@ HTTPでは、レスポンスの一部として3桁の数字のステータスコ 先ほどの例をもう一度見てみましょう: -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} `201`は「作成完了」のためのステータスコードです。 @@ -82,7 +82,7 @@ HTTPでは、レスポンスの一部として3桁の数字のステータスコ `fastapi.status`の便利な変数を利用することができます。 -{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} それらは単なる便利なものであり、同じ番号を保持しています。しかし、その方法ではエディタの自動補完を使用してそれらを見つけることができます。 diff --git a/docs/ja/docs/tutorial/schema-extra-example.md b/docs/ja/docs/tutorial/schema-extra-example.md index e526685c2..76a6b0f94 100644 --- a/docs/ja/docs/tutorial/schema-extra-example.md +++ b/docs/ja/docs/tutorial/schema-extra-example.md @@ -74,7 +74,7 @@ Pydanticモデルで`Field()`を使う場合、追加の`examples`も宣言で この場合、examplesはそのボディデータの内部**JSON Schema**の一部になります。 -それでも、執筆時点では、ドキュメントUIの表示を担当するツールであるSwagger UIは、**JSON Schema**内のデータに対して複数の例を表示することをサポートしていません。しかし、回避策については以下を読んでください。 +それでも、執筆時点では、ドキュメントUIの表示を担当するツールであるSwagger UIは、**JSON Schema**内のデータに対して複数の例を表示することをサポートしていません。しかし、回避策については以下を読んでください。 ### OpenAPI固有の`examples` { #openapi-specific-examples } diff --git a/docs/ja/docs/tutorial/security/first-steps.md b/docs/ja/docs/tutorial/security/first-steps.md index 76ef04db8..5bf04386a 100644 --- a/docs/ja/docs/tutorial/security/first-steps.md +++ b/docs/ja/docs/tutorial/security/first-steps.md @@ -20,7 +20,7 @@ `main.py`に、下記の例をコピーします: -{* ../../docs_src/security/tutorial001_an_py39.py *} +{* ../../docs_src/security/tutorial001_an_py310.py *} ## 実行 { #run-it } @@ -132,7 +132,7 @@ OAuth2は、バックエンドやAPIがユーザーを認証するサーバー `OAuth2PasswordBearer` クラスのインスタンスを作成する時に、パラメーター`tokenUrl`を渡します。このパラメーターには、クライアント (ユーザーのブラウザで動作するフロントエンド) がトークンを取得するために`username`と`password`を送信するURLを指定します。 -{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} /// tip | 豆知識 @@ -150,7 +150,7 @@ OAuth2は、バックエンドやAPIがユーザーを認証するサーバー /// info | 情報 -非常に厳格な「Pythonista」であれば、パラメーター名のスタイルが`token_url`ではなく`tokenUrl`であることを気に入らないかもしれません。 +非常に厳格な「Pythonista」であれば、パラメーター名のスタイルが`tokenUrl`ではなく`token_url`であることを気に入らないかもしれません。 それはOpenAPI仕様と同じ名前を使用しているからです。そのため、これらのセキュリティスキームについてもっと調べる必要がある場合は、それをコピーして貼り付ければ、それについての詳細な情報を見つけることができます。 @@ -170,7 +170,7 @@ oauth2_scheme(some, parameters) これで`oauth2_scheme`を`Depends`で依存関係に渡すことができます。 -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} この依存関係は、*path operation 関数*のパラメーター`token`に代入される`str`を提供します。 diff --git a/docs/ja/docs/tutorial/security/get-current-user.md b/docs/ja/docs/tutorial/security/get-current-user.md index 39b97cca5..60378fd98 100644 --- a/docs/ja/docs/tutorial/security/get-current-user.md +++ b/docs/ja/docs/tutorial/security/get-current-user.md @@ -2,7 +2,7 @@ 一つ前の章では、(依存性注入システムに基づいた)セキュリティシステムは、 *path operation 関数* に `str` として `token` を与えていました: -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} しかし、それはまだそんなに有用ではありません。 diff --git a/docs/ja/docs/tutorial/security/index.md b/docs/ja/docs/tutorial/security/index.md index 14f2c8f44..b96021b7f 100644 --- a/docs/ja/docs/tutorial/security/index.md +++ b/docs/ja/docs/tutorial/security/index.md @@ -1,4 +1,4 @@ -# セキュリティ入門 +# セキュリティ入門 { #security } セキュリティ、認証、認可を扱うには多くの方法があります。 @@ -10,11 +10,11 @@ しかし、その前に、いくつかの小さな概念を確認しましょう。 -## お急ぎですか? +## お急ぎですか? { #in-a-hurry } もし、これらの用語に興味がなく、ユーザー名とパスワードに基づく認証でセキュリティを**今すぐ**確保する必要がある場合は、次の章に進んでください。 -## OAuth2 +## OAuth2 { #oauth2 } OAuth2は、認証と認可を処理するためのいくつかの方法を定義した仕様です。 @@ -24,7 +24,7 @@ OAuth2は、認証と認可を処理するためのいくつかの方法を定 これが、「Facebook、Google、X (Twitter)、GitHubを使ってログイン」を使用したすべてのシステムの背後で使われている仕組みです。 -### OAuth 1 +### OAuth 1 { #oauth-1 } OAuth 1というものもありましたが、これはOAuth2とは全く異なり、通信をどのように暗号化するかという仕様が直接的に含まれており、より複雑なものとなっています。 @@ -38,7 +38,7 @@ OAuth2は、通信を暗号化する方法を指定せず、アプリケーシ /// -## OpenID Connect +## OpenID Connect { #openid-connect } OpenID Connectは、**OAuth2**をベースにした別の仕様です。 @@ -48,7 +48,7 @@ OpenID Connectは、**OAuth2**をベースにした別の仕様です。 しかし、FacebookのログインはOpenID Connectをサポートしていません。OAuth2を独自にアレンジしています。 -### OpenID (「OpenID Connect」ではない) +### OpenID (「OpenID Connect」ではない) { #openid-not-openid-connect } また、「OpenID」という仕様もありました。それは、**OpenID Connect**と同じことを解決しようとしたものですが、OAuth2に基づいているわけではありませんでした。 @@ -56,7 +56,7 @@ OpenID Connectは、**OAuth2**をベースにした別の仕様です。 現在ではあまり普及していませんし、使われてもいません。 -## OpenAPI +## OpenAPI { #openapi } OpenAPI(以前はSwaggerとして知られていました)は、APIを構築するためのオープンな仕様です(現在はLinux Foundationの一部になっています)。 @@ -97,7 +97,7 @@ Google、Facebook、X (Twitter)、GitHubなど、他の認証/認可プロバイ /// -## **FastAPI** ユーティリティ +## **FastAPI** ユーティリティ { #fastapi-utilities } FastAPIは `fastapi.security` モジュールの中で、これらのセキュリティスキームごとにいくつかのツールを提供し、これらのセキュリティメカニズムを簡単に使用できるようにします。 diff --git a/docs/ja/docs/tutorial/security/oauth2-jwt.md b/docs/ja/docs/tutorial/security/oauth2-jwt.md index 186936f1b..0d6be90a2 100644 --- a/docs/ja/docs/tutorial/security/oauth2-jwt.md +++ b/docs/ja/docs/tutorial/security/oauth2-jwt.md @@ -1,6 +1,6 @@ # パスワード(およびハッシュ化)によるOAuth2、JWTトークンによるBearer { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens } -これでセキュリティの流れが全てわかったので、JWTトークンと安全なパスワードのハッシュ化を使用して、実際にアプリケーションを安全にしてみましょう。 +これでセキュリティの流れが全てわかったので、JWTトークンと安全なパスワードのハッシュ化を使用して、実際にアプリケーションを安全にしてみましょう。 このコードは、アプリケーションで実際に使用したり、パスワードハッシュをデータベースに保存するといった用途に利用できます。 @@ -116,7 +116,11 @@ pwdlibはbcryptハッシュアルゴリズムもサポートしていますが さらに、ユーザーを認証して返す関数も作成します。 -{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +`authenticate_user` がデータベースに存在しないユーザー名で呼び出された場合でも、ダミーのハッシュを使って `verify_password` を実行します。 + +これにより、ユーザー名が有効かどうかに関わらずエンドポイントの応答時間がおおよそ同じになり、既存のユーザー名を列挙するために悪用されうる「タイミング攻撃」を防止できます。 /// note | 備考 @@ -152,7 +156,7 @@ JWTトークンの署名に使用するアルゴリズム`"HS256"`を指定し 新しいアクセストークンを生成するユーティリティ関数を作成します。 -{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} ## 依存関係の更新 { #update-the-dependencies } @@ -162,7 +166,7 @@ JWTトークンの署名に使用するアルゴリズム`"HS256"`を指定し トークンが無効な場合は、すぐにHTTPエラーを返します。 -{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} ## `/token` *path operation* の更新 { #update-the-token-path-operation } @@ -170,7 +174,7 @@ JWTトークンの署名に使用するアルゴリズム`"HS256"`を指定し 実際のJWTアクセストークンを作成し、それを返します。 -{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} ### JWTの「subject」`sub` についての技術的な詳細 { #technical-details-about-the-jwt-subject-sub } diff --git a/docs/ja/docs/tutorial/security/simple-oauth2.md b/docs/ja/docs/tutorial/security/simple-oauth2.md new file mode 100644 index 000000000..c371b0acf --- /dev/null +++ b/docs/ja/docs/tutorial/security/simple-oauth2.md @@ -0,0 +1,289 @@ +# パスワードとBearerによるシンプルなOAuth2 { #simple-oauth2-with-password-and-bearer } + +前章から発展させて、完全なセキュリティフローに必要な不足部分を追加していきます。 + +## `username` と `password` を取得する { #get-the-username-and-password } + +`username` と `password` を取得するために **FastAPI** のセキュリティユーティリティを使います。 + +OAuth2 では、「password flow」(ここで使用するフロー)を使う場合、クライアント/ユーザーはフォームデータとして `username` と `password` フィールドを送信する必要があります。 + +しかも、フィールド名はこの通りでなければなりません。つまり、`user-name` や `email` では動作しません。 + +ただし、フロントエンドで最終ユーザーにどう表示するかは自由です。 + +また、データベースのモデルでは任意の別名を使って構いません。 + +しかし、ログイン用の path operation では、仕様との互換性を保つ(たとえば組み込みのAPIドキュメントシステムを使えるようにする)ために、これらの名前を使う必要があります。 + +また、仕様では `username` と `password` はフォームデータとして送らなければならない(つまり、ここではJSONは使わない)ことも定められています。 + +### `scope` { #scope } + +仕様では、クライアントは追加のフォームフィールド「`scope`」を送ることができるとも書かれています。 + +フォームフィールド名は `scope`(単数形)ですが、実態はスペース区切りの「スコープ」文字列を並べた長い文字列です。 + +各「スコープ」は(スペースを含まない)単なる文字列です。 + +通常、特定のセキュリティ権限を宣言するために使われます。例えば: + +- `users:read` や `users:write` はよくある例です。 +- `instagram_basic` は Facebook / Instagram で使われます。 +- `https://www.googleapis.com/auth/drive` は Google で使われます。 + +/// info | 情報 + +OAuth2 における「スコープ」は、要求される特定の権限を表す単なる文字列です。 + +`:` のような他の文字を含んでいても、URL であっても構いません。 + +それらの詳細は実装依存です。 + +OAuth2 にとっては単なる文字列です。 + +/// + +## `username` と `password` を取得するコード { #code-to-get-the-username-and-password } + +では、これを処理するために **FastAPI** が提供するユーティリティを使いましょう。 + +### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform } + +まず、`OAuth2PasswordRequestForm` をインポートし、`/token` の path operation に `Depends` で依存関係として使います: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *} + +`OAuth2PasswordRequestForm` は次のフォームボディを宣言するクラス依存関係です: + +- `username` +- `password` +- スペース区切りの文字列で構成される、オプションの `scope` フィールド +- オプションの `grant_type` + +/// tip | 豆知識 + +OAuth2 の仕様では、固定値 `password` を持つフィールド `grant_type` が実際には必須ですが、`OAuth2PasswordRequestForm` はそれを強制しません。 + +強制したい場合は、`OAuth2PasswordRequestForm` の代わりに `OAuth2PasswordRequestFormStrict` を使ってください。 + +/// + +- オプションの `client_id`(この例では不要) +- オプションの `client_secret`(この例では不要) + +/// info | 情報 + +`OAuth2PasswordRequestForm` は、`OAuth2PasswordBearer` のように **FastAPI** にとって特別なクラスではありません。 + +`OAuth2PasswordBearer` は **FastAPI** にセキュリティスキームであることを認識させます。そのため OpenAPI にそのように追加されます。 + +一方、`OAuth2PasswordRequestForm` は、あなた自身でも書けるような単なるクラス依存関係であり、直接 `Form` パラメータを宣言することもできます。 + +ただし一般的なユースケースなので、簡単に使えるよう **FastAPI** が直接提供しています。 + +/// + +### フォームデータの利用 { #use-the-form-data } + +/// tip | 豆知識 + +依存関係クラス `OAuth2PasswordRequestForm` のインスタンスは、スペース区切りの長い文字列を持つ `scope` 属性は持ちません。代わりに、送られてきた各スコープの実際の文字列リストを格納する `scopes` 属性を持ちます。 + +この例では `scopes` は使いませんが、必要ならその機能が利用できます。 + +/// + +次に、フォームフィールドの `username` を使って(疑似の)データベースからユーザーデータを取得します。 + +そのユーザーが存在しない場合は、「Incorrect username or password」というエラーを返します。 + +エラーには `HTTPException` 例外を使います: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *} + +### パスワードのチェック { #check-the-password } + +この時点でデータベースからユーザーデータは取得できましたが、まだパスワードを確認していません。 + +まず、そのデータを Pydantic の `UserInDB` モデルに入れます。 + +プレーンテキストのパスワードを保存してはいけないので、(疑似の)パスワードハッシュ化システムを使います。 + +パスワードが一致しなければ、同じエラーを返します。 + +#### パスワードハッシュ化 { #password-hashing } + +「ハッシュ化」とは、ある内容(ここではパスワード)を、乱雑に見えるバイト列(単なる文字列)に変換することを指します。 + +まったく同じ内容(まったく同じパスワード)を渡すと、毎回まったく同じ乱雑な文字列が得られます。 + +しかし、その乱雑な文字列から元のパスワードに戻すことはできません。 + +##### なぜパスワードをハッシュ化するのか { #why-use-password-hashing } + +もしデータベースが盗まれても、盗んだ側が手にするのはユーザーのプレーンテキストのパスワードではなく、ハッシュだけです。 + +したがって、盗んだ側は同じパスワードを別のシステムで試すことができません(多くのユーザーがあらゆる場所で同じパスワードを使っているため、これは危険になり得ます)。 + +{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *} + +#### `**user_dict` について { #about-user-dict } + +`UserInDB(**user_dict)` は次を意味します: + +`user_dict` のキーと値を、そのままキーワード引数として渡します。つまり次と同等です: + +```Python +UserInDB( + username = user_dict["username"], + email = user_dict["email"], + full_name = user_dict["full_name"], + disabled = user_dict["disabled"], + hashed_password = user_dict["hashed_password"], +) +``` + +/// info | 情報 + +`**user_dict` のより完全な解説は、[**追加モデル**のドキュメント](../extra-models.md#about-user-in-dict){.internal-link target=_blank}を参照してください。 + +/// + +## トークンを返す { #return-the-token } + +`token` エンドポイントのレスポンスは JSON オブジェクトでなければなりません。 + +`token_type` を含める必要があります。ここでは「Bearer」トークンを使うので、トークンタイプは「`bearer`」です。 + +そして `access_token` を含め、その中にアクセストークンの文字列を入れます。 + +この単純な例では、完全に安全ではありませんが、トークンとして同じ `username` をそのまま返します。 + +/// tip | 豆知識 + +次の章では、パスワードハッシュ化と JWT トークンを使った本当に安全な実装を見ます。 + +しかし今は、必要な特定の詳細に集中しましょう。 + +/// + +{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *} + +/// tip | 豆知識 + +仕様に従うと、この例と同じく `access_token` と `token_type` を含む JSON を返す必要があります。 + +これはあなた自身のコードで実装する必要があり、これらのJSONキーを使っていることを確認してください。 + +仕様に準拠するために、あなた自身が正しく覚えて実装すべきことは、ほぼこれだけです。 + +それ以外は **FastAPI** が面倒を見てくれます。 + +/// + +## 依存関係の更新 { #update-the-dependencies } + +ここで依存関係を更新します。 + +アクティブなユーザーの場合にのみ `current_user` を取得したいとします。 + +そこで、`get_current_user` を依存関係として利用する追加の依存関係 `get_current_active_user` を作成します。 + +これら2つの依存関係は、ユーザーが存在しない、または非アクティブである場合に、HTTPエラーを返すだけです。 + +したがって、エンドポイントでは、ユーザーが存在し、正しく認証され、かつアクティブである場合にのみ、ユーザーを取得します: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *} + +/// info | 情報 + +ここで返している値が `Bearer` の追加ヘッダー `WWW-Authenticate` も仕様の一部です。 + +HTTP(エラー)ステータスコード 401「UNAUTHORIZED」は、`WWW-Authenticate` ヘッダーも返すことになっています。 + +ベアラートークン(今回のケース)の場合、そのヘッダーの値は `Bearer` であるべきです。 + +実際のところ、この追加ヘッダーを省略しても動作はします。 + +しかし、仕様に準拠するためにここでは付与しています。 + +また、(今または将来)それを想定して利用するツールがあるかもしれず、あなたやユーザーにとって有用になる可能性があります。 + +これが標準の利点です…。 + +/// + +## 動作確認 { #see-it-in-action } + +インタラクティブドキュメントを開きます: http://127.0.0.1:8000/docs。 + +### 認証 { #authenticate } + +「Authorize」ボタンをクリックします。 + +次の認証情報を使います: + +User: `johndoe` + +Password: `secret` + + + +システムで認証されると、次のように表示されます: + + + +### 自分のユーザーデータを取得 { #get-your-own-user-data } + +`GET` の path `/users/me` を使います。 + +次のようなユーザーデータが取得できます: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" +} +``` + + + +錠前アイコンをクリックしてログアウトし、同じ操作を再度試すと、次のような HTTP 401 エラーになります: + +```JSON +{ + "detail": "Not authenticated" +} +``` + +### 非アクティブユーザー { #inactive-user } + +今度は非アクティブなユーザーで試してみます。次で認証してください: + +User: `alice` + +Password: `secret2` + +そして `GET` の path `/users/me` を使います。 + +次のような「Inactive user」エラーになります: + +```JSON +{ + "detail": "Inactive user" +} +``` + +## まとめ { #recap } + +これで、API のために `username` と `password` に基づく完全なセキュリティシステムを実装するための道具が揃いました。 + +これらの道具を使えば、任意のデータベース、任意のユーザー/データモデルと互換性のあるセキュリティシステムを構築できます。 + +ただし、実際にはまだ「安全」ではありません。 + +次の章では、安全なパスワードハッシュライブラリと JWT トークンの使い方を見ていきます。 diff --git a/docs/ja/docs/tutorial/sql-databases.md b/docs/ja/docs/tutorial/sql-databases.md new file mode 100644 index 000000000..930e433de --- /dev/null +++ b/docs/ja/docs/tutorial/sql-databases.md @@ -0,0 +1,357 @@ +# SQL(リレーショナル)データベース { #sql-relational-databases } + +FastAPI は SQL(リレーショナル)データベースの使用を必須にはしません。必要であれば、任意のデータベースを使用できます。 + +ここでは SQLModel を使った例を見ていきます。 + +SQLModel は SQLAlchemy と Pydantic の上に構築されています。FastAPI と同じ作者により、SQL データベースを使う必要がある FastAPI アプリに最適になるように作られています。 + +/// tip | 豆知識 + +他の任意の SQL あるいは NoSQL のデータベースライブラリ(場合によっては "ORMs" と呼ばれます)を使うこともできます。FastAPI は何も強制しません。😎 + +/// + +SQLModel は SQLAlchemy をベースにしているため、SQLAlchemy がサポートする任意のデータベース(SQLModel からもサポートされます)を簡単に使えます。例えば: + +* PostgreSQL +* MySQL +* SQLite +* Oracle +* Microsoft SQL Server など + +この例では、単一ファイルで動作し、Python に統合サポートがあるため、SQLite を使います。つまり、この例をそのままコピーして実行できます。 + +本番アプリでは、PostgreSQL のようなデータベースサーバーを使いたくなるかもしれません。 + +/// tip | 豆知識 + +フロントエンドやその他のツールを含む、FastAPI と PostgreSQL の公式プロジェクトジェネレーターがあります: https://github.com/fastapi/full-stack-fastapi-template + +/// + +これはとてもシンプルで短いチュートリアルです。データベースや SQL、より高度な機能について学びたい場合は、SQLModel のドキュメントをご覧ください。 + +## `SQLModel` のインストール { #install-sqlmodel } + +まずは [仮想環境](../virtual-environments.md){.internal-link target=_blank} を作成・有効化し、`sqlmodel` をインストールします: + +
+ +```console +$ pip install sqlmodel +---> 100% +``` + +
+ +## 単一モデルでアプリ作成 { #create-the-app-with-a-single-model } + +まずは最も簡単な、単一の SQLModel モデルだけを使うバージョンを作ります。 + +後で、下記のとおり複数モデルにしてセキュリティと汎用性を高めます。🤓 + +### モデルの作成 { #create-models } + +`SQLModel` をインポートしてデータベースモデルを作成します: + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} + +`Hero` クラスは Pydantic モデルによく似ています(実際には内部的に Pydantic モデルでもあります)。 + +いくつかの違いがあります: + +* `table=True` は SQLModel に対して「これはテーブルモデルであり、SQL データベースのテーブルを表す。単なるデータモデル(通常の Pydantic クラス)ではない」と伝えます。 + +* `Field(primary_key=True)` は `id` が SQL データベースのプライマリキーであることを SQLModel に伝えます(SQL のプライマリキーについては SQLModel ドキュメントを参照してください)。 + + 注: プライマリキーのフィールドには `int | None` を使っています。これは Python コード内で `id=None` のように「`id` なしでオブジェクトを作成」し、保存時にデータベースが生成することを想定するためです。SQLModel はデータベースが `id` を提供することを理解し、スキーマでは「NULL 不可の `INTEGER` 列」を定義します。詳細は SQLModel のプライマリキーに関するドキュメント を参照してください。 + +* `Field(index=True)` は、この列に対して SQL のインデックスを作成するよう SQLModel に指示します。これにより、この列でフィルタしてデータを読む場合に検索が高速になります。 + + `str` と宣言されたものは、SQL の `TEXT`(データベースによっては `VARCHAR`)型の列になることを SQLModel は理解します。 + +### Engine の作成 { #create-an-engine } + +SQLModel の `engine`(内部的には SQLAlchemy の `engine`)は、データベースへの接続を保持します。 + +同じデータベースに接続するために、コード全体で 1 つの `engine` オブジェクトを共有します。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} + +`check_same_thread=False` を使うと、FastAPI が異なるスレッドで同じ SQLite データベースを使えるようになります。これは、依存関係などにより 1 つのリクエストが複数スレッドを使う可能性があるため、必要です。 + +心配はいりません。このコードの構成では、後で「1 リクエストにつき 1 つの SQLModel セッション」を確実に使うようにします。実際、`check_same_thread` はそれを実現しようとしています。 + +### テーブルの作成 { #create-the-tables } + +`SQLModel.metadata.create_all(engine)` を使って、すべてのテーブルモデルのテーブルを作成する関数を追加します。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} + +### Session 依存関係の作成 { #create-a-session-dependency } + +`Session` は、メモリ上でオブジェクトを保持して変更を追跡し、`engine` を使ってデータベースと通信します。 + +各リクエストごとに新しい `Session` を提供する、`yield` を使った FastAPI の依存関係を作成します。これにより、1 リクエストにつき 1 つのセッションを使うことが保証されます。🤓 + +続いて、この依存関係を使うコードを簡潔にするために、`Annotated` による依存関係 `SessionDep` を作成します。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} + +### 起動時にテーブルを作成 { #create-database-tables-on-startup } + +アプリケーションの起動時にデータベースのテーブルを作成します。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} + +ここでは、アプリケーションのスタートアップイベントでテーブルを作成しています。 + +本番では、アプリを起動する前にマイグレーションスクリプトを実行するのが一般的でしょう。🤓 + +/// tip | 豆知識 + +SQLModel は Alembic をラップしたマイグレーションユーティリティを提供予定ですが、現時点では Alembic を直接使えます。 + +/// + +### Hero の作成 { #create-a-hero } + +各 SQLModel モデルは Pydantic モデルでもあるため、Pydantic モデルと同じように型アノテーションで使えます。 + +例えば、`Hero` 型のパラメータを宣言すると、JSON ボディから読み込まれます。 + +同様に、関数の戻り値の型として宣言すると、そのデータ形状が自動 API ドキュメントの UI に表示されます。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} + +ここでは `SessionDep` 依存関係(`Session`)を使って、新しい `Hero` を `Session` インスタンスに追加し、データベースにコミットし、`hero` のデータをリフレッシュしてから返します。 + +### Hero の取得 { #read-heroes } + +`select()` を使ってデータベースから `Hero` を取得できます。結果のページネーションのために `limit` と `offset` を含められます。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} + +### 単一の Hero を取得 { #read-one-hero } + +単一の `Hero` を取得できます。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} + +### Hero の削除 { #delete-a-hero } + +`Hero` を削除することもできます。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} + +### アプリの起動 { #run-the-app } + +アプリを起動します: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +その後 `/docs` の UI にアクセスすると、FastAPI がこれらのモデルを使って API をドキュメント化し、同時にデータのシリアライズとバリデーションにも使っていることがわかります。 + +
+ +
+ +## 複数モデルでアプリを更新 { #update-the-app-with-multiple-models } + +ここで、少しリファクタリングしてセキュリティと汎用性を高めましょう。 + +前のアプリでは、UI 上でクライアントが作成する `Hero` の `id` を自分で決められてしまいます。😱 + +それは許可すべきではありません。すでに DB で割り当て済みの `id` を上書きされる可能性があります。`id` の決定はクライアントではなく、バックエンドまたはデータベースが行うべきです。 + +さらに、`secret_name` を作っていますが、現状ではそれをどこでも返してしまっています。これではあまり「シークレット」ではありません... 😅 + +これらを、いくつかの追加モデルで修正します。ここで SQLModel の真価が発揮されます。✨ + +### 複数モデルの作成 { #create-multiple-models } + +SQLModel では、`table=True` のあるモデルクラスがテーブルモデルです。 + +`table=True` のないモデルクラスはデータモデルで、実体は(小さな機能がいくつか追加された)Pydantic モデルです。🤓 + +SQLModel では継承を使って、あらゆるケースでフィールドの重複を避けられます。 + +#### `HeroBase` - ベースクラス { #herobase-the-base-class } + +まず、すべてのモデルで共有されるフィールドを持つ `HeroBase` モデルを作ります: + +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} + +#### `Hero` - テーブルモデル { #hero-the-table-model } + +次に、実際のテーブルモデルである `Hero` を作ります。他のモデルには常に含まれない追加フィールドを持ちます: + +* `id` +* `secret_name` + +`Hero` は `HeroBase` を継承しているため、`HeroBase` で宣言されたフィールドも持ちます。つまり、`Hero` の全フィールドは次のとおりです: + +* `id` +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} + +#### `HeroPublic` - 公開用データモデル { #heropublic-the-public-data-model } + +次に、API のクライアントに返す `HeroPublic` モデルを作ります。 + +これは `HeroBase` と同じフィールドを持つため、`secret_name` は含みません。 + +これでヒーローの正体は守られます!🥷 + +また、`id: int` を再宣言します。これにより、API クライアントとの間で「常に `id` が存在し、`int` である(`None` にはならない)」という契約を結びます。 + +/// tip | 豆知識 + +戻り値のモデルで、値が常に存在し常に `int`(`None` ではない)であることを保証すると、API クライアント側のコードははるかにシンプルに書けます。 + +加えて、自動生成クライアントのインターフェースも簡潔になり、あなたの API とやり取りする開発者体験が向上します。😎 + +/// + +`HeroPublic` のフィールドは `HeroBase` と同じで、`id` は `int`(`None` ではない)として宣言されます: + +* `id` +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} + +#### `HeroCreate` - 作成用データモデル { #herocreate-the-data-model-to-create-a-hero } + +次に、クライアントからのデータをバリデートする `HeroCreate` モデルを作ります。 + +これは `HeroBase` と同じフィールドに加え、`secret_name` も持ちます。 + +これで、クライアントが新しいヒーローを作成する際に `secret_name` を送信し、データベースに保存されますが、そのシークレット名は API ではクライアントに返されません。 + +/// tip | 豆知識 + +これはパスワードを扱う際の方法と同じです。受け取りますが、API では返しません。 + +また、保存前にパスワードの値はハッシュ化し、平文のまま保存しないでください。 + +/// + +`HeroCreate` のフィールド: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} + +#### `HeroUpdate` - 更新用データモデル { #heroupdate-the-data-model-to-update-a-hero } + +前のバージョンのアプリにはヒーローを更新する方法がありませんでしたが、複数モデルを使えば可能です。🎉 + +`HeroUpdate` データモデルは少し特殊で、新しいヒーローを作成するのに必要なフィールドと同じフィールドをすべて持ちますが、すべてのフィールドがオプショナル(デフォルト値を持つ)です。これにより、更新時には変更したいフィールドだけを送れます。 + +すべてのフィールドの型が実質的に変わる(`None` を含み、デフォルト値が `None` になる)ため、フィールドは再宣言する必要があります。 + +すべてのフィールドを再宣言するので、厳密には `HeroBase` を継承する必要はありません。一貫性のためにここでは継承していますが、必須ではありません。好みの問題です。🤷 + +`HeroUpdate` のフィールド: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} + +### `HeroCreate` で作成し `HeroPublic` を返す { #create-with-herocreate-and-return-a-heropublic } + +複数モデルが用意できたので、それらを使うようにアプリの部分を更新します。 + +リクエストでは `HeroCreate` データモデルを受け取り、そこから `Hero` テーブルモデルを作成します。 + +この新しいテーブルモデル `Hero` は、クライアントから送られたフィールドを持ち、データベースによって生成された `id` も持ちます。 + +関数からはこのテーブルモデル `Hero` をそのまま返します。しかし `response_model` に `HeroPublic` データモデルを指定しているため、FastAPI が `HeroPublic` を使ってデータをバリデート・シリアライズします。 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} + +/// tip | 豆知識 + +今回は返却値の型アノテーション `-> HeroPublic` の代わりに `response_model=HeroPublic` を使います。返している値は実際には `HeroPublic` ではないためです。 + +もし `-> HeroPublic` と宣言すると、エディタや Linter は(正しく)「`HeroPublic` ではなく `Hero` を返している」と警告します。 + +`response_model` に指定することで、型アノテーションやエディタ等の補助を崩さずに、FastAPI にシリアライズの仕事を任せられます。 + +/// + +### `HeroPublic` で Hero を取得 { #read-heroes-with-heropublic } + +前と同様に `Hero` を取得できます。再び `response_model=list[HeroPublic]` を使って、データが正しくバリデート・シリアライズされることを保証します。 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} + +### `HeroPublic` で単一の Hero を取得 { #read-one-hero-with-heropublic } + +単一のヒーローを取得します: + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} + +### `HeroUpdate` で Hero を更新 { #update-a-hero-with-heroupdate } + +ヒーローを更新できます。ここでは HTTP の `PATCH` を使います。 + +コードでは、クライアントが送ったデータのみ(デフォルト値として入ってくる値は除外)を持つ `dict` を取得します。これには `exclude_unset=True` を使います。これが主なコツです。🪄 + +その後、`hero_db.sqlmodel_update(hero_data)` を使って、`hero_db` を `hero_data` の内容で更新します。 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} + +### 再度 Hero を削除 { #delete-a-hero-again } + +ヒーローの削除はほとんど変わりません。 + +ここはリファクタリング欲求を満たさないままにしておきます。😅 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} + +### アプリの再起動 { #run-the-app-again } + +アプリを再度起動します: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +`/docs` の API UI に行くと、内容が更新されており、ヒーロー作成時にクライアントから `id` を受け取ることは期待されていない、などが確認できます。 + +
+ +
+ +## まとめ { #recap } + +SQLModel を使って SQL データベースとやり取りし、データモデルとテーブルモデルでコードを簡潔にできます。 + +さらに多くを学ぶには SQLModel のドキュメントをご覧ください。FastAPI と SQLModel を使うチュートリアル もあります。🚀 diff --git a/docs/ja/docs/tutorial/static-files.md b/docs/ja/docs/tutorial/static-files.md index c79789494..52e28d6b0 100644 --- a/docs/ja/docs/tutorial/static-files.md +++ b/docs/ja/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ * `StaticFiles` をインポート。 * `StaticFiles()` インスタンスを特定のパスに「マウント」。 -{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} /// note | 技術詳細 diff --git a/docs/ja/docs/tutorial/testing.md b/docs/ja/docs/tutorial/testing.md index 0ec6250f3..b09f1af72 100644 --- a/docs/ja/docs/tutorial/testing.md +++ b/docs/ja/docs/tutorial/testing.md @@ -30,7 +30,7 @@ $ pip install httpx チェックしたい Python の標準的な式と共に、シンプルに `assert` 文を記述します (これも `pytest` の標準です)。 -{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} /// tip | 豆知識 @@ -76,7 +76,7 @@ FastAPIアプリケーションへのリクエストの送信とは別に、テ ファイル `main.py` に **FastAPI** アプリがあります: -{* ../../docs_src/app_testing/app_a_py39/main.py *} +{* ../../docs_src/app_testing/app_a_py310/main.py *} ### テストファイル { #testing-file } @@ -92,7 +92,7 @@ FastAPIアプリケーションへのリクエストの送信とは別に、テ このファイルは同じパッケージ内にあるため、相対インポートを使って `main` モジュール (`main.py`) からオブジェクト `app` をインポートできます: -{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} ...そして、これまでと同じようにテストコードを書けます。 diff --git a/docs/ja/docs/virtual-environments.md b/docs/ja/docs/virtual-environments.md index 672323063..94af6ddae 100644 --- a/docs/ja/docs/virtual-environments.md +++ b/docs/ja/docs/virtual-environments.md @@ -50,7 +50,7 @@ $ cd awesome-project ## 仮想環境の作成 { #create-a-virtual-environment } -Pythonプロジェクトでの**初めての**作業を開始する際には、**プロジェクト内**に仮想環境を作成してください。 +Pythonプロジェクトでの**初めての**作業を開始する際には、**プロジェクト内**に仮想環境を作成してください。 /// tip | 豆知識 @@ -163,7 +163,7 @@ $ source .venv/Scripts/activate **新しいパッケージ**を仮想環境にインストールするたびに、環境をもう一度**有効化**してください。 -こうすることで、そのパッケージがインストールした**ターミナル(CLI)プログラム**を使用する場合に、仮想環境内のものが確実に使われ、グローバル環境にインストールされている別のもの(おそらく必要なものとは異なるバージョン)を誤って使用することを防ぎます。 +こうすることで、そのパッケージがインストールした**ターミナル(CLI)プログラム**を使用する場合に、仮想環境内のものが確実に使われ、グローバル環境にインストールされている別のもの(おそらく必要なものとは異なるバージョン)を誤って使用することを防ぎます。 /// From 5420847d9fbdb442aa469b48d40bd61436bc5acc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Feb 2026 15:24:56 +0000 Subject: [PATCH 129/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 152f684e7..2f2d599dc 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for ja (update-all). PR [#14916](https://github.com/fastapi/fastapi/pull/14916) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for pt (update-all). PR [#14912](https://github.com/fastapi/fastapi/pull/14912) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for es (update-all and add-missing). PR [#14911](https://github.com/fastapi/fastapi/pull/14911) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for zh (update-all). PR [#14917](https://github.com/fastapi/fastapi/pull/14917) by [@YuriiMotov](https://github.com/YuriiMotov). From dca874c58cc36b1d98570d55df1127523cb2d1a8 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Sat, 14 Feb 2026 08:57:30 +0100 Subject: [PATCH 130/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20de=20(update-all)=20=20(#14910)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all * 🎨 Auto format * Apply suggestions from code review --------- Co-authored-by: github-actions[bot] --- docs/de/docs/advanced/additional-responses.md | 4 +- .../de/docs/advanced/advanced-dependencies.md | 8 +- .../de/docs/advanced/advanced-python-types.md | 61 ++++++ docs/de/docs/advanced/async-tests.md | 8 +- docs/de/docs/advanced/behind-a-proxy.md | 12 +- docs/de/docs/advanced/custom-response.md | 32 +-- docs/de/docs/advanced/dataclasses.md | 4 +- docs/de/docs/advanced/events.md | 12 +- docs/de/docs/advanced/generate-clients.md | 12 +- docs/de/docs/advanced/middleware.md | 8 +- docs/de/docs/advanced/openapi-webhooks.md | 6 +- .../path-operation-advanced-configuration.md | 20 +- .../advanced/response-change-status-code.md | 2 +- docs/de/docs/advanced/response-cookies.md | 4 +- docs/de/docs/advanced/response-directly.md | 2 +- docs/de/docs/advanced/response-headers.md | 4 +- .../docs/advanced/security/http-basic-auth.md | 6 +- docs/de/docs/advanced/settings.md | 26 +-- docs/de/docs/advanced/sub-applications.md | 6 +- docs/de/docs/advanced/templates.md | 2 +- docs/de/docs/advanced/testing-events.md | 4 +- docs/de/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/de/docs/advanced/websockets.md | 8 +- docs/de/docs/advanced/wsgi.md | 2 +- docs/de/docs/alternatives.md | 14 +- docs/de/docs/benchmarks.md | 2 +- docs/de/docs/deployment/cloud.md | 2 +- docs/de/docs/deployment/docker.md | 12 +- docs/de/docs/deployment/https.md | 2 +- docs/de/docs/deployment/index.md | 2 +- docs/de/docs/deployment/server-workers.md | 2 +- docs/de/docs/features.md | 12 +- docs/de/docs/history-design-future.md | 2 +- .../authentication-error-status-code.md | 2 +- docs/de/docs/how-to/conditional-openapi.md | 2 +- docs/de/docs/how-to/configure-swagger-ui.md | 6 +- docs/de/docs/how-to/custom-docs-ui-assets.md | 14 +- docs/de/docs/how-to/extending-openapi.md | 10 +- docs/de/docs/how-to/graphql.md | 2 +- docs/de/docs/index.md | 12 +- docs/de/docs/project-generation.md | 2 +- docs/de/docs/python-types.md | 192 ++++-------------- docs/de/docs/translation-banner.md | 11 + docs/de/docs/tutorial/background-tasks.md | 6 +- docs/de/docs/tutorial/bigger-applications.md | 26 +-- docs/de/docs/tutorial/body-fields.md | 2 +- docs/de/docs/tutorial/body-multiple-params.md | 6 - docs/de/docs/tutorial/body-nested-models.md | 4 +- docs/de/docs/tutorial/body.md | 2 +- docs/de/docs/tutorial/cookie-param-models.md | 8 +- docs/de/docs/tutorial/cors.md | 2 +- docs/de/docs/tutorial/debugging.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +-- ...pendencies-in-path-operation-decorators.md | 12 +- .../dependencies/dependencies-with-yield.md | 24 +-- .../dependencies/global-dependencies.md | 3 +- docs/de/docs/tutorial/dependencies/index.md | 2 +- .../tutorial/dependencies/sub-dependencies.md | 6 +- docs/de/docs/tutorial/extra-models.md | 8 +- docs/de/docs/tutorial/first-steps.md | 16 +- docs/de/docs/tutorial/handling-errors.md | 18 +- docs/de/docs/tutorial/metadata.md | 12 +- docs/de/docs/tutorial/middleware.md | 4 +- .../tutorial/path-operation-configuration.md | 8 +- .../path-params-numeric-validations.md | 14 +- docs/de/docs/tutorial/path-params.md | 29 ++- .../tutorial/query-params-str-validations.md | 46 +---- docs/de/docs/tutorial/query-params.md | 6 +- docs/de/docs/tutorial/request-files.md | 12 +- docs/de/docs/tutorial/request-form-models.md | 4 +- .../docs/tutorial/request-forms-and-files.md | 4 +- docs/de/docs/tutorial/request-forms.md | 6 +- docs/de/docs/tutorial/response-model.md | 6 +- docs/de/docs/tutorial/response-status-code.md | 8 +- docs/de/docs/tutorial/schema-extra-example.md | 2 +- docs/de/docs/tutorial/security/first-steps.md | 8 +- .../tutorial/security/get-current-user.md | 2 +- docs/de/docs/tutorial/security/index.md | 2 +- docs/de/docs/tutorial/security/oauth2-jwt.md | 12 +- .../docs/tutorial/security/simple-oauth2.md | 2 +- docs/de/docs/tutorial/static-files.md | 2 +- docs/de/docs/tutorial/testing.md | 6 +- docs/de/docs/virtual-environments.md | 12 +- 84 files changed, 431 insertions(+), 507 deletions(-) create mode 100644 docs/de/docs/advanced/advanced-python-types.md create mode 100644 docs/de/docs/translation-banner.md diff --git a/docs/de/docs/advanced/additional-responses.md b/docs/de/docs/advanced/additional-responses.md index 0f9b12251..531702bf1 100644 --- a/docs/de/docs/advanced/additional-responses.md +++ b/docs/de/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ Jedes dieser Response-`dict`s kann einen Schlüssel `model` haben, welcher ein P Um beispielsweise eine weitere Response mit dem Statuscode `404` und einem Pydantic-Modell `Message` zu deklarieren, können Sie schreiben: -{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} /// note | Hinweis @@ -203,7 +203,7 @@ Sie können beispielsweise eine Response mit dem Statuscode `404` deklarieren, d Und eine Response mit dem Statuscode `200`, die Ihr `response_model` verwendet, aber ein benutzerdefiniertes Beispiel (`example`) enthält: -{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} Es wird alles kombiniert und in Ihre OpenAPI eingebunden und in der API-Dokumentation angezeigt: diff --git a/docs/de/docs/advanced/advanced-dependencies.md b/docs/de/docs/advanced/advanced-dependencies.md index e60df2883..50b4e20ac 100644 --- a/docs/de/docs/advanced/advanced-dependencies.md +++ b/docs/de/docs/advanced/advanced-dependencies.md @@ -18,7 +18,7 @@ Nicht die Klasse selbst (die bereits aufrufbar ist), sondern eine Instanz dieser Dazu deklarieren wir eine Methode `__call__`: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} In diesem Fall ist dieses `__call__` das, was **FastAPI** verwendet, um nach zusätzlichen Parametern und Unterabhängigkeiten zu suchen, und das ist es auch, was später aufgerufen wird, um einen Wert an den Parameter in Ihrer *Pfadoperation-Funktion* zu übergeben. @@ -26,7 +26,7 @@ In diesem Fall ist dieses `__call__` das, was **FastAPI** verwendet, um nach zus Und jetzt können wir `__init__` verwenden, um die Parameter der Instanz zu deklarieren, die wir zum „Parametrisieren“ der Abhängigkeit verwenden können: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} In diesem Fall wird **FastAPI** `__init__` nie berühren oder sich darum kümmern, wir werden es direkt in unserem Code verwenden. @@ -34,7 +34,7 @@ In diesem Fall wird **FastAPI** `__init__` nie berühren oder sich darum kümmer Wir könnten eine Instanz dieser Klasse erstellen mit: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} Und auf diese Weise können wir unsere Abhängigkeit „parametrisieren“, die jetzt `"bar"` enthält, als das Attribut `checker.fixed_content`. @@ -50,7 +50,7 @@ checker(q="somequery") ... und übergibt, was immer das als Wert dieser Abhängigkeit in unserer *Pfadoperation-Funktion* zurückgibt, als den Parameter `fixed_content_included`: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} /// tip | Tipp diff --git a/docs/de/docs/advanced/advanced-python-types.md b/docs/de/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..aed859cef --- /dev/null +++ b/docs/de/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# Fortgeschrittene Python-Typen { #advanced-python-types } + +Hier sind einige zusätzliche Ideen, die beim Arbeiten mit Python-Typen nützlich sein könnten. + +## `Union` oder `Optional` verwenden { #using-union-or-optional } + +Wenn Ihr Code aus irgendeinem Grund nicht `|` verwenden kann, z. B. wenn es nicht in einer Typannotation ist, sondern in etwas wie `response_model=`, können Sie anstelle des senkrechten Strichs (`|`) `Union` aus `typing` verwenden. + +Zum Beispiel könnten Sie deklarieren, dass etwas ein `str` oder `None` sein könnte: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +`typing` hat außerdem eine Abkürzung, um zu deklarieren, dass etwas `None` sein könnte, mit `Optional`. + +Hier ist ein Tipp aus meiner sehr **subjektiven** Perspektive: + +* 🚨 Vermeiden Sie die Verwendung von `Optional[SomeType]` +* Verwenden Sie stattdessen ✨ **`Union[SomeType, None]`** ✨. + +Beides ist äquivalent und unter der Haube identisch, aber ich würde `Union` statt `Optional` empfehlen, weil das Wort „**optional**“ implizieren könnte, dass der Wert optional ist; tatsächlich bedeutet es jedoch „es kann `None` sein“, selbst wenn es nicht optional ist und weiterhin erforderlich bleibt. + +Ich finde, `Union[SomeType, None]` ist expliziter in dem, was es bedeutet. + +Es geht nur um Wörter und Namen. Aber diese Wörter können beeinflussen, wie Sie und Ihr Team über den Code denken. + +Als Beispiel nehmen wir diese Funktion: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +Der Parameter `name` ist als `Optional[str]` definiert, aber er ist **nicht optional**, Sie können die Funktion nicht ohne den Parameter aufrufen: + +```Python +say_hi() # Oh nein, das löst einen Fehler aus! 😱 +``` + +Der Parameter `name` ist **weiterhin erforderlich** (nicht *optional*), weil er keinen Defaultwert hat. Dennoch akzeptiert `name` den Wert `None`: + +```Python +say_hi(name=None) # Das funktioniert, None ist gültig 🎉 +``` + +Die gute Nachricht ist: In den meisten Fällen können Sie einfach `|` verwenden, um Unions von Typen zu definieren: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +Sie müssen sich also normalerweise keine Gedanken über Namen wie `Optional` und `Union` machen. 😎 diff --git a/docs/de/docs/advanced/async-tests.md b/docs/de/docs/advanced/async-tests.md index 7206f136f..7702cc1c5 100644 --- a/docs/de/docs/advanced/async-tests.md +++ b/docs/de/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ Betrachten wir als einfaches Beispiel eine Dateistruktur ähnlich der in [Größ Die Datei `main.py` hätte als Inhalt: -{* ../../docs_src/async_tests/app_a_py39/main.py *} +{* ../../docs_src/async_tests/app_a_py310/main.py *} Die Datei `test_main.py` hätte die Tests für `main.py`, das könnte jetzt so aussehen: -{* ../../docs_src/async_tests/app_a_py39/test_main.py *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} ## Es ausführen { #run-it } @@ -56,7 +56,7 @@ $ pytest Der Marker `@pytest.mark.anyio` teilt pytest mit, dass diese Testfunktion asynchron aufgerufen werden soll: -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} /// tip | Tipp @@ -66,7 +66,7 @@ Beachten Sie, dass die Testfunktion jetzt `async def` ist und nicht nur `def` wi Dann können wir einen `AsyncClient` mit der App erstellen und mit `await` asynchrone Requests an ihn senden. -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} Das ist das Äquivalent zu: diff --git a/docs/de/docs/advanced/behind-a-proxy.md b/docs/de/docs/advanced/behind-a-proxy.md index 1c7459050..cbcdc59d4 100644 --- a/docs/de/docs/advanced/behind-a-proxy.md +++ b/docs/de/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" Angenommen, Sie definieren eine *Pfadoperation* `/items/`: -{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} Wenn der Client versucht, zu `/items` zu gehen, würde er standardmäßig zu `/items/` umgeleitet. @@ -115,7 +115,7 @@ In diesem Fall würde der ursprüngliche Pfad `/app` tatsächlich unter `/api/v1 Auch wenn Ihr gesamter Code unter der Annahme geschrieben ist, dass es nur `/app` gibt. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} Und der Proxy würde das **Pfadpräfix** on-the-fly **„entfernen“**, bevor er den Request an den Anwendungsserver (wahrscheinlich Uvicorn via FastAPI CLI) übermittelt, dafür sorgend, dass Ihre Anwendung davon überzeugt ist, dass sie unter `/app` bereitgestellt wird, sodass Sie nicht Ihren gesamten Code dahingehend aktualisieren müssen, das Präfix `/api/v1` zu verwenden. @@ -193,7 +193,7 @@ Sie können den aktuellen `root_path` abrufen, der von Ihrer Anwendung für jede Hier fügen wir ihn, nur zu Demonstrationszwecken, in die Nachricht ein. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} Wenn Sie Uvicorn dann starten mit: @@ -220,7 +220,7 @@ wäre die Requests und Responses zu deklarieren. @@ -64,7 +64,7 @@ In diesem Fall können Sie einfach die Standard-`dataclasses` durch `pydantic.da 6. Hier geben wir ein Dictionary zurück, das `items` enthält, welches eine Liste von Datenklassen ist. - FastAPI ist weiterhin in der Lage, die Daten nach JSON zu serialisieren. + FastAPI ist weiterhin in der Lage, die Daten nach JSON zu Serialisieren. 7. Hier verwendet das `response_model` als Typannotation eine Liste von `Author`-Datenklassen. diff --git a/docs/de/docs/advanced/events.md b/docs/de/docs/advanced/events.md index 7e1191b55..17d14d71c 100644 --- a/docs/de/docs/advanced/events.md +++ b/docs/de/docs/advanced/events.md @@ -30,7 +30,7 @@ Beginnen wir mit einem Beispiel und sehen es uns dann im Detail an. Wir erstellen eine asynchrone Funktion `lifespan()` mit `yield` wie folgt: -{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} Hier simulieren wir den langsamen *Startup*, das Laden des Modells, indem wir die (Fake-)Modellfunktion vor dem `yield` in das Dictionary mit Modellen für maschinelles Lernen einfügen. Dieser Code wird ausgeführt, **bevor** die Anwendung **beginnt, Requests entgegenzunehmen**, während des *Startups*. @@ -48,7 +48,7 @@ Möglicherweise müssen Sie eine neue Version starten, oder Sie haben es einfach Das Erste, was auffällt, ist, dass wir eine asynchrone Funktion mit `yield` definieren. Das ist sehr ähnlich zu Abhängigkeiten mit `yield`. -{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} Der erste Teil der Funktion, vor dem `yield`, wird ausgeführt **bevor** die Anwendung startet. @@ -60,7 +60,7 @@ Wie Sie sehen, ist die Funktion mit einem `@asynccontextmanager` versehen. Dadurch wird die Funktion in einen sogenannten „**asynchronen Kontextmanager**“ umgewandelt. -{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} Ein **Kontextmanager** in Python ist etwas, das Sie in einer `with`-Anweisung verwenden können, zum Beispiel kann `open()` als Kontextmanager verwendet werden: @@ -82,7 +82,7 @@ In unserem obigen Codebeispiel verwenden wir ihn nicht direkt, sondern übergebe Der Parameter `lifespan` der `FastAPI`-App benötigt einen **asynchronen Kontextmanager**, wir können ihm also unseren neuen asynchronen Kontextmanager `lifespan` übergeben. -{* ../../docs_src/events/tutorial003_py39.py hl[22] *} +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} ## Alternative Events (deprecatet) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ Diese Funktionen können mit `async def` oder normalem `def` deklariert werden. Um eine Funktion hinzuzufügen, die vor dem Start der Anwendung ausgeführt werden soll, deklarieren Sie diese mit dem Event `startup`: -{* ../../docs_src/events/tutorial001_py39.py hl[8] *} +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} In diesem Fall initialisiert die Eventhandler-Funktion `startup` die „Datenbank“ der Items (nur ein `dict`) mit einigen Werten. @@ -116,7 +116,7 @@ Und Ihre Anwendung empfängt erst dann Requests, wenn alle `startup`-Eventhandle Um eine Funktion hinzuzufügen, die beim Shutdown der Anwendung ausgeführt werden soll, deklarieren Sie sie mit dem Event `shutdown`: -{* ../../docs_src/events/tutorial002_py39.py hl[6] *} +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} Hier schreibt die `shutdown`-Eventhandler-Funktion eine Textzeile `"Application shutdown"` in eine Datei `log.txt`. diff --git a/docs/de/docs/advanced/generate-clients.md b/docs/de/docs/advanced/generate-clients.md index 659343f5b..fcb551002 100644 --- a/docs/de/docs/advanced/generate-clients.md +++ b/docs/de/docs/advanced/generate-clients.md @@ -2,7 +2,7 @@ Da **FastAPI** auf der **OpenAPI**-Spezifikation basiert, können dessen APIs in einem standardisierten Format beschrieben werden, das viele Tools verstehen. -Dies vereinfacht es, aktuelle **Dokumentation** und Client-Bibliotheken (**SDKs**) in verschiedenen Sprachen zu generieren sowie **Test-** oder **Automatisierungs-Workflows**, die mit Ihrem Code synchron bleiben. +Dies vereinfacht es, aktuelle **Dokumentation** und Client-Bibliotheken (**SDKs**) in verschiedenen Sprachen zu generieren sowie **Test-** oder **Automatisierungs-Workflows**, die mit Ihrem Code synchron bleiben. In diesem Leitfaden erfahren Sie, wie Sie ein **TypeScript-SDK** für Ihr FastAPI-Backend generieren. @@ -40,7 +40,7 @@ Einige dieser Lösungen sind möglicherweise auch Open Source oder bieten kosten Beginnen wir mit einer einfachen FastAPI-Anwendung: -{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} Beachten Sie, dass die *Pfadoperationen* die Modelle definieren, die sie für die Request- und Response-Payload verwenden, indem sie die Modelle `Item` und `ResponseMessage` verwenden. @@ -98,7 +98,7 @@ In vielen Fällen wird Ihre FastAPI-App größer sein und Sie werden wahrscheinl Zum Beispiel könnten Sie einen Abschnitt für **Items (Artikel)** und einen weiteren Abschnitt für **Users (Benutzer)** haben, und diese könnten durch Tags getrennt sein: -{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} ### Einen TypeScript-Client mit Tags generieren { #generate-a-typescript-client-with-tags } @@ -145,7 +145,7 @@ Hier verwendet sie beispielsweise den ersten Tag (Sie werden wahrscheinlich nur Anschließend können Sie diese benutzerdefinierte Funktion als `generate_unique_id_function`-Parameter an **FastAPI** übergeben: -{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} ### Einen TypeScript-Client mit benutzerdefinierten Operation-IDs generieren { #generate-a-typescript-client-with-custom-operation-ids } @@ -167,7 +167,7 @@ Aber für den generierten Client könnten wir die OpenAPI-Operation-IDs direkt v Wir könnten das OpenAPI-JSON in eine Datei `openapi.json` herunterladen und dann mit einem Skript wie dem folgenden **den präfixierten Tag entfernen**: -{* ../../docs_src/generate_clients/tutorial004_py39.py *} +{* ../../docs_src/generate_clients/tutorial004_py310.py *} //// tab | Node.js @@ -179,7 +179,7 @@ Wir könnten das OpenAPI-JSON in eine Datei `openapi.json` herunterladen und dan Damit würden die Operation-IDs von Dingen wie `items-get_items` in `get_items` umbenannt, sodass der Client-Generator einfachere Methodennamen generieren kann. -### Einen TypeScript-Client mit der modifizierten OpenAPI generieren { #generate-a-typescript-client-with-the-preprocessed-openapi } +### Einen TypeScript-Client mit der vorverarbeiteten OpenAPI generieren { #generate-a-typescript-client-with-the-preprocessed-openapi } Da das Endergebnis nun in einer `openapi.json`-Datei vorliegt, müssen Sie Ihren Eingabeort aktualisieren: diff --git a/docs/de/docs/advanced/middleware.md b/docs/de/docs/advanced/middleware.md index ccc6a64c3..4af583b5b 100644 --- a/docs/de/docs/advanced/middleware.md +++ b/docs/de/docs/advanced/middleware.md @@ -57,13 +57,13 @@ Erzwingt, dass alle eingehenden hook* ## Webhooks-Schritte { #webhooks-steps } -Der Prozess besteht normalerweise darin, dass **Sie in Ihrem Code definieren**, welche Nachricht Sie senden möchten, den **Body des Requests**. +Der Prozess besteht normalerweise darin, dass **Sie in Ihrem Code definieren**, welche Nachricht Sie senden möchten, den **Requestbody**. Sie definieren auch auf irgendeine Weise, in welchen **Momenten** Ihre App diese Requests oder Events senden wird. @@ -18,7 +18,7 @@ Die gesamte **Logik** zur Registrierung der URLs für Webhooks und der Code zum ## Webhooks mit **FastAPI** und OpenAPI dokumentieren { #documenting-webhooks-with-fastapi-and-openapi } -Mit **FastAPI**, mithilfe von OpenAPI, können Sie die Namen dieser Webhooks, die Arten von HTTP-Operationen, die Ihre App senden kann (z. B. `POST`, `PUT`, usw.) und die Request**bodys** definieren, die Ihre App senden würde. +Mit **FastAPI**, mithilfe von OpenAPI, können Sie die Namen dieser Webhooks, die Arten von HTTP-Operationen, die Ihre App senden kann (z. B. `POST`, `PUT`, usw.) und die **Requestbodys** definieren, die Ihre App senden würde. Dies kann es Ihren Benutzern viel einfacher machen, **deren APIs zu implementieren**, um Ihre **Webhook**-Requests zu empfangen. Möglicherweise können diese sogar einen Teil ihres eigenen API-Codes automatisch generieren. @@ -32,7 +32,7 @@ Webhooks sind in OpenAPI 3.1.0 und höher verfügbar und werden von FastAPI `0.9 Wenn Sie eine **FastAPI**-Anwendung erstellen, gibt es ein `webhooks`-Attribut, das Sie verwenden können, um *Webhooks* zu definieren, genauso wie Sie *Pfadoperationen* definieren würden, zum Beispiel mit `@app.webhooks.post()`. -{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} Die von Ihnen definierten Webhooks landen im **OpenAPI**-Schema und der automatischen **Dokumentations-Oberfläche**. diff --git a/docs/de/docs/advanced/path-operation-advanced-configuration.md b/docs/de/docs/advanced/path-operation-advanced-configuration.md index c7ac1cf61..a2dd212a1 100644 --- a/docs/de/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/de/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ Mit dem Parameter `operation_id` können Sie die OpenAPI `operationId` festlegen Sie müssten sicherstellen, dass sie für jede Operation eindeutig ist. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} ### Verwendung des Namens der *Pfadoperation-Funktion* als operationId { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ Wenn Sie die Funktionsnamen Ihrer API als `operationId`s verwenden möchten, kö Sie sollten dies tun, nachdem Sie alle Ihre *Pfadoperationen* hinzugefügt haben. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} /// tip | Tipp @@ -40,7 +40,7 @@ Auch wenn diese sich in unterschiedlichen Modulen (Python-Dateien) befinden. Um eine *Pfadoperation* aus dem generierten OpenAPI-Schema (und damit aus den automatischen Dokumentationssystemen) auszuschließen, verwenden Sie den Parameter `include_in_schema` und setzen Sie ihn auf `False`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} ## Fortgeschrittene Beschreibung mittels Docstring { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ Sie können das OpenAPI-Schema für eine *Pfadoperation* erweitern, indem Sie de Dieses `openapi_extra` kann beispielsweise hilfreich sein, um [OpenAPI-Erweiterungen](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) zu deklarieren: -{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *} Wenn Sie die automatische API-Dokumentation öffnen, wird Ihre Erweiterung am Ende der spezifischen *Pfadoperation* angezeigt. @@ -135,13 +135,13 @@ Das Request mit Ihrem eigenen Code zu lesen und zu validieren, ohne die automatischen Funktionen von FastAPI mit Pydantic zu verwenden, aber Sie könnten den Request trotzdem im OpenAPI-Schema definieren wollen. +Sie könnten sich beispielsweise dafür entscheiden, den Request mit Ihrem eigenen Code zu lesen und zu validieren, ohne FastAPIs automatische Funktionen mit Pydantic zu verwenden, aber Sie könnten den Request trotzdem im OpenAPI-Schema definieren wollen. Das könnte man mit `openapi_extra` machen: -{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *} -In diesem Beispiel haben wir kein Pydantic-Modell deklariert. Tatsächlich wird der Requestbody nicht einmal als JSON geparst, sondern direkt als `bytes` gelesen und die Funktion `magic_data_reader()` wäre dafür verantwortlich, ihn in irgendeiner Weise zu parsen. +In diesem Beispiel haben wir kein Pydantic-Modell deklariert. Tatsächlich wird der Requestbody nicht einmal als JSON geparst, sondern direkt als `bytes` gelesen und die Funktion `magic_data_reader()` wäre dafür verantwortlich, ihn in irgendeiner Weise zu parsen. Dennoch können wir das zu erwartende Schema für den Requestbody deklarieren. @@ -151,9 +151,9 @@ Mit demselben Trick könnten Sie ein Pydantic-Modell verwenden, um das JSON-Sche Und Sie könnten dies auch tun, wenn der Datentyp im Request nicht JSON ist. -In der folgenden Anwendung verwenden wir beispielsweise weder die integrierte Funktionalität von FastAPI zum Extrahieren des JSON-Schemas aus Pydantic-Modellen noch die automatische Validierung für JSON. Tatsächlich deklarieren wir den Request-Content-Type als YAML und nicht als JSON: +In der folgenden Anwendung verwenden wir beispielsweise weder FastAPIs integrierte Funktionalität zum Extrahieren des JSON-Schemas aus Pydantic-Modellen noch die automatische Validierung für JSON. Tatsächlich deklarieren wir den Request-Content-Type als YAML und nicht als JSON: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *} Obwohl wir nicht die standardmäßig integrierte Funktionalität verwenden, verwenden wir dennoch ein Pydantic-Modell, um das JSON-Schema für die Daten, die wir in YAML empfangen möchten, manuell zu generieren. @@ -161,7 +161,7 @@ Dann verwenden wir den Request direkt und extrahieren den Body als `bytes`. Das Und dann parsen wir in unserem Code diesen YAML-Inhalt direkt und verwenden dann wieder dasselbe Pydantic-Modell, um den YAML-Inhalt zu validieren: -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *} /// tip | Tipp diff --git a/docs/de/docs/advanced/response-change-status-code.md b/docs/de/docs/advanced/response-change-status-code.md index b209c2d67..26308b823 100644 --- a/docs/de/docs/advanced/response-change-status-code.md +++ b/docs/de/docs/advanced/response-change-status-code.md @@ -20,7 +20,7 @@ Sie können einen Parameter vom Typ `Response` in Ihrer *Pfadoperation-Funktion* Anschließend können Sie den `status_code` in diesem *vorübergehenden* Response-Objekt festlegen. -{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *} Und dann können Sie jedes benötigte Objekt zurückgeben, wie Sie es normalerweise tun würden (ein `dict`, ein Datenbankmodell usw.). diff --git a/docs/de/docs/advanced/response-cookies.md b/docs/de/docs/advanced/response-cookies.md index 87e636cfa..bf6c40e61 100644 --- a/docs/de/docs/advanced/response-cookies.md +++ b/docs/de/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ Sie können einen Parameter vom Typ `Response` in Ihrer *Pfadoperation-Funktion* Und dann können Sie Cookies in diesem *vorübergehenden* Response-Objekt setzen. -{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *} Anschließend können Sie wie gewohnt jedes gewünschte Objekt zurückgeben (ein `dict`, ein Datenbankmodell, usw.). @@ -24,7 +24,7 @@ Dazu können Sie eine Response erstellen, wie unter [Eine Response direkt zurüc Setzen Sie dann Cookies darin und geben Sie sie dann zurück: -{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *} /// tip | Tipp diff --git a/docs/de/docs/advanced/response-directly.md b/docs/de/docs/advanced/response-directly.md index 0a28a6d0e..06bea4794 100644 --- a/docs/de/docs/advanced/response-directly.md +++ b/docs/de/docs/advanced/response-directly.md @@ -54,7 +54,7 @@ Nehmen wir an, Sie möchten eine Response-Objekt festlegen. -{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *} +{* ../../docs_src/response_headers/tutorial002_py310.py hl[1, 7:8] *} Anschließend können Sie wie gewohnt jedes gewünschte Objekt zurückgeben (ein `dict`, ein Datenbankmodell, usw.). @@ -22,7 +22,7 @@ Sie können auch Header hinzufügen, wenn Sie eine `Response` direkt zurückgebe Erstellen Sie eine Response wie in [Eine Response direkt zurückgeben](response-directly.md){.internal-link target=_blank} beschrieben und übergeben Sie die Header als zusätzlichen Parameter: -{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *} +{* ../../docs_src/response_headers/tutorial001_py310.py hl[10:12] *} /// note | Technische Details diff --git a/docs/de/docs/advanced/security/http-basic-auth.md b/docs/de/docs/advanced/security/http-basic-auth.md index f906ecd92..2f66587b1 100644 --- a/docs/de/docs/advanced/security/http-basic-auth.md +++ b/docs/de/docs/advanced/security/http-basic-auth.md @@ -20,7 +20,7 @@ Wenn Sie dann den Benutzernamen und das Passwort eingeben, sendet der Browser di * Diese gibt ein Objekt vom Typ `HTTPBasicCredentials` zurück: * Es enthält den gesendeten `username` und das gesendete `password`. -{* ../../docs_src/security/tutorial006_an_py39.py hl[4,8,12] *} +{* ../../docs_src/security/tutorial006_an_py310.py hl[4,8,12] *} Wenn Sie versuchen, die URL zum ersten Mal zu öffnen (oder in der Dokumentation auf den Button „Execute“ zu klicken), wird der Browser Sie nach Ihrem Benutzernamen und Passwort fragen: @@ -40,7 +40,7 @@ Um dies zu lösen, konvertieren wir zunächst den `username` und das `password` Dann können wir `secrets.compare_digest()` verwenden, um sicherzustellen, dass `credentials.username` `"stanleyjobson"` und `credentials.password` `"swordfish"` ist. -{* ../../docs_src/security/tutorial007_an_py39.py hl[1,12:24] *} +{* ../../docs_src/security/tutorial007_an_py310.py hl[1,12:24] *} Dies wäre das gleiche wie: @@ -104,4 +104,4 @@ So ist Ihr Anwendungscode, dank der Verwendung von `secrets.compare_digest()`, v Nachdem Sie festgestellt haben, dass die Anmeldeinformationen falsch sind, geben Sie eine `HTTPException` mit dem Statuscode 401 zurück (derselbe, der auch zurückgegeben wird, wenn keine Anmeldeinformationen angegeben werden) und fügen den Header `WWW-Authenticate` hinzu, damit der Browser die Anmeldeaufforderung erneut anzeigt: -{* ../../docs_src/security/tutorial007_an_py39.py hl[26:30] *} +{* ../../docs_src/security/tutorial007_an_py310.py hl[26:30] *} diff --git a/docs/de/docs/advanced/settings.md b/docs/de/docs/advanced/settings.md index ea4540e10..f83e635a7 100644 --- a/docs/de/docs/advanced/settings.md +++ b/docs/de/docs/advanced/settings.md @@ -46,12 +46,6 @@ $ pip install "fastapi[all]"
-/// info | Info - -In Pydantic v1 war es im Hauptpackage enthalten. Jetzt wird es als unabhängiges Package verteilt, sodass Sie wählen können, ob Sie es installieren möchten oder nicht, falls Sie die Funktionalität nicht benötigen. - -/// - ### Das `Settings`-Objekt erstellen { #create-the-settings-object } Importieren Sie `BaseSettings` aus Pydantic und erstellen Sie eine Unterklasse, ganz ähnlich wie bei einem Pydantic-Modell. @@ -60,7 +54,7 @@ Auf die gleiche Weise wie bei Pydantic-Modellen deklarieren Sie Klassenattribute Sie können dieselben Validierungs-Funktionen und -Tools verwenden, die Sie für Pydantic-Modelle verwenden, z. B. verschiedene Datentypen und zusätzliche Validierungen mit `Field()`. -{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} +{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *} /// tip | Tipp @@ -76,7 +70,7 @@ Als Nächstes werden die Daten konvertiert und validiert. Wenn Sie also dieses ` Dann können Sie das neue `settings`-Objekt in Ihrer Anwendung verwenden: -{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *} +{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *} ### Den Server ausführen { #run-the-server } @@ -110,11 +104,11 @@ Sie könnten diese Einstellungen in eine andere Moduldatei einfügen, wie Sie in Sie könnten beispielsweise eine Datei `config.py` haben mit: -{* ../../docs_src/settings/app01_py39/config.py *} +{* ../../docs_src/settings/app01_py310/config.py *} Und dann verwenden Sie diese in einer Datei `main.py`: -{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *} +{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *} /// tip | Tipp @@ -132,7 +126,7 @@ Dies könnte besonders beim Testen nützlich sein, da es sehr einfach ist, eine Ausgehend vom vorherigen Beispiel könnte Ihre Datei `config.py` so aussehen: -{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} +{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *} Beachten Sie, dass wir jetzt keine Standardinstanz `settings = Settings()` erstellen. @@ -140,7 +134,7 @@ Beachten Sie, dass wir jetzt keine Standardinstanz `settings = Settings()` erste Jetzt erstellen wir eine Abhängigkeit, die ein neues `config.Settings()` zurückgibt. -{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} +{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *} /// tip | Tipp @@ -152,13 +146,13 @@ Im Moment nehmen Sie an, dass `get_settings()` eine normale Funktion ist. Und dann können wir das von der *Pfadoperation-Funktion* als Abhängigkeit einfordern und es überall dort verwenden, wo wir es brauchen. -{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} +{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *} ### Einstellungen und Tests { #settings-and-testing } Dann wäre es sehr einfach, beim Testen ein anderes Einstellungsobjekt bereitzustellen, indem man eine Abhängigkeitsüberschreibung für `get_settings` erstellt: -{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} +{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *} Bei der Abhängigkeitsüberschreibung legen wir einen neuen Wert für `admin_email` fest, wenn wir das neue `Settings`-Objekt erstellen, und geben dann dieses neue Objekt zurück. @@ -199,7 +193,7 @@ APP_NAME="ChimichangApp" Und dann aktualisieren Sie Ihre `config.py` mit: -{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} +{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *} /// tip | Tipp @@ -232,7 +226,7 @@ würden wir dieses Objekt für jeden Request erstellen und die `.env`-Datei für Da wir jedoch den `@lru_cache`-Dekorator oben verwenden, wird das `Settings`-Objekt nur einmal erstellt, nämlich beim ersten Aufruf. ✔️ -{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} +{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *} Dann wird bei allen nachfolgenden Aufrufen von `get_settings()`, in den Abhängigkeiten für darauffolgende Requests, dasselbe Objekt zurückgegeben, das beim ersten Aufruf zurückgegeben wurde, anstatt den Code von `get_settings()` erneut auszuführen und ein neues `Settings`-Objekt zu erstellen. diff --git a/docs/de/docs/advanced/sub-applications.md b/docs/de/docs/advanced/sub-applications.md index 081574d0a..6b862b44e 100644 --- a/docs/de/docs/advanced/sub-applications.md +++ b/docs/de/docs/advanced/sub-applications.md @@ -10,7 +10,7 @@ Wenn Sie zwei unabhängige FastAPI-Anwendungen mit deren eigenen unabhängigen O Erstellen Sie zunächst die Hauptanwendung **FastAPI** und deren *Pfadoperationen*: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *} ### Unteranwendung { #sub-application } @@ -18,7 +18,7 @@ Erstellen Sie dann Ihre Unteranwendung und deren *Pfadoperationen*. Diese Unteranwendung ist nur eine weitere Standard-FastAPI-Anwendung, aber diese wird „gemountet“: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *} ### Die Unteranwendung mounten { #mount-the-sub-application } @@ -26,7 +26,7 @@ Mounten Sie in Ihrer Top-Level-Anwendung `app` die Unteranwendung `subapi`. In diesem Fall wird sie im Pfad `/subapi` gemountet: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *} ### Die automatische API-Dokumentation testen { #check-the-automatic-api-docs } diff --git a/docs/de/docs/advanced/templates.md b/docs/de/docs/advanced/templates.md index 97a45e612..87baba359 100644 --- a/docs/de/docs/advanced/templates.md +++ b/docs/de/docs/advanced/templates.md @@ -27,7 +27,7 @@ $ pip install jinja2 * Deklarieren Sie einen `Request`-Parameter in der *Pfadoperation*, welcher ein Template zurückgibt. * Verwenden Sie die von Ihnen erstellten `templates`, um eine `TemplateResponse` zu rendern und zurückzugeben, übergeben Sie den Namen des Templates, das Requestobjekt und ein „Kontext“-Dictionary mit Schlüssel-Wert-Paaren, die innerhalb des Jinja2-Templates verwendet werden sollen. -{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *} +{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *} /// note | Hinweis diff --git a/docs/de/docs/advanced/testing-events.md b/docs/de/docs/advanced/testing-events.md index 5b12f3f18..053aeffc8 100644 --- a/docs/de/docs/advanced/testing-events.md +++ b/docs/de/docs/advanced/testing-events.md @@ -2,11 +2,11 @@ Wenn Sie `lifespan` in Ihren Tests ausführen müssen, können Sie den `TestClient` mit einer `with`-Anweisung verwenden: -{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *} +{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *} Sie können mehr Details unter [„Lifespan in Tests ausführen in der offiziellen Starlette-Dokumentation.“](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) nachlesen. Für die deprecateten Events `startup` und `shutdown` können Sie den `TestClient` wie folgt verwenden: -{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *} +{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *} diff --git a/docs/de/docs/advanced/testing-websockets.md b/docs/de/docs/advanced/testing-websockets.md index 9ecca7a4f..4bc46da3e 100644 --- a/docs/de/docs/advanced/testing-websockets.md +++ b/docs/de/docs/advanced/testing-websockets.md @@ -4,7 +4,7 @@ Sie können den schon bekannten `TestClient` zum Testen von WebSockets verwenden Dazu verwenden Sie den `TestClient` in einer `with`-Anweisung, eine Verbindung zum WebSocket herstellend: -{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *} +{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *} /// note | Hinweis diff --git a/docs/de/docs/advanced/using-request-directly.md b/docs/de/docs/advanced/using-request-directly.md index 36d73b806..bed1cdbea 100644 --- a/docs/de/docs/advanced/using-request-directly.md +++ b/docs/de/docs/advanced/using-request-directly.md @@ -29,7 +29,7 @@ Angenommen, Sie möchten auf die IP-Adresse/den Host des Clients in Ihrer *Pfado Dazu müssen Sie direkt auf den Request zugreifen. -{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *} +{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *} Durch die Deklaration eines *Pfadoperation-Funktionsparameters*, dessen Typ der `Request` ist, weiß **FastAPI**, dass es den `Request` diesem Parameter übergeben soll. diff --git a/docs/de/docs/advanced/websockets.md b/docs/de/docs/advanced/websockets.md index 05ae5a4b3..b1a49c5aa 100644 --- a/docs/de/docs/advanced/websockets.md +++ b/docs/de/docs/advanced/websockets.md @@ -38,13 +38,13 @@ In der Produktion hätten Sie eine der oben genannten Optionen. Aber es ist der einfachste Weg, sich auf die Serverseite von WebSockets zu konzentrieren und ein funktionierendes Beispiel zu haben: -{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} ## Einen `websocket` erstellen { #create-a-websocket } Erstellen Sie in Ihrer **FastAPI**-Anwendung einen `websocket`: -{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} /// note | Technische Details @@ -58,7 +58,7 @@ Sie könnten auch `from starlette.websockets import WebSocket` verwenden. In Ihrer WebSocket-Route können Sie Nachrichten `await`en und Nachrichten senden. -{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} Sie können Binär-, Text- und JSON-Daten empfangen und senden. @@ -154,7 +154,7 @@ Damit können Sie den WebSocket verbinden und dann Nachrichten senden und empfan Wenn eine WebSocket-Verbindung geschlossen wird, löst `await websocket.receive_text()` eine `WebSocketDisconnect`-Exception aus, die Sie dann wie in folgendem Beispiel abfangen und behandeln können. -{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} +{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} Zum Ausprobieren: diff --git a/docs/de/docs/advanced/wsgi.md b/docs/de/docs/advanced/wsgi.md index 0090883ce..b2f170e80 100644 --- a/docs/de/docs/advanced/wsgi.md +++ b/docs/de/docs/advanced/wsgi.md @@ -18,7 +18,7 @@ Wrappen Sie dann die WSGI-Anwendung (z. B. Flask) mit der Middleware. Und dann mounten Sie das auf einem Pfad. -{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} +{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *} /// note | Hinweis diff --git a/docs/de/docs/alternatives.md b/docs/de/docs/alternatives.md index 4dd127dba..b6509ec09 100644 --- a/docs/de/docs/alternatives.md +++ b/docs/de/docs/alternatives.md @@ -20,7 +20,7 @@ Es ist das beliebteste Python-Framework und genießt großes Vertrauen. Es wird Es ist relativ eng mit relationalen Datenbanken (wie MySQL oder PostgreSQL) gekoppelt, daher ist es nicht sehr einfach, eine NoSQL-Datenbank (wie Couchbase, MongoDB, Cassandra, usw.) als Hauptspeicherengine zu verwenden. -Es wurde erstellt, um den HTML-Code im Backend zu generieren, nicht um APIs zu erstellen, die von einem modernen Frontend (wie React, Vue.js und Angular) oder von anderen Systemen (wie IoT-Geräten) verwendet werden, um mit ihm zu kommunizieren. +Es wurde erstellt, um den HTML-Code im Backend zu generieren, nicht um APIs zu erstellen, die von einem modernen Frontend (wie React, Vue.js und Angular) oder von anderen Systemen (wie IoT-Geräten) verwendet werden, um mit ihm zu kommunizieren. ### Django REST Framework { #django-rest-framework } @@ -82,7 +82,7 @@ Aus diesem Grund heißt es auf der offiziellen Website: > Requests ist eines der am häufigsten heruntergeladenen Python-Packages aller Zeiten -Die Art und Weise, wie Sie es verwenden, ist sehr einfach. Um beispielsweise einen `GET`-Request zu machen, würden Sie schreiben: +Die Art und Weise, wie Sie es verwenden, ist sehr einfach. Um beispielsweise einen `GET`-Request zu machen, würden Sie schreiben: ```Python response = requests.get("http://example.com/some/url") @@ -137,7 +137,7 @@ Es gibt mehrere Flask REST Frameworks, aber nachdem ich die Zeit und Arbeit inve ### Marshmallow { #marshmallow } -Eine der von API-Systemen benötigten Hauptfunktionen ist die Daten-„Serialisierung“, welche Daten aus dem Code (Python) entnimmt und in etwas umwandelt, was durch das Netzwerk gesendet werden kann. Beispielsweise das Konvertieren eines Objekts, welches Daten aus einer Datenbank enthält, in ein JSON-Objekt. Konvertieren von `datetime`-Objekten in Strings, usw. +Eine der von API-Systemen benötigten Hauptfunktionen ist die Daten-„Serialisierung“, welche Daten aus dem Code (Python) entnimmt und in etwas umwandelt, was durch das Netzwerk gesendet werden kann. Beispielsweise das Konvertieren eines Objekts, welches Daten aus einer Datenbank enthält, in ein JSON-Objekt. Konvertieren von `datetime`-Objekten in Strings, usw. Eine weitere wichtige Funktion, benötigt von APIs, ist die Datenvalidierung, welche sicherstellt, dass die Daten unter gegebenen Umständen gültig sind. Zum Beispiel, dass ein Feld ein `int` ist und kein zufälliger String. Das ist besonders nützlich für hereinkommende Daten. @@ -145,7 +145,7 @@ Ohne ein Datenvalidierungssystem müssten Sie alle Prüfungen manuell im Code du Für diese Funktionen wurde Marshmallow entwickelt. Es ist eine großartige Bibliothek und ich habe sie schon oft genutzt. -Aber sie wurde erstellt, bevor Typhinweise in Python existierten. Um also ein Schema zu definieren, müssen Sie bestimmte Werkzeuge und Klassen verwenden, die von Marshmallow bereitgestellt werden. +Aber sie wurde erstellt, bevor Typhinweise in Python existierten. Um also ein Schema zu definieren, müssen Sie bestimmte Werkzeuge und Klassen verwenden, die von Marshmallow bereitgestellt werden. /// check | Inspirierte **FastAPI** @@ -155,7 +155,7 @@ Code zu verwenden, um „Schemas“ zu definieren, welche Datentypen und Validie ### Webargs { #webargs } -Eine weitere wichtige Funktion, die von APIs benötigt wird, ist das Parsen von Daten aus eingehenden Requests. +Eine weitere wichtige Funktion, die von APIs benötigt wird, ist das Parsen von Daten aus eingehenden Requests. Webargs wurde entwickelt, um dieses für mehrere Frameworks, einschließlich Flask, bereitzustellen. @@ -283,7 +283,7 @@ Aus diesem Grund basiert **FastAPI** auf Starlette, da dieses das schnellste ver Falcon ist ein weiteres leistungsstarkes Python-Framework. Es ist minimalistisch konzipiert und dient als Grundlage für andere Frameworks wie Hug. -Es ist so konzipiert, dass es über Funktionen verfügt, welche zwei Parameter empfangen, einen „Request“ und eine „Response“. Dann „lesen“ Sie Teile des Requests und „schreiben“ Teile der Response. Aufgrund dieses Designs ist es nicht möglich, Request-Parameter und -Bodys mit Standard-Python-Typhinweisen als Funktionsparameter zu deklarieren. +Es ist so konzipiert, dass es über Funktionen verfügt, welche zwei Parameter empfangen, einen „Request“ und eine „Response“. Dann „lesen“ Sie Teile des Requests und „schreiben“ Teile der Response. Aufgrund dieses Designs ist es nicht möglich, Request-Parameter und -Bodys mit Standard-Python-Typhinweisen als Funktionsparameter zu deklarieren. Daher müssen Datenvalidierung, Serialisierung und Dokumentation im Code und nicht automatisch erfolgen. Oder sie müssen als Framework oberhalb von Falcon implementiert werden, so wie Hug. Dieselbe Unterscheidung findet auch in anderen Frameworks statt, die vom Design von Falcon inspiriert sind und ein Requestobjekt und ein Responseobjekt als Parameter haben. @@ -419,7 +419,7 @@ Die gesamte Datenvalidierung, Datenserialisierung und automatische Modelldokumen ### Starlette { #starlette } -Starlette ist ein leichtgewichtiges ASGI-Framework/Toolkit, welches sich ideal für die Erstellung hochperformanter asynchroner Dienste eignet. +Starlette ist ein leichtgewichtiges ASGI-Framework/Toolkit, welches sich ideal für die Erstellung hochperformanter asynchroner Dienste eignet. Es ist sehr einfach und intuitiv. Es ist so konzipiert, dass es leicht erweiterbar ist und über modulare Komponenten verfügt. diff --git a/docs/de/docs/benchmarks.md b/docs/de/docs/benchmarks.md index 09126c5d9..285d35bf9 100644 --- a/docs/de/docs/benchmarks.md +++ b/docs/de/docs/benchmarks.md @@ -23,7 +23,7 @@ Die Hierarchie ist wie folgt: * Sie würden eine Anwendung nicht direkt in Uvicorn schreiben. Das würde bedeuten, dass Ihr Code zumindest mehr oder weniger den gesamten von Starlette (oder **FastAPI**) bereitgestellten Code enthalten müsste. Und wenn Sie das täten, hätte Ihre endgültige Anwendung den gleichen Overhead wie bei der Verwendung eines Frameworks und der Minimierung Ihres Anwendungscodes und der Fehler. * Wenn Sie Uvicorn vergleichen, vergleichen Sie es mit Anwendungsservern wie Daphne, Hypercorn, uWSGI, usw. * **Starlette**: - * Wird nach Uvicorn die nächstbeste Performanz erbringen. Tatsächlich verwendet Starlette intern Uvicorn, um zu laufen. Daher kann es wahrscheinlich nur „langsamer“ als Uvicorn werden, weil mehr Code ausgeführt werden muss. + * Wird nach Uvicorn die nächstbeste Performanz erbringen. Tatsächlich verwendet Starlette Uvicorn, um zu laufen. Daher kann es wahrscheinlich nur „langsamer“ als Uvicorn werden, weil mehr Code ausgeführt werden muss. * Aber es bietet Ihnen die Werkzeuge, um einfache Webanwendungen zu erstellen, mit Routing basierend auf Pfaden, usw. * Wenn Sie Starlette vergleichen, vergleichen Sie es mit Webframeworks (oder Mikroframeworks) wie Sanic, Flask, Django, usw. * **FastAPI**: diff --git a/docs/de/docs/deployment/cloud.md b/docs/de/docs/deployment/cloud.md index ad3ff76db..08a554b8d 100644 --- a/docs/de/docs/deployment/cloud.md +++ b/docs/de/docs/deployment/cloud.md @@ -1,6 +1,6 @@ # FastAPI bei Cloudanbietern deployen { #deploy-fastapi-on-cloud-providers } -Sie können praktisch **jeden Cloudanbieter** verwenden, um Ihre FastAPI-Anwendung bereitzustellen. +Sie können praktisch **jeden Cloudanbieter** verwenden, um Ihre FastAPI-Anwendung zu deployen. In den meisten Fällen bieten die großen Cloudanbieter Anleitungen zum Deployment von FastAPI an. diff --git a/docs/de/docs/deployment/docker.md b/docs/de/docs/deployment/docker.md index 1e28efe52..704311b26 100644 --- a/docs/de/docs/deployment/docker.md +++ b/docs/de/docs/deployment/docker.md @@ -14,7 +14,7 @@ Sie haben es eilig und kennen sich bereits aus? Springen Sie zum [`Dockerfile` u Dockerfile-Vorschau 👀 ```Dockerfile -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -166,7 +166,7 @@ Erstellen Sie nun im selben Projektverzeichnis eine Datei `Dockerfile` mit: ```{ .dockerfile .annotate } # (1)! -FROM python:3.9 +FROM python:3.14 # (2)! WORKDIR /code @@ -390,7 +390,7 @@ Wenn Ihr FastAPI eine einzelne Datei ist, zum Beispiel `main.py` ohne ein `./app Dann müssten Sie nur noch die entsprechenden Pfade ändern, um die Datei im `Dockerfile` zu kopieren: ```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -454,7 +454,7 @@ Ohne die Verwendung von Containern kann es umständlich und schwierig sein, Anwe ## Replikation – Anzahl der Prozesse { #replication-number-of-processes } -Wenn Sie einen Cluster von Maschinen mit **Kubernetes**, Docker Swarm Mode, Nomad verwenden, oder einem anderen, ähnlich komplexen System zur Verwaltung verteilter Container auf mehreren Maschinen, möchten Sie wahrscheinlich die **Replikation auf Cluster-Ebene abwickeln**, anstatt in jedem Container einen **Prozessmanager** (wie Uvicorn mit Workern) zu verwenden. +Wenn Sie einen Cluster von Maschinen mit **Kubernetes**, Docker Swarm Mode, Nomad verwenden, oder einem anderen, ähnlich komplexen System zur Verwaltung verteilter Container auf mehreren Maschinen, möchten Sie wahrscheinlich die **Replikation auf Cluster-Ebene abwickeln**, anstatt in jedem Container einen **Prozessmanager** (wie Uvicorn mit Workern) zu verwenden. Diese verteilten Containerverwaltungssysteme wie Kubernetes verfügen normalerweise über eine integrierte Möglichkeit, die **Replikation von Containern** zu handhaben und gleichzeitig **Load Balancing** für die eingehenden Requests zu unterstützen. Alles auf **Cluster-Ebene**. @@ -499,7 +499,7 @@ Natürlich gibt es **Sonderfälle**, in denen Sie **einen Container** mit mehrer In diesen Fällen können Sie die `--workers` Befehlszeilenoption verwenden, um die Anzahl der zu startenden Worker festzulegen: ```{ .dockerfile .annotate } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -570,7 +570,7 @@ Wenn Sie ein einfaches Setup mit einem **einzelnen Container** haben, welcher da ### Docker-Basisimage { #base-docker-image } -Es gab ein offizielles FastAPI-Docker-Image: tiangolo/uvicorn-gunicorn-fastapi. Dieses ist jedoch jetzt veraltet. ⛔️ +Es gab ein offizielles FastAPI-Docker-Image: tiangolo/uvicorn-gunicorn-fastapi. Dieses ist jedoch jetzt deprecatet. ⛔️ Sie sollten wahrscheinlich **nicht** dieses Basis-Docker-Image (oder ein anderes ähnliches) verwenden. diff --git a/docs/de/docs/deployment/https.md b/docs/de/docs/deployment/https.md index 1c4ce6b44..08963306f 100644 --- a/docs/de/docs/deployment/https.md +++ b/docs/de/docs/deployment/https.md @@ -65,7 +65,7 @@ Hier ist ein Beispiel, wie eine HTTPS-API aussehen könnte, Schritt für Schritt Alles beginnt wahrscheinlich damit, dass Sie einen **Domainnamen erwerben**. Anschließend konfigurieren Sie ihn in einem DNS-Server (wahrscheinlich beim selben Cloudanbieter). -Sie würden wahrscheinlich einen Cloud-Server (eine virtuelle Maschine) oder etwas Ähnliches bekommen, und dieser hätte eine feste **öffentliche IP-Adresse**. +Sie würden wahrscheinlich einen Cloud-Server (eine virtuelle Maschine) oder etwas Ähnliches bekommen, und dieser hätte eine feste **öffentliche IP-Adresse**. In dem oder den DNS-Server(n) würden Sie einen Eintrag (einen „`A record`“) konfigurieren, um mit **Ihrer Domain** auf die öffentliche **IP-Adresse Ihres Servers** zu verweisen. diff --git a/docs/de/docs/deployment/index.md b/docs/de/docs/deployment/index.md index cb3e53746..0c83cc529 100644 --- a/docs/de/docs/deployment/index.md +++ b/docs/de/docs/deployment/index.md @@ -4,7 +4,7 @@ Das Deployment einer **FastAPI**-Anwendung ist relativ einfach. ## Was bedeutet Deployment { #what-does-deployment-mean } -**Deployment** bedeutet, die notwendigen Schritte durchzuführen, um die Anwendung **für die Endbenutzer verfügbar** zu machen. +**Deployment** bedeutet, die notwendigen Schritte durchzuführen, um die Anwendung **für die Benutzer verfügbar** zu machen. Bei einer **Web-API** bedeutet das normalerweise, diese auf einem **entfernten Rechner** zu platzieren, mit einem **Serverprogramm**, welches gute Leistung, Stabilität, usw. bietet, damit Ihre **Benutzer** auf die Anwendung effizient und ohne Unterbrechungen oder Probleme **zugreifen** können. diff --git a/docs/de/docs/deployment/server-workers.md b/docs/de/docs/deployment/server-workers.md index 7b68f1b1a..fbc9f6906 100644 --- a/docs/de/docs/deployment/server-workers.md +++ b/docs/de/docs/deployment/server-workers.md @@ -119,7 +119,7 @@ In der Liste der Deployment-Konzepte von oben würde die Verwendung von Workern * **Sicherheit – HTTPS** * **Beim Hochfahren ausführen** -* **Neustarts** +* ***Neustarts*** * Replikation (die Anzahl der laufenden Prozesse) * **Arbeitsspeicher** * **Schritte vor dem Start** diff --git a/docs/de/docs/features.md b/docs/de/docs/features.md index 0b51e9737..6b7b4c296 100644 --- a/docs/de/docs/features.md +++ b/docs/de/docs/features.md @@ -6,7 +6,7 @@ ### Basiert auf offenen Standards { #based-on-open-standards } -* OpenAPI für die Erstellung von APIs, inklusive Deklarationen von Pfad-Operationen, Parametern, Requestbodys, Sicherheit, usw. +* OpenAPI für die Erstellung von APIs, inklusive Deklarationen von Pfad-Operationen, Parametern, Requestbodys, Sicherheit, usw. * Automatische Dokumentation der Datenmodelle mit JSON Schema (da OpenAPI selbst auf JSON Schema basiert). * Um diese Standards herum entworfen, nach sorgfältigem Studium. Statt einer nachträglichen Schicht darüber. * Dies ermöglicht auch automatische **Client-Code-Generierung** in vielen Sprachen. @@ -136,7 +136,7 @@ Alles als wiederverwendbare Tools und Komponenten gebaut, die einfach in Ihre Sy ### Dependency Injection { #dependency-injection } -FastAPI enthält ein extrem einfach zu verwendendes, aber extrem mächtiges Dependency Injection System. +FastAPI enthält ein extrem einfach zu verwendendes, aber extrem mächtiges Dependency Injection System. * Selbst Abhängigkeiten können Abhängigkeiten haben, woraus eine Hierarchie oder ein **„Graph“ von Abhängigkeiten** entsteht. * Alles **automatisch gehandhabt** durch das Framework. @@ -153,8 +153,8 @@ Jede Integration wurde so entworfen, dass sie so einfach zu nutzen ist (mit Abh ### Getestet { #tested } -* 100 % Testabdeckung. -* 100 % Typen annotiert. +* 100 % Testabdeckung. +* 100 % Typen annotiert. * Verwendet in Produktionsanwendungen. ## Starlette Merkmale { #starlette-features } @@ -179,7 +179,7 @@ Mit **FastAPI** bekommen Sie alles von **Starlette** (da FastAPI nur Starlette a **FastAPI** ist vollkommen kompatibel (und basiert auf) Pydantic. Das bedeutet, wenn Sie eigenen Pydantic Quellcode haben, funktioniert der. -Inklusive externer Bibliotheken, die auf Pydantic basieren, wie ORMs, ODMs für Datenbanken. +Inklusive externer Bibliotheken, die auf Pydantic basieren, wie ORMs, ODMs für Datenbanken. Daher können Sie in vielen Fällen das Objekt eines Requests **direkt zur Datenbank** schicken, weil alles automatisch validiert wird. @@ -190,7 +190,7 @@ Mit **FastAPI** bekommen Sie alle Funktionen von **Pydantic** (da FastAPI für d * **Kein Kopfzerbrechen**: * Keine neue Schemadefinition-Mikrosprache zu lernen. * Wenn Sie Pythons Typen kennen, wissen Sie, wie man Pydantic verwendet. -* Gutes Zusammenspiel mit Ihrer/Ihrem **IDE/Linter/Gehirn**: +* Gutes Zusammenspiel mit Ihrer/Ihrem **IDE/Linter/Gehirn**: * Weil Pydantics Datenstrukturen einfach nur Instanzen ihrer definierten Klassen sind; Autovervollständigung, Linting, mypy und Ihre Intuition sollten alle einwandfrei mit Ihren validierten Daten funktionieren. * Validierung von **komplexen Strukturen**: * Benutzung von hierarchischen Pydantic-Modellen, Python-`typing`s `List` und `Dict`, etc. diff --git a/docs/de/docs/history-design-future.md b/docs/de/docs/history-design-future.md index 45198ff1c..0217f4276 100644 --- a/docs/de/docs/history-design-future.md +++ b/docs/de/docs/history-design-future.md @@ -58,7 +58,7 @@ Nachdem ich mehrere Alternativen getestet hatte, entschied ich, dass ich **Starlette** beigetragen, der anderen Schlüsselanforderung. +Während der Entwicklung habe ich auch zu **Starlette** beigetragen, die andere Schlüsselanforderung. ## Entwicklung { #development } diff --git a/docs/de/docs/how-to/authentication-error-status-code.md b/docs/de/docs/how-to/authentication-error-status-code.md index c743b54d9..2d54926ff 100644 --- a/docs/de/docs/how-to/authentication-error-status-code.md +++ b/docs/de/docs/how-to/authentication-error-status-code.md @@ -8,7 +8,7 @@ Aber falls Ihre Clients aus irgendeinem Grund vom alten Verhalten abhängen, kö Sie können beispielsweise eine Unterklasse von `HTTPBearer` erstellen, die einen Fehler `403 Forbidden` zurückgibt, statt des Default-`401 Unauthorized`-Fehlers: -{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} /// tip | Tipp diff --git a/docs/de/docs/how-to/conditional-openapi.md b/docs/de/docs/how-to/conditional-openapi.md index 6e665cc4c..07573f01f 100644 --- a/docs/de/docs/how-to/conditional-openapi.md +++ b/docs/de/docs/how-to/conditional-openapi.md @@ -29,7 +29,7 @@ Sie können problemlos dieselben Pydantic-Einstellungen verwenden, um Ihre gener Zum Beispiel: -{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} Hier deklarieren wir die Einstellung `openapi_url` mit dem gleichen Defaultwert `"/openapi.json"`. diff --git a/docs/de/docs/how-to/configure-swagger-ui.md b/docs/de/docs/how-to/configure-swagger-ui.md index 1c3f5c0c5..efb57339c 100644 --- a/docs/de/docs/how-to/configure-swagger-ui.md +++ b/docs/de/docs/how-to/configure-swagger-ui.md @@ -18,7 +18,7 @@ Ohne Änderung der Einstellungen ist die Syntaxhervorhebung standardmäßig akti Sie können sie jedoch deaktivieren, indem Sie `syntaxHighlight` auf `False` setzen: -{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} ... und dann zeigt die Swagger-Oberfläche die Syntaxhervorhebung nicht mehr an: @@ -28,7 +28,7 @@ Sie können sie jedoch deaktivieren, indem Sie `syntaxHighlight` auf `False` set Auf die gleiche Weise könnten Sie das Theme der Syntaxhervorhebung mit dem Schlüssel `"syntaxHighlight.theme"` festlegen (beachten Sie, dass er einen Punkt in der Mitte hat): -{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} Obige Konfiguration würde das Theme für die Farbe der Syntaxhervorhebung ändern: @@ -46,7 +46,7 @@ Sie können jede davon überschreiben, indem Sie im Argument `swagger_ui_paramet Um beispielsweise `deepLinking` zu deaktivieren, könnten Sie folgende Einstellungen an `swagger_ui_parameters` übergeben: -{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} ## Andere Parameter der Swagger-Oberfläche { #other-swagger-ui-parameters } diff --git a/docs/de/docs/how-to/custom-docs-ui-assets.md b/docs/de/docs/how-to/custom-docs-ui-assets.md index 6b1d654ad..a34a21a31 100644 --- a/docs/de/docs/how-to/custom-docs-ui-assets.md +++ b/docs/de/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ Der erste Schritt besteht darin, die automatischen Dokumentationen zu deaktivier Um diese zu deaktivieren, setzen Sie deren URLs beim Erstellen Ihrer `FastAPI`-App auf `None`: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} ### Die benutzerdefinierten Dokumentationen hinzufügen { #include-the-custom-docs } @@ -34,7 +34,7 @@ Sie können die internen Funktionen von FastAPI wiederverwenden, um die HTML-Sei Und ähnlich für ReDoc ... -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} /// tip | Tipp @@ -50,7 +50,7 @@ Swagger UI erledigt das hinter den Kulissen für Sie, benötigt aber diesen „U Um nun testen zu können, ob alles funktioniert, erstellen Sie eine *Pfadoperation*: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} ### Es testen { #test-it } @@ -118,7 +118,7 @@ Danach könnte Ihre Dateistruktur wie folgt aussehen: * Importieren Sie `StaticFiles`. * „Mounten“ Sie eine `StaticFiles()`-Instanz in einem bestimmten Pfad. -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} ### Die statischen Dateien testen { #test-the-static-files } @@ -144,7 +144,7 @@ Wie bei der Verwendung eines benutzerdefinierten CDN besteht der erste Schritt d Um sie zu deaktivieren, setzen Sie deren URLs beim Erstellen Ihrer `FastAPI`-App auf `None`: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} ### Die benutzerdefinierten Dokumentationen für statische Dateien hinzufügen { #include-the-custom-docs-for-static-files } @@ -160,7 +160,7 @@ Auch hier können Sie die internen Funktionen von FastAPI wiederverwenden, um di Und ähnlich für ReDoc ... -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} /// tip | Tipp @@ -176,7 +176,7 @@ Swagger UI erledigt das hinter den Kulissen für Sie, benötigt aber diesen „U Um nun testen zu können, ob alles funktioniert, erstellen Sie eine *Pfadoperation*: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} ### Benutzeroberfläche mit statischen Dateien testen { #test-static-files-ui } diff --git a/docs/de/docs/how-to/extending-openapi.md b/docs/de/docs/how-to/extending-openapi.md index c07ed2aa0..d23dc4df0 100644 --- a/docs/de/docs/how-to/extending-openapi.md +++ b/docs/de/docs/how-to/extending-openapi.md @@ -43,19 +43,19 @@ Fügen wir beispielsweise Requests
verwendet. -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *} ### Die Methode überschreiben { #override-the-method } Jetzt können Sie die Methode `.openapi()` durch Ihre neue Funktion ersetzen. -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *} ### Es testen { #check-it } diff --git a/docs/de/docs/how-to/graphql.md b/docs/de/docs/how-to/graphql.md index 5c908cec4..1f0c96eef 100644 --- a/docs/de/docs/how-to/graphql.md +++ b/docs/de/docs/how-to/graphql.md @@ -35,7 +35,7 @@ Abhängig von Ihrem Anwendungsfall könnten Sie eine andere Bibliothek vorziehen Hier ist eine kleine Vorschau, wie Sie Strawberry mit FastAPI integrieren können: -{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *} +{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *} Weitere Informationen zu Strawberry finden Sie in der Strawberry-Dokumentation. diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md index 3ce5cb27f..bb28c68f7 100644 --- a/docs/de/docs/index.md +++ b/docs/de/docs/index.md @@ -40,7 +40,7 @@ Seine Schlüssel-Merkmale sind: * **Schnell**: Sehr hohe Performanz, auf Augenhöhe mit **NodeJS** und **Go** (dank Starlette und Pydantic). [Eines der schnellsten verfügbaren Python-Frameworks](#performance). * **Schnell zu entwickeln**: Erhöhen Sie die Geschwindigkeit bei der Entwicklung von Features um etwa 200 % bis 300 %. * * **Weniger Bugs**: Verringern Sie die von Menschen (Entwicklern) verursachten Fehler um etwa 40 %. * -* **Intuitiv**: Hervorragende Editor-Unterstützung. Code-Vervollständigung überall. Weniger Zeit mit Debuggen verbringen. +* **Intuitiv**: Hervorragende Editor-Unterstützung. Code-Vervollständigung überall. Weniger Zeit mit Debuggen verbringen. * **Einfach**: So konzipiert, dass es einfach zu benutzen und zu erlernen ist. Weniger Zeit mit dem Lesen von Dokumentation verbringen. * **Kurz**: Minimieren Sie die Verdoppelung von Code. Mehrere Features aus jeder Parameterdeklaration. Weniger Bugs. * **Robust**: Erhalten Sie produktionsreifen Code. Mit automatischer, interaktiver Dokumentation. @@ -363,12 +363,12 @@ item: Item ... und mit dieser einen Deklaration erhalten Sie: * Editor-Unterstützung, einschließlich: - * Code-Vervollständigung. + * Vervollständigung. * Typprüfungen. * Validierung von Daten: * Automatische und eindeutige Fehler, wenn die Daten ungültig sind. * Validierung sogar für tief verschachtelte JSON-Objekte. -* Konvertierung von Eingabedaten: Aus dem Netzwerk kommend, zu Python-Daten und -Typen. Lesen von: +* Konvertierung von Eingabedaten: Aus dem Netzwerk kommend, zu Python-Daten und -Typen. Lesen von: * JSON. * Pfad-Parametern. * Query-Parametern. @@ -376,7 +376,7 @@ item: Item * Headern. * Formularen. * Dateien. -* Konvertierung von Ausgabedaten: Konvertierung von Python-Daten und -Typen zu Netzwerkdaten (als JSON): +* Konvertierung von Ausgabedaten: Konvertierung von Python-Daten und -Typen zu Netzwerkdaten (als JSON): * Konvertieren von Python-Typen (`str`, `int`, `float`, `bool`, `list`, usw.). * `datetime`-Objekte. * `UUID`-Objekte. @@ -439,7 +439,7 @@ Für ein vollständigeres Beispiel, mit weiteren Funktionen, siehe das Dependency Injection**. +* Ein sehr leistungsfähiges und einfach zu bedienendes System für **Dependency Injection**. * Sicherheit und Authentifizierung, einschließlich Unterstützung für **OAuth2** mit **JWT-Tokens** und **HTTP Basic** Authentifizierung. * Fortgeschrittenere (aber ebenso einfache) Techniken zur Deklaration **tief verschachtelter JSON-Modelle** (dank Pydantic). * **GraphQL**-Integration mit Strawberry und anderen Bibliotheken. @@ -524,7 +524,7 @@ Verwendet von Starlette: * httpx – erforderlich, wenn Sie den `TestClient` verwenden möchten. * jinja2 – erforderlich, wenn Sie die Default-Template-Konfiguration verwenden möchten. -* python-multipart – erforderlich, wenn Sie Formulare mittels `request.form()` „parsen“ möchten. +* python-multipart – erforderlich, wenn Sie Formulare mittels `request.form()` „parsen“ möchten. Verwendet von FastAPI: diff --git a/docs/de/docs/project-generation.md b/docs/de/docs/project-generation.md index 62702d852..2c252933d 100644 --- a/docs/de/docs/project-generation.md +++ b/docs/de/docs/project-generation.md @@ -20,7 +20,7 @@ GitHub-Repository: „Typhinweise“ (auch „Typannotationen“ genannt). +Python hat Unterstützung für optionale „Typhinweise“ (auch „Typannotationen“ genannt). -Diese **„Typhinweise“** oder -Annotationen sind eine spezielle Syntax, die es erlaubt, den Typ einer Variablen zu deklarieren. +Diese **„Typhinweise“** oder -Annotationen sind eine spezielle Syntax, die es erlaubt, den Typ einer Variablen zu deklarieren. Durch das Deklarieren von Typen für Ihre Variablen können Editoren und Tools bessere Unterstützung bieten. @@ -22,7 +22,7 @@ Wenn Sie ein Python-Experte sind und bereits alles über Typhinweise wissen, üb Fangen wir mit einem einfachen Beispiel an: -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} Dieses Programm gibt aus: @@ -34,9 +34,9 @@ Die Funktion macht Folgendes: * Nimmt einen `first_name` und `last_name`. * Schreibt den ersten Buchstaben eines jeden Wortes groß, mithilfe von `title()`. -* Verkettet sie mit einem Leerzeichen in der Mitte. +* Verkettet sie mit einem Leerzeichen in der Mitte. -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### Es bearbeiten { #edit-it } @@ -78,7 +78,7 @@ Das war's. Das sind die „Typhinweise“: -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} Das ist nicht das gleiche wie das Deklarieren von Defaultwerten, wie es hier der Fall ist: @@ -106,7 +106,7 @@ Hier können Sie durch die Optionen blättern, bis Sie diejenige finden, bei der Sehen Sie sich diese Funktion an, sie hat bereits Typhinweise: -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} Da der Editor die Typen der Variablen kennt, erhalten Sie nicht nur Code-Vervollständigung, sondern auch eine Fehlerprüfung: @@ -114,7 +114,7 @@ Da der Editor die Typen der Variablen kennt, erhalten Sie nicht nur Code-Vervoll Jetzt, da Sie wissen, dass Sie das reparieren müssen, konvertieren Sie `age` mittels `str(age)` in einen String: -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} ## Deklarieren von Typen { #declaring-types } @@ -133,29 +133,32 @@ Zum Beispiel diese: * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} -### Generische Typen mit Typ-Parametern { #generic-types-with-type-parameters } +### `typing`-Modul { #typing-module } -Es gibt Datenstrukturen, die andere Werte enthalten können, wie etwa `dict`, `list`, `set` und `tuple`. Die inneren Werte können auch ihren eigenen Typ haben. +Für einige zusätzliche Anwendungsfälle müssen Sie möglicherweise Dinge aus dem Standardmodul `typing` importieren. Zum Beispiel, wenn Sie deklarieren möchten, dass etwas „jeden Typ“ haben kann, können Sie `Any` aus `typing` verwenden: -Diese Typen mit inneren Typen werden „**generische**“ Typen genannt. Es ist möglich, sie mit ihren inneren Typen zu deklarieren. +```python +from typing import Any -Um diese Typen und die inneren Typen zu deklarieren, können Sie Pythons Standardmodul `typing` verwenden. Es existiert speziell für die Unterstützung dieser Typhinweise. -#### Neuere Python-Versionen { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -Die Syntax, welche `typing` verwendet, ist **kompatibel** mit allen Versionen, von Python 3.6 aufwärts zu den neuesten, inklusive Python 3.9, Python 3.10, usw. +### Generische Typen { #generic-types } -Mit der Weiterentwicklung von Python kommen **neuere Versionen** heraus, mit verbesserter Unterstützung für Typannotationen, und in vielen Fällen müssen Sie gar nicht mehr das `typing`-Modul importieren, um Typannotationen zu schreiben. +Einige Typen können „Typ-Parameter“ in eckigen Klammern annehmen, um ihre inneren Typen zu definieren, z. B. eine „Liste von Strings“ würde als `list[str]` deklariert. -Wenn Sie eine neuere Python-Version für Ihr Projekt wählen können, werden Sie aus dieser zusätzlichen Vereinfachung Nutzen ziehen können. +Diese Typen, die Typ-Parameter annehmen können, werden **generische Typen** oder **Generics** genannt. -In der gesamten Dokumentation gibt es Beispiele, welche kompatibel mit unterschiedlichen Python-Versionen sind (wenn es Unterschiede gibt). +Sie können dieselben eingebauten Typen als Generics verwenden (mit eckigen Klammern und Typen darin): -Zum Beispiel bedeutet „**Python 3.6+**“, dass das Beispiel kompatibel mit Python 3.6 oder höher ist (inklusive 3.7, 3.8, 3.9, 3.10, usw.). Und „**Python 3.9+**“ bedeutet, es ist kompatibel mit Python 3.9 oder höher (inklusive 3.10, usw.). - -Wenn Sie über die **neueste Version von Python** verfügen, verwenden Sie die Beispiele für die neueste Version, diese werden die **beste und einfachste Syntax** haben, zum Beispiel, „**Python 3.10+**“. +* `list` +* `tuple` +* `set` +* `dict` #### Liste { #list } @@ -167,7 +170,7 @@ Als Typ nehmen Sie `list`. Da die Liste ein Typ ist, welcher innere Typen enthält, werden diese von eckigen Klammern umfasst: -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} /// info | Info @@ -193,7 +196,7 @@ Und trotzdem weiß der Editor, dass es sich um ein `str` handelt, und bietet ent Das Gleiche gilt für die Deklaration eines Tupels – `tuple` – und einer Menge – `set`: -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} Das bedeutet: @@ -208,7 +211,7 @@ Der erste Typ-Parameter ist für die Schlüssel des `dict`. Der zweite Typ-Parameter ist für die Werte des `dict`: -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} Das bedeutet: @@ -216,47 +219,23 @@ Das bedeutet: * Die Schlüssel dieses `dict` sind vom Typ `str` (z. B. die Namen der einzelnen Artikel). * Die Werte dieses `dict` sind vom Typ `float` (z. B. der Preis jedes Artikels). -#### Union { #union } +#### Union { #union } Sie können deklarieren, dass eine Variable einer von **verschiedenen Typen** sein kann, zum Beispiel ein `int` oder ein `str`. -In Python 3.6 und höher (inklusive Python 3.10) können Sie den `Union`-Typ von `typing` verwenden und die möglichen Typen innerhalb der eckigen Klammern auflisten. +Um das zu definieren, verwenden Sie den vertikalen Balken (`|`), um beide Typen zu trennen. -In Python 3.10 gibt es zusätzlich eine **neue Syntax**, die es erlaubt, die möglichen Typen getrennt von einem vertikalen Balken (`|`) aufzulisten. - -//// tab | Python 3.10+ +Das wird „Union“ genannt, weil die Variable etwas aus der Vereinigung dieser beiden Typmengen sein kann. ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -In beiden Fällen bedeutet das, dass `item` ein `int` oder ein `str` sein kann. +Das bedeutet, dass `item` ein `int` oder ein `str` sein könnte. #### Vielleicht `None` { #possibly-none } -Sie können deklarieren, dass ein Wert ein `str`, aber vielleicht auch `None` sein kann. - -In Python 3.6 und darüber (inklusive Python 3.10) können Sie das deklarieren, indem Sie `Optional` vom `typing` Modul importieren und verwenden. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -Wenn Sie `Optional[str]` anstelle von nur `str` verwenden, wird Ihr Editor Ihnen dabei helfen, Fehler zu erkennen, bei denen Sie annehmen könnten, dass ein Wert immer eine String (`str`) ist, obwohl er auch `None` sein könnte. - -`Optional[Something]` ist tatsächlich eine Abkürzung für `Union[Something, None]`, diese beiden sind äquivalent. - -Das bedeutet auch, dass Sie in Python 3.10 `Something | None` verwenden können: +Sie können deklarieren, dass ein Wert einen Typ haben könnte, wie `str`, dass er aber auch `None` sein könnte. //// tab | Python 3.10+ @@ -266,96 +245,7 @@ Das bedeutet auch, dass Sie in Python 3.10 `Something | None` verwenden können: //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ Alternative - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### `Union` oder `Optional` verwenden? { #using-union-or-optional } - -Wenn Sie eine Python-Version unterhalb 3.10 verwenden, hier ist mein sehr **subjektiver** Standpunkt dazu: - -* 🚨 Vermeiden Sie `Optional[SomeType]` -* Stattdessen ✨ **verwenden Sie `Union[SomeType, None]`** ✨. - -Beide sind äquivalent und im Hintergrund dasselbe, aber ich empfehle `Union` statt `Optional`, weil das Wort „**optional**“ impliziert, dass dieser Wert, zum Beispiel als Funktionsparameter, optional ist. Tatsächlich bedeutet es aber nur „Der Wert kann `None` sein“, selbst wenn der Wert nicht optional ist und benötigt wird. - -Ich denke, `Union[SomeType, None]` ist expliziter bezüglich seiner Bedeutung. - -Es geht nur um Worte und Namen. Aber diese Worte können beeinflussen, wie Sie und Ihre Teamkollegen über den Code denken. - -Nehmen wir zum Beispiel diese Funktion: - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -Der Parameter `name` ist definiert als `Optional[str]`, aber er ist **nicht optional**, Sie können die Funktion nicht ohne diesen Parameter aufrufen: - -```Python -say_hi() # Oh, nein, das löst einen Fehler aus! 😱 -``` - -Der `name` Parameter wird **immer noch benötigt** (nicht *optional*), weil er keinen Default-Wert hat. `name` akzeptiert aber dennoch `None` als Wert: - -```Python -say_hi(name=None) # Das funktioniert, None ist gültig 🎉 -``` - -Die gute Nachricht ist, dass Sie sich darüber keine Sorgen mehr machen müssen, wenn Sie Python 3.10 verwenden, da Sie einfach `|` verwenden können, um Vereinigungen von Typen zu definieren: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -Und dann müssen Sie sich nicht mehr um Namen wie `Optional` und `Union` kümmern. 😎 - -#### Generische Typen { #generic-types } - -Diese Typen, die Typ-Parameter in eckigen Klammern akzeptieren, werden **generische Typen** oder **Generics** genannt. - -//// tab | Python 3.10+ - -Sie können die eingebauten Typen als Generics verwenden (mit eckigen Klammern und Typen darin): - -* `list` -* `tuple` -* `set` -* `dict` - -Und ebenso wie bei früheren Python-Versionen, aus dem `typing`-Modul: - -* `Union` -* `Optional` -* ... und andere. - -In Python 3.10 können Sie als Alternative zu den Generics `Union` und `Optional` den vertikalen Balken (`|`) verwenden, um Vereinigungen von Typen zu deklarieren, das ist besser und einfacher. - -//// - -//// tab | Python 3.9+ - -Sie können die eingebauten Typen als Generics verwenden (mit eckigen Klammern und Typen darin): - -* `list` -* `tuple` -* `set` -* `dict` - -Und Generics aus dem `typing`-Modul: - -* `Union` -* `Optional` -* ... und andere. - -//// +Wenn Sie `str | None` anstelle von nur `str` verwenden, wird Ihr Editor Ihnen dabei helfen, Fehler zu erkennen, bei denen Sie annehmen könnten, dass ein Wert immer ein `str` ist, obwohl er auch `None` sein könnte. ### Klassen als Typen { #classes-as-types } @@ -363,11 +253,11 @@ Sie können auch eine Klasse als Typ einer Variablen deklarieren. Nehmen wir an, Sie haben eine Klasse `Person`, mit einem Namen: -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} Dann können Sie eine Variable vom Typ `Person` deklarieren: -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} Und wiederum bekommen Sie die volle Editor-Unterstützung: @@ -403,19 +293,13 @@ Um mehr über Erforderliche optionale Felder mehr erfahren. - -/// - ## Typhinweise mit Metadaten-Annotationen { #type-hints-with-metadata-annotations } -Python bietet auch die Möglichkeit, **zusätzliche Metadaten** in Typhinweisen unterzubringen, mittels `Annotated`. +Python bietet auch die Möglichkeit, **zusätzliche Metadaten** in Typhinweisen unterzubringen, mittels `Annotated`. -Seit Python 3.9 ist `Annotated` ein Teil der Standardbibliothek, Sie können es von `typing` importieren. +Sie können `Annotated` von `typing` importieren. -{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} Python selbst macht nichts mit `Annotated`. Für Editoren und andere Tools ist der Typ immer noch `str`. diff --git a/docs/de/docs/translation-banner.md b/docs/de/docs/translation-banner.md new file mode 100644 index 000000000..1801a0190 --- /dev/null +++ b/docs/de/docs/translation-banner.md @@ -0,0 +1,11 @@ +/// details | 🌐 Übersetzung durch KI und Menschen + +Diese Übersetzung wurde von KI erstellt, angeleitet von Menschen. 🤝 + +Sie könnte Fehler enthalten, etwa Missverständnisse des ursprünglichen Sinns oder unnatürliche Formulierungen, usw. 🤖 + +Sie können diese Übersetzung verbessern, indem Sie [uns helfen, die KI-LLM besser anzuleiten](https://fastapi.tiangolo.com/de/contributing/#translations). + +[Englische Version](ENGLISH_VERSION_URL) + +/// diff --git a/docs/de/docs/tutorial/background-tasks.md b/docs/de/docs/tutorial/background-tasks.md index 1d34430dc..950174d9c 100644 --- a/docs/de/docs/tutorial/background-tasks.md +++ b/docs/de/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ Hierzu zählen beispielsweise: Importieren Sie zunächst `BackgroundTasks` und definieren Sie einen Parameter in Ihrer *Pfadoperation-Funktion* mit der Typdeklaration `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} **FastAPI** erstellt für Sie das Objekt vom Typ `BackgroundTasks` und übergibt es als diesen Parameter. @@ -31,13 +31,13 @@ In diesem Fall schreibt die Taskfunktion in eine Datei (den Versand einer E-Mail Und da der Schreibvorgang nicht `async` und `await` verwendet, definieren wir die Funktion mit normalem `def`: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} ## Den Hintergrundtask hinzufügen { #add-the-background-task } Übergeben Sie innerhalb Ihrer *Pfadoperation-Funktion* Ihre Taskfunktion mit der Methode `.add_task()` an das *Hintergrundtasks*-Objekt: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} `.add_task()` erhält als Argumente: diff --git a/docs/de/docs/tutorial/bigger-applications.md b/docs/de/docs/tutorial/bigger-applications.md index d478d77c2..97fa9b81a 100644 --- a/docs/de/docs/tutorial/bigger-applications.md +++ b/docs/de/docs/tutorial/bigger-applications.md @@ -85,7 +85,7 @@ Sie können die *Pfadoperationen* für dieses Modul mit `APIRouter` erstellen. Sie importieren ihn und erstellen eine „Instanz“ auf die gleiche Weise wie mit der Klasse `FastAPI`: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} ### *Pfadoperationen* mit `APIRouter` { #path-operations-with-apirouter } @@ -93,7 +93,7 @@ Und dann verwenden Sie ihn, um Ihre *Pfadoperationen* zu deklarieren. Verwenden Sie ihn auf die gleiche Weise wie die Klasse `FastAPI`: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} Sie können sich `APIRouter` als eine „Mini-`FastAPI`“-Klasse vorstellen. @@ -117,7 +117,7 @@ Also fügen wir sie in ihr eigenes `dependencies`-Modul (`app/dependencies.py`) Wir werden nun eine einfache Abhängigkeit verwenden, um einen benutzerdefinierten `X-Token`-Header zu lesen: -{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | Tipp @@ -149,7 +149,7 @@ Wir wissen, dass alle *Pfadoperationen* in diesem Modul folgendes haben: Anstatt also alles zu jeder *Pfadoperation* hinzuzufügen, können wir es dem `APIRouter` hinzufügen. -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} Da der Pfad jeder *Pfadoperation* mit `/` beginnen muss, wie in: @@ -208,7 +208,7 @@ Und wir müssen die Abhängigkeitsfunktion aus dem Modul `app.dependencies` impo Daher verwenden wir einen relativen Import mit `..` für die Abhängigkeiten: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} #### Wie relative Importe funktionieren { #how-relative-imports-work } @@ -279,7 +279,7 @@ Wir fügen weder das Präfix `/items` noch `tags=["items"]` zu jeder *Pfadoperat Aber wir können immer noch _mehr_ `tags` hinzufügen, die auf eine bestimmte *Pfadoperation* angewendet werden, sowie einige zusätzliche `responses`, die speziell für diese *Pfadoperation* gelten: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | Tipp @@ -305,13 +305,13 @@ Sie importieren und erstellen wie gewohnt eine `FastAPI`-Klasse. Und wir können sogar [globale Abhängigkeiten](dependencies/global-dependencies.md){.internal-link target=_blank} deklarieren, die mit den Abhängigkeiten für jeden `APIRouter` kombiniert werden: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} ### Den `APIRouter` importieren { #import-the-apirouter } Jetzt importieren wir die anderen Submodule, die `APIRouter` haben: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} Da es sich bei den Dateien `app/routers/users.py` und `app/routers/items.py` um Submodule handelt, die Teil desselben Python-Packages `app` sind, können wir einen einzelnen Punkt `.` verwenden, um sie mit „relativen Imports“ zu importieren. @@ -374,13 +374,13 @@ würde der `router` von `users` den von `items` überschreiben und wir könnten Um also beide in derselben Datei verwenden zu können, importieren wir die Submodule direkt: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} ### Die `APIRouter` für `users` und `items` inkludieren { #include-the-apirouters-for-users-and-items } Inkludieren wir nun die `router` aus diesen Submodulen `users` und `items`: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} /// info | Info @@ -420,13 +420,13 @@ Sie enthält einen `APIRouter` mit einigen administrativen *Pfadoperationen*, di In diesem Beispiel wird es ganz einfach sein. Nehmen wir jedoch an, dass wir, da sie mit anderen Projekten in der Organisation geteilt wird, sie nicht ändern und kein `prefix`, `dependencies`, `tags`, usw. direkt zum `APIRouter` hinzufügen können: -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} Aber wir möchten immer noch ein benutzerdefiniertes `prefix` festlegen, wenn wir den `APIRouter` einbinden, sodass alle seine *Pfadoperationen* mit `/admin` beginnen, wir möchten es mit den `dependencies` sichern, die wir bereits für dieses Projekt haben, und wir möchten `tags` und `responses` hinzufügen. Wir können das alles deklarieren, ohne den ursprünglichen `APIRouter` ändern zu müssen, indem wir diese Parameter an `app.include_router()` übergeben: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} Auf diese Weise bleibt der ursprüngliche `APIRouter` unverändert, sodass wir dieselbe `app/internal/admin.py`-Datei weiterhin mit anderen Projekten in der Organisation teilen können. @@ -447,7 +447,7 @@ Wir können *Pfadoperationen* auch direkt zur `FastAPI`-App hinzufügen. Hier machen wir es ... nur um zu zeigen, dass wir es können 🤷: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} und es wird korrekt funktionieren, zusammen mit allen anderen *Pfadoperationen*, die mit `app.include_router()` hinzugefügt wurden. diff --git a/docs/de/docs/tutorial/body-fields.md b/docs/de/docs/tutorial/body-fields.md index b73d57d2d..4edb22478 100644 --- a/docs/de/docs/tutorial/body-fields.md +++ b/docs/de/docs/tutorial/body-fields.md @@ -44,7 +44,7 @@ Beachten Sie, wie jedes Attribut eines Modells mit einem Typ, Defaultwert und `F Sie können zusätzliche Information in `Field`, `Query`, `Body`, usw. deklarieren. Und es wird im generierten JSON-Schema untergebracht. -Sie werden später mehr darüber lernen, wie man zusätzliche Information unterbringt, wenn Sie lernen, Beispiele zu deklarieren. +Sie werden später in der Dokumentation mehr darüber lernen, wie man zusätzliche Information unterbringt, wenn Sie lernen, Beispiele zu deklarieren. /// warning | Achtung diff --git a/docs/de/docs/tutorial/body-multiple-params.md b/docs/de/docs/tutorial/body-multiple-params.md index daa48f23d..60a0ceefe 100644 --- a/docs/de/docs/tutorial/body-multiple-params.md +++ b/docs/de/docs/tutorial/body-multiple-params.md @@ -104,12 +104,6 @@ Da einfache Werte standardmäßig als Query-Parameter interpretiert werden, müs q: str | None = None ``` -Oder in Python 3.9: - -```Python -q: Union[str, None] = None -``` - Zum Beispiel: {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/de/docs/tutorial/body-nested-models.md b/docs/de/docs/tutorial/body-nested-models.md index 65a5d7c1d..52b00e036 100644 --- a/docs/de/docs/tutorial/body-nested-models.md +++ b/docs/de/docs/tutorial/body-nested-models.md @@ -163,7 +163,7 @@ images: list[Image] so wie in: -{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} ## Editor-Unterstützung überall { #editor-support-everywhere } @@ -193,7 +193,7 @@ Das schauen wir uns mal an. Im folgenden Beispiel akzeptieren Sie irgendein `dict`, solange es `int`-Schlüssel und `float`-Werte hat: -{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} /// tip | Tipp diff --git a/docs/de/docs/tutorial/body.md b/docs/de/docs/tutorial/body.md index cdf3122f2..e1214bc53 100644 --- a/docs/de/docs/tutorial/body.md +++ b/docs/de/docs/tutorial/body.md @@ -154,7 +154,7 @@ Die Funktionsparameter werden wie folgt erkannt: FastAPI weiß, dass der Wert von `q` nicht erforderlich ist, aufgrund des definierten Defaultwertes `= None`. -Das `str | None` (Python 3.10+) oder `Union` in `Union[str, None]` (Python 3.9+) wird von FastAPI nicht verwendet, um zu bestimmen, dass der Wert nicht erforderlich ist. FastAPI weiß, dass er nicht erforderlich ist, weil er einen Defaultwert von `= None` hat. +Das `str | None` wird von FastAPI nicht verwendet, um zu bestimmen, dass der Wert nicht erforderlich ist. FastAPI weiß, dass er nicht erforderlich ist, weil er einen Defaultwert von `= None` hat. Das Hinzufügen der Typannotationen ermöglicht jedoch Ihrem Editor, Ihnen eine bessere Unterstützung zu bieten und Fehler zu erkennen. diff --git a/docs/de/docs/tutorial/cookie-param-models.md b/docs/de/docs/tutorial/cookie-param-models.md index 25718bd33..81f7abb7d 100644 --- a/docs/de/docs/tutorial/cookie-param-models.md +++ b/docs/de/docs/tutorial/cookie-param-models.md @@ -46,7 +46,7 @@ Aber selbst wenn Sie die **Daten ausfüllen** und auf „Ausführen“ klicken, In einigen speziellen Anwendungsfällen (wahrscheinlich nicht sehr häufig) möchten Sie möglicherweise die Cookies, die Sie empfangen möchten, **einschränken**. -Ihre API hat jetzt die Macht, ihre eigene Cookie-Einwilligung zu kontrollieren. 🤪🍪 +Ihre API hat jetzt die Macht, ihre eigene Cookie-Einwilligung zu kontrollieren. 🤪🍪 Sie können die Modellkonfiguration von Pydantic verwenden, um `extra` Felder zu verbieten (`forbid`): @@ -54,9 +54,9 @@ Sie können die Modellkonfiguration von Pydantic verwenden, um `extra` Felder zu Wenn ein Client versucht, einige **zusätzliche Cookies** zu senden, erhält er eine **Error-Response**. -Arme Cookie-Banner, wie sie sich mühen, Ihre Einwilligung zu erhalten, dass die API sie ablehnen darf. 🍪 +Arme Cookie-Banner, wie sie sich mühen, Ihre Einwilligung zu erhalten, dass die API sie ablehnen darf. 🍪 -Wenn der Client beispielsweise versucht, ein `santa_tracker`-Cookie mit einem Wert von `good-list-please` zu senden, erhält der Client eine **Error-Response**, die ihm mitteilt, dass das `santa_tracker` Cookie nicht erlaubt ist: +Wenn der Client beispielsweise versucht, ein `santa_tracker`-Cookie mit einem Wert von `good-list-please` zu senden, erhält der Client eine **Error-Response**, die ihm mitteilt, dass das `santa_tracker` Cookie nicht erlaubt ist: ```json { @@ -73,4 +73,4 @@ Wenn der Client beispielsweise versucht, ein `santa_tracker`-Cookie mit einem We ## Zusammenfassung { #summary } -Sie können **Pydantic-Modelle** verwenden, um **Cookies** in **FastAPI** zu deklarieren. 😎 +Sie können **Pydantic-Modelle** verwenden, um **Cookies** in **FastAPI** zu deklarieren. 😎 diff --git a/docs/de/docs/tutorial/cors.md b/docs/de/docs/tutorial/cors.md index 81f0f3605..4e714f215 100644 --- a/docs/de/docs/tutorial/cors.md +++ b/docs/de/docs/tutorial/cors.md @@ -46,7 +46,7 @@ Sie können auch angeben, ob Ihr Backend erlaubt: * Bestimmte HTTP-Methoden (`POST`, `PUT`) oder alle mit der Wildcard `"*"`. * Bestimmte HTTP-Header oder alle mit der Wildcard `"*"`. -{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} Die von der `CORSMiddleware`-Implementierung verwendeten Defaultparameter sind standardmäßig restriktiv, daher müssen Sie bestimmte Origins, Methoden oder Header ausdrücklich aktivieren, damit Browser sie in einem Cross-Domain-Kontext verwenden dürfen. diff --git a/docs/de/docs/tutorial/debugging.md b/docs/de/docs/tutorial/debugging.md index 0d12877c1..cabaf5a3a 100644 --- a/docs/de/docs/tutorial/debugging.md +++ b/docs/de/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ Sie können den Debugger in Ihrem Editor verbinden, zum Beispiel mit Visual Stud Importieren und führen Sie `uvicorn` direkt in Ihrer FastAPI-Anwendung aus: -{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} ### Über `__name__ == "__main__"` { #about-name-main } diff --git a/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md index 7df0842eb..4fd8d092f 100644 --- a/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ Jetzt können Sie Ihre Abhängigkeit mithilfe dieser Klasse deklarieren. Beachten Sie, wie wir `CommonQueryParams` im obigen Code zweimal schreiben: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ nicht annotiert +//// tab | Python 3.10+ nicht annotiert /// tip | Tipp @@ -137,7 +137,7 @@ Aus diesem extrahiert FastAPI die deklarierten Parameter, und dieses ist es, was In diesem Fall hat das erste `CommonQueryParams` in: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.9+ nicht annotiert +//// tab | Python 3.10+ nicht annotiert /// tip | Tipp @@ -163,7 +163,7 @@ commons: CommonQueryParams ... Sie könnten tatsächlich einfach schreiben: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ nicht annotiert +//// tab | Python 3.10+ nicht annotiert /// tip | Tipp @@ -197,7 +197,7 @@ Es wird jedoch empfohlen, den Typ zu deklarieren, da Ihr Editor so weiß, was al Aber Sie sehen, dass wir hier etwas Codeduplizierung haben, indem wir `CommonQueryParams` zweimal schreiben: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ nicht annotiert +//// tab | Python 3.10+ nicht annotiert /// tip | Tipp @@ -225,7 +225,7 @@ In diesem speziellen Fall können Sie Folgendes tun: Anstatt zu schreiben: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ nicht annotiert +//// tab | Python 3.10+ nicht annotiert /// tip | Tipp @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ... schreiben Sie: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.9+ nicht annotiert +//// tab | Python 3.10+ nicht annotiert /// tip | Tipp diff --git a/docs/de/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/de/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 59c9fcf48..f63273f16 100644 --- a/docs/de/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/de/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -6,15 +6,15 @@ Oder die Abhängigkeit gibt keinen Wert zurück. Aber Sie müssen sie trotzdem ausführen/auflösen. -In diesen Fällen können Sie, anstatt einen Parameter der *Pfadoperation-Funktion* mit `Depends` zu deklarieren, eine `list`e von `dependencies` zum *Pfadoperation-Dekorator* hinzufügen. +In diesen Fällen können Sie, anstatt einen Parameter der *Pfadoperation-Funktion* mit `Depends` zu deklarieren, eine `list` von `dependencies` zum *Pfadoperation-Dekorator* hinzufügen. ## `dependencies` zum *Pfadoperation-Dekorator* hinzufügen { #add-dependencies-to-the-path-operation-decorator } Der *Pfadoperation-Dekorator* erhält ein optionales Argument `dependencies`. -Es sollte eine `list`e von `Depends()` sein: +Es sollte eine `list` von `Depends()` sein: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} Diese Abhängigkeiten werden auf die gleiche Weise wie normale Abhängigkeiten ausgeführt/aufgelöst. Aber ihr Wert (falls sie einen zurückgeben) wird nicht an Ihre *Pfadoperation-Funktion* übergeben. @@ -44,13 +44,13 @@ Sie können dieselben Abhängigkeits-*Funktionen* verwenden, die Sie normalerwei Sie können Anforderungen für einen Request (wie Header) oder andere Unterabhängigkeiten deklarieren: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} ### Exceptions auslösen { #raise-exceptions } Die Abhängigkeiten können Exceptions `raise`n, genau wie normale Abhängigkeiten: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} ### Rückgabewerte { #return-values } @@ -58,7 +58,7 @@ Und sie können Werte zurückgeben oder nicht, die Werte werden nicht verwendet. Sie können also eine normale Abhängigkeit (die einen Wert zurückgibt), die Sie bereits an anderer Stelle verwenden, wiederverwenden, und auch wenn der Wert nicht verwendet wird, wird die Abhängigkeit ausgeführt: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} ## Abhängigkeiten für eine Gruppe von *Pfadoperationen* { #dependencies-for-a-group-of-path-operations } diff --git a/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md index 0083e7e7e..c5f2acbae 100644 --- a/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,6 +1,6 @@ # Abhängigkeiten mit `yield` { #dependencies-with-yield } -FastAPI unterstützt Abhängigkeiten, die nach Abschluss einige zusätzliche Schritte ausführen. +FastAPI unterstützt Abhängigkeiten, die einige zusätzliche Schritte nach Abschluss ausführen. Verwenden Sie dazu `yield` statt `return` und schreiben Sie die zusätzlichen Schritte / den zusätzlichen Code danach. @@ -29,15 +29,15 @@ Sie könnten damit beispielsweise eine Datenbanksession erstellen und diese nach Nur der Code vor und einschließlich der `yield`-Anweisung wird ausgeführt, bevor eine Response erzeugt wird: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[2:4] *} Der ge`yield`ete Wert ist das, was in *Pfadoperationen* und andere Abhängigkeiten eingefügt wird: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[4] *} Der auf die `yield`-Anweisung folgende Code wird nach der Response ausgeführt: -{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[5:6] *} /// tip | Tipp @@ -57,7 +57,7 @@ Sie können also mit `except SomeException` diese bestimmte Exception innerhalb Auf die gleiche Weise können Sie `finally` verwenden, um sicherzustellen, dass die Exit-Schritte ausgeführt werden, unabhängig davon, ob eine Exception geworfen wurde oder nicht. -{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py310.py hl[3,5] *} ## Unterabhängigkeiten mit `yield` { #sub-dependencies-with-yield } @@ -67,7 +67,7 @@ Sie können Unterabhängigkeiten und „Bäume“ von Unterabhängigkeiten belie Beispielsweise kann `dependency_c` von `dependency_b` und `dependency_b` von `dependency_a` abhängen: -{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *} +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[6,14,22] *} Und alle können `yield` verwenden. @@ -75,7 +75,7 @@ In diesem Fall benötigt `dependency_c` zum Ausführen seines Exit-Codes, dass d Und wiederum benötigt `dependency_b` den Wert von `dependency_a` (hier `dep_a` genannt) für seinen Exit-Code. -{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *} +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[18:19,26:27] *} Auf die gleiche Weise könnten Sie einige Abhängigkeiten mit `yield` und einige andere Abhängigkeiten mit `return` haben, und alle können beliebig voneinander abhängen. @@ -109,7 +109,7 @@ Aber es ist für Sie da, wenn Sie es brauchen. 🤓 /// -{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *} +{* ../../docs_src/dependencies/tutorial008b_an_py310.py hl[18:22,31] *} Wenn Sie Exceptions abfangen und darauf basierend eine benutzerdefinierte Response erstellen möchten, erstellen Sie einen [benutzerdefinierten Exceptionhandler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. @@ -117,7 +117,7 @@ Wenn Sie Exceptions abfangen und darauf basierend eine benutzerdefinierte Respon Wenn Sie eine Exception mit `except` in einer Abhängigkeit mit `yield` abfangen und sie nicht erneut auslösen (oder eine neue Exception auslösen), kann FastAPI nicht feststellen, dass es eine Exception gab, genau so wie es bei normalem Python der Fall wäre: -{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *} +{* ../../docs_src/dependencies/tutorial008c_an_py310.py hl[15:16] *} In diesem Fall sieht der Client eine *HTTP 500 Internal Server Error*-Response, wie es sein sollte, da wir keine `HTTPException` oder Ähnliches auslösen, aber der Server hat **keine Logs** oder einen anderen Hinweis darauf, was der Fehler war. 😱 @@ -127,7 +127,7 @@ Wenn Sie eine Exception in einer Abhängigkeit mit `yield` abfangen, sollten Sie Sie können dieselbe Exception mit `raise` erneut auslösen: -{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *} +{* ../../docs_src/dependencies/tutorial008d_an_py310.py hl[17] *} Jetzt erhält der Client dieselbe *HTTP 500 Internal Server Error*-Response, aber der Server enthält unseren benutzerdefinierten `InternalError` in den Logs. 😎 @@ -190,7 +190,7 @@ Normalerweise wird der Exit-Code von Abhängigkeiten mit `yield` ausgeführt **n Wenn Sie aber wissen, dass Sie die Abhängigkeit nach der Rückkehr aus der *Pfadoperation-Funktion* nicht mehr benötigen, können Sie `Depends(scope="function")` verwenden, um FastAPI mitzuteilen, dass es die Abhängigkeit nach der Rückkehr aus der *Pfadoperation-Funktion* schließen soll, jedoch **bevor** die **Response gesendet wird**. -{* ../../docs_src/dependencies/tutorial008e_an_py39.py hl[12,16] *} +{* ../../docs_src/dependencies/tutorial008e_an_py310.py hl[12,16] *} `Depends()` erhält einen `scope`-Parameter, der sein kann: @@ -268,7 +268,7 @@ In Python können Sie Kontextmanager erstellen, indem Sie Dependency Injection** System. +**FastAPI** hat ein sehr mächtiges, aber intuitives **Abhängigkeitsinjektion** System. Es ist so konzipiert, sehr einfach zu verwenden zu sein und es jedem Entwickler sehr leicht zu machen, andere Komponenten mit **FastAPI** zu integrieren. diff --git a/docs/de/docs/tutorial/dependencies/sub-dependencies.md b/docs/de/docs/tutorial/dependencies/sub-dependencies.md index d72f820dc..b01cc80a7 100644 --- a/docs/de/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/de/docs/tutorial/dependencies/sub-dependencies.md @@ -58,11 +58,11 @@ query_extractor --> query_or_cookie_extractor --> read_query Wenn eine Ihrer Abhängigkeiten mehrmals für dieselbe *Pfadoperation* deklariert wird, beispielsweise wenn mehrere Abhängigkeiten eine gemeinsame Unterabhängigkeit haben, wird **FastAPI** diese Unterabhängigkeit nur einmal pro Request aufrufen. -Und es speichert den zurückgegebenen Wert in einem „Cache“ und übergibt diesen gecachten Wert an alle „Dependanten“, die ihn in diesem spezifischen Request benötigen, anstatt die Abhängigkeit mehrmals für denselben Request aufzurufen. +Und es speichert den zurückgegebenen Wert in einem „Cache“ und übergibt diesen gecachten Wert an alle „Dependanten“, die ihn in diesem spezifischen Request benötigen, anstatt die Abhängigkeit mehrmals für denselben Request aufzurufen. In einem fortgeschrittenen Szenario, bei dem Sie wissen, dass die Abhängigkeit bei jedem Schritt (möglicherweise mehrmals) in demselben Request aufgerufen werden muss, anstatt den zwischengespeicherten Wert zu verwenden, können Sie den Parameter `use_cache=False` festlegen, wenn Sie `Depends` verwenden: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python hl_lines="1" async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): @@ -71,7 +71,7 @@ async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_ca //// -//// tab | Python 3.9+ nicht annotiert +//// tab | Python 3.10+ nicht annotiert /// tip | Tipp diff --git a/docs/de/docs/tutorial/extra-models.md b/docs/de/docs/tutorial/extra-models.md index 889fdb9a3..4f55f2428 100644 --- a/docs/de/docs/tutorial/extra-models.md +++ b/docs/de/docs/tutorial/extra-models.md @@ -190,9 +190,9 @@ Aber wenn wir das in der Zuweisung `response_model=PlaneItem | CarItem` machen, Auf die gleiche Weise können Sie Responses von Listen von Objekten deklarieren. -Dafür verwenden Sie Pythons Standard-`typing.List` (oder nur `list` in Python 3.9 und höher): +Dafür verwenden Sie Pythons Standard-`list`: -{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} +{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *} ## Response mit beliebigem `dict` { #response-with-arbitrary-dict } @@ -200,9 +200,9 @@ Sie können auch eine Response deklarieren, die ein beliebiges `dict` zurückgib Dies ist nützlich, wenn Sie die gültigen Feld-/Attributnamen nicht im Voraus kennen (die für ein Pydantic-Modell benötigt werden würden). -In diesem Fall können Sie `typing.Dict` verwenden (oder nur `dict` in Python 3.9 und höher): +In diesem Fall können Sie `dict` verwenden: -{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} +{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *} ## Zusammenfassung { #recap } diff --git a/docs/de/docs/tutorial/first-steps.md b/docs/de/docs/tutorial/first-steps.md index 9505a0bdb..f02fb3d43 100644 --- a/docs/de/docs/tutorial/first-steps.md +++ b/docs/de/docs/tutorial/first-steps.md @@ -2,7 +2,7 @@ Die einfachste FastAPI-Datei könnte wie folgt aussehen: -{* ../../docs_src/first_steps/tutorial001_py39.py *} +{* ../../docs_src/first_steps/tutorial001_py310.py *} Kopieren Sie das in eine Datei `main.py`. @@ -183,7 +183,7 @@ Das war's! Jetzt können Sie Ihre App unter dieser URL aufrufen. ✨ ### Schritt 1: `FastAPI` importieren { #step-1-import-fastapi } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[1] *} `FastAPI` ist eine Python-Klasse, die die gesamte Funktionalität für Ihre API bereitstellt. @@ -197,7 +197,7 @@ Sie können alle Requests zuständig ist, die an: * den Pfad `/` -* unter der Verwendung der get-Operation gehen +* unter der Verwendung der get-Operation gehen /// info | `@decorator` Info @@ -320,7 +320,7 @@ Das ist unsere „**Pfadoperation-Funktion**“: * **Operation**: ist `get`. * **Funktion**: ist die Funktion direkt unter dem „Dekorator“ (unter `@app.get("/")`). -{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[7] *} Dies ist eine Python-Funktion. @@ -332,7 +332,7 @@ In diesem Fall handelt es sich um eine `async`-Funktion. Sie könnten sie auch als normale Funktion anstelle von `async def` definieren: -{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py310.py hl[7] *} /// note | Hinweis @@ -342,7 +342,7 @@ Wenn Sie den Unterschied nicht kennen, lesen Sie [Async: *„In Eile?“*](../as ### Schritt 5: den Inhalt zurückgeben { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[8] *} Sie können ein `dict`, eine `list`, einzelne Werte wie `str`, `int`, usw. zurückgeben. diff --git a/docs/de/docs/tutorial/handling-errors.md b/docs/de/docs/tutorial/handling-errors.md index d890b4462..0cb0de2ca 100644 --- a/docs/de/docs/tutorial/handling-errors.md +++ b/docs/de/docs/tutorial/handling-errors.md @@ -25,7 +25,7 @@ Um HTTP-Exceptionhandler mit den gleichen Exception-Werkzeugen von Starlette hinzufügen. +Sie können benutzerdefinierte Exceptionhandler mit den gleichen Exception-Werkzeugen von Starlette hinzufügen. Angenommen, Sie haben eine benutzerdefinierte Exception `UnicornException`, die Sie (oder eine Bibliothek, die Sie verwenden) `raise`n könnten. @@ -89,7 +89,7 @@ Und Sie möchten diese Exception global mit FastAPI handhaben. Sie könnten einen benutzerdefinierten Exceptionhandler mit `@app.exception_handler()` hinzufügen: -{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *} +{* ../../docs_src/handling_errors/tutorial003_py310.py hl[5:7,13:18,24] *} Hier, wenn Sie `/unicorns/yolo` anfordern, wird die *Pfadoperation* eine `UnicornException` `raise`n. @@ -127,7 +127,7 @@ Um diesen zu überschreiben, importieren Sie den `RequestValidationError` und ve Der Exceptionhandler erhält einen `Request` und die Exception. -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[2,14:19] *} Wenn Sie nun zu `/items/foo` gehen, erhalten Sie anstelle des standardmäßigen JSON-Fehlers mit: @@ -159,7 +159,7 @@ Auf die gleiche Weise können Sie den `HTTPException`-Handler überschreiben. Zum Beispiel könnten Sie eine Klartext-Response statt JSON für diese Fehler zurückgeben wollen: -{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *} +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[3:4,9:11,25] *} /// note | Technische Details @@ -183,7 +183,7 @@ Der `RequestValidationError` enthält den empfangenen `body` mit den ungültigen Sie könnten diesen während der Entwicklung Ihrer Anwendung verwenden, um den Body zu loggen und zu debuggen, ihn an den Benutzer zurückzugeben usw. -{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial005_py310.py hl[14] *} Versuchen Sie nun, einen ungültigen Artikel zu senden: @@ -239,6 +239,6 @@ from starlette.exceptions import HTTPException as StarletteHTTPException Wenn Sie die Exception zusammen mit den gleichen Default-Exceptionhandlern von **FastAPI** verwenden möchten, können Sie die Default-Exceptionhandler aus `fastapi.exception_handlers` importieren und wiederverwenden: -{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *} +{* ../../docs_src/handling_errors/tutorial006_py310.py hl[2:5,15,21] *} In diesem Beispiel geben Sie nur den Fehler mit einer sehr ausdrucksstarken Nachricht aus, aber Sie verstehen das Prinzip. Sie können die Exception verwenden und dann einfach die Default-Exceptionhandler wiederverwenden. diff --git a/docs/de/docs/tutorial/metadata.md b/docs/de/docs/tutorial/metadata.md index ee88a21d6..f5999a667 100644 --- a/docs/de/docs/tutorial/metadata.md +++ b/docs/de/docs/tutorial/metadata.md @@ -18,7 +18,7 @@ Sie können die folgenden Felder festlegen, die in der OpenAPI-Spezifikation und Sie können diese wie folgt setzen: -{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} +{* ../../docs_src/metadata/tutorial001_py310.py hl[3:16, 19:32] *} /// tip | Tipp @@ -36,7 +36,7 @@ Seit OpenAPI 3.1.0 und FastAPI 0.99.0 können Sie die `license_info` auch mit ei Zum Beispiel: -{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} +{* ../../docs_src/metadata/tutorial001_1_py310.py hl[31] *} ## Metadaten für Tags { #metadata-for-tags } @@ -58,7 +58,7 @@ Versuchen wir es mit einem Beispiel mit Tags für `users` und `items`. Erstellen Sie Metadaten für Ihre Tags und übergeben Sie diese an den Parameter `openapi_tags`: -{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[3:16,18] *} Beachten Sie, dass Sie Markdown innerhalb der Beschreibungen verwenden können. Zum Beispiel wird „login“ in Fettschrift (**login**) und „fancy“ in Kursivschrift (_fancy_) angezeigt. @@ -72,7 +72,7 @@ Sie müssen nicht für alle von Ihnen verwendeten Tags Metadaten hinzufügen. Verwenden Sie den Parameter `tags` mit Ihren *Pfadoperationen* (und `APIRouter`n), um diese verschiedenen Tags zuzuweisen: -{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py310.py hl[21,26] *} /// info | Info @@ -100,7 +100,7 @@ Sie können das aber mit dem Parameter `openapi_url` konfigurieren. Um beispielsweise festzulegen, dass es unter `/api/v1/openapi.json` bereitgestellt wird: -{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py310.py hl[3] *} Wenn Sie das OpenAPI-Schema vollständig deaktivieren möchten, können Sie `openapi_url=None` festlegen, wodurch auch die Dokumentationsbenutzeroberflächen deaktiviert werden, die es verwenden. @@ -117,4 +117,4 @@ Sie können die beiden enthaltenen Dokumentationsbenutzeroberflächen konfigurie Um beispielsweise Swagger UI so einzustellen, dass sie unter `/documentation` bereitgestellt wird, und ReDoc zu deaktivieren: -{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} +{* ../../docs_src/metadata/tutorial003_py310.py hl[3] *} diff --git a/docs/de/docs/tutorial/middleware.md b/docs/de/docs/tutorial/middleware.md index 540a18c4d..116f87d23 100644 --- a/docs/de/docs/tutorial/middleware.md +++ b/docs/de/docs/tutorial/middleware.md @@ -31,7 +31,7 @@ Die Middleware-Funktion erhält: * Dann gibt es die von der entsprechenden *Pfadoperation* generierte `response` zurück. * Sie können die `response` dann weiter modifizieren, bevor Sie sie zurückgeben. -{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[8:9,11,14] *} /// tip | Tipp @@ -57,7 +57,7 @@ Und auch nachdem die `response` generiert wurde, bevor sie zurückgegeben wird. Sie könnten beispielsweise einen benutzerdefinierten Header `X-Process-Time` hinzufügen, der die Zeit in Sekunden enthält, die benötigt wurde, um den Request zu verarbeiten und eine Response zu generieren: -{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *} +{* ../../docs_src/middleware/tutorial001_py310.py hl[10,12:13] *} /// tip | Tipp diff --git a/docs/de/docs/tutorial/path-operation-configuration.md b/docs/de/docs/tutorial/path-operation-configuration.md index a06c85e57..48d6d6603 100644 --- a/docs/de/docs/tutorial/path-operation-configuration.md +++ b/docs/de/docs/tutorial/path-operation-configuration.md @@ -46,7 +46,7 @@ In diesem Fall macht es Sinn, die Tags in einem `Enum` zu speichern. **FastAPI** unterstützt das auf die gleiche Weise wie einfache Strings: -{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *} +{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *} ## Zusammenfassung und Beschreibung { #summary-and-description } @@ -56,7 +56,7 @@ Sie können eine `summary` und eine Docstring der Funktion deklarieren, und **FastAPI** wird sie daraus auslesen. +Da Beschreibungen oft mehrere Zeilen lang sind, können Sie die Beschreibung der *Pfadoperation* im Docstring der Funktion deklarieren, und **FastAPI** wird sie daraus auslesen. Sie können Markdown im Docstring schreiben, es wird korrekt interpretiert und angezeigt (unter Berücksichtigung der Einrückung des Docstring). @@ -90,9 +90,9 @@ Daher, wenn Sie keine vergeben, wird **FastAPI** automatisch eine für „Erfolg ## Eine *Pfadoperation* deprecaten { #deprecate-a-path-operation } -Wenn Sie eine *Pfadoperation* als deprecatet kennzeichnen möchten, ohne sie zu entfernen, fügen Sie den Parameter `deprecated` hinzu: +Wenn Sie eine *Pfadoperation* als deprecatet kennzeichnen möchten, ohne sie zu entfernen, fügen Sie den Parameter `deprecated` hinzu: -{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} Sie wird in der interaktiven Dokumentation gut sichtbar als deprecatet markiert werden: diff --git a/docs/de/docs/tutorial/path-params-numeric-validations.md b/docs/de/docs/tutorial/path-params-numeric-validations.md index 8b52e8b42..5147a7fbc 100644 --- a/docs/de/docs/tutorial/path-params-numeric-validations.md +++ b/docs/de/docs/tutorial/path-params-numeric-validations.md @@ -54,11 +54,11 @@ Für **FastAPI** spielt es keine Rolle. Es erkennt die Parameter anhand ihrer Na Sie können Ihre Funktion also so deklarieren: -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} Aber bedenken Sie, dass Sie dieses Problem nicht haben, wenn Sie `Annotated` verwenden, da es nicht darauf ankommt, dass Sie keine Funktionsparameter-Defaultwerte für `Query()` oder `Path()` verwenden. -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## Die Parameter sortieren, wie Sie möchten: Tricks { #order-the-parameters-as-you-need-tricks } @@ -83,13 +83,13 @@ Wenn Sie: Python wird nichts mit diesem `*` machen, aber es wird wissen, dass alle folgenden Parameter als Schlüsselwortargumente (Schlüssel-Wert-Paare) verwendet werden sollen, auch bekannt als kwargs. Selbst wenn diese keinen Defaultwert haben. -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### Besser mit `Annotated` { #better-with-annotated } Bedenken Sie, dass Sie, wenn Sie `Annotated` verwenden, da Sie keine Funktionsparameter-Defaultwerte verwenden, dieses Problem nicht haben werden und wahrscheinlich nicht `*` verwenden müssen. -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## Validierung von Zahlen: Größer oder gleich { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ Mit `Query` und `Path` (und anderen, die Sie später sehen werden) können Sie Z Hier, mit `ge=1`, muss `item_id` eine ganze Zahl sein, die „`g`reater than or `e`qual to“ (größer oder gleich) `1` ist. -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## Validierung von Zahlen: Größer und kleiner oder gleich { #number-validations-greater-than-and-less-than-or-equal } @@ -106,7 +106,7 @@ Das Gleiche gilt für: * `gt`: `g`reater `t`han (größer als) * `le`: `l`ess than or `e`qual (kleiner oder gleich) -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## Validierung von Zahlen: Floats, größer und kleiner { #number-validations-floats-greater-than-and-less-than } @@ -118,7 +118,7 @@ Also wäre `0.5` ein gültiger Wert. Aber `0.0` oder `0` nicht. Und das Gleiche gilt für lt. -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## Zusammenfassung { #recap } diff --git a/docs/de/docs/tutorial/path-params.md b/docs/de/docs/tutorial/path-params.md index 1de497315..41f6aea55 100644 --- a/docs/de/docs/tutorial/path-params.md +++ b/docs/de/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Sie können Pfad-„Parameter“ oder -„Variablen“ mit der gleichen Syntax deklarieren, welche in Python-Formatstrings verwendet wird: -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} Der Wert des Pfad-Parameters `item_id` wird Ihrer Funktion als das Argument `item_id` übergeben. @@ -16,7 +16,7 @@ Wenn Sie dieses Beispiel ausführen und auf Konversion { #data-conversion } +## Daten-Konversion { #data-conversion } Wenn Sie dieses Beispiel ausführen und Ihren Browser unter http://127.0.0.1:8000/items/3 öffnen, sehen Sie als Response: @@ -38,7 +38,7 @@ Wenn Sie dieses Beispiel ausführen und Ihren Browser unter Request automatisch „parsen“. +Sprich, mit dieser Typdeklaration wird **FastAPI** den „parsen“. /// @@ -118,13 +118,13 @@ Und Sie haben auch einen Pfad `/users/{user_id}`, um Daten über einen spezifisc Weil *Pfadoperationen* in ihrer Reihenfolge ausgewertet werden, müssen Sie sicherstellen, dass der Pfad `/users/me` vor `/users/{user_id}` deklariert wurde: -{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *} Ansonsten würde der Pfad für `/users/{user_id}` auch `/users/me` auswerten, und annehmen, dass ein Parameter `user_id` mit dem Wert `"me"` übergeben wurde. Sie können eine Pfadoperation auch nicht erneut definieren: -{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *} Die erste Definition wird immer verwendet werden, da ihr Pfad zuerst übereinstimmt. @@ -140,12 +140,11 @@ Indem Sie von `str` erben, weiß die API-Dokumentation, dass die Werte vom Typ ` Erstellen Sie dann Klassen-Attribute mit festgelegten Werten, welches die erlaubten Werte sein werden: -{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} - +{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *} /// tip | Tipp -Falls Sie sich fragen, was „AlexNet“, „ResNet“ und „LeNet“ ist, das sind Namen von Modellen für maschinelles Lernen. +Falls Sie sich fragen, was „AlexNet“, „ResNet“ und „LeNet“ ist, das sind Namen von Modellen für maschinelles Lernen. /// @@ -153,7 +152,7 @@ Falls Sie sich fragen, was „AlexNet“, „ResNet“ und „LeNet“ ist, das Dann erstellen Sie einen *Pfad-Parameter*, der als Typ die gerade erstellte Enum-Klasse hat (`ModelName`): -{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[16] *} ### Die API-Dokumentation testen { #check-the-docs } @@ -169,13 +168,13 @@ Der *Pfad-Parameter* wird ein *parsen" +* Daten „parsen“ * Datenvalidierung * API-Annotationen und automatische Dokumentation diff --git a/docs/de/docs/tutorial/query-params-str-validations.md b/docs/de/docs/tutorial/query-params-str-validations.md index 8977dbcd5..865d10d13 100644 --- a/docs/de/docs/tutorial/query-params-str-validations.md +++ b/docs/de/docs/tutorial/query-params-str-validations.md @@ -39,7 +39,7 @@ Stellen Sie sicher, dass Sie [die FastAPI-Version aktualisieren](../deployment/v /// -## Verwenden von `Annotated` im Typ für den `q`-Parameter { #use-annotated-in-the-type-for-the-q-parameter } +## `Annotated` im Typ für den `q`-Parameter verwenden { #use-annotated-in-the-type-for-the-q-parameter } Erinnern Sie sich, dass ich Ihnen zuvor in [Python-Typen-Intro](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank} gesagt habe, dass `Annotated` verwendet werden kann, um Metadaten zu Ihren Parametern hinzuzufügen? @@ -47,40 +47,16 @@ Jetzt ist es soweit, dies mit FastAPI zu verwenden. 🚀 Wir hatten diese Typannotation: -//// tab | Python 3.10+ - ```Python q: str | None = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Union[str, None] = None -``` - -//// - Was wir tun werden, ist, dies mit `Annotated` zu wrappen, sodass es zu: -//// tab | Python 3.10+ - ```Python q: Annotated[str | None] = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Annotated[Union[str, None]] = None -``` - -//// - Beide dieser Versionen bedeuten dasselbe: `q` ist ein Parameter, der ein `str` oder `None` sein kann, und standardmäßig ist er `None`. Jetzt springen wir zu den spannenden Dingen. 🎉 @@ -109,7 +85,7 @@ FastAPI wird nun: ## Alternative (alt): `Query` als Defaultwert { #alternative-old-query-as-the-default-value } -Frühere Versionen von FastAPI (vor 0.95.0) erforderten, dass Sie `Query` als den Defaultwert Ihres Parameters verwendeten, anstatt es innerhalb von `Annotated` zu platzieren. Es besteht eine hohe Wahrscheinlichkeit, dass Sie Code sehen, der es so verwendet, also werde ich es Ihnen erklären. +Frühere Versionen von FastAPI (vor 0.95.0) erforderten, dass Sie `Query` als den Defaultwert Ihres Parameters verwendeten, anstatt es innerhalb von `Annotated` zu platzieren. Es besteht eine hohe Wahrscheinlichkeit, dass Sie Code sehen, der es so verwendet, also werde ich es Ihnen erklären. /// tip | Tipp @@ -191,7 +167,7 @@ Sie können auch einen `min_length`-Parameter hinzufügen: ## Reguläre Ausdrücke hinzufügen { #add-regular-expressions } -Sie können einen regulären Ausdruck `pattern` definieren, mit dem der Parameter übereinstimmen muss: +Sie können einen regulären Ausdruck `pattern` definieren, mit dem der Parameter übereinstimmen muss: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -211,7 +187,7 @@ Natürlich können Sie Defaultwerte verwenden, die nicht `None` sind. Nehmen wir an, Sie möchten, dass der `q` Query-Parameter eine `min_length` von `3` hat und einen Defaultwert von `"fixedquery"`: -{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} /// note | Hinweis @@ -241,7 +217,7 @@ q: Annotated[str | None, Query(min_length=3)] = None Wenn Sie einen Wert als erforderlich deklarieren müssen, während Sie `Query` verwenden, deklarieren Sie einfach keinen Defaultwert: -{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} ### Erforderlich, kann `None` sein { #required-can-be-none } @@ -292,7 +268,7 @@ Die interaktive API-Dokumentation wird entsprechend aktualisiert, um mehrere Wer Sie können auch eine Default-`list` von Werten definieren, wenn keine bereitgestellt werden: -{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} Wenn Sie zu: @@ -315,7 +291,7 @@ gehen, wird der Default für `q` sein: `["foo", "bar"]`, und Ihre Response wird Sie können auch `list` direkt verwenden, anstelle von `list[str]`: -{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} /// note | Hinweis @@ -371,7 +347,7 @@ Dann können Sie ein `alias` deklarieren, und dieser Alias wird verwendet, um de Nehmen wir an, Ihnen gefällt dieser Parameter nicht mehr. -Sie müssen ihn eine Weile dort belassen, da es Clients gibt, die ihn verwenden, aber Sie möchten, dass die Dokumentation ihn klar als deprecatet anzeigt. +Sie müssen ihn eine Weile dort belassen, da es Clients gibt, die ihn verwenden, aber Sie möchten, dass die Dokumentation ihn klar als deprecatet anzeigt. Dann übergeben Sie den Parameter `deprecated=True` an `Query`: @@ -393,7 +369,7 @@ Es kann Fälle geben, in denen Sie eine **benutzerdefinierte Validierung** durch In diesen Fällen können Sie eine **benutzerdefinierte Validierungsfunktion** verwenden, die nach der normalen Validierung angewendet wird (z. B. nach der Validierung, dass der Wert ein `str` ist). -Sie können dies mit Pydantic's `AfterValidator` innerhalb von `Annotated` erreichen. +Sie können dies mit Pydantics `AfterValidator` innerhalb von `Annotated` erreichen. /// tip | Tipp @@ -401,7 +377,7 @@ Pydantic unterstützt auch ISBN-Buchnummer oder mit `imdb-` für eine IMDB-Film-URL-ID beginnt: +Zum Beispiel überprüft dieser benutzerdefinierte Validator, ob die Artikel-ID mit `isbn-` für eine ISBN-Buchnummer oder mit `imdb-` für eine IMDB-Film-URL-ID beginnt: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -435,7 +411,7 @@ Haben Sie bemerkt? Eine Zeichenkette mit `value.startswith()` kann ein Tuple üb #### Ein zufälliges Item { #a-random-item } -Mit `data.items()` erhalten wir ein iterierbares Objekt mit Tupeln, die Schlüssel und Wert für jedes Dictionary-Element enthalten. +Mit `data.items()` erhalten wir ein iterierbares Objekt mit Tupeln, die Schlüssel und Wert für jedes Dictionary-Element enthalten. Wir konvertieren dieses iterierbare Objekt mit `list(data.items())` in eine richtige `list`. diff --git a/docs/de/docs/tutorial/query-params.md b/docs/de/docs/tutorial/query-params.md index 05ed3bc83..5a2a2a012 100644 --- a/docs/de/docs/tutorial/query-params.md +++ b/docs/de/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ Wenn Sie in Ihrer Funktion andere Parameter deklarieren, die nicht Teil der Pfad-Parameter sind, dann werden diese automatisch als „Query“-Parameter interpretiert. -{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} Die Query ist die Menge von Schlüssel-Wert-Paaren, die nach dem `?` in einer URL folgen und durch `&`-Zeichen getrennt sind. @@ -24,7 +24,7 @@ Aber wenn Sie sie mit Python-Typen deklarieren (im obigen Beispiel als `int`), w Die gleichen Prozesse, die für Pfad-Parameter gelten, werden auch auf Query-Parameter angewendet: * Editor Unterstützung (natürlich) -* Daten-„Parsen“ +* Daten-„Parsen“ * Datenvalidierung * Automatische Dokumentation @@ -127,7 +127,7 @@ Wenn Sie keinen spezifischen Wert haben wollen, sondern der Parameter einfach op Aber wenn Sie wollen, dass ein Query-Parameter erforderlich ist, vergeben Sie einfach keinen Defaultwert: -{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} Hier ist `needy` ein erforderlicher Query-Parameter vom Typ `str`. diff --git a/docs/de/docs/tutorial/request-files.md b/docs/de/docs/tutorial/request-files.md index 0aee898b9..5b5f72d6d 100644 --- a/docs/de/docs/tutorial/request-files.md +++ b/docs/de/docs/tutorial/request-files.md @@ -20,13 +20,13 @@ Das liegt daran, dass hochgeladene Dateien als „Formulardaten“ gesendet werd Importieren Sie `File` und `UploadFile` von `fastapi`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} ## `File`-Parameter definieren { #define-file-parameters } Erstellen Sie Datei-Parameter, so wie Sie es auch mit `Body` und `Form` machen würden: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} /// info | Info @@ -54,7 +54,7 @@ Aber es gibt viele Fälle, in denen Sie davon profitieren, `UploadFile` zu verwe Definieren Sie einen Datei-Parameter mit dem Typ `UploadFile`: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} `UploadFile` zu verwenden, hat mehrere Vorzüge gegenüber `bytes`: @@ -143,7 +143,7 @@ Sie können eine Datei optional machen, indem Sie Standard-Typannotationen verwe Sie können auch `File()` mit `UploadFile` verwenden, um zum Beispiel zusätzliche Metadaten zu setzen: -{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} ## Mehrere Datei-Uploads { #multiple-file-uploads } @@ -153,7 +153,7 @@ Diese werden demselben Formularfeld zugeordnet, welches mit den Formulardaten ge Um das zu machen, deklarieren Sie eine Liste von `bytes` oder `UploadFile`s: -{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} Sie erhalten, wie deklariert, eine `list` von `bytes` oder `UploadFile`s. @@ -169,7 +169,7 @@ Sie können auch `from starlette.responses import HTMLResponse` verwenden. Und so wie zuvor können Sie `File()` verwenden, um zusätzliche Parameter zu setzen, sogar für `UploadFile`: -{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} ## Zusammenfassung { #recap } diff --git a/docs/de/docs/tutorial/request-form-models.md b/docs/de/docs/tutorial/request-form-models.md index fbc6c094c..262a14d6d 100644 --- a/docs/de/docs/tutorial/request-form-models.md +++ b/docs/de/docs/tutorial/request-form-models.md @@ -24,7 +24,7 @@ Dies wird seit FastAPI Version `0.113.0` unterstützt. 🤓 Sie müssen nur ein **Pydantic-Modell** mit den Feldern deklarieren, die Sie als **Formularfelder** erhalten möchten, und dann den Parameter als `Form` deklarieren: -{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} **FastAPI** wird die Daten für **jedes Feld** aus den **Formulardaten** im Request **extrahieren** und Ihnen das von Ihnen definierte Pydantic-Modell übergeben. @@ -48,7 +48,7 @@ Dies wird seit FastAPI Version `0.114.0` unterstützt. 🤓 Sie können die Modellkonfiguration von Pydantic verwenden, um jegliche `extra` Felder zu `verbieten`: -{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} Wenn ein Client versucht, einige zusätzliche Daten zu senden, erhält er eine **Error-Response**. diff --git a/docs/de/docs/tutorial/request-forms-and-files.md b/docs/de/docs/tutorial/request-forms-and-files.md index cda38bcc2..f779ff509 100644 --- a/docs/de/docs/tutorial/request-forms-and-files.md +++ b/docs/de/docs/tutorial/request-forms-and-files.md @@ -16,13 +16,13 @@ $ pip install python-multipart ## `File` und `Form` importieren { #import-file-and-form } -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} ## `File` und `Form`-Parameter definieren { #define-file-and-form-parameters } Erstellen Sie Datei- und Formularparameter, so wie Sie es auch mit `Body` oder `Query` machen würden: -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} Die Datei- und Formularfelder werden als Formulardaten hochgeladen, und Sie erhalten diese Dateien und Formularfelder. diff --git a/docs/de/docs/tutorial/request-forms.md b/docs/de/docs/tutorial/request-forms.md index 5c2ace67b..4a36dba72 100644 --- a/docs/de/docs/tutorial/request-forms.md +++ b/docs/de/docs/tutorial/request-forms.md @@ -18,17 +18,17 @@ $ pip install python-multipart Importieren Sie `Form` von `fastapi`: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} ## `Form`-Parameter definieren { #define-form-parameters } Erstellen Sie Formular-Parameter, so wie Sie es auch mit `Body` und `Query` machen würden: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} Zum Beispiel stellt eine der Möglichkeiten, die OAuth2-Spezifikation zu verwenden (genannt „password flow“), die Bedingung, einen `username` und ein `password` als Formularfelder zu senden. -Die Spec erfordert, dass die Felder exakt `username` und `password` genannt werden und als Formularfelder, nicht JSON, gesendet werden. +Die Spezifikation erfordert, dass die Felder exakt `username` und `password` genannt werden und als Formularfelder, nicht JSON, gesendet werden. Mit `Form` haben Sie die gleichen Konfigurationsmöglichkeiten wie mit `Body` (und `Query`, `Path`, `Cookie`), inklusive Validierung, Beispielen, einem Alias (z. B. `user-name` statt `username`), usw. diff --git a/docs/de/docs/tutorial/response-model.md b/docs/de/docs/tutorial/response-model.md index f759bb257..99d194fe1 100644 --- a/docs/de/docs/tutorial/response-model.md +++ b/docs/de/docs/tutorial/response-model.md @@ -183,7 +183,7 @@ Es kann Fälle geben, bei denen Sie etwas zurückgeben, das kein gültiges Pydan Der häufigste Anwendungsfall ist, wenn Sie [eine Response direkt zurückgeben, wie es später im Handbuch für fortgeschrittene Benutzer erläutert wird](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} Dieser einfache Anwendungsfall wird automatisch von FastAPI gehandhabt, weil die Annotation des Rückgabetyps die Klasse (oder eine Unterklasse von) `Response` ist. @@ -193,7 +193,7 @@ Und Tools werden auch glücklich sein, weil sowohl `RedirectResponse` als auch ` Sie können auch eine Unterklasse von `Response` in der Typannotation verwenden. -{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} Das wird ebenfalls funktionieren, weil `RedirectResponse` eine Unterklasse von `Response` ist, und FastAPI sich um diesen einfachen Anwendungsfall automatisch kümmert. @@ -201,7 +201,7 @@ Das wird ebenfalls funktionieren, weil `RedirectResponse` eine Unterklasse von ` Aber wenn Sie ein beliebiges anderes Objekt zurückgeben, das kein gültiger Pydantic-Typ ist (z. B. ein Datenbank-Objekt), und Sie annotieren es so in der Funktion, wird FastAPI versuchen, ein Pydantic-Responsemodell von dieser Typannotation zu erstellen, und scheitern. -Das gleiche wird passieren, wenn Sie eine Union mehrerer Typen haben, und einer oder mehrere sind nicht gültige Pydantic-Typen. Zum Beispiel funktioniert folgendes nicht 💥: +Das gleiche wird passieren, wenn Sie eine Union mehrerer Typen haben, und einer oder mehrere sind nicht gültige Pydantic-Typen. Zum Beispiel funktioniert folgendes nicht 💥: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} diff --git a/docs/de/docs/tutorial/response-status-code.md b/docs/de/docs/tutorial/response-status-code.md index fd17c9933..2ed74f590 100644 --- a/docs/de/docs/tutorial/response-status-code.md +++ b/docs/de/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ Genauso wie Sie ein Responsemodell angeben können, können Sie auch den HTTP-St * `@app.delete()` * usw. -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} /// note | Hinweis @@ -66,7 +66,7 @@ Kurz gefasst: /// tip | Tipp -Um mehr über die einzelnen Statuscodes zu erfahren und welcher wofür verwendet wird, sehen Sie sich die MDN Dokumentation über HTTP-Statuscodes an. +Um mehr über die einzelnen Statuscodes zu erfahren und welcher wofür verwendet wird, sehen Sie sich die MDN Dokumentation über HTTP-Statuscodes an. /// @@ -74,7 +74,7 @@ Um mehr über die einzelnen Statuscodes zu erfahren und welcher wofür verwendet Lassen Sie uns das vorherige Beispiel noch einmal anschauen: -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} `201` ist der Statuscode für „Created“ („Erzeugt“). @@ -82,7 +82,7 @@ Aber Sie müssen sich nicht merken, was jeder dieser Codes bedeutet. Sie können die Annehmlichkeit von Variablen aus `fastapi.status` nutzen. -{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} Diese sind nur eine Annehmlichkeit, sie enthalten dieselbe Zahl, aber so können Sie die Autovervollständigung Ihres Editors verwenden, um sie zu finden: diff --git a/docs/de/docs/tutorial/schema-extra-example.md b/docs/de/docs/tutorial/schema-extra-example.md index 07fe8c5d9..c7b1c2b9e 100644 --- a/docs/de/docs/tutorial/schema-extra-example.md +++ b/docs/de/docs/tutorial/schema-extra-example.md @@ -74,7 +74,7 @@ Sie können natürlich auch mehrere `examples` übergeben: Wenn Sie das tun, werden die Beispiele Teil des internen **JSON-Schemas** für diese Body-Daten. -Während dies geschrieben wird, unterstützt Swagger UI, das für die Anzeige der Dokumentations-Benutzeroberfläche zuständige Tool, jedoch nicht die Anzeige mehrerer Beispiele für die Daten in **JSON Schema**. Aber lesen Sie unten für einen Workaround weiter. +Nichtsdestotrotz unterstützt Swagger UI, das für die Anzeige der Dokumentations-Benutzeroberfläche zuständige Tool, zum Zeitpunkt der Erstellung nicht die Anzeige mehrerer Beispiele für die Daten in **JSON Schema**. Aber lesen Sie unten für einen Workaround weiter. ### OpenAPI-spezifische `examples` { #openapi-specific-examples } diff --git a/docs/de/docs/tutorial/security/first-steps.md b/docs/de/docs/tutorial/security/first-steps.md index 20fcd0c00..5806a8ea0 100644 --- a/docs/de/docs/tutorial/security/first-steps.md +++ b/docs/de/docs/tutorial/security/first-steps.md @@ -20,7 +20,7 @@ Lassen Sie uns zunächst einfach den Code verwenden und sehen, wie er funktionie Kopieren Sie das Beispiel in eine Datei `main.py`: -{* ../../docs_src/security/tutorial001_an_py39.py *} +{* ../../docs_src/security/tutorial001_an_py310.py *} ## Ausführen { #run-it } @@ -111,7 +111,7 @@ Betrachten wir es also aus dieser vereinfachten Sicht: * Der Benutzer klickt im Frontend, um zu einem anderen Abschnitt der Frontend-Web-Anwendung zu gelangen. * Das Frontend muss weitere Daten von der API abrufen. * Es benötigt jedoch eine Authentifizierung für diesen bestimmten Endpunkt. - * Um sich also bei unserer API zu authentifizieren, sendet es einen Header `Authorization` mit dem Wert `Bearer ` plus dem Token. + * Um sich also bei unserer API zu authentifizieren, sendet es einen `Authorization`-Header mit dem Wert `Bearer ` plus dem Token. * Wenn der Token `foobar` enthielte, wäre der Inhalt des `Authorization`-Headers: `Bearer foobar`. ## **FastAPI**s `OAuth2PasswordBearer` { #fastapis-oauth2passwordbearer } @@ -134,7 +134,7 @@ In dem Fall gibt Ihnen **FastAPI** ebenfalls die Tools, die Sie zum Erstellen br Wenn wir eine Instanz der Klasse `OAuth2PasswordBearer` erstellen, übergeben wir den Parameter `tokenUrl`. Dieser Parameter enthält die URL, die der Client (das Frontend, das im Browser des Benutzers ausgeführt wird) verwendet, wenn er den `username` und das `password` sendet, um einen Token zu erhalten. -{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} /// tip | Tipp @@ -172,7 +172,7 @@ Es kann also mit `Depends` verwendet werden. Jetzt können Sie dieses `oauth2_scheme` als Abhängigkeit `Depends` übergeben. -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} Diese Abhängigkeit stellt einen `str` bereit, der dem Parameter `token` der *Pfadoperation-Funktion* zugewiesen wird. diff --git a/docs/de/docs/tutorial/security/get-current-user.md b/docs/de/docs/tutorial/security/get-current-user.md index e32e36669..cfb59ff12 100644 --- a/docs/de/docs/tutorial/security/get-current-user.md +++ b/docs/de/docs/tutorial/security/get-current-user.md @@ -2,7 +2,7 @@ Im vorherigen Kapitel hat das Sicherheitssystem (das auf dem Dependency Injection System basiert) der *Pfadoperation-Funktion* einen `token` vom Typ `str` überreicht: -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} Aber das ist immer noch nicht so nützlich. diff --git a/docs/de/docs/tutorial/security/index.md b/docs/de/docs/tutorial/security/index.md index 39b0b93c9..6330d3d9d 100644 --- a/docs/de/docs/tutorial/security/index.md +++ b/docs/de/docs/tutorial/security/index.md @@ -20,7 +20,7 @@ OAuth2 ist eine Spezifikation, die verschiedene Möglichkeiten zur Handhabung vo Es handelt sich um eine recht umfangreiche Spezifikation, und sie deckt mehrere komplexe Anwendungsfälle ab. -Sie umfasst Möglichkeiten zur Authentifizierung mithilfe eines „Dritten“ („third party“). +Sie umfasst Möglichkeiten zur Authentifizierung mithilfe eines „Dritten“. Das ist es, was alle diese „Login mit Facebook, Google, X (Twitter), GitHub“-Systeme unter der Haube verwenden. diff --git a/docs/de/docs/tutorial/security/oauth2-jwt.md b/docs/de/docs/tutorial/security/oauth2-jwt.md index 0d5ce587b..6d35a1436 100644 --- a/docs/de/docs/tutorial/security/oauth2-jwt.md +++ b/docs/de/docs/tutorial/security/oauth2-jwt.md @@ -116,7 +116,11 @@ Und eine weitere, um zu überprüfen, ob ein empfangenes Passwort mit dem gespei Und noch eine, um einen Benutzer zu authentifizieren und zurückzugeben. -{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +Wenn `authenticate_user` mit einem Benutzernamen aufgerufen wird, der in der Datenbank nicht existiert, führen wir dennoch `verify_password` gegen einen Dummy-Hash aus. + +So stellt man sicher, dass der Endpunkt ungefähr gleich viel Zeit für die Antwort benötigt, unabhängig davon, ob der Benutzername gültig ist oder nicht. Dadurch werden Timing-Angriffe verhindert, mit denen vorhandene Benutzernamen ermittelt werden könnten. /// note | Hinweis @@ -152,7 +156,7 @@ Definieren Sie ein Pydantic-Modell, das im Token-Endpunkt für die `timedelta` mit der Ablaufz Erstellen Sie einen echten JWT-Zugriffstoken und geben Sie ihn zurück. -{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} ### Technische Details zum JWT-„Subjekt“ `sub` { #technical-details-about-the-jwt-subject-sub } diff --git a/docs/de/docs/tutorial/security/simple-oauth2.md b/docs/de/docs/tutorial/security/simple-oauth2.md index 28cb83ba9..ae631f8b7 100644 --- a/docs/de/docs/tutorial/security/simple-oauth2.md +++ b/docs/de/docs/tutorial/security/simple-oauth2.md @@ -18,7 +18,7 @@ Aber für die Login-*Pfadoperation* müssen wir diese Namen verwenden, um mit de Die Spezifikation besagt auch, dass `username` und `password` als Formulardaten gesendet werden müssen (hier also kein JSON). -### `scope` { #scope } +### `scope` { #scope } Ferner sagt die Spezifikation, dass der Client ein weiteres Formularfeld "`scope`" („Geltungsbereich“) senden kann. diff --git a/docs/de/docs/tutorial/static-files.md b/docs/de/docs/tutorial/static-files.md index 9ba250175..26e385316 100644 --- a/docs/de/docs/tutorial/static-files.md +++ b/docs/de/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ Mit `StaticFiles` können Sie statische Dateien aus einem Verzeichnis automatisc * Importieren Sie `StaticFiles`. * „Mounten“ Sie eine `StaticFiles()`-Instanz in einem bestimmten Pfad. -{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} /// note | Technische Details diff --git a/docs/de/docs/tutorial/testing.md b/docs/de/docs/tutorial/testing.md index d889b1e1f..4fbe37279 100644 --- a/docs/de/docs/tutorial/testing.md +++ b/docs/de/docs/tutorial/testing.md @@ -30,7 +30,7 @@ Verwenden Sie das `TestClient`-Objekt auf die gleiche Weise wie `httpx`. Schreiben Sie einfache `assert`-Anweisungen mit den Standard-Python-Ausdrücken, die Sie überprüfen müssen (wiederum, Standard-`pytest`). -{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} /// tip | Tipp @@ -76,7 +76,7 @@ Nehmen wir an, Sie haben eine Dateistruktur wie in [Größere Anwendungen](bigge In der Datei `main.py` haben Sie Ihre **FastAPI**-Anwendung: -{* ../../docs_src/app_testing/app_a_py39/main.py *} +{* ../../docs_src/app_testing/app_a_py310/main.py *} ### Testdatei { #testing-file } @@ -93,7 +93,7 @@ Dann könnten Sie eine Datei `test_main.py` mit Ihren Tests haben. Sie könnte s Da sich diese Datei im selben Package befindet, können Sie relative Importe verwenden, um das Objekt `app` aus dem `main`-Modul (`main.py`) zu importieren: -{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} ... und haben den Code für die Tests wie zuvor. diff --git a/docs/de/docs/virtual-environments.md b/docs/de/docs/virtual-environments.md index 11da496c5..621b4b111 100644 --- a/docs/de/docs/virtual-environments.md +++ b/docs/de/docs/virtual-environments.md @@ -53,7 +53,7 @@ $ cd awesome-project ## Eine virtuelle Umgebung erstellen { #create-a-virtual-environment } -Wenn Sie zum **ersten Mal** an einem Python-Projekt arbeiten, erstellen Sie eine virtuelle Umgebung **innerhalb Ihres Projekts**. +Wenn Sie zum **ersten Mal** an einem Python-Projekt arbeiten, erstellen Sie eine virtuelle Umgebung **innerhalb Ihres Projekts**. /// tip | Tipp @@ -166,7 +166,7 @@ $ source .venv/Scripts/activate Jedes Mal, wenn Sie ein **neues Paket** in dieser Umgebung installieren, aktivieren Sie die Umgebung erneut. -So stellen Sie sicher, dass, wenn Sie ein **Terminalprogramm (CLI)** verwenden, das durch dieses Paket installiert wurde, Sie das aus Ihrer virtuellen Umgebung verwenden und nicht eines, das global installiert ist, wahrscheinlich mit einer anderen Version als der, die Sie benötigen. +So stellen Sie sicher, dass, wenn Sie ein **Terminalprogramm (CLI)** verwenden, das durch dieses Paket installiert wurde, Sie das aus Ihrer virtuellen Umgebung verwenden und nicht eines, das global installiert ist, wahrscheinlich mit einer anderen Version als der, die Sie benötigen. /// @@ -639,7 +639,7 @@ $ source .venv/Scripts/activate Dieser Befehl erstellt oder ändert einige [Umgebungsvariablen](environment-variables.md){.internal-link target=_blank}, die für die nächsten Befehle verfügbar sein werden. -Eine dieser Variablen ist die `PATH`-Variable. +Eine dieser Variablen ist die `PATH`-Umgebungsvariable. /// tip | Tipp @@ -649,7 +649,7 @@ Sie können mehr über die `PATH`-Umgebungsvariable im Abschnitt [Umgebungsvaria Das Aktivieren einer virtuellen Umgebung fügt deren Pfad `.venv/bin` (auf Linux und macOS) oder `.venv\Scripts` (auf Windows) zur `PATH`-Umgebungsvariable hinzu. -Angenommen, die `PATH`-Variable sah vor dem Aktivieren der Umgebung so aus: +Angenommen, die `PATH`-Umgebungsvariable sah vor dem Aktivieren der Umgebung so aus: //// tab | Linux, macOS @@ -678,7 +678,7 @@ Das bedeutet, dass das System nach Programmen sucht in: //// -Nach dem Aktivieren der virtuellen Umgebung würde die `PATH`-Variable folgendermaßen aussehen: +Nach dem Aktivieren der virtuellen Umgebung würde die `PATH`-Umgebungsvariable folgendermaßen aussehen: //// tab | Linux, macOS @@ -728,7 +728,7 @@ finden und dieses verwenden. //// -Ein wichtiger Punkt ist, dass es den Pfad der virtuellen Umgebung am **Anfang** der `PATH`-Variable platziert. Das System wird es **vor** allen anderen verfügbaren Pythons finden. Auf diese Weise, wenn Sie `python` ausführen, wird das Python **aus der virtuellen Umgebung** verwendet anstelle eines anderen `python` (zum Beispiel, einem `python` aus einer globalen Umgebung). +Ein wichtiger Punkt ist, dass es den Pfad der virtuellen Umgebung am **Anfang** der `PATH`-Umgebungsvariable platziert. Das System wird es **vor** allen anderen verfügbaren Pythons finden. Auf diese Weise, wenn Sie `python` ausführen, wird das Python **aus der virtuellen Umgebung** verwendet anstelle eines anderen `python` (zum Beispiel, einem `python` aus einer globalen Umgebung). Das Aktivieren einer virtuellen Umgebung ändert auch ein paar andere Dinge, aber dies ist eines der wichtigsten Dinge, die es tut. From 417990fcb7d933a87117878e131056e76edf969c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 14 Feb 2026 07:57:54 +0000 Subject: [PATCH 131/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2f2d599dc..631de0611 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for de (update-all) . PR [#14910](https://github.com/fastapi/fastapi/pull/14910) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for ja (update-all). PR [#14916](https://github.com/fastapi/fastapi/pull/14916) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for pt (update-all). PR [#14912](https://github.com/fastapi/fastapi/pull/14912) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for es (update-all and add-missing). PR [#14911](https://github.com/fastapi/fastapi/pull/14911) by [@YuriiMotov](https://github.com/YuriiMotov). From 7dc1be8b8815c5ea1d09325a973f6af7b531c62b Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Sat, 14 Feb 2026 09:12:41 +0100 Subject: [PATCH 132/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20fr=20(update-all=20and=20add-missing)=20(#14920)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all * Add missing * 🎨 Auto format --------- Co-authored-by: github-actions[bot] --- docs/fr/docs/_llm-test.md | 503 ++++++++++ docs/fr/docs/about/index.md | 3 + docs/fr/docs/advanced/additional-responses.md | 6 +- .../docs/advanced/additional-status-codes.md | 2 +- .../fr/docs/advanced/advanced-dependencies.md | 163 ++++ .../fr/docs/advanced/advanced-python-types.md | 61 ++ docs/fr/docs/advanced/async-tests.md | 99 ++ docs/fr/docs/advanced/behind-a-proxy.md | 466 ++++++++++ docs/fr/docs/advanced/custom-response.md | 312 +++++++ docs/fr/docs/advanced/dataclasses.md | 95 ++ docs/fr/docs/advanced/events.md | 165 ++++ docs/fr/docs/advanced/generate-clients.md | 208 +++++ docs/fr/docs/advanced/middleware.md | 97 ++ docs/fr/docs/advanced/openapi-callbacks.md | 186 ++++ docs/fr/docs/advanced/openapi-webhooks.md | 55 ++ .../path-operation-advanced-configuration.md | 16 +- .../advanced/response-change-status-code.md | 31 + docs/fr/docs/advanced/response-cookies.md | 51 ++ docs/fr/docs/advanced/response-directly.md | 4 +- docs/fr/docs/advanced/response-headers.md | 41 + .../docs/advanced/security/http-basic-auth.md | 107 +++ docs/fr/docs/advanced/security/index.md | 19 + .../docs/advanced/security/oauth2-scopes.md | 274 ++++++ docs/fr/docs/advanced/settings.md | 302 ++++++ docs/fr/docs/advanced/sub-applications.md | 67 ++ docs/fr/docs/advanced/templates.md | 126 +++ docs/fr/docs/advanced/testing-dependencies.md | 52 ++ docs/fr/docs/advanced/testing-events.md | 11 + docs/fr/docs/advanced/testing-websockets.md | 13 + .../docs/advanced/using-request-directly.md | 56 ++ docs/fr/docs/advanced/websockets.md | 186 ++++ docs/fr/docs/advanced/wsgi.md | 51 ++ docs/fr/docs/alternatives.md | 152 ++- docs/fr/docs/async.md | 180 ++-- docs/fr/docs/deployment/cloud.md | 24 + docs/fr/docs/deployment/concepts.md | 321 +++++++ docs/fr/docs/deployment/docker.md | 10 +- docs/fr/docs/deployment/fastapicloud.md | 65 ++ docs/fr/docs/deployment/https.md | 2 +- docs/fr/docs/deployment/server-workers.md | 139 +++ docs/fr/docs/environment-variables.md | 298 ++++++ docs/fr/docs/fastapi-cli.md | 75 ++ docs/fr/docs/features.md | 198 ++-- docs/fr/docs/help-fastapi.md | 258 ++++-- docs/fr/docs/history-design-future.md | 22 +- .../authentication-error-status-code.md | 17 + docs/fr/docs/how-to/conditional-openapi.md | 56 ++ docs/fr/docs/how-to/configure-swagger-ui.md | 70 ++ docs/fr/docs/how-to/custom-docs-ui-assets.md | 185 ++++ .../docs/how-to/custom-request-and-route.md | 109 +++ docs/fr/docs/how-to/extending-openapi.md | 80 ++ docs/fr/docs/how-to/general.md | 39 + docs/fr/docs/how-to/graphql.md | 60 ++ docs/fr/docs/how-to/index.md | 13 + ...migrate-from-pydantic-v1-to-pydantic-v2.md | 135 +++ .../docs/how-to/separate-openapi-schemas.md | 102 +++ docs/fr/docs/how-to/testing-database.md | 7 + docs/fr/docs/index.md | 10 +- docs/fr/docs/learn/index.md | 2 +- docs/fr/docs/python-types.md | 206 +---- docs/fr/docs/resources/index.md | 3 + docs/fr/docs/translation-banner.md | 11 + docs/fr/docs/tutorial/background-tasks.md | 8 +- docs/fr/docs/tutorial/bigger-applications.md | 504 ++++++++++ docs/fr/docs/tutorial/body-fields.md | 61 ++ docs/fr/docs/tutorial/body-multiple-params.md | 6 - docs/fr/docs/tutorial/body-nested-models.md | 220 +++++ docs/fr/docs/tutorial/body-updates.md | 100 ++ docs/fr/docs/tutorial/body.md | 14 +- docs/fr/docs/tutorial/cookie-param-models.md | 76 ++ docs/fr/docs/tutorial/cookie-params.md | 45 + docs/fr/docs/tutorial/cors.md | 88 ++ docs/fr/docs/tutorial/debugging.md | 6 +- .../dependencies/classes-as-dependencies.md | 288 ++++++ ...pendencies-in-path-operation-decorators.md | 69 ++ .../dependencies/dependencies-with-yield.md | 289 ++++++ .../dependencies/global-dependencies.md | 15 + docs/fr/docs/tutorial/dependencies/index.md | 250 +++++ .../tutorial/dependencies/sub-dependencies.md | 105 +++ docs/fr/docs/tutorial/encoder.md | 35 + docs/fr/docs/tutorial/extra-data-types.md | 62 ++ docs/fr/docs/tutorial/extra-models.md | 211 +++++ docs/fr/docs/tutorial/first-steps.md | 24 +- docs/fr/docs/tutorial/handling-errors.md | 244 +++++ docs/fr/docs/tutorial/header-param-models.md | 72 ++ docs/fr/docs/tutorial/header-params.md | 91 ++ docs/fr/docs/tutorial/metadata.md | 120 +++ docs/fr/docs/tutorial/middleware.md | 95 ++ .../tutorial/path-operation-configuration.md | 107 +++ .../path-params-numeric-validations.md | 16 +- docs/fr/docs/tutorial/path-params.md | 30 +- docs/fr/docs/tutorial/query-param-models.md | 68 ++ .../tutorial/query-params-str-validations.md | 42 +- docs/fr/docs/tutorial/query-params.md | 8 +- docs/fr/docs/tutorial/request-files.md | 176 ++++ docs/fr/docs/tutorial/request-form-models.md | 78 ++ .../docs/tutorial/request-forms-and-files.md | 41 + docs/fr/docs/tutorial/request-forms.md | 73 ++ docs/fr/docs/tutorial/response-model.md | 343 +++++++ docs/fr/docs/tutorial/response-status-code.md | 101 ++ docs/fr/docs/tutorial/schema-extra-example.md | 202 ++++ docs/fr/docs/tutorial/security/first-steps.md | 203 ++++ .../tutorial/security/get-current-user.md | 105 +++ docs/fr/docs/tutorial/security/index.md | 106 +++ docs/fr/docs/tutorial/security/oauth2-jwt.md | 277 ++++++ .../docs/tutorial/security/simple-oauth2.md | 289 ++++++ docs/fr/docs/tutorial/sql-databases.md | 357 ++++++++ docs/fr/docs/tutorial/static-files.md | 40 + docs/fr/docs/tutorial/testing.md | 193 ++++ docs/fr/docs/virtual-environments.md | 864 ++++++++++++++++++ 110 files changed, 12889 insertions(+), 605 deletions(-) create mode 100644 docs/fr/docs/_llm-test.md create mode 100644 docs/fr/docs/about/index.md create mode 100644 docs/fr/docs/advanced/advanced-dependencies.md create mode 100644 docs/fr/docs/advanced/advanced-python-types.md create mode 100644 docs/fr/docs/advanced/async-tests.md create mode 100644 docs/fr/docs/advanced/behind-a-proxy.md create mode 100644 docs/fr/docs/advanced/custom-response.md create mode 100644 docs/fr/docs/advanced/dataclasses.md create mode 100644 docs/fr/docs/advanced/events.md create mode 100644 docs/fr/docs/advanced/generate-clients.md create mode 100644 docs/fr/docs/advanced/middleware.md create mode 100644 docs/fr/docs/advanced/openapi-callbacks.md create mode 100644 docs/fr/docs/advanced/openapi-webhooks.md create mode 100644 docs/fr/docs/advanced/response-change-status-code.md create mode 100644 docs/fr/docs/advanced/response-cookies.md create mode 100644 docs/fr/docs/advanced/response-headers.md create mode 100644 docs/fr/docs/advanced/security/http-basic-auth.md create mode 100644 docs/fr/docs/advanced/security/index.md create mode 100644 docs/fr/docs/advanced/security/oauth2-scopes.md create mode 100644 docs/fr/docs/advanced/settings.md create mode 100644 docs/fr/docs/advanced/sub-applications.md create mode 100644 docs/fr/docs/advanced/templates.md create mode 100644 docs/fr/docs/advanced/testing-dependencies.md create mode 100644 docs/fr/docs/advanced/testing-events.md create mode 100644 docs/fr/docs/advanced/testing-websockets.md create mode 100644 docs/fr/docs/advanced/using-request-directly.md create mode 100644 docs/fr/docs/advanced/websockets.md create mode 100644 docs/fr/docs/advanced/wsgi.md create mode 100644 docs/fr/docs/deployment/cloud.md create mode 100644 docs/fr/docs/deployment/concepts.md create mode 100644 docs/fr/docs/deployment/fastapicloud.md create mode 100644 docs/fr/docs/deployment/server-workers.md create mode 100644 docs/fr/docs/environment-variables.md create mode 100644 docs/fr/docs/fastapi-cli.md create mode 100644 docs/fr/docs/how-to/authentication-error-status-code.md create mode 100644 docs/fr/docs/how-to/conditional-openapi.md create mode 100644 docs/fr/docs/how-to/configure-swagger-ui.md create mode 100644 docs/fr/docs/how-to/custom-docs-ui-assets.md create mode 100644 docs/fr/docs/how-to/custom-request-and-route.md create mode 100644 docs/fr/docs/how-to/extending-openapi.md create mode 100644 docs/fr/docs/how-to/general.md create mode 100644 docs/fr/docs/how-to/graphql.md create mode 100644 docs/fr/docs/how-to/index.md create mode 100644 docs/fr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md create mode 100644 docs/fr/docs/how-to/separate-openapi-schemas.md create mode 100644 docs/fr/docs/how-to/testing-database.md create mode 100644 docs/fr/docs/resources/index.md create mode 100644 docs/fr/docs/translation-banner.md create mode 100644 docs/fr/docs/tutorial/bigger-applications.md create mode 100644 docs/fr/docs/tutorial/body-fields.md create mode 100644 docs/fr/docs/tutorial/body-nested-models.md create mode 100644 docs/fr/docs/tutorial/body-updates.md create mode 100644 docs/fr/docs/tutorial/cookie-param-models.md create mode 100644 docs/fr/docs/tutorial/cookie-params.md create mode 100644 docs/fr/docs/tutorial/cors.md create mode 100644 docs/fr/docs/tutorial/dependencies/classes-as-dependencies.md create mode 100644 docs/fr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md create mode 100644 docs/fr/docs/tutorial/dependencies/dependencies-with-yield.md create mode 100644 docs/fr/docs/tutorial/dependencies/global-dependencies.md create mode 100644 docs/fr/docs/tutorial/dependencies/index.md create mode 100644 docs/fr/docs/tutorial/dependencies/sub-dependencies.md create mode 100644 docs/fr/docs/tutorial/encoder.md create mode 100644 docs/fr/docs/tutorial/extra-data-types.md create mode 100644 docs/fr/docs/tutorial/extra-models.md create mode 100644 docs/fr/docs/tutorial/handling-errors.md create mode 100644 docs/fr/docs/tutorial/header-param-models.md create mode 100644 docs/fr/docs/tutorial/header-params.md create mode 100644 docs/fr/docs/tutorial/metadata.md create mode 100644 docs/fr/docs/tutorial/middleware.md create mode 100644 docs/fr/docs/tutorial/path-operation-configuration.md create mode 100644 docs/fr/docs/tutorial/query-param-models.md create mode 100644 docs/fr/docs/tutorial/request-files.md create mode 100644 docs/fr/docs/tutorial/request-form-models.md create mode 100644 docs/fr/docs/tutorial/request-forms-and-files.md create mode 100644 docs/fr/docs/tutorial/request-forms.md create mode 100644 docs/fr/docs/tutorial/response-model.md create mode 100644 docs/fr/docs/tutorial/response-status-code.md create mode 100644 docs/fr/docs/tutorial/schema-extra-example.md create mode 100644 docs/fr/docs/tutorial/security/first-steps.md create mode 100644 docs/fr/docs/tutorial/security/get-current-user.md create mode 100644 docs/fr/docs/tutorial/security/index.md create mode 100644 docs/fr/docs/tutorial/security/oauth2-jwt.md create mode 100644 docs/fr/docs/tutorial/security/simple-oauth2.md create mode 100644 docs/fr/docs/tutorial/sql-databases.md create mode 100644 docs/fr/docs/tutorial/static-files.md create mode 100644 docs/fr/docs/tutorial/testing.md create mode 100644 docs/fr/docs/virtual-environments.md diff --git a/docs/fr/docs/_llm-test.md b/docs/fr/docs/_llm-test.md new file mode 100644 index 000000000..4fdc67738 --- /dev/null +++ b/docs/fr/docs/_llm-test.md @@ -0,0 +1,503 @@ +# Fichier de test LLM { #llm-test-file } + +Ce document teste si le LLM, qui traduit la documentation, comprend le `general_prompt` dans `scripts/translate.py` et l’invite spécifique à la langue dans `docs/{language code}/llm-prompt.md`. L’invite spécifique à la langue est ajoutée à la fin de `general_prompt`. + +Les tests ajoutés ici seront visibles par tous les concepteurs d’invites spécifiques à chaque langue. + +Utiliser comme suit : + +* Avoir une invite spécifique à la langue - `docs/{language code}/llm-prompt.md`. +* Effectuer une nouvelle traduction de ce document dans votre langue cible souhaitée (voir par exemple la commande `translate-page` de `translate.py`). Cela créera la traduction sous `docs/{language code}/docs/_llm-test.md`. +* Vérifier si tout est correct dans la traduction. +* Si nécessaire, améliorer votre invite spécifique à la langue, l’invite générale, ou le document anglais. +* Corriger ensuite manuellement les problèmes restants dans la traduction, afin que ce soit une bonne traduction. +* Retraduire, en ayant la bonne traduction en place. Le résultat idéal serait que le LLM ne fasse plus aucun changement à la traduction. Cela signifie que l’invite générale et votre invite spécifique à la langue sont aussi bonnes que possible (il fera parfois quelques changements apparemment aléatoires, la raison étant que les LLM ne sont pas des algorithmes déterministes). + +Les tests : + +## Extraits de code { #code-snippets } + +//// tab | Test + +Ceci est un extrait de code : `foo`. Et ceci est un autre extrait de code : `bar`. Et encore un autre : `baz quux`. + +//// + +//// tab | Info + +Le contenu des extraits de code doit être laissé tel quel. + +Voir la section `### Content of code snippets` dans l’invite générale dans `scripts/translate.py`. + +//// + +## Guillemets { #quotes } + +//// tab | Test + +Hier, mon ami a écrit : « Si vous écrivez « incorrectly » correctement, vous l’avez écrit de façon incorrecte ». À quoi j’ai répondu : « Correct, mais ‘incorrectly’ est incorrectement non pas ‘« incorrectly »’ ». + +/// note | Remarque + +Le LLM traduira probablement ceci de manière erronée. Il est seulement intéressant de voir s’il conserve la traduction corrigée lors d’une retraduction. + +/// + +//// + +//// tab | Info + +Le concepteur de l’invite peut choisir s’il souhaite convertir les guillemets neutres en guillemets typographiques. Il est acceptable de les laisser tels quels. + +Voir par exemple la section `### Quotes` dans `docs/de/llm-prompt.md`. + +//// + +## Guillemets dans les extraits de code { #quotes-in-code-snippets } + +//// tab | Test + +`pip install "foo[bar]"` + +Exemples de littéraux de chaîne dans des extraits de code : `"this"`, `'that'`. + +Un exemple difficile de littéraux de chaîne dans des extraits de code : `f"I like {'oranges' if orange else "apples"}"` + +Hardcore: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` + +//// + +//// tab | Info + +... Cependant, les guillemets à l’intérieur des extraits de code doivent rester tels quels. + +//// + +## Blocs de code { #code-blocks } + +//// tab | Test + +Un exemple de code Bash ... + +```bash +# Afficher un message de bienvenue à l'univers +echo "Hello universe" +``` + +... et un exemple de code console ... + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +... et un autre exemple de code console ... + +```console +// Créer un répertoire "Code" +$ mkdir code +// Aller dans ce répertoire +$ cd code +``` + +... et un exemple de code Python ... + +```Python +wont_work() # Cela ne fonctionnera pas 😱 +works(foo="bar") # Cela fonctionne 🎉 +``` + +... et c’est tout. + +//// + +//// tab | Info + +Le code dans les blocs de code ne doit pas être modifié, à l’exception des commentaires. + +Voir la section `### Content of code blocks` dans l’invite générale dans `scripts/translate.py`. + +//// + +## Onglets et encadrés colorés { #tabs-and-colored-boxes } + +//// tab | Test + +/// info | Info +Du texte +/// + +/// note | Remarque +Du texte +/// + +/// note | Détails techniques +Du texte +/// + +/// check | Vérifications +Du texte +/// + +/// tip | Astuce +Du texte +/// + +/// warning | Alertes +Du texte +/// + +/// danger | Danger +Du texte +/// + +//// + +//// tab | Info + +Les onglets et les blocs « Info »/« Note »/« Warning »/etc. doivent avoir la traduction de leur titre ajoutée après une barre verticale (« | »). + +Voir les sections `### Special blocks` et `### Tab blocks` dans l’invite générale dans `scripts/translate.py`. + +//// + +## Liens Web et internes { #web-and-internal-links } + +//// tab | Test + +Le texte du lien doit être traduit, l’adresse du lien doit rester inchangée : + +* [Lien vers le titre ci-dessus](#code-snippets) +* [Lien interne](index.md#installation){.internal-link target=_blank} +* Lien externe +* Lien vers une feuille de style +* Lien vers un script +* Lien vers une image + +Le texte du lien doit être traduit, l’adresse du lien doit pointer vers la traduction : + +* Lien FastAPI + +//// + +//// tab | Info + +Les liens doivent être traduits, mais leur adresse doit rester inchangée. Exception faite des liens absolus vers des pages de la documentation FastAPI. Dans ce cas, il faut pointer vers la traduction. + +Voir la section `### Links` dans l’invite générale dans `scripts/translate.py`. + +//// + +## Éléments HTML « abbr » { #html-abbr-elements } + +//// tab | Test + +Voici quelques éléments entourés d’un élément HTML « abbr » (certains sont inventés) : + +### L’abbr fournit une expression complète { #the-abbr-gives-a-full-phrase } + +* GTD +* lt +* XWT +* PSGI + +### L’abbr donne une expression complète et une explication { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* MDN +* I/O. + +//// + +//// tab | Info + +Les attributs « title » des éléments « abbr » sont traduits en suivant des consignes spécifiques. + +Les traductions peuvent ajouter leurs propres éléments « abbr » que le LLM ne doit pas supprimer. Par exemple pour expliquer des mots anglais. + +Voir la section `### HTML abbr elements` dans l’invite générale dans `scripts/translate.py`. + +//// + +## Éléments HTML « dfn » { #html-dfn-elements } + +* grappe +* Apprentissage profond + +## Titres { #headings } + +//// tab | Test + +### Créer une application Web - un tutoriel { #develop-a-webapp-a-tutorial } + +Bonjour. + +### Annotations de type et indications de type { #type-hints-and-annotations } + +Rebonjour. + +### Superclasses et sous-classes { #super-and-subclasses } + +Rebonjour. + +//// + +//// tab | Info + +La seule règle stricte pour les titres est que le LLM laisse la partie hachage entre accolades inchangée, ce qui garantit que les liens ne se rompent pas. + +Voir la section `### Headings` dans l’invite générale dans `scripts/translate.py`. + +Pour certaines consignes spécifiques à la langue, voir par exemple la section `### Headings` dans `docs/de/llm-prompt.md`. + +//// + +## Termes utilisés dans les documents { #terms-used-in-the-docs } + +//// tab | Test + +* vous +* votre + +* p. ex. +* etc. + +* `foo` en tant que `int` +* `bar` en tant que `str` +* `baz` en tant que `list` + +* le Tutoriel - Guide utilisateur +* le Guide utilisateur avancé +* la documentation SQLModel +* la documentation de l’API +* la documentation automatique + +* Data Science +* Apprentissage profond +* Apprentissage automatique +* Injection de dépendances +* authentification HTTP Basic +* HTTP Digest +* format ISO +* la norme JSON Schema +* le schéma JSON +* la définition de schéma +* Flux Password +* Mobile + +* déprécié +* conçu +* invalide +* à la volée +* standard +* par défaut +* sensible à la casse +* insensible à la casse + +* servir l’application +* servir la page + +* l’app +* l’application + +* la requête +* la réponse +* la réponse d’erreur + +* le chemin d’accès +* le décorateur de chemin d’accès +* la fonction de chemin d’accès + +* le corps +* le corps de la requête +* le corps de la réponse +* le corps JSON +* le corps de formulaire +* le corps de fichier +* le corps de la fonction + +* le paramètre +* le paramètre de corps +* le paramètre de chemin +* le paramètre de requête +* le paramètre de cookie +* le paramètre d’en-tête +* le paramètre de formulaire +* le paramètre de fonction + +* l’événement +* l’événement de démarrage +* le démarrage du serveur +* l’événement d’arrêt +* l’événement de cycle de vie + +* le gestionnaire +* le gestionnaire d’événements +* le gestionnaire d’exceptions +* gérer + +* le modèle +* le modèle Pydantic +* le modèle de données +* le modèle de base de données +* le modèle de formulaire +* l’objet modèle + +* la classe +* la classe de base +* la classe parente +* la sous-classe +* la classe enfant +* la classe sœur +* la méthode de classe + +* l’en-tête +* les en-têtes +* l’en-tête d’autorisation +* l’en-tête `Authorization` +* l’en-tête transféré + +* le système d’injection de dépendances +* la dépendance +* l’élément dépendable +* le dépendant + +* lié aux E/S +* lié au processeur +* concurrence +* parallélisme +* multi-traitement + +* la variable d’env +* la variable d’environnement +* le `PATH` +* la variable `PATH` + +* l’authentification +* le fournisseur d’authentification +* l’autorisation +* le formulaire d’autorisation +* le fournisseur d’autorisation +* l’utilisateur s’authentifie +* le système authentifie l’utilisateur + +* la CLI +* l’interface en ligne de commande + +* le serveur +* le client + +* le fournisseur cloud +* le service cloud + +* le développement +* les étapes de développement + +* le dict +* le dictionnaire +* l’énumération +* l’enum +* le membre d’enum + +* l’encodeur +* le décodeur +* encoder +* décoder + +* l’exception +* lever + +* l’expression +* l’instruction + +* le frontend +* le backend + +* la discussion GitHub +* le ticket GitHub + +* la performance +* l’optimisation des performances + +* le type de retour +* la valeur de retour + +* la sécurité +* le schéma de sécurité + +* la tâche +* la tâche d’arrière-plan +* la fonction de tâche + +* le template +* le moteur de templates + +* l’annotation de type +* l’annotation de type + +* le worker du serveur +* le worker Uvicorn +* le Worker Gunicorn +* le processus worker +* la classe de worker +* la charge de travail + +* le déploiement +* déployer + +* le SDK +* le kit de développement logiciel + +* le `APIRouter` +* le `requirements.txt` +* le jeton Bearer +* le changement majeur incompatible +* le bogue +* le bouton +* l’appelable +* le code +* le commit +* le gestionnaire de contexte +* la coroutine +* la session de base de données +* le disque +* le domaine +* le moteur +* le faux X +* la méthode HTTP GET +* l’élément +* la bibliothèque +* le cycle de vie +* le verrou +* le middleware +* l’application mobile +* le module +* le montage +* le réseau +* l’origine +* la surcharge +* le payload +* le processeur +* la propriété +* le proxy +* la pull request +* la requête +* la RAM +* la machine distante +* le code d’état +* la chaîne +* l’étiquette +* le framework Web +* le joker +* retourner +* valider + +//// + +//// tab | Info + +Il s’agit d’une liste non exhaustive et non normative de termes (principalement) techniques présents dans les documents. Elle peut aider le concepteur de l’invite à déterminer pour quels termes le LLM a besoin d’un coup de main. Par exemple, lorsqu’il continue de remplacer une bonne traduction par une traduction sous-optimale. Ou lorsqu’il a des difficultés à conjuguer/décliner un terme dans votre langue. + +Voir par exemple la section `### List of English terms and their preferred German translations` dans `docs/de/llm-prompt.md`. + +//// diff --git a/docs/fr/docs/about/index.md b/docs/fr/docs/about/index.md new file mode 100644 index 000000000..f6ec12a4f --- /dev/null +++ b/docs/fr/docs/about/index.md @@ -0,0 +1,3 @@ +# À propos { #about } + +À propos de FastAPI, de sa conception, de ses sources d'inspiration et plus encore. 🤓 diff --git a/docs/fr/docs/advanced/additional-responses.md b/docs/fr/docs/advanced/additional-responses.md index dabcded52..a073dec69 100644 --- a/docs/fr/docs/advanced/additional-responses.md +++ b/docs/fr/docs/advanced/additional-responses.md @@ -8,7 +8,7 @@ Si vous débutez avec **FastAPI**, vous n'en aurez peut-être pas besoin. /// -Vous pouvez déclarer des réponses supplémentaires, avec des codes HTTP, des types de médias, des descriptions, etc. +Vous pouvez déclarer des réponses supplémentaires, avec des codes d'état supplémentaires, des types de médias, des descriptions, etc. Ces réponses supplémentaires seront incluses dans le schéma OpenAPI, elles apparaîtront donc également dans la documentation de l'API. @@ -26,7 +26,7 @@ Chacun de ces `dict` de réponse peut avoir une clé `model`, contenant un modè Par exemple, pour déclarer une autre réponse avec un code HTTP `404` et un modèle Pydantic `Message`, vous pouvez écrire : -{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} /// note | Remarque @@ -203,7 +203,7 @@ Par exemple, vous pouvez déclarer une réponse avec un code HTTP `404` qui util Et une réponse avec un code HTTP `200` qui utilise votre `response_model`, mais inclut un `example` personnalisé : -{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} Tout sera combiné et inclus dans votre OpenAPI, et affiché dans la documentation de l'API : diff --git a/docs/fr/docs/advanced/additional-status-codes.md b/docs/fr/docs/advanced/additional-status-codes.md index b2befffa8..b9c8ab113 100644 --- a/docs/fr/docs/advanced/additional-status-codes.md +++ b/docs/fr/docs/advanced/additional-status-codes.md @@ -36,6 +36,6 @@ Pour plus de commodités, **FastAPI** fournit les objets `starlette.responses` s ## Documents OpenAPI et API { #openapi-and-api-docs } -Si vous renvoyez directement des codes HTTP et des réponses supplémentaires, ils ne seront pas inclus dans le schéma OpenAPI (la documentation de l'API), car FastAPI n'a aucun moyen de savoir à l'avance ce que vous allez renvoyer. +Si vous renvoyez directement des codes HTTP et des réponses supplémentaires, ils ne seront pas inclus dans le schéma OpenAPI (les documents de l'API), car FastAPI n'a aucun moyen de savoir à l'avance ce que vous allez renvoyer. Mais vous pouvez documenter cela dans votre code, en utilisant : [Réponses supplémentaires](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/fr/docs/advanced/advanced-dependencies.md b/docs/fr/docs/advanced/advanced-dependencies.md new file mode 100644 index 000000000..8afd58b48 --- /dev/null +++ b/docs/fr/docs/advanced/advanced-dependencies.md @@ -0,0 +1,163 @@ +# Dépendances avancées { #advanced-dependencies } + +## Dépendances paramétrées { #parameterized-dependencies } + +Toutes les dépendances que nous avons vues étaient des fonctions ou des classes fixes. + +Mais il peut y avoir des cas où vous souhaitez pouvoir définir des paramètres sur la dépendance, sans devoir déclarer de nombreuses fonctions ou classes différentes. + +Imaginons que nous voulions avoir une dépendance qui vérifie si le paramètre de requête `q` contient un contenu fixe. + +Mais nous voulons pouvoir paramétrer ce contenu fixe. + +## Une instance « callable » { #a-callable-instance } + +En Python, il existe un moyen de rendre une instance de classe « callable ». + +Pas la classe elle‑même (qui est déjà un callable), mais une instance de cette classe. + +Pour cela, nous déclarons une méthode `__call__` : + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} + +Dans ce cas, ce `__call__` est ce que **FastAPI** utilisera pour détecter des paramètres supplémentaires et des sous‑dépendances, et c’est ce qui sera appelé pour transmettre ensuite une valeur au paramètre dans votre *fonction de chemin d'accès*. + +## Paramétrer l'instance { #parameterize-the-instance } + +Et maintenant, nous pouvons utiliser `__init__` pour déclarer les paramètres de l’instance, que nous utiliserons pour « paramétrer » la dépendance : + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} + +Dans ce cas, **FastAPI** n’accèdera pas à `__init__` et ne s’en souciera pas ; nous l’utiliserons directement dans notre code. + +## Créer une instance { #create-an-instance } + +Nous pouvons créer une instance de cette classe avec : + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} + +Et de cette façon, nous pouvons « paramétrer » notre dépendance, qui contient maintenant « bar », en tant qu’attribut `checker.fixed_content`. + +## Utiliser l'instance comme dépendance { #use-the-instance-as-a-dependency } + +Ensuite, nous pourrions utiliser ce `checker` dans un `Depends(checker)`, au lieu de `Depends(FixedContentQueryChecker)`, car la dépendance est l’instance, `checker`, et non la classe elle‑même. + +Et lors de la résolution de la dépendance, **FastAPI** appellera ce `checker` comme ceci : + +```Python +checker(q="somequery") +``` + +... et passera ce que cela renvoie comme valeur de la dépendance à notre *fonction de chemin d'accès*, en tant que paramètre `fixed_content_included` : + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} + +/// tip | Astuce + +Tout cela peut sembler artificiel. Et il n’est peut‑être pas encore très clair en quoi c’est utile. + +Ces exemples sont volontairement simples, mais ils montrent comment tout cela fonctionne. + +Dans les chapitres sur la sécurité, il existe des fonctions utilitaires implémentées de la même manière. + +Si vous avez compris tout cela, vous savez déjà comment ces outils utilitaires pour la sécurité fonctionnent en interne. + +/// + +## Dépendances avec `yield`, `HTTPException`, `except` et tâches d'arrière‑plan { #dependencies-with-yield-httpexception-except-and-background-tasks } + +/// warning | Alertes + +Vous n’avez très probablement pas besoin de ces détails techniques. + +Ces détails sont utiles principalement si vous aviez une application FastAPI antérieure à la version 0.121.0 et que vous rencontrez des problèmes avec des dépendances utilisant `yield`. + +/// + +Les dépendances avec `yield` ont évolué au fil du temps pour couvrir différents cas d’utilisation et corriger certains problèmes ; voici un résumé de ce qui a changé. + +### Dépendances avec `yield` et `scope` { #dependencies-with-yield-and-scope } + +Dans la version 0.121.0, **FastAPI** a ajouté la prise en charge de `Depends(scope="function")` pour les dépendances avec `yield`. + +Avec `Depends(scope="function")`, le code d’arrêt après `yield` s’exécute immédiatement après la fin de la *fonction de chemin d'accès*, avant que la réponse ne soit renvoyée au client. + +Et lorsque vous utilisez `Depends(scope="request")` (valeur par défaut), le code d’arrêt après `yield` s’exécute après l’envoi de la réponse. + +Vous pouvez en lire davantage dans les documents pour [Dépendances avec `yield` - Sortie anticipée et `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope). + +### Dépendances avec `yield` et `StreamingResponse`, Détails techniques { #dependencies-with-yield-and-streamingresponse-technical-details } + +Avant FastAPI 0.118.0, si vous utilisiez une dépendance avec `yield`, elle exécutait le code d’arrêt après que la *fonction de chemin d'accès* a retourné, mais juste avant d’envoyer la réponse. + +L’objectif était d’éviter de conserver des ressources plus longtemps que nécessaire pendant que la réponse transitait sur le réseau. + +Ce changement impliquait aussi que si vous retourniez une `StreamingResponse`, le code d’arrêt de la dépendance avec `yield` aurait déjà été exécuté. + +Par exemple, si vous aviez une session de base de données dans une dépendance avec `yield`, la `StreamingResponse` ne pourrait pas utiliser cette session pendant le streaming des données, car la session aurait déjà été fermée dans le code d’arrêt après `yield`. + +Ce comportement a été annulé en 0.118.0, afin que le code d’arrêt après `yield` s’exécute après l’envoi de la réponse. + +/// info + +Comme vous le verrez ci‑dessous, c’est très similaire au comportement avant la version 0.106.0, mais avec plusieurs améliorations et corrections de bogues pour des cas limites. + +/// + +#### Cas d’utilisation avec sortie anticipée du code { #use-cases-with-early-exit-code } + +Il existe certains cas d’utilisation avec des conditions spécifiques qui pourraient bénéficier de l’ancien comportement, où le code d’arrêt des dépendances avec `yield` s’exécute avant l’envoi de la réponse. + +Par exemple, imaginez que vous ayez du code qui utilise une session de base de données dans une dépendance avec `yield` uniquement pour vérifier un utilisateur, mais que la session de base de données ne soit plus jamais utilisée dans la *fonction de chemin d'accès*, seulement dans la dépendance, et que la réponse mette longtemps à être envoyée, comme une `StreamingResponse` qui envoie les données lentement mais qui, pour une raison quelconque, n’utilise pas la base de données. + +Dans ce cas, la session de base de données serait conservée jusqu’à la fin de l’envoi de la réponse, mais si vous ne l’utilisez pas, il ne serait pas nécessaire de la conserver. + +Voici à quoi cela pourrait ressembler : + +{* ../../docs_src/dependencies/tutorial013_an_py310.py *} + +Le code d’arrêt, la fermeture automatique de la `Session` dans : + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +... serait exécuté après que la réponse a fini d’envoyer les données lentes : + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +Mais comme `generate_stream()` n’utilise pas la session de base de données, il n’est pas vraiment nécessaire de garder la session ouverte pendant l’envoi de la réponse. + +Si vous avez ce cas d’utilisation spécifique avec SQLModel (ou SQLAlchemy), vous pouvez fermer explicitement la session dès que vous n’en avez plus besoin : + +{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *} + +De cette manière, la session libérera la connexion à la base de données, afin que d’autres requêtes puissent l’utiliser. + +Si vous avez un autre cas d’utilisation qui nécessite une sortie anticipée depuis une dépendance avec `yield`, veuillez créer une Question de discussion GitHub avec votre cas spécifique et pourquoi vous bénéficieriez d’une fermeture anticipée pour les dépendances avec `yield`. + +S’il existe des cas d’utilisation convaincants pour une fermeture anticipée dans les dépendances avec `yield`, j’envisagerai d’ajouter une nouvelle façon d’y opter. + +### Dépendances avec `yield` et `except`, Détails techniques { #dependencies-with-yield-and-except-technical-details } + +Avant FastAPI 0.110.0, si vous utilisiez une dépendance avec `yield`, puis capturiez une exception avec `except` dans cette dépendance, et que vous ne relanciez pas l’exception, l’exception était automatiquement levée/transmise à tout gestionnaire d’exceptions ou au gestionnaire d’erreur interne du serveur. + +Cela a été modifié dans la version 0.110.0 pour corriger une consommation de mémoire non gérée due aux exceptions transmises sans gestionnaire (erreurs internes du serveur), et pour rendre le comportement cohérent avec celui du code Python classique. + +### Tâches d'arrière‑plan et dépendances avec `yield`, Détails techniques { #background-tasks-and-dependencies-with-yield-technical-details } + +Avant FastAPI 0.106.0, lever des exceptions après `yield` n’était pas possible, le code d’arrêt dans les dépendances avec `yield` s’exécutait après l’envoi de la réponse, donc les [Gestionnaires d'exceptions](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} avaient déjà été exécutés. + +Cela avait été conçu ainsi principalement pour permettre d’utiliser les mêmes objets « générés par yield » par les dépendances à l’intérieur de tâches d’arrière‑plan, car le code d’arrêt s’exécutait après la fin des tâches d’arrière‑plan. + +Cela a été modifié dans FastAPI 0.106.0 afin de ne pas conserver des ressources pendant l’attente de la transmission de la réponse sur le réseau. + +/// tip | Astuce + +De plus, une tâche d’arrière‑plan est normalement un ensemble de logique indépendant qui devrait être géré séparément, avec ses propres ressources (par ex. sa propre connexion à la base de données). + +Ainsi, vous aurez probablement un code plus propre. + +/// + +Si vous comptiez sur ce comportement, vous devez désormais créer les ressources pour les tâches d’arrière‑plan à l’intérieur de la tâche elle‑même, et n’utiliser en interne que des données qui ne dépendent pas des ressources des dépendances avec `yield`. + +Par exemple, au lieu d’utiliser la même session de base de données, vous créeriez une nouvelle session de base de données à l’intérieur de la tâche d’arrière‑plan, et vous obtiendriez les objets depuis la base de données en utilisant cette nouvelle session. Puis, au lieu de passer l’objet obtenu depuis la base de données en paramètre à la fonction de tâche d’arrière‑plan, vous passeriez l’identifiant (ID) de cet objet et vous obtiendriez à nouveau l’objet à l’intérieur de la fonction de la tâche d’arrière‑plan. diff --git a/docs/fr/docs/advanced/advanced-python-types.md b/docs/fr/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..3e2d9453b --- /dev/null +++ b/docs/fr/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# Types Python avancés { #advanced-python-types } + +Voici quelques idées supplémentaires qui peuvent être utiles lorsque vous travaillez avec les types Python. + +## Utiliser `Union` ou `Optional` { #using-union-or-optional } + +Si votre code ne peut pas utiliser `|` pour une raison quelconque, par exemple si ce n'est pas dans une annotation de type mais dans quelque chose comme `response_model=`, au lieu d'utiliser la barre verticale (`|`) vous pouvez utiliser `Union` de `typing`. + +Par exemple, vous pourriez déclarer que quelque chose peut être un `str` ou `None` : + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +`typing` propose également un raccourci pour déclarer que quelque chose peut être `None`, avec `Optional`. + +Voici un conseil issu de mon point de vue très subjectif : + +- 🚨 Évitez d'utiliser `Optional[SomeType]` +- À la place ✨ **utilisez `Union[SomeType, None]`** ✨. + +Les deux sont équivalents et, en interne, identiques, mais je recommande `Union` plutôt que `Optional` parce que le mot « optional » semble impliquer que la valeur est facultative, alors qu'il signifie en réalité « elle peut être `None` », même si elle n'est pas facultative et reste requise. + +Je pense que `Union[SomeType, None]` est plus explicite quant à sa signification. + +Il ne s'agit que des mots et des noms. Mais ces mots peuvent influencer la manière dont vous et vos coéquipiers pensez au code. + +À titre d'exemple, prenons cette fonction : + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +Le paramètre `name` est défini comme `Optional[str]`, mais il n'est pas facultatif, vous ne pouvez pas appeler la fonction sans le paramètre : + +```Python +say_hi() # Oh non, cela lève une erreur ! 😱 +``` + +Le paramètre `name` est toujours requis (pas facultatif) car il n'a pas de valeur par défaut. En revanche, `name` accepte `None` comme valeur : + +```Python +say_hi(name=None) # Ceci fonctionne, None est valide 🎉 +``` + +La bonne nouvelle, c'est que, dans la plupart des cas, vous pourrez simplement utiliser `|` pour définir des unions de types : + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +Ainsi, normalement, vous n'avez pas à vous préoccuper de noms comme `Optional` et `Union`. 😎 diff --git a/docs/fr/docs/advanced/async-tests.md b/docs/fr/docs/advanced/async-tests.md new file mode 100644 index 000000000..f9cea0ad1 --- /dev/null +++ b/docs/fr/docs/advanced/async-tests.md @@ -0,0 +1,99 @@ +# Tests asynchrones { #async-tests } + +Vous avez déjà vu comment tester vos applications **FastAPI** en utilisant le `TestClient` fourni. Jusqu'à présent, vous n'avez vu que comment écrire des tests synchrones, sans utiliser de fonctions `async`. + +Pouvoir utiliser des fonctions asynchrones dans vos tests peut être utile, par exemple lorsque vous interrogez votre base de données de manière asynchrone. Imaginez que vous vouliez tester l'envoi de requêtes à votre application FastAPI puis vérifier que votre backend a bien écrit les bonnes données dans la base, tout en utilisant une bibliothèque de base de données asynchrone. + +Voyons comment procéder. + +## pytest.mark.anyio { #pytest-mark-anyio } + +Si nous voulons appeler des fonctions asynchrones dans nos tests, nos fonctions de test doivent être asynchrones. AnyIO fournit un plug-in pratique qui nous permet d'indiquer que certaines fonctions de test doivent être appelées de manière asynchrone. + +## HTTPX { #httpx } + +Même si votre application **FastAPI** utilise des fonctions `def` normales au lieu de `async def`, c'est toujours une application `async` en interne. + +Le `TestClient` fait un peu de magie pour appeler l'application FastAPI asynchrone depuis vos fonctions de test `def` normales, en utilisant pytest standard. Mais cette magie ne fonctionne plus lorsque nous l'utilisons dans des fonctions asynchrones. En exécutant nos tests de manière asynchrone, nous ne pouvons plus utiliser le `TestClient` dans nos fonctions de test. + +Le `TestClient` est basé sur HTTPX et, heureusement, nous pouvons l'utiliser directement pour tester l'API. + +## Exemple { #example } + +Pour un exemple simple, considérons une structure de fichiers similaire à celle décrite dans [Applications plus grandes](../tutorial/bigger-applications.md){.internal-link target=_blank} et [Tests](../tutorial/testing.md){.internal-link target=_blank} : + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Le fichier `main.py` contiendrait : + +{* ../../docs_src/async_tests/app_a_py310/main.py *} + +Le fichier `test_main.py` contiendrait les tests pour `main.py`, il pourrait maintenant ressembler à ceci : + +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} + +## Exécuter { #run-it } + +Vous pouvez lancer vos tests comme d'habitude via : + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## En détail { #in-detail } + +Le marqueur `@pytest.mark.anyio` indique à pytest que cette fonction de test doit être appelée de manière asynchrone : + +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} + +/// tip | Astuce + +Notez que la fonction de test est maintenant `async def` au lieu de simplement `def` comme auparavant avec le `TestClient`. + +/// + +Nous pouvons ensuite créer un `AsyncClient` avec l'application et lui envoyer des requêtes asynchrones en utilisant `await`. + +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} + +C'est l'équivalent de : + +```Python +response = client.get('/') +``` + +... que nous utilisions pour faire nos requêtes avec le `TestClient`. + +/// tip | Astuce + +Notez que nous utilisons async/await avec le nouveau `AsyncClient` — la requête est asynchrone. + +/// + +/// warning | Alertes + +Si votre application s'appuie sur des événements de cycle de vie (lifespan), le `AsyncClient` ne déclenchera pas ces événements. Pour vous assurer qu'ils sont déclenchés, utilisez `LifespanManager` depuis florimondmanca/asgi-lifespan. + +/// + +## Autres appels de fonctions asynchrones { #other-asynchronous-function-calls } + +Comme la fonction de test est désormais asynchrone, vous pouvez également appeler (et `await`) d'autres fonctions `async` en plus d'envoyer des requêtes à votre application FastAPI dans vos tests, exactement comme vous le feriez ailleurs dans votre code. + +/// tip | Astuce + +Si vous rencontrez une erreur `RuntimeError: Task attached to a different loop` lors de l'intégration d'appels de fonctions asynchrones dans vos tests (par exemple en utilisant MotorClient de MongoDB), n'oubliez pas d'instancier les objets qui ont besoin d'une boucle d'événements uniquement dans des fonctions async, par exemple dans un callback `@app.on_event("startup")`. + +/// diff --git a/docs/fr/docs/advanced/behind-a-proxy.md b/docs/fr/docs/advanced/behind-a-proxy.md new file mode 100644 index 000000000..4b540e1a1 --- /dev/null +++ b/docs/fr/docs/advanced/behind-a-proxy.md @@ -0,0 +1,466 @@ +# Être derrière un proxy { #behind-a-proxy } + +Dans de nombreuses situations, vous utiliserez un **proxy** comme Traefik ou Nginx devant votre application FastAPI. + +Ces proxies peuvent gérer les certificats HTTPS et d'autres aspects. + +## En-têtes transférés par le proxy { #proxy-forwarded-headers } + +Un **proxy** placé devant votre application définit normalement certains en-têtes à la volée avant d'envoyer les requêtes à votre **serveur**, afin d'indiquer au serveur que la requête a été **transférée** par le proxy, en lui donnant l'URL d'origine (publique), y compris le domaine, le fait qu'elle utilise HTTPS, etc. + +Le programme **serveur** (par exemple **Uvicorn** via **FastAPI CLI**) est capable d'interpréter ces en‑têtes, puis de transmettre ces informations à votre application. + +Mais, par sécurité, comme le serveur ne sait pas qu'il se trouve derrière un proxy de confiance, il n'interprétera pas ces en‑têtes. + +/// note | Détails techniques + +Les en-têtes du proxy sont : + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +### Activer les en-têtes transférés par le proxy { #enable-proxy-forwarded-headers } + +Vous pouvez démarrer FastAPI CLI avec l'option de CLI `--forwarded-allow-ips` et fournir les adresses IP à considérer comme fiables pour lire ces en‑têtes transférés. + +Si vous la définissez à `--forwarded-allow-ips="*"`, elle fera confiance à toutes les IP entrantes. + +Si votre **serveur** est derrière un **proxy** de confiance et que seul le proxy lui parle, cela fera accepter l'IP de ce **proxy**, quelle qu'elle soit. + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Redirections avec HTTPS { #redirects-with-https } + +Par exemple, disons que vous définissez un *chemin d'accès* `/items/` : + +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} + +Si le client essaie d'aller à `/items`, par défaut, il sera redirigé vers `/items/`. + +Mais avant de définir l'option de CLI `--forwarded-allow-ips`, il pourrait rediriger vers `http://localhost:8000/items/`. + +Mais peut‑être que votre application est hébergée à `https://mysuperapp.com`, et la redirection devrait être vers `https://mysuperapp.com/items/`. + +En définissant `--proxy-headers`, FastAPI pourra désormais rediriger vers l'emplacement correct. 😎 + +``` +https://mysuperapp.com/items/ +``` + +/// tip | Astuce + +Si vous voulez en savoir plus sur HTTPS, consultez le guide [À propos de HTTPS](../deployment/https.md){.internal-link target=_blank}. + +/// + +### Comment fonctionnent les en‑têtes transférés par le proxy { #how-proxy-forwarded-headers-work } + +Voici une représentation visuelle de la façon dont le **proxy** ajoute des en‑têtes transférés entre le client et le **serveur d'application** : + +```mermaid +sequenceDiagram + participant Client + participant Proxy as Proxy/Load Balancer + participant Server as FastAPI Server + + Client->>Proxy: HTTPS Request
Host: mysuperapp.com
Path: /items + + Note over Proxy: Proxy adds forwarded headers + + Proxy->>Server: HTTP Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items + + Note over Server: Server interprets headers
(if --forwarded-allow-ips is set) + + Server->>Proxy: HTTP Response
with correct HTTPS URLs + + Proxy->>Client: HTTPS Response +``` + +Le **proxy** intercepte la requête client d'origine et ajoute les en-têtes spéciaux *forwarded* (`X-Forwarded-*`) avant de transmettre la requête au **serveur d'application**. + +Ces en‑têtes conservent des informations sur la requête d'origine qui seraient autrement perdues : + +* **X-Forwarded-For** : l'adresse IP du client d'origine +* **X-Forwarded-Proto** : le protocole d'origine (`https`) +* **X-Forwarded-Host** : l'hôte d'origine (`mysuperapp.com`) + +Lorsque **FastAPI CLI** est configurée avec `--forwarded-allow-ips`, elle fait confiance à ces en‑têtes et les utilise, par exemple pour générer les bonnes URL dans les redirections. + +## Proxy avec un préfixe de chemin supprimé { #proxy-with-a-stripped-path-prefix } + +Vous pouvez avoir un proxy qui ajoute un préfixe de chemin à votre application. + +Dans ces cas, vous pouvez utiliser `root_path` pour configurer votre application. + +Le `root_path` est un mécanisme fourni par la spécification ASGI (sur laquelle FastAPI est construit, via Starlette). + +Le `root_path` est utilisé pour gérer ces cas spécifiques. + +Et il est également utilisé en interne lors du montage de sous‑applications. + +Avoir un proxy avec un préfixe de chemin supprimé, dans ce cas, signifie que vous pourriez déclarer un chemin à `/app` dans votre code, mais ensuite, vous ajoutez une couche au‑dessus (le proxy) qui place votre application **FastAPI** sous un chemin comme `/api/v1`. + +Dans ce cas, le chemin original `/app` serait en réalité servi à `/api/v1/app`. + +Même si tout votre code est écrit en supposant qu'il n'y a que `/app`. + +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} + +Et le proxy **« stripping »** le **préfixe de chemin** à la volée avant de transmettre la requête au serveur de l'application (probablement Uvicorn via FastAPI CLI), en gardant votre application convaincue qu'elle est servie à `/app`, afin que vous n'ayez pas à mettre à jour tout votre code pour inclure le préfixe `/api/v1`. + +Jusqu'ici, tout fonctionnerait normalement. + +Mais ensuite, lorsque vous ouvrez l'interface de documentation intégrée (le frontend), elle s'attendra à obtenir le schéma OpenAPI à `/openapi.json`, au lieu de `/api/v1/openapi.json`. + +Ainsi, le frontend (qui s'exécute dans le navigateur) essaiera d'atteindre `/openapi.json` et ne pourra pas obtenir le schéma OpenAPI. + +Parce que nous avons un proxy avec un préfixe de chemin `/api/v1` pour notre application, le frontend doit récupérer le schéma OpenAPI à `/api/v1/openapi.json`. + +```mermaid +graph LR + +browser("Browser") +proxy["Proxy on http://0.0.0.0:9999/api/v1/app"] +server["Server on http://127.0.0.1:8000/app"] + +browser --> proxy +proxy --> server +``` + +/// tip | Astuce + +L'IP `0.0.0.0` est couramment utilisée pour signifier que le programme écoute sur toutes les IP disponibles de cette machine/serveur. + +/// + +L'interface de documents doit également indiquer dans le schéma OpenAPI que ce `server` d'API se trouve à `/api/v1` (derrière le proxy). Par exemple : + +```JSON hl_lines="4-8" +{ + "openapi": "3.1.0", + // Plus d'éléments ici + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + // Plus d'éléments ici + } +} +``` + +Dans cet exemple, le « Proxy » pourrait être quelque chose comme **Traefik**. Et le serveur serait quelque chose comme FastAPI CLI avec **Uvicorn**, exécutant votre application FastAPI. + +### Fournir le `root_path` { #providing-the-root-path } + +Pour y parvenir, vous pouvez utiliser l'option de ligne de commande `--root-path` comme suit : + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Si vous utilisez Hypercorn, il dispose également de l'option `--root-path`. + +/// note | Détails techniques + +La spécification ASGI définit un `root_path` pour ce cas d'usage. + +Et l'option de ligne de commande `--root-path` fournit ce `root_path`. + +/// + +### Vérifier le `root_path` actuel { #checking-the-current-root-path } + +Vous pouvez obtenir le `root_path` actuel utilisé par votre application pour chaque requête, il fait partie du dictionnaire `scope` (qui fait partie de la spécification ASGI). + +Ici, nous l'incluons dans le message uniquement à des fins de démonstration. + +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} + +Ensuite, si vous démarrez Uvicorn avec : + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +La réponse sera semblable à : + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +### Définir le `root_path` dans l'application FastAPI { #setting-the-root-path-in-the-fastapi-app } + +Autrement, si vous n'avez pas la possibilité de fournir une option de ligne de commande comme `--root-path` ou équivalent, vous pouvez définir le paramètre `root_path` lors de la création de votre application FastAPI : + +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} + +Passer le `root_path` à `FastAPI` équivaut à passer l'option de ligne de commande `--root-path` à Uvicorn ou Hypercorn. + +### À propos de `root_path` { #about-root-path } + +Gardez à l'esprit que le serveur (Uvicorn) n'utilisera ce `root_path` que pour le transmettre à l'application. + +Mais si vous allez avec votre navigateur sur http://127.0.0.1:8000/app, vous verrez la réponse normale : + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +Donc, il ne s'attendra pas à être accessible à `http://127.0.0.1:8000/api/v1/app`. + +Uvicorn s'attendra à ce que le proxy accède à Uvicorn sur `http://127.0.0.1:8000/app`, et ce sera ensuite la responsabilité du proxy d'ajouter le préfixe supplémentaire `/api/v1` au‑dessus. + +## À propos des proxies avec un préfixe de chemin supprimé { #about-proxies-with-a-stripped-path-prefix } + +Gardez à l'esprit qu'un proxy avec préfixe de chemin supprimé n'est qu'une des façons de le configurer. + +Dans de nombreux cas, la valeur par défaut sera probablement que le proxy n'a pas de préfixe de chemin supprimé. + +Dans un cas comme celui‑ci (sans préfixe de chemin supprimé), le proxy écoutera sur quelque chose comme `https://myawesomeapp.com`, puis si le navigateur va sur `https://myawesomeapp.com/api/v1/app` et que votre serveur (par ex. Uvicorn) écoute sur `http://127.0.0.1:8000`, le proxy (sans préfixe de chemin supprimé) accédera à Uvicorn au même chemin : `http://127.0.0.1:8000/api/v1/app`. + +## Tester localement avec Traefik { #testing-locally-with-traefik } + +Vous pouvez facilement faire l'expérience en local avec un préfixe de chemin supprimé en utilisant Traefik. + +Téléchargez Traefik ; c'est un binaire unique, vous pouvez extraire le fichier compressé et l'exécuter directement depuis le terminal. + +Créez ensuite un fichier `traefik.toml` avec : + +```TOML hl_lines="3" +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +Cela indique à Traefik d'écouter sur le port 9999 et d'utiliser un autre fichier `routes.toml`. + +/// tip | Astuce + +Nous utilisons le port 9999 au lieu du port HTTP standard 80 afin que vous n'ayez pas à l'exécuter avec des privilèges administrateur (`sudo`). + +/// + +Créez maintenant cet autre fichier `routes.toml` : + +```TOML hl_lines="5 12 20" +[http] + [http.middlewares] + + [http.middlewares.api-stripprefix.stripPrefix] + prefixes = ["/api/v1"] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + middlewares = ["api-stripprefix"] + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +Ce fichier configure Traefik pour utiliser le préfixe de chemin `/api/v1`. + +Puis Traefik redirigera ses requêtes vers votre Uvicorn tournant sur `http://127.0.0.1:8000`. + +Démarrez maintenant Traefik : + +
+ +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml +``` + +
+ +Et démarrez maintenant votre application, en utilisant l'option `--root-path` : + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Vérifier les réponses { #check-the-responses } + +Maintenant, si vous allez à l'URL avec le port pour Uvicorn : http://127.0.0.1:8000/app, vous verrez la réponse normale : + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +/// tip | Astuce + +Remarquez que même si vous y accédez via `http://127.0.0.1:8000/app`, il affiche le `root_path` de `/api/v1`, repris depuis l'option `--root-path`. + +/// + +Et maintenant ouvrez l'URL avec le port pour Traefik, en incluant le préfixe de chemin : http://127.0.0.1:9999/api/v1/app. + +Nous obtenons la même réponse : + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +mais cette fois à l'URL avec le préfixe fourni par le proxy : `/api/v1`. + +Bien sûr, l'idée ici est que tout le monde accède à l'application via le proxy ; la version avec le préfixe de chemin `/api/v1` est donc la « correcte ». + +Et la version sans préfixe de chemin (`http://127.0.0.1:8000/app`), fournie directement par Uvicorn, serait exclusivement destinée au _proxy_ (Traefik) pour y accéder. + +Cela montre comment le Proxy (Traefik) utilise le préfixe de chemin et comment le serveur (Uvicorn) utilise le `root_path` fourni par l'option `--root-path`. + +### Vérifier l'interface de documentation { #check-the-docs-ui } + +Mais voici la partie intéressante. ✨ + +La manière « officielle » d'accéder à l'application serait via le proxy avec le préfixe de chemin que nous avons défini. Donc, comme on s'y attend, si vous essayez l'interface de documentation servie directement par Uvicorn, sans le préfixe de chemin dans l'URL, cela ne fonctionne pas, car elle s'attend à être accédée via le proxy. + +Vous pouvez le vérifier sur http://127.0.0.1:8000/docs : + + + +Mais si nous accédons à l'interface de documents à l'URL « officielle » en utilisant le proxy avec le port `9999`, à `/api/v1/docs`, cela fonctionne correctement ! 🎉 + +Vous pouvez le vérifier sur http://127.0.0.1:9999/api/v1/docs : + + + +Exactement comme nous le voulions. ✔️ + +C'est parce que FastAPI utilise ce `root_path` pour créer le `server` par défaut dans OpenAPI avec l'URL fournie par `root_path`. + +## Serveurs supplémentaires { #additional-servers } + +/// warning | Alertes + +Ceci est un cas d'utilisation plus avancé. N'hésitez pas à l'ignorer. + +/// + +Par défaut, **FastAPI** créera un `server` dans le schéma OpenAPI avec l'URL correspondant au `root_path`. + +Mais vous pouvez aussi fournir d'autres `servers` alternatifs, par exemple si vous voulez que la même interface de documents interagisse avec un environnement de staging et un environnement de production. + +Si vous passez une liste personnalisée de `servers` et qu'il y a un `root_path` (parce que votre API vit derrière un proxy), **FastAPI** insérera un « server » avec ce `root_path` au début de la liste. + +Par exemple : + +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} + +Générera un schéma OpenAPI comme : + +```JSON hl_lines="5-7" +{ + "openapi": "3.1.0", + // Plus d'éléments ici + "servers": [ + { + "url": "/api/v1" + }, + { + "url": "https://stag.example.com", + "description": "Staging environment" + }, + { + "url": "https://prod.example.com", + "description": "Production environment" + } + ], + "paths": { + // Plus d'éléments ici + } +} +``` + +/// tip | Astuce + +Remarquez le serveur généré automatiquement avec une valeur `url` de `/api/v1`, reprise depuis le `root_path`. + +/// + +Dans l'interface de documents sur http://127.0.0.1:9999/api/v1/docs, cela ressemblera à ceci : + + + +/// tip | Astuce + +L'interface de documents interagit avec le serveur que vous sélectionnez. + +/// + +/// note | Détails techniques + +La propriété `servers` dans la spécification OpenAPI est facultative. + +Si vous ne spécifiez pas le paramètre `servers` et que `root_path` est égal à `/`, la propriété `servers` dans le schéma OpenAPI généré sera entièrement omise par défaut, ce qui équivaut à un seul serveur avec une valeur `url` de `/`. + +/// + +### Désactiver le serveur automatique issu de `root_path` { #disable-automatic-server-from-root-path } + +Si vous ne voulez pas que **FastAPI** inclue un serveur automatique utilisant le `root_path`, vous pouvez utiliser le paramètre `root_path_in_servers=False` : + +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} + +et il ne l'inclura alors pas dans le schéma OpenAPI. + +## Monter une sous-application { #mounting-a-sub-application } + +Si vous avez besoin de monter une sous‑application (comme décrit dans [Sous‑applications - montages](sub-applications.md){.internal-link target=_blank}) tout en utilisant un proxy avec `root_path`, vous pouvez le faire normalement, comme vous vous y attendez. + +FastAPI utilisera intelligemment le `root_path` en interne, donc cela fonctionnera simplement. ✨ diff --git a/docs/fr/docs/advanced/custom-response.md b/docs/fr/docs/advanced/custom-response.md new file mode 100644 index 000000000..7eab5b53f --- /dev/null +++ b/docs/fr/docs/advanced/custom-response.md @@ -0,0 +1,312 @@ +# Réponse personnalisée - HTML, flux, fichier, autres { #custom-response-html-stream-file-others } + +Par défaut, **FastAPI** renverra les réponses en utilisant `JSONResponse`. + +Vous pouvez le remplacer en renvoyant directement une `Response` comme expliqué dans [Renvoyer directement une Response](response-directly.md){.internal-link target=_blank}. + +Mais si vous renvoyez directement une `Response` (ou n'importe quelle sous-classe, comme `JSONResponse`), les données ne seront pas automatiquement converties (même si vous déclarez un `response_model`), et la documentation ne sera pas générée automatiquement (par exemple, l'inclusion du « media type » dans l'en-tête HTTP `Content-Type` comme partie de l'OpenAPI généré). + +Vous pouvez aussi déclarer la `Response` que vous voulez utiliser (par ex. toute sous-classe de `Response`), dans le décorateur de chemin d'accès en utilisant le paramètre `response_class`. + +Le contenu que vous renvoyez depuis votre fonction de chemin d'accès sera placé à l'intérieur de cette `Response`. + +Et si cette `Response` a un « media type » JSON (`application/json`), comme c'est le cas avec `JSONResponse` et `UJSONResponse`, les données que vous renvoyez seront automatiquement converties (et filtrées) avec tout `response_model` Pydantic que vous avez déclaré dans le décorateur de chemin d'accès. + +/// note | Remarque + +Si vous utilisez une classe de réponse sans « media type », FastAPI s'attendra à ce que votre réponse n'ait pas de contenu ; il ne documentera donc pas le format de la réponse dans les documents OpenAPI générés. + +/// + +## Utiliser `ORJSONResponse` { #use-orjsonresponse } + +Par exemple, si vous cherchez à maximiser la performance, vous pouvez installer et utiliser `orjson` et définir la réponse sur `ORJSONResponse`. + +Importez la classe (sous-classe) `Response` que vous voulez utiliser et déclarez-la dans le décorateur de chemin d'accès. + +Pour de grandes réponses, renvoyer directement une `Response` est bien plus rapide que de renvoyer un dictionnaire. + +Cela vient du fait que, par défaut, FastAPI inspectera chaque élément et s'assurera qu'il est sérialisable en JSON, en utilisant le même [Encodeur compatible JSON](../tutorial/encoder.md){.internal-link target=_blank} expliqué dans le didacticiel. C'est ce qui vous permet de renvoyer des objets arbitraires, par exemple des modèles de base de données. + +Mais si vous êtes certain que le contenu que vous renvoyez est sérialisable en JSON, vous pouvez le passer directement à la classe de réponse et éviter le surcoût supplémentaire qu'aurait FastAPI en faisant passer votre contenu de retour par le `jsonable_encoder` avant de le transmettre à la classe de réponse. + +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} + +/// info + +Le paramètre `response_class` sera aussi utilisé pour définir le « media type » de la réponse. + +Dans ce cas, l'en-tête HTTP `Content-Type` sera défini à `application/json`. + +Et il sera documenté comme tel dans OpenAPI. + +/// + +/// tip | Astuce + +`ORJSONResponse` est disponible uniquement dans FastAPI, pas dans Starlette. + +/// + +## Réponse HTML { #html-response } + +Pour renvoyer une réponse avec du HTML directement depuis **FastAPI**, utilisez `HTMLResponse`. + +- Importez `HTMLResponse`. +- Passez `HTMLResponse` comme paramètre `response_class` de votre décorateur de chemin d'accès. + +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} + +/// info + +Le paramètre `response_class` sera aussi utilisé pour définir le « media type » de la réponse. + +Dans ce cas, l'en-tête HTTP `Content-Type` sera défini à `text/html`. + +Et il sera documenté comme tel dans OpenAPI. + +/// + +### Renvoyer une `Response` { #return-a-response } + +Comme vu dans [Renvoyer directement une Response](response-directly.md){.internal-link target=_blank}, vous pouvez aussi remplacer la réponse directement dans votre chemin d'accès, en la renvoyant. + +Le même exemple ci-dessus, renvoyant une `HTMLResponse`, pourrait ressembler à : + +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} + +/// warning | Alertes + +Une `Response` renvoyée directement par votre fonction de chemin d'accès ne sera pas documentée dans OpenAPI (par exemple, le `Content-Type` ne sera pas documenté) et ne sera pas visible dans les documents interactifs automatiques. + +/// + +/// info + +Bien sûr, l'en-tête `Content-Type` réel, le code d'état, etc., proviendront de l'objet `Response` que vous avez renvoyé. + +/// + +### Documenter dans OpenAPI et remplacer `Response` { #document-in-openapi-and-override-response } + +Si vous voulez remplacer la réponse depuis l'intérieur de la fonction mais en même temps documenter le « media type » dans OpenAPI, vous pouvez utiliser le paramètre `response_class` ET renvoyer un objet `Response`. + +`response_class` sera alors utilisé uniquement pour documenter l'opération de chemin d'accès OpenAPI, mais votre `Response` sera utilisée telle quelle. + +#### Renvoyer directement une `HTMLResponse` { #return-an-htmlresponse-directly } + +Par exemple, cela pourrait être quelque chose comme : + +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} + +Dans cet exemple, la fonction `generate_html_response()` génère déjà et renvoie une `Response` au lieu de renvoyer le HTML dans une `str`. + +En renvoyant le résultat de l'appel à `generate_html_response()`, vous renvoyez déjà une `Response` qui remplacera le comportement par défaut de **FastAPI**. + +Mais comme vous avez aussi passé `HTMLResponse` dans `response_class`, **FastAPI** saura comment la documenter dans OpenAPI et les documents interactifs comme HTML avec `text/html` : + + + +## Réponses disponibles { #available-responses } + +Voici certaines des réponses disponibles. + +Gardez à l'esprit que vous pouvez utiliser `Response` pour renvoyer autre chose, ou même créer une sous-classe personnalisée. + +/// note | Détails techniques + +Vous pourriez aussi utiliser `from starlette.responses import HTMLResponse`. + +**FastAPI** fournit les mêmes `starlette.responses` sous `fastapi.responses` simplement pour votre confort de développement. Mais la plupart des réponses disponibles viennent directement de Starlette. + +/// + +### `Response` { #response } + +La classe principale `Response`, toutes les autres réponses en héritent. + +Vous pouvez la renvoyer directement. + +Elle accepte les paramètres suivants : + +- `content` - Une `str` ou des `bytes`. +- `status_code` - Un code d'état HTTP de type `int`. +- `headers` - Un `dict` de chaînes. +- `media_type` - Une `str` donnant le media type. Par exemple « text/html ». + +FastAPI (en fait Starlette) inclura automatiquement un en-tête Content-Length. Il inclura aussi un en-tête Content-Type, basé sur `media_type` et en ajoutant un charset pour les types textuels. + +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} + +### `HTMLResponse` { #htmlresponse } + +Prend du texte ou des octets et renvoie une réponse HTML, comme vous l'avez lu ci-dessus. + +### `PlainTextResponse` { #plaintextresponse } + +Prend du texte ou des octets et renvoie une réponse en texte brut. + +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} + +### `JSONResponse` { #jsonresponse } + +Prend des données et renvoie une réponse encodée en `application/json`. + +C'est la réponse par défaut utilisée dans **FastAPI**, comme vous l'avez lu ci-dessus. + +### `ORJSONResponse` { #orjsonresponse } + +Une réponse JSON alternative rapide utilisant `orjson`, comme vous l'avez lu ci-dessus. + +/// info + +Cela nécessite l'installation de `orjson`, par exemple avec `pip install orjson`. + +/// + +### `UJSONResponse` { #ujsonresponse } + +Une réponse JSON alternative utilisant `ujson`. + +/// info + +Cela nécessite l'installation de `ujson`, par exemple avec `pip install ujson`. + +/// + +/// warning | Alertes + +`ujson` est moins rigoureux que l'implémentation intégrée de Python dans sa gestion de certains cas limites. + +/// + +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} + +/// tip | Astuce + +Il est possible que `ORJSONResponse` soit une alternative plus rapide. + +/// + +### `RedirectResponse` { #redirectresponse } + +Renvoie une redirection HTTP. Utilise par défaut un code d'état 307 (Temporary Redirect). + +Vous pouvez renvoyer directement une `RedirectResponse` : + +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} + +--- + +Ou vous pouvez l'utiliser dans le paramètre `response_class` : + +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} + +Si vous faites cela, vous pouvez alors renvoyer directement l'URL depuis votre fonction de chemin d'accès. + +Dans ce cas, le `status_code` utilisé sera celui par défaut pour `RedirectResponse`, c'est-à-dire `307`. + +--- + +Vous pouvez aussi utiliser le paramètre `status_code` combiné avec le paramètre `response_class` : + +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} + +### `StreamingResponse` { #streamingresponse } + +Prend un générateur async ou un générateur/itérateur normal et diffuse le corps de la réponse. + +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} + +#### Utiliser `StreamingResponse` avec des objets de type fichier { #using-streamingresponse-with-file-like-objects } + +Si vous avez un objet de type fichier (par ex. l'objet renvoyé par `open()`), vous pouvez créer une fonction génératrice pour itérer sur cet objet de type fichier. + +De cette façon, vous n'avez pas à tout lire en mémoire au préalable, et vous pouvez passer cette fonction génératrice à `StreamingResponse`, puis la renvoyer. + +Cela inclut de nombreuses bibliothèques pour interagir avec du stockage cloud, du traitement vidéo, et autres. + +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} + +1. C'est la fonction génératrice. C'est une « fonction génératrice » parce qu'elle contient des instructions `yield` à l'intérieur. +2. En utilisant un bloc `with`, nous nous assurons que l'objet de type fichier est fermé après l'exécution de la fonction génératrice. Donc, après qu'elle a fini d'envoyer la réponse. +3. Ce `yield from` indique à la fonction d'itérer sur l'objet nommé `file_like`. Puis, pour chaque partie itérée, de produire cette partie comme provenant de cette fonction génératrice (`iterfile`). + + Ainsi, c'est une fonction génératrice qui transfère le travail de « génération » à autre chose en interne. + + En procédant ainsi, nous pouvons la placer dans un bloc `with` et, de cette façon, garantir que l'objet de type fichier est fermé après la fin. + +/// tip | Astuce + +Remarquez qu'ici, comme nous utilisons le `open()` standard qui ne prend pas en charge `async` et `await`, nous déclarons le chemin d'accès avec un `def` normal. + +/// + +### `FileResponse` { #fileresponse } + +Diffuse de façon asynchrone un fichier comme réponse. + +Prend un ensemble de paramètres différent à l'instanciation par rapport aux autres types de réponse : + +- `path` - Le chemin du fichier à diffuser. +- `headers` - D'éventuels en-têtes personnalisés à inclure, sous forme de dictionnaire. +- `media_type` - Une chaîne donnant le media type. Si non défini, le nom du fichier ou le chemin sera utilisé pour en déduire un media type. +- `filename` - Si défini, sera inclus dans l'en-tête `Content-Disposition` de la réponse. + +Les réponses de type fichier incluront les en-têtes appropriés `Content-Length`, `Last-Modified` et `ETag`. + +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} + +Vous pouvez aussi utiliser le paramètre `response_class` : + +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} + +Dans ce cas, vous pouvez renvoyer directement le chemin du fichier depuis votre fonction de chemin d'accès. + +## Classe de réponse personnalisée { #custom-response-class } + +Vous pouvez créer votre propre classe de réponse personnalisée, héritant de `Response`, et l'utiliser. + +Par exemple, disons que vous voulez utiliser `orjson`, mais avec certains réglages personnalisés non utilisés dans la classe `ORJSONResponse` incluse. + +Disons que vous voulez renvoyer du JSON indenté et formaté, donc vous voulez utiliser l'option orjson `orjson.OPT_INDENT_2`. + +Vous pourriez créer une `CustomORJSONResponse`. L'essentiel est de créer une méthode `Response.render(content)` qui renvoie le contenu en `bytes` : + +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} + +Maintenant, au lieu de renvoyer : + +```json +{"message": "Hello World"} +``` + +... cette réponse renverra : + +```json +{ + "message": "Hello World" +} +``` + +Bien sûr, vous trouverez probablement des moyens bien meilleurs de tirer parti de cela que de formater du JSON. 😉 + +## Classe de réponse par défaut { #default-response-class } + +Lors de la création d'une instance de classe **FastAPI** ou d'un `APIRouter`, vous pouvez spécifier quelle classe de réponse utiliser par défaut. + +Le paramètre qui le définit est `default_response_class`. + +Dans l'exemple ci-dessous, **FastAPI** utilisera `ORJSONResponse` par défaut, dans tous les chemins d'accès, au lieu de `JSONResponse`. + +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} + +/// tip | Astuce + +Vous pouvez toujours remplacer `response_class` dans les chemins d'accès comme auparavant. + +/// + +## Documentation supplémentaire { #additional-documentation } + +Vous pouvez aussi déclarer le media type et de nombreux autres détails dans OpenAPI en utilisant `responses` : [Réponses supplémentaires dans OpenAPI](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/fr/docs/advanced/dataclasses.md b/docs/fr/docs/advanced/dataclasses.md new file mode 100644 index 000000000..2bd77157e --- /dev/null +++ b/docs/fr/docs/advanced/dataclasses.md @@ -0,0 +1,95 @@ +# Utiliser des dataclasses { #using-dataclasses } + +FastAPI est construit au‑dessus de **Pydantic**, et je vous ai montré comment utiliser des modèles Pydantic pour déclarer les requêtes et les réponses. + +Mais FastAPI prend aussi en charge l'utilisation de `dataclasses` de la même manière : + +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} + +Cela fonctionne grâce à **Pydantic**, qui offre une prise en charge interne des `dataclasses`. + +Ainsi, même avec le code ci‑dessus qui n'emploie pas explicitement Pydantic, FastAPI utilise Pydantic pour convertir ces dataclasses standard en la variante de dataclasses de Pydantic. + +Et bien sûr, cela prend en charge la même chose : + +* validation des données +* sérialisation des données +* documentation des données, etc. + +Cela fonctionne de la même manière qu'avec les modèles Pydantic. Et, en réalité, c'est mis en œuvre de la même façon en interne, en utilisant Pydantic. + +/// info | Info + +Gardez à l'esprit que les dataclasses ne peuvent pas tout ce que peuvent faire les modèles Pydantic. + +Vous pourriez donc avoir encore besoin d'utiliser des modèles Pydantic. + +Mais si vous avez déjà un ensemble de dataclasses sous la main, c'est une astuce pratique pour les utiliser afin d'alimenter une API Web avec FastAPI. 🤓 + +/// + +## Utiliser des dataclasses dans `response_model` { #dataclasses-in-response-model } + +Vous pouvez aussi utiliser `dataclasses` dans le paramètre `response_model` : + +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} + +La dataclass sera automatiquement convertie en dataclass Pydantic. + +Ainsi, son schéma apparaîtra dans l'interface utilisateur de la documentation de l'API : + + + +## Utiliser des dataclasses dans des structures de données imbriquées { #dataclasses-in-nested-data-structures } + +Vous pouvez aussi combiner `dataclasses` avec d'autres annotations de type pour créer des structures de données imbriquées. + +Dans certains cas, vous devrez peut‑être encore utiliser la version `dataclasses` de Pydantic. Par exemple, si vous rencontrez des erreurs avec la documentation d'API générée automatiquement. + +Dans ce cas, vous pouvez simplement remplacer les `dataclasses` standard par `pydantic.dataclasses`, qui est un remplacement drop‑in : + +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} + +1. Nous continuons à importer `field` depuis les `dataclasses` standard. + +2. `pydantic.dataclasses` est un remplacement drop‑in pour `dataclasses`. + +3. La dataclass `Author` inclut une liste de dataclasses `Item`. + +4. La dataclass `Author` est utilisée comme paramètre `response_model`. + +5. Vous pouvez utiliser d'autres annotations de type standard avec des dataclasses comme corps de la requête. + + Dans ce cas, il s'agit d'une liste de dataclasses `Item`. + +6. Ici, nous renvoyons un dictionnaire qui contient `items`, qui est une liste de dataclasses. + + FastAPI est toujours capable de sérialiser les données en JSON. + +7. Ici, `response_model` utilise une annotation de type correspondant à une liste de dataclasses `Author`. + + Là encore, vous pouvez combiner `dataclasses` avec des annotations de type standard. + +8. Notez que cette *fonction de chemin d'accès* utilise un `def` classique au lieu de `async def`. + + Comme toujours, avec FastAPI vous pouvez combiner `def` et `async def` selon vos besoins. + + Si vous avez besoin d'un rappel sur quand utiliser l'un ou l'autre, consultez la section _« In a hurry? »_ dans la documentation à propos de [`async` et `await`](../async.md#in-a-hurry){.internal-link target=_blank}. + +9. Cette *fonction de chemin d'accès* ne renvoie pas des dataclasses (même si elle le pourrait), mais une liste de dictionnaires contenant des données internes. + + FastAPI utilisera le paramètre `response_model` (qui inclut des dataclasses) pour convertir la réponse. + +Vous pouvez combiner `dataclasses` avec d'autres annotations de type, selon de nombreuses combinaisons, pour former des structures de données complexes. + +Reportez‑vous aux annotations dans le code ci‑dessus pour voir plus de détails spécifiques. + +## En savoir plus { #learn-more } + +Vous pouvez aussi combiner `dataclasses` avec d'autres modèles Pydantic, en hériter, les inclure dans vos propres modèles, etc. + +Pour en savoir plus, consultez la documentation Pydantic sur les dataclasses. + +## Version { #version } + +C'est disponible depuis FastAPI version `0.67.0`. 🔖 diff --git a/docs/fr/docs/advanced/events.md b/docs/fr/docs/advanced/events.md new file mode 100644 index 000000000..6d0907a8b --- /dev/null +++ b/docs/fr/docs/advanced/events.md @@ -0,0 +1,165 @@ +# Événements de cycle de vie { #lifespan-events } + +Vous pouvez définir une logique (du code) qui doit être exécutée avant que l'application ne **démarre**. Cela signifie que ce code sera exécuté **une seule fois**, **avant** que l'application ne **commence à recevoir des requêtes**. + +De la même manière, vous pouvez définir une logique (du code) qui doit être exécutée lorsque l'application **s'arrête**. Dans ce cas, ce code sera exécuté **une seule fois**, **après** avoir traité potentiellement **de nombreuses requêtes**. + +Comme ce code est exécuté avant que l'application ne **commence** à recevoir des requêtes, et juste après qu'elle **termine** de les traiter, il couvre tout le **cycle de vie** de l'application (le mot « lifespan » va être important dans un instant 😉). + +Cela peut être très utile pour configurer des **ressources** dont vous avez besoin pour l'ensemble de l'application, qui sont **partagées** entre les requêtes, et/ou que vous devez **nettoyer** ensuite. Par exemple, un pool de connexions à une base de données, ou le chargement d'un modèle d'apprentissage automatique partagé. + +## Cas d'utilisation { #use-case } + +Commençons par un exemple de **cas d'utilisation**, puis voyons comment le résoudre avec ceci. + +Imaginons que vous ayez des **modèles d'apprentissage automatique** que vous souhaitez utiliser pour traiter des requêtes. 🤖 + +Les mêmes modèles sont partagés entre les requêtes, ce n'est donc pas un modèle par requête, ni un par utilisateur, ou quelque chose de similaire. + +Imaginons que le chargement du modèle puisse **prendre pas mal de temps**, car il doit lire beaucoup de **données depuis le disque**. Vous ne voulez donc pas le faire pour chaque requête. + +Vous pourriez le charger au niveau supérieur du module/fichier, mais cela signifierait aussi qu'il **chargerait le modèle** même si vous exécutez simplement un test automatisé simple ; ce test serait alors **lent** car il devrait attendre le chargement du modèle avant de pouvoir exécuter une partie indépendante du code. + +C'est ce que nous allons résoudre : chargeons le modèle avant que les requêtes ne soient traitées, mais seulement juste avant que l'application ne commence à recevoir des requêtes, pas pendant le chargement du code. + +## Cycle de vie { #lifespan } + +Vous pouvez définir cette logique de *démarrage* et d'*arrêt* en utilisant le paramètre `lifespan` de l'application `FastAPI`, et un « gestionnaire de contexte » (je vais vous montrer ce que c'est dans un instant). + +Commençons par un exemple, puis voyons-le en détail. + +Nous créons une fonction async `lifespan()` avec `yield` comme ceci : + +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} + +Ici, nous simulons l'opération de *démarrage* coûteuse de chargement du modèle en plaçant la fonction (factice) du modèle dans le dictionnaire avec les modèles d'apprentissage automatique avant le `yield`. Ce code sera exécuté **avant** que l'application ne **commence à recevoir des requêtes**, pendant le *démarrage*. + +Puis, juste après le `yield`, nous déchargeons le modèle. Ce code sera exécuté **après** que l'application **a fini de traiter les requêtes**, juste avant l'*arrêt*. Cela pourrait, par exemple, libérer des ressources comme la mémoire ou un GPU. + +/// tip | Astuce + +L’« arrêt » se produit lorsque vous **arrêtez** l'application. + +Peut-être devez-vous démarrer une nouvelle version, ou vous en avez simplement assez de l'exécuter. 🤷 + +/// + +### Fonction de cycle de vie { #lifespan-function } + +La première chose à remarquer est que nous définissons une fonction async avec `yield`. C'est très similaire aux Dépendances avec `yield`. + +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} + +La première partie de la fonction, avant le `yield`, sera exécutée **avant** le démarrage de l'application. + +Et la partie après le `yield` sera exécutée **après** que l'application a terminé. + +### Gestionnaire de contexte asynchrone { #async-context-manager } + +Si vous regardez, la fonction est décorée avec `@asynccontextmanager`. + +Cela convertit la fonction en quelque chose appelé un « **gestionnaire de contexte asynchrone** ». + +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} + +Un **gestionnaire de contexte** en Python est quelque chose que vous pouvez utiliser dans une instruction `with`. Par exemple, `open()` peut être utilisé comme gestionnaire de contexte : + +```Python +with open("file.txt") as file: + file.read() +``` + +Dans les versions récentes de Python, il existe aussi un **gestionnaire de contexte asynchrone**. Vous l'utiliseriez avec `async with` : + +```Python +async with lifespan(app): + await do_stuff() +``` + +Quand vous créez un gestionnaire de contexte ou un gestionnaire de contexte asynchrone comme ci-dessus, ce qu'il fait, c'est qu'avant d'entrer dans le bloc `with`, il exécute le code avant le `yield`, et après être sorti du bloc `with`, il exécute le code après le `yield`. + +Dans notre exemple de code ci-dessus, nous ne l'utilisons pas directement, mais nous le transmettons à FastAPI pour qu'il l'utilise. + +Le paramètre `lifespan` de l'application `FastAPI` accepte un **gestionnaire de contexte asynchrone**, nous pouvons donc lui passer notre nouveau gestionnaire de contexte asynchrone `lifespan`. + +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} + +## Événements alternatifs (déprécié) { #alternative-events-deprecated } + +/// warning | Alertes + +La méthode recommandée pour gérer le *démarrage* et l'*arrêt* est d'utiliser le paramètre `lifespan` de l'application `FastAPI` comme décrit ci-dessus. Si vous fournissez un paramètre `lifespan`, les gestionnaires d'événements `startup` et `shutdown` ne seront plus appelés. C'est soit tout en `lifespan`, soit tout en événements, pas les deux. + +Vous pouvez probablement passer cette partie. + +/// + +Il existe une autre manière de définir cette logique à exécuter au *démarrage* et à l'*arrêt*. + +Vous pouvez définir des gestionnaires d'événements (fonctions) qui doivent être exécutés avant le démarrage de l'application, ou lorsque l'application s'arrête. + +Ces fonctions peuvent être déclarées avec `async def` ou un `def` normal. + +### Événement `startup` { #startup-event } + +Pour ajouter une fonction qui doit être exécutée avant le démarrage de l'application, déclarez-la avec l'événement « startup » : + +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} + +Dans ce cas, la fonction gestionnaire de l'événement `startup` initialisera la « base de données » des items (juste un `dict`) avec quelques valeurs. + +Vous pouvez ajouter plusieurs fonctions de gestion d'événements. + +Et votre application ne commencera pas à recevoir des requêtes avant que tous les gestionnaires de l'événement `startup` aient terminé. + +### Événement `shutdown` { #shutdown-event } + +Pour ajouter une fonction qui doit être exécutée lorsque l'application s'arrête, déclarez-la avec l'événement « shutdown » : + +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} + +Ici, la fonction gestionnaire de l'événement `shutdown` écrira une ligne de texte « Application shutdown » dans un fichier `log.txt`. + +/// info + +Dans la fonction `open()`, le `mode="a"` signifie « append » (ajouter) ; la ligne sera donc ajoutée après ce qui se trouve déjà dans ce fichier, sans écraser le contenu précédent. + +/// + +/// tip | Astuce + +Notez que dans ce cas, nous utilisons une fonction Python standard `open()` qui interagit avec un fichier. + +Cela implique des E/S (input/output), qui nécessitent « d'attendre » que des choses soient écrites sur le disque. + +Mais `open()` n'utilise pas `async` et `await`. + +Nous déclarons donc la fonction gestionnaire d'événement avec un `def` standard plutôt qu'avec `async def`. + +/// + +### `startup` et `shutdown` ensemble { #startup-and-shutdown-together } + +Il y a de fortes chances que la logique de votre *démarrage* et de votre *arrêt* soit liée : vous pourriez vouloir démarrer quelque chose puis le terminer, acquérir une ressource puis la libérer, etc. + +Faire cela dans des fonctions séparées qui ne partagent pas de logique ni de variables est plus difficile, car vous devriez stocker des valeurs dans des variables globales ou recourir à des astuces similaires. + +Pour cette raison, il est désormais recommandé d'utiliser plutôt le `lifespan` comme expliqué ci-dessus. + +## Détails techniques { #technical-details } + +Juste un détail technique pour les nerds curieux. 🤓 + +Sous le capot, dans la spécification technique ASGI, cela fait partie du protocole Lifespan, et il y définit des événements appelés `startup` et `shutdown`. + +/// info + +Vous pouvez en lire plus sur les gestionnaires `lifespan` de Starlette dans la documentation « Lifespan » de Starlette. + +Y compris comment gérer l'état de cycle de vie qui peut être utilisé dans d'autres parties de votre code. + +/// + +## Sous-applications { #sub-applications } + +🚨 Gardez à l'esprit que ces événements de cycle de vie (démarrage et arrêt) ne seront exécutés que pour l'application principale, pas pour [Sous-applications - Montages](sub-applications.md){.internal-link target=_blank}. diff --git a/docs/fr/docs/advanced/generate-clients.md b/docs/fr/docs/advanced/generate-clients.md new file mode 100644 index 000000000..6f51ac7be --- /dev/null +++ b/docs/fr/docs/advanced/generate-clients.md @@ -0,0 +1,208 @@ +# Générer des SDK { #generating-sdks } + +Parce que **FastAPI** est basé sur la spécification **OpenAPI**, ses API peuvent être décrites dans un format standard compris par de nombreux outils. + +Cela facilite la génération de **documentation** à jour, de bibliothèques clientes (**SDKs**) dans plusieurs langages, ainsi que de **tests** ou de **workflows d’automatisation** qui restent synchronisés avec votre code. + +Dans ce guide, vous apprendrez à générer un **SDK TypeScript** pour votre backend FastAPI. + +## Générateurs de SDK open source { #open-source-sdk-generators } + +Une option polyvalente est OpenAPI Generator, qui prend en charge **de nombreux langages de programmation** et peut générer des SDK à partir de votre spécification OpenAPI. + +Pour les **clients TypeScript**, Hey API est une solution dédiée, offrant une expérience optimisée pour l’écosystème TypeScript. + +Vous pouvez découvrir davantage de générateurs de SDK sur OpenAPI.Tools. + +/// tip | Astuce + +FastAPI génère automatiquement des spécifications **OpenAPI 3.1**, donc tout outil que vous utilisez doit prendre en charge cette version. + +/// + +## Générateurs de SDK par les sponsors de FastAPI { #sdk-generators-from-fastapi-sponsors } + +Cette section met en avant des solutions **soutenues par des fonds** et **par des entreprises** qui sponsorisent FastAPI. Ces produits offrent **des fonctionnalités supplémentaires** et **des intégrations** en plus de SDK de haute qualité générés. + +En ✨ [**sponsorisant FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, ces entreprises contribuent à garantir que le framework et son **écosystème** restent sains et **durables**. + +Leur sponsoring démontre également un fort engagement envers la **communauté** FastAPI (vous), montrant qu’elles se soucient non seulement d’offrir un **excellent service**, mais aussi de soutenir un **framework robuste et florissant**, FastAPI. 🙇 + +Par exemple, vous pourriez essayer : + +* Speakeasy +* Stainless +* liblab + +Certaines de ces solutions peuvent aussi être open source ou proposer des niveaux gratuits, afin que vous puissiez les essayer sans engagement financier. D’autres générateurs de SDK commerciaux existent et peuvent être trouvés en ligne. 🤓 + +## Créer un SDK TypeScript { #create-a-typescript-sdk } + +Commençons par une application FastAPI simple : + +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} + +Remarquez que les *chemins d'accès* définissent les modèles qu’ils utilisent pour le payload de requête et le payload de réponse, en utilisant les modèles `Item` et `ResponseMessage`. + +### Documentation de l’API { #api-docs } + +Si vous allez sur `/docs`, vous verrez qu’elle contient les **schémas** pour les données à envoyer dans les requêtes et reçues dans les réponses : + + + +Vous voyez ces schémas parce qu’ils ont été déclarés avec les modèles dans l’application. + +Ces informations sont disponibles dans le **schéma OpenAPI** de l’application, puis affichées dans la documentation de l’API. + +Ces mêmes informations issues des modèles, incluses dans OpenAPI, peuvent être utilisées pour **générer le code client**. + +### Hey API { #hey-api } + +Une fois que vous avez une application FastAPI avec les modèles, vous pouvez utiliser Hey API pour générer un client TypeScript. Le moyen le plus rapide de le faire est via npx. + +```sh +npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client +``` + +Cela générera un SDK TypeScript dans `./src/client`. + +Vous pouvez apprendre à installer `@hey-api/openapi-ts` et lire à propos du résultat généré sur leur site. + +### Utiliser le SDK { #using-the-sdk } + +Vous pouvez maintenant importer et utiliser le code client. Cela pourrait ressembler à ceci, remarquez que vous obtenez l’autocomplétion pour les méthodes : + + + +Vous obtiendrez également l’autocomplétion pour le payload à envoyer : + + + +/// tip | Astuce + +Remarquez l’autocomplétion pour `name` et `price`, qui a été définie dans l’application FastAPI, dans le modèle `Item`. + +/// + +Vous aurez des erreurs en ligne pour les données que vous envoyez : + + + +L’objet de réponse aura également l’autocomplétion : + + + +## Application FastAPI avec des tags { #fastapi-app-with-tags } + +Dans de nombreux cas, votre application FastAPI sera plus grande, et vous utiliserez probablement des tags pour séparer différents groupes de *chemins d'accès*. + +Par exemple, vous pourriez avoir une section pour les **items** et une autre section pour les **users**, et elles pourraient être séparées par des tags : + +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} + +### Générer un client TypeScript avec des tags { #generate-a-typescript-client-with-tags } + +Si vous générez un client pour une application FastAPI utilisant des tags, il séparera normalement aussi le code client en fonction des tags. + +De cette façon, vous pourrez avoir les éléments ordonnés et correctement groupés côté client : + + + +Dans ce cas, vous avez : + +* `ItemsService` +* `UsersService` + +### Noms des méthodes du client { #client-method-names } + +À l’heure actuelle, les noms de méthodes générés comme `createItemItemsPost` ne sont pas très propres : + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +... c’est parce que le générateur de client utilise l’**operation ID** interne OpenAPI pour chaque *chemin d'accès*. + +OpenAPI exige que chaque operation ID soit unique parmi tous les *chemins d'accès*, donc FastAPI utilise le **nom de la fonction**, le **chemin**, et la **méthode/opération HTTP** pour générer cet operation ID, car de cette façon il peut s’assurer que les operation IDs sont uniques. + +Mais je vais vous montrer comment améliorer cela ensuite. 🤓 + +## IDs d’opération personnalisés et meilleurs noms de méthodes { #custom-operation-ids-and-better-method-names } + +Vous pouvez **modifier** la façon dont ces operation IDs sont **générés** pour les simplifier et obtenir des **noms de méthodes plus simples** dans les clients. + +Dans ce cas, vous devez vous assurer que chaque operation ID est **unique** d’une autre manière. + +Par exemple, vous pouvez vous assurer que chaque *chemin d'accès* a un tag, puis générer l’operation ID à partir du **tag** et du **nom** du *chemin d'accès* (le nom de la fonction). + +### Fonction personnalisée de génération d’ID unique { #custom-generate-unique-id-function } + +FastAPI utilise un **ID unique** pour chaque *chemin d'accès*, qui est utilisé pour l’**operation ID** et également pour les noms des modèles personnalisés nécessaires, pour les requêtes ou les réponses. + +Vous pouvez personnaliser cette fonction. Elle prend un `APIRoute` et retourne une chaîne. + +Par exemple, ici elle utilise le premier tag (vous n’en aurez probablement qu’un) et le nom du *chemin d'accès* (le nom de la fonction). + +Vous pouvez ensuite passer cette fonction personnalisée à **FastAPI** via le paramètre `generate_unique_id_function` : + +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} + +### Générer un client TypeScript avec des IDs d’opération personnalisés { #generate-a-typescript-client-with-custom-operation-ids } + +Maintenant, si vous régénérez le client, vous verrez qu’il possède des noms de méthodes améliorés : + + + +Comme vous le voyez, les noms de méthodes contiennent maintenant le tag puis le nom de la fonction ; ils n’incluent plus d’informations provenant du chemin d’URL et de l’opération HTTP. + +### Prétraiter la spécification OpenAPI pour le générateur de client { #preprocess-the-openapi-specification-for-the-client-generator } + +Le code généré contient encore des **informations dupliquées**. + +Nous savons déjà que cette méthode est liée aux **items** parce que ce mot figure dans `ItemsService` (issu du tag), mais nous avons encore le nom du tag préfixé dans le nom de la méthode. 😕 + +Nous voudrons probablement le conserver pour OpenAPI en général, car cela garantira que les operation IDs sont **uniques**. + +Mais pour le client généré, nous pourrions **modifier** les operation IDs d’OpenAPI juste avant de générer les clients, simplement pour rendre ces noms de méthodes plus agréables et **plus clairs**. + +Nous pourrions télécharger le JSON OpenAPI dans un fichier `openapi.json` puis **supprimer ce tag préfixé** avec un script comme celui-ci : + +{* ../../docs_src/generate_clients/tutorial004_py310.py *} + +//// tab | Node.js + +```Javascript +{!> ../../docs_src/generate_clients/tutorial004.js!} +``` + +//// + +Avec cela, les operation IDs seraient renommés de `items-get_items` en simplement `get_items`, de sorte que le générateur de client puisse produire des noms de méthodes plus simples. + +### Générer un client TypeScript avec l’OpenAPI prétraité { #generate-a-typescript-client-with-the-preprocessed-openapi } + +Puisque le résultat final se trouve maintenant dans un fichier `openapi.json`, vous devez mettre à jour l’emplacement d’entrée : + +```sh +npx @hey-api/openapi-ts -i ./openapi.json -o src/client +``` + +Après avoir généré le nouveau client, vous aurez désormais des **noms de méthodes propres**, avec toute l’**autocomplétion**, les **erreurs en ligne**, etc. : + + + +## Avantages { #benefits } + +En utilisant les clients générés automatiquement, vous obtiendrez de l’**autocomplétion** pour : + +* Méthodes. +* Payloads de requête dans le corps, paramètres de requête, etc. +* Payloads de réponse. + +Vous auriez également des **erreurs en ligne** pour tout. + +Et chaque fois que vous mettez à jour le code du backend et **régénérez** le frontend, il inclura les nouveaux *chemins d'accès* disponibles en tant que méthodes, supprimera les anciens, et tout autre changement sera reflété dans le code généré. 🤓 + +Cela signifie aussi que si quelque chose change, cela sera **reflété** automatiquement dans le code client. Et si vous **bâtissez** le client, il échouera en cas de **discordance** dans les données utilisées. + +Ainsi, vous **détecterez de nombreuses erreurs** très tôt dans le cycle de développement au lieu d’attendre qu’elles apparaissent pour vos utilisateurs finaux en production puis de tenter de déboguer l’origine du problème. ✨ diff --git a/docs/fr/docs/advanced/middleware.md b/docs/fr/docs/advanced/middleware.md new file mode 100644 index 000000000..934c91041 --- /dev/null +++ b/docs/fr/docs/advanced/middleware.md @@ -0,0 +1,97 @@ +# Utiliser des middlewares avancés { #advanced-middleware } + +Dans le tutoriel principal, vous avez vu comment ajouter des [middlewares personnalisés](../tutorial/middleware.md){.internal-link target=_blank} à votre application. + +Vous avez également vu comment gérer [CORS avec le `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. + +Dans cette section, nous allons voir comment utiliser d'autres middlewares. + +## Ajouter des middlewares ASGI { #adding-asgi-middlewares } + +Comme **FastAPI** est basé sur Starlette et implémente la spécification ASGI, vous pouvez utiliser n'importe quel middleware ASGI. + +Un middleware n'a pas besoin d'être conçu pour FastAPI ou Starlette pour fonctionner, tant qu'il suit la spécification ASGI. + +En général, les middlewares ASGI sont des classes qui s'attendent à recevoir une application ASGI en premier argument. + +Ainsi, dans la documentation de middlewares ASGI tiers, on vous indiquera probablement de faire quelque chose comme : + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +Mais FastAPI (en fait Starlette) fournit une manière plus simple de le faire, qui garantit que les middlewares internes gèrent les erreurs serveur et que les gestionnaires d'exceptions personnalisés fonctionnent correctement. + +Pour cela, vous utilisez `app.add_middleware()` (comme dans l'exemple pour CORS). + +```Python +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +`app.add_middleware()` reçoit une classe de middleware en premier argument, ainsi que tout argument supplémentaire à transmettre au middleware. + +## Utiliser les middlewares intégrés { #integrated-middlewares } + +**FastAPI** inclut plusieurs middlewares pour des cas d'usage courants ; voyons comment les utiliser. + +/// note | Détails techniques + +Pour les prochains exemples, vous pourriez aussi utiliser `from starlette.middleware.something import SomethingMiddleware`. + +**FastAPI** fournit plusieurs middlewares dans `fastapi.middleware` simplement pour vous faciliter la vie, en tant que développeur. Mais la plupart des middlewares disponibles viennent directement de Starlette. + +/// + +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } + +Impose que toutes les requêtes entrantes soient soit `https`, soit `wss`. + +Toute requête entrante en `http` ou `ws` sera redirigée vers le schéma sécurisé correspondant. + +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} + +## `TrustedHostMiddleware` { #trustedhostmiddleware } + +Impose que toutes les requêtes entrantes aient un en-tête `Host` correctement défini, afin de se prémunir contre les attaques de type HTTP Host Header. + +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} + +Les arguments suivants sont pris en charge : + +- `allowed_hosts` - Une liste de noms de domaine autorisés comme noms d'hôte. Les domaines génériques tels que `*.example.com` sont pris en charge pour faire correspondre les sous-domaines. Pour autoriser n'importe quel nom d'hôte, utilisez `allowed_hosts=["*"]` ou omettez le middleware. +- `www_redirect` - Si défini à `True`, les requêtes vers les versions sans www des hôtes autorisés seront redirigées vers leurs équivalents avec www. Valeur par défaut : `True`. + +Si une requête entrante n'est pas valide, une réponse `400` sera envoyée. + +## `GZipMiddleware` { #gzipmiddleware } + +Gère les réponses GZip pour toute requête qui inclut « gzip » dans l'en-tête `Accept-Encoding`. + +Le middleware gérera les réponses standard et en streaming. + +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} + +Les arguments suivants sont pris en charge : + +- `minimum_size` - Ne pas compresser en GZip les réponses dont la taille est inférieure à ce minimum en octets. Valeur par défaut : `500`. +- `compresslevel` - Utilisé pendant la compression GZip. Entier compris entre 1 et 9. Valeur par défaut : `9`. Une valeur plus faible entraîne une compression plus rapide mais des fichiers plus volumineux, tandis qu'une valeur plus élevée entraîne une compression plus lente mais des fichiers plus petits. + +## Autres middlewares { #other-middlewares } + +Il existe de nombreux autres middlewares ASGI. + +Par exemple : + +- Le `ProxyHeadersMiddleware` d'Uvicorn +- MessagePack + +Pour voir d'autres middlewares disponibles, consultez la documentation des middlewares de Starlette et la liste ASGI Awesome. diff --git a/docs/fr/docs/advanced/openapi-callbacks.md b/docs/fr/docs/advanced/openapi-callbacks.md new file mode 100644 index 000000000..669d9447a --- /dev/null +++ b/docs/fr/docs/advanced/openapi-callbacks.md @@ -0,0 +1,186 @@ +# Callbacks OpenAPI { #openapi-callbacks } + +Vous pourriez créer une API avec un *chemin d'accès* qui déclenche une requête vers une *API externe* créée par quelqu'un d'autre (probablement la même personne développeuse qui utiliserait votre API). + +Le processus qui se produit lorsque votre application API appelle l’*API externe* s’appelle un « callback ». Parce que le logiciel écrit par la personne développeuse externe envoie une requête à votre API puis votre API « rappelle », en envoyant une requête à une *API externe* (probablement créée par la même personne développeuse). + +Dans ce cas, vous pourriez vouloir documenter à quoi cette API externe devrait ressembler. Quel *chemin d'accès* elle devrait avoir, quel corps elle devrait attendre, quelle réponse elle devrait renvoyer, etc. + +## Une application avec des callbacks { #an-app-with-callbacks } + +Voyons tout cela avec un exemple. + +Imaginez que vous développiez une application qui permet de créer des factures. + +Ces factures auront un `id`, un `title` (facultatif), un `customer` et un `total`. + +L’utilisateur de votre API (une personne développeuse externe) créera une facture dans votre API avec une requête POST. + +Ensuite votre API va (imaginons) : + +* Envoyer la facture à un client de la personne développeuse externe. +* Encaisser l’argent. +* Renvoyer une notification à l’utilisateur de l’API (la personne développeuse externe). + * Cela sera fait en envoyant une requête POST (depuis *votre API*) vers une *API externe* fournie par cette personne développeuse externe (c’est le « callback »). + +## L’application **FastAPI** normale { #the-normal-fastapi-app } + +Voyons d’abord à quoi ressemble l’application API normale avant d’ajouter le callback. + +Elle aura un *chemin d'accès* qui recevra un corps `Invoice`, et un paramètre de requête `callback_url` qui contiendra l’URL pour le callback. + +Cette partie est assez normale, la plupart du code vous est probablement déjà familier : + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} + +/// tip | Astuce + +Le paramètre de requête `callback_url` utilise un type Pydantic Url. + +/// + +La seule nouveauté est `callbacks=invoices_callback_router.routes` comme argument du *décorateur de chemin d'accès*. Nous allons voir ce que c’est ensuite. + +## Documenter le callback { #documenting-the-callback } + +Le code réel du callback dépendra fortement de votre application API. + +Et il variera probablement beaucoup d’une application à l’autre. + +Cela pourrait être seulement une ou deux lignes de code, comme : + +```Python +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +Mais la partie la plus importante du callback est sans doute de vous assurer que l’utilisateur de votre API (la personne développeuse externe) implémente correctement l’*API externe*, conformément aux données que *votre API* va envoyer dans le corps de la requête du callback, etc. + +Ainsi, ce que nous allons faire ensuite, c’est ajouter le code pour documenter à quoi cette *API externe* devrait ressembler pour recevoir le callback de *votre API*. + +Cette documentation apparaîtra dans Swagger UI à `/docs` dans votre API, et permettra aux personnes développeuses externes de savoir comment construire l’*API externe*. + +Cet exemple n’implémente pas le callback lui-même (qui pourrait être une simple ligne de code), uniquement la partie documentation. + +/// tip | Astuce + +Le callback réel n’est qu’une requête HTTP. + +En implémentant vous-même le callback, vous pourriez utiliser quelque chose comme HTTPX ou Requests. + +/// + +## Écrire le code de documentation du callback { #write-the-callback-documentation-code } + +Ce code ne sera pas exécuté dans votre application, nous en avons seulement besoin pour *documenter* à quoi devrait ressembler cette *API externe*. + +Mais vous savez déjà comment créer facilement une documentation automatique pour une API avec **FastAPI**. + +Nous allons donc utiliser ce même savoir pour documenter à quoi l’*API externe* devrait ressembler ... en créant le(s) *chemin(s) d'accès* que l’API externe devrait implémenter (ceux que votre API appellera). + +/// tip | Astuce + +Lorsque vous écrivez le code pour documenter un callback, il peut être utile d’imaginer que vous êtes cette *personne développeuse externe*. Et que vous implémentez actuellement l’*API externe*, pas *votre API*. + +Adopter temporairement ce point de vue (celui de la *personne développeuse externe*) peut vous aider à trouver plus évident où placer les paramètres, le modèle Pydantic pour le corps, pour la réponse, etc., pour cette *API externe*. + +/// + +### Créer un `APIRouter` de callback { #create-a-callback-apirouter } + +Commencez par créer un nouveau `APIRouter` qui contiendra un ou plusieurs callbacks. + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *} + +### Créer le *chemin d'accès* du callback { #create-the-callback-path-operation } + +Pour créer le *chemin d'accès* du callback, utilisez le même `APIRouter` que vous avez créé ci-dessus. + +Il devrait ressembler exactement à un *chemin d'accès* FastAPI normal : + +* Il devrait probablement déclarer le corps qu’il doit recevoir, par exemple `body: InvoiceEvent`. +* Et il pourrait aussi déclarer la réponse qu’il doit renvoyer, par exemple `response_model=InvoiceEventReceived`. + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} + +Il y a 2 principales différences par rapport à un *chemin d'accès* normal : + +* Il n’a pas besoin d’avoir de code réel, car votre application n’appellera jamais ce code. Il sert uniquement à documenter l’*API externe*. La fonction peut donc simplement contenir `pass`. +* Le *chemin* peut contenir une expression OpenAPI 3 (voir plus bas) où il peut utiliser des variables avec des paramètres et des parties de la requête originale envoyée à *votre API*. + +### L’expression du chemin de callback { #the-callback-path-expression } + +Le *chemin* du callback peut contenir une expression OpenAPI 3 qui peut inclure des parties de la requête originale envoyée à *votre API*. + +Dans ce cas, c’est la `str` : + +```Python +"{$callback_url}/invoices/{$request.body.id}" +``` + +Ainsi, si l’utilisateur de votre API (la personne développeuse externe) envoie une requête à *votre API* vers : + +``` +https://yourapi.com/invoices/?callback_url=https://www.external.org/events +``` + +avec un corps JSON : + +```JSON +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +alors *votre API* traitera la facture et, à un moment ultérieur, enverra une requête de callback à `callback_url` (l’*API externe*) : + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +avec un corps JSON contenant quelque chose comme : + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +et elle s’attendra à une réponse de cette *API externe* avec un corps JSON comme : + +```JSON +{ + "ok": true +} +``` + +/// tip | Astuce + +Remarquez que l’URL de callback utilisée contient l’URL reçue en paramètre de requête dans `callback_url` (`https://www.external.org/events`) et aussi l’`id` de la facture à l’intérieur du corps JSON (`2expen51ve`). + +/// + +### Ajouter le routeur de callback { #add-the-callback-router } + +À ce stade, vous avez le(s) *chemin(s) d'accès de callback* nécessaire(s) (celui/ceux que la *personne développeuse externe* doit implémenter dans l’*API externe*) dans le routeur de callback que vous avez créé ci-dessus. + +Utilisez maintenant le paramètre `callbacks` dans *le décorateur de chemin d'accès de votre API* pour passer l’attribut `.routes` (qui est en fait juste une `list` de routes/*chemins d'accès*) depuis ce routeur de callback : + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} + +/// tip | Astuce + +Remarquez que vous ne passez pas le routeur lui-même (`invoices_callback_router`) à `callback=`, mais l’attribut `.routes`, comme dans `invoices_callback_router.routes`. + +/// + +### Vérifier la documentation { #check-the-docs } + +Vous pouvez maintenant démarrer votre application et aller sur http://127.0.0.1:8000/docs. + +Vous verrez votre documentation incluant une section « Callbacks » pour votre *chemin d'accès* qui montre à quoi l’*API externe* devrait ressembler : + + diff --git a/docs/fr/docs/advanced/openapi-webhooks.md b/docs/fr/docs/advanced/openapi-webhooks.md new file mode 100644 index 000000000..21b6f5f00 --- /dev/null +++ b/docs/fr/docs/advanced/openapi-webhooks.md @@ -0,0 +1,55 @@ +# Webhooks OpenAPI { #openapi-webhooks } + +Il existe des cas où vous voulez informer les utilisateurs de votre API que votre application peut appeler leur application (en envoyant une requête) avec des données, généralement pour notifier un type d'événement. + +Cela signifie qu'au lieu du processus habituel où vos utilisateurs envoient des requêtes à votre API, c'est votre API (ou votre application) qui peut envoyer des requêtes vers leur système (vers leur API, leur application). + +On appelle généralement cela un webhook. + +## Étapes des webhooks { #webhooks-steps } + +Le processus consiste généralement à définir dans votre code le message que vous enverrez, c'est-à-dire le corps de la requête. + +Vous définissez également, d'une manière ou d'une autre, à quels moments votre application enverra ces requêtes ou événements. + +Et vos utilisateurs définissent aussi, d'une manière ou d'une autre (par exemple dans un tableau de bord Web), l'URL à laquelle votre application doit envoyer ces requêtes. + +Toute la logique de gestion des URL des webhooks et le code qui envoie effectivement ces requêtes vous incombent. Vous l'implémentez comme vous le souhaitez dans votre propre code. + +## Documenter des webhooks avec FastAPI et OpenAPI { #documenting-webhooks-with-fastapi-and-openapi } + +Avec FastAPI, en utilisant OpenAPI, vous pouvez définir les noms de ces webhooks, les types d'opérations HTTP que votre application peut envoyer (par exemple `POST`, `PUT`, etc.) et les corps des requêtes que votre application enverra. + +Cela peut grandement faciliter la tâche de vos utilisateurs pour implémenter leurs API afin de recevoir vos requêtes de webhook ; ils pourront même peut-être générer automatiquement une partie de leur propre code d'API. + +/// info + +Les webhooks sont disponibles dans OpenAPI 3.1.0 et versions ultérieures, pris en charge par FastAPI `0.99.0` et versions ultérieures. + +/// + +## Créer une application avec des webhooks { #an-app-with-webhooks } + +Lorsque vous créez une application FastAPI, il existe un attribut `webhooks` que vous pouvez utiliser pour définir des webhooks, de la même manière que vous définiriez des chemins d'accès, par exemple avec `@app.webhooks.post()`. + +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} + +Les webhooks que vous définissez apparaîtront dans le schéma **OpenAPI** et dans l'interface de **documentation** automatique. + +/// info + +L'objet `app.webhooks` est en fait simplement un `APIRouter`, le même type que vous utiliseriez pour structurer votre application en plusieurs fichiers. + +/// + +Notez qu'avec les webhooks, vous ne déclarez pas réellement un chemin (comme `/items/`), le texte que vous y passez est simplement un identifiant du webhook (le nom de l'événement). Par exemple, dans `@app.webhooks.post("new-subscription")`, le nom du webhook est `new-subscription`. + +C'est parce qu'on s'attend à ce que vos utilisateurs définissent, par un autre moyen (par exemple un tableau de bord Web), le véritable chemin d'URL où ils souhaitent recevoir la requête de webhook. + +### Consulter la documentation { #check-the-docs } + +Vous pouvez maintenant démarrer votre application et aller sur http://127.0.0.1:8000/docs. + +Vous verrez que votre documentation contient les chemins d'accès habituels et désormais aussi des webhooks : + + diff --git a/docs/fr/docs/advanced/path-operation-advanced-configuration.md b/docs/fr/docs/advanced/path-operation-advanced-configuration.md index fc88f3363..b482f97cc 100644 --- a/docs/fr/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/fr/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ Vous pouvez définir l’OpenAPI `operationId` à utiliser dans votre chemin d Vous devez vous assurer qu’il est unique pour chaque opération. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} ### Utiliser le nom de la fonction de chemin d’accès comme operationId { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ Si vous souhaitez utiliser les noms de fonction de vos API comme `operationId`, Vous devez le faire après avoir ajouté tous vos chemins d’accès. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} /// tip | Astuce @@ -40,7 +40,7 @@ Même si elles se trouvent dans des modules différents (fichiers Python). Pour exclure un chemin d’accès du schéma OpenAPI généré (et donc des systèmes de documentation automatiques), utilisez le paramètre `include_in_schema` et définissez-le à `False` : -{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} ## Description avancée depuis la docstring { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ Vous pouvez étendre le schéma OpenAPI pour un chemin d’accès en utilisant l Cet `openapi_extra` peut être utile, par exemple, pour déclarer des [Extensions OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) : -{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *} Si vous ouvrez la documentation automatique de l’API, votre extension apparaîtra en bas du chemin d’accès spécifique. @@ -139,9 +139,9 @@ Par exemple, vous pourriez décider de lire et de valider la requête avec votre Vous pourriez le faire avec `openapi_extra` : -{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *} -Dans cet exemple, nous n’avons déclaré aucun modèle Pydantic. En fait, le corps de la requête n’est même pas parsé en JSON, il est lu directement en tant que `bytes`, et la fonction `magic_data_reader()` serait chargée de l’analyser d’une manière ou d’une autre. +Dans cet exemple, nous n’avons déclaré aucun modèle Pydantic. En fait, le corps de la requête n’est même pas parsé en JSON, il est lu directement en tant que `bytes`, et la fonction `magic_data_reader()` serait chargée de l’analyser d’une manière ou d’une autre. Néanmoins, nous pouvons déclarer le schéma attendu pour le corps de la requête. @@ -153,7 +153,7 @@ Et vous pourriez le faire même si le type de données dans la requête n’est Par exemple, dans cette application nous n’utilisons pas la fonctionnalité intégrée de FastAPI pour extraire le JSON Schema des modèles Pydantic ni la validation automatique pour le JSON. En fait, nous déclarons le type de contenu de la requête comme YAML, pas JSON : -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *} Néanmoins, bien que nous n’utilisions pas la fonctionnalité intégrée par défaut, nous utilisons toujours un modèle Pydantic pour générer manuellement le JSON Schema pour les données que nous souhaitons recevoir en YAML. @@ -161,7 +161,7 @@ Ensuite, nous utilisons directement la requête et extrayons le corps en tant qu Ensuite, dans notre code, nous analysons directement ce contenu YAML, puis nous utilisons à nouveau le même modèle Pydantic pour valider le contenu YAML : -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *} /// tip | Astuce diff --git a/docs/fr/docs/advanced/response-change-status-code.md b/docs/fr/docs/advanced/response-change-status-code.md new file mode 100644 index 000000000..d08e87099 --- /dev/null +++ b/docs/fr/docs/advanced/response-change-status-code.md @@ -0,0 +1,31 @@ +# Réponse - Modifier le code d'état { #response-change-status-code } + +Vous avez probablement déjà lu que vous pouvez définir un [Code d'état de la réponse](../tutorial/response-status-code.md){.internal-link target=_blank} par défaut. + +Mais dans certains cas, vous devez renvoyer un code d'état différent de celui par défaut. + +## Cas d'utilisation { #use-case } + +Par exemple, imaginez que vous vouliez renvoyer par défaut un code d'état HTTP « OK » `200`. + +Mais si les données n'existent pas, vous voulez les créer et renvoyer un code d'état HTTP « CREATED » `201`. + +Mais vous souhaitez toujours pouvoir filtrer et convertir les données que vous renvoyez avec un `response_model`. + +Pour ces cas, vous pouvez utiliser un paramètre `Response`. + +## Utiliser un paramètre `Response` { #use-a-response-parameter } + +Vous pouvez déclarer un paramètre de type `Response` dans votre fonction de chemin d'accès (comme vous pouvez le faire pour les cookies et les en-têtes). + +Vous pouvez ensuite définir le `status_code` dans cet objet de réponse *temporaire*. + +{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *} + +Vous pouvez ensuite renvoyer n'importe quel objet nécessaire, comme d'habitude (un `dict`, un modèle de base de données, etc.). + +Et si vous avez déclaré un `response_model`, il sera toujours utilisé pour filtrer et convertir l'objet que vous avez renvoyé. + +**FastAPI** utilisera cette réponse *temporaire* pour extraire le code d'état (ainsi que les cookies et les en-têtes), et les placera dans la réponse finale qui contient la valeur que vous avez renvoyée, filtrée par tout `response_model`. + +Vous pouvez également déclarer le paramètre `Response` dans des dépendances et y définir le code d'état. Mais gardez à l'esprit que la dernière valeur définie prévaut. diff --git a/docs/fr/docs/advanced/response-cookies.md b/docs/fr/docs/advanced/response-cookies.md new file mode 100644 index 000000000..d3e51f331 --- /dev/null +++ b/docs/fr/docs/advanced/response-cookies.md @@ -0,0 +1,51 @@ +# Cookies de réponse { #response-cookies } + +## Utiliser un paramètre `Response` { #use-a-response-parameter } + +Vous pouvez déclarer un paramètre de type `Response` dans votre fonction de chemin d'accès. + +Vous pouvez ensuite définir des cookies dans cet objet de réponse *temporaire*. + +{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *} + +Vous pouvez ensuite renvoyer n'importe quel objet dont vous avez besoin, comme d'habitude (un `dict`, un modèle de base de données, etc.). + +Et si vous avez déclaré un `response_model`, il sera toujours utilisé pour filtrer et convertir l'objet que vous avez renvoyé. + +**FastAPI** utilisera cette réponse *temporaire* pour extraire les cookies (ainsi que les en-têtes et le code d'état), et les placera dans la réponse finale qui contient la valeur que vous avez renvoyée, filtrée par tout `response_model`. + +Vous pouvez également déclarer le paramètre `Response` dans des dépendances, et y définir des cookies (et des en-têtes). + +## Renvoyer une `Response` directement { #return-a-response-directly } + +Vous pouvez également créer des cookies en renvoyant une `Response` directement dans votre code. + +Pour ce faire, vous pouvez créer une réponse comme décrit dans [Renvoyer une Response directement](response-directly.md){.internal-link target=_blank}. + +Définissez ensuite des cookies dessus, puis renvoyez-la : + +{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *} + +/// tip | Astuce + +Gardez à l'esprit que si vous renvoyez une réponse directement au lieu d'utiliser le paramètre `Response`, FastAPI la renverra telle quelle. + +Vous devez donc vous assurer que vos données sont du type correct. Par exemple, qu'elles sont compatibles avec JSON si vous renvoyez une `JSONResponse`. + +Et également que vous n'envoyez pas de données qui auraient dû être filtrées par un `response_model`. + +/// + +### En savoir plus { #more-info } + +/// note | Détails techniques + +Vous pouvez également utiliser `from starlette.responses import Response` ou `from starlette.responses import JSONResponse`. + +**FastAPI** fournit les mêmes `starlette.responses` que `fastapi.responses` simplement pour votre commodité, en tant que développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette. + +Et comme `Response` peut être utilisé fréquemment pour définir des en-têtes et des cookies, **FastAPI** la met également à disposition via `fastapi.Response`. + +/// + +Pour voir tous les paramètres et options disponibles, consultez la documentation de Starlette. diff --git a/docs/fr/docs/advanced/response-directly.md b/docs/fr/docs/advanced/response-directly.md index f35c39c06..4a4951864 100644 --- a/docs/fr/docs/advanced/response-directly.md +++ b/docs/fr/docs/advanced/response-directly.md @@ -22,7 +22,7 @@ En fait, vous pouvez retourner n'importe quelle `Response` ou n'importe quelle s Et quand vous retournez une `Response`, **FastAPI** la transmet directement. -Elle ne fera aucune conversion de données avec les modèles Pydantic, elle ne convertira pas le contenu en un type quelconque. +Elle ne fera aucune conversion de données avec les modèles Pydantic, elle ne convertira pas le contenu en un type quelconque, etc. Cela vous donne beaucoup de flexibilité. Vous pouvez retourner n'importe quel type de données, surcharger n'importe quelle déclaration ou validation de données, etc. @@ -54,7 +54,7 @@ Disons que vous voulez retourner une réponse en utilisant le préfixe `X-`. + +Mais si vous avez des en-têtes personnalisés que vous voulez qu'un client dans un navigateur puisse voir, vous devez les ajouter à vos configurations CORS (en savoir plus dans [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), en utilisant le paramètre `expose_headers` documenté dans la documentation CORS de Starlette. diff --git a/docs/fr/docs/advanced/security/http-basic-auth.md b/docs/fr/docs/advanced/security/http-basic-auth.md new file mode 100644 index 000000000..a8742ce7c --- /dev/null +++ b/docs/fr/docs/advanced/security/http-basic-auth.md @@ -0,0 +1,107 @@ +# Authentification HTTP Basic { #http-basic-auth } + +Pour les cas les plus simples, vous pouvez utiliser l'authentification HTTP Basic. + +Avec l'authentification HTTP Basic, l'application attend un en-tête contenant un nom d'utilisateur et un mot de passe. + +Si elle ne le reçoit pas, elle renvoie une erreur HTTP 401 « Unauthorized ». + +Et elle renvoie un en-tête `WWW-Authenticate` avec la valeur `Basic`, et un paramètre optionnel `realm`. + +Cela indique au navigateur d'afficher l'invite intégrée pour saisir un nom d'utilisateur et un mot de passe. + +Ensuite, lorsque vous saisissez ce nom d'utilisateur et ce mot de passe, le navigateur les envoie automatiquement dans l'en-tête. + +## Authentification HTTP Basic simple { #simple-http-basic-auth } + +- Importer `HTTPBasic` et `HTTPBasicCredentials`. +- Créer un « schéma de sécurité » en utilisant `HTTPBasic`. +- Utiliser ce `security` avec une dépendance dans votre chemin d'accès. +- Cela renvoie un objet de type `HTTPBasicCredentials` : + - Il contient le `username` et le `password` envoyés. + +{* ../../docs_src/security/tutorial006_an_py310.py hl[4,8,12] *} + +Lorsque vous essayez d'ouvrir l'URL pour la première fois (ou cliquez sur le bouton « Execute » dans les documents) le navigateur vous demandera votre nom d'utilisateur et votre mot de passe : + + + +## Vérifier le nom d'utilisateur { #check-the-username } + +Voici un exemple plus complet. + +Utilisez une dépendance pour vérifier si le nom d'utilisateur et le mot de passe sont corrects. + +Pour cela, utilisez le module standard Python `secrets` pour vérifier le nom d'utilisateur et le mot de passe. + +`secrets.compare_digest()` doit recevoir des `bytes` ou une `str` ne contenant que des caractères ASCII (ceux de l'anglais), ce qui signifie qu'elle ne fonctionnerait pas avec des caractères comme `á`, comme dans `Sebastián`. + +Pour gérer cela, nous convertissons d'abord `username` et `password` en `bytes` en les encodant en UTF-8. + +Nous pouvons ensuite utiliser `secrets.compare_digest()` pour vérifier que `credentials.username` est « stanleyjobson » et que `credentials.password` est « swordfish ». + +{* ../../docs_src/security/tutorial007_an_py310.py hl[1,12:24] *} + +Cela serait équivalent à : + +```Python +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # Renvoyer une erreur + ... +``` + +Mais en utilisant `secrets.compare_digest()`, cela sera sécurisé contre un type d'attaques appelé « attaques par chronométrage ». + +### Attaques par chronométrage { #timing-attacks } + +Mais qu'est-ce qu'une « attaque par chronométrage » ? + +Imaginons que des attaquants essaient de deviner le nom d'utilisateur et le mot de passe. + +Ils envoient alors une requête avec un nom d'utilisateur `johndoe` et un mot de passe `love123`. + +Le code Python de votre application serait alors équivalent à quelque chose comme : + +```Python +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Mais au moment où Python compare le premier `j` de `johndoe` au premier `s` de `stanleyjobson`, il retournera `False`, car il sait déjà que ces deux chaînes ne sont pas identiques, en se disant qu'« il n'est pas nécessaire de gaspiller plus de calcul pour comparer le reste des lettres ». Et votre application dira « Nom d'utilisateur ou mot de passe incorrect ». + +Mais ensuite, les attaquants essaient avec le nom d'utilisateur `stanleyjobsox` et le mot de passe `love123`. + +Et le code de votre application fait quelque chose comme : + +```Python +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Python devra comparer tout `stanleyjobso` dans `stanleyjobsox` et `stanleyjobson` avant de réaliser que les deux chaînes ne sont pas identiques. Cela prendra donc quelques microsecondes supplémentaires pour répondre « Nom d'utilisateur ou mot de passe incorrect ». + +#### Le temps de réponse aide les attaquants { #the-time-to-answer-helps-the-attackers } + +À ce stade, en remarquant que le serveur a mis quelques microsecondes de plus à envoyer la réponse « Nom d'utilisateur ou mot de passe incorrect », les attaquants sauront qu'ils ont trouvé quelque chose de juste : certaines des premières lettres étaient correctes. + +Ils peuvent alors réessayer en sachant que c'est probablement quelque chose de plus proche de `stanleyjobsox` que de `johndoe`. + +#### Une attaque « professionnelle » { #a-professional-attack } + +Bien sûr, les attaquants n'essaieraient pas tout cela à la main ; ils écriraient un programme pour le faire, avec éventuellement des milliers ou des millions de tests par seconde. Ils obtiendraient une lettre correcte supplémentaire à la fois. + +Ce faisant, en quelques minutes ou heures, les attaquants devineraient le nom d'utilisateur et le mot de passe corrects, avec « l'aide » de notre application, simplement en se basant sur le temps de réponse. + +#### Corrigez-le avec `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest } + +Mais dans notre code nous utilisons justement `secrets.compare_digest()`. + +En bref, il faudra le même temps pour comparer `stanleyjobsox` à `stanleyjobson` que pour comparer `johndoe` à `stanleyjobson`. Il en va de même pour le mot de passe. + +Ainsi, en utilisant `secrets.compare_digest()` dans le code de votre application, votre application sera protégée contre toute cette gamme d'attaques de sécurité. + +### Renvoyer l'erreur { #return-the-error } + +Après avoir détecté que les identifiants sont incorrects, renvoyez une `HTTPException` avec un code d'état 401 (le même que lorsque aucun identifiant n'est fourni) et ajoutez l'en-tête `WWW-Authenticate` pour que le navigateur affiche à nouveau l'invite de connexion : + +{* ../../docs_src/security/tutorial007_an_py310.py hl[26:30] *} diff --git a/docs/fr/docs/advanced/security/index.md b/docs/fr/docs/advanced/security/index.md new file mode 100644 index 000000000..e84fcef62 --- /dev/null +++ b/docs/fr/docs/advanced/security/index.md @@ -0,0 +1,19 @@ +# Sécurité avancée { #advanced-security } + +## Fonctionnalités supplémentaires { #additional-features } + +Il existe des fonctionnalités supplémentaires pour gérer la sécurité en plus de celles couvertes dans le [Tutoriel - Guide utilisateur : Sécurité](../../tutorial/security/index.md){.internal-link target=_blank}. + +/// tip | Astuce + +Les sections suivantes ne sont pas nécessairement « advanced ». + +Et il est possible que, pour votre cas d’utilisation, la solution se trouve dans l’une d’entre elles. + +/// + +## Lire d’abord le tutoriel { #read-the-tutorial-first } + +Les sections suivantes partent du principe que vous avez déjà lu le [Tutoriel - Guide utilisateur : Sécurité](../../tutorial/security/index.md){.internal-link target=_blank} principal. + +Elles s’appuient toutes sur les mêmes concepts, mais permettent des fonctionnalités supplémentaires. diff --git a/docs/fr/docs/advanced/security/oauth2-scopes.md b/docs/fr/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 000000000..c890a5129 --- /dev/null +++ b/docs/fr/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,274 @@ +# Scopes OAuth2 { #oauth2-scopes } + +Vous pouvez utiliser des scopes OAuth2 directement avec **FastAPI**, ils sont intégrés pour fonctionner de manière transparente. + +Cela vous permettrait d’avoir un système d’autorisations plus fin, conforme au standard OAuth2, intégré à votre application OpenAPI (et à la documentation de l’API). + +OAuth2 avec scopes est le mécanisme utilisé par de nombreux grands fournisseurs d’authentification, comme Facebook, Google, GitHub, Microsoft, X (Twitter), etc. Ils l’utilisent pour fournir des permissions spécifiques aux utilisateurs et aux applications. + +Chaque fois que vous « log in with » Facebook, Google, GitHub, Microsoft, X (Twitter), cette application utilise OAuth2 avec scopes. + +Dans cette section, vous verrez comment gérer l’authentification et l’autorisation avec le même OAuth2 avec scopes dans votre application **FastAPI**. + +/// warning | Alertes + +C’est une section plus ou moins avancée. Si vous débutez, vous pouvez la passer. + +Vous n’avez pas nécessairement besoin des scopes OAuth2, et vous pouvez gérer l’authentification et l’autorisation comme vous le souhaitez. + +Mais OAuth2 avec scopes peut s’intégrer élégamment à votre API (avec OpenAPI) et à votre documentation d’API. + +Néanmoins, c’est toujours à vous de faire appliquer ces scopes, ou toute autre exigence de sécurité/autorisation, selon vos besoins, dans votre code. + +Dans de nombreux cas, OAuth2 avec scopes peut être excessif. + +Mais si vous savez que vous en avez besoin, ou si vous êtes curieux, continuez à lire. + +/// + +## Scopes OAuth2 et OpenAPI { #oauth2-scopes-and-openapi } + +La spécification OAuth2 définit des « scopes » comme une liste de chaînes séparées par des espaces. + +Le contenu de chacune de ces chaînes peut avoir n’importe quel format, mais ne doit pas contenir d’espaces. + +Ces scopes représentent des « permissions ». + +Dans OpenAPI (par ex. la documentation de l’API), vous pouvez définir des « schémas de sécurité ». + +Lorsqu’un de ces schémas de sécurité utilise OAuth2, vous pouvez aussi déclarer et utiliser des scopes. + +Chaque « scope » est juste une chaîne (sans espaces). + +Ils sont généralement utilisés pour déclarer des permissions de sécurité spécifiques, par exemple : + +* `users:read` ou `users:write` sont des exemples courants. +* `instagram_basic` est utilisé par Facebook / Instagram. +* `https://www.googleapis.com/auth/drive` est utilisé par Google. + +/// info + +Dans OAuth2, un « scope » est simplement une chaîne qui déclare une permission spécifique requise. + +Peu importe s’il contient d’autres caractères comme `:` ou si c’est une URL. + +Ces détails dépendent de l’implémentation. + +Pour OAuth2, ce ne sont que des chaînes. + +/// + +## Vue d’ensemble { #global-view } + +Voyons d’abord rapidement les parties qui changent par rapport aux exemples du **Tutoriel - Guide utilisateur** pour [OAuth2 avec mot de passe (et hachage), Bearer avec jetons JWT](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Cette fois, en utilisant des scopes OAuth2 : + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} + +Passons maintenant en revue ces changements étape par étape. + +## Déclarer le schéma de sécurité OAuth2 { #oauth2-security-scheme } + +Le premier changement est que nous déclarons maintenant le schéma de sécurité OAuth2 avec deux scopes disponibles, `me` et `items`. + +Le paramètre `scopes` reçoit un `dict` avec chaque scope en clé et la description en valeur : + +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} + +Comme nous déclarons maintenant ces scopes, ils apparaîtront dans la documentation de l’API lorsque vous vous authentifiez/autorisez. + +Et vous pourrez sélectionner à quels scopes vous souhaitez accorder l’accès : `me` et `items`. + +C’est le même mécanisme utilisé lorsque vous donnez des permissions en vous connectant avec Facebook, Google, GitHub, etc. : + + + +## Jeton JWT avec scopes { #jwt-token-with-scopes } + +Modifiez maintenant le *chemin d’accès* du jeton pour renvoyer les scopes demandés. + +Nous utilisons toujours le même `OAuth2PasswordRequestForm`. Il inclut une propriété `scopes` avec une `list` de `str`, contenant chaque scope reçu dans la requête. + +Et nous renvoyons les scopes comme partie du jeton JWT. + +/// danger | Danger + +Pour simplifier, ici nous ajoutons directement au jeton les scopes reçus. + +Mais dans votre application, pour la sécurité, vous devez vous assurer de n’ajouter que les scopes que l’utilisateur est réellement autorisé à avoir, ou ceux que vous avez prédéfinis. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} + +## Déclarer des scopes dans les chemins d’accès et les dépendances { #declare-scopes-in-path-operations-and-dependencies } + +Nous déclarons maintenant que le *chemin d’accès* `/users/me/items/` nécessite le scope `items`. + +Pour cela, nous importons et utilisons `Security` depuis `fastapi`. + +Vous pouvez utiliser `Security` pour déclarer des dépendances (comme `Depends`), mais `Security` reçoit aussi un paramètre `scopes` avec une liste de scopes (chaînes). + +Dans ce cas, nous passons une fonction de dépendance `get_current_active_user` à `Security` (de la même manière que nous le ferions avec `Depends`). + +Mais nous passons aussi une `list` de scopes, ici avec un seul scope : `items` (il pourrait y en avoir plus). + +Et la fonction de dépendance `get_current_active_user` peut également déclarer des sous-dépendances, non seulement avec `Depends` mais aussi avec `Security`. En déclarant sa propre fonction de sous-dépendance (`get_current_user`), et davantage d’exigences de scopes. + +Dans ce cas, elle nécessite le scope `me` (elle pourrait en exiger plusieurs). + +/// note | Remarque + +Vous n’avez pas nécessairement besoin d’ajouter des scopes différents à différents endroits. + +Nous le faisons ici pour montrer comment **FastAPI** gère des scopes déclarés à différents niveaux. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} + +/// info | Détails techniques + +`Security` est en réalité une sous-classe de `Depends`, et elle n’a qu’un paramètre supplémentaire que nous verrons plus tard. + +Mais en utilisant `Security` au lieu de `Depends`, **FastAPI** saura qu’il peut déclarer des scopes de sécurité, les utiliser en interne et documenter l’API avec OpenAPI. + +Cependant, lorsque vous importez `Query`, `Path`, `Depends`, `Security` et d’autres depuis `fastapi`, ce sont en fait des fonctions qui renvoient des classes spéciales. + +/// + +## Utiliser `SecurityScopes` { #use-securityscopes } + +Mettez maintenant à jour la dépendance `get_current_user`. + +C’est celle utilisée par les dépendances ci-dessus. + +C’est ici que nous utilisons le même schéma OAuth2 que nous avons créé auparavant, en le déclarant comme dépendance : `oauth2_scheme`. + +Comme cette fonction de dépendance n’a pas elle-même d’exigences de scope, nous pouvons utiliser `Depends` avec `oauth2_scheme`, nous n’avons pas à utiliser `Security` quand nous n’avons pas besoin de spécifier des scopes de sécurité. + +Nous déclarons également un paramètre spécial de type `SecurityScopes`, importé de `fastapi.security`. + +Cette classe `SecurityScopes` est similaire à `Request` (`Request` servait à obtenir directement l’objet requête). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} + +## Utiliser les `scopes` { #use-the-scopes } + +Le paramètre `security_scopes` sera de type `SecurityScopes`. + +Il aura une propriété `scopes` avec une liste contenant tous les scopes requis par lui-même et par toutes les dépendances qui l’utilisent comme sous-dépendance. Cela signifie, tous les « dépendants » ... cela peut paraître déroutant, c’est expliqué à nouveau plus bas. + +L’objet `security_scopes` (de classe `SecurityScopes`) fournit aussi un attribut `scope_str` avec une chaîne unique, contenant ces scopes séparés par des espaces (nous allons l’utiliser). + +Nous créons une `HTTPException` que nous pouvons réutiliser (`raise`) plus tard à plusieurs endroits. + +Dans cette exception, nous incluons les scopes requis (le cas échéant) sous forme de chaîne séparée par des espaces (en utilisant `scope_str`). Nous plaçons cette chaîne contenant les scopes dans l’en-tête `WWW-Authenticate` (cela fait partie de la spécification). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} + +## Vérifier le `username` et la structure des données { #verify-the-username-and-data-shape } + +Nous vérifions que nous obtenons un `username`, et extrayons les scopes. + +Nous validons ensuite ces données avec le modèle Pydantic (en capturant l’exception `ValidationError`), et si nous obtenons une erreur lors de la lecture du jeton JWT ou de la validation des données avec Pydantic, nous levons la `HTTPException` que nous avons créée auparavant. + +Pour cela, nous mettons à jour le modèle Pydantic `TokenData` avec une nouvelle propriété `scopes`. + +En validant les données avec Pydantic, nous pouvons nous assurer que nous avons, par exemple, exactement une `list` de `str` pour les scopes et un `str` pour le `username`. + +Au lieu, par exemple, d’un `dict`, ou autre chose, ce qui pourrait casser l’application plus tard et constituer un risque de sécurité. + +Nous vérifions également que nous avons un utilisateur avec ce nom d’utilisateur, et sinon, nous levons la même exception que précédemment. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} + +## Vérifier les `scopes` { #verify-the-scopes } + +Nous vérifions maintenant que tous les scopes requis, par cette dépendance et tous les dépendants (y compris les *chemins d’accès*), sont inclus dans les scopes fournis dans le jeton reçu, sinon nous levons une `HTTPException`. + +Pour cela, nous utilisons `security_scopes.scopes`, qui contient une `list` avec tous ces scopes en `str`. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} + +## Arbre de dépendances et scopes { #dependency-tree-and-scopes } + +Revoyons encore cet arbre de dépendances et les scopes. + +Comme la dépendance `get_current_active_user` a une sous-dépendance `get_current_user`, le scope « me » déclaré dans `get_current_active_user` sera inclus dans la liste des scopes requis dans `security_scopes.scopes` passé à `get_current_user`. + +Le *chemin d’accès* lui-même déclare également un scope, « items », il sera donc aussi présent dans la liste `security_scopes.scopes` passée à `get_current_user`. + +Voici à quoi ressemble la hiérarchie des dépendances et des scopes : + +* Le *chemin d’accès* `read_own_items` a : + * Des scopes requis `["items"]` avec la dépendance : + * `get_current_active_user` : + * La fonction de dépendance `get_current_active_user` a : + * Des scopes requis `["me"]` avec la dépendance : + * `get_current_user` : + * La fonction de dépendance `get_current_user` a : + * Aucun scope requis par elle-même. + * Une dépendance utilisant `oauth2_scheme`. + * Un paramètre `security_scopes` de type `SecurityScopes` : + * Ce paramètre `security_scopes` a une propriété `scopes` avec une `list` contenant tous les scopes déclarés ci-dessus, donc : + * `security_scopes.scopes` contiendra `["me", "items"]` pour le *chemin d’accès* `read_own_items`. + * `security_scopes.scopes` contiendra `["me"]` pour le *chemin d’accès* `read_users_me`, car il est déclaré dans la dépendance `get_current_active_user`. + * `security_scopes.scopes` contiendra `[]` (rien) pour le *chemin d’accès* `read_system_status`, car il n’a déclaré aucun `Security` avec des `scopes`, et sa dépendance, `get_current_user`, ne déclare pas non plus de `scopes`. + +/// tip | Astuce + +L’élément important et « magique » ici est que `get_current_user` aura une liste différente de `scopes` à vérifier pour chaque *chemin d’accès*. + +Tout dépend des `scopes` déclarés dans chaque *chemin d’accès* et chaque dépendance dans l’arbre de dépendances pour ce *chemin d’accès* spécifique. + +/// + +## Détails supplémentaires sur `SecurityScopes` { #more-details-about-securityscopes } + +Vous pouvez utiliser `SecurityScopes` à n’importe quel endroit, et à de multiples endroits, il n’a pas besoin d’être dans la dépendance « root ». + +Il aura toujours les scopes de sécurité déclarés dans les dépendances `Security` actuelles et tous les dépendants pour **ce** *chemin d’accès* spécifique et **cet** arbre de dépendances spécifique. + +Comme `SecurityScopes` contient tous les scopes déclarés par les dépendants, vous pouvez l’utiliser pour vérifier qu’un jeton possède les scopes requis dans une fonction de dépendance centrale, puis déclarer des exigences de scopes différentes dans différents *chemins d’accès*. + +Elles seront vérifiées indépendamment pour chaque *chemin d’accès*. + +## Tester { #check-it } + +Si vous ouvrez la documentation de l’API, vous pouvez vous authentifier et spécifier quels scopes vous voulez autoriser. + + + +Si vous ne sélectionnez aucun scope, vous serez « authenticated », mais lorsque vous essayerez d’accéder à `/users/me/` ou `/users/me/items/`, vous obtiendrez une erreur indiquant que vous n’avez pas suffisamment de permissions. Vous pourrez toujours accéder à `/status/`. + +Et si vous sélectionnez le scope `me` mais pas le scope `items`, vous pourrez accéder à `/users/me/` mais pas à `/users/me/items/`. + +C’est ce qui arriverait à une application tierce qui tenterait d’accéder à l’un de ces *chemins d’accès* avec un jeton fourni par un utilisateur, selon le nombre de permissions que l’utilisateur a accordées à l’application. + +## À propos des intégrations tierces { #about-third-party-integrations } + +Dans cet exemple, nous utilisons le flux OAuth2 « password ». + +C’est approprié lorsque nous nous connectons à notre propre application, probablement avec notre propre frontend. + +Parce que nous pouvons lui faire confiance pour recevoir le `username` et le `password`, puisque nous le contrôlons. + +Mais si vous construisez une application OAuth2 à laquelle d’autres se connecteraient (c.-à-d., si vous construisez un fournisseur d’authentification équivalent à Facebook, Google, GitHub, etc.), vous devez utiliser l’un des autres flux. + +Le plus courant est le flux implicite. + +Le plus sûr est le flux « code », mais il est plus complexe à implémenter car il nécessite plus d’étapes. Comme il est plus complexe, de nombreux fournisseurs finissent par recommander le flux implicite. + +/// note | Remarque + +Il est courant que chaque fournisseur d’authentification nomme ses flux différemment, pour en faire une partie de sa marque. + +Mais au final, ils implémentent le même standard OAuth2. + +/// + +**FastAPI** inclut des utilitaires pour tous ces flux d’authentification OAuth2 dans `fastapi.security.oauth2`. + +## `Security` dans les dépendances du décorateur `dependencies` { #security-in-decorator-dependencies } + +De la même manière que vous pouvez définir une `list` de `Depends` dans le paramètre `dependencies` du décorateur (comme expliqué dans [Dépendances dans les décorateurs de chemins d’accès](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), vous pouvez aussi utiliser `Security` avec des `scopes` à cet endroit. diff --git a/docs/fr/docs/advanced/settings.md b/docs/fr/docs/advanced/settings.md new file mode 100644 index 000000000..ed724bf4f --- /dev/null +++ b/docs/fr/docs/advanced/settings.md @@ -0,0 +1,302 @@ +# Paramètres et variables d'environnement { #settings-and-environment-variables } + +Dans de nombreux cas, votre application peut avoir besoin de paramètres ou de configurations externes, par exemple des clés secrètes, des identifiants de base de données, des identifiants pour des services d'e-mail, etc. + +La plupart de ces paramètres sont variables (peuvent changer), comme les URL de base de données. Et beaucoup peuvent être sensibles, comme les secrets. + +C'est pourquoi il est courant de les fournir via des variables d'environnement lues par l'application. + +/// tip | Astuce + +Pour comprendre les variables d'environnement, vous pouvez lire [Variables d'environnement](../environment-variables.md){.internal-link target=_blank}. + +/// + +## Types et validation { #types-and-validation } + +Ces variables d'environnement ne gèrent que des chaînes de texte, car elles sont externes à Python et doivent être compatibles avec d'autres programmes et le reste du système (et même avec différents systèmes d'exploitation, comme Linux, Windows, macOS). + +Cela signifie que toute valeur lue en Python depuis une variable d'environnement sera une `str`, et toute conversion vers un autre type ou toute validation doit être effectuée dans le code. + +## Pydantic `Settings` { #pydantic-settings } + +Heureusement, Pydantic fournit un excellent utilitaire pour gérer ces paramètres provenant des variables d'environnement avec Pydantic : gestion des paramètres. + +### Installer `pydantic-settings` { #install-pydantic-settings } + +D'abord, vous devez créer votre [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, l'activer, puis installer le paquet `pydantic-settings` : + +
+ +```console +$ pip install pydantic-settings +---> 100% +``` + +
+ +Il est également inclus lorsque vous installez les extras `all` avec : + +
+ +```console +$ pip install "fastapi[all]" +---> 100% +``` + +
+ +### Créer l'objet `Settings` { #create-the-settings-object } + +Importez `BaseSettings` depuis Pydantic et créez une sous-classe, comme pour un modèle Pydantic. + +De la même manière qu'avec les modèles Pydantic, vous déclarez des attributs de classe avec des annotations de type, et éventuellement des valeurs par défaut. + +Vous pouvez utiliser toutes les mêmes fonctionnalités et outils de validation que pour les modèles Pydantic, comme différents types de données et des validations supplémentaires avec `Field()`. + +{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *} + +/// tip | Astuce + +Si vous voulez quelque chose à copier-coller rapidement, n'utilisez pas cet exemple, utilisez le dernier ci-dessous. + +/// + +Ensuite, lorsque vous créez une instance de cette classe `Settings` (dans ce cas, l'objet `settings`), Pydantic lira les variables d'environnement de manière insensible à la casse, donc une variable en majuscules `APP_NAME` sera tout de même lue pour l'attribut `app_name`. + +Il convertira ensuite et validera les données. Ainsi, lorsque vous utilisez cet objet `settings`, vous aurez des données des types que vous avez déclarés (par exemple, `items_per_user` sera un `int`). + +### Utiliser `settings` { #use-the-settings } + +Vous pouvez ensuite utiliser le nouvel objet `settings` dans votre application : + +{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *} + +### Exécuter le serveur { #run-the-server } + +Ensuite, vous exécutez le serveur en passant les configurations comme variables d'environnement ; par exemple, vous pouvez définir un `ADMIN_EMAIL` et `APP_NAME` avec : + +
+ +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +/// tip | Astuce + +Pour définir plusieurs variables d'environnement pour une seule commande, séparez-les simplement par un espace et placez-les toutes avant la commande. + +/// + +Ainsi, le paramètre `admin_email` sera défini sur « deadpool@example.com ». + +Le `app_name` sera « ChimichangApp ». + +Et `items_per_user` conservera sa valeur par défaut de `50`. + +## Paramètres dans un autre module { #settings-in-another-module } + +Vous pouvez placer ces paramètres dans un autre module comme vous l'avez vu dans [Applications plus grandes - Plusieurs fichiers](../tutorial/bigger-applications.md){.internal-link target=_blank}. + +Par exemple, vous pourriez avoir un fichier `config.py` avec : + +{* ../../docs_src/settings/app01_py310/config.py *} + +Puis l'utiliser dans un fichier `main.py` : + +{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *} + +/// tip | Astuce + +Vous aurez également besoin d'un fichier `__init__.py` comme vous l'avez vu dans [Applications plus grandes - Plusieurs fichiers](../tutorial/bigger-applications.md){.internal-link target=_blank}. + +/// + +## Paramètres dans une dépendance { #settings-in-a-dependency } + +Dans certains cas, il peut être utile de fournir les paramètres via une dépendance, au lieu d'avoir un objet global `settings` utilisé partout. + +Cela peut être particulièrement utile pendant les tests, car il est très facile de surcharger une dépendance avec vos propres paramètres personnalisés. + +### Le fichier de configuration { #the-config-file } + +En repartant de l'exemple précédent, votre fichier `config.py` pourrait ressembler à : + +{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *} + +Notez que maintenant, nous ne créons pas d'instance par défaut `settings = Settings()`. + +### Le fichier principal de l'application { #the-main-app-file } + +Nous créons maintenant une dépendance qui renvoie un nouveau `config.Settings()`. + +{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *} + +/// tip | Astuce + +Nous parlerons de `@lru_cache` dans un instant. + +Pour l'instant, vous pouvez supposer que `get_settings()` est une fonction normale. + +/// + +Nous pouvons ensuite l'exiger depuis la fonction de chemin d'accès comme dépendance et l'utiliser où nous en avons besoin. + +{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *} + +### Paramètres et tests { #settings-and-testing } + +Il devient alors très simple de fournir un autre objet de paramètres pendant les tests en créant une surcharge de dépendance pour `get_settings` : + +{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *} + +Dans la surcharge de dépendance, nous définissons une nouvelle valeur pour `admin_email` lors de la création du nouvel objet `Settings`, puis nous renvoyons ce nouvel objet. + +Nous pouvons ensuite tester qu'il est bien utilisé. + +## Lire un fichier `.env` { #reading-a-env-file } + +Si vous avez de nombreux paramètres susceptibles de beaucoup changer, peut-être selon les environnements, il peut être utile de les placer dans un fichier, puis de les lire comme s'il s'agissait de variables d'environnement. + +Cette pratique est suffisamment courante pour avoir un nom ; ces variables d'environnement sont fréquemment placées dans un fichier `.env`, et le fichier est appelé un « dotenv ». + +/// tip | Astuce + +Un fichier commençant par un point (`.`) est un fichier caché dans les systèmes de type Unix, comme Linux et macOS. + +Mais un fichier dotenv n'a pas forcément exactement ce nom de fichier. + +/// + +Pydantic prend en charge la lecture depuis ce type de fichiers en utilisant une bibliothèque externe. Vous pouvez en lire davantage ici : Pydantic Settings : prise en charge de Dotenv (.env). + +/// tip | Astuce + +Pour que cela fonctionne, vous devez exécuter `pip install python-dotenv`. + +/// + +### Le fichier `.env` { #the-env-file } + +Vous pouvez avoir un fichier `.env` avec : + +```bash +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +### Lire les paramètres depuis `.env` { #read-settings-from-env } + +Puis mettre à jour votre `config.py` avec : + +{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *} + +/// tip | Astuce + +L'attribut `model_config` est utilisé uniquement pour la configuration Pydantic. Vous pouvez en lire davantage ici : Pydantic : Concepts : Configuration. + +/// + +Ici, nous définissons la configuration `env_file` à l'intérieur de votre classe Pydantic `Settings` et lui attribuons le nom du fichier dotenv que nous voulons utiliser. + +### Créer `Settings` une seule fois avec `lru_cache` { #creating-the-settings-only-once-with-lru-cache } + +Lire un fichier depuis le disque est normalement une opération coûteuse (lente), vous voudrez donc probablement le faire une seule fois puis réutiliser le même objet de paramètres, au lieu de le lire à chaque requête. + +Mais chaque fois que nous faisons : + +```Python +Settings() +``` + +un nouvel objet `Settings` serait créé, et à sa création il lirait à nouveau le fichier `.env`. + +Si la fonction de dépendance était simplement : + +```Python +def get_settings(): + return Settings() +``` + +nous créerions cet objet pour chaque requête, et nous lirions le fichier `.env` pour chaque requête. ⚠️ + +Mais comme nous utilisons le décorateur `@lru_cache` au-dessus, l'objet `Settings` sera créé une seule fois, la première fois qu'il est appelé. ✔️ + +{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *} + +Ensuite, pour tout appel ultérieur de `get_settings()` dans les dépendances pour les requêtes suivantes, au lieu d'exécuter le code interne de `get_settings()` et de créer un nouvel objet `Settings`, il renverra le même objet que celui retourné au premier appel, encore et encore. + +#### Détails techniques de `lru_cache` { #lru-cache-technical-details } + +`@lru_cache` modifie la fonction qu'il décore pour renvoyer la même valeur que celle qui a été retournée la première fois, au lieu de la recalculer en exécutant le code de la fonction à chaque fois. + +Ainsi, la fonction située en dessous sera exécutée une fois pour chaque combinaison d'arguments. Ensuite, les valeurs renvoyées par chacune de ces combinaisons d'arguments seront réutilisées à chaque fois que la fonction sera appelée avec exactement la même combinaison d'arguments. + +Par exemple, si vous avez une fonction : + +```Python +@lru_cache +def say_hi(name: str, salutation: str = "Ms."): + return f"Hello {salutation} {name}" +``` + +votre programme pourrait s'exécuter comme ceci : + +```mermaid +sequenceDiagram + +participant code as Code +participant function as say_hi() +participant execute as Execute function + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Camila") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick", salutation="Mr.") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: return stored result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end +``` + +Dans le cas de notre dépendance `get_settings()`, la fonction ne prend même aucun argument, elle renvoie donc toujours la même valeur. + +De cette façon, elle se comporte presque comme s'il s'agissait simplement d'une variable globale. Mais comme elle utilise une fonction de dépendance, nous pouvons alors la surcharger facilement pour les tests. + +`@lru_cache` fait partie de `functools` qui fait partie de la bibliothèque standard de Python, vous pouvez en lire davantage dans la documentation Python pour `@lru_cache`. + +## Récapitulatif { #recap } + +Vous pouvez utiliser Pydantic Settings pour gérer les paramètres ou configurations de votre application, avec toute la puissance des modèles Pydantic. + +* En utilisant une dépendance, vous pouvez simplifier les tests. +* Vous pouvez utiliser des fichiers `.env`. +* Utiliser `@lru_cache` vous permet d'éviter de relire le fichier dotenv à chaque requête, tout en vous permettant de le surcharger pendant les tests. diff --git a/docs/fr/docs/advanced/sub-applications.md b/docs/fr/docs/advanced/sub-applications.md new file mode 100644 index 000000000..777056040 --- /dev/null +++ b/docs/fr/docs/advanced/sub-applications.md @@ -0,0 +1,67 @@ +# Sous-applications - Montage { #sub-applications-mounts } + +Si vous avez besoin de deux applications FastAPI indépendantes, avec leur propre OpenAPI et leurs propres interfaces de la documentation, vous pouvez avoir une application principale et « monter » une (ou plusieurs) sous‑application(s). + +## Monter une application **FastAPI** { #mounting-a-fastapi-application } + +« Monter » signifie ajouter une application entièrement « indépendante » à un chemin spécifique, qui se chargera ensuite de tout gérer sous ce chemin, avec les _chemins d'accès_ déclarés dans cette sous‑application. + +### Application de premier niveau { #top-level-application } + +Créez d'abord l'application **FastAPI** principale (de premier niveau) et ses *chemins d'accès* : + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *} + +### Sous-application { #sub-application } + +Ensuite, créez votre sous‑application et ses *chemins d'accès*. + +Cette sous‑application est simplement une autre application FastAPI standard, mais c'est celle qui sera « montée » : + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *} + +### Monter la sous-application { #mount-the-sub-application } + +Dans votre application de premier niveau, `app`, montez la sous‑application, `subapi`. + +Dans ce cas, elle sera montée au chemin `/subapi` : + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *} + +### Vérifier la documentation API automatique { #check-the-automatic-api-docs } + +Exécutez maintenant la commande `fastapi` avec votre fichier : + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Puis ouvrez la documentation à http://127.0.0.1:8000/docs. + +Vous verrez la documentation API automatique pour l'application principale, n'incluant que ses propres _chemins d'accès_ : + + + +Ensuite, ouvrez la documentation de la sous‑application à http://127.0.0.1:8000/subapi/docs. + +Vous verrez la documentation API automatique pour la sous‑application, n'incluant que ses propres _chemins d'accès_, tous sous le préfixe de sous‑chemin correct `/subapi` : + + + +Si vous essayez d'interagir avec l'une ou l'autre des deux interfaces, elles fonctionneront correctement, car le navigateur pourra communiquer avec chaque application ou sous‑application spécifique. + +### Détails techniques : `root_path` { #technical-details-root-path } + +Lorsque vous montez une sous‑application comme ci‑dessus, FastAPI se charge de communiquer le chemin de montage à la sous‑application au moyen d'un mécanisme de la spécification ASGI appelé `root_path`. + +De cette manière, la sous‑application saura utiliser ce préfixe de chemin pour l'interface de documentation. + +La sous‑application peut également avoir ses propres sous‑applications montées et tout fonctionnera correctement, car FastAPI gère automatiquement tous ces `root_path`. + +Vous en apprendrez davantage sur `root_path` et sur la façon de l'utiliser explicitement dans la section [Derrière un proxy](behind-a-proxy.md){.internal-link target=_blank}. diff --git a/docs/fr/docs/advanced/templates.md b/docs/fr/docs/advanced/templates.md new file mode 100644 index 000000000..7c886ab69 --- /dev/null +++ b/docs/fr/docs/advanced/templates.md @@ -0,0 +1,126 @@ +# Templates { #templates } + +Vous pouvez utiliser n'importe quel moteur de templates avec **FastAPI**. + +Un choix courant est Jinja2, le même que celui utilisé par Flask et d'autres outils. + +Il existe des utilitaires pour le configurer facilement que vous pouvez utiliser directement dans votre application **FastAPI** (fournis par Starlette). + +## Installer les dépendances { #install-dependencies } + +Vous devez créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, l'activer, puis installer `jinja2` : + +
+ +```console +$ pip install jinja2 + +---> 100% +``` + +
+ +## Utiliser `Jinja2Templates` { #using-jinja2templates } + +- Importez `Jinja2Templates`. +- Créez un objet `templates` que vous pourrez réutiliser par la suite. +- Déclarez un paramètre `Request` dans le *chemin d'accès* qui renverra un template. +- Utilisez l'objet `templates` que vous avez créé pour rendre et retourner une `TemplateResponse`, en transmettant le nom du template, l'objet de requête et un dictionnaire de « context » avec des paires clé-valeur à utiliser dans le template Jinja2. + +{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *} + +/// note | Remarque + +Avant FastAPI 0.108.0 et Starlette 0.29.0, `name` était le premier paramètre. + +De plus, auparavant, dans les versions précédentes, l'objet `request` faisait partie des paires clé-valeur du contexte pour Jinja2. + +/// + +/// tip | Astuce + +En déclarant `response_class=HTMLResponse`, l'interface de la documentation saura que la réponse sera en HTML. + +/// + +/// note | Détails techniques + +Vous pouvez aussi utiliser `from starlette.templating import Jinja2Templates`. + +**FastAPI** expose le même `starlette.templating` sous `fastapi.templating` par simple commodité pour vous, développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette. C'est également le cas pour `Request` et `StaticFiles`. + +/// + +## Écrire des templates { #writing-templates } + +Vous pouvez ensuite écrire un template dans `templates/item.html`, par exemple : + +```jinja hl_lines="7" +{!../../docs_src/templates/templates/item.html!} +``` + +### Valeurs de contexte du template { #template-context-values } + +Dans le HTML qui contient : + +{% raw %} + +```jinja +Item ID: {{ id }} +``` + +{% endraw %} + +... il affichera l’`id` récupéré à partir du `dict` « context » que vous avez passé : + +```Python +{"id": id} +``` + +Par exemple, avec un ID de `42`, cela rendrait : + +```html +Item ID: 42 +``` + +### Arguments de `url_for` dans le template { #template-url-for-arguments } + +Vous pouvez aussi utiliser `url_for()` dans le template ; elle prend en paramètres les mêmes arguments que ceux utilisés par votre *fonction de chemin d'accès*. + +Ainsi, la section suivante : + +{% raw %} + +```jinja + +``` + +{% endraw %} + +... générera un lien vers la même URL que celle gérée par la *fonction de chemin d'accès* `read_item(id=id)`. + +Par exemple, avec un ID de `42`, cela rendrait : + +```html + +``` + +## Templates et fichiers statiques { #templates-and-static-files } + +Vous pouvez aussi utiliser `url_for()` dans le template, par exemple avec les `StaticFiles` que vous avez montés avec `name="static"`. + +```jinja hl_lines="4" +{!../../docs_src/templates/templates/item.html!} +``` + +Dans cet exemple, cela créera un lien vers un fichier CSS `static/styles.css` avec : + +```CSS hl_lines="4" +{!../../docs_src/templates/static/styles.css!} +``` + +Et comme vous utilisez `StaticFiles`, ce fichier CSS est servi automatiquement par votre application **FastAPI** à l’URL `/static/styles.css`. + +## En savoir plus { #more-details } + +Pour plus de détails, y compris sur la façon de tester des templates, consultez la documentation de Starlette sur les templates. diff --git a/docs/fr/docs/advanced/testing-dependencies.md b/docs/fr/docs/advanced/testing-dependencies.md new file mode 100644 index 000000000..d6fc576bf --- /dev/null +++ b/docs/fr/docs/advanced/testing-dependencies.md @@ -0,0 +1,52 @@ +# Tester des dépendances avec des surcharges { #testing-dependencies-with-overrides } + +## Surcharger des dépendances pendant les tests { #overriding-dependencies-during-testing } + +Il existe des cas où vous souhaiterez surcharger une dépendance pendant les tests. + +Vous ne voulez pas exécuter la dépendance originale (ni ses éventuelles sous‑dépendances). + +À la place, vous souhaitez fournir une dépendance différente, utilisée uniquement pendant les tests (éventuellement seulement pour certains tests), et qui fournira une valeur utilisable partout où l’on utilisait celle de la dépendance originale. + +### Cas d’usage : service externe { #use-cases-external-service } + +Par exemple, vous avez un fournisseur d’authentification externe à appeler. + +Vous lui envoyez un token et il renvoie un utilisateur authentifié. + +Ce fournisseur peut vous facturer à la requête, et l’appeler peut prendre plus de temps que si vous aviez un utilisateur factice fixe pour les tests. + +Vous voudrez probablement tester le fournisseur externe une fois, mais pas nécessairement l’appeler pour chaque test exécuté. + +Dans ce cas, vous pouvez surcharger la dépendance qui appelle ce fournisseur et utiliser une dépendance personnalisée qui renvoie un utilisateur factice, uniquement pour vos tests. + +### Utiliser l’attribut `app.dependency_overrides` { #use-the-app-dependency-overrides-attribute } + +Pour ces cas, votre **FastAPI** application possède un attribut `app.dependency_overrides` ; c’est un simple `dict`. + +Pour surcharger une dépendance lors des tests, vous mettez comme clé la dépendance originale (une fonction) et comme valeur votre surcharge de dépendance (une autre fonction). + +Ensuite, **FastAPI** appellera cette surcharge au lieu de la dépendance originale. + +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} + +/// tip | Astuce + +Vous pouvez définir une surcharge de dépendance pour une dépendance utilisée n’importe où dans votre application **FastAPI**. + +La dépendance originale peut être utilisée dans une fonction de chemin d'accès, un décorateur de chemin d'accès (quand vous n’utilisez pas la valeur de retour), un appel à `.include_router()`, etc. + +FastAPI pourra toujours la surcharger. + +/// + +Vous pouvez ensuite réinitialiser vos surcharges (les supprimer) en affectant à `app.dependency_overrides` un `dict` vide : + +```Python +app.dependency_overrides = {} +``` +/// tip | Astuce + +Si vous souhaitez surcharger une dépendance uniquement pendant certains tests, vous pouvez définir la surcharge au début du test (dans la fonction de test) et la réinitialiser à la fin (à la fin de la fonction de test). + +/// diff --git a/docs/fr/docs/advanced/testing-events.md b/docs/fr/docs/advanced/testing-events.md new file mode 100644 index 000000000..c4f9141b3 --- /dev/null +++ b/docs/fr/docs/advanced/testing-events.md @@ -0,0 +1,11 @@ +# Tester les événements : lifespan et startup - shutdown { #testing-events-lifespan-and-startup-shutdown } + +Lorsque vous avez besoin d'exécuter `lifespan` dans vos tests, vous pouvez utiliser `TestClient` avec une instruction `with` : + +{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *} + +Vous pouvez lire plus de détails dans [« Exécuter lifespan dans les tests sur le site de documentation officiel de Starlette. »](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) + +Pour les événements dépréciés `startup` et `shutdown`, vous pouvez utiliser le `TestClient` comme suit : + +{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *} diff --git a/docs/fr/docs/advanced/testing-websockets.md b/docs/fr/docs/advanced/testing-websockets.md new file mode 100644 index 000000000..e9f97ab5f --- /dev/null +++ b/docs/fr/docs/advanced/testing-websockets.md @@ -0,0 +1,13 @@ +# Tester les WebSockets { #testing-websockets } + +Vous pouvez utiliser le même `TestClient` pour tester les WebSockets. + +Pour cela, vous utilisez `TestClient` dans une instruction `with`, en vous connectant au WebSocket : + +{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *} + +/// note | Remarque + +Pour plus de détails, consultez la documentation de Starlette sur le test des WebSockets. + +/// diff --git a/docs/fr/docs/advanced/using-request-directly.md b/docs/fr/docs/advanced/using-request-directly.md new file mode 100644 index 000000000..4df3f90e8 --- /dev/null +++ b/docs/fr/docs/advanced/using-request-directly.md @@ -0,0 +1,56 @@ +# Utiliser Request directement { #using-the-request-directly } + +Jusqu'à présent, vous avez déclaré les parties de la requête dont vous avez besoin, avec leurs types. + +En récupérant des données depuis : + +* Le chemin, sous forme de paramètres. +* En-têtes. +* Cookies. +* etc. + +Et ce faisant, **FastAPI** valide ces données, les convertit et génère automatiquement la documentation de votre API. + +Mais il existe des situations où vous pouvez avoir besoin d'accéder directement à l'objet `Request`. + +## Détails sur l'objet `Request` { #details-about-the-request-object } + +Comme **FastAPI** est en fait **Starlette** en dessous, avec une couche de plusieurs outils au-dessus, vous pouvez utiliser directement l'objet `Request` de Starlette lorsque vous en avez besoin. + +Cela signifie aussi que si vous récupérez des données directement à partir de l'objet `Request` (par exemple, lire le corps), elles ne seront pas validées, converties ni documentées (avec OpenAPI, pour l'interface utilisateur automatique de l'API) par FastAPI. + +En revanche, tout autre paramètre déclaré normalement (par exemple, le corps avec un modèle Pydantic) sera toujours validé, converti, annoté, etc. + +Mais il existe des cas spécifiques où il est utile d'obtenir l'objet `Request`. + +## Utiliser l'objet `Request` directement { #use-the-request-object-directly } + +Imaginons que vous souhaitiez obtenir l'adresse IP/l'hôte du client dans votre fonction de chemin d'accès. + +Pour cela, vous devez accéder directement à la requête. + +{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *} + +En déclarant un paramètre de fonction de chemin d'accès de type `Request`, **FastAPI** saura passer la `Request` dans ce paramètre. + +/// tip | Astuce + +Notez que, dans ce cas, nous déclarons un paramètre de chemin en plus du paramètre de requête. + +Ainsi, le paramètre de chemin sera extrait, validé, converti vers le type spécifié et annoté avec OpenAPI. + +De la même façon, vous pouvez déclarer tout autre paramètre normalement, et en plus, obtenir aussi la `Request`. + +/// + +## Documentation de `Request` { #request-documentation } + +Vous pouvez lire plus de détails sur l'objet `Request` sur le site de documentation officiel de Starlette. + +/// note | Détails techniques + +Vous pouvez également utiliser `from starlette.requests import Request`. + +**FastAPI** le fournit directement pour votre commodité, en tant que développeur. Mais il provient directement de Starlette. + +/// diff --git a/docs/fr/docs/advanced/websockets.md b/docs/fr/docs/advanced/websockets.md new file mode 100644 index 000000000..6f5c3e703 --- /dev/null +++ b/docs/fr/docs/advanced/websockets.md @@ -0,0 +1,186 @@ +# WebSockets { #websockets } + +Vous pouvez utiliser API WebSockets avec **FastAPI**. + +## Installer `websockets` { #install-websockets } + +Vous devez créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, l'activer, et installer `websockets` (une bibliothèque Python qui facilite l'utilisation du protocole « WebSocket ») : + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## Client WebSocket { #websockets-client } + +### En production { #in-production } + +Dans votre système de production, vous avez probablement un frontend créé avec un framework moderne comme React, Vue.js ou Angular. + +Et pour communiquer en utilisant WebSockets avec votre backend, vous utiliseriez probablement les outils fournis par votre frontend. + +Ou vous pouvez avoir une application mobile native qui communique directement avec votre backend WebSocket, en code natif. + +Ou vous pouvez avoir toute autre façon de communiquer avec l'endpoint WebSocket. + +--- + +Mais pour cet exemple, nous utiliserons un document HTML très simple avec un peu de JavaScript, le tout dans une longue chaîne. + +Cela, bien entendu, n'est pas optimal et vous ne l'utiliseriez pas en production. + +En production, vous auriez l'une des options ci-dessus. + +Mais c'est la façon la plus simple de se concentrer sur la partie serveur des WebSockets et d'avoir un exemple fonctionnel : + +{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} + +## Créer un `websocket` { #create-a-websocket } + +Dans votre application **FastAPI**, créez un `websocket` : + +{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} + +/// note | Détails techniques + +Vous pourriez aussi utiliser `from starlette.websockets import WebSocket`. + +**FastAPI** fournit le même `WebSocket` directement, simplement pour vous faciliter la vie en tant que développeur. Mais il provient directement de Starlette. + +/// + +## Attendre des messages et envoyer des messages { #await-for-messages-and-send-messages } + +Dans votre route WebSocket, vous pouvez `await` des messages et envoyer des messages. + +{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} + +Vous pouvez recevoir et envoyer des données binaires, texte et JSON. + +## Essayer { #try-it } + +Si votre fichier s'appelle `main.py`, exécutez votre application avec : + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Ouvrez votre navigateur à l'adresse http://127.0.0.1:8000. + +Vous verrez une page simple comme : + + + +Vous pouvez saisir des messages dans le champ de saisie et les envoyer : + + + +Et votre application **FastAPI** avec WebSockets vous répondra : + + + +Vous pouvez envoyer (et recevoir) de nombreux messages : + + + +Et tous utiliseront la même connexion WebSocket. + +## Utiliser `Depends` et autres { #using-depends-and-others } + +Dans les endpoints WebSocket, vous pouvez importer depuis `fastapi` et utiliser : + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +Ils fonctionnent de la même manière que pour les autres endpoints/*chemins d'accès* FastAPI : + +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} + +/// info + +Comme il s'agit d'un WebSocket, il n'est pas vraiment logique de lever une `HTTPException`, nous levons plutôt une `WebSocketException`. + +Vous pouvez utiliser un code de fermeture parmi les codes valides définis dans la spécification. + +/// + +### Essayez les WebSockets avec des dépendances { #try-the-websockets-with-dependencies } + +Si votre fichier s'appelle `main.py`, exécutez votre application avec : + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Ouvrez votre navigateur à l'adresse http://127.0.0.1:8000. + +Là, vous pouvez définir : + +* « Item ID », utilisé dans le chemin. +* « Token » utilisé comme paramètre de requête. + +/// tip | Astuce + +Notez que le `token` de requête sera géré par une dépendance. + +/// + +Avec cela, vous pouvez connecter le WebSocket puis envoyer et recevoir des messages : + + + +## Gérer les déconnexions et plusieurs clients { #handling-disconnections-and-multiple-clients } + +Lorsqu'une connexion WebSocket est fermée, l'instruction `await websocket.receive_text()` lèvera une exception `WebSocketDisconnect`, que vous pouvez ensuite intercepter et gérer comme dans cet exemple. + +{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} + +Pour l'essayer : + +* Ouvrez l'application dans plusieurs onglets du navigateur. +* Écrivez des messages depuis ceux-ci. +* Puis fermez l'un des onglets. + +Cela lèvera l'exception `WebSocketDisconnect`, et tous les autres clients recevront un message comme : + +``` +Client #1596980209979 left the chat +``` + +/// tip | Astuce + +L'application ci-dessus est un exemple minimal et simple pour montrer comment gérer et diffuser des messages à plusieurs connexions WebSocket. + +Mais gardez à l'esprit que, comme tout est géré en mémoire, dans une seule liste, cela ne fonctionnera que tant que le processus s'exécute et uniquement avec un seul processus. + +Si vous avez besoin de quelque chose de facile à intégrer avec FastAPI mais plus robuste, pris en charge par Redis, PostgreSQL ou autres, consultez encode/broadcaster. + +/// + +## Plus d'informations { #more-info } + +Pour en savoir plus sur les options, consultez la documentation de Starlette concernant : + +* La classe `WebSocket`. +* Gestion des WebSocket basée sur des classes. diff --git a/docs/fr/docs/advanced/wsgi.md b/docs/fr/docs/advanced/wsgi.md new file mode 100644 index 000000000..fc89819d2 --- /dev/null +++ b/docs/fr/docs/advanced/wsgi.md @@ -0,0 +1,51 @@ +# Inclure WSGI - Flask, Django, autres { #including-wsgi-flask-django-others } + +Vous pouvez monter des applications WSGI comme vous l'avez vu avec [Sous-applications - Montages](sub-applications.md){.internal-link target=_blank}, [Derrière un proxy](behind-a-proxy.md){.internal-link target=_blank}. + +Pour cela, vous pouvez utiliser `WSGIMiddleware` et l'utiliser pour envelopper votre application WSGI, par exemple Flask, Django, etc. + +## Utiliser `WSGIMiddleware` { #using-wsgimiddleware } + +/// info + +Cela nécessite l'installation de `a2wsgi`, par exemple avec `pip install a2wsgi`. + +/// + +Vous devez importer `WSGIMiddleware` depuis `a2wsgi`. + +Ensuite, enveloppez l'application WSGI (par ex. Flask) avec le middleware. + +Puis, montez-la sous un chemin. + +{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *} + +/// note | Remarque + +Auparavant, il était recommandé d'utiliser `WSGIMiddleware` depuis `fastapi.middleware.wsgi`, mais il est désormais déprécié. + +Il est conseillé d'utiliser le package `a2wsgi` à la place. L'utilisation reste la même. + +Assurez-vous simplement que le package `a2wsgi` est installé et importez `WSGIMiddleware` correctement depuis `a2wsgi`. + +/// + +## Vérifiez { #check-it } + +Désormais, chaque requête sous le chemin `/v1/` sera gérée par l'application Flask. + +Et le reste sera géré par **FastAPI**. + +Si vous l'exécutez et allez à http://localhost:8000/v1/, vous verrez la réponse de Flask : + +```txt +Hello, World from Flask! +``` + +Et si vous allez à http://localhost:8000/v2, vous verrez la réponse de FastAPI : + +```JSON +{ + "message": "Hello World" +} +``` diff --git a/docs/fr/docs/alternatives.md b/docs/fr/docs/alternatives.md index 9d8d85705..c344bd1f8 100644 --- a/docs/fr/docs/alternatives.md +++ b/docs/fr/docs/alternatives.md @@ -1,8 +1,8 @@ -# Alternatives, inspiration et comparaisons +# Alternatives, inspiration et comparaisons { #alternatives-inspiration-and-comparisons } Ce qui a inspiré **FastAPI**, comment il se compare à d'autres solutions et ce qu'il en a appris. -## Intro +## Intro { #intro } **FastAPI** n'existerait pas sans les précédentes contributions d'autres projets. @@ -13,11 +13,11 @@ fonctionnalités couvertes par **FastAPI** en utilisant de nombreux frameworks, Mais à un moment donné il n'y avait pas d'autre option que de créer quelque chose qui offrait toutes ces fonctionnalités, en reprenant et en combinant de la meilleure façon possible les meilleures idées des outils -précédents, en utilisant des fonctionnalités du langage qui n'étaient même pas disponibles auparavant (type hints depuis Python 3.6+). +précédents, en utilisant des fonctionnalités du langage qui n'étaient même pas disponibles auparavant (annotations de type depuis Python 3.6+). -## Outils précédents +## Outils précédents { #previous-tools } -### Django +### Django { #django } C'est le framework Python le plus populaire et il bénéficie d'une grande confiance. Il est utilisé pour construire des systèmes tel qu'Instagram. @@ -26,18 +26,18 @@ Il est relativement fortement couplé aux bases de données relationnelles (comm n'est pas très facile d'utiliser une base de données NoSQL (comme Couchbase, MongoDB, Cassandra, etc.) comme principal moyen de stockage. -Il a été créé pour générer le HTML en backend, pas pour créer des API consommées par un frontend moderne (comme React, Vue.js et Angular) ou par d'autres systèmes (comme les appareils IoT) communiquant avec lui. +Il a été créé pour générer le HTML en backend, pas pour créer des API consommées par un frontend moderne (comme React, Vue.js et Angular) ou par d'autres systèmes (comme les appareils IoT) communiquant avec lui. -### Django REST Framework +### Django REST Framework { #django-rest-framework } Django REST framework a été conçu comme une boîte à outils flexible permettant de construire des API Web à partir de Django, afin d'améliorer ses capacités en matière d'API. Il est utilisé par de nombreuses entreprises, dont Mozilla, Red Hat et Eventbrite. Il s'agissait de l'un des premiers exemples de **documentation automatique pour API**, et c'est précisément l'une des -premières idées qui a inspiré "la recherche de" **FastAPI**. +premières idées qui a inspiré « la recherche de » **FastAPI**. -/// note +/// note | Remarque Django REST framework a été créé par Tom Christie. Le créateur de Starlette et Uvicorn, sur lesquels **FastAPI** est basé. @@ -49,9 +49,9 @@ Avoir une interface de documentation automatique de l'API. /// -### Flask +### Flask { #flask } -Flask est un "micro-framework", il ne comprend pas d'intégrations de bases de données ni beaucoup de choses qui sont fournies par défaut dans Django. +Flask est un « micro‑framework », il ne comprend pas d'intégrations de bases de données ni beaucoup de choses qui sont fournies par défaut dans Django. Cette simplicité et cette flexibilité permettent d'utiliser des bases de données NoSQL comme principal système de stockage de données. @@ -60,20 +60,20 @@ technique par moments. Il est aussi couramment utilisé pour d'autres applications qui n'ont pas nécessairement besoin d'une base de données, de gestion des utilisateurs ou de l'une des nombreuses fonctionnalités préinstallées dans Django. Bien que beaucoup de ces fonctionnalités puissent être ajoutées avec des plug-ins. -Ce découplage des parties, et le fait d'être un "micro-framework" qui puisse être étendu pour couvrir exactement ce +Ce découplage des parties, et le fait d'être un « micro‑framework » qui puisse être étendu pour couvrir exactement ce qui est nécessaire, était une caractéristique clé que je voulais conserver. -Compte tenu de la simplicité de Flask, il semblait bien adapté à la création d'API. La prochaine chose à trouver était un "Django REST Framework" pour Flask. +Compte tenu de la simplicité de Flask, il semblait bien adapté à la création d'API. La prochaine chose à trouver était un « Django REST Framework » pour Flask. /// check | A inspiré **FastAPI** à -Être un micro-framework. Il est donc facile de combiner les outils et les pièces nécessaires. +Être un micro‑framework. Il est donc facile de combiner les outils et les pièces nécessaires. Proposer un système de routage simple et facile à utiliser. /// -### Requests +### Requests { #requests } **FastAPI** n'est pas réellement une alternative à **Requests**. Leur cadre est très différent. @@ -97,7 +97,7 @@ La façon dont vous l'utilisez est très simple. Par exemple, pour faire une req response = requests.get("http://example.com/some/url") ``` -En contrepartie l'API _des opérations de chemin_ de FastAPI pourrait ressembler à ceci : +L’opération de chemin d'accès correspondante dans **FastAPI** pourrait ressembler à ceci : ```Python hl_lines="1" @app.get("/some/url") @@ -109,13 +109,13 @@ Notez les similitudes entre `requests.get(...)` et `@app.get(...)`. /// check | A inspiré **FastAPI** à -Avoir une API simple et intuitive. - -Utiliser les noms de méthodes HTTP (opérations) directement, de manière simple et intuitive. \* Avoir des valeurs par défaut raisonnables, mais des personnalisations puissantes. +* Avoir une API simple et intuitive. +* Utiliser les noms de méthodes HTTP (opérations) directement, de manière simple et intuitive. +* Avoir des valeurs par défaut raisonnables, mais des personnalisations puissantes. /// -### Swagger / OpenAPI +### Swagger / OpenAPI { #swagger-openapi } La principale fonctionnalité que j'ai emprunté à Django REST Framework était la documentation automatique des API. @@ -126,7 +126,7 @@ Swagger pour une API permettrait d'utiliser cette interface utilisateur web auto À un moment donné, Swagger a été cédé à la Fondation Linux, puis a été rebaptisé OpenAPI. -C'est pourquoi, lorsqu'on parle de la version 2.0, il est courant de dire "Swagger", et pour la version 3+ "OpenAPI". +C'est pourquoi, lorsqu'on parle de la version 2.0, il est courant de dire « Swagger », et pour la version 3+ « OpenAPI ». /// check | A inspiré **FastAPI** à @@ -141,16 +141,15 @@ Ces deux-là ont été choisis parce qu'ils sont populaires et stables, mais en /// -### Frameworks REST pour Flask +### Frameworks REST pour Flask { #flask-rest-frameworks } Il y a plusieurs frameworks REST pour Flask, mais après avoir investi du temps et du travail pour les étudier, j'ai découvert que le développement de beaucoup d'entre eux sont suspendus ou abandonnés, avec plusieurs problèmes permanents qui les rendent inadaptés. -### Marshmallow +### Marshmallow { #marshmallow } -L'une des principales fonctionnalités nécessaires aux systèmes API est la "sérialisation" des données, qui consiste à prendre les données du code (Python) et à +L'une des principales fonctionnalités nécessaires aux systèmes API est la « sérialisation » des données, qui consiste à prendre les données du code (Python) et à les convertir en quelque chose qui peut être envoyé sur le réseau. Par exemple, convertir un objet contenant des données provenant d'une base de données en un objet JSON. Convertir des objets `datetime` en strings, etc. @@ -163,19 +162,17 @@ Sans un système de validation des données, vous devriez effectuer toutes les v Ces fonctionnalités sont ce pourquoi Marshmallow a été construit. C'est une excellente bibliothèque, et je l'ai déjà beaucoup utilisée. -Mais elle a été créée avant que les type hints n'existent en Python. Ainsi, pour définir chaque schéma, vous devez utiliser des utilitaires et des classes spécifiques fournies par Marshmallow. +Mais elle a été créée avant que les annotations de type n'existent en Python. Ainsi, pour définir chaque schéma, vous devez utiliser des utilitaires et des classes spécifiques fournies par Marshmallow. /// check | A inspiré **FastAPI** à -Utilisez du code pour définir des "schémas" qui fournissent automatiquement les types de données et la validation. +Utilisez du code pour définir des « schémas » qui fournissent automatiquement les types de données et la validation. /// -### Webargs +### Webargs { #webargs } -Une autre grande fonctionnalité requise par les API est le parsing des données provenant des requêtes entrantes. +Une autre grande fonctionnalité requise par les API est l’analyse des données provenant des requêtes entrantes. Webargs est un outil qui a été créé pour fournir cela par-dessus plusieurs frameworks, dont Flask. @@ -195,7 +192,7 @@ Disposer d'une validation automatique des données des requêtes entrantes. /// -### APISpec +### APISpec { #apispec } Marshmallow et Webargs fournissent la validation, l'analyse et la sérialisation en tant que plug-ins. @@ -225,7 +222,7 @@ Supporter la norme ouverte pour les API, OpenAPI. /// -### Flask-apispec +### Flask-apispec { #flask-apispec } C'est un plug-in pour Flask, qui relie Webargs, Marshmallow et APISpec. @@ -240,11 +237,11 @@ Cette combinaison de Flask, Flask-apispec avec Marshmallow et Webargs était ma Son utilisation a conduit à la création de plusieurs générateurs Flask full-stack. Ce sont les principales stacks que j'ai (ainsi que plusieurs équipes externes) utilisées jusqu'à présent : -- https://github.com/tiangolo/full-stack -- https://github.com/tiangolo/full-stack-flask-couchbase -- https://github.com/tiangolo/full-stack-flask-couchdb +* https://github.com/tiangolo/full-stack +* https://github.com/tiangolo/full-stack-flask-couchbase +* https://github.com/tiangolo/full-stack-flask-couchdb -Ces mêmes générateurs full-stack ont servi de base aux [Générateurs de projets pour **FastAPI**](project-generation.md){.internal-link target=\_blank}. +Ces mêmes générateurs full-stack ont servi de base aux [Générateurs de projets pour **FastAPI**](project-generation.md){.internal-link target=_blank}. /// info @@ -258,15 +255,15 @@ Générer le schéma OpenAPI automatiquement, à partir du même code qui défin /// -### NestJS (et Angular) +### NestJS (et Angular) { #nestjs-and-angular } Ce n'est même pas du Python, NestJS est un framework JavaScript (TypeScript) NodeJS inspiré d'Angular. Il réalise quelque chose de similaire à ce qui peut être fait avec Flask-apispec. -Il possède un système d'injection de dépendances intégré, inspiré d'Angular 2. Il nécessite de pré-enregistrer les "injectables" (comme tous les autres systèmes d'injection de dépendances que je connais), donc, cela ajoute à la verbosité et à la répétition du code. +Il possède un système d'injection de dépendances intégré, inspiré d'Angular 2. Il nécessite de pré-enregistrer les « injectables » (comme tous les autres systèmes d'injection de dépendances que je connais), donc, cela ajoute à la verbosité et à la répétition du code. -Comme les paramètres sont décrits avec des types TypeScript (similaires aux type hints de Python), la prise en charge +Comme les paramètres sont décrits avec des types TypeScript (similaires aux annotations de type de Python), la prise en charge par l'éditeur est assez bonne. Mais comme les données TypeScript ne sont pas préservées après la compilation en JavaScript, il ne peut pas compter sur les types pour définir la validation, la sérialisation et la documentation en même temps. En raison de cela et de certaines décisions de conception, pour obtenir la validation, la sérialisation et la génération automatique de schémas, il est nécessaire d'ajouter des décorateurs à de nombreux endroits. Cela devient donc assez verbeux. @@ -281,7 +278,7 @@ Disposer d'un puissant système d'injection de dépendances. Trouver un moyen de /// -### Sanic +### Sanic { #sanic } C'était l'un des premiers frameworks Python extrêmement rapides basés sur `asyncio`. Il a été conçu pour être très similaire à Flask. @@ -301,14 +298,12 @@ C'est pourquoi **FastAPI** est basé sur Starlette, car il s'agit du framework l /// -### Falcon +### Falcon { #falcon } Falcon est un autre framework Python haute performance, il est conçu pour être minimal, et est utilisé comme fondation pour d'autres frameworks comme Hug. -Il utilise le standard précédent pour les frameworks web Python (WSGI) qui est synchrone, donc il ne peut pas gérer les WebSockets et d'autres cas d'utilisation. Néanmoins, il offre de très bonnes performances. - -Il est conçu pour avoir des fonctions qui reçoivent deux paramètres, une "requête" et une "réponse". Ensuite, vous -"lisez" des parties de la requête et "écrivez" des parties dans la réponse. En raison de cette conception, il n'est +Il est conçu pour avoir des fonctions qui reçoivent deux paramètres, une « requête » et une « réponse ». Ensuite, vous +« lisez » des parties de la requête et « écrivez » des parties dans la réponse. En raison de cette conception, il n'est pas possible de déclarer des paramètres de requête et des corps avec des indications de type Python standard comme paramètres de fonction. Ainsi, la validation, la sérialisation et la documentation des données doivent être effectuées dans le code, et non pas automatiquement. Ou bien elles doivent être implémentées comme un framework au-dessus de Falcon, comme Hug. Cette même distinction se retrouve dans d'autres frameworks qui s'inspirent de la conception de Falcon, qui consiste à avoir un objet de requête et un objet de réponse comme paramètres. @@ -323,20 +318,20 @@ Bien que dans FastAPI, il est facultatif, et est utilisé principalement pour d /// -### Molten +### Molten { #molten } J'ai découvert Molten lors des premières étapes de développement de **FastAPI**. Et il a des idées assez similaires : -- Basé sur les type hints Python. -- Validation et documentation via ces types. -- Système d'injection de dépendances. +* Basé sur les annotations de type Python. +* Validation et documentation via ces types. +* Système d'injection de dépendances. Il n'utilise pas une librairie tiers de validation, sérialisation et de documentation tel que Pydantic, il utilise son propre système. Ainsi, ces définitions de types de données ne sont pas réutilisables aussi facilement. -Il nécessite une configuration un peu plus verbeuse. Et comme il est basé sur WSGI (au lieu dASGI), il n'est pas +Il nécessite une configuration un peu plus verbeuse. Et comme il est basé sur WSGI (au lieu d'ASGI), il n'est pas conçu pour profiter des hautes performances fournies par des outils comme Uvicorn, Starlette et Sanic. -Le système d'injection de dépendances exige le pré-enregistrement des dépendances et les dépendances sont résolues sur la base des types déclarés. Ainsi, il n'est pas possible de déclarer plus d'un "composant" qui fournit un certain type. +Le système d'injection de dépendances exige le pré-enregistrement des dépendances et les dépendances sont résolues sur la base des types déclarés. Ainsi, il n'est pas possible de déclarer plus d'un « composant » qui fournit un certain type. Les routes sont déclarées à un seul endroit, en utilisant des fonctions déclarées à d'autres endroits (au lieu d'utiliser des décorateurs qui peuvent être placés juste au-dessus de la fonction qui gère l'endpoint). Cette @@ -345,15 +340,15 @@ qui sont relativement fortement couplées. /// check | A inspiré **FastAPI** à -Définir des validations supplémentaires pour les types de données utilisant la valeur "par défaut" des attributs du modèle. Ceci améliore le support de l'éditeur, et n'était pas disponible dans Pydantic auparavant. +Définir des validations supplémentaires pour les types de données utilisant la valeur « par défaut » des attributs du modèle. Ceci améliore le support de l'éditeur, et n'était pas disponible dans Pydantic auparavant. Cela a en fait inspiré la mise à jour de certaines parties de Pydantic, afin de supporter le même style de déclaration de validation (toute cette fonctionnalité est maintenant déjà disponible dans Pydantic). /// -### Hug +### Hug { #hug } -Hug a été l'un des premiers frameworks à implémenter la déclaration des types de paramètres d'API en utilisant les type hints Python. C'était une excellente idée qui a inspiré d'autres outils à faire de même. +Hug a été l'un des premiers frameworks à implémenter la déclaration des types de paramètres d'API en utilisant les annotations de type Python. C'était une excellente idée qui a inspiré d'autres outils à faire de même. Il utilisait des types personnalisés dans ses déclarations au lieu des types Python standard, mais c'était tout de même un énorme pas en avant. @@ -372,28 +367,28 @@ Hug a été créé par Timothy Crosley, le créateur de APIStar (<= 0.5) +### APIStar (<= 0.5) { #apistar-0-5 } Juste avant de décider de développer **FastAPI**, j'ai trouvé le serveur **APIStar**. Il contenait presque tout ce que je recherchais et avait un beau design. -C'était l'une des premières implémentations d'un framework utilisant les type hints Python pour déclarer les paramètres +C'était l'une des premières implémentations d'un framework utilisant les annotations de type Python pour déclarer les paramètres et les requêtes que j'ai vues (avant NestJS et Molten). Je l'ai trouvé plus ou moins en même temps que Hug. Mais APIStar utilisait le standard OpenAPI. -Il disposait de la validation automatique, sérialisation des données et d'une génération de schéma OpenAPI basée sur les mêmes type hints à plusieurs endroits. +Il disposait de la validation automatique, sérialisation des données et d'une génération de schéma OpenAPI basée sur les mêmes annotations de type à plusieurs endroits. -La définition du schéma de corps de requête n'utilisait pas les mêmes type hints Python que Pydantic, il était un peu plus proche de Marshmallow, donc le support de l'éditeur n'était pas aussi bon, mais APIStar était quand même la meilleure option disponible. +La définition du schéma de corps de requête n'utilisait pas les mêmes annotations de type Python que Pydantic, il était un peu plus proche de Marshmallow, donc le support de l'éditeur n'était pas aussi bon, mais APIStar était quand même la meilleure option disponible. Il avait les meilleures performances d'après les benchmarks de l'époque (seulement surpassé par Starlette). @@ -429,20 +424,20 @@ Et après avoir longtemps cherché un framework similaire et testé de nombreuse Puis APIStar a cessé d'exister en tant que serveur et Starlette a été créé, et a constitué une meilleure base pour un tel système. Ce fut l'inspiration finale pour construire **FastAPI**. -Je considère **FastAPI** comme un "successeur spirituel" d'APIStar, tout en améliorant et en augmentant les fonctionnalités, le système de typage et d'autres parties, sur la base des enseignements tirés de tous ces outils précédents. +Je considère **FastAPI** comme un « successeur spirituel » d'APIStar, tout en améliorant et en augmentant les fonctionnalités, le système de typage et d'autres parties, sur la base des enseignements tirés de tous ces outils précédents. /// -## Utilisés par **FastAPI** +## Utilisés par **FastAPI** { #used-by-fastapi } -### Pydantic +### Pydantic { #pydantic } -Pydantic est une bibliothèque permettant de définir la validation, la sérialisation et la documentation des données (à l'aide de JSON Schema) en se basant sur les Python type hints. +Pydantic est une bibliothèque permettant de définir la validation, la sérialisation et la documentation des données (à l'aide de JSON Schema) en se basant sur les annotations de type Python. Cela le rend extrêmement intuitif. Il est comparable à Marshmallow. Bien qu'il soit plus rapide que Marshmallow dans les benchmarks. Et comme il est -basé sur les mêmes type hints Python, le support de l'éditeur est grand. +basé sur les mêmes annotations de type Python, le support de l'éditeur est grand. /// check | **FastAPI** l'utilise pour @@ -452,9 +447,9 @@ Gérer toute la validation des données, leur sérialisation et la documentation /// -### Starlette +### Starlette { #starlette } -Starlette est un framework/toolkit léger ASGI, qui est idéal pour construire des services asyncio performants. +Starlette est un framework/toolkit léger ASGI, qui est idéal pour construire des services asyncio performants. Il est très simple et intuitif. Il est conçu pour être facilement extensible et avoir des composants modulaires. @@ -462,29 +457,28 @@ Il offre : - Des performances vraiment impressionnantes. - Le support des WebSockets. -- Le support de GraphQL. - Les tâches d'arrière-plan. - Les événements de démarrage et d'arrêt. -- Un client de test basé sur request. +- Un client de test basé sur HTTPX. - CORS, GZip, fichiers statiques, streaming des réponses. - Le support des sessions et des cookies. - Une couverture de test à 100 %. - 100 % de la base de code avec des annotations de type. -- Zéro forte dépendance à d'autres packages. +- Peu de dépendances strictes. Starlette est actuellement le framework Python le plus rapide testé. Seulement dépassé par Uvicorn, qui n'est pas un framework, mais un serveur. -Starlette fournit toutes les fonctionnalités de base d'un micro-framework web. +Starlette fournit toutes les fonctionnalités de base d'un micro‑framework web. Mais il ne fournit pas de validation automatique des données, de sérialisation ou de documentation. -C'est l'une des principales choses que **FastAPI** ajoute par-dessus, le tout basé sur les type hints Python (en utilisant Pydantic). Cela, plus le système d'injection de dépendances, les utilitaires de sécurité, la génération de schémas OpenAPI, etc. +C'est l'une des principales choses que **FastAPI** ajoute par-dessus, le tout basé sur les annotations de type Python (en utilisant Pydantic). Cela, plus le système d'injection de dépendances, les utilitaires de sécurité, la génération de schémas OpenAPI, etc. /// note | Détails techniques -ASGI est une nouvelle "norme" développée par les membres de l'équipe principale de Django. Il ne s'agit pas encore d'une "norme Python" (un PEP), bien qu'ils soient en train de le faire. +ASGI est une nouvelle « norme » développée par les membres de l'équipe principale de Django. Il ne s'agit pas encore d'une « norme Python » (un PEP), bien qu'ils soient en train de le faire. -Néanmoins, il est déjà utilisé comme "standard" par plusieurs outils. Cela améliore grandement l'interopérabilité, puisque vous pouvez remplacer Uvicorn par n'importe quel autre serveur ASGI (comme Daphne ou Hypercorn), ou vous pouvez ajouter des outils compatibles ASGI, comme `python-socketio`. +Néanmoins, il est déjà utilisé comme « standard » par plusieurs outils. Cela améliore grandement l'interopérabilité, puisque vous pouvez remplacer Uvicorn par n'importe quel autre serveur ASGI (comme Daphne ou Hypercorn), ou vous pouvez ajouter des outils compatibles ASGI, comme `python-socketio`. /// @@ -498,7 +492,7 @@ Ainsi, tout ce que vous pouvez faire avec Starlette, vous pouvez le faire direct /// -### Uvicorn +### Uvicorn { #uvicorn } Uvicorn est un serveur ASGI rapide comme l'éclair, basé sur uvloop et httptools. @@ -511,12 +505,12 @@ C'est le serveur recommandé pour Starlette et **FastAPI**. Le serveur web principal pour exécuter les applications **FastAPI**. -Vous pouvez le combiner avec Gunicorn, pour avoir un serveur multi-processus asynchrone. +Vous pouvez également utiliser l'option de ligne de commande `--workers` pour avoir un serveur multi‑processus asynchrone. Pour plus de détails, consultez la section [Déploiement](deployment/index.md){.internal-link target=_blank}. /// -## Benchmarks et vitesse +## Benchmarks et vitesse { #benchmarks-and-speed } -Pour comprendre, comparer et voir la différence entre Uvicorn, Starlette et FastAPI, consultez la section sur les [Benchmarks](benchmarks.md){.internal-link target=\_blank}. +Pour comprendre, comparer et voir la différence entre Uvicorn, Starlette et FastAPI, consultez la section sur les [Benchmarks](benchmarks.md){.internal-link target=_blank}. diff --git a/docs/fr/docs/async.md b/docs/fr/docs/async.md index 1437ae517..72923e03b 100644 --- a/docs/fr/docs/async.md +++ b/docs/fr/docs/async.md @@ -1,17 +1,18 @@ -# Concurrence et les mots-clés async et await +# Concurrence et async / await { #concurrency-and-async-await } -Cette page vise à fournir des détails sur la syntaxe `async def` pour les *fonctions de chemins* et quelques rappels sur le code asynchrone, la concurrence et le parallélisme. +Détails sur la syntaxe `async def` pour les *fonctions de chemin d'accès* et quelques rappels sur le code asynchrone, la concurrence et le parallélisme. -## Vous êtes pressés ? +## Vous êtes pressés ? { #in-a-hurry } -TL;DR : +TL;DR : Si vous utilisez des bibliothèques tierces qui nécessitent d'être appelées avec `await`, telles que : ```Python results = await some_library() ``` -Alors, déclarez vos *fonctions de chemins* avec `async def` comme ceci : + +Alors, déclarez vos *fonctions de chemin d'accès* avec `async def` comme ceci : ```Python hl_lines="2" @app.get('/') @@ -20,7 +21,7 @@ async def read_results(): return results ``` -/// note +/// note | Remarque Vous pouvez uniquement utiliser `await` dans les fonctions créées avec `async def`. @@ -28,7 +29,7 @@ Vous pouvez uniquement utiliser `await` dans les fonctions créées avec `async --- -Si vous utilisez une bibliothèque externe qui communique avec quelque chose (une BDD, une API, un système de fichiers, etc.) et qui ne supporte pas l'utilisation d'`await` (ce qui est actuellement le cas pour la majorité des bibliothèques de BDD), alors déclarez vos *fonctions de chemin* normalement, avec le classique `def`, comme ceci : +Si vous utilisez une bibliothèque externe qui communique avec quelque chose (une base de données, une API, le système de fichiers, etc.) et qui ne supporte pas l'utilisation d'`await` (ce qui est actuellement le cas pour la majorité des bibliothèques de base de données), alors déclarez vos *fonctions de chemin d'accès* normalement, avec le classique `def`, comme ceci : ```Python hl_lines="2" @app.get('/') @@ -39,7 +40,7 @@ def results(): --- -Si votre application n'a pas à communiquer avec une bibliothèque externe et pas à attendre de réponse, utilisez `async def`. +Si votre application n'a pas à communiquer avec une autre chose et à attendre sa réponse, utilisez `async def`, même si vous n'avez pas besoin d'utiliser `await` à l'intérieur. --- @@ -47,15 +48,15 @@ Si vous ne savez pas, utilisez seulement `def` comme vous le feriez habituelleme --- -**Note** : vous pouvez mélanger `def` et `async def` dans vos *fonctions de chemin* autant que nécessaire, **FastAPI** saura faire ce qu'il faut avec. +Note : vous pouvez mélanger `def` et `async def` dans vos *fonctions de chemin d'accès* autant que nécessaire, et définir chacune avec l’option la plus adaptée pour vous. FastAPI fera ce qu'il faut avec elles. -Au final, peu importe le cas parmi ceux ci-dessus, **FastAPI** fonctionnera de manière asynchrone et sera extrêmement rapide. +Au final, peu importe le cas parmi ceux ci-dessus, FastAPI fonctionnera de manière asynchrone et sera extrêmement rapide. -Mais si vous suivez bien les instructions ci-dessus, alors **FastAPI** pourra effectuer quelques optimisations et ainsi améliorer les performances. +Mais si vous suivez bien les instructions ci-dessus, il pourra effectuer quelques optimisations et ainsi améliorer les performances. -## Détails techniques +## Détails techniques { #technical-details } -Les versions modernes de Python supportent le **code asynchrone** grâce aux **"coroutines"** avec les syntaxes **`async` et `await`**. +Les versions modernes de Python supportent le **code asynchrone** grâce aux **« coroutines »** avec les syntaxes **`async` et `await`**. Analysons les différentes parties de cette phrase dans les sections suivantes : @@ -63,46 +64,46 @@ Analysons les différentes parties de cette phrase dans les sections suivantes : * **`async` et `await`** * **Coroutines** -## Code asynchrone +## Code asynchrone { #asynchronous-code } -Faire du code asynchrone signifie que le langage 💬 est capable de dire à l'ordinateur / au programme 🤖 qu'à un moment du code, il 🤖 devra attendre que *quelque chose d'autre* se termine autre part. Disons que ce *quelque chose d'autre* est appelé "fichier-lent" 📝. +Faire du code asynchrone signifie que le langage 💬 est capable de dire à l'ordinateur / au programme 🤖 qu'à un moment du code, il 🤖 devra attendre que *quelque chose d'autre* se termine autre part. Disons que ce *quelque chose d'autre* est appelé « slow-file » 📝. -Donc, pendant ce temps, l'ordinateur pourra effectuer d'autres tâches, pendant que "fichier-lent" 📝 se termine. +Donc, pendant ce temps, l'ordinateur pourra effectuer d'autres tâches, pendant que « slow-file » 📝 se termine. Ensuite l'ordinateur / le programme 🤖 reviendra à chaque fois qu'il en a la chance que ce soit parce qu'il attend à nouveau, ou car il 🤖 a fini tout le travail qu'il avait à faire. Il 🤖 regardera donc si les tâches qu'il attend ont terminé d'être effectuées. -Ensuite, il 🤖 prendra la première tâche à finir (disons, notre "fichier-lent" 📝) et continuera à faire avec cette dernière ce qu'il était censé. +Ensuite, il 🤖 prendra la première tâche à finir (disons, notre « slow-file » 📝) et continuera à faire avec cette dernière ce qu'il était censé. -Ce "attendre quelque chose d'autre" fait généralement référence à des opérations I/O qui sont relativement "lentes" (comparées à la vitesse du processeur et de la mémoire RAM) telles qu'attendre que : +Ce « attendre quelque chose d'autre » fait généralement référence à des opérations I/O qui sont relativement « lentes » (comparées à la vitesse du processeur et de la mémoire RAM) telles qu'attendre que : * de la donnée soit envoyée par le client à travers le réseau * de la donnée envoyée depuis votre programme soit reçue par le client à travers le réseau * le contenu d'un fichier sur le disque soit lu par le système et passé à votre programme * le contenu que votre programme a passé au système soit écrit sur le disque * une opération effectuée à distance par une API se termine -* une opération en BDD se termine -* une requête à une BDD renvoie un résultat +* une opération en base de données se termine +* une requête à une base de données renvoie un résultat * etc. -Le temps d'exécution étant consommé majoritairement par l'attente d'opérations I/O on appelle ceci des opérations "I/O bound". +Le temps d'exécution étant consommé majoritairement par l'attente d'opérations I/O, on appelle ceci des opérations « I/O bound ». -Ce concept se nomme l'"asynchronisme" car l'ordinateur / le programme n'a pas besoin d'être "synchronisé" avec la tâche, attendant le moment exact où cette dernière se terminera en ne faisant rien, pour être capable de récupérer le résultat de la tâche et l'utiliser dans la suite des opérations. +Ce concept se nomme « asynchrone » car l'ordinateur / le programme n'a pas besoin d'être « synchronisé » avec la tâche, attendant le moment exact où cette dernière se terminera en ne faisant rien, pour être capable de récupérer le résultat de la tâche et l'utiliser dans la suite des opérations. -À la place, en étant "asynchrone", une fois terminée, une tâche peut légèrement attendre (quelques microsecondes) que l'ordinateur / le programme finisse ce qu'il était en train de faire, et revienne récupérer le résultat. +À la place, en étant « asynchrone », une fois terminée, une tâche peut légèrement attendre (quelques microsecondes) que l'ordinateur / le programme finisse ce qu'il était en train de faire, et revienne récupérer le résultat. -Pour parler de tâches "synchrones" (en opposition à "asynchrones"), on utilise souvent le terme "séquentiel", car l'ordinateur / le programme va effectuer toutes les étapes d'une tâche séquentiellement avant de passer à une autre tâche, même si ces étapes impliquent de l'attente. +Pour parler de tâches « synchrones » (en opposition à « asynchrones »), on utilise souvent le terme « séquentiel », car l'ordinateur / le programme va effectuer toutes les étapes d'une tâche séquentiellement avant de passer à une autre tâche, même si ces étapes impliquent de l'attente. -### Concurrence et Burgers +### Concurrence et Burgers { #concurrency-and-burgers } -L'idée de code **asynchrone** décrite ci-dessus est parfois aussi appelée **"concurrence"**. Ce qui est différent du **"parallélisme"**. +L'idée de code **asynchrone** décrite ci-dessus est parfois aussi appelée **« concurrence »**. Ce qui est différent du **« parallélisme »**. -La **concurrence** et le **parallélisme** sont tous deux liés à l'idée de "différentes choses arrivant plus ou moins au même moment". +La **concurrence** et le **parallélisme** sont tous deux liés à l'idée de « différentes choses arrivant plus ou moins au même moment ». Mais les détails entre la **concurrence** et le **parallélisme** diffèrent sur de nombreux points. Pour expliquer la différence, voici une histoire de burgers : -#### Burgers concurrents +### Burgers concurrents { #concurrent-burgers } Vous amenez votre crush 😍 dans votre fast food 🍔 favori, et faites la queue pendant que le serveur 💁 prend les commandes des personnes devant vous. @@ -122,13 +123,13 @@ Le serveur 💁 vous donne le numéro assigné à votre commande. -Pendant que vous attendez, vous allez choisir une table avec votre crush 😍, vous discutez avec votre crush 😍 pendant un long moment (les burgers étant "magnifiques" ils sont très longs à préparer ✨🍔✨). +Pendant que vous attendez, vous allez choisir une table avec votre crush 😍, vous discutez avec votre crush 😍 pendant un long moment (les burgers étant « magnifiques » ils sont très longs à préparer ✨🍔✨). Pendant que vous êtes assis à table, en attendant que les burgers 🍔 soient prêts, vous pouvez passer ce temps à admirer à quel point votre crush 😍 est géniale, mignonne et intelligente ✨😍✨. -Pendant que vous discutez avec votre crush 😍, de temps en temps vous jetez un coup d'oeil au nombre affiché au-dessus du comptoir pour savoir si c'est à votre tour d'être servis. +Pendant que vous discutez avec votre crush 😍, de temps en temps vous jetez un coup d’œil au nombre affiché au-dessus du comptoir pour savoir si c'est à votre tour d'être servis. Jusqu'au moment où c'est (enfin) votre tour. Vous allez au comptoir, récupérez vos burgers 🍔 et revenez à votre table. @@ -148,23 +149,23 @@ Illustrations proposées par @@ -212,7 +213,7 @@ Illustrations proposées par (tout ça grâce à Starlette). +Et comme on peut avoir du parallélisme et de l'asynchronicité en même temps, on obtient des performances plus hautes que la plupart des frameworks NodeJS testés et égales à celles du Go, qui est un langage compilé plus proche du C (tout ça grâce à Starlette). -### Est-ce que la concurrence est mieux que le parallélisme ? +### Est-ce que la concurrence est mieux que le parallélisme ? { #is-concurrency-better-than-parallelism } Nope ! C'est ça la morale de l'histoire. @@ -276,11 +277,11 @@ Mais dans ce cas, si pouviez amener 8 ex-serveurs/cuisiniers/devenus-nettoyeurs Dans ce scénario, chacun des nettoyeurs (vous y compris) serait un processeur, faisant sa partie du travail. -Et comme la plupart du temps d'exécution est pris par du "vrai" travail (et non de l'attente), et que le travail dans un ordinateur est fait par un CPU, ce sont des problèmes dits "CPU bound". +Et comme la plupart du temps d'exécution est pris par du « vrai » travail (et non de l'attente), et que le travail dans un ordinateur est fait par un CPU, ce sont des problèmes dits « CPU bound ». --- -Des exemples communs d'opérations "CPU bounds" sont les procédés qui requièrent des traitements mathématiques complexes. +Des exemples communs d'opérations « CPU bound » sont les procédés qui requièrent des traitements mathématiques complexes. Par exemple : @@ -289,19 +290,19 @@ Par exemple : * L'apprentissage automatique (ou **Machine Learning**) : cela nécessite de nombreuses multiplications de matrices et vecteurs. Imaginez une énorme feuille de calcul remplie de nombres que vous multiplierez entre eux tous au même moment. * L'apprentissage profond (ou **Deep Learning**) : est un sous-domaine du **Machine Learning**, donc les mêmes raisons s'appliquent. Avec la différence qu'il n'y a pas une unique feuille de calcul de nombres à multiplier, mais une énorme quantité d'entre elles, et dans de nombreux cas, on utilise un processeur spécial pour construire et / ou utiliser ces modèles. -### Concurrence + Parallélisme : Web + Machine Learning +### Concurrence + Parallélisme : Web + Machine Learning { #concurrency-parallelism-web-machine-learning } Avec **FastAPI** vous pouvez bénéficier de la concurrence qui est très courante en développement web (c'est l'attrait principal de NodeJS). -Mais vous pouvez aussi profiter du parallélisme et multiprocessing afin de gérer des charges **CPU bound** qui sont récurrentes dans les systèmes de *Machine Learning*. +Mais vous pouvez aussi profiter du parallélisme et du multiprocessing (plusieurs processus s'exécutant en parallèle) afin de gérer des charges **CPU bound** qui sont récurrentes dans les systèmes de *Machine Learning*. Ça, ajouté au fait que Python soit le langage le plus populaire pour la **Data Science**, le **Machine Learning** et surtout le **Deep Learning**, font de **FastAPI** un très bon choix pour les APIs et applications de **Data Science** / **Machine Learning**. Pour comprendre comment mettre en place ce parallélisme en production, allez lire la section [Déploiement](deployment/index.md){.internal-link target=_blank}. -## `async` et `await` +## `async` et `await` { #async-and-await } -Les versions modernes de Python ont une manière très intuitive de définir le code asynchrone, tout en gardant une apparence de code "séquentiel" classique en laissant Python faire l'attente pour vous au bon moment. +Les versions modernes de Python ont une manière très intuitive de définir le code asynchrone, tout en gardant une apparence de code « séquentiel » classique en laissant Python faire l'attente pour vous au bon moment. Pour une opération qui nécessite de l'attente avant de donner un résultat et qui supporte ces nouvelles fonctionnalités Python, vous pouvez l'utiliser comme tel : @@ -319,12 +320,12 @@ async def get_burgers(number: int): return burgers ``` -...et non `def` : +... et non `def` : ```Python hl_lines="2" # Ceci n'est pas asynchrone def get_sequential_burgers(number: int): - # Opérations asynchrones pour créer les burgers + # Opérations séquentielles pour créer les burgers return burgers ``` @@ -339,7 +340,7 @@ burgers = get_burgers(2) --- -Donc, si vous utilisez une bibliothèque qui nécessite que ses fonctions soient appelées avec `await`, vous devez définir la *fonction de chemin* en utilisant `async def` comme dans : +Donc, si vous utilisez une bibliothèque qui nécessite que ses fonctions soient appelées avec `await`, vous devez définir la *fonction de chemin d'accès* en utilisant `async def` comme dans : ```Python hl_lines="2-3" @app.get('/burgers') @@ -348,52 +349,61 @@ async def read_burgers(): return burgers ``` -### Plus de détails techniques +### Plus de détails techniques { #more-technical-details } Vous avez donc compris que `await` peut seulement être utilisé dans des fonctions définies avec `async def`. Mais en même temps, les fonctions définies avec `async def` doivent être appelées avec `await` et donc dans des fonctions définies elles aussi avec `async def`. -Vous avez donc remarqué ce paradoxe d'oeuf et de la poule, comment appelle-t-on la première fonction `async` ? +Vous avez donc remarqué ce paradoxe d'œuf et de la poule, comment appelle-t-on la première fonction `async` ? -Si vous utilisez **FastAPI**, pas besoin de vous en inquiéter, car cette "première" fonction sera votre *fonction de chemin* ; et **FastAPI** saura comment arriver au résultat attendu. +Si vous utilisez **FastAPI**, pas besoin de vous en inquiéter, car cette « première » fonction sera votre *fonction de chemin d'accès* ; et **FastAPI** saura comment arriver au résultat attendu. -Mais si vous utilisez `async` / `await` sans **FastAPI**, allez jetez un coup d'oeil à la documentation officielle de Python. +Mais si vous souhaitez utiliser `async` / `await` sans FastAPI, vous pouvez également le faire. -### Autres formes de code asynchrone +### Écrire votre propre code async { #write-your-own-async-code } + +Starlette (et **FastAPI**) s’appuie sur AnyIO, ce qui le rend compatible à la fois avec la bibliothèque standard asyncio de Python et avec Trio. + +En particulier, vous pouvez utiliser directement AnyIO pour vos cas d’usage de concurrence avancés qui nécessitent des schémas plus élaborés dans votre propre code. + +Et même si vous n’utilisiez pas FastAPI, vous pourriez aussi écrire vos propres applications async avec AnyIO pour une grande compatibilité et pour bénéficier de ses avantages (par ex. la « structured concurrency »). + +J’ai créé une autre bibliothèque au-dessus d’AnyIO, comme une fine surcouche, pour améliorer un peu les annotations de type et obtenir une meilleure **autocomplétion**, des **erreurs en ligne**, etc. Elle propose également une introduction et un tutoriel accessibles pour vous aider à **comprendre** et écrire **votre propre code async** : Asyncer. Elle sera particulièrement utile si vous devez **combiner du code async avec du code classique** (bloquant/synchrone). + +### Autres formes de code asynchrone { #other-forms-of-asynchronous-code } L'utilisation d'`async` et `await` est relativement nouvelle dans ce langage. Mais cela rend la programmation asynchrone bien plus simple. -Cette même syntaxe (ou presque) était aussi incluse dans les versions modernes de Javascript (dans les versions navigateur et NodeJS). +Cette même syntaxe (ou presque) a aussi été incluse récemment dans les versions modernes de JavaScript (dans les navigateurs et NodeJS). Mais avant ça, gérer du code asynchrone était bien plus complexe et difficile. -Dans les versions précédentes de Python, vous auriez utilisé des *threads* ou Gevent. Mais le code aurait été bien plus difficile à comprendre, débugger, et concevoir. +Dans les versions précédentes de Python, vous auriez utilisé des threads ou Gevent. Mais le code aurait été bien plus difficile à comprendre, débugger, et concevoir. -Dans les versions précédentes de Javascript NodeJS / Navigateur, vous auriez utilisé des "callbacks". Menant potentiellement à ce que l'on appelle le "callback hell". +Dans les versions précédentes de JavaScript côté navigateur / NodeJS, vous auriez utilisé des « callbacks ». Menant potentiellement à ce que l'on appelle le « callback hell ». +## Coroutines { #coroutines } -## Coroutines +« Coroutine » est juste un terme élaboré pour désigner ce qui est retourné par une fonction définie avec `async def`. Python sait que c'est comme une fonction classique qui va démarrer à un moment et terminer à un autre, mais qu'elle peut aussi être mise en pause ⏸, du moment qu'il y a un `await` dans son contenu. -**Coroutine** est juste un terme élaboré pour désigner ce qui est retourné par une fonction définie avec `async def`. Python sait que c'est comme une fonction classique qui va démarrer à un moment et terminer à un autre, mais qu'elle peut aussi être mise en pause ⏸, du moment qu'il y a un `await` dans son contenu. +Mais toutes ces fonctionnalités d'utilisation de code asynchrone avec `async` et `await` sont souvent résumées comme l'utilisation des « coroutines ». On peut comparer cela à la principale fonctionnalité clé de Go, les « Goroutines ». -Mais toutes ces fonctionnalités d'utilisation de code asynchrone avec `async` et `await` sont souvent résumées comme l'utilisation des *coroutines*. On peut comparer cela à la principale fonctionnalité clé de Go, les "Goroutines". - -## Conclusion +## Conclusion { #conclusion } Reprenons la phrase du début de la page : -> Les versions modernes de Python supportent le **code asynchrone** grâce aux **"coroutines"** avec les syntaxes **`async` et `await`**. +> Les versions modernes de Python supportent le **code asynchrone** grâce aux **« coroutines »** avec les syntaxes **`async` et `await`**. Ceci devrait être plus compréhensible désormais. ✨ -Tout ceci est donc ce qui donne sa force à **FastAPI** (à travers Starlette) et lui permet d'avoir des performances aussi impressionnantes. +Tout ceci est donc ce qui donne sa force à FastAPI (à travers Starlette) et lui permet d'avoir une performance aussi impressionnante. -## Détails très techniques +## Détails très techniques { #very-technical-details } -/// warning | Attention ! +/// warning | Alertes Vous pouvez probablement ignorer cela. @@ -403,32 +413,32 @@ Si vous avez de bonnes connaissances techniques (coroutines, threads, code bloqu /// -### Fonctions de chemin +### Fonctions de chemin d'accès { #path-operation-functions } -Quand vous déclarez une *fonction de chemin* avec un `def` normal et non `async def`, elle est exécutée dans un groupe de threads (threadpool) externe qui est ensuite attendu, plutôt que d'être appelée directement (car cela bloquerait le serveur). +Quand vous déclarez une *fonction de chemin d'accès* avec un `def` normal et non `async def`, elle est exécutée dans un groupe de threads (threadpool) externe qui est ensuite attendu, plutôt que d'être appelée directement (car cela bloquerait le serveur). -Si vous venez d'un autre framework asynchrone qui ne fonctionne pas comme de la façon décrite ci-dessus et que vous êtes habitués à définir des *fonctions de chemin* basiques avec un simple `def` pour un faible gain de performance (environ 100 nanosecondes), veuillez noter que dans **FastAPI**, l'effet serait plutôt contraire. Dans ces cas-là, il vaut mieux utiliser `async def` à moins que votre *fonction de chemin* utilise du code qui effectue des opérations I/O bloquantes. +Si vous venez d'un autre framework asynchrone qui ne fonctionne pas comme de la façon décrite ci-dessus et que vous êtes habitué à définir des *fonctions de chemin d'accès* basiques et purement calculatoires avec un simple `def` pour un faible gain de performance (environ 100 nanosecondes), veuillez noter que dans **FastAPI**, l'effet serait plutôt contraire. Dans ces cas-là, il vaut mieux utiliser `async def` à moins que votre *fonction de chemin d'accès* utilise du code qui effectue des opérations I/O bloquantes. Au final, dans les deux situations, il est fort probable que **FastAPI** soit tout de même [plus rapide](index.md#performance){.internal-link target=_blank} que (ou au moins de vitesse égale à) votre framework précédent. -### Dépendances +### Dépendances { #dependencies } -La même chose s'applique aux dépendances. Si une dépendance est définie avec `def` plutôt que `async def`, elle est exécutée dans la threadpool externe. +La même chose s'applique aux [dépendances](tutorial/dependencies/index.md){.internal-link target=_blank}. Si une dépendance est définie avec `def` plutôt que `async def`, elle est exécutée dans la threadpool externe. -### Sous-dépendances +### Sous-dépendances { #sub-dependencies } -Vous pouvez avoir de multiples dépendances et sous-dépendances dépendant les unes des autres (en tant que paramètres de la définition de la *fonction de chemin*), certaines créées avec `async def` et d'autres avec `def`. Cela fonctionnerait aussi, et celles définies avec un simple `def` seraient exécutées sur un thread externe (venant de la threadpool) plutôt que d'être "attendues". +Vous pouvez avoir de multiples dépendances et [sous-dépendances](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} dépendant les unes des autres (en tant que paramètres de la définition de la *fonction de chemin d'accès*), certaines créées avec `async def` et d'autres avec `def`. Cela fonctionnerait aussi, et celles définies avec un simple `def` seraient exécutées sur un thread externe (venant de la threadpool) plutôt que d'être « attendues ». -### Autres fonctions utilitaires +### Autres fonctions utilitaires { #other-utility-functions } -Toute autre fonction utilitaire que vous appelez directement peut être créée avec un classique `def` ou avec `async def` et **FastAPI** n'aura pas d'impact sur la façon dont vous l'appelez. +Toute autre fonction utilitaire que vous appelez directement peut être créée avec un classique `def` ou avec `async def` et FastAPI n'aura pas d'impact sur la façon dont vous l'appelez. -Contrairement aux fonctions que **FastAPI** appelle pour vous : les *fonctions de chemin* et dépendances. +Contrairement aux fonctions que FastAPI appelle pour vous : les *fonctions de chemin d'accès* et dépendances. -Si votre fonction utilitaire est une fonction classique définie avec `def`, elle sera appelée directement (telle qu'écrite dans votre code), pas dans une threadpool, si la fonction est définie avec `async def` alors vous devrez attendre (avec `await`) que cette fonction se termine avant de passer à la suite du code. +Si votre fonction utilitaire est une fonction classique définie avec `def`, elle sera appelée directement (telle qu'écrite dans votre code), pas dans une threadpool ; si la fonction est définie avec `async def` alors vous devrez attendre (avec `await`) que cette fonction se termine avant de passer à la suite du code. --- Encore une fois, ce sont des détails très techniques qui peuvent être utiles si vous venez ici les chercher. -Sinon, les instructions de la section Vous êtes pressés ? ci-dessus sont largement suffisantes. +Sinon, les instructions de la section Vous êtes pressés ? ci-dessus sont largement suffisantes. diff --git a/docs/fr/docs/deployment/cloud.md b/docs/fr/docs/deployment/cloud.md new file mode 100644 index 000000000..798a72a74 --- /dev/null +++ b/docs/fr/docs/deployment/cloud.md @@ -0,0 +1,24 @@ +# Déployer FastAPI sur des fournisseurs cloud { #deploy-fastapi-on-cloud-providers } + +Vous pouvez utiliser pratiquement n'importe quel fournisseur cloud pour déployer votre application FastAPI. + +Dans la plupart des cas, les principaux fournisseurs cloud proposent des guides pour déployer FastAPI avec leurs services. + +## FastAPI Cloud { #fastapi-cloud } + +**FastAPI Cloud** est créée par le même auteur et l'équipe à l'origine de **FastAPI**. + +Elle simplifie le processus de **création**, de **déploiement** et **d'accès** à une API avec un effort minimal. + +Elle apporte la même **expérience développeur** que celle de la création d'applications avec FastAPI au **déploiement** de celles-ci dans le cloud. 🎉 + +FastAPI Cloud est le sponsor principal et le financeur des projets open source *FastAPI and friends*. ✨ + +## Fournisseurs cloud - Sponsors { #cloud-providers-sponsors } + +D'autres fournisseurs cloud ✨ [**parrainent FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ également. 🙇 + +Vous pouvez également envisager ces fournisseurs pour suivre leurs guides et essayer leurs services : + +* Render +* Railway diff --git a/docs/fr/docs/deployment/concepts.md b/docs/fr/docs/deployment/concepts.md new file mode 100644 index 000000000..59b8ddd1b --- /dev/null +++ b/docs/fr/docs/deployment/concepts.md @@ -0,0 +1,321 @@ +# Concepts de déploiement { #deployments-concepts } + +Lorsque vous déployez une application **FastAPI**, ou en fait n'importe quel type de web API, il existe plusieurs concepts qui vous importent probablement, et en les utilisant vous pouvez trouver la manière la **plus appropriée** de **déployer votre application**. + +Parmi les concepts importants, on trouve : + +* Sécurité - HTTPS +* Exécuter au démarrage +* Redémarrages +* Réplication (le nombre de processus en cours d'exécution) +* Mémoire +* Étapes préalables avant de démarrer + +Nous allons voir comment ils affectent les **déploiements**. + +Au final, l'objectif ultime est de pouvoir **servir vos clients d'API** de manière **sécurisée**, d'**éviter les interruptions**, et d'utiliser les **ressources de calcul** (par exemple des serveurs/VM distants) aussi efficacement que possible. 🚀 + +Je vais vous en dire un peu plus ici sur ces **concepts**, ce qui devrait vous donner l'**intuition** nécessaire pour décider comment déployer votre API dans des environnements très différents, voire même dans des environnements **futurs** qui n'existent pas encore. + +En tenant compte de ces concepts, vous serez en mesure **d'évaluer et de concevoir** la meilleure façon de déployer **vos propres API**. + +Dans les chapitres suivants, je vous donnerai des **recettes concrètes** pour déployer des applications FastAPI. + +Mais pour l'instant, voyons ces **idées conceptuelles** importantes. Ces concepts s'appliquent aussi à tout autre type de web API. 💡 + +## Sécurité - HTTPS { #security-https } + +Dans le [chapitre précédent à propos de HTTPS](https.md){.internal-link target=_blank}, nous avons vu comment HTTPS fournit le chiffrement pour votre API. + +Nous avons également vu que HTTPS est normalement fourni par un composant **externe** à votre serveur d'application, un **TLS Termination Proxy**. + +Et il doit y avoir quelque chose chargé de **renouveler les certificats HTTPS** ; cela peut être le même composant ou quelque chose de différent. + +### Exemples d’outils pour HTTPS { #example-tools-for-https } + +Parmi les outils que vous pourriez utiliser comme TLS Termination Proxy : + +* Traefik + * Gère automatiquement le renouvellement des certificats ✨ +* Caddy + * Gère automatiquement le renouvellement des certificats ✨ +* Nginx + * Avec un composant externe comme Certbot pour le renouvellement des certificats +* HAProxy + * Avec un composant externe comme Certbot pour le renouvellement des certificats +* Kubernetes avec un Ingress Controller comme Nginx + * Avec un composant externe comme cert-manager pour le renouvellement des certificats +* Pris en charge en interne par un fournisseur cloud dans le cadre de ses services (lisez ci-dessous 👇) + +Une autre option consiste à utiliser un **service cloud** qui fait davantage de travail, y compris la mise en place de HTTPS. Il peut avoir certaines restrictions ou vous facturer davantage, etc. Mais dans ce cas, vous n'auriez pas à configurer vous‑même un TLS Termination Proxy. + +Je vous montrerai des exemples concrets dans les prochains chapitres. + +--- + +Les concepts suivants à considérer concernent tous le programme qui exécute votre API réelle (par ex. Uvicorn). + +## Programme et processus { #program-and-process } + +Nous allons beaucoup parler du « **processus** » en cours d'exécution, il est donc utile d'être clair sur ce que cela signifie, et sur la différence avec le mot « **programme** ». + +### Qu'est-ce qu'un programme { #what-is-a-program } + +Le mot **programme** est couramment utilisé pour décrire plusieurs choses : + +* Le **code** que vous écrivez, les **fichiers Python**. +* Le **fichier** qui peut être **exécuté** par le système d'exploitation, par exemple : `python`, `python.exe` ou `uvicorn`. +* Un programme particulier lorsqu'il **s'exécute** sur le système d'exploitation, utilisant le CPU et stockant des choses en mémoire. On appelle aussi cela un **processus**. + +### Qu'est-ce qu'un processus { #what-is-a-process } + +Le mot **processus** est normalement utilisé de manière plus spécifique, en ne se référant qu'à l'élément qui s'exécute dans le système d'exploitation (comme dans le dernier point ci‑dessus) : + +* Un programme particulier lorsqu'il **s'exécute** sur le système d'exploitation. + * Cela ne se réfère ni au fichier, ni au code ; cela se réfère **spécifiquement** à l'élément qui est **exécuté** et géré par le système d'exploitation. +* N'importe quel programme, n'importe quel code, **ne peut faire des choses** que lorsqu'il est **exécuté**. Donc, lorsqu'il y a un **processus en cours**. +* Le processus peut être **arrêté** (ou « tué ») par vous ou par le système d'exploitation. À ce moment‑là, il cesse de s'exécuter/d'être exécuté, et il **ne peut plus rien faire**. +* Chaque application que vous avez en cours d'exécution sur votre ordinateur a un processus derrière elle, chaque programme lancé, chaque fenêtre, etc. Et il y a normalement de nombreux processus exécutés **en même temps** tant qu'un ordinateur est allumé. +* Il peut y avoir **plusieurs processus** du **même programme** exécutés simultanément. + +Si vous ouvrez le « gestionnaire des tâches » ou le « moniteur système » (ou des outils similaires) de votre système d'exploitation, vous verrez nombre de ces processus en cours d'exécution. + +Et, par exemple, vous verrez probablement qu'il y a plusieurs processus exécutant le même navigateur (Firefox, Chrome, Edge, etc.). Ils exécutent normalement un processus par onglet, plus quelques processus supplémentaires. + + + +--- + +Maintenant que nous connaissons la différence entre les termes **processus** et **programme**, continuons à parler des déploiements. + +## Exécuter au démarrage { #running-on-startup } + +Dans la plupart des cas, lorsque vous créez une web API, vous voulez qu'elle **tourne en permanence**, sans interruption, afin que vos clients puissent toujours y accéder. Bien sûr, sauf si vous avez une raison spécifique de ne vouloir l'exécuter que dans certaines situations, mais la plupart du temps vous la voulez constamment en cours et **disponible**. + +### Sur un serveur distant { #in-a-remote-server } + +Lorsque vous configurez un serveur distant (un serveur cloud, une machine virtuelle, etc.), la chose la plus simple à faire est d'utiliser `fastapi run` (qui utilise Uvicorn) ou quelque chose de similaire, manuellement, de la même manière que lorsque vous développez en local. + +Et cela fonctionnera et sera utile **pendant le développement**. + +Mais si votre connexion au serveur est coupée, le **processus en cours** va probablement s'arrêter. + +Et si le serveur est redémarré (par exemple après des mises à jour, ou des migrations chez le fournisseur cloud) vous **ne le remarquerez probablement pas**. Et à cause de cela, vous ne saurez même pas que vous devez redémarrer le processus manuellement. Ainsi, votre API restera tout simplement à l'arrêt. 😱 + +### Lancer automatiquement au démarrage { #run-automatically-on-startup } + +En général, vous voudrez probablement que le programme serveur (par ex. Uvicorn) soit démarré automatiquement au démarrage du serveur, et sans aucune **intervention humaine**, afin d'avoir en permanence un processus exécutant votre API (par ex. Uvicorn exécutant votre app FastAPI). + +### Programme séparé { #separate-program } + +Pour y parvenir, vous aurez normalement un **programme séparé** qui s'assure que votre application est lancée au démarrage. Et dans de nombreux cas, il s'assurera également que d'autres composants ou applications sont également lancés, par exemple une base de données. + +### Exemples d’outils pour lancer au démarrage { #example-tools-to-run-at-startup } + +Voici quelques exemples d'outils capables de faire ce travail : + +* Docker +* Kubernetes +* Docker Compose +* Docker en mode Swarm +* Systemd +* Supervisor +* Pris en charge en interne par un fournisseur cloud dans le cadre de ses services +* Autres ... + +Je vous donnerai des exemples plus concrets dans les prochains chapitres. + +## Redémarrages { #restarts } + +De la même manière que vous voulez vous assurer que votre application est lancée au démarrage, vous voulez probablement aussi vous assurer qu'elle est **redémarrée** après des échecs. + +### Nous faisons des erreurs { #we-make-mistakes } + +Nous, humains, faisons des **erreurs**, tout le temps. Les logiciels ont presque *toujours* des **bugs** cachés à différents endroits. 🐛 + +Et nous, développeurs, continuons à améliorer le code au fur et à mesure que nous trouvons ces bugs et que nous implémentons de nouvelles fonctionnalités (en ajoutant éventuellement de nouveaux bugs aussi 😅). + +### Petites erreurs gérées automatiquement { #small-errors-automatically-handled } + +Lors de la création de web API avec FastAPI, s'il y a une erreur dans notre code, FastAPI la contiendra normalement à la seule requête qui a déclenché l'erreur. 🛡 + +Le client recevra un **500 Internal Server Error** pour cette requête, mais l'application continuera de fonctionner pour les requêtes suivantes au lieu de simplement s'effondrer complètement. + +### Erreurs plus importantes - plantages { #bigger-errors-crashes } + +Néanmoins, il peut y avoir des cas où nous écrivons du code qui **fait planter l'application entière**, faisant planter Uvicorn et Python. 💥 + +Et malgré cela, vous ne voudrez probablement pas que l'application reste à l'arrêt parce qu'il y a eu une erreur à un endroit ; vous voudrez probablement qu'elle **continue de tourner**, au moins pour les *chemins d'accès* qui ne sont pas cassés. + +### Redémarrer après un plantage { #restart-after-crash } + +Mais dans ces cas avec de très mauvaises erreurs qui font planter le **processus** en cours, vous voudrez un composant externe chargé de **redémarrer** le processus, au moins quelques fois ... + +/// tip | Astuce + +... Bien que si l'application entière **plante immédiatement**, il n'est probablement pas logique de continuer à la redémarrer indéfiniment. Mais dans ces cas, vous le remarquerez probablement pendant le développement, ou au moins juste après le déploiement. + +Concentrons‑nous donc sur les cas principaux, où elle pourrait planter entièrement dans certaines situations particulières **à l'avenir**, et où il est toujours logique de la redémarrer. + +/// + +Vous voudrez probablement que l'élément chargé de redémarrer votre application soit un **composant externe**, car à ce stade, l'application elle‑même avec Uvicorn et Python a déjà planté, donc il n'y a rien dans le même code de la même app qui pourrait y faire quoi que ce soit. + +### Exemples d’outils pour redémarrer automatiquement { #example-tools-to-restart-automatically } + +Dans la plupart des cas, le même outil qui est utilisé pour **lancer le programme au démarrage** est également utilisé pour gérer les **redémarrages** automatiques. + +Par exemple, cela peut être géré par : + +* Docker +* Kubernetes +* Docker Compose +* Docker en mode Swarm +* Systemd +* Supervisor +* Pris en charge en interne par un fournisseur cloud dans le cadre de ses services +* Autres ... + +## Réplication - Processus et mémoire { #replication-processes-and-memory } + +Avec une application FastAPI, en utilisant un programme serveur comme la commande `fastapi` qui exécute Uvicorn, l'exécuter une fois dans **un processus** peut servir plusieurs clients simultanément. + +Mais dans de nombreux cas, vous voudrez exécuter plusieurs processus de travail en même temps. + +### Multiples processus - Workers { #multiple-processes-workers } + +Si vous avez plus de clients que ce qu'un seul processus peut gérer (par exemple si la machine virtuelle n'est pas très grande) et que vous avez **plusieurs cœurs** dans le CPU du serveur, alors vous pouvez avoir **plusieurs processus** exécutant la même application simultanément, et distribuer toutes les requêtes entre eux. + +Quand vous exécutez **plusieurs processus** du même programme d'API, on les appelle couramment des **workers**. + +### Processus workers et ports { #worker-processes-and-ports } + +Rappelez‑vous, d'après les documents [À propos de HTTPS](https.md){.internal-link target=_blank}, qu'un seul processus peut écouter une combinaison de port et d'adresse IP sur un serveur ? + +C'est toujours vrai. + +Donc, pour pouvoir avoir **plusieurs processus** en même temps, il doit y avoir un **seul processus à l'écoute sur un port** qui transmet ensuite la communication à chaque processus worker d'une manière ou d'une autre. + +### Mémoire par processus { #memory-per-process } + +Maintenant, lorsque le programme charge des choses en mémoire, par exemple, un modèle de machine learning dans une variable, ou le contenu d'un gros fichier dans une variable, tout cela **consomme une partie de la mémoire (RAM)** du serveur. + +Et plusieurs processus **ne partagent normalement pas de mémoire**. Cela signifie que chaque processus en cours a ses propres éléments, variables et mémoire. Et si vous consommez une grande quantité de mémoire dans votre code, **chaque processus** consommera une quantité équivalente de mémoire. + +### Mémoire du serveur { #server-memory } + +Par exemple, si votre code charge un modèle de Machine Learning de **1 Go**, lorsque vous exécutez un processus avec votre API, il consommera au moins 1 Go de RAM. Et si vous démarrez **4 processus** (4 workers), chacun consommera 1 Go de RAM. Donc au total, votre API consommera **4 Go de RAM**. + +Et si votre serveur distant ou votre machine virtuelle n'a que 3 Go de RAM, essayer de charger plus de 4 Go de RAM posera problème. 🚨 + +### Multiples processus - Un exemple { #multiple-processes-an-example } + +Dans cet exemple, il y a un **processus gestionnaire** qui démarre et contrôle deux **processus workers**. + +Ce processus gestionnaire serait probablement celui qui écoute sur le **port** de l'IP. Et il transmettrait toute la communication aux processus workers. + +Ces processus workers seraient ceux qui exécutent votre application, ils effectueraient les calculs principaux pour recevoir une **requête** et renvoyer une **réponse**, et ils chargeraient tout ce que vous mettez dans des variables en RAM. + + + +Et bien sûr, la même machine aurait probablement **d'autres processus** en cours d'exécution également, en plus de votre application. + +Un détail intéressant est que le pourcentage de **CPU utilisé** par chaque processus peut **varier** fortement dans le temps, mais la **mémoire (RAM)** reste normalement plus ou moins **stable**. + +Si vous avez une API qui effectue une quantité comparable de calculs à chaque fois et que vous avez beaucoup de clients, alors l'**utilisation du CPU** sera probablement *également stable* (au lieu de monter et descendre rapidement en permanence). + +### Exemples d’outils et de stratégies de réplication { #examples-of-replication-tools-and-strategies } + +Il peut y avoir plusieurs approches pour y parvenir, et je vous en dirai plus sur des stratégies spécifiques dans les prochains chapitres, par exemple en parlant de Docker et des conteneurs. + +La principale contrainte à considérer est qu'il doit y avoir un **seul** composant gérant le **port** sur l'**IP publique**. Et il doit ensuite avoir un moyen de **transmettre** la communication aux **processus/workers** répliqués. + +Voici quelques combinaisons et stratégies possibles : + +* **Uvicorn** avec `--workers` + * Un **gestionnaire de processus** Uvicorn écouterait sur l'**IP** et le **port**, et il démarrerait **plusieurs processus workers Uvicorn**. +* **Kubernetes** et autres systèmes **de conteneurs** distribués + * Quelque chose dans la couche **Kubernetes** écouterait sur l'**IP** et le **port**. La réplication se ferait en ayant **plusieurs conteneurs**, chacun avec **un processus Uvicorn** en cours. +* **Services cloud** qui s'en chargent pour vous + * Le service cloud **gérera probablement la réplication pour vous**. Il vous permettra éventuellement de définir **un processus à exécuter**, ou une **image de conteneur** à utiliser ; dans tous les cas, ce sera très probablement **un seul processus Uvicorn**, et le service cloud sera chargé de le répliquer. + +/// tip | Astuce + +Ne vous inquiétez pas si certains de ces éléments concernant les **conteneurs**, Docker ou Kubernetes ne sont pas encore très clairs. + +Je vous en dirai plus sur les images de conteneurs, Docker, Kubernetes, etc. dans un chapitre à venir : [FastAPI dans des conteneurs - Docker](docker.md){.internal-link target=_blank}. + +/// + +## Étapes préalables avant de démarrer { #previous-steps-before-starting } + +Il existe de nombreux cas où vous souhaitez effectuer certaines étapes **avant de démarrer** votre application. + +Par exemple, vous pourriez vouloir exécuter des **migrations de base de données**. + +Mais dans la plupart des cas, vous voudrez effectuer ces étapes **une seule fois**. + +Vous voudrez donc avoir un **processus unique** pour effectuer ces **étapes préalables**, avant de démarrer l'application. + +Et vous devez vous assurer que c'est un processus unique qui exécute ces étapes préalables *même si*, ensuite, vous démarrez **plusieurs processus** (plusieurs workers) pour l'application elle‑même. Si ces étapes étaient exécutées par **plusieurs processus**, ils **dupliqueraient** le travail en l'exécutant **en parallèle**, et si les étapes étaient délicates comme une migration de base de données, elles pourraient entrer en conflit les unes avec les autres. + +Bien sûr, il y a des cas où il n'y a aucun problème à exécuter les étapes préalables plusieurs fois ; dans ce cas, c'est beaucoup plus simple à gérer. + +/// tip | Astuce + +Gardez aussi à l'esprit que selon votre configuration, dans certains cas vous **n'aurez peut‑être même pas besoin d'étapes préalables** avant de démarrer votre application. + +Dans ce cas, vous n'auriez pas à vous soucier de tout cela. 🤷 + +/// + +### Exemples de stratégies pour les étapes préalables { #examples-of-previous-steps-strategies } + +Cela **dépendra fortement** de la manière dont vous **déployez votre système**, et sera probablement lié à votre manière de démarrer les programmes, de gérer les redémarrages, etc. + +Voici quelques idées possibles : + +* Un « Init Container » dans Kubernetes qui s'exécute avant votre conteneur d'application +* Un script bash qui exécute les étapes préalables puis démarre votre application + * Vous aurez toujours besoin d'un moyen de démarrer/redémarrer *ce* script bash, de détecter les erreurs, etc. + +/// tip | Astuce + +Je vous donnerai des exemples plus concrets pour faire cela avec des conteneurs dans un chapitre à venir : [FastAPI dans des conteneurs - Docker](docker.md){.internal-link target=_blank}. + +/// + +## Utilisation des ressources { #resource-utilization } + +Votre ou vos serveurs constituent une **ressource** que vos programmes peuvent consommer ou **utiliser** : le temps de calcul des CPU et la mémoire RAM disponible. + +Quelle quantité des ressources système voulez‑vous consommer/utiliser ? Il peut être facile de penser « pas beaucoup », mais en réalité, vous voudrez probablement consommer **le plus possible sans planter**. + +Si vous payez pour 3 serveurs mais que vous n'utilisez qu'un petit peu de leur RAM et CPU, vous **gaspillez probablement de l'argent** 💸, et **gaspillez probablement l'électricité des serveurs** 🌎, etc. + +Dans ce cas, il pourrait être préférable de n'avoir que 2 serveurs et d'utiliser un pourcentage plus élevé de leurs ressources (CPU, mémoire, disque, bande passante réseau, etc.). + +À l'inverse, si vous avez 2 serveurs et que vous utilisez **100 % de leur CPU et de leur RAM**, à un moment donné un processus demandera plus de mémoire, et le serveur devra utiliser le disque comme « mémoire » (ce qui peut être des milliers de fois plus lent), voire **planter**. Ou un processus pourrait avoir besoin de faire un calcul et devrait attendre que le CPU soit à nouveau libre. + +Dans ce cas, il serait préférable d'obtenir **un serveur supplémentaire** et d'y exécuter certains processus afin qu'ils aient tous **suffisamment de RAM et de temps CPU**. + +Il est également possible que, pour une raison quelconque, vous ayez un **pic** d'utilisation de votre API. Peut‑être qu'elle devient virale, ou peut‑être que d'autres services ou bots commencent à l'utiliser. Et vous voudrez peut‑être disposer de ressources supplémentaires pour être en sécurité dans ces cas. + +Vous pouvez définir un **chiffre arbitraire** comme cible, par exemple **entre 50 % et 90 %** d'utilisation des ressources. L'idée est que ce sont probablement les principaux éléments que vous voudrez mesurer et utiliser pour ajuster vos déploiements. + +Vous pouvez utiliser des outils simples comme `htop` pour voir le CPU et la RAM utilisés sur votre serveur ou la quantité utilisée par chaque processus. Ou vous pouvez utiliser des outils de supervision plus complexes, éventuellement distribués sur plusieurs serveurs, etc. + +## Récapitulatif { #recap } + +Vous venez de lire ici certains des principaux concepts que vous devrez probablement garder à l'esprit lorsque vous décidez comment déployer votre application : + +* Sécurité - HTTPS +* Exécuter au démarrage +* Redémarrages +* Réplication (le nombre de processus en cours d'exécution) +* Mémoire +* Étapes préalables avant de démarrer + +Comprendre ces idées et comment les appliquer devrait vous donner l'intuition nécessaire pour prendre toutes les décisions lors de la configuration et de l'ajustement de vos déploiements. 🤓 + +Dans les sections suivantes, je vous donnerai des exemples plus concrets de stratégies possibles à suivre. 🚀 diff --git a/docs/fr/docs/deployment/docker.md b/docs/fr/docs/deployment/docker.md index ec30f9607..2d86d4a40 100644 --- a/docs/fr/docs/deployment/docker.md +++ b/docs/fr/docs/deployment/docker.md @@ -14,7 +14,7 @@ Vous êtes pressé et vous connaissez déjà tout ça ? Allez directement au [`D Aperçu du Dockerfile 👀 ```Dockerfile -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -166,7 +166,7 @@ Maintenant, dans le même répertoire de projet, créez un fichier `Dockerfile` ```{ .dockerfile .annotate } # (1)! -FROM python:3.9 +FROM python:3.14 # (2)! WORKDIR /code @@ -390,7 +390,7 @@ Si votre FastAPI est un seul fichier, par exemple `main.py` sans répertoire `./ Vous n'auriez alors qu'à changer les chemins correspondants pour copier le fichier dans le `Dockerfile` : ```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -454,7 +454,7 @@ Sans utiliser de conteneurs, faire en sorte que les applications s'exécutent au ## Réplication - Nombre de processus { #replication-number-of-processes } -Si vous avez un cluster de machines avec **Kubernetes**, Docker Swarm Mode, Nomad, ou un autre système complexe similaire pour gérer des conteneurs distribués sur plusieurs machines, alors vous voudrez probablement **gérer la réplication** au **niveau du cluster** plutôt que d'utiliser un **gestionnaire de processus** (comme Uvicorn avec workers) dans chaque conteneur. +Si vous avez un cluster de machines avec **Kubernetes**, Docker Swarm Mode, Nomad, ou un autre système complexe similaire pour gérer des conteneurs distribués sur plusieurs machines, alors vous voudrez probablement **gérer la réplication** au **niveau du cluster** plutôt que d'utiliser un **gestionnaire de processus** (comme Uvicorn avec workers) dans chaque conteneur. L'un de ces systèmes de gestion de conteneurs distribués comme Kubernetes dispose normalement d'une manière intégrée de gérer la **réplication des conteneurs** tout en supportant l'**équilibrage de charge** des requêtes entrantes. Le tout au **niveau du cluster**. @@ -499,7 +499,7 @@ Bien sûr, il existe des **cas particuliers** où vous pourriez vouloir avoir ** Dans ces cas, vous pouvez utiliser l'option de ligne de commande `--workers` pour définir le nombre de workers que vous souhaitez exécuter : ```{ .dockerfile .annotate } -FROM python:3.9 +FROM python:3.14 WORKDIR /code diff --git a/docs/fr/docs/deployment/fastapicloud.md b/docs/fr/docs/deployment/fastapicloud.md new file mode 100644 index 000000000..72f275cf6 --- /dev/null +++ b/docs/fr/docs/deployment/fastapicloud.md @@ -0,0 +1,65 @@ +# FastAPI Cloud { #fastapi-cloud } + +Vous pouvez déployer votre application FastAPI sur FastAPI Cloud avec une **seule commande**, allez vous inscrire sur la liste d’attente si ce n’est pas déjà fait. 🚀 + +## Se connecter { #login } + +Vous devez vous assurer que vous avez déjà un compte **FastAPI Cloud** (nous vous avons invité depuis la liste d’attente 😉). + +Connectez-vous ensuite : + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +## Déployer { #deploy } + +Déployez maintenant votre application, avec une **seule commande** : + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +C’est tout ! Vous pouvez maintenant accéder à votre application à cette URL. ✨ + +## À propos de FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** est développé par le même auteur et la même équipe à l’origine de **FastAPI**. + +Cela simplifie le processus de **création**, de **déploiement** et **d’accès** à une API avec un effort minimal. + +Cela apporte la même **expérience développeur** que pour créer des applications avec FastAPI au **déploiement** dans le cloud. 🎉 + +Cela prend également en charge la plupart des éléments nécessaires lors du déploiement d’une application, notamment : + +* HTTPS +* Réplication, avec mise à l’échelle automatique basée sur les requêtes +* etc. + +FastAPI Cloud est le sponsor principal et le financeur des projets open source *FastAPI and friends*. ✨ + +## Déployer sur d’autres fournisseurs cloud { #deploy-to-other-cloud-providers } + +FastAPI est open source et basé sur des standards. Vous pouvez déployer des applications FastAPI sur n’importe quel fournisseur cloud de votre choix. + +Suivez les guides de votre fournisseur cloud pour déployer des applications FastAPI avec eux. 🤓 + +## Déployer votre propre serveur { #deploy-your-own-server } + +Je vous expliquerai également plus loin dans ce guide de **Déploiement** tous les détails, afin que vous compreniez ce qui se passe, ce qui doit être fait, et comment déployer des applications FastAPI par vous-même, y compris sur vos propres serveurs. 🤓 diff --git a/docs/fr/docs/deployment/https.md b/docs/fr/docs/deployment/https.md index 74d38cdb9..1b3c7be56 100644 --- a/docs/fr/docs/deployment/https.md +++ b/docs/fr/docs/deployment/https.md @@ -65,7 +65,7 @@ Voici un exemple de ce à quoi pourrait ressembler une API HTTPS, étape par ét Tout commencerait probablement par le fait que vous **acquériez** un **nom de domaine**. Ensuite, vous le configureriez dans un serveur DNS (possiblement le même que votre fournisseur cloud). -Vous obtiendriez probablement un serveur cloud (une machine virtuelle) ou quelque chose de similaire, et il aurait une adresse IP **publique** fixe. +Vous obtiendriez probablement un serveur cloud (une machine virtuelle) ou quelque chose de similaire, et il aurait une adresse IP publique fixe. Dans le ou les serveurs DNS, vous configureriez un enregistrement (un « `A record` ») pour faire pointer **votre domaine** vers l'**adresse IP publique de votre serveur**. diff --git a/docs/fr/docs/deployment/server-workers.md b/docs/fr/docs/deployment/server-workers.md new file mode 100644 index 000000000..338a5003d --- /dev/null +++ b/docs/fr/docs/deployment/server-workers.md @@ -0,0 +1,139 @@ +# Workers du serveur - Uvicorn avec workers { #server-workers-uvicorn-with-workers } + +Reprenons ces concepts de déploiement vus précédemment : + +* Sécurité - HTTPS +* Exécution au démarrage +* Redémarrages +* Réplication (le nombre de processus en cours d'exécution) +* Mémoire +* Étapes préalables avant le démarrage + +Jusqu'à présent, avec tous les tutoriels dans les documents, vous avez probablement exécuté un programme serveur, par exemple avec la commande `fastapi`, qui lance Uvicorn en exécutant un seul processus. + +Lors du déploiement d'applications, vous voudrez probablement avoir une réplication de processus pour tirer parti de plusieurs cœurs et pouvoir gérer davantage de requêtes. + +Comme vous l'avez vu dans le chapitre précédent sur les [Concepts de déploiement](concepts.md){.internal-link target=_blank}, il existe plusieurs stratégies possibles. + +Ici, je vais vous montrer comment utiliser Uvicorn avec des processus workers en utilisant la commande `fastapi` ou directement la commande `uvicorn`. + +/// info | Info + +Si vous utilisez des conteneurs, par exemple avec Docker ou Kubernetes, je vous en dirai plus à ce sujet dans le prochain chapitre : [FastAPI dans des conteneurs - Docker](docker.md){.internal-link target=_blank}. + +En particulier, lorsque vous exécutez sur Kubernetes, vous ne voudrez probablement pas utiliser de workers et plutôt exécuter un seul processus Uvicorn par conteneur, mais je vous en parlerai plus en détail dans ce chapitre. + +/// + +## Utiliser plusieurs workers { #multiple-workers } + +Vous pouvez démarrer plusieurs workers avec l'option de ligne de commande `--workers` : + +//// tab | `fastapi` + +Si vous utilisez la commande `fastapi` : + +
+ +```console +$ fastapi run --workers 4 main.py + + FastAPI Starting production 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://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. +``` + +
+ +//// + +//// tab | `uvicorn` + +Si vous préférez utiliser directement la commande `uvicorn` : + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 +INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit) +INFO: Started parent process [27365] +INFO: Started server process [27368] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27369] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27370] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27367] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +//// + +La seule option nouvelle ici est `--workers` qui indique à Uvicorn de démarrer 4 processus workers. + +Vous pouvez aussi voir qu'il affiche le PID de chaque processus, `27365` pour le processus parent (c'est le gestionnaire de processus) et un pour chaque processus worker : `27368`, `27369`, `27370` et `27367`. + +## Concepts de déploiement { #deployment-concepts } + +Ici, vous avez vu comment utiliser plusieurs workers pour paralléliser l'exécution de l'application, tirer parti de plusieurs cœurs du CPU et être en mesure de servir davantage de requêtes. + +Dans la liste des concepts de déploiement ci-dessus, l'utilisation de workers aide principalement à la partie réplication, et un peu aux redémarrages, mais vous devez toujours vous occuper des autres : + +* Sécurité - HTTPS +* Exécution au démarrage +* ***Redémarrages*** +* Réplication (le nombre de processus en cours d'exécution) +* Mémoire +* Étapes préalables avant le démarrage + +## Conteneurs et Docker { #containers-and-docker } + +Dans le prochain chapitre sur [FastAPI dans des conteneurs - Docker](docker.md){.internal-link target=_blank}, j'expliquerai quelques stratégies que vous pourriez utiliser pour gérer les autres concepts de déploiement. + +Je vous montrerai comment créer votre propre image à partir de zéro pour exécuter un seul processus Uvicorn. C'est un processus simple et c'est probablement ce que vous voudrez faire lorsque vous utilisez un système distribué de gestion de conteneurs comme Kubernetes. + +## Récapitulatif { #recap } + +Vous pouvez utiliser plusieurs processus workers avec l'option CLI `--workers` des commandes `fastapi` ou `uvicorn` pour tirer parti des CPU multicœurs, et exécuter plusieurs processus en parallèle. + +Vous pourriez utiliser ces outils et idées si vous mettez en place votre propre système de déploiement tout en prenant vous-même en charge les autres concepts de déploiement. + +Consultez le prochain chapitre pour en savoir plus sur FastAPI avec des conteneurs (par exemple Docker et Kubernetes). Vous verrez que ces outils offrent aussi des moyens simples de résoudre les autres concepts de déploiement. ✨ diff --git a/docs/fr/docs/environment-variables.md b/docs/fr/docs/environment-variables.md new file mode 100644 index 000000000..57479852a --- /dev/null +++ b/docs/fr/docs/environment-variables.md @@ -0,0 +1,298 @@ +# Variables d'environnement { #environment-variables } + +/// tip | Astuce + +Si vous savez déjà ce que sont les « variables d'environnement » et comment les utiliser, vous pouvez passer cette section. + +/// + +Une variable d'environnement (également appelée « env var ») est une variable qui vit en dehors du code Python, dans le système d'exploitation, et qui peut être lue par votre code Python (ou par d'autres programmes également). + +Les variables d'environnement peuvent être utiles pour gérer des **paramètres** d'application, dans le cadre de l'**installation** de Python, etc. + +## Créer et utiliser des variables d'environnement { #create-and-use-env-vars } + +Vous pouvez créer et utiliser des variables d'environnement dans le **shell (terminal)**, sans avoir besoin de Python : + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Vous pouvez créer une variable d'environnement MY_NAME avec +$ export MY_NAME="Wade Wilson" + +// Vous pouvez ensuite l'utiliser avec d'autres programmes, par exemple +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Créer une variable d'environnement MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// L'utiliser avec d'autres programmes, par exemple +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## Lire des variables d'environnement en Python { #read-env-vars-in-python } + +Vous pouvez également créer des variables d'environnement **en dehors** de Python, dans le terminal (ou par tout autre moyen), puis les **lire en Python**. + +Par exemple, vous pouvez avoir un fichier `main.py` contenant : + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip | Astuce + +Le deuxième argument de `os.getenv()` est la valeur par défaut à retourner. + +S'il n'est pas fourni, c'est `None` par défaut ; ici, nous fournissons `"World"` comme valeur par défaut à utiliser. + +/// + +Vous pouvez ensuite exécuter ce programme Python : + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Ici, nous ne définissons pas encore la variable d'environnement +$ python main.py + +// Comme nous ne l'avons pas définie, nous obtenons la valeur par défaut + +Hello World from Python + +// Mais si nous créons d'abord une variable d'environnement +$ export MY_NAME="Wade Wilson" + +// Puis que nous relançons le programme +$ python main.py + +// Il peut maintenant lire la variable d'environnement + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Ici, nous ne définissons pas encore la variable d'environnement +$ python main.py + +// Comme nous ne l'avons pas définie, nous obtenons la valeur par défaut + +Hello World from Python + +// Mais si nous créons d'abord une variable d'environnement +$ $Env:MY_NAME = "Wade Wilson" + +// Puis que nous relançons le programme +$ python main.py + +// Il peut maintenant lire la variable d'environnement + +Hello Wade Wilson from Python +``` + +
+ +//// + +Comme les variables d'environnement peuvent être définies en dehors du code, mais lues par le code, et qu'elles n'ont pas besoin d'être stockées (validées dans `git`) avec le reste des fichiers, il est courant de les utiliser pour les configurations ou les **paramètres**. + +Vous pouvez également créer une variable d'environnement uniquement pour l'**invocation d'un programme spécifique**, qui ne sera disponible que pour ce programme et uniquement pendant sa durée d'exécution. + +Pour cela, créez-la juste avant le programme, sur la même ligne : + +
+ +```console +// Créer en ligne une variable d'environnement MY_NAME pour cet appel de programme +$ MY_NAME="Wade Wilson" python main.py + +// Il peut maintenant lire la variable d'environnement + +Hello Wade Wilson from Python + +// La variable d'environnement n'existe plus ensuite +$ python main.py + +Hello World from Python +``` + +
+ +/// tip | Astuce + +Vous pouvez en lire davantage sur The Twelve-Factor App : Config. + +/// + +## Gérer les types et la validation { #types-and-validation } + +Ces variables d'environnement ne peuvent gérer que des **chaînes de texte**, car elles sont externes à Python et doivent être compatibles avec les autres programmes et le reste du système (et même avec différents systèmes d'exploitation, comme Linux, Windows, macOS). + +Cela signifie que **toute valeur** lue en Python à partir d'une variable d'environnement **sera une `str`**, et que toute conversion vers un autre type ou toute validation doit être effectuée dans le code. + +Vous en apprendrez davantage sur l'utilisation des variables d'environnement pour gérer les **paramètres d'application** dans le [Guide utilisateur avancé - Paramètres et variables d'environnement](./advanced/settings.md){.internal-link target=_blank}. + +## Variable d'environnement `PATH` { #path-environment-variable } + +Il existe une **variable d'environnement spéciale** appelée **`PATH`** qui est utilisée par les systèmes d'exploitation (Linux, macOS, Windows) pour trouver les programmes à exécuter. + +La valeur de la variable `PATH` est une longue chaîne composée de répertoires séparés par deux-points `:` sous Linux et macOS, et par point-virgule `;` sous Windows. + +Par exemple, la variable d'environnement `PATH` peut ressembler à ceci : + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Cela signifie que le système doit rechercher les programmes dans les répertoires : + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +Cela signifie que le système doit rechercher les programmes dans les répertoires : + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +Lorsque vous tapez une **commande** dans le terminal, le système d'exploitation **cherche** le programme dans **chacun de ces répertoires** listés dans la variable d'environnement `PATH`. + +Par exemple, lorsque vous tapez `python` dans le terminal, le système d'exploitation cherche un programme nommé `python` dans le **premier répertoire** de cette liste. + +S'il le trouve, alors il **l'utilise**. Sinon, il continue à chercher dans les **autres répertoires**. + +### Installer Python et mettre à jour `PATH` { #installing-python-and-updating-the-path } + +Lorsque vous installez Python, il est possible que l'on vous demande si vous souhaitez mettre à jour la variable d'environnement `PATH`. + +//// tab | Linux, macOS + +Supposons que vous installiez Python et qu'il se retrouve dans un répertoire `/opt/custompython/bin`. + +Si vous acceptez de mettre à jour la variable d'environnement `PATH`, l'installateur ajoutera `/opt/custompython/bin` à la variable d'environnement `PATH`. + +Cela pourrait ressembler à ceci : + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +Ainsi, lorsque vous tapez `python` dans le terminal, le système trouvera le programme Python dans `/opt/custompython/bin` (le dernier répertoire) et utilisera celui-là. + +//// + +//// tab | Windows + +Supposons que vous installiez Python et qu'il se retrouve dans un répertoire `C:\opt\custompython\bin`. + +Si vous acceptez de mettre à jour la variable d'environnement `PATH`, l'installateur ajoutera `C:\opt\custompython\bin` à la variable d'environnement `PATH`. + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +Ainsi, lorsque vous tapez `python` dans le terminal, le système trouvera le programme Python dans `C:\opt\custompython\bin` (le dernier répertoire) et utilisera celui-là. + +//// + +Ainsi, si vous tapez : + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +Le système va **trouver** le programme `python` dans `/opt/custompython/bin` et l'exécuter. + +Cela reviendrait à peu près à taper : + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +Le système va **trouver** le programme `python` dans `C:\opt\custompython\bin\python` et l'exécuter. + +Cela reviendrait à peu près à taper : + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +Ces informations vous seront utiles lors de l'apprentissage des [Environnements virtuels](virtual-environments.md){.internal-link target=_blank}. + +## Conclusion { #conclusion } + +Avec cela, vous devriez avoir une compréhension de base de ce que sont les **variables d'environnement** et de la façon de les utiliser en Python. + +Vous pouvez également en lire davantage sur la page Wikipédia dédiée aux variables d'environnement. + +Dans de nombreux cas, il n'est pas évident de voir immédiatement en quoi les variables d'environnement seraient utiles et applicables. Mais elles réapparaissent dans de nombreux scénarios lorsque vous développez, il est donc bon de les connaître. + +Par exemple, vous aurez besoin de ces informations dans la section suivante, sur les [Environnements virtuels](virtual-environments.md). diff --git a/docs/fr/docs/fastapi-cli.md b/docs/fr/docs/fastapi-cli.md new file mode 100644 index 000000000..9f31e8a2f --- /dev/null +++ b/docs/fr/docs/fastapi-cli.md @@ -0,0 +1,75 @@ +# FastAPI CLI { #fastapi-cli } + +**FastAPI CLI** est un programme en ligne de commande que vous pouvez utiliser pour servir votre application FastAPI, gérer votre projet FastAPI, et plus encore. + +Lorsque vous installez FastAPI (par exemple avec `pip install "fastapi[standard]"`), cela inclut un package appelé `fastapi-cli` ; ce package fournit la commande `fastapi` dans le terminal. + +Pour exécuter votre application FastAPI en développement, vous pouvez utiliser la commande `fastapi dev` : + +
+ +```console +$ 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. +``` + +
+ +Le programme en ligne de commande nommé `fastapi` est **FastAPI CLI**. + +FastAPI CLI prend le chemin vers votre programme Python (par exemple `main.py`), détecte automatiquement l’instance `FastAPI` (généralement nommée `app`), détermine la procédure d’importation correcte, puis la sert. + +Pour la production, vous utiliserez plutôt `fastapi run`. 🚀 + +En interne, **FastAPI CLI** utilise Uvicorn, un serveur ASGI haute performance, prêt pour la production. 😎 + +## `fastapi dev` { #fastapi-dev } + +L’exécution de `fastapi dev` lance le mode développement. + +Par défaut, l’**auto-reload** est activé et recharge automatiquement le serveur lorsque vous modifiez votre code. Cela consomme des ressources et peut être moins stable que lorsqu’il est désactivé. Vous devez l’utiliser uniquement pour le développement. Il écoute aussi sur l’adresse IP `127.0.0.1`, qui est l’adresse IP permettant à votre machine de communiquer uniquement avec elle‑même (`localhost`). + +## `fastapi run` { #fastapi-run } + +Exécuter `fastapi run` démarre FastAPI en mode production par défaut. + +Par défaut, l’**auto-reload** est désactivé. Il écoute aussi sur l’adresse IP `0.0.0.0`, ce qui signifie toutes les adresses IP disponibles ; de cette manière, il sera accessible publiquement à toute personne pouvant communiquer avec la machine. C’est ainsi que vous l’exécutez normalement en production, par exemple dans un conteneur. + +Dans la plupart des cas, vous avez (et devez avoir) un « termination proxy » au‑dessus qui gère le HTTPS pour vous ; cela dépend de la façon dont vous déployez votre application : votre fournisseur peut le faire pour vous, ou vous devrez le configurer vous‑même. + +/// tip | Astuce + +Vous pouvez en savoir plus à ce sujet dans la [documentation de déploiement](deployment/index.md){.internal-link target=_blank}. + +/// diff --git a/docs/fr/docs/features.md b/docs/fr/docs/features.md index bc63e11b4..e5e809940 100644 --- a/docs/fr/docs/features.md +++ b/docs/fr/docs/features.md @@ -1,43 +1,43 @@ -# Fonctionnalités +# Fonctionnalités { #features } -## Fonctionnalités de FastAPI +## Fonctionnalités de FastAPI { #fastapi-features } -**FastAPI** vous offre ceci: +**FastAPI** vous offre les éléments suivants : -### Basé sur des standards ouverts +### Basé sur des standards ouverts { #based-on-open-standards } -* OpenAPI pour la création d'API, incluant la déclaration de path operations, paramètres, corps de requêtes, sécurité, etc. -* Documentation automatique des modèles de données avec JSON Schema (comme OpenAPI est aussi basée sur JSON Schema). -* Conçue avec ces standards après une analyse méticuleuse. Plutôt qu'en rajoutant des surcouches après coup. -* Cela permet d'utiliser de la **génération automatique de code client** dans beaucoup de langages. +* OpenAPI pour la création d'API, incluant la déclaration de chemin opérations, paramètres, corps de requêtes, sécurité, etc. +* Documentation automatique des modèles de données avec JSON Schema (puisque OpenAPI est lui-même basé sur JSON Schema). +* Conçu autour de ces standards, après une étude méticuleuse. Plutôt qu'une couche ajoutée après coup. +* Cela permet également d'utiliser la **génération automatique de code client** dans de nombreux langages. -### Documentation automatique +### Documentation automatique { #automatic-docs } -Documentation d'API interactive et interface web d'exploration. Comme le framework est basé sur OpenAPI, de nombreuses options sont disponibles. Deux d'entre-elles sont incluses par défaut. +Documentation d'API interactive et interfaces web d'exploration. Comme le framework est basé sur OpenAPI, plusieurs options existent, 2 incluses par défaut. -* Swagger UI, propose une documentation interactive. Vous permet de directement tester l'API depuis votre navigateur. +* Swagger UI, avec exploration interactive, appelez et testez votre API directement depuis le navigateur. ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* Une autre documentation d'API est fournie par ReDoc. +* Documentation d'API alternative avec ReDoc. ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Faite en python moderne +### Uniquement du Python moderne { #just-modern-python } -Tout est basé sur la déclaration de type standard de **Python 3.8** (grâce à Pydantic). Pas de nouvelles syntaxes à apprendre. Juste du Python standard et moderne. +Tout est basé sur les déclarations de **types Python** standard (grâce à Pydantic). Aucune nouvelle syntaxe à apprendre. Juste du Python moderne standard. -Si vous souhaitez un rappel de 2 minutes sur l'utilisation des types en Python (même si vous ne comptez pas utiliser FastAPI), jetez un oeil au tutoriel suivant: [Python Types](python-types.md){.internal-link target=_blank}. +Si vous avez besoin d'un rappel de 2 minutes sur l'utilisation des types en Python (même si vous n'utilisez pas FastAPI), consultez le court tutoriel : [Types Python](python-types.md){.internal-link target=_blank}. -Vous écrivez du python standard avec des annotations de types: +Vous écrivez du Python standard avec des types : ```Python from datetime import date from pydantic import BaseModel -# Déclare une variable comme étant une str -# et profitez de l'aide de votre IDE dans cette fonction +# Déclarez une variable comme étant une str +# et profitez de l'aide de l'éditeur dans cette fonction def main(user_id: str): return user_id @@ -48,7 +48,8 @@ class User(BaseModel): name: str joined: date ``` -Qui peuvent ensuite être utilisés comme cela: + +Qui peuvent ensuite être utilisés comme ceci : ```Python my_user: User = User(id=3, name="John Doe", joined="2018-07-19") @@ -64,138 +65,137 @@ my_second_user: User = User(**second_user_data) /// info -`**second_user_data` signifie: +`**second_user_data` signifie : -Utilise les clés et valeurs du dictionnaire `second_user_data` directement comme des arguments clé-valeur. C'est équivalent à: `User(id=4, name="Mary", joined="2018-11-30")` +Passez les clés et valeurs du dictionnaire `second_user_data` directement comme arguments clé-valeur, équivalent à : `User(id=4, name="Mary", joined="2018-11-30")` /// -### Support d'éditeurs +### Support des éditeurs { #editor-support } -Tout le framework a été conçu pour être facile et intuitif d'utilisation, toutes les décisions de design ont été testées sur de nombreux éditeurs avant même de commencer le développement final afin d'assurer la meilleure expérience de développement possible. +Tout le framework a été conçu pour être facile et intuitif à utiliser, toutes les décisions ont été testées sur plusieurs éditeurs avant même de commencer le développement, pour assurer la meilleure expérience de développement. -Dans le dernier sondage effectué auprès de développeurs python il était clair que la fonctionnalité la plus utilisée est "l’autocomplétion". +Dans les enquêtes auprès des développeurs Python, il est clair que l’une des fonctionnalités les plus utilisées est « autocomplétion ». -Tout le framework **FastAPI** a été conçu avec cela en tête. L'autocomplétion fonctionne partout. +L'ensemble du framework **FastAPI** est conçu pour satisfaire cela. L'autocomplétion fonctionne partout. -Vous devrez rarement revenir à la documentation. +Vous aurez rarement besoin de revenir aux documents. -Voici comment votre éditeur peut vous aider: +Voici comment votre éditeur peut vous aider : -* dans Visual Studio Code: +* dans Visual Studio Code : ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -* dans PyCharm: +* dans PyCharm : ![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) -Vous aurez des propositions de complétion que vous n'auriez jamais imaginées. Par exemple la clé `prix` dans le corps d'un document JSON (qui est peut-être imbriqué) venant d'une requête. +Vous obtiendrez de l'autocomplétion dans du code que vous auriez pu considérer impossible auparavant. Par exemple, la clé `price` à l'intérieur d'un corps JSON (qui aurait pu être imbriqué) provenant d'une requête. -Plus jamais vous ne vous tromperez en tapant le nom d'une clé, vous ne ferez des aller-retour entre votre code et la documentation ou vous ne scrollerez de haut en bas afin d'enfin savoir si vous devez taper `username` ou `user_name`. +Fini de taper des noms de clés erronés, de faire des allers-retours entre les documents, ou de faire défiler vers le haut et vers le bas pour savoir si vous avez finalement utilisé `username` ou `user_name`. -### Court +### Court { #short } -Des **valeurs par défaut** sont définies pour tout, des configurations optionnelles sont présentent partout. Tous ces paramètres peuvent être ajustés afin de faire ce que vous voulez et définir l'API dont vous avez besoin. +Des **valeurs par défaut** sensées pour tout, avec des configurations optionnelles partout. Tous les paramètres peuvent être ajustés finement pour faire ce dont vous avez besoin et définir l'API dont vous avez besoin. -Mais, **tout fonctionne** par défaut. +Mais par défaut, tout **« just works »**. -### Validation +### Validation { #validation } -* Validation pour la plupart (ou tous?) les **types de données** Python incluant: +* Validation pour la plupart (ou tous ?) des **types de données** Python, y compris : * objets JSON (`dict`). - * listes JSON (`list`) définissant des types d'éléments. - * Champs String (`str`), définition de longueur minimum ou maximale. - * Nombres (`int`, `float`) avec valeur minimale and maximale, etc. + * tableaux JSON (`list`) définissant les types d'éléments. + * champs String (`str`), définition des longueurs minimale et maximale. + * nombres (`int`, `float`) avec valeurs minimale et maximale, etc. -* Validation pour des types plus exotiques, tel que: +* Validation pour des types plus exotiques, comme : * URL. * Email. * UUID. - * ...et autres. + * ... et autres. -Toutes les validations sont gérées par le bien établi et robuste **Pydantic**. +Toutes les validations sont gérées par le **Pydantic** bien établi et robuste. -### Sécurité et authentification +### Sécurité et authentification { #security-and-authentication } -La sécurité et l'authentification sont intégrées. Sans aucun compromis avec les bases de données ou les modèles de données. +Sécurité et authentification intégrées. Sans aucun compromis avec les bases de données ou les modèles de données. -Tous les protocoles de sécurités sont définis dans OpenAPI, incluant: +Tous les schémas de sécurité définis dans OpenAPI, y compris : * HTTP Basic. -* **OAuth2** (aussi avec **JWT tokens**). Jetez un oeil au tutoriel [OAuth2 avec JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. -* Clés d'API dans: - * Le header. - * Les paramètres de requêtes. - * Les cookies, etc. +* **OAuth2** (également avec des **tokens JWT**). Consultez le tutoriel [OAuth2 avec JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. +* Clés d'API dans : + * les en-têtes. + * les paramètres de requête. + * les cookies, etc. -Plus toutes les fonctionnalités de sécurités venant de Starlette (incluant les **cookies de sessions**). +Plus toutes les fonctionnalités de sécurité de Starlette (y compris les **cookies de session**). -Le tout conçu en composant réutilisable facilement intégrable à vos systèmes, data stores, base de données relationnelle ou NoSQL, etc. +Le tout construit comme des outils et composants réutilisables, faciles à intégrer à vos systèmes, magasins de données, bases de données relationnelles et NoSQL, etc. -### Injection de dépendances +### Injection de dépendances { #dependency-injection } -FastAPI contient un système simple mais extrêmement puissant d'Injection de Dépendances. +FastAPI inclut un système d’Injection de dépendances extrêmement simple à utiliser, mais extrêmement puissant. -* Même les dépendances peuvent avoir des dépendances, créant une hiérarchie ou un **"graph" de dépendances** -* Tout est **automatiquement géré** par le framework -* Toutes les dépendances peuvent exiger des données d'une requêtes et **Augmenter les contraintes d'un path operation** et de la documentation automatique. -* **Validation automatique** même pour les paramètres de *path operation* définis dans les dépendances. -* Supporte les systèmes d'authentification d'utilisateurs complexes, les **connexions de base de données**, etc. -* **Aucun compromis** avec les bases de données, les frontends, etc. Mais une intégration facile avec n'importe lequel d'entre eux. +* Même les dépendances peuvent avoir des dépendances, créant une hiérarchie ou un **« graphe » de dépendances**. +* Le tout **géré automatiquement** par le framework. +* Toutes les dépendances peuvent exiger des données des requêtes et **augmenter les contraintes du chemin d'accès** ainsi que la documentation automatique. +* **Validation automatique** même pour les paramètres de *chemin d'accès* définis dans les dépendances. +* Prise en charge des systèmes d'authentification d'utilisateurs complexes, des **connexions de base de données**, etc. +* **Aucun compromis** avec les bases de données, les frontends, etc. Mais une intégration facile avec tous. -### "Plug-ins" illimités +### « Plug-ins » illimités { #unlimited-plug-ins } -Ou, en d'autres termes, pas besoin d'eux, importez le code que vous voulez et utilisez le. +Ou, autrement dit, pas besoin d'eux, importez et utilisez le code dont vous avez besoin. -Tout intégration est conçue pour être si simple à utiliser (avec des dépendances) que vous pouvez créer un "plug-in" pour votre application en deux lignes de code utilisant la même syntaxe que celle de vos *path operations* +Toute intégration est conçue pour être si simple à utiliser (avec des dépendances) que vous pouvez créer un « plug-in » pour votre application en 2 lignes de code en utilisant la même structure et la même syntaxe que pour vos *chemins d'accès*. -### Testé +### Testé { #tested } -* 100% de couverture de test. -* 100% d'annotations de type dans le code. -* Utilisé dans des applications mises en production. +* 100 % de couverture de test. +* 100 % de base de code annotée avec des types. +* Utilisé dans des applications en production. -## Fonctionnalités de Starlette +## Fonctionnalités de Starlette { #starlette-features } -**FastAPI** est complètement compatible (et basé sur) Starlette. Le code utilisant Starlette que vous ajouterez fonctionnera donc aussi. +**FastAPI** est entièrement compatible avec (et basé sur) Starlette. Donc, tout code Starlette additionnel que vous avez fonctionnera aussi. -En fait, `FastAPI` est un sous composant de `Starlette`. Donc, si vous savez déjà comment utiliser Starlette, la plupart des fonctionnalités fonctionneront de la même manière. +`FastAPI` est en fait une sous-classe de `Starlette`. Ainsi, si vous connaissez ou utilisez déjà Starlette, la plupart des fonctionnalités fonctionneront de la même manière. -Avec **FastAPI** vous aurez toutes les fonctionnalités de **Starlette** (FastAPI est juste Starlette sous stéroïdes): +Avec **FastAPI** vous obtenez toutes les fonctionnalités de **Starlette** (puisque FastAPI est juste Starlette sous stéroïdes) : -* Des performances vraiment impressionnantes. C'est l'un des framework Python les plus rapide, à égalité avec **NodeJS** et **GO**. -* Le support des **WebSockets**. -* Le support de **GraphQL**. -* Les tâches d'arrière-plan. -* Des évènements de démarrages et d'arrêt. -* Un client de test basé sur `request` -* **CORS**, GZip, Static Files, Streaming responses. -* Le support des **Sessions et Cookies**. -* Une couverture de test à 100 %. -* 100 % de la base de code avec des annotations de type. +* Des performances vraiment impressionnantes. C'est l’un des frameworks Python les plus rapides disponibles, à l’égal de **NodeJS** et **Go**. +* Prise en charge des **WebSocket**. +* Tâches d'arrière-plan dans le processus. +* Évènements de démarrage et d'arrêt. +* Client de test basé sur HTTPX. +* **CORS**, GZip, fichiers statiques, réponses en streaming. +* Prise en charge des **Sessions et Cookies**. +* Couverture de test à 100 %. +* Base de code annotée à 100 % avec des types. -## Fonctionnalités de Pydantic +## Fonctionnalités de Pydantic { #pydantic-features } -**FastAPI** est totalement compatible avec (et basé sur) Pydantic. Le code utilisant Pydantic que vous ajouterez fonctionnera donc aussi. +**FastAPI** est entièrement compatible avec (et basé sur) Pydantic. Donc, tout code Pydantic additionnel que vous avez fonctionnera aussi. -Inclus des librairies externes basées, aussi, sur Pydantic, servent d'ORMs, ODMs pour les bases de données. +Y compris des bibliothèques externes également basées sur Pydantic, servant d’ORM, d’ODM pour les bases de données. -Cela signifie aussi que, dans la plupart des cas, vous pouvez fournir l'objet reçu d'une requête **directement à la base de données**, comme tout est validé automatiquement. +Cela signifie également que, dans de nombreux cas, vous pouvez passer l'objet que vous recevez d'une requête **directement à la base de données**, puisque tout est validé automatiquement. -Inversement, dans la plupart des cas vous pourrez juste envoyer l'objet récupéré de la base de données **directement au client** +L’inverse est également vrai, dans de nombreux cas, vous pouvez simplement passer l'objet que vous récupérez de la base de données **directement au client**. -Avec **FastAPI** vous aurez toutes les fonctionnalités de **Pydantic** (comme FastAPI est basé sur Pydantic pour toutes les manipulations de données): +Avec **FastAPI** vous obtenez toutes les fonctionnalités de **Pydantic** (puisque FastAPI est basé sur Pydantic pour toute la gestion des données) : -* **Pas de prise de tête**: - * Pas de nouveau langage de définition de schéma à apprendre. - * Si vous connaissez le typage en python vous savez comment utiliser Pydantic. -* Aide votre **IDE/linter/cerveau**: - * Parce que les structures de données de pydantic consistent seulement en une instance de classe que vous définissez; l'auto-complétion, le linting, mypy et votre intuition devrait être largement suffisante pour valider vos données. -* Valide les **structures complexes**: - * Utilise les modèles hiérarchique de Pydantic, le `typage` Python pour les `Lists`, `Dict`, etc. - * Et les validateurs permettent aux schémas de données complexes d'être clairement et facilement définis, validés et documentés sous forme d'un schéma JSON. - * Vous pouvez avoir des objets **JSON fortement imbriqués** tout en ayant, pour chacun, de la validation et des annotations. -* **Renouvelable**: - * Pydantic permet de définir de nouveaux types de données ou vous pouvez étendre la validation avec des méthodes sur un modèle décoré avec le décorateur de validation -* 100% de couverture de test. +* **Pas de prise de tête** : + * Pas de micro-langage de définition de schéma à apprendre. + * Si vous connaissez les types Python vous savez utiliser Pydantic. +* Fonctionne bien avec votre **IDE/linter/cerveau** : + * Parce que les structures de données de Pydantic sont simplement des instances de classes que vous définissez ; l'autocomplétion, le linting, mypy et votre intuition devraient tous bien fonctionner avec vos données validées. +* Valider des **structures complexes** : + * Utilisation de modèles Pydantic hiérarchiques, de `List` et `Dict` du `typing` Python, etc. + * Et les validateurs permettent de définir, vérifier et documenter clairement et facilement des schémas de données complexes en tant que JSON Schema. + * Vous pouvez avoir des objets **JSON fortement imbriqués** et les faire tous valider et annoter. +* **Extensible** : + * Pydantic permet de définir des types de données personnalisés ou vous pouvez étendre la validation avec des méthodes sur un modèle décoré avec le décorateur de validation. +* Couverture de test à 100 %. diff --git a/docs/fr/docs/help-fastapi.md b/docs/fr/docs/help-fastapi.md index 9b75f463b..08d9a7a72 100644 --- a/docs/fr/docs/help-fastapi.md +++ b/docs/fr/docs/help-fastapi.md @@ -1,103 +1,255 @@ -# Help FastAPI - Obtenir de l'aide +# Aider FastAPI - Obtenir de l'aide { #help-fastapi-get-help } Aimez-vous **FastAPI** ? -Vous souhaitez aider FastAPI, les autres utilisateurs et l'auteur ? +Souhaitez-vous aider FastAPI, les autres utilisateurs et l'auteur ? -Ou souhaitez-vous obtenir de l'aide avec le **FastAPI** ? +Ou souhaitez-vous obtenir de l'aide avec **FastAPI** ? Il existe des moyens très simples d'aider (plusieurs ne nécessitent qu'un ou deux clics). -Il existe également plusieurs façons d'obtenir de l'aide. +Et il existe aussi plusieurs façons d'obtenir de l'aide. -## Star **FastAPI** sur GitHub +## S'abonner à la newsletter { #subscribe-to-the-newsletter } -Vous pouvez "star" FastAPI dans GitHub (en cliquant sur le bouton étoile en haut à droite) : https://github.com/fastapi/fastapi. ⭐️ +Vous pouvez vous abonner à la (peu fréquente) [newsletter **FastAPI and friends**](newsletter.md){.internal-link target=_blank} pour rester informé à propos : -En ajoutant une étoile, les autres utilisateurs pourront la trouver plus facilement et constater qu'elle a déjà été utile à d'autres. +* Nouvelles sur FastAPI et ses amis 🚀 +* Guides 📝 +* Fonctionnalités ✨ +* Changements majeurs 🚨 +* Astuces et conseils ✅ -## Watch le dépôt GitHub pour les releases +## Suivre FastAPI sur X (Twitter) { #follow-fastapi-on-x-twitter } -Vous pouvez "watch" FastAPI dans GitHub (en cliquant sur le bouton "watch" en haut à droite) : https://github.com/fastapi/fastapi. 👀 +Suivez @fastapi sur **X (Twitter)** pour obtenir les dernières nouvelles sur **FastAPI**. 🐦 -Vous pouvez y sélectionner "Releases only". +## Mettre une étoile à **FastAPI** sur GitHub { #star-fastapi-in-github } -Ainsi, vous recevrez des notifications (dans votre courrier électronique) chaque fois qu'il y aura une nouvelle version de **FastAPI** avec des corrections de bugs et de nouvelles fonctionnalités. +Vous pouvez « star » FastAPI sur GitHub (en cliquant sur le bouton étoile en haut à droite) : https://github.com/fastapi/fastapi. ⭐️ -## Se rapprocher de l'auteur +En ajoutant une étoile, les autres utilisateurs pourront le trouver plus facilement et voir qu'il a déjà été utile à d'autres. -Vous pouvez vous rapprocher de moi (Sebastián Ramírez / `tiangolo`), l'auteur. +## Suivre le dépôt GitHub pour les releases { #watch-the-github-repository-for-releases } -Vous pouvez : +Vous pouvez « watch » FastAPI sur GitHub (en cliquant sur le bouton « watch » en haut à droite) : https://github.com/fastapi/fastapi. 👀 + +Vous pouvez y sélectionner « Releases only ». + +Ainsi, vous recevrez des notifications (par e‑mail) chaque fois qu'il y aura une nouvelle release (une nouvelle version) de **FastAPI** avec des corrections de bugs et de nouvelles fonctionnalités. + +## Entrer en contact avec l'auteur { #connect-with-the-author } + +Vous pouvez entrer en contact avec moi (Sebastián Ramírez / `tiangolo`), l'auteur. + +Vous pouvez : * Me suivre sur **GitHub**. * Voir d'autres projets Open Source que j'ai créés et qui pourraient vous aider. - * Suivez-moi pour voir quand je crée un nouveau projet Open Source. -* Me suivre sur **X (Twitter)**. - * Dites-moi comment vous utilisez FastAPI (j'adore entendre ça). - * Entendre quand je fais des annonces ou que je lance de nouveaux outils. -* Vous connectez à moi sur **LinkedIn**. - * Etre notifié quand je fais des annonces ou que je lance de nouveaux outils (bien que j'utilise plus souvent X (Twitter) 🤷‍♂). -* Lire ce que j’écris (ou me suivre) sur **Dev.to** ou **Medium**. - * Lire d'autres idées, articles, et sur les outils que j'ai créés. - * Suivez-moi pour lire quand je publie quelque chose de nouveau. + * Me suivre pour voir quand je crée un nouveau projet Open Source. +* Me suivre sur **X (Twitter)** ou sur Mastodon. + * Me dire comment vous utilisez FastAPI (j'adore l'entendre). + * Être informé quand je fais des annonces ou publie de nouveaux outils. + * Vous pouvez aussi suivre @fastapi sur X (Twitter) (un compte séparé). +* Me suivre sur **LinkedIn**. + * Être informé quand je fais des annonces ou publie de nouveaux outils (même si j'utilise plus souvent X (Twitter) 🤷‍♂). +* Lire ce que j'écris (ou me suivre) sur **Dev.to** ou **Medium**. + * Lire d'autres idées, des articles, et découvrir des outils que j'ai créés. + * Me suivre pour lire quand je publie quelque chose de nouveau. -## Tweeter sur **FastAPI** +## Tweeter à propos de **FastAPI** { #tweet-about-fastapi } -Tweetez à propos de **FastAPI** et faites-moi savoir, ainsi qu'aux autres, pourquoi vous aimez ça. 🎉 +Tweetez à propos de **FastAPI** et faites savoir à moi et aux autres pourquoi vous l'appréciez. 🎉 -J'aime entendre parler de l'utilisation du **FastAPI**, de ce que vous avez aimé dedans, dans quel projet/entreprise l'utilisez-vous, etc. +J'adore entendre comment **FastAPI** est utilisé, ce que vous avez aimé, dans quel projet/quelle entreprise vous l'utilisez, etc. -## Voter pour FastAPI +## Voter pour FastAPI { #vote-for-fastapi } * Votez pour **FastAPI** sur Slant. -* Votez pour **FastAPI** sur AlternativeTo. -* Votez pour **FastAPI** sur awesome-rest. +* Votez pour **FastAPI** sur AlternativeTo. +* Indiquez que vous utilisez **FastAPI** sur StackShare. -## Aider les autres à résoudre les problèmes dans GitHub +## Aider les autres avec des questions sur GitHub { #help-others-with-questions-in-github } -Vous pouvez voir les problèmes existants et essayer d'aider les autres, la plupart du temps il s'agit de questions dont vous connaissez peut-être déjà la réponse. 🤓 +Vous pouvez essayer d'aider les autres avec leurs questions dans : -## Watch le dépôt GitHub +* GitHub Discussions +* GitHub Issues -Vous pouvez "watch" FastAPI dans GitHub (en cliquant sur le bouton "watch" en haut à droite) : https://github.com/fastapi/fastapi. 👀 +Dans de nombreux cas, vous connaissez peut-être déjà la réponse à ces questions. 🤓 -Si vous sélectionnez "Watching" au lieu de "Releases only", vous recevrez des notifications lorsque quelqu'un crée une nouvelle Issue. +Si vous aidez beaucoup de personnes avec leurs questions, vous deviendrez un [Expert FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank} officiel. 🎉 -Vous pouvez alors essayer de les aider à résoudre ces problèmes. +N'oubliez pas, le point le plus important est : essayez d'être aimable. Les gens viennent avec leurs frustrations et, dans bien des cas, ne posent pas la question de la meilleure façon, mais faites de votre mieux pour rester aimable. 🤗 -## Créer une Issue +L'idée est que la communauté **FastAPI** soit bienveillante et accueillante. En même temps, n'acceptez pas l'intimidation ni les comportements irrespectueux envers les autres. Nous devons prendre soin les uns des autres. -Vous pouvez créer une Issue dans le dépôt GitHub, par exemple pour : +--- -* Poser une question ou s'informer sur un problème. -* Suggérer une nouvelle fonctionnalité. +Voici comment aider les autres avec des questions (dans les discussions ou les issues) : -**Note** : si vous créez un problème, alors je vais vous demander d'aider aussi les autres. 😉 +### Comprendre la question { #understand-the-question } -## Créer une Pull Request +* Vérifiez si vous comprenez quel est l’**objectif** et le cas d'utilisation de la personne qui pose la question. -Vous pouvez créer une Pull Request, par exemple : +* Ensuite, vérifiez si la question (la grande majorité sont des questions) est **claire**. -* Pour corriger une faute de frappe que vous avez trouvée sur la documentation. +* Dans de nombreux cas, la question porte sur une solution imaginaire de l'utilisateur, mais il pourrait y en avoir une **meilleure**. Si vous comprenez mieux le problème et le cas d'utilisation, vous pourriez suggérer une **solution alternative** plus adaptée. + +* Si vous ne comprenez pas la question, demandez plus de **détails**. + +### Reproduire le problème { #reproduce-the-problem } + +Dans la plupart des cas et pour la plupart des questions, il y a quelque chose lié au **code original** de la personne. + +Dans de nombreux cas, elle ne copiera qu'un fragment de code, mais ce n'est pas suffisant pour **reproduire le problème**. + +* Vous pouvez leur demander de fournir un exemple minimal, complet et vérifiable, que vous pouvez **copier‑coller** et exécuter localement pour voir la même erreur ou le même comportement qu'ils observent, ou pour mieux comprendre leur cas d'utilisation. + +* Si vous vous sentez très généreux, vous pouvez essayer de **créer un tel exemple** vous‑même, simplement à partir de la description du problème. Gardez simplement à l'esprit que cela peut prendre beaucoup de temps et qu'il peut être préférable de leur demander d'abord de clarifier le problème. + +### Suggérer des solutions { #suggest-solutions } + +* Après avoir compris la question, vous pouvez leur donner une **réponse** possible. + +* Dans de nombreux cas, il est préférable de comprendre leur **problème sous‑jacent ou cas d'utilisation**, car il pourrait exister une meilleure façon de le résoudre que ce qu'ils essaient de faire. + +### Demander la clôture { #ask-to-close } + +S'ils répondent, il y a de fortes chances que vous ayez résolu leur problème, bravo, **vous êtes un héros** ! 🦸 + +* Maintenant, si cela a résolu leur problème, vous pouvez leur demander de : + + * Dans GitHub Discussions : marquer le commentaire comme **réponse**. + * Dans GitHub Issues : **fermer** l'issue. + +## Suivre le dépôt GitHub { #watch-the-github-repository } + +Vous pouvez « watch » FastAPI sur GitHub (en cliquant sur le bouton « watch » en haut à droite) : https://github.com/fastapi/fastapi. 👀 + +Si vous sélectionnez « Watching » au lieu de « Releases only », vous recevrez des notifications lorsque quelqu'un crée une nouvelle issue ou question. Vous pouvez aussi préciser que vous ne souhaitez être notifié que pour les nouvelles issues, ou les discussions, ou les PR, etc. + +Vous pouvez alors essayer de les aider à résoudre ces questions. + +## Poser des questions { #ask-questions } + +Vous pouvez créer une nouvelle question dans le dépôt GitHub, par exemple pour : + +* Poser une **question** ou demander à propos d'un **problème**. +* Suggérer une nouvelle **fonctionnalité**. + +**Remarque** : si vous le faites, je vais vous demander d'aider aussi les autres. 😉 + +## Relire des Pull Requests { #review-pull-requests } + +Vous pouvez m'aider à relire les pull requests des autres. + +Encore une fois, essayez autant que possible d'être aimable. 🤗 + +--- + +Voici ce à garder à l'esprit et comment relire une pull request : + +### Comprendre le problème { #understand-the-problem } + +* D'abord, assurez‑vous de **comprendre le problème** que la pull request essaie de résoudre. Il peut y avoir une discussion plus longue dans une GitHub Discussion ou une issue. + +* Il y a aussi de bonnes chances que la pull request ne soit pas réellement nécessaire parce que le problème peut être résolu d'une **autre manière**. Vous pouvez alors le suggérer ou poser la question. + +### Ne pas s'inquiéter du style { #dont-worry-about-style } + +* Ne vous souciez pas trop des choses comme les styles de messages de commit, je ferai un squash and merge en personnalisant le commit manuellement. + +* Ne vous inquiétez pas non plus des règles de style, il existe déjà des outils automatisés qui vérifient cela. + +Et s'il y a d'autres besoins de style ou de cohérence, je le demanderai directement, ou j'ajouterai des commits par‑dessus avec les changements nécessaires. + +### Vérifier le code { #check-the-code } + +* Vérifiez et lisez le code, voyez s'il a du sens, **exécutez‑le localement** et voyez s'il résout effectivement le problème. + +* Ensuite, **commentez** en disant que vous l'avez fait, c'est ainsi que je saurai que vous l'avez vraiment vérifié. + +/// info + +Malheureusement, je ne peux pas simplement faire confiance aux PR qui ont juste plusieurs approbations. + +Plusieurs fois, il est arrivé qu'il y ait des PR avec 3, 5 ou plus approbations, probablement parce que la description est attrayante, mais lorsque je vérifie les PR, elles sont en fait cassées, ont un bug, ou ne résolvent pas le problème qu'elles prétendent résoudre. 😅 + +Donc, il est vraiment important que vous lisiez et exécutiez le code, et que vous me le disiez dans les commentaires. 🤓 + +/// + +* Si la PR peut être simplifiée d'une certaine manière, vous pouvez le demander, mais il n'est pas nécessaire d'être trop pointilleux, il peut y avoir beaucoup de points de vue subjectifs (et j'aurai les miens aussi 🙈), donc il est préférable de vous concentrer sur les choses fondamentales. + +### Tests { #tests } + +* Aidez‑moi à vérifier que la PR a des **tests**. + +* Vérifiez que les tests **échouent** avant la PR. 🚨 + +* Puis vérifiez que les tests **réussissent** après la PR. ✅ + +* Beaucoup de PR n'ont pas de tests, vous pouvez leur **rappeler** d'ajouter des tests, ou même **suggérer** des tests vous‑même. C'est l'une des choses qui consomment le plus de temps et vous pouvez beaucoup aider. + +* Commentez aussi ce que vous avez essayé, ainsi je saurai que vous l'avez vérifié. 🤓 + +## Créer une Pull Request { #create-a-pull-request } + +Vous pouvez [contribuer](contributing.md){.internal-link target=_blank} au code source avec des Pull Requests, par exemple : + +* Corriger une coquille que vous avez trouvée dans la documentation. +* Partager un article, une vidéo ou un podcast que vous avez créé ou trouvé à propos de FastAPI en modifiant ce fichier. + * Vous devez vous assurer d'ajouter votre lien au début de la section correspondante. +* Aider à [traduire la documentation](contributing.md#translations){.internal-link target=_blank} dans votre langue. + * Vous pouvez aussi aider à relire les traductions créées par d'autres. * Proposer de nouvelles sections de documentation. -* Pour corriger une Issue/Bug existant. -* Pour ajouter une nouvelle fonctionnalité. +* Corriger une issue/un bug existant. + * Vous devez ajouter des tests. +* Ajouter une nouvelle fonctionnalité. + * Vous devez ajouter des tests. + * Vous devez ajouter de la documentation si c'est pertinent. -## Parrainer l'auteur +## Aider à maintenir FastAPI { #help-maintain-fastapi } -Vous pouvez également soutenir financièrement l'auteur (moi) via GitHub sponsors. +Aidez‑moi à maintenir **FastAPI** ! 🤓 -Là, vous pourriez m'offrir un café ☕️ pour me remercier 😄. +Il y a beaucoup de travail à faire, et pour la plupart, **VOUS** pouvez le faire. -## Sponsoriser les outils qui font fonctionner FastAPI +Les principales tâches que vous pouvez faire dès maintenant sont : -Comme vous l'avez vu dans la documentation, FastAPI se tient sur les épaules des géants, Starlette et Pydantic. +* [Aider les autres avec des questions sur GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (voir la section ci‑dessus). +* [Relire des Pull Requests](#review-pull-requests){.internal-link target=_blank} (voir la section ci‑dessus). -Vous pouvez également parrainer : +Ces deux tâches sont celles qui **consomment le plus de temps**. C'est le travail principal de la maintenance de FastAPI. -* Samuel Colvin (Pydantic) -* Encode (Starlette, Uvicorn) +Si vous pouvez m'aider avec cela, **vous m'aidez à maintenir FastAPI** et à vous assurer qu'il continue **d'avancer plus vite et mieux**. 🚀 + +## Rejoindre le chat { #join-the-chat } + +Rejoignez le 👥 serveur Discord 👥 et échangez avec d'autres membres de la communauté FastAPI. + +/// tip | Astuce + +Pour les questions, posez‑les dans GitHub Discussions, vous avez bien plus de chances de recevoir de l'aide par les [Experts FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. + +Utilisez le chat uniquement pour d'autres conversations générales. + +/// + +### N'utilisez pas le chat pour les questions { #dont-use-the-chat-for-questions } + +Gardez à l'esprit que, comme les chats permettent une « conversation libre », il est facile de poser des questions trop générales et plus difficiles à répondre ; vous pourriez donc ne pas recevoir de réponses. + +Sur GitHub, le modèle vous guidera pour rédiger la bonne question afin que vous puissiez plus facilement obtenir une bonne réponse, ou même résoudre le problème vous‑même avant de demander. Et sur GitHub, je peux m'assurer de toujours tout répondre, même si cela prend du temps. Je ne peux pas personnellement faire cela avec les systèmes de chat. 😅 + +Les conversations dans les systèmes de chat ne sont pas non plus aussi facilement recherchables que sur GitHub, donc les questions et réponses peuvent se perdre dans la conversation. Et seules celles sur GitHub comptent pour devenir un [Expert FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}, vous aurez donc très probablement plus d'attention sur GitHub. + +D'un autre côté, il y a des milliers d'utilisateurs dans les systèmes de chat, il y a donc de fortes chances que vous trouviez presque toujours quelqu'un avec qui parler. 😄 + +## Sponsoriser l'auteur { #sponsor-the-author } + +Si votre **produit/entreprise** dépend de **FastAPI** ou y est lié et que vous souhaitez atteindre ses utilisateurs, vous pouvez sponsoriser l'auteur (moi) via GitHub sponsors. Selon le niveau, vous pourriez obtenir des avantages supplémentaires, comme un badge dans la documentation. 🎁 --- diff --git a/docs/fr/docs/history-design-future.md b/docs/fr/docs/history-design-future.md index 15be545ee..300f2e0f5 100644 --- a/docs/fr/docs/history-design-future.md +++ b/docs/fr/docs/history-design-future.md @@ -1,4 +1,4 @@ -# Histoire, conception et avenir +# Histoire, conception et avenir { #history-design-and-future } Il y a quelque temps, un utilisateur de **FastAPI** a demandé : @@ -6,7 +6,7 @@ Il y a quelque temps, @@ -28,7 +28,7 @@ Mais à un moment donné, il n'y avait pas d'autre option que de créer quelque -## Recherche +## Recherche { #investigation } En utilisant toutes les alternatives précédentes, j'ai eu la chance d'apprendre de toutes, de prendre des idées, et de les combiner de la meilleure façon que j'ai pu trouver pour moi-même et les équipes de développeurs avec lesquelles j'ai travaillé. @@ -38,9 +38,9 @@ De plus, la meilleure approche était d'utiliser des normes déjà existantes. Ainsi, avant même de commencer à coder **FastAPI**, j'ai passé plusieurs mois à étudier les spécifications d'OpenAPI, JSON Schema, OAuth2, etc. Comprendre leurs relations, leurs similarités et leurs différences. -## Conception +## Conception { #design } -Ensuite, j'ai passé du temps à concevoir l'"API" de développeur que je voulais avoir en tant qu'utilisateur (en tant que développeur utilisant FastAPI). +Ensuite, j'ai passé du temps à concevoir l'« API » de développeur que je voulais avoir en tant qu'utilisateur (en tant que développeur utilisant FastAPI). J'ai testé plusieurs idées dans les éditeurs Python les plus populaires : PyCharm, VS Code, les éditeurs basés sur Jedi. @@ -48,11 +48,11 @@ D'après la dernière **Pydantic** pour ses avantages. @@ -60,11 +60,11 @@ J'y ai ensuite contribué, pour le rendre entièrement compatible avec JSON Sche Pendant le développement, j'ai également contribué à **Starlette**, l'autre exigence clé. -## Développement +## Développement { #development } Au moment où j'ai commencé à créer **FastAPI** lui-même, la plupart des pièces étaient déjà en place, la conception était définie, les exigences et les outils étaient prêts, et la connaissance des normes et des spécifications était claire et fraîche. -## Futur +## Futur { #future } À ce stade, il est déjà clair que **FastAPI** et ses idées sont utiles pour de nombreuses personnes. @@ -76,4 +76,4 @@ Mais il y a encore de nombreuses améliorations et fonctionnalités à venir. **FastAPI** a un grand avenir devant lui. -Et [votre aide](help-fastapi.md){.internal-link target=\_blank} est grandement appréciée. +Et [votre aide](help-fastapi.md){.internal-link target=_blank} est grandement appréciée. diff --git a/docs/fr/docs/how-to/authentication-error-status-code.md b/docs/fr/docs/how-to/authentication-error-status-code.md new file mode 100644 index 000000000..b8e87ee71 --- /dev/null +++ b/docs/fr/docs/how-to/authentication-error-status-code.md @@ -0,0 +1,17 @@ +# Utiliser les anciens codes d'erreur d'authentification 403 { #use-old-403-authentication-error-status-codes } + +Avant FastAPI version `0.122.0`, lorsque les utilitaires de sécurité intégrés renvoyaient une erreur au client après un échec d'authentification, ils utilisaient le code d'état HTTP `403 Forbidden`. + +À partir de FastAPI version `0.122.0`, ils utilisent le code d'état HTTP plus approprié `401 Unauthorized`, et renvoient un en-tête `WWW-Authenticate` pertinent dans la réponse, conformément aux spécifications HTTP, RFC 7235, RFC 9110. + +Mais si, pour une raison quelconque, vos clients dépendent de l'ancien comportement, vous pouvez y revenir en surchargeant la méthode `make_not_authenticated_error` dans vos classes de sécurité. + +Par exemple, vous pouvez créer une sous-classe de `HTTPBearer` qui renvoie une erreur `403 Forbidden` au lieu de l'erreur par défaut `401 Unauthorized` : + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} + +/// tip | Astuce + +Remarquez que la fonction renvoie l'instance de l'exception, elle ne la lève pas. La levée est effectuée dans le reste du code interne. + +/// diff --git a/docs/fr/docs/how-to/conditional-openapi.md b/docs/fr/docs/how-to/conditional-openapi.md new file mode 100644 index 000000000..61aa187cc --- /dev/null +++ b/docs/fr/docs/how-to/conditional-openapi.md @@ -0,0 +1,56 @@ +# Configurer OpenAPI de manière conditionnelle { #conditional-openapi } + +Si nécessaire, vous pouvez utiliser des paramètres et des variables d'environnement pour configurer OpenAPI de manière conditionnelle selon l'environnement, et même le désactiver complètement. + +## À propos de la sécurité, des API et de la documentation { #about-security-apis-and-docs } + +Masquer vos interfaces utilisateur de la documentation en production ne devrait pas être la manière de protéger votre API. + +Cela n'ajoute aucune sécurité supplémentaire à votre API, les *chemins d'accès* resteront disponibles là où ils se trouvent. + +S'il y a une faille de sécurité dans votre code, elle existera toujours. + +Masquer la documentation rend simplement plus difficile la compréhension de la manière d'interagir avec votre API et pourrait aussi rendre son débogage en production plus difficile. Cela pourrait être considéré simplement comme une forme de Sécurité par l'obscurité. + +Si vous voulez sécuriser votre API, il y a plusieurs meilleures approches possibles, par exemple : + +* Vous devez vous assurer d'avoir des modèles Pydantic bien définis pour le corps de la requête et la réponse. +* Configurez toutes les autorisations et tous les rôles nécessaires à l'aide de dépendances. +* Ne stockez jamais de mots de passe en clair, seulement des hachages de mots de passe. +* Implémentez et utilisez des outils cryptographiques reconnus, comme pwdlib et des jetons JWT, ... etc. +* Ajoutez des contrôles d'autorisation plus granulaires avec des scopes OAuth2 lorsque nécessaire. +* ... etc. + +Néanmoins, vous pourriez avoir un cas d'utilisation très spécifique où vous devez vraiment désactiver la documentation de l'API pour un certain environnement (par exemple pour la production) ou selon des configurations provenant de variables d'environnement. + +## Configurer OpenAPI de manière conditionnelle avec des paramètres et des variables d'environnement { #conditional-openapi-from-settings-and-env-vars } + +Vous pouvez facilement utiliser les mêmes paramètres Pydantic pour configurer votre OpenAPI généré et les interfaces utilisateur de la documentation. + +Par exemple : + +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} + +Ici nous déclarons le paramètre `openapi_url` avec la même valeur par défaut `"/openapi.json"`. + +Nous l'utilisons ensuite lors de la création de l'application `FastAPI`. + +Vous pouvez alors désactiver OpenAPI (y compris les interfaces utilisateur de la documentation) en définissant la variable d'environnement `OPENAPI_URL` sur la chaîne vide, comme ceci : + +
+ +```console +$ OPENAPI_URL= uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Ensuite, si vous allez aux URL `/openapi.json`, `/docs` ou `/redoc`, vous obtiendrez simplement une erreur `404 Not Found` comme : + +```JSON +{ + "detail": "Not Found" +} +``` diff --git a/docs/fr/docs/how-to/configure-swagger-ui.md b/docs/fr/docs/how-to/configure-swagger-ui.md new file mode 100644 index 000000000..73d0f00e8 --- /dev/null +++ b/docs/fr/docs/how-to/configure-swagger-ui.md @@ -0,0 +1,70 @@ +# Configurer Swagger UI { #configure-swagger-ui } + +Vous pouvez configurer des paramètres supplémentaires de Swagger UI. + +Pour les configurer, passez l'argument `swagger_ui_parameters` lors de la création de l'objet d'application `FastAPI()` ou à la fonction `get_swagger_ui_html()`. + +`swagger_ui_parameters` reçoit un dictionnaire avec les configurations passées directement à Swagger UI. + +FastAPI convertit les configurations en **JSON** pour les rendre compatibles avec JavaScript, car c'est ce dont Swagger UI a besoin. + +## Désactiver la coloration syntaxique { #disable-syntax-highlighting } + +Par exemple, vous pourriez désactiver la coloration syntaxique dans Swagger UI. + +Sans modifier les paramètres, la coloration syntaxique est activée par défaut : + + + +Mais vous pouvez la désactiver en définissant `syntaxHighlight` à `False` : + +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} + +... et ensuite Swagger UI n'affichera plus la coloration syntaxique : + + + +## Modifier le thème { #change-the-theme } + +De la même manière, vous pouvez définir le thème de la coloration syntaxique avec la clé « syntaxHighlight.theme » (remarquez le point au milieu) : + +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} + +Cette configuration modifierait le thème de couleurs de la coloration syntaxique : + + + +## Modifier les paramètres Swagger UI par défaut { #change-default-swagger-ui-parameters } + +FastAPI inclut des paramètres de configuration par défaut adaptés à la plupart des cas d'utilisation. + +Il inclut ces configurations par défaut : + +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} + +Vous pouvez remplacer n'importe lequel d'entre eux en définissant une valeur différente dans l'argument `swagger_ui_parameters`. + +Par exemple, pour désactiver `deepLinking`, vous pourriez passer ces paramètres à `swagger_ui_parameters` : + +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} + +## Autres paramètres de Swagger UI { #other-swagger-ui-parameters } + +Pour voir toutes les autres configurations possibles que vous pouvez utiliser, lisez la documentation officielle des paramètres de Swagger UI. + +## Paramètres JavaScript uniquement { #javascript-only-settings } + +Swagger UI permet également d'autres configurations qui sont des objets réservés à JavaScript (par exemple, des fonctions JavaScript). + +FastAPI inclut aussi ces paramètres `presets` réservés à JavaScript : + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +Ce sont des objets **JavaScript**, pas des chaînes, vous ne pouvez donc pas les passer directement depuis du code Python. + +Si vous devez utiliser des configurations réservées à JavaScript comme celles-ci, vous pouvez utiliser l'une des méthodes ci-dessus. Surchargez entièrement le *chemin d'accès* Swagger UI et écrivez manuellement tout JavaScript nécessaire. diff --git a/docs/fr/docs/how-to/custom-docs-ui-assets.md b/docs/fr/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 000000000..d239a9696 --- /dev/null +++ b/docs/fr/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,185 @@ +# Héberger en propre les ressources statiques de l’UI des docs personnalisées { #custom-docs-ui-static-assets-self-hosting } + +Les documents de l’API utilisent **Swagger UI** et **ReDoc**, et chacune nécessite des fichiers JavaScript et CSS. + +Par défaut, ces fichiers sont servis depuis un CDN. + +Mais il est possible de le personnaliser : vous pouvez définir un CDN spécifique, ou servir vous‑même les fichiers. + +## Configurer un CDN personnalisé pour JavaScript et CSS { #custom-cdn-for-javascript-and-css } + +Supposons que vous souhaitiez utiliser un autre CDN, par exemple vous voulez utiliser `https://unpkg.com/`. + +Cela peut être utile si, par exemple, vous vivez dans un pays qui restreint certaines URL. + +### Désactiver les docs automatiques { #disable-the-automatic-docs } + +La première étape consiste à désactiver les docs automatiques, car par défaut elles utilisent le CDN par défaut. + +Pour les désactiver, définissez leurs URL sur `None` lors de la création de votre application `FastAPI` : + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} + +### Inclure les docs personnalisées { #include-the-custom-docs } + +Vous pouvez maintenant créer les chemins d'accès pour les docs personnalisées. + +Vous pouvez réutiliser les fonctions internes de FastAPI pour créer les pages HTML de la documentation et leur passer les arguments nécessaires : + +- `openapi_url` : l’URL où la page HTML des docs peut récupérer le schéma OpenAPI de votre API. Vous pouvez utiliser ici l’attribut `app.openapi_url`. +- `title` : le titre de votre API. +- `oauth2_redirect_url` : vous pouvez utiliser `app.swagger_ui_oauth2_redirect_url` ici pour utiliser la valeur par défaut. +- `swagger_js_url` : l’URL où la page HTML de Swagger UI peut récupérer le fichier **JavaScript**. C’est l’URL du CDN personnalisé. +- `swagger_css_url` : l’URL où la page HTML de Swagger UI peut récupérer le fichier **CSS**. C’est l’URL du CDN personnalisé. + +Et de même pour ReDoc ... + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} + +/// tip | Astuce + +Le chemin d'accès pour `swagger_ui_redirect` est un assistant lorsque vous utilisez OAuth2. + +Si vous intégrez votre API à un fournisseur OAuth2, vous pourrez vous authentifier et revenir aux docs de l’API avec les identifiants acquis. Et interagir avec elle en utilisant la véritable authentification OAuth2. + +Swagger UI s’en chargera en arrière‑plan pour vous, mais il a besoin de cet assistant « redirect ». + +/// + +### Créer un chemin d'accès pour tester { #create-a-path-operation-to-test-it } + +Maintenant, pour pouvoir vérifier que tout fonctionne, créez un chemin d'accès : + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} + +### Tester { #test-it } + +Vous devriez maintenant pouvoir aller à vos docs sur http://127.0.0.1:8000/docs, puis recharger la page : elle chargera ces ressources depuis le nouveau CDN. + +## Héberger en propre JavaScript et CSS pour les docs { #self-hosting-javascript-and-css-for-docs } + +Héberger vous‑même le JavaScript et le CSS peut être utile si, par exemple, votre application doit continuer de fonctionner même hors ligne, sans accès Internet ouvert, ou sur un réseau local. + +Vous verrez ici comment servir ces fichiers vous‑même, dans la même application FastAPI, et configurer les docs pour les utiliser. + +### Structure des fichiers du projet { #project-file-structure } + +Supposons que la structure de vos fichiers de projet ressemble à ceci : + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +Créez maintenant un répertoire pour stocker ces fichiers statiques. + +Votre nouvelle structure de fichiers pourrait ressembler à ceci : + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### Télécharger les fichiers { #download-the-files } + +Téléchargez les fichiers statiques nécessaires aux docs et placez‑les dans ce répertoire `static/`. + +Vous pouvez probablement cliquer avec le bouton droit sur chaque lien et choisir une option similaire à « Enregistrer le lien sous ... ». + +**Swagger UI** utilise les fichiers : + +- `swagger-ui-bundle.js` +- `swagger-ui.css` + +Et **ReDoc** utilise le fichier : + +- `redoc.standalone.js` + +Après cela, votre structure de fichiers pourrait ressembler à : + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### Servir les fichiers statiques { #serve-the-static-files } + +- Importer `StaticFiles`. +- « Monter » une instance `StaticFiles()` sur un chemin spécifique. + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} + +### Tester les fichiers statiques { #test-the-static-files } + +Démarrez votre application et rendez‑vous sur http://127.0.0.1:8000/static/redoc.standalone.js. + +Vous devriez voir un très long fichier JavaScript pour **ReDoc**. + +Il pourrait commencer par quelque chose comme : + +```JavaScript +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): +... +``` + +Cela confirme que vous parvenez à servir des fichiers statiques depuis votre application et que vous avez placé les fichiers statiques des docs au bon endroit. + +Nous pouvons maintenant configurer l’application pour utiliser ces fichiers statiques pour les docs. + +### Désactiver les docs automatiques pour les fichiers statiques { #disable-the-automatic-docs-for-static-files } + +Comme lors de l’utilisation d’un CDN personnalisé, la première étape consiste à désactiver les docs automatiques, car elles utilisent un CDN par défaut. + +Pour les désactiver, définissez leurs URL sur `None` lors de la création de votre application `FastAPI` : + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} + +### Inclure les docs personnalisées pour les fichiers statiques { #include-the-custom-docs-for-static-files } + +Et comme avec un CDN personnalisé, vous pouvez maintenant créer les chemins d'accès pour les docs personnalisées. + +Là encore, vous pouvez réutiliser les fonctions internes de FastAPI pour créer les pages HTML de la documentation et leur passer les arguments nécessaires : + +- `openapi_url` : l’URL où la page HTML des docs peut récupérer le schéma OpenAPI de votre API. Vous pouvez utiliser ici l’attribut `app.openapi_url`. +- `title` : le titre de votre API. +- `oauth2_redirect_url` : vous pouvez utiliser `app.swagger_ui_oauth2_redirect_url` ici pour utiliser la valeur par défaut. +- `swagger_js_url` : l’URL où la page HTML de Swagger UI peut récupérer le fichier **JavaScript**. **C’est celui que votre propre application sert désormais**. +- `swagger_css_url` : l’URL où la page HTML de Swagger UI peut récupérer le fichier **CSS**. **C’est celui que votre propre application sert désormais**. + +Et de même pour ReDoc ... + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} + +/// tip | Astuce + +Le chemin d'accès pour `swagger_ui_redirect` est un assistant lorsque vous utilisez OAuth2. + +Si vous intégrez votre API à un fournisseur OAuth2, vous pourrez vous authentifier et revenir aux docs de l’API avec les identifiants acquis. Et interagir avec elle en utilisant la véritable authentification OAuth2. + +Swagger UI s’en chargera en arrière‑plan pour vous, mais il a besoin de cet assistant « redirect ». + +/// + +### Créer un chemin d'accès pour tester les fichiers statiques { #create-a-path-operation-to-test-static-files } + +Maintenant, pour pouvoir vérifier que tout fonctionne, créez un chemin d'accès : + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} + +### Tester l’UI avec des fichiers statiques { #test-static-files-ui } + +Vous devriez maintenant pouvoir couper votre Wi‑Fi, aller à vos docs sur http://127.0.0.1:8000/docs et recharger la page. + +Et même sans Internet, vous pourrez voir la documentation de votre API et interagir avec elle. diff --git a/docs/fr/docs/how-to/custom-request-and-route.md b/docs/fr/docs/how-to/custom-request-and-route.md new file mode 100644 index 000000000..506187d9f --- /dev/null +++ b/docs/fr/docs/how-to/custom-request-and-route.md @@ -0,0 +1,109 @@ +# Personnaliser les classes Request et APIRoute { #custom-request-and-apiroute-class } + +Dans certains cas, vous pouvez vouloir surcharger la logique utilisée par les classes `Request` et `APIRoute`. + +En particulier, cela peut être une bonne alternative à une logique dans un middleware. + +Par exemple, si vous voulez lire ou manipuler le corps de la requête avant qu'il ne soit traité par votre application. + +/// danger | Danger + +Ceci est une fonctionnalité « avancée ». + +Si vous débutez avec **FastAPI**, vous pouvez ignorer cette section. + +/// + +## Cas d'utilisation { #use-cases } + +Voici quelques cas d'utilisation : + +* Convertir des corps de requête non JSON en JSON (par exemple `msgpack`). +* Décompresser des corps de requête compressés en gzip. +* Journaliser automatiquement tous les corps de requête. + +## Gérer les encodages personnalisés du corps de la requête { #handling-custom-request-body-encodings } + +Voyons comment utiliser une sous-classe personnalisée de `Request` pour décompresser des requêtes gzip. + +Et une sous-classe d'`APIRoute` pour utiliser cette classe de requête personnalisée. + +### Créer une classe `GzipRequest` personnalisée { #create-a-custom-gziprequest-class } + +/// tip | Astuce + +Il s'agit d'un exemple simplifié pour montrer le fonctionnement ; si vous avez besoin de la prise en charge de Gzip, vous pouvez utiliser le [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank} fourni. + +/// + +Commencez par créer une classe `GzipRequest`, qui va surcharger la méthode `Request.body()` pour décompresser le corps en présence d'un en-tête approprié. + +S'il n'y a pas `gzip` dans l'en-tête, elle n'essaiera pas de décompresser le corps. + +De cette manière, la même classe de route peut gérer des requêtes gzip compressées ou non compressées. + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} + +### Créer une classe `GzipRoute` personnalisée { #create-a-custom-gziproute-class } + +Ensuite, nous créons une sous-classe personnalisée de `fastapi.routing.APIRoute` qui utilisera `GzipRequest`. + +Cette fois, elle va surcharger la méthode `APIRoute.get_route_handler()`. + +Cette méthode renvoie une fonction. Et c'est cette fonction qui recevra une requête et retournera une réponse. + +Ici, nous l'utilisons pour créer une `GzipRequest` à partir de la requête originale. + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} + +/// note | Détails techniques + +Un `Request` possède un attribut `request.scope`, qui n'est qu'un `dict` Python contenant les métadonnées liées à la requête. + +Un `Request` a également un `request.receive`, qui est une fonction pour « recevoir » le corps de la requête. + +Le `dict` `scope` et la fonction `receive` font tous deux partie de la spécification ASGI. + +Et ces deux éléments, `scope` et `receive`, sont ce dont on a besoin pour créer une nouvelle instance de `Request`. + +Pour en savoir plus sur `Request`, consultez la documentation de Starlette sur les requêtes. + +/// + +La seule chose que fait différemment la fonction renvoyée par `GzipRequest.get_route_handler`, c'est de convertir la `Request` en `GzipRequest`. + +Ce faisant, notre `GzipRequest` se chargera de décompresser les données (si nécessaire) avant de les transmettre à nos *chemins d'accès*. + +Après cela, toute la logique de traitement est identique. + +Mais grâce à nos modifications dans `GzipRequest.body`, le corps de la requête sera automatiquement décompressé lorsque **FastAPI** le chargera, si nécessaire. + +## Accéder au corps de la requête dans un gestionnaire d'exceptions { #accessing-the-request-body-in-an-exception-handler } + +/// tip | Astuce + +Pour résoudre ce même problème, il est probablement beaucoup plus simple d'utiliser `body` dans un gestionnaire personnalisé pour `RequestValidationError` ([Gérer les erreurs](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). + +Mais cet exemple reste valable et montre comment interagir avec les composants internes. + +/// + +Nous pouvons également utiliser cette même approche pour accéder au corps de la requête dans un gestionnaire d'exceptions. + +Il suffit de traiter la requête dans un bloc `try`/`except` : + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} + +Si une exception se produit, l'instance de `Request` sera toujours dans la portée, ce qui nous permet de lire et d'utiliser le corps de la requête lors du traitement de l'erreur : + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} + +## Utiliser une classe `APIRoute` personnalisée dans un routeur { #custom-apiroute-class-in-a-router } + +Vous pouvez également définir le paramètre `route_class` d'un `APIRouter` : + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} + +Dans cet exemple, les *chemins d'accès* sous le `router` utiliseront la classe personnalisée `TimedRoute`, et auront un en-tête supplémentaire `X-Response-Time` dans la réponse avec le temps nécessaire pour générer la réponse : + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} diff --git a/docs/fr/docs/how-to/extending-openapi.md b/docs/fr/docs/how-to/extending-openapi.md new file mode 100644 index 000000000..1c540ea6c --- /dev/null +++ b/docs/fr/docs/how-to/extending-openapi.md @@ -0,0 +1,80 @@ +# Étendre OpenAPI { #extending-openapi } + +Il existe des cas où vous pouvez avoir besoin de modifier le schéma OpenAPI généré. + +Dans cette section, vous verrez comment faire. + +## Le processus normal { #the-normal-process } + +Le processus normal (par défaut) est le suivant. + +Une application (instance) `FastAPI` a une méthode `.openapi()` censée retourner le schéma OpenAPI. + +Lors de la création de l'objet application, un *chemin d'accès* pour `/openapi.json` (ou pour l'URL que vous avez définie dans votre `openapi_url`) est enregistré. + +Il renvoie simplement une réponse JSON avec le résultat de la méthode `.openapi()` de l'application. + +Par défaut, la méthode `.openapi()` vérifie la propriété `.openapi_schema` pour voir si elle contient des données et les renvoie. + +Sinon, elle les génère à l'aide de la fonction utilitaire `fastapi.openapi.utils.get_openapi`. + +Et cette fonction `get_openapi()` reçoit comme paramètres : + +* `title` : Le titre OpenAPI, affiché dans les documents. +* `version` : La version de votre API, p. ex. `2.5.0`. +* `openapi_version` : La version de la spécification OpenAPI utilisée. Par défaut, la plus récente : `3.1.0`. +* `summary` : Un court résumé de l'API. +* `description` : La description de votre API ; elle peut inclure du markdown et sera affichée dans la documentation. +* `routes` : Une liste de routes ; chacune correspond à un *chemin d'accès* enregistré. Elles sont extraites de `app.routes`. + +/// info + +Le paramètre `summary` est disponible à partir d'OpenAPI 3.1.0, pris en charge par FastAPI 0.99.0 et versions ultérieures. + +/// + +## Remplacer les valeurs par défaut { #overriding-the-defaults } + +En vous appuyant sur les informations ci-dessus, vous pouvez utiliser la même fonction utilitaire pour générer le schéma OpenAPI et remplacer chaque partie dont vous avez besoin. + +Par exemple, ajoutons l’extension OpenAPI de ReDoc pour inclure un logo personnalisé. + +### **FastAPI** normal { #normal-fastapi } + +Tout d’abord, écrivez votre application **FastAPI** comme d’habitude : + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *} + +### Générer le schéma OpenAPI { #generate-the-openapi-schema } + +Ensuite, utilisez la même fonction utilitaire pour générer le schéma OpenAPI, dans une fonction `custom_openapi()` : + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *} + +### Modifier le schéma OpenAPI { #modify-the-openapi-schema } + +Vous pouvez maintenant ajouter l’extension ReDoc, en ajoutant un `x-logo` personnalisé à l’« objet » `info` dans le schéma OpenAPI : + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *} + +### Mettre en cache le schéma OpenAPI { #cache-the-openapi-schema } + +Vous pouvez utiliser la propriété `.openapi_schema` comme « cache » pour stocker votre schéma généré. + +Ainsi, votre application n’aura pas à générer le schéma à chaque fois qu’un utilisateur ouvre les documents de votre API. + +Il ne sera généré qu’une seule fois, puis le même schéma en cache sera utilisé pour les requêtes suivantes. + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *} + +### Remplacer la méthode { #override-the-method } + +Vous pouvez maintenant remplacer la méthode `.openapi()` par votre nouvelle fonction. + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *} + +### Vérifier { #check-it } + +Une fois que vous allez sur http://127.0.0.1:8000/redoc, vous verrez que vous utilisez votre logo personnalisé (dans cet exemple, le logo de **FastAPI**) : + + diff --git a/docs/fr/docs/how-to/general.md b/docs/fr/docs/how-to/general.md new file mode 100644 index 000000000..09d4498ac --- /dev/null +++ b/docs/fr/docs/how-to/general.md @@ -0,0 +1,39 @@ +# Général - Guides pratiques - Recettes { #general-how-to-recipes } + +Voici plusieurs renvois vers d'autres endroits de la documentation, pour des questions générales ou fréquentes. + +## Filtrer des données - Sécurité { #filter-data-security } + +Pour vous assurer que vous ne renvoyez pas plus de données que nécessaire, lisez la documentation [Tutoriel - Modèle de réponse - Type de retour](../tutorial/response-model.md){.internal-link target=_blank}. + +## Étiquettes de documentation - OpenAPI { #documentation-tags-openapi } + +Pour ajouter des étiquettes à vos *chemins d'accès* et les regrouper dans l'interface utilisateur de la documentation, lisez la documentation [Tutoriel - Configurations de chemin d'accès - Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. + +## Résumé et description de la documentation - OpenAPI { #documentation-summary-and-description-openapi } + +Pour ajouter un résumé et une description à vos *chemins d'accès* et les afficher dans l'interface utilisateur de la documentation, lisez la documentation [Tutoriel - Configurations de chemin d'accès - Résumé et description](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. + +## Description de la réponse dans la documentation - OpenAPI { #documentation-response-description-openapi } + +Pour définir la description de la réponse, affichée dans l'interface utilisateur de la documentation, lisez la documentation [Tutoriel - Configurations de chemin d'accès - Description de la réponse](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. + +## Déprécier un *chemin d'accès* dans la documentation - OpenAPI { #documentation-deprecate-a-path-operation-openapi } + +Pour déprécier un *chemin d'accès* et l'indiquer dans l'interface utilisateur de la documentation, lisez la documentation [Tutoriel - Configurations de chemin d'accès - Déprécier un chemin d'accès](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. + +## Convertir n'importe quelles données au format compatible JSON { #convert-any-data-to-json-compatible } + +Pour convertir des données vers un format compatible JSON, lisez la documentation [Tutoriel - Encodeur compatible JSON](../tutorial/encoder.md){.internal-link target=_blank}. + +## Métadonnées OpenAPI - Documentation { #openapi-metadata-docs } + +Pour ajouter des métadonnées à votre schéma OpenAPI, y compris une licence, une version, un contact, etc., lisez la documentation [Tutoriel - Métadonnées et URLs de la documentation](../tutorial/metadata.md){.internal-link target=_blank}. + +## URL OpenAPI personnalisée { #openapi-custom-url } + +Pour personnaliser l'URL OpenAPI (ou la supprimer), lisez la documentation [Tutoriel - Métadonnées et URLs de la documentation](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. + +## URL de la documentation OpenAPI { #openapi-docs-urls } + +Pour mettre à jour les URL utilisées pour les interfaces utilisateur de documentation générées automatiquement, lisez la documentation [Tutoriel - Métadonnées et URLs de la documentation](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. diff --git a/docs/fr/docs/how-to/graphql.md b/docs/fr/docs/how-to/graphql.md new file mode 100644 index 000000000..59cd1590f --- /dev/null +++ b/docs/fr/docs/how-to/graphql.md @@ -0,0 +1,60 @@ +# GraphQL { #graphql } + +Comme **FastAPI** est basé sur la norme **ASGI**, il est très facile d'intégrer toute bibliothèque **GraphQL** également compatible avec ASGI. + +Vous pouvez combiner des *chemins d'accès* FastAPI classiques avec GraphQL dans la même application. + +/// tip | Astuce + +**GraphQL** résout des cas d'utilisation très spécifiques. + +Il présente des **avantages** et des **inconvénients** par rapport aux **API web** classiques. + +Assurez-vous d'évaluer si les **bénéfices** pour votre cas d'utilisation compensent les **inconvénients**. 🤓 + +/// + +## Bibliothèques GraphQL { #graphql-libraries } + +Voici quelques bibliothèques **GraphQL** qui prennent en charge **ASGI**. Vous pouvez les utiliser avec **FastAPI** : + +* Strawberry 🍓 + * Avec la documentation pour FastAPI +* Ariadne + * Avec la documentation pour FastAPI +* Tartiflette + * Avec Tartiflette ASGI pour fournir l'intégration ASGI +* Graphene + * Avec starlette-graphene3 + +## GraphQL avec Strawberry { #graphql-with-strawberry } + +Si vous avez besoin ou souhaitez travailler avec **GraphQL**, **Strawberry** est la bibliothèque **recommandée** car sa conception est la plus proche de celle de **FastAPI**, tout est basé sur des **annotations de type**. + +Selon votre cas d'utilisation, vous pourriez préférer une autre bibliothèque, mais si vous me le demandiez, je vous suggérerais probablement d'essayer **Strawberry**. + +Voici un petit aperçu de la manière dont vous pouvez intégrer Strawberry avec FastAPI : + +{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *} + +Vous pouvez en apprendre davantage sur Strawberry dans la documentation de Strawberry. + +Et également la documentation sur Strawberry avec FastAPI. + +## Ancien `GraphQLApp` de Starlette { #older-graphqlapp-from-starlette } + +Les versions précédentes de Starlette incluaient une classe `GraphQLApp` pour s'intégrer à Graphene. + +Elle a été dépréciée dans Starlette, mais si vous avez du code qui l'utilisait, vous pouvez facilement **migrer** vers starlette-graphene3, qui couvre le même cas d'utilisation et propose une **interface presque identique**. + +/// tip | Astuce + +Si vous avez besoin de GraphQL, je vous recommande tout de même de regarder Strawberry, car il est basé sur des annotations de type plutôt que sur des classes et types personnalisés. + +/// + +## En savoir plus { #learn-more } + +Vous pouvez en apprendre davantage sur **GraphQL** dans la documentation officielle de GraphQL. + +Vous pouvez également en lire davantage sur chacune des bibliothèques décrites ci-dessus via leurs liens. diff --git a/docs/fr/docs/how-to/index.md b/docs/fr/docs/how-to/index.md new file mode 100644 index 000000000..03736fa43 --- /dev/null +++ b/docs/fr/docs/how-to/index.md @@ -0,0 +1,13 @@ +# Comment faire - Recettes { #how-to-recipes } + +Vous trouverez ici différentes recettes ou des guides « comment faire » pour **plusieurs sujets**. + +La plupart de ces idées sont plus ou moins **indépendantes**, et dans la plupart des cas vous n'avez besoin de les étudier que si elles s'appliquent directement à **votre projet**. + +Si quelque chose vous paraît intéressant et utile pour votre projet, allez-y et consultez-le ; sinon, vous pouvez probablement simplement les ignorer. + +/// tip | Astuce + +Si vous voulez **apprendre FastAPI** de façon structurée (recommandé), allez lire le [Tutoriel - Guide utilisateur](../tutorial/index.md){.internal-link target=_blank} chapitre par chapitre à la place. + +/// diff --git a/docs/fr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/fr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md new file mode 100644 index 000000000..681cf697b --- /dev/null +++ b/docs/fr/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -0,0 +1,135 @@ +# Migrer de Pydantic v1 à Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 } + +Si vous avez une ancienne application FastAPI, vous utilisez peut-être Pydantic version 1. + +FastAPI version 0.100.0 prenait en charge soit Pydantic v1 soit v2. Il utilisait celle que vous aviez installée. + +FastAPI version 0.119.0 a introduit une prise en charge partielle de Pydantic v1 depuis l'intérieur de Pydantic v2 (comme `pydantic.v1`), pour faciliter la migration vers v2. + +FastAPI 0.126.0 a supprimé la prise en charge de Pydantic v1, tout en continuant à prendre en charge `pydantic.v1` pendant un certain temps. + +/// warning | Alertes + +L'équipe Pydantic a arrêté la prise en charge de Pydantic v1 pour les dernières versions de Python, à partir de Python 3.14. + +Cela inclut `pydantic.v1`, qui n'est plus pris en charge à partir de Python 3.14. + +Si vous souhaitez utiliser les dernières fonctionnalités de Python, vous devez vous assurer que vous utilisez Pydantic v2. + +/// + +Si vous avez une ancienne application FastAPI avec Pydantic v1, je vais vous montrer comment la migrer vers Pydantic v2, et les fonctionnalités de FastAPI 0.119.0 pour vous aider à une migration progressive. + +## Guide officiel { #official-guide } + +Pydantic propose un Guide de migration officiel de la v1 à la v2. + +Il inclut aussi ce qui a changé, comment les validations sont désormais plus correctes et strictes, les pièges possibles, etc. + +Vous pouvez le lire pour mieux comprendre ce qui a changé. + +## Tests { #tests } + +Vous devez vous assurer d'avoir des [tests](../tutorial/testing.md){.internal-link target=_blank} pour votre application et de les exécuter en intégration continue (CI). + +De cette façon, vous pouvez effectuer la mise à niveau et vous assurer que tout fonctionne toujours comme prévu. + +## `bump-pydantic` { #bump-pydantic } + +Dans de nombreux cas, lorsque vous utilisez des modèles Pydantic classiques sans personnalisations, vous pourrez automatiser la majeure partie du processus de migration de Pydantic v1 à Pydantic v2. + +Vous pouvez utiliser `bump-pydantic` de la même équipe Pydantic. + +Cet outil vous aidera à modifier automatiquement la majeure partie du code à adapter. + +Après cela, vous pouvez exécuter les tests et vérifier que tout fonctionne. Si c'est le cas, vous avez terminé. 😎 + +## Pydantic v1 dans v2 { #pydantic-v1-in-v2 } + +Pydantic v2 inclut tout Pydantic v1 sous la forme du sous-module `pydantic.v1`. Mais cela n'est plus pris en charge dans les versions au-delà de Python 3.13. + +Cela signifie que vous pouvez installer la dernière version de Pydantic v2 et importer/utiliser les anciens composants de Pydantic v1 depuis ce sous-module, comme si vous aviez l'ancien Pydantic v1 installé. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} + +### Prise en charge de FastAPI pour Pydantic v1 dans v2 { #fastapi-support-for-pydantic-v1-in-v2 } + +Depuis FastAPI 0.119.0, il existe également une prise en charge partielle de Pydantic v1 depuis l'intérieur de Pydantic v2, pour faciliter la migration vers v2. + +Vous pouvez donc mettre à niveau Pydantic vers la dernière version 2 et modifier les imports pour utiliser le sous-module `pydantic.v1`, et dans de nombreux cas cela fonctionnera tel quel. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} + +/// warning | Alertes + +Gardez à l'esprit que, puisque l'équipe Pydantic ne prend plus en charge Pydantic v1 dans les versions récentes de Python à partir de Python 3.14, l'utilisation de `pydantic.v1` n'est pas non plus prise en charge à partir de Python 3.14. + +/// + +### Pydantic v1 et v2 dans la même application { #pydantic-v1-and-v2-on-the-same-app } + +Pydantic ne prend pas en charge le fait d'avoir un modèle Pydantic v2 contenant des champs eux-mêmes définis comme des modèles Pydantic v1, et inversement. + +```mermaid +graph TB + subgraph "❌ Not Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V1Field["Pydantic v1 Model"] + end + subgraph V1["Pydantic v1 Model"] + V2Field["Pydantic v2 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +... mais vous pouvez avoir des modèles séparés utilisant Pydantic v1 et v2 dans la même application. + +```mermaid +graph TB + subgraph "✅ Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V2Field["Pydantic v2 Model"] + end + subgraph V1["Pydantic v1 Model"] + V1Field["Pydantic v1 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +Dans certains cas, il est même possible d'avoir des modèles Pydantic v1 et v2 dans le même chemin d'accès de votre application FastAPI : + +{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} + +Dans l'exemple ci-dessus, le modèle d'entrée est un modèle Pydantic v1 et le modèle de sortie (défini dans `response_model=ItemV2`) est un modèle Pydantic v2. + +### Paramètres Pydantic v1 { #pydantic-v1-parameters } + +Si vous devez utiliser certains des outils spécifiques à FastAPI pour les paramètres comme `Body`, `Query`, `Form`, etc., avec des modèles Pydantic v1, vous pouvez les importer depuis `fastapi.temp_pydantic_v1_params` le temps de terminer la migration vers Pydantic v2 : + +{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} + +### Migrer par étapes { #migrate-in-steps } + +/// tip | Astuce + +Essayez d'abord avec `bump-pydantic` ; si vos tests passent et que cela fonctionne, vous avez tout terminé en une seule commande. ✨ + +/// + +Si `bump-pydantic` ne fonctionne pas pour votre cas d'usage, vous pouvez utiliser la prise en charge des modèles Pydantic v1 et v2 dans la même application pour effectuer la migration vers Pydantic v2 progressivement. + +Vous pouvez d'abord mettre à niveau Pydantic pour utiliser la dernière version 2 et modifier les imports pour utiliser `pydantic.v1` pour tous vos modèles. + +Ensuite, vous pouvez commencer à migrer vos modèles de Pydantic v1 vers v2 par groupes, par étapes progressives. 🚶 diff --git a/docs/fr/docs/how-to/separate-openapi-schemas.md b/docs/fr/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 000000000..fd767d738 --- /dev/null +++ b/docs/fr/docs/how-to/separate-openapi-schemas.md @@ -0,0 +1,102 @@ +# Séparer les schémas OpenAPI pour l'entrée et la sortie ou non { #separate-openapi-schemas-for-input-and-output-or-not } + +Depuis la sortie de **Pydantic v2**, l'OpenAPI généré est un peu plus précis et **correct** qu'avant. 😎 + +En fait, dans certains cas, il y aura même **deux schémas JSON** dans OpenAPI pour le même modèle Pydantic, pour l'entrée et pour la sortie, selon s'ils ont des **valeurs par défaut**. + +Voyons comment cela fonctionne et comment le modifier si vous devez le faire. + +## Utiliser des modèles Pydantic pour l'entrée et la sortie { #pydantic-models-for-input-and-output } + +Supposons que vous ayez un modèle Pydantic avec des valeurs par défaut, comme celui‑ci : + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} + +### Modèle pour l'entrée { #model-for-input } + +Si vous utilisez ce modèle en entrée comme ici : + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} + +... alors, le champ `description` ne sera **pas requis**. Parce qu'il a une valeur par défaut de `None`. + +### Modèle d'entrée dans les documents { #input-model-in-docs } + +Vous pouvez le confirmer dans les documents, le champ `description` n'a pas d'**astérisque rouge**, il n'est pas indiqué comme requis : + +
+ +
+ +### Modèle pour la sortie { #model-for-output } + +Mais si vous utilisez le même modèle en sortie, comme ici : + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} + +... alors, comme `description` a une valeur par défaut, si vous ne retournez rien pour ce champ, il aura tout de même cette **valeur par défaut**. + +### Modèle pour les données de réponse en sortie { #model-for-output-response-data } + +Si vous interagissez avec les documents et vérifiez la réponse, même si le code n'a rien ajouté dans l'un des champs `description`, la réponse JSON contient la valeur par défaut (`null`) : + +
+ +
+ +Cela signifie qu'il aura **toujours une valeur**, simplement, parfois la valeur pourra être `None` (ou `null` en JSON). + +Cela signifie que les clients utilisant votre API n'ont pas à vérifier si la valeur existe ou non, ils peuvent **supposer que le champ sera toujours présent**, mais que, dans certains cas, il aura la valeur par défaut `None`. + +La manière de décrire cela dans OpenAPI est de marquer ce champ comme **requis**, car il sera toujours présent. + +Pour cette raison, le schéma JSON d'un modèle peut être différent selon qu'il est utilisé pour **l'entrée ou la sortie** : + +- pour **l'entrée**, `description` ne sera **pas requis** +- pour **la sortie**, il sera **requis** (et éventuellement `None`, ou en termes JSON, `null`) + +### Modèle de sortie dans les documents { #model-for-output-in-docs } + +Vous pouvez également vérifier le modèle de sortie dans les documents, **à la fois** `name` et `description` sont marqués comme **requis** avec un **astérisque rouge** : + +
+ +
+ +### Modèle pour l'entrée et la sortie dans les documents { #model-for-input-and-output-in-docs } + +Et si vous consultez tous les schémas disponibles (schémas JSON) dans OpenAPI, vous verrez qu'il y en a deux, un `Item-Input` et un `Item-Output`. + +Pour `Item-Input`, `description` n'est **pas requis**, il n'a pas d'astérisque rouge. + +Mais pour `Item-Output`, `description` est **requis**, il a un astérisque rouge. + +
+ +
+ +Avec cette fonctionnalité de **Pydantic v2**, la documentation de votre API est plus **précise**, et si vous avez des clients et SDKs générés automatiquement, ils seront eux aussi plus précis, avec une meilleure **expérience développeur** et davantage de cohérence. 🎉 + +## Ne pas séparer les schémas { #do-not-separate-schemas } + +Il existe des cas où vous pourriez vouloir avoir le **même schéma pour l'entrée et la sortie**. + +Le cas d'usage principal est probablement que vous avez déjà du code client/SDKs générés automatiquement et que vous ne souhaitez pas encore mettre à jour tout ce code client/ces SDKs générés automatiquement ; vous le ferez sans doute à un moment donné, mais peut‑être pas tout de suite. + +Dans ce cas, vous pouvez désactiver cette fonctionnalité dans **FastAPI**, avec le paramètre `separate_input_output_schemas=False`. + +/// info | info + +La prise en charge de `separate_input_output_schemas` a été ajoutée dans FastAPI `0.102.0`. 🤓 + +/// + +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} + +### Utiliser le même schéma pour les modèles d'entrée et de sortie dans les documents { #same-schema-for-input-and-output-models-in-docs } + +Désormais, il n'y aura qu'un seul schéma pour l'entrée et la sortie du modèle, uniquement `Item`, et `description` ne sera pas requis : + +
+ +
diff --git a/docs/fr/docs/how-to/testing-database.md b/docs/fr/docs/how-to/testing-database.md new file mode 100644 index 000000000..3179bc4c6 --- /dev/null +++ b/docs/fr/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# Tester une base de données { #testing-a-database } + +Vous pouvez étudier les bases de données, SQL et SQLModel dans les documents SQLModel. 🤓 + +Il existe un mini tutoriel sur l'utilisation de SQLModel avec FastAPI. ✨ + +Ce tutoriel comprend une section sur les tests des bases de données SQL. 😎 diff --git a/docs/fr/docs/index.md b/docs/fr/docs/index.md index 2aeaa1c69..bf4446b94 100644 --- a/docs/fr/docs/index.md +++ b/docs/fr/docs/index.md @@ -40,7 +40,7 @@ Les principales fonctionnalités sont : * **Rapide** : très hautes performances, au niveau de **NodeJS** et **Go** (grâce à Starlette et Pydantic). [L'un des frameworks Python les plus rapides](#performance). * **Rapide à coder** : augmente la vitesse de développement des fonctionnalités d'environ 200 % à 300 %. * * **Moins de bugs** : réduit d'environ 40 % les erreurs induites par le développeur. * -* **Intuitif** : excellente compatibilité avec les éditeurs. Autocomplétion partout. Moins de temps passé à déboguer. +* **Intuitif** : excellente compatibilité avec les éditeurs. Autocomplétion partout. Moins de temps passé à déboguer. * **Facile** : conçu pour être facile à utiliser et à apprendre. Moins de temps passé à lire les documents. * **Concis** : diminue la duplication de code. Plusieurs fonctionnalités à partir de chaque déclaration de paramètre. Moins de bugs. * **Robuste** : obtenez un code prêt pour la production. Avec une documentation interactive automatique. @@ -368,7 +368,7 @@ item: Item * La validation des données : * des erreurs automatiques et claires lorsque les données ne sont pas valides. * une validation même pour les objets JSON profondément imbriqués. -* Conversion des données d'entrée : venant du réseau vers les données et types Python. Lecture depuis : +* Conversion des données d'entrée : venant du réseau vers les données et types Python. Lecture depuis : * JSON. * Paramètres de chemin. * Paramètres de requête. @@ -376,7 +376,7 @@ item: Item * En-têtes. * Formulaires. * Fichiers. -* Conversion des données de sortie : conversion des données et types Python en données réseau (au format JSON) : +* Conversion des données de sortie : conversion des données et types Python en données réseau (au format JSON) : * Conversion des types Python (`str`, `int`, `float`, `bool`, `list`, etc). * Objets `datetime`. * Objets `UUID`. @@ -439,7 +439,7 @@ Pour un exemple plus complet comprenant plus de fonctionnalités, voir le d'injection de dépendances** très puissant et facile à utiliser. +* Un système **d'injection de dépendances** très puissant et facile à utiliser. * Sécurité et authentification, y compris la prise en charge de **OAuth2** avec des **JWT tokens** et l'authentification **HTTP Basic**. * Des techniques plus avancées (mais tout aussi faciles) pour déclarer des **modèles JSON profondément imbriqués** (grâce à Pydantic). * Intégration **GraphQL** avec Strawberry et d'autres bibliothèques. @@ -524,7 +524,7 @@ Utilisées par Starlette : * httpx - Obligatoire si vous souhaitez utiliser le `TestClient`. * jinja2 - Obligatoire si vous souhaitez utiliser la configuration de template par défaut. -* python-multipart - Obligatoire si vous souhaitez prendre en charge l’« parsing » de formulaires avec `request.form()`. +* python-multipart - Obligatoire si vous souhaitez prendre en charge l’« parsing » de formulaires avec `request.form()`. Utilisées par FastAPI : diff --git a/docs/fr/docs/learn/index.md b/docs/fr/docs/learn/index.md index 552687703..e595ecf78 100644 --- a/docs/fr/docs/learn/index.md +++ b/docs/fr/docs/learn/index.md @@ -2,4 +2,4 @@ Voici les sections introductives et les tutoriels pour apprendre **FastAPI**. -Vous pouvez considérer ceci comme un **livre**, un **cours**, la **méthode officielle** et recommandée pour apprendre FastAPI. 😎 +Vous pouvez considérer ceci comme un **livre**, un **cours**, la méthode **officielle** et recommandée pour apprendre FastAPI. 😎 diff --git a/docs/fr/docs/python-types.md b/docs/fr/docs/python-types.md index f393b0f5c..770f1514a 100644 --- a/docs/fr/docs/python-types.md +++ b/docs/fr/docs/python-types.md @@ -1,8 +1,8 @@ # Introduction aux types Python { #python-types-intro } -Python prend en charge des « type hints » (aussi appelées « annotations de type ») facultatives. +Python prend en charge des « annotations de type » (aussi appelées « type hints ») facultatives. -Ces « type hints » ou annotations sont une syntaxe spéciale qui permet de déclarer le type d'une variable. +Ces **« annotations de type »** sont une syntaxe spéciale qui permet de déclarer le type d'une variable. En déclarant les types de vos variables, les éditeurs et outils peuvent vous offrir un meilleur support. @@ -22,7 +22,7 @@ Si vous êtes un expert Python, et que vous savez déjà tout sur les annotation Commençons par un exemple simple : -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} Exécuter ce programme affiche : @@ -34,9 +34,9 @@ La fonction fait ce qui suit : * Prend un `first_name` et un `last_name`. * Convertit la première lettre de chacun en majuscule avec `title()`. -* Concatène ces deux valeurs avec un espace au milieu. +* Concatène ces deux valeurs avec un espace au milieu. -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### Modifier le code { #edit-it } @@ -78,7 +78,7 @@ C'est tout. Ce sont les « annotations de type » : -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} Ce n'est pas la même chose que de déclarer des valeurs par défaut, ce qui serait : @@ -106,7 +106,7 @@ Avec cela, vous pouvez faire défiler en voyant les options, jusqu'à trouver ce Regardez cette fonction, elle a déjà des annotations de type : -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} Comme l'éditeur connaît les types des variables, vous n'obtenez pas seulement l'autocomplétion, vous obtenez aussi des vérifications d'erreurs : @@ -114,7 +114,7 @@ Comme l'éditeur connaît les types des variables, vous n'obtenez pas seulement Vous savez maintenant qu'il faut corriger, convertir `age` en chaîne avec `str(age)` : -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} ## Déclarer des types { #declaring-types } @@ -133,29 +133,32 @@ Vous pouvez utiliser, par exemple : * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} -### Types génériques avec paramètres de type { #generic-types-with-type-parameters } +### Module `typing` { #typing-module } -Il existe certaines structures de données qui peuvent contenir d'autres valeurs, comme `dict`, `list`, `set` et `tuple`. Et les valeurs internes peuvent aussi avoir leur propre type. +Pour certains cas d'utilisation supplémentaires, vous pourriez avoir besoin d'importer certains éléments depuis le module standard `typing`, par exemple lorsque vous voulez déclarer que quelque chose a « n'importe quel type », vous pouvez utiliser `Any` depuis `typing` : -Ces types qui ont des types internes sont appelés types « génériques ». Et il est possible de les déclarer, même avec leurs types internes. +```python +from typing import Any -Pour déclarer ces types et les types internes, vous pouvez utiliser le module standard Python `typing`. Il existe spécifiquement pour prendre en charge ces annotations de type. -#### Versions plus récentes de Python { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -La syntaxe utilisant `typing` est compatible avec toutes les versions, de Python 3.6 aux plus récentes, y compris Python 3.9, Python 3.10, etc. +### Types génériques { #generic-types } -Au fur et à mesure que Python évolue, les versions plus récentes apportent un meilleur support pour ces annotations de type et dans de nombreux cas vous n'aurez même pas besoin d'importer et d'utiliser le module `typing` pour les déclarer. +Certains types peuvent prendre des « paramètres de type » entre crochets, pour définir leurs types internes, par exemple une « liste de chaînes » se déclarerait `list[str]`. -Si vous pouvez choisir une version plus récente de Python pour votre projet, vous pourrez profiter de cette simplicité supplémentaire. +Ces types qui peuvent prendre des paramètres de type sont appelés des **types génériques** ou **Generics**. -Dans toute la documentation, il y a des exemples compatibles avec chaque version de Python (lorsqu'il y a une différence). +Vous pouvez utiliser les mêmes types intégrés comme génériques (avec des crochets et des types à l'intérieur) : -Par exemple « Python 3.6+ » signifie que c'est compatible avec Python 3.6 ou supérieur (y compris 3.7, 3.8, 3.9, 3.10, etc.). Et « Python 3.9+ » signifie que c'est compatible avec Python 3.9 ou supérieur (y compris 3.10, etc). - -Si vous pouvez utiliser les dernières versions de Python, utilisez les exemples pour la dernière version, ils auront la meilleure et la plus simple syntaxe, par exemple, « Python 3.10+ ». +* `list` +* `tuple` +* `set` +* `dict` #### Liste { #list } @@ -167,9 +170,9 @@ Comme type, mettez `list`. Comme la liste est un type qui contient des types internes, mettez-les entre crochets : -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} -/// info +/// info | Info Ces types internes entre crochets sont appelés « paramètres de type ». @@ -193,7 +196,7 @@ Et pourtant, l'éditeur sait que c'est un `str` et fournit le support approprié Vous feriez la même chose pour déclarer des `tuple` et des `set` : -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} Cela signifie : @@ -208,7 +211,7 @@ Le premier paramètre de type est pour les clés du `dict`. Le second paramètre de type est pour les valeurs du `dict` : -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} Cela signifie : @@ -218,46 +221,22 @@ Cela signifie : #### Union { #union } -Vous pouvez déclarer qu'une variable peut être de plusieurs types, par exemple, un `int` ou un `str`. +Vous pouvez déclarer qu'une variable peut être **plusieurs types**, par exemple, un `int` ou un `str`. -Dans Python 3.6 et supérieur (y compris Python 3.10), vous pouvez utiliser le type `Union` de `typing` et mettre entre crochets les types possibles à accepter. +Pour le définir, vous utilisez la barre verticale (`|`) pour séparer les deux types. -Dans Python 3.10, il existe aussi une nouvelle syntaxe où vous pouvez mettre les types possibles séparés par une barre verticale (`|`). - -//// tab | Python 3.10+ +C'est ce qu'on appelle une « union », car la variable peut être n'importe quoi dans l'union de ces deux ensembles de types. ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -Dans les deux cas, cela signifie que `item` peut être un `int` ou un `str`. +Cela signifie que `item` peut être un `int` ou un `str`. #### Possiblement `None` { #possibly-none } Vous pouvez déclarer qu'une valeur peut avoir un type, comme `str`, mais qu'elle peut aussi être `None`. -Dans Python 3.6 et supérieur (y compris Python 3.10), vous pouvez le déclarer en important et en utilisant `Optional` depuis le module `typing`. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -Utiliser `Optional[str]` au lieu de simplement `str` permettra à l'éditeur de vous aider à détecter des erreurs où vous supposeriez qu'une valeur est toujours un `str`, alors qu'elle pourrait en fait aussi être `None`. - -`Optional[Something]` est en réalité un raccourci pour `Union[Something, None]`, ils sont équivalents. - -Cela signifie aussi que dans Python 3.10, vous pouvez utiliser `Something | None` : - //// tab | Python 3.10+ ```Python hl_lines="1" @@ -266,96 +245,7 @@ Cela signifie aussi que dans Python 3.10, vous pouvez utiliser `Something | None //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ alternative - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### Utiliser `Union` ou `Optional` { #using-union-or-optional } - -Si vous utilisez une version de Python inférieure à 3.10, voici un conseil de mon point de vue très **subjectif** : - -* 🚨 Évitez d'utiliser `Optional[SomeType]` -* À la place ✨ **utilisez `Union[SomeType, None]`** ✨. - -Les deux sont équivalents et sous le capot ce sont les mêmes, mais je recommanderais `Union` plutôt que `Optional` parce que le mot « facultatif » semble impliquer que la valeur est optionnelle, alors que cela signifie en fait « elle peut être `None` », même si elle n'est pas facultative et est toujours requise. - -Je pense que `Union[SomeType, None]` est plus explicite sur ce que cela signifie. - -Il ne s'agit que des mots et des noms. Mais ces mots peuvent influencer la manière dont vous et vos coéquipiers pensez au code. - -Par exemple, prenons cette fonction : - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -Le paramètre `name` est défini comme `Optional[str]`, mais il n'est pas facultatif, vous ne pouvez pas appeler la fonction sans le paramètre : - -```Python -say_hi() # Oh non, cela lève une erreur ! 😱 -``` - -Le paramètre `name` est toujours requis (pas « optionnel ») parce qu'il n'a pas de valeur par défaut. Néanmoins, `name` accepte `None` comme valeur : - -```Python -say_hi(name=None) # Cela fonctionne, None est valide 🎉 -``` - -La bonne nouvelle est que, dès que vous êtes sur Python 3.10, vous n'avez plus à vous en soucier, car vous pourrez simplement utiliser `|` pour définir des unions de types : - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -Et alors vous n'aurez plus à vous soucier de noms comme `Optional` et `Union`. 😎 - -#### Types génériques { #generic-types } - -Ces types qui prennent des paramètres de type entre crochets sont appelés des **types génériques** ou **Generics**, par exemple : - -//// tab | Python 3.10+ - -Vous pouvez utiliser les mêmes types intégrés comme génériques (avec des crochets et des types à l'intérieur) : - -* `list` -* `tuple` -* `set` -* `dict` - -Et, comme avec les versions précédentes de Python, depuis le module `typing` : - -* `Union` -* `Optional` -* ... et d'autres. - -Dans Python 3.10, comme alternative à l'utilisation des génériques `Union` et `Optional`, vous pouvez utiliser la barre verticale (`|`) pour déclarer des unions de types, c'est bien mieux et plus simple. - -//// - -//// tab | Python 3.9+ - -Vous pouvez utiliser les mêmes types intégrés comme génériques (avec des crochets et des types à l'intérieur) : - -* `list` -* `tuple` -* `set` -* `dict` - -Et des génériques depuis le module `typing` : - -* `Union` -* `Optional` -* ... et d'autres. - -//// +Utiliser `str | None` au lieu de simplement `str` permettra à l'éditeur de vous aider à détecter des erreurs où vous supposeriez qu'une valeur est toujours un `str`, alors qu'elle pourrait en fait aussi être `None`. ### Classes en tant que types { #classes-as-types } @@ -363,19 +253,19 @@ Vous pouvez aussi déclarer une classe comme type d'une variable. Disons que vous avez une classe `Person`, avec un nom : -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} Vous pouvez ensuite déclarer une variable de type `Person` : -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} Et là encore, vous obtenez tout le support de l'éditeur : -Remarquez que cela signifie « `one_person` est une instance de la classe `Person` ». +Remarquez que cela signifie « `one_person` est une **instance** de la classe `Person` ». -Cela ne signifie pas « `one_person` est la classe appelée `Person` ». +Cela ne signifie pas « `one_person` est la **classe** appelée `Person` ». ## Modèles Pydantic { #pydantic-models } @@ -393,7 +283,7 @@ Un exemple tiré de la documentation officielle de Pydantic : {* ../../docs_src/python_types/tutorial011_py310.py *} -/// info +/// info | Info Pour en savoir plus à propos de Pydantic, consultez sa documentation. @@ -403,33 +293,27 @@ Pour en savoir plus à propos de champs Optionals requis. - -/// - ## Annotations de type avec métadonnées { #type-hints-with-metadata-annotations } -Python dispose également d'une fonctionnalité qui permet de mettre des **métadonnées supplémentaires** dans ces annotations de type en utilisant `Annotated`. +Python dispose également d'une fonctionnalité qui permet de mettre des **métadonnées supplémentaires** dans ces annotations de type en utilisant `Annotated`. -Depuis Python 3.9, `Annotated` fait partie de la bibliothèque standard, vous pouvez donc l'importer depuis `typing`. +Vous pouvez importer `Annotated` depuis `typing`. -{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} Python lui-même ne fait rien avec ce `Annotated`. Et pour les éditeurs et autres outils, le type est toujours `str`. Mais vous pouvez utiliser cet espace dans `Annotated` pour fournir à **FastAPI** des métadonnées supplémentaires sur la façon dont vous voulez que votre application se comporte. -L'important à retenir est que le premier paramètre de type que vous passez à `Annotated` est le type réel. Le reste n'est que des métadonnées pour d'autres outils. +L'important à retenir est que **le premier « paramètre de type »** que vous passez à `Annotated` est le **type réel**. Le reste n'est que des métadonnées pour d'autres outils. Pour l'instant, vous avez juste besoin de savoir que `Annotated` existe, et que c'est du Python standard. 😎 -Plus tard, vous verrez à quel point cela peut être puissant. +Plus tard, vous verrez à quel point cela peut être **puissant**. /// tip | Astuce -Le fait que ce soit du Python standard signifie que vous bénéficierez toujours de la meilleure expérience développeur possible dans votre éditeur, avec les outils que vous utilisez pour analyser et refactoriser votre code, etc. ✨ +Le fait que ce soit du **Python standard** signifie que vous bénéficierez toujours de la **meilleure expérience développeur possible** dans votre éditeur, avec les outils que vous utilisez pour analyser et refactoriser votre code, etc. ✨ Et aussi que votre code sera très compatible avec de nombreux autres outils et bibliothèques Python. 🚀 @@ -457,7 +341,7 @@ Tout cela peut sembler abstrait. Ne vous inquiétez pas. Vous verrez tout cela e L'important est qu'en utilisant les types standards de Python, en un seul endroit (au lieu d'ajouter plus de classes, de décorateurs, etc.), **FastAPI** fera une grande partie du travail pour vous. -/// info +/// info | Info Si vous avez déjà parcouru tout le tutoriel et êtes revenu pour en voir plus sur les types, une bonne ressource est l'« aide-mémoire » de `mypy`. diff --git a/docs/fr/docs/resources/index.md b/docs/fr/docs/resources/index.md new file mode 100644 index 000000000..e62db346d --- /dev/null +++ b/docs/fr/docs/resources/index.md @@ -0,0 +1,3 @@ +# Ressources { #resources } + +Ressources supplémentaires, liens externes et plus encore. ✈️ diff --git a/docs/fr/docs/translation-banner.md b/docs/fr/docs/translation-banner.md new file mode 100644 index 000000000..9eaedf1b1 --- /dev/null +++ b/docs/fr/docs/translation-banner.md @@ -0,0 +1,11 @@ +/// details | 🌐 Traduction par IA et humains + +Cette traduction a été réalisée par une IA guidée par des humains. 🤝 + +Elle peut contenir des erreurs d'interprétation du sens original, ou paraître peu naturelle, etc. 🤖 + +Vous pouvez améliorer cette traduction en [nous aidant à mieux guider le LLM d'IA](https://fastapi.tiangolo.com/fr/contributing/#translations). + +[Version anglaise](ENGLISH_VERSION_URL) + +/// diff --git a/docs/fr/docs/tutorial/background-tasks.md b/docs/fr/docs/tutorial/background-tasks.md index ed7494669..a8444ba27 100644 --- a/docs/fr/docs/tutorial/background-tasks.md +++ b/docs/fr/docs/tutorial/background-tasks.md @@ -15,12 +15,14 @@ Cela comprend, par exemple : Pour commencer, importez `BackgroundTasks` et définissez un paramètre dans votre *fonction de chemin d'accès* avec `BackgroundTasks` comme type déclaré. -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} **FastAPI** créera l'objet de type `BackgroundTasks` pour vous et le passera comme paramètre. ## Créer une fonction de tâche { #create-a-task-function } +Créez une fonction à exécuter comme tâche d'arrière-plan. + Une fonction à exécuter comme tâche d'arrière-plan est juste une fonction standard qui peut recevoir des paramètres. Elle peut être une fonction asynchrone (`async def`) ou une fonction normale (`def`), **FastAPI** saura la gérer correctement. @@ -29,13 +31,13 @@ Dans cet exemple, la fonction de tâche écrira dans un fichier (afin de simuler L'opération d'écriture n'utilisant ni `async` ni `await`, on définit la fonction avec un `def` normal. -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} ## Ajouter une tâche d'arrière-plan { #add-the-background-task } Dans votre *fonction de chemin d'accès*, passez votre fonction de tâche à l'objet de type `BackgroundTasks` (`background_tasks` ici) grâce à la méthode `.add_task()` : -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} `.add_task()` reçoit comme arguments : diff --git a/docs/fr/docs/tutorial/bigger-applications.md b/docs/fr/docs/tutorial/bigger-applications.md new file mode 100644 index 000000000..065962236 --- /dev/null +++ b/docs/fr/docs/tutorial/bigger-applications.md @@ -0,0 +1,504 @@ +# Créer des applications plus grandes - Plusieurs fichiers { #bigger-applications-multiple-files } + +Si vous créez une application ou une API web, il est rare que vous puissiez tout mettre dans un seul fichier. + +**FastAPI** fournit un outil pratique pour structurer votre application tout en conservant toute la flexibilité. + +/// info + +Si vous venez de Flask, cela équivaut aux Blueprints de Flask. + +/// + +## Exemple de structure de fichiers { #an-example-file-structure } + +Supposons que vous ayez une structure de fichiers comme ceci : + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   ├── dependencies.py +│   └── routers +│   │ ├── __init__.py +│   │ ├── items.py +│   │ └── users.py +│   └── internal +│   ├── __init__.py +│   └── admin.py +``` + +/// tip | Astuce + +Il y a plusieurs fichiers `__init__.py` : un dans chaque répertoire ou sous-répertoire. + +C'est cela qui permet d'importer du code d'un fichier dans un autre. + +Par exemple, dans `app/main.py` vous pourriez avoir une ligne comme : + +``` +from app.routers import items +``` + +/// + +* Le répertoire `app` contient tout. Et il a un fichier vide `app/__init__.py`, c'est donc un « package Python » (une collection de « modules Python ») : `app`. +* Il contient un fichier `app/main.py`. Comme il se trouve dans un package Python (un répertoire avec un fichier `__init__.py`), c'est un « module » de ce package : `app.main`. +* Il y a aussi un fichier `app/dependencies.py`, tout comme `app/main.py`, c'est un « module » : `app.dependencies`. +* Il y a un sous-répertoire `app/routers/` avec un autre fichier `__init__.py`, c'est donc un « sous-package Python » : `app.routers`. +* Le fichier `app/routers/items.py` est dans un package, `app/routers/`, c'est donc un sous-module : `app.routers.items`. +* De même pour `app/routers/users.py`, c'est un autre sous-module : `app.routers.users`. +* Il y a aussi un sous-répertoire `app/internal/` avec un autre fichier `__init__.py`, c'est donc un autre « sous-package Python » : `app.internal`. +* Et le fichier `app/internal/admin.py` est un autre sous-module : `app.internal.admin`. + + + +La même structure de fichiers avec des commentaires : + +```bash +. +├── app # "app" est un package Python +│   ├── __init__.py # ce fichier fait de "app" un "package Python" +│   ├── main.py # module "main", ex. import app.main +│   ├── dependencies.py # module "dependencies", ex. import app.dependencies +│   └── routers # "routers" est un "sous-package Python" +│   │ ├── __init__.py # fait de "routers" un "sous-package Python" +│   │ ├── items.py # sous-module "items", ex. import app.routers.items +│   │ └── users.py # sous-module "users", ex. import app.routers.users +│   └── internal # "internal" est un "sous-package Python" +│   ├── __init__.py # fait de "internal" un "sous-package Python" +│   └── admin.py # sous-module "admin", ex. import app.internal.admin +``` + +## `APIRouter` { #apirouter } + +Supposons que le fichier dédié à la gestion des utilisateurs soit le sous-module `/app/routers/users.py`. + +Vous voulez séparer les *chemins d'accès* liés à vos utilisateurs du reste du code pour le garder organisé. + +Mais cela fait toujours partie de la même application/API web **FastAPI** (cela fait partie du même « package Python »). + +Vous pouvez créer les *chemins d'accès* pour ce module à l'aide de `APIRouter`. + +### Importer `APIRouter` { #import-apirouter } + +Vous l'importez et créez une « instance » de la même manière que vous le feriez avec la classe `FastAPI` : + +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} + +### Déclarer des *chemins d'accès* avec `APIRouter` { #path-operations-with-apirouter } + +Puis vous l'utilisez pour déclarer vos *chemins d'accès*. + +Utilisez-le de la même manière que vous utiliseriez la classe `FastAPI` : + +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} + +Vous pouvez considérer `APIRouter` comme une « mini `FastAPI` ». + +Toutes les mêmes options sont prises en charge. + +Tous les mêmes `parameters`, `responses`, `dependencies`, `tags`, etc. + +/// tip | Astuce + +Dans cet exemple, la variable s'appelle `router`, mais vous pouvez la nommer comme vous le souhaitez. + +/// + +Nous allons inclure ce `APIRouter` dans l'application principale `FastAPI`, mais d'abord, examinons les dépendances et un autre `APIRouter`. + +## Gérer les dépendances { #dependencies } + +Nous voyons que nous allons avoir besoin de certaines dépendances utilisées à plusieurs endroits de l'application. + +Nous les mettons donc dans leur propre module `dependencies` (`app/dependencies.py`). + +Nous allons maintenant utiliser une dépendance simple pour lire un en-tête personnalisé `X-Token` : + +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} + +/// tip | Astuce + +Nous utilisons un en-tête inventé pour simplifier cet exemple. + +Mais dans les cas réels, vous obtiendrez de meilleurs résultats en utilisant les [utilitaires de sécurité](security/index.md){.internal-link target=_blank} intégrés. + +/// + +## Créer un autre module avec `APIRouter` { #another-module-with-apirouter } + +Supposons que vous ayez également les endpoints dédiés à la gestion des « items » de votre application dans le module `app/routers/items.py`. + +Vous avez des *chemins d'accès* pour : + +* `/items/` +* `/items/{item_id}` + +C'est exactement la même structure que pour `app/routers/users.py`. + +Mais nous voulons être plus malins et simplifier un peu le code. + +Nous savons que tous les *chemins d'accès* de ce module ont les mêmes éléments : + +* Préfixe de chemin `prefix` : `/items`. +* `tags` : (un seul tag : `items`). +* `responses` supplémentaires. +* `dependencies` : ils ont tous besoin de la dépendance `X-Token` que nous avons créée. + +Donc, au lieu d'ajouter tout cela à chaque *chemin d'accès*, nous pouvons l'ajouter au `APIRouter`. + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} + +Comme le chemin de chaque *chemin d'accès* doit commencer par `/`, comme dans : + +```Python hl_lines="1" +@router.get("/{item_id}") +async def read_item(item_id: str): + ... +``` + +... le préfixe ne doit pas inclure un `/` final. + +Ainsi, le préfixe dans ce cas est `/items`. + +Nous pouvons également ajouter une liste de `tags` et des `responses` supplémentaires qui seront appliqués à tous les *chemins d'accès* inclus dans ce routeur. + +Et nous pouvons ajouter une liste de `dependencies` qui seront ajoutées à tous les *chemins d'accès* du routeur et seront exécutées/résolues pour chaque requête qui leur est faite. + +/// tip | Astuce + +Notez que, tout comme pour les [dépendances dans les décorateurs de *chemin d'accès*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, aucune valeur ne sera transmise à votre *fonction de chemin d'accès*. + +/// + +Le résultat final est que les chemins d'item sont désormais : + +* `/items/` +* `/items/{item_id}` + +... comme prévu. + +* Ils seront marqués avec une liste de tags qui contient une seule chaîne « items ». + * Ces « tags » sont particulièrement utiles pour les systèmes de documentation interactive automatique (utilisant OpenAPI). +* Ils incluront tous les `responses` prédéfinies. +* Tous ces *chemins d'accès* auront la liste des `dependencies` évaluées/exécutées avant eux. + * Si vous déclarez également des dépendances dans un *chemin d'accès* spécifique, **elles seront aussi exécutées**. + * Les dépendances du routeur sont exécutées en premier, puis les [`dependencies` dans le décorateur](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, puis les dépendances des paramètres normaux. + * Vous pouvez également ajouter des [`Security` dependencies avec des `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}. + +/// tip | Astuce + +Avoir des `dependencies` dans le `APIRouter` peut servir, par exemple, à exiger une authentification pour tout un groupe de *chemins d'accès*. Même si les dépendances ne sont pas ajoutées individuellement à chacun d'eux. + +/// + +/// check | Vérifications + +Les paramètres `prefix`, `tags`, `responses` et `dependencies` sont (comme dans de nombreux autres cas) simplement une fonctionnalité de **FastAPI** pour vous aider à éviter la duplication de code. + +/// + +### Importer les dépendances { #import-the-dependencies } + +Ce code se trouve dans le module `app.routers.items`, le fichier `app/routers/items.py`. + +Et nous devons récupérer la fonction de dépendance depuis le module `app.dependencies`, le fichier `app/dependencies.py`. + +Nous utilisons donc un import relatif avec `..` pour les dépendances : + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} + +#### Comprendre le fonctionnement des imports relatifs { #how-relative-imports-work } + +/// tip | Astuce + +Si vous savez parfaitement comment fonctionnent les imports, passez à la section suivante ci-dessous. + +/// + +Un seul point `.`, comme dans : + +```Python +from .dependencies import get_token_header +``` + +signifierait : + +* En partant du même package dans lequel vit ce module (le fichier `app/routers/items.py`) (le répertoire `app/routers/`)... +* trouver le module `dependencies` (un fichier imaginaire `app/routers/dependencies.py`)... +* et en importer la fonction `get_token_header`. + +Mais ce fichier n'existe pas, nos dépendances sont dans un fichier `app/dependencies.py`. + +Rappelez-vous à quoi ressemble la structure de notre app/fichiers : + + + +--- + +Les deux points `..`, comme dans : + +```Python +from ..dependencies import get_token_header +``` + +veulent dire : + +* En partant du même package dans lequel vit ce module (le fichier `app/routers/items.py`) (le répertoire `app/routers/`)... +* aller au package parent (le répertoire `app/`)... +* et là, trouver le module `dependencies` (le fichier `app/dependencies.py`)... +* et en importer la fonction `get_token_header`. + +Cela fonctionne correctement ! 🎉 + +--- + +De la même manière, si nous avions utilisé trois points `...`, comme dans : + +```Python +from ...dependencies import get_token_header +``` + +cela voudrait dire : + +* En partant du même package dans lequel vit ce module (le fichier `app/routers/items.py`) (le répertoire `app/routers/`)... +* aller au package parent (le répertoire `app/`)... +* puis aller au parent de ce package (il n'y a pas de package parent, `app` est le niveau supérieur 😱)... +* et là, trouver le module `dependencies` (le fichier `app/dependencies.py`)... +* et en importer la fonction `get_token_header`. + +Cela ferait référence à un package au-dessus de `app/`, avec son propre fichier `__init__.py`, etc. Mais nous n'avons pas cela. Donc, cela lèverait une erreur dans notre exemple. 🚨 + +Mais maintenant vous savez comment cela fonctionne, vous pouvez donc utiliser des imports relatifs dans vos propres applications, aussi complexes soient-elles. 🤓 + +### Ajouter des `tags`, `responses` et `dependencies` personnalisés { #add-some-custom-tags-responses-and-dependencies } + +Nous n'ajoutons pas le préfixe `/items` ni `tags=["items"]` à chaque *chemin d'accès* parce que nous les avons ajoutés au `APIRouter`. + +Mais nous pouvons toujours ajouter _davantage_ de `tags` qui seront appliqués à un *chemin d'accès* spécifique, ainsi que des `responses` supplémentaires propres à ce *chemin d'accès* : + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} + +/// tip | Astuce + +Ce dernier *chemin d'accès* aura la combinaison de tags : `["items", "custom"]`. + +Et il aura également les deux réponses dans la documentation, une pour `404` et une pour `403`. + +/// + +## Créer l'application `FastAPI` principale { #the-main-fastapi } + +Voyons maintenant le module `app/main.py`. + +C'est ici que vous importez et utilisez la classe `FastAPI`. + +Ce sera le fichier principal de votre application qui reliera tout ensemble. + +Et comme la plupart de votre logique vivra désormais dans son propre module, le fichier principal sera assez simple. + +### Importer `FastAPI` { #import-fastapi } + +Vous importez et créez une classe `FastAPI` comme d'habitude. + +Et nous pouvons même déclarer des [dépendances globales](dependencies/global-dependencies.md){.internal-link target=_blank} qui seront combinées avec les dépendances de chaque `APIRouter` : + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} + +### Importer les `APIRouter` { #import-the-apirouter } + +Nous importons maintenant les autres sous-modules qui ont des `APIRouter` : + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} + +Comme les fichiers `app/routers/users.py` et `app/routers/items.py` sont des sous-modules qui font partie du même package Python `app`, nous pouvons utiliser un seul point `.` pour les importer en utilisant des « imports relatifs ». + +### Comprendre le fonctionnement de l'import { #how-the-importing-works } + +La section : + +```Python +from .routers import items, users +``` + +signifie : + +* En partant du même package dans lequel vit ce module (le fichier `app/main.py`) (le répertoire `app/`)... +* chercher le sous-package `routers` (le répertoire `app/routers/`)... +* et en importer le sous-module `items` (le fichier `app/routers/items.py`) et `users` (le fichier `app/routers/users.py`)... + +Le module `items` aura une variable `router` (`items.router`). C'est celle que nous avons créée dans le fichier `app/routers/items.py`, c'est un objet `APIRouter`. + +Nous faisons ensuite la même chose pour le module `users`. + +Nous pourrions aussi les importer ainsi : + +```Python +from app.routers import items, users +``` + +/// info + +La première version est un « import relatif » : + +```Python +from .routers import items, users +``` + +La deuxième version est un « import absolu » : + +```Python +from app.routers import items, users +``` + +Pour en savoir plus sur les Packages et Modules Python, lisez la documentation officielle de Python sur les modules. + +/// + +### Éviter les collisions de noms { #avoid-name-collisions } + +Nous importons le sous-module `items` directement, au lieu d'importer uniquement sa variable `router`. + +C'est parce que nous avons également une autre variable nommée `router` dans le sous-module `users`. + +Si nous les avions importées l'une après l'autre, comme : + +```Python +from .routers.items import router +from .routers.users import router +``` + +le `router` de `users` écraserait celui de `items` et nous ne pourrions pas les utiliser en même temps. + +Donc, pour pouvoir utiliser les deux dans le même fichier, nous importons directement les sous-modules : + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} + +### Inclure les `APIRouter` pour `users` et `items` { #include-the-apirouters-for-users-and-items } + +Incluons maintenant les `router` des sous-modules `users` et `items` : + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} + +/// info + +`users.router` contient le `APIRouter` à l'intérieur du fichier `app/routers/users.py`. + +Et `items.router` contient le `APIRouter` à l'intérieur du fichier `app/routers/items.py`. + +/// + +Avec `app.include_router()`, nous pouvons ajouter chaque `APIRouter` à l'application principale `FastAPI`. + +Cela inclura toutes les routes de ce routeur comme faisant partie de l'application. + +/// note | Détails techniques + +En interne, cela créera en fait un *chemin d'accès* pour chaque *chemin d'accès* qui a été déclaré dans le `APIRouter`. + +Donc, en coulisses, cela fonctionnera comme si tout faisait partie d'une seule et même application. + +/// + +/// check | Vérifications + +Vous n'avez pas à vous soucier de la performance lors de l'inclusion de routeurs. + +Cela prendra des microsecondes et ne se produira qu'au démarrage. + +Donc cela n'affectera pas la performance. ⚡ + +/// + +### Inclure un `APIRouter` avec un `prefix`, des `tags`, des `responses` et des `dependencies` personnalisés { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies } + +Imaginons maintenant que votre organisation vous ait fourni le fichier `app/internal/admin.py`. + +Il contient un `APIRouter` avec quelques *chemins d'accès* d'administration que votre organisation partage entre plusieurs projets. + +Pour cet exemple, il sera très simple. Mais supposons que, parce qu'il est partagé avec d'autres projets de l'organisation, nous ne puissions pas le modifier et ajouter un `prefix`, des `dependencies`, des `tags`, etc. directement au `APIRouter` : + +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} + +Mais nous voulons quand même définir un `prefix` personnalisé lors de l'inclusion du `APIRouter` afin que tous ses *chemins d'accès* commencent par `/admin`, nous voulons le sécuriser avec les `dependencies` que nous avons déjà pour ce projet, et nous voulons inclure des `tags` et des `responses`. + +Nous pouvons déclarer tout cela sans avoir à modifier le `APIRouter` d'origine en passant ces paramètres à `app.include_router()` : + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} + +De cette façon, le `APIRouter` original restera inchangé, afin que nous puissions toujours partager ce même fichier `app/internal/admin.py` avec d'autres projets de l'organisation. + +Le résultat est que, dans notre application, chacun des *chemins d'accès* du module `admin` aura : + +* Le préfixe `/admin`. +* Le tag `admin`. +* La dépendance `get_token_header`. +* La réponse `418`. 🍵 + +Mais cela n'affectera que ce `APIRouter` dans notre application, pas dans tout autre code qui l'utilise. + +Ainsi, par exemple, d'autres projets pourraient utiliser le même `APIRouter` avec une méthode d'authentification différente. + +### Inclure un *chemin d'accès* { #include-a-path-operation } + +Nous pouvons également ajouter des *chemins d'accès* directement à l'application `FastAPI`. + +Ici, nous le faisons... juste pour montrer que nous le pouvons 🤷 : + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} + +et cela fonctionnera correctement, avec tous les autres *chemins d'accès* ajoutés avec `app.include_router()`. + +/// info | Détails très techniques + +Note : c'est un détail très technique que vous pouvez probablement **simplement ignorer**. + +--- + +Les `APIRouter` ne sont pas « montés », ils ne sont pas isolés du reste de l'application. + +C'est parce que nous voulons inclure leurs *chemins d'accès* dans le schéma OpenAPI et les interfaces utilisateur. + +Comme nous ne pouvons pas simplement les isoler et les « monter » indépendamment du reste, les *chemins d'accès* sont « clonés » (recréés), pas inclus directement. + +/// + +## Consulter la documentation API automatique { #check-the-automatic-api-docs } + +Maintenant, exécutez votre application : + +
+ +```console +$ fastapi dev app/main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Et ouvrez les documents à http://127.0.0.1:8000/docs. + +Vous verrez la documentation API automatique, incluant les chemins de tous les sous-modules, utilisant les bons chemins (et préfixes) et les bons tags : + + + +## Inclure le même routeur plusieurs fois avec des `prefix` différents { #include-the-same-router-multiple-times-with-different-prefix } + +Vous pouvez aussi utiliser `.include_router()` plusieurs fois avec le même routeur en utilisant des préfixes différents. + +Cela peut être utile, par exemple, pour exposer la même API sous des préfixes différents, p. ex. `/api/v1` et `/api/latest`. + +C'est un usage avancé dont vous n'aurez peut-être pas vraiment besoin, mais il est là au cas où. + +## Inclure un `APIRouter` dans un autre { #include-an-apirouter-in-another } + +De la même manière que vous pouvez inclure un `APIRouter` dans une application `FastAPI`, vous pouvez inclure un `APIRouter` dans un autre `APIRouter` en utilisant : + +```Python +router.include_router(other_router) +``` + +Vous devez vous assurer de le faire avant d'inclure `router` dans l'application `FastAPI`, afin que les *chemins d'accès* de `other_router` soient également inclus. diff --git a/docs/fr/docs/tutorial/body-fields.md b/docs/fr/docs/tutorial/body-fields.md new file mode 100644 index 000000000..9830292c9 --- /dev/null +++ b/docs/fr/docs/tutorial/body-fields.md @@ -0,0 +1,61 @@ +# Corps - Champs { #body-fields } + +De la même manière que vous pouvez déclarer des validations supplémentaires et des métadonnées dans les paramètres d'une fonction de chemin d'accès avec `Query`, `Path` et `Body`, vous pouvez déclarer des validations et des métadonnées à l'intérieur des modèles Pydantic en utilisant `Field` de Pydantic. + +## Importer `Field` { #import-field } + +D'abord, vous devez l'importer : + +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *} + + +/// warning | Alertes + +Notez que `Field` est importé directement depuis `pydantic`, et non depuis `fastapi` comme le sont les autres (`Query`, `Path`, `Body`, etc.). + +/// + +## Déclarer les attributs du modèle { #declare-model-attributes } + +Vous pouvez ensuite utiliser `Field` avec des attributs de modèle : + +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *} + +`Field` fonctionne de la même manière que `Query`, `Path` et `Body`, il dispose des mêmes paramètres, etc. + +/// note | Détails techniques + +En réalité, `Query`, `Path` et d'autres que vous verrez ensuite créent des objets de sous-classes d'une classe commune `Param`, qui est elle-même une sous-classe de la classe `FieldInfo` de Pydantic. + +Et `Field` de Pydantic renvoie également une instance de `FieldInfo`. + +`Body` renvoie aussi directement des objets d'une sous-classe de `FieldInfo`. Et il y en a d'autres que vous verrez plus tard qui sont des sous-classes de la classe `Body`. + +Rappelez-vous que lorsque vous importez `Query`, `Path` et d'autres depuis `fastapi`, ce sont en réalité des fonctions qui renvoient des classes spéciales. + +/// + +/// tip | Astuce + +Remarquez comment chaque attribut de modèle avec un type, une valeur par défaut et `Field` a la même structure qu'un paramètre de fonction de chemin d'accès, avec `Field` au lieu de `Path`, `Query` et `Body`. + +/// + +## Ajouter des informations supplémentaires { #add-extra-information } + +Vous pouvez déclarer des informations supplémentaires dans `Field`, `Query`, `Body`, etc. Elles seront incluses dans le JSON Schema généré. + +Vous en apprendrez davantage sur l'ajout d'informations supplémentaires plus loin dans les documents, lorsque vous apprendrez à déclarer des exemples. + +/// warning | Alertes + +Les clés supplémentaires passées à `Field` seront également présentes dans le schéma OpenAPI résultant pour votre application. +Comme ces clés ne font pas nécessairement partie de la spécification OpenAPI, certains outils OpenAPI, par exemple [le validateur OpenAPI](https://validator.swagger.io/), peuvent ne pas fonctionner avec votre schéma généré. + +/// + +## Récapitulatif { #recap } + +Vous pouvez utiliser `Field` de Pydantic pour déclarer des validations supplémentaires et des métadonnées pour les attributs de modèle. + +Vous pouvez également utiliser des arguments nommés supplémentaires pour transmettre des métadonnées JSON Schema additionnelles. diff --git a/docs/fr/docs/tutorial/body-multiple-params.md b/docs/fr/docs/tutorial/body-multiple-params.md index 92ca2afc3..1c1ab0fca 100644 --- a/docs/fr/docs/tutorial/body-multiple-params.md +++ b/docs/fr/docs/tutorial/body-multiple-params.md @@ -104,12 +104,6 @@ Comme, par défaut, les valeurs singulières sont interprétées comme des param q: str | None = None ``` -Ou en Python 3.9 : - -```Python -q: Union[str, None] = None -``` - Par exemple : {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/fr/docs/tutorial/body-nested-models.md b/docs/fr/docs/tutorial/body-nested-models.md new file mode 100644 index 000000000..dccfdb6c5 --- /dev/null +++ b/docs/fr/docs/tutorial/body-nested-models.md @@ -0,0 +1,220 @@ +# Corps - Modèles imbriqués { #body-nested-models } + +Avec FastAPI, vous pouvez définir, valider, documenter et utiliser des modèles imbriqués à n'importe quelle profondeur (grâce à Pydantic). + +## Déclarer des champs de liste { #list-fields } + +Vous pouvez définir un attribut comme étant un sous-type. Par exemple, une `list` Python : + +{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *} + +Cela fera de `tags` une liste, bien que le type des éléments de la liste ne soit pas déclaré. + +## Champs de liste avec paramètre de type { #list-fields-with-type-parameter } + +Mais Python a une manière spécifique de déclarer des listes avec des types internes, ou « paramètres de type » : + +### Déclarer une `list` avec un paramètre de type { #declare-a-list-with-a-type-parameter } + +Pour déclarer des types qui ont des paramètres de type (types internes), comme `list`, `dict`, `tuple`, +passez le(s) type(s) interne(s) comme « paramètres de type » à l'aide de crochets : `[` et `]` + +```Python +my_list: list[str] +``` + +C'est simplement la syntaxe Python standard pour les déclarations de type. + +Utilisez cette même syntaxe standard pour les attributs de modèles avec des types internes. + +Ainsi, dans notre exemple, nous pouvons faire de `tags` spécifiquement une « liste de chaînes » : + +{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *} + +## Types set { #set-types } + +Mais en y réfléchissant, nous réalisons que les tags ne devraient pas se répéter, ce seraient probablement des chaînes uniques. + +Et Python dispose d'un type de données spécial pour les ensembles d'éléments uniques, le `set`. + +Nous pouvons alors déclarer `tags` comme un set de chaînes : + +{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *} + +Avec cela, même si vous recevez une requête contenant des doublons, elle sera convertie en un set d'éléments uniques. + +Et chaque fois que vous renverrez ces données, même si la source contenait des doublons, elles seront renvoyées sous la forme d'un set d'éléments uniques. + +Elles seront également annotées / documentées en conséquence. + +## Modèles imbriqués { #nested-models } + +Chaque attribut d'un modèle Pydantic a un type. + +Mais ce type peut lui-même être un autre modèle Pydantic. + +Ainsi, vous pouvez déclarer des « objets » JSON profondément imbriqués avec des noms d'attributs, des types et des validations spécifiques. + +Tout cela, de manière arbitrairement imbriquée. + +### Définir un sous-modèle { #define-a-submodel } + +Par exemple, nous pouvons définir un modèle `Image` : + +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *} + +### Utiliser le sous-modèle comme type { #use-the-submodel-as-a-type } + +Nous pouvons ensuite l'utiliser comme type d'un attribut : + +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *} + +Cela signifie que FastAPI attendrait un corps similaire à : + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": ["rock", "metal", "bar"], + "image": { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + } +} +``` + +Là encore, avec cette simple déclaration, avec FastAPI vous obtenez : + +- Prise en charge par l'éditeur (autocomplétion, etc.), même pour les modèles imbriqués +- Conversion des données +- Validation des données +- Documentation automatique + +## Types spéciaux et validation { #special-types-and-validation } + +Outre les types singuliers normaux comme `str`, `int`, `float`, etc. vous pouvez utiliser des types singuliers plus complexes qui héritent de `str`. + +Pour voir toutes les options dont vous disposez, consultez l’aperçu des types de Pydantic. Vous verrez quelques exemples au chapitre suivant. + +Par exemple, comme dans le modèle `Image` nous avons un champ `url`, nous pouvons le déclarer comme instance de `HttpUrl` de Pydantic au lieu de `str` : + +{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *} + +La chaîne sera vérifiée comme URL valide et documentée comme telle dans JSON Schema / OpenAPI. + +## Attributs avec des listes de sous-modèles { #attributes-with-lists-of-submodels } + +Vous pouvez également utiliser des modèles Pydantic comme sous-types de `list`, `set`, etc. : + +{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *} + +Cela attendra (convertira, validera, documentera, etc.) un corps JSON comme : + +```JSON hl_lines="11" +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": [ + "rock", + "metal", + "bar" + ], + "images": [ + { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + }, + { + "url": "http://example.com/dave.jpg", + "name": "The Baz" + } + ] +} +``` +/// info + +Remarquez que la clé `images` contient maintenant une liste d'objets image. + +/// + +## Modèles profondément imbriqués { #deeply-nested-models } + +Vous pouvez définir des modèles imbriqués à une profondeur arbitraire : + +{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *} + +/// info + +Remarquez que `Offer` a une liste d’`Item`, qui à leur tour ont une liste optionnelle d’`Image`. + +/// + +## Corps de listes pures { #bodies-of-pure-lists } + +Si la valeur de premier niveau du corps JSON attendu est un `array` JSON (une `list` Python), vous pouvez déclarer le type dans le paramètre de la fonction, de la même manière que dans les modèles Pydantic : + +```Python +images: list[Image] +``` + +comme : + +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} + +## Bénéficier de la prise en charge de l'éditeur partout { #editor-support-everywhere } + +Et vous bénéficiez de la prise en charge de l'éditeur partout. + +Même pour les éléments à l'intérieur des listes : + + + +Vous ne pourriez pas obtenir ce type de prise en charge de l'éditeur si vous travailliez directement avec des `dict` au lieu de modèles Pydantic. + +Mais vous n'avez pas à vous en soucier non plus, les `dict` entrants sont convertis automatiquement et votre sortie est également convertie automatiquement en JSON. + +## Corps de `dict` arbitraires { #bodies-of-arbitrary-dicts } + +Vous pouvez également déclarer un corps comme un `dict` avec des clés d’un certain type et des valeurs d’un autre type. + +De cette façon, vous n'avez pas besoin de savoir à l'avance quels sont les noms de champs/attributs valides (comme ce serait le cas avec des modèles Pydantic). + +Cela serait utile si vous voulez recevoir des clés que vous ne connaissez pas à l'avance. + +--- + +Un autre cas utile est lorsque vous souhaitez avoir des clés d'un autre type (par exemple `int`). + +C'est ce que nous allons voir ici. + +Dans ce cas, vous accepteriez n'importe quel `dict` tant qu'il a des clés `int` avec des valeurs `float` : + +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} + +/// tip | Astuce + +Gardez à l'esprit que JSON ne prend en charge que des `str` comme clés. + +Mais Pydantic dispose d'une conversion automatique des données. + +Cela signifie que, même si vos clients d'API ne peuvent envoyer que des chaînes comme clés, tant que ces chaînes contiennent des entiers purs, Pydantic les convertira et les validera. + +Et le `dict` que vous recevez dans `weights` aura en réalité des clés `int` et des valeurs `float`. + +/// + +## Récapitulatif { #recap } + +Avec FastAPI, vous bénéficiez de la flexibilité maximale fournie par les modèles Pydantic, tout en gardant votre code simple, concis et élégant. + +Mais avec tous les avantages : + +- Prise en charge par l'éditeur (autocomplétion partout !) +- Conversion des données (a.k.a. parsing / sérialisation) +- Validation des données +- Documentation des schémas +- Documentation automatique diff --git a/docs/fr/docs/tutorial/body-updates.md b/docs/fr/docs/tutorial/body-updates.md new file mode 100644 index 000000000..36ad12681 --- /dev/null +++ b/docs/fr/docs/tutorial/body-updates.md @@ -0,0 +1,100 @@ +# Corps - Mises à jour { #body-updates } + +## Mettre à jour en remplaçant avec `PUT` { #update-replacing-with-put } + +Pour mettre à jour un élément, vous pouvez utiliser l’opération HTTP `PUT`. + +Vous pouvez utiliser le `jsonable_encoder` pour convertir les données d’entrée en données pouvant être stockées au format JSON (par exemple, avec une base de données NoSQL). Par exemple, convertir `datetime` en `str`. + +{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *} + +On utilise `PUT` pour recevoir des données qui doivent remplacer les données existantes. + +### Avertissement concernant le remplacement { #warning-about-replacing } + +Cela signifie que si vous souhaitez mettre à jour l’élément `bar` avec `PUT` et un corps contenant : + +```Python +{ + "name": "Barz", + "price": 3, + "description": None, +} +``` + +comme il n’inclut pas l’attribut déjà enregistré « tax »: 20.2, le modèle d’entrée prendrait la valeur par défaut « tax »: 10.5. + +Et les données seraient enregistrées avec cette « nouvelle » `tax` de `10.5`. + +## Effectuer des mises à jour partielles avec `PATCH` { #partial-updates-with-patch } + +Vous pouvez également utiliser l’opération HTTP `PATCH` pour mettre à jour des données de manière partielle. + +Cela signifie que vous pouvez n’envoyer que les données que vous souhaitez mettre à jour, en laissant le reste intact. + +/// note | Remarque + +`PATCH` est moins utilisé et moins connu que `PUT`. + +Et de nombreuses équipes n’utilisent que `PUT`, même pour les mises à jour partielles. + +Vous êtes libre de les utiliser comme vous le souhaitez, **FastAPI** n’impose aucune restriction. + +Mais ce guide vous montre, plus ou moins, la façon dont ils sont censés être utilisés. + +/// + +### Utiliser le paramètre `exclude_unset` de Pydantic { #using-pydantics-exclude-unset-parameter } + +Si vous souhaitez recevoir des mises à jour partielles, il est très utile d’utiliser le paramètre `exclude_unset` dans la méthode `.model_dump()` du modèle Pydantic. + +Comme `item.model_dump(exclude_unset=True)`. + +Cela génère un `dict` ne contenant que les données définies lors de la création du modèle `item`, en excluant les valeurs par défaut. + +Vous pouvez ensuite l’utiliser pour produire un `dict` avec uniquement les données définies (envoyées dans la requête), en omettant les valeurs par défaut : + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *} + +### Utiliser le paramètre `update` de Pydantic { #using-pydantics-update-parameter } + +Vous pouvez maintenant créer une copie du modèle existant avec `.model_copy()`, et passer le paramètre `update` avec un `dict` contenant les données à mettre à jour. + +Comme `stored_item_model.model_copy(update=update_data)` : + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} + +### Récapitulatif des mises à jour partielles { #partial-updates-recap } + +En résumé, pour appliquer des mises à jour partielles, vous procédez ainsi : + +* (Optionnel) utilisez `PATCH` au lieu de `PUT`. +* Récupérez les données stockées. +* Placez ces données dans un modèle Pydantic. +* Générez un `dict` sans valeurs par défaut à partir du modèle d’entrée (en utilisant `exclude_unset`). + * De cette façon, vous mettez à jour uniquement les valeurs effectivement définies par l’utilisateur, au lieu d’écraser des valeurs déjà stockées par des valeurs par défaut de votre modèle. +* Créez une copie du modèle stocké, en mettant à jour ses attributs avec les mises à jour partielles reçues (en utilisant le paramètre `update`). +* Convertissez le modèle copié en quelque chose qui peut être stocké dans votre base de données (par exemple en utilisant le `jsonable_encoder`). + * Cela est comparable à l’utilisation à nouveau de la méthode `.model_dump()` du modèle, mais cela vérifie (et convertit) les valeurs vers des types pouvant être convertis en JSON, par exemple `datetime` en `str`. +* Enregistrez les données dans votre base de données. +* Retournez le modèle mis à jour. + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *} + +/// tip | Astuce + +Vous pouvez en réalité utiliser cette même technique avec une opération HTTP `PUT`. + +Mais l’exemple ici utilise `PATCH` car il a été créé pour ces cas d’usage. + +/// + +/// note | Remarque + +Remarquez que le modèle d’entrée est toujours validé. + +Ainsi, si vous souhaitez recevoir des mises à jour partielles pouvant omettre tous les attributs, vous devez disposer d’un modèle avec tous les attributs marqués comme optionnels (avec des valeurs par défaut ou `None`). + +Pour distinguer les modèles avec toutes les valeurs optionnelles pour les mises à jour et les modèles avec des valeurs requises pour la création, vous pouvez utiliser les idées décrites dans [Modèles supplémentaires](extra-models.md){.internal-link target=_blank}. + +/// diff --git a/docs/fr/docs/tutorial/body.md b/docs/fr/docs/tutorial/body.md index ca115fabc..a8703e030 100644 --- a/docs/fr/docs/tutorial/body.md +++ b/docs/fr/docs/tutorial/body.md @@ -10,7 +10,7 @@ Pour déclarer un corps de **requête**, on utilise les modèles de -Et seront aussi utilisés dans chaque *opération de chemin* de la documentation utilisant ces modèles : +Et seront aussi utilisés dans chaque *chemin d'accès* de la documentation utilisant ces modèles : @@ -115,7 +115,7 @@ Ce qui améliore le support pour les modèles Pydantic avec : * de l'autocomplétion * des vérifications de type -* du « refactoring » (ou remaniement de code) +* du « refactoring » * de la recherche * des inspections @@ -129,7 +129,7 @@ Dans la fonction, vous pouvez accéder à tous les attributs de l'objet du modè ## Corps de la requête + paramètres de chemin { #request-body-path-parameters } -Vous pouvez déclarer des paramètres de chemin et un corps de requête pour la même *opération de chemin*. +Vous pouvez déclarer des paramètres de chemin et un corps de requête pour la même *chemin d'accès*. **FastAPI** est capable de reconnaître que les paramètres de la fonction qui correspondent aux paramètres de chemin doivent être **récupérés depuis le chemin**, et que les paramètres de fonctions déclarés comme modèles Pydantic devraient être **récupérés depuis le corps de la requête**. @@ -137,7 +137,7 @@ Vous pouvez déclarer des paramètres de chemin et un corps de requête pour la ## Corps de la requête + paramètres de chemin et de requête { #request-body-path-query-parameters } -Vous pouvez aussi déclarer un **corps**, et des paramètres de **chemin** et de **requête** dans la même *opération de chemin*. +Vous pouvez aussi déclarer un **corps**, et des paramètres de **chemin** et de **requête** dans la même *chemin d'accès*. **FastAPI** saura reconnaître chacun d'entre eux et récupérer la bonne donnée au bon endroit. @@ -153,7 +153,7 @@ Les paramètres de la fonction seront reconnus comme tel : **FastAPI** saura que la valeur de `q` n'est pas requise grâce à la valeur par défaut `= None`. -L'annotation de type `str | None` (Python 3.10+) ou `Union` dans `Union[str, None]` (Python 3.9+) n'est pas utilisée par **FastAPI** pour déterminer que la valeur n'est pas requise, il le saura parce qu'elle a une valeur par défaut `= None`. +L'annotation de type `str | None` n'est pas utilisée par **FastAPI** pour déterminer que la valeur n'est pas requise, il le saura parce qu'elle a une valeur par défaut `= None`. Mais ajouter ces annotations de type permettra à votre éditeur de vous offrir un meilleur support et de détecter des erreurs. diff --git a/docs/fr/docs/tutorial/cookie-param-models.md b/docs/fr/docs/tutorial/cookie-param-models.md new file mode 100644 index 000000000..c6fc2f826 --- /dev/null +++ b/docs/fr/docs/tutorial/cookie-param-models.md @@ -0,0 +1,76 @@ +# Modèles de paramètres de cookies { #cookie-parameter-models } + +Si vous avez un groupe de **cookies** liés, vous pouvez créer un **modèle Pydantic** pour les déclarer. 🍪 + +Cela vous permet de **réutiliser le modèle** à **plusieurs endroits** et aussi de déclarer des validations et des métadonnées pour tous les paramètres en une seule fois. 😎 + +/// note | Remarque + +Ceci est pris en charge depuis la version `0.115.0` de FastAPI. 🤓 + +/// + +/// tip | Astuce + +Cette même technique s'applique à `Query`, `Cookie` et `Header`. 😎 + +/// + +## Déclarer des cookies avec un modèle Pydantic { #cookies-with-a-pydantic-model } + +Déclarez les paramètres de **cookie** dont vous avez besoin dans un **modèle Pydantic**, puis déclarez le paramètre comme `Cookie` : + +{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} + +**FastAPI** va **extraire** les données pour **chaque champ** à partir des **cookies** reçus dans la requête et vous fournir le modèle Pydantic que vous avez défini. + +## Consulter la documentation { #check-the-docs } + +Vous pouvez voir les cookies définis dans l'interface de la documentation à `/docs` : + +
+ +
+ +/// info + +Gardez à l'esprit que, comme les **navigateurs gèrent les cookies** de manière particulière et en arrière-plan, ils **n'autorisent pas** facilement **JavaScript** à y accéder. + +Si vous allez dans **l'interface de la documentation de l'API** à `/docs`, vous pourrez voir la **documentation** des cookies pour vos *chemins d'accès*. + +Mais même si vous **remplissez les données** et cliquez sur « Execute », comme l'interface de la documentation fonctionne avec **JavaScript**, les cookies ne seront pas envoyés et vous verrez un **message d'erreur** comme si vous n'aviez saisi aucune valeur. + +/// + +## Interdire les cookies supplémentaires { #forbid-extra-cookies } + +Dans certains cas d'utilisation particuliers (probablement peu courants), vous pourriez vouloir **restreindre** les cookies que vous souhaitez recevoir. + +Votre API a désormais le pouvoir de contrôler son propre consentement aux cookies. 🤪🍪 + +Vous pouvez utiliser la configuration du modèle de Pydantic pour `forbid` tout champ `extra` : + +{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *} + +Si un client tente d'envoyer des **cookies supplémentaires**, il recevra une **réponse d'erreur**. + +Pauvres bannières de cookies, avec tous leurs efforts pour obtenir votre consentement pour que l'API pour le rejeter. 🍪 + +Par exemple, si le client tente d'envoyer un cookie `santa_tracker` avec la valeur `good-list-please`, il recevra une **réponse d'erreur** lui indiquant que le `santa_tracker` le cookie n'est pas autorisé : + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "santa_tracker"], + "msg": "Extra inputs are not permitted", + "input": "good-list-please", + } + ] +} +``` + +## Récapitulatif { #summary } + +Vous pouvez utiliser des **modèles Pydantic** pour déclarer des **cookies** dans **FastAPI**. 😎 diff --git a/docs/fr/docs/tutorial/cookie-params.md b/docs/fr/docs/tutorial/cookie-params.md new file mode 100644 index 000000000..8f77d35dc --- /dev/null +++ b/docs/fr/docs/tutorial/cookie-params.md @@ -0,0 +1,45 @@ +# Paramètres de cookie { #cookie-parameters } + +Vous pouvez définir des paramètres de cookie de la même manière que vous définissez les paramètres `Query` et `Path`. + +## Importer `Cookie` { #import-cookie } + +Commencez par importer `Cookie` : + +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *} + +## Déclarer des paramètres `Cookie` { #declare-cookie-parameters } + +Déclarez ensuite les paramètres de cookie en utilisant la même structure qu'avec `Path` et `Query`. + +Vous pouvez définir la valeur par défaut ainsi que tous les paramètres supplémentaires de validation ou d'annotation : + +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *} + +/// note | Détails techniques + +`Cookie` est une classe « sœur » de `Path` et `Query`. Elle hérite également de la même classe commune `Param`. + +Mais rappelez-vous que lorsque vous importez `Query`, `Path`, `Cookie` et d'autres depuis `fastapi`, il s'agit en réalité de fonctions qui renvoient des classes spéciales. + +/// + +/// info + +Pour déclarer des cookies, vous devez utiliser `Cookie`, sinon les paramètres seraient interprétés comme des paramètres de requête. + +/// + +/// info + +Gardez à l'esprit que, comme **les navigateurs gèrent les cookies** de manière particulière et en coulisses, ils **n'autorisent pas** facilement **JavaScript** à y accéder. + +Si vous allez dans l'**interface de la documentation de l'API** à `/docs`, vous pourrez voir la **documentation** des cookies pour vos *chemins d'accès*. + +Mais même si vous **renseignez les données** et cliquez sur « Execute », comme l'interface de documentation fonctionne avec **JavaScript**, les cookies ne seront pas envoyés et vous verrez un message **d'erreur** comme si vous n'aviez saisi aucune valeur. + +/// + +## Récapitulatif { #recap } + +Déclarez les cookies avec `Cookie`, en utilisant le même schéma commun que `Query` et `Path`. diff --git a/docs/fr/docs/tutorial/cors.md b/docs/fr/docs/tutorial/cors.md new file mode 100644 index 000000000..3ae7de07c --- /dev/null +++ b/docs/fr/docs/tutorial/cors.md @@ -0,0 +1,88 @@ +# CORS (Partage des ressources entre origines) { #cors-cross-origin-resource-sharing } + +
CORS ou « Cross-Origin Resource Sharing » fait référence aux situations où un frontend exécuté dans un navigateur contient du code JavaScript qui communique avec un backend, et où le backend se trouve dans une « origine » différente de celle du frontend. + +## Origine { #origin } + +Une origine est la combinaison du protocole (`http`, `https`), du domaine (`myapp.com`, `localhost`, `localhost.tiangolo.com`) et du port (`80`, `443`, `8080`). + +Ainsi, toutes celles-ci sont des origines différentes : + +* `http://localhost` +* `https://localhost` +* `http://localhost:8080` + +Même si elles sont toutes sur `localhost`, elles utilisent des protocoles ou des ports différents, ce sont donc des « origines » différentes. + +## Étapes { #steps } + +Disons donc que vous avez un frontend exécuté dans votre navigateur à `http://localhost:8080`, et que son JavaScript essaie de communiquer avec un backend exécuté à `http://localhost` (comme nous ne spécifions pas de port, le navigateur supposera le port par défaut `80`). + +Le navigateur enverra alors une requête HTTP `OPTIONS` au backend `:80`, et si le backend envoie les en-têtes appropriés autorisant la communication depuis cette origine différente (`http://localhost:8080`), alors le navigateur `:8080` permettra au JavaScript du frontend d’envoyer sa requête au backend `:80`. + +Pour y parvenir, le backend `:80` doit disposer d’une liste « d’origines autorisées ». + +Dans ce cas, la liste devrait inclure `http://localhost:8080` pour que le frontend `:8080` fonctionne correctement. + +## Caractères génériques { #wildcards } + +Il est également possible de déclarer la liste comme « * » (un « wildcard ») pour indiquer que toutes sont autorisées. + +Mais cela n’autorisera que certains types de communication, en excluant tout ce qui implique des informations d’identification : cookies, en-têtes Authorization comme ceux utilisés avec les Bearer Tokens, etc. + +Ainsi, pour que tout fonctionne correctement, il est préférable d’indiquer explicitement les origines autorisées. + +## Utiliser `CORSMiddleware` { #use-corsmiddleware } + +Vous pouvez le configurer dans votre application **FastAPI** à l’aide de `CORSMiddleware`. + +* Importer `CORSMiddleware`. +* Créer une liste d’origines autorisées (sous forme de chaînes). +* L’ajouter comme « middleware » à votre application **FastAPI**. + +Vous pouvez également spécifier si votre backend autorise : + +* Les informations d’identification (en-têtes Authorization, cookies, etc.). +* Des méthodes HTTP spécifiques (`POST`, `PUT`) ou toutes avec le caractère générique « * ». +* Des en-têtes HTTP spécifiques ou tous avec le caractère générique « * ». + +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} + +Les paramètres utilisés par défaut par l’implémentation de `CORSMiddleware` sont restrictifs, vous devez donc activer explicitement des origines, méthodes ou en-têtes particuliers afin que les navigateurs soient autorisés à les utiliser dans un contexte inter‑domaine. + +Les arguments suivants sont pris en charge : + +* `allow_origins` - Une liste d’origines autorisées à effectuer des requêtes cross-origin. Par ex. `['https://example.org', 'https://www.example.org']`. Vous pouvez utiliser `['*']` pour autoriser n’importe quelle origine. +* `allow_origin_regex` - Une chaîne regex pour faire correspondre les origines autorisées à effectuer des requêtes cross-origin. Par ex. `'https://.*\.example\.org'`. +* `allow_methods` - Une liste de méthodes HTTP qui doivent être autorisées pour les requêtes cross-origin. Par défaut `['GET']`. Vous pouvez utiliser `['*']` pour autoriser toutes les méthodes standard. +* `allow_headers` - Une liste d’en-têtes HTTP de requête qui doivent être pris en charge pour les requêtes cross-origin. Par défaut `[]`. Vous pouvez utiliser `['*']` pour autoriser tous les en-têtes. Les en-têtes `Accept`, `Accept-Language`, `Content-Language` et `Content-Type` sont toujours autorisés pour les requêtes CORS simples. +* `allow_credentials` - Indique que les cookies doivent être pris en charge pour les requêtes cross-origin. Par défaut `False`. + + Aucun de `allow_origins`, `allow_methods` et `allow_headers` ne peut être défini à `['*']` si `allow_credentials` est défini à `True`. Ils doivent tous être spécifiés explicitement. + +* `expose_headers` - Indique les en-têtes de réponse qui doivent être accessibles au navigateur. Par défaut `[]`. +* `max_age` - Définit un temps maximum (en secondes) pendant lequel les navigateurs peuvent mettre en cache les réponses CORS. Par défaut `600`. + +Le middleware répond à deux types particuliers de requêtes HTTP ... + +### Requêtes CORS de pré‑vérification { #cors-preflight-requests } + +Il s’agit de toute requête `OPTIONS` avec les en-têtes `Origin` et `Access-Control-Request-Method`. + +Dans ce cas, le middleware interceptera la requête entrante et répondra avec les en-têtes CORS appropriés, et soit une réponse `200`, soit `400` à titre informatif. + +### Requêtes simples { #simple-requests } + +Toute requête avec un en-tête `Origin`. Dans ce cas, le middleware laissera passer la requête normalement, mais inclura les en-têtes CORS appropriés dans la réponse. + +## En savoir plus { #more-info } + +Pour plus d’informations sur CORS, consultez la documentation CORS de Mozilla. + +/// note | Détails techniques + +Vous pouvez également utiliser `from starlette.middleware.cors import CORSMiddleware`. + +**FastAPI** fournit plusieurs middlewares dans `fastapi.middleware` uniquement pour votre confort, en tant que développeur. Mais la plupart des middlewares disponibles proviennent directement de Starlette. + +/// diff --git a/docs/fr/docs/tutorial/debugging.md b/docs/fr/docs/tutorial/debugging.md index a88fa2b23..d69e6a3ba 100644 --- a/docs/fr/docs/tutorial/debugging.md +++ b/docs/fr/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ Vous pouvez connecter le débogueur da Dans votre application FastAPI, importez et exécutez directement `uvicorn` : -{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} ### À propos de `__name__ == "__main__"` { #about-name-main } @@ -87,7 +87,7 @@ Parce que vous exécutez le serveur Uvicorn directement depuis votre code, vous Par exemple, dans Visual Studio Code, vous pouvez : - Allez dans le panneau « Debug ». -- « Add configuration... ». +- « Add configuration ... ». - Sélectionnez « Python ». - Lancez le débogueur avec l'option « Python: Current File (Integrated Terminal) ». @@ -102,7 +102,7 @@ Voici à quoi cela pourrait ressembler : Si vous utilisez Pycharm, vous pouvez : - Ouvrez le menu « Run ». -- Sélectionnez l'option « Debug... ». +- Sélectionnez l'option « Debug ... ». - Un menu contextuel s'affiche alors. - Sélectionnez le fichier à déboguer (dans ce cas, `main.py`). diff --git a/docs/fr/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/fr/docs/tutorial/dependencies/classes-as-dependencies.md new file mode 100644 index 000000000..69bc6008a --- /dev/null +++ b/docs/fr/docs/tutorial/dependencies/classes-as-dependencies.md @@ -0,0 +1,288 @@ +# Utiliser des classes comme dépendances { #classes-as-dependencies } + +Avant d'aller plus loin dans le système d'**Injection de dépendances**, mettons à niveau l'exemple précédent. + +## Un `dict` de l'exemple précédent { #a-dict-from-the-previous-example } + +Dans l'exemple précédent, nous renvoyions un `dict` depuis notre dépendance (« dependable ») : + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *} + +Mais nous recevons alors un `dict` dans le paramètre `commons` de la fonction de chemin d'accès. + +Et les éditeurs ne peuvent pas apporter beaucoup d'assistance (comme l'autocomplétion) pour les `dict`, car ils ne peuvent pas connaître leurs clés ni les types de valeurs. + +Nous pouvons faire mieux ... + +## Ce qui fait d'un objet une dépendance { #what-makes-a-dependency } + +Jusqu'à présent, vous avez vu des dépendances déclarées sous forme de fonctions. + +Mais ce n'est pas la seule manière de déclarer des dépendances (même si c'est probablement la plus courante). + +L'élément clé est qu'une dépendance doit être un « callable ». + +Un « callable » en Python est tout ce que Python peut « appeler » comme une fonction. + +Ainsi, si vous avez un objet `something` (qui n'est peut‑être pas une fonction) et que vous pouvez « l'appeler » (l'exécuter) comme : + +```Python +something() +``` + +ou + +```Python +something(some_argument, some_keyword_argument="foo") +``` + +alors c'est un « callable ». + +## Utiliser des classes comme dépendances { #classes-as-dependencies_1 } + +Vous remarquerez que pour créer une instance d'une classe Python, vous utilisez la même syntaxe. + +Par exemple : + +```Python +class Cat: + def __init__(self, name: str): + self.name = name + + +fluffy = Cat(name="Mr Fluffy") +``` + +Dans ce cas, `fluffy` est une instance de la classe `Cat`. + +Et pour créer `fluffy`, vous « appelez » `Cat`. + +Donc, une classe Python est aussi un « callable ». + +Ainsi, avec **FastAPI**, vous pouvez utiliser une classe Python comme dépendance. + +Ce que **FastAPI** vérifie réellement, c'est qu'il s'agit d'un « callable » (fonction, classe ou autre) et des paramètres qui y sont définis. + +Si vous passez un « callable » comme dépendance dans **FastAPI**, il en analysera les paramètres et les traitera de la même manière que les paramètres d'une fonction de chemin d'accès. Y compris les sous‑dépendances. + +Cela s'applique également aux callables sans aucun paramètre. Comme ce serait le cas pour des fonctions de chemin d'accès sans paramètres. + +Nous pouvons alors remplacer la dépendance « dependable » `common_parameters` ci‑dessus par la classe `CommonQueryParams` : + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *} + +Faites attention à la méthode `__init__` utilisée pour créer l'instance de la classe : + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *} + +... il a les mêmes paramètres que notre précédent `common_parameters` : + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *} + +Ce sont ces paramètres que **FastAPI** utilisera pour « résoudre » la dépendance. + +Dans les deux cas, il y aura : + +- Un paramètre de requête optionnel `q` qui est un `str`. +- Un paramètre de requête `skip` qui est un `int`, avec une valeur par défaut de `0`. +- Un paramètre de requête `limit` qui est un `int`, avec une valeur par défaut de `100`. + +Dans les deux cas, les données seront converties, validées, documentées dans le schéma OpenAPI, etc. + +## Utiliser { #use-it } + +Vous pouvez maintenant déclarer votre dépendance en utilisant cette classe. + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *} + +**FastAPI** appelle la classe `CommonQueryParams`. Cela crée une « instance » de cette classe et l'instance sera passée comme paramètre `commons` à votre fonction. + +## Annotation de type vs `Depends` { #type-annotation-vs-depends } + +Remarquez que nous écrivons `CommonQueryParams` deux fois dans le code ci‑dessus : + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ sans Annotated + +/// tip | Astuce + +Privilégiez la version avec `Annotated` si possible. + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +Le dernier `CommonQueryParams`, dans : + +```Python +... Depends(CommonQueryParams) +``` + +... est ce que **FastAPI** utilisera réellement pour savoir quelle est la dépendance. + +C'est à partir de celui‑ci que FastAPI extraira les paramètres déclarés et c'est ce que FastAPI appellera effectivement. + +--- + +Dans ce cas, le premier `CommonQueryParams`, dans : + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, ... +``` + +//// + +//// tab | Python 3.10+ sans Annotated + +/// tip | Astuce + +Privilégiez la version avec `Annotated` si possible. + +/// + +```Python +commons: CommonQueryParams ... +``` + +//// + +... n'a aucune signification particulière pour **FastAPI**. FastAPI ne l'utilisera pas pour la conversion des données, la validation, etc. (car il utilise `Depends(CommonQueryParams)` pour cela). + +Vous pourriez en fait écrire simplement : + +//// tab | Python 3.10+ + +```Python +commons: Annotated[Any, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ sans Annotated + +/// tip | Astuce + +Privilégiez la version avec `Annotated` si possible. + +/// + +```Python +commons = Depends(CommonQueryParams) +``` + +//// + +... comme dans : + +{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *} + +Mais il est recommandé de déclarer le type ; ainsi, votre éditeur saura ce qui sera passé comme paramètre `commons`, et pourra vous aider avec l'autocomplétion, les vérifications de type, etc. : + + + +## Raccourci { #shortcut } + +Mais vous voyez qu'il y a ici de la duplication de code : nous écrivons `CommonQueryParams` deux fois : + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ sans Annotated + +/// tip | Astuce + +Privilégiez la version avec `Annotated` si possible. + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +**FastAPI** fournit un raccourci pour ces cas, lorsque la dépendance est spécifiquement une classe que **FastAPI** va « appeler » pour créer une instance de la classe elle‑même. + +Pour ces cas précis, vous pouvez faire ce qui suit : + +Au lieu d'écrire : + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ sans Annotated + +/// tip | Astuce + +Privilégiez la version avec `Annotated` si possible. + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +... vous écrivez : + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends()] +``` + +//// + +//// tab | Python 3.10+ sans Annotated + +/// tip | Astuce + +Privilégiez la version avec `Annotated` si possible. + +/// + +```Python +commons: CommonQueryParams = Depends() +``` + +//// + +Vous déclarez la dépendance comme type du paramètre et vous utilisez `Depends()` sans aucun paramètre, au lieu d'avoir à réécrire la classe entière à l'intérieur de `Depends(CommonQueryParams)`. + +Le même exemple ressemblerait alors à ceci : + +{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *} + +... et **FastAPI** saura quoi faire. + +/// tip | Astuce + +Si cela vous semble plus déroutant qu'utile, ignorez‑le, vous n'en avez pas besoin. + +Ce n'est qu'un raccourci. Parce que **FastAPI** tient à vous aider à minimiser la duplication de code. + +/// diff --git a/docs/fr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/fr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md new file mode 100644 index 000000000..bf697fe8d --- /dev/null +++ b/docs/fr/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -0,0 +1,69 @@ +# Gérer les dépendances dans les décorateurs de chemins d'accès { #dependencies-in-path-operation-decorators } + +Dans certains cas, vous n'avez pas vraiment besoin de la valeur de retour d'une dépendance dans votre *fonction de chemin d'accès*. + +Ou la dépendance ne retourne aucune valeur. + +Mais vous avez quand même besoin qu'elle soit exécutée/résolue. + +Dans ces cas, au lieu de déclarer un paramètre de *fonction de chemin d'accès* avec `Depends`, vous pouvez ajouter une `list` de `dependencies` au *décorateur de chemin d'accès*. + +## Ajouter `dependencies` au *décorateur de chemin d'accès* { #add-dependencies-to-the-path-operation-decorator } + +Le *décorateur de chemin d'accès* accepte un argument optionnel `dependencies`. + +Il doit s'agir d'une `list` de `Depends()` : + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} + +Ces dépendances seront exécutées/résolues de la même manière que des dépendances normales. Mais leur valeur (si elles en retournent une) ne sera pas transmise à votre *fonction de chemin d'accès*. + +/// tip | Astuce + +Certains éditeurs vérifient les paramètres de fonction non utilisés et les signalent comme des erreurs. + +En utilisant ces `dependencies` dans le *décorateur de chemin d'accès*, vous pouvez vous assurer qu'elles sont exécutées tout en évitant des erreurs de l'éditeur/des outils. + +Cela peut également éviter toute confusion pour les nouveaux développeurs qui voient un paramètre inutilisé dans votre code et pourraient penser qu'il est superflu. + +/// + +/// info | Info + +Dans cet exemple, nous utilisons des en-têtes personnalisés fictifs `X-Key` et `X-Token`. + +Mais dans des cas réels, lors de l'implémentation de la sécurité, vous tirerez davantage d'avantages en utilisant les [utilitaires de sécurité (chapitre suivant)](../security/index.md){.internal-link target=_blank} intégrés. + +/// + +## Gérer les erreurs et les valeurs de retour des dépendances { #dependencies-errors-and-return-values } + +Vous pouvez utiliser les mêmes *fonctions* de dépendance que d'habitude. + +### Définir les exigences des dépendances { #dependency-requirements } + +Elles peuvent déclarer des exigences pour la requête (comme des en-têtes) ou d'autres sous-dépendances : + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} + +### Lever des exceptions { #raise-exceptions } + +Ces dépendances peuvent `raise` des exceptions, comme des dépendances normales : + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} + +### Gérer les valeurs de retour { #return-values } + +Elles peuvent retourner des valeurs ou non, ces valeurs ne seront pas utilisées. + +Vous pouvez donc réutiliser une dépendance normale (qui retourne une valeur) que vous utilisez déjà ailleurs ; même si la valeur n'est pas utilisée, la dépendance sera exécutée : + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} + +## Définir des dépendances pour un groupe de chemins d'accès { #dependencies-for-a-group-of-path-operations } + +Plus tard, en lisant comment structurer des applications plus grandes ([Applications plus grandes - Plusieurs fichiers](../../tutorial/bigger-applications.md){.internal-link target=_blank}), éventuellement avec plusieurs fichiers, vous apprendrez à déclarer un unique paramètre `dependencies` pour un groupe de *chemins d'accès*. + +## Définir des dépendances globales { #global-dependencies } + +Ensuite, nous verrons comment ajouter des dépendances à l'application `FastAPI` entière, afin qu'elles s'appliquent à chaque *chemin d'accès*. diff --git a/docs/fr/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/fr/docs/tutorial/dependencies/dependencies-with-yield.md new file mode 100644 index 000000000..3f06df767 --- /dev/null +++ b/docs/fr/docs/tutorial/dependencies/dependencies-with-yield.md @@ -0,0 +1,289 @@ +# Utiliser des dépendances avec `yield` { #dependencies-with-yield } + +FastAPI prend en charge des dépendances qui effectuent des étapes supplémentaires après l'exécution. + +Pour cela, utilisez `yield` au lieu de `return`, et écrivez les étapes supplémentaires (code) après. + +/// tip | Astuce + +Vous devez vous assurer d'utiliser `yield` une seule fois par dépendance. + +/// + +/// note | Détails techniques + +Toute fonction valide à utiliser avec : + +* `@contextlib.contextmanager` ou +* `@contextlib.asynccontextmanager` + +sera valide comme dépendance **FastAPI**. + +En fait, FastAPI utilise ces deux décorateurs en interne. + +/// + +## Créer une dépendance de base de données avec `yield` { #a-database-dependency-with-yield } + +Par exemple, vous pouvez l'utiliser pour créer une session de base de données et la fermer après la fin. + +Seul le code précédant et incluant l'instruction `yield` est exécuté avant la création de la réponse : + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[2:4] *} + +La valeur transmise par `yield` est celle qui est injectée dans les *chemins d'accès* et autres dépendances : + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[4] *} + +Le code suivant l'instruction `yield` est exécuté après la réponse : + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[5:6] *} + +/// tip | Astuce + +Vous pouvez utiliser des fonctions `async` ou des fonctions classiques. + +**FastAPI** fera ce qu'il faut dans chaque cas, comme avec des dépendances normales. + +/// + +## Créer une dépendance avec `yield` et `try` { #a-dependency-with-yield-and-try } + +Si vous utilisez un bloc `try` dans une dépendance avec `yield`, vous recevrez toute exception qui a été levée lors de l'utilisation de la dépendance. + +Par exemple, si à un moment donné, dans une autre dépendance ou dans un *chemin d'accès*, un code a effectué un « rollback » de transaction de base de données ou a créé une autre exception, vous recevrez l'exception dans votre dépendance. + +Vous pouvez donc rechercher cette exception spécifique dans la dépendance avec `except SomeException`. + +De la même manière, vous pouvez utiliser `finally` pour vous assurer que les étapes de sortie sont exécutées, qu'il y ait eu une exception ou non. + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[3,5] *} + +## Utiliser des sous-dépendances avec `yield` { #sub-dependencies-with-yield } + +Vous pouvez avoir des sous-dépendances et des « arbres » de sous-dépendances de toute taille et forme, et certaines ou toutes peuvent utiliser `yield`. + +**FastAPI** s'assurera que le « code de sortie » dans chaque dépendance avec `yield` est exécuté dans le bon ordre. + +Par exemple, `dependency_c` peut dépendre de `dependency_b`, et `dependency_b` de `dependency_a` : + +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[6,14,22] *} + +Et elles peuvent toutes utiliser `yield`. + +Dans ce cas, `dependency_c`, pour exécuter son code de sortie, a besoin que la valeur de `dependency_b` (appelée ici `dep_b`) soit toujours disponible. + +Et, à son tour, `dependency_b` a besoin que la valeur de `dependency_a` (appelée ici `dep_a`) soit disponible pour son code de sortie. + +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[18:19,26:27] *} + +De la même manière, vous pouvez avoir certaines dépendances avec `yield` et d'autres avec `return`, et faire en sorte que certaines dépendent des autres. + +Et vous pouvez avoir une seule dépendance qui exige plusieurs autres dépendances avec `yield`, etc. + +Vous pouvez combiner les dépendances comme vous le souhaitez. + +**FastAPI** s'assurera que tout est exécuté dans le bon ordre. + +/// note | Détails techniques + +Cela fonctionne grâce aux gestionnaires de contexte de Python. + +**FastAPI** les utilise en interne pour y parvenir. + +/// + +## Utiliser des dépendances avec `yield` et `HTTPException` { #dependencies-with-yield-and-httpexception } + +Vous avez vu que vous pouvez utiliser des dépendances avec `yield` et avoir des blocs `try` qui tentent d'exécuter du code puis exécutent du code de sortie après `finally`. + +Vous pouvez également utiliser `except` pour intercepter l'exception qui a été levée et faire quelque chose avec. + +Par exemple, vous pouvez lever une autre exception, comme `HTTPException`. + +/// tip | Astuce + +C'est une technique plutôt avancée, et dans la plupart des cas vous n'en aurez pas vraiment besoin, car vous pouvez lever des exceptions (y compris `HTTPException`) depuis le reste de votre code applicatif, par exemple, dans la *fonction de chemin d'accès*. + +Mais elle est à votre disposition si vous en avez besoin. 🤓 + +/// + +{* ../../docs_src/dependencies/tutorial008b_an_py310.py hl[18:22,31] *} + +Si vous souhaitez intercepter des exceptions et créer une réponse personnalisée en fonction de cela, créez un [Gestionnaire d'exceptions personnalisé](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. + +## Utiliser des dépendances avec `yield` et `except` { #dependencies-with-yield-and-except } + +Si vous interceptez une exception avec `except` dans une dépendance avec `yield` et que vous ne la relancez pas (ou que vous ne levez pas une nouvelle exception), FastAPI ne pourra pas remarquer qu'il y a eu une exception, de la même manière que cela se produirait avec Python classique : + +{* ../../docs_src/dependencies/tutorial008c_an_py310.py hl[15:16] *} + +Dans ce cas, le client verra une réponse *HTTP 500 Internal Server Error* comme il se doit, étant donné que nous ne levons pas de `HTTPException` ou similaire, mais le serveur **n'aura aucun logs** ni aucune autre indication de l'erreur. 😱 + +### Toujours `raise` dans les dépendances avec `yield` et `except` { #always-raise-in-dependencies-with-yield-and-except } + +Si vous interceptez une exception dans une dépendance avec `yield`, à moins de lever une autre `HTTPException` ou similaire, **vous devez relancer l'exception d'origine**. + +Vous pouvez relancer la même exception avec `raise` : + +{* ../../docs_src/dependencies/tutorial008d_an_py310.py hl[17] *} + +À présent, le client recevra la même réponse *HTTP 500 Internal Server Error*, mais le serveur aura notre `InternalError` personnalisé dans les logs. 😎 + +## Comprendre l'exécution des dépendances avec `yield` { #execution-of-dependencies-with-yield } + +La séquence d'exécution ressemble plus ou moins à ce diagramme. Le temps s'écoule de haut en bas. Et chaque colonne représente une des parties qui interagit ou exécute du code. + +```mermaid +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,operation: Can raise exceptions, including HTTPException + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise Exception + dep -->> handler: Raise Exception + handler -->> client: HTTP error response + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise Exception (e.g. HTTPException) + opt handle + dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception + end + handler -->> client: HTTP error response + end + + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> tasks: Handle exceptions in the background task code + end +``` + +/// info + +Une **seule réponse** sera envoyée au client. Il peut s'agir d'une des réponses d'erreur ou de la réponse provenant du *chemin d'accès*. + +Après l'envoi de l'une de ces réponses, aucune autre réponse ne peut être envoyée. + +/// + +/// tip | Astuce + +Si vous levez une exception dans le code de la *fonction de chemin d'accès*, elle sera transmise aux dépendances avec `yield`, y compris `HTTPException`. Dans la plupart des cas, vous voudrez relancer cette même exception ou en lever une nouvelle depuis la dépendance avec `yield` pour vous assurer qu'elle est correctement gérée. + +/// + +## Utiliser la sortie anticipée et `scope` { #early-exit-and-scope } + +Normalement, le code de sortie des dépendances avec `yield` est exécuté **après la réponse** envoyée au client. + +Mais si vous savez que vous n'aurez pas besoin d'utiliser la dépendance après être revenu de la *fonction de chemin d'accès*, vous pouvez utiliser `Depends(scope="function")` pour indiquer à FastAPI qu'il doit fermer la dépendance après le retour de la *fonction de chemin d'accès*, mais **avant** que la **réponse ne soit envoyée**. + +{* ../../docs_src/dependencies/tutorial008e_an_py310.py hl[12,16] *} + +`Depends()` reçoit un paramètre `scope` qui peut être : + +* « function » : démarrer la dépendance avant la *fonction de chemin d'accès* qui gère la requête, terminer la dépendance après la fin de la *fonction de chemin d'accès*, mais **avant** que la réponse ne soit renvoyée au client. Ainsi, la fonction de dépendance sera exécutée **autour** de la *fonction de chemin d'accès*. +* « request » : démarrer la dépendance avant la *fonction de chemin d'accès* qui gère la requête (similaire à l'utilisation de « function »), mais terminer **après** que la réponse a été renvoyée au client. Ainsi, la fonction de dépendance sera exécutée **autour** du cycle **requête** et réponse. + +S'il n'est pas spécifié et que la dépendance utilise `yield`, le `scope` sera par défaut « request ». + +### Définir `scope` pour les sous-dépendances { #scope-for-sub-dependencies } + +Lorsque vous déclarez une dépendance avec un `scope="request"` (par défaut), toute sous-dépendance doit également avoir un `scope` de « request ». + +Mais une dépendance avec un `scope` de « function » peut avoir des dépendances avec un `scope` de « function » et un `scope` de « request ». + +Cela vient du fait que toute dépendance doit pouvoir exécuter son code de sortie avant ses sous-dépendances, car elle pourrait encore avoir besoin de les utiliser pendant son code de sortie. + +```mermaid +sequenceDiagram + +participant client as Client +participant dep_req as Dep scope="request" +participant dep_func as Dep scope="function" +participant operation as Path Operation + + client ->> dep_req: Start request + Note over dep_req: Run code up to yield + dep_req ->> dep_func: Pass dependency + Note over dep_func: Run code up to yield + dep_func ->> operation: Run path operation with dependency + operation ->> dep_func: Return from path operation + Note over dep_func: Run code after yield + Note over dep_func: ✅ Dependency closed + dep_func ->> client: Send response to client + Note over client: Response sent + Note over dep_req: Run code after yield + Note over dep_req: ✅ Dependency closed +``` + +## Utiliser des dépendances avec `yield`, `HTTPException`, `except` et Background Tasks { #dependencies-with-yield-httpexception-except-and-background-tasks } + +Les dépendances avec `yield` ont évolué au fil du temps pour couvrir différents cas d'utilisation et corriger certains problèmes. + +Si vous souhaitez voir ce qui a changé dans différentes versions de FastAPI, vous pouvez en savoir plus dans le guide avancé, dans [Dépendances avancées - Dépendances avec `yield`, `HTTPException`, `except` et Background Tasks](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}. +## Gestionnaires de contexte { #context-managers } + +### Que sont les « Context Managers » { #what-are-context-managers } + +Les « Context Managers » sont des objets Python que vous pouvez utiliser dans une instruction `with`. + +Par exemple, vous pouvez utiliser `with` pour lire un fichier : + +```Python +with open("./somefile.txt") as f: + contents = f.read() + print(contents) +``` + +En coulisse, `open("./somefile.txt")` crée un objet appelé « Context Manager ». + +Lorsque le bloc `with` se termine, il s'assure de fermer le fichier, même s'il y a eu des exceptions. + +Lorsque vous créez une dépendance avec `yield`, **FastAPI** créera en interne un gestionnaire de contexte pour celle-ci et le combinera avec d'autres outils associés. + +### Utiliser des gestionnaires de contexte dans des dépendances avec `yield` { #using-context-managers-in-dependencies-with-yield } + +/// warning | Alertes + +C'est, plus ou moins, une idée « avancée ». + +Si vous débutez avec **FastAPI**, vous voudrez peut-être l'ignorer pour le moment. + +/// + +En Python, vous pouvez créer des gestionnaires de contexte en créant une classe avec deux méthodes : `__enter__()` et `__exit__()`. + +Vous pouvez également les utiliser dans des dépendances **FastAPI** avec `yield` en utilisant +des instructions `with` ou `async with` à l'intérieur de la fonction de dépendance : + +{* ../../docs_src/dependencies/tutorial010_py310.py hl[1:9,13] *} + +/// tip | Astuce + +Une autre façon de créer un gestionnaire de contexte consiste à utiliser : + +* `@contextlib.contextmanager` ou +* `@contextlib.asynccontextmanager` + +pour décorer une fonction avec un unique `yield`. + +C'est ce que **FastAPI** utilise en interne pour les dépendances avec `yield`. + +Mais vous n'avez pas à utiliser ces décorateurs pour les dépendances FastAPI (et vous ne devriez pas). + +FastAPI le fera pour vous en interne. + +/// diff --git a/docs/fr/docs/tutorial/dependencies/global-dependencies.md b/docs/fr/docs/tutorial/dependencies/global-dependencies.md new file mode 100644 index 000000000..2c418ee4a --- /dev/null +++ b/docs/fr/docs/tutorial/dependencies/global-dependencies.md @@ -0,0 +1,15 @@ +# Dépendances globales { #global-dependencies } + +Pour certains types d'applications, vous pourriez vouloir ajouter des dépendances à l'application entière. + +Comme vous pouvez [ajouter des `dependencies` aux *décorateurs de chemin d'accès*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, vous pouvez les ajouter à l'application `FastAPI`. + +Dans ce cas, elles seront appliquées à tous les *chemins d'accès* de l'application : + +{* ../../docs_src/dependencies/tutorial012_an_py310.py hl[17] *} + +Et toutes les idées de la section sur [l'ajout de `dependencies` aux *décorateurs de chemin d'accès*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} s'appliquent toujours, mais dans ce cas à tous les *chemins d'accès* de l'application. + +## Dépendances pour des groupes de *chemins d'accès* { #dependencies-for-groups-of-path-operations } + +Plus tard, en lisant comment structurer des applications plus grandes ([Applications plus grandes - Plusieurs fichiers](../../tutorial/bigger-applications.md){.internal-link target=_blank}), éventuellement avec plusieurs fichiers, vous apprendrez comment déclarer un unique paramètre `dependencies` pour un groupe de *chemins d'accès*. diff --git a/docs/fr/docs/tutorial/dependencies/index.md b/docs/fr/docs/tutorial/dependencies/index.md new file mode 100644 index 000000000..8fad77f62 --- /dev/null +++ b/docs/fr/docs/tutorial/dependencies/index.md @@ -0,0 +1,250 @@ +# Dépendances { #dependencies } + +**FastAPI** dispose d’un système d’**Injection de dépendances** très puissant mais intuitif. + +Il est conçu pour être très simple à utiliser, et pour faciliter l’intégration d’autres composants à **FastAPI** pour n’importe quel développeur. + +## Qu’est-ce que « l’injection de dépendances » { #what-is-dependency-injection } + +L’**« injection de dépendances »** signifie, en programmation, qu’il existe un moyen pour votre code (dans ce cas, vos fonctions de chemins d’accès) de déclarer ce dont il a besoin pour fonctionner et utiliser : « dépendances ». + +Ensuite, ce système (dans ce cas **FastAPI**) se charge de faire tout le nécessaire pour fournir à votre code ces dépendances requises (« injecter » les dépendances). + +C’est très utile lorsque vous avez besoin de : + +* Avoir de la logique partagée (la même logique de code encore et encore). +* Partager des connexions à la base de données. +* Imposer la sécurité, l’authentification, des exigences de rôles, etc. +* Et bien d’autres choses ... + +Tout cela, en minimisant la répétition de code. + +## Premiers pas { #first-steps } + +Voyons un exemple très simple. Il sera tellement simple qu’il n’est pas très utile, pour l’instant. + +Mais de cette façon nous pouvons nous concentrer sur le fonctionnement du système d’**injection de dépendances**. + +### Créer une dépendance, ou « dependable » { #create-a-dependency-or-dependable } + +Concentrons-nous d’abord sur la dépendance. + +C’est simplement une fonction qui peut prendre tous les mêmes paramètres qu’une fonction de chemin d’accès peut prendre : + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *} + +C’est tout. + +**2 lignes**. + +Et elle a la même forme et structure que toutes vos fonctions de chemins d’accès. + +Vous pouvez la considérer comme une fonction de chemin d’accès sans le « décorateur » (sans le `@app.get("/some-path")`). + +Et elle peut retourner tout ce que vous voulez. + +Dans ce cas, cette dépendance attend : + +* Un paramètre de requête optionnel `q` qui est une `str`. +* Un paramètre de requête optionnel `skip` qui est un `int`, et vaut `0` par défaut. +* Un paramètre de requête optionnel `limit` qui est un `int`, et vaut `100` par défaut. + +Puis elle retourne simplement un `dict` contenant ces valeurs. + +/// info | Info + +FastAPI a ajouté la prise en charge de `Annotated` (et a commencé à le recommander) dans la version 0.95.0. + +Si vous avez une version plus ancienne, vous obtiendrez des erreurs en essayant d’utiliser `Annotated`. + +Vous devez vous assurer de [mettre à niveau la version de FastAPI](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} vers au moins la 0.95.1 avant d’utiliser `Annotated`. + +/// + +### Importer `Depends` { #import-depends } + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *} + +### Déclarer la dépendance, dans le « dependant » { #declare-the-dependency-in-the-dependant } + +De la même manière que vous utilisez `Body`, `Query`, etc. avec les paramètres de votre fonction de chemin d’accès, utilisez `Depends` avec un nouveau paramètre : + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *} + +Même si vous utilisez `Depends` dans les paramètres de votre fonction de la même façon que `Body`, `Query`, etc., `Depends` fonctionne un peu différemment. + +Vous ne donnez à `Depends` qu’un seul paramètre. + +Ce paramètre doit être quelque chose comme une fonction. + +Vous ne l’appelez pas directement (n’ajoutez pas de parenthèses à la fin), vous le passez simplement en paramètre à `Depends()`. + +Et cette fonction prend des paramètres de la même manière que les fonctions de chemins d’accès. + +/// tip | Astuce + +Vous verrez quelles autres « choses », en plus des fonctions, peuvent être utilisées comme dépendances dans le prochain chapitre. + +/// + +Chaque fois qu’une nouvelle requête arrive, **FastAPI** se charge de : + +* Appeler votre fonction de dépendance (« dependable ») avec les bons paramètres. +* Récupérer le résultat de votre fonction. +* Affecter ce résultat au paramètre dans votre fonction de chemin d’accès. + +```mermaid +graph TB + +common_parameters(["common_parameters"]) +read_items["/items/"] +read_users["/users/"] + +common_parameters --> read_items +common_parameters --> read_users +``` + +De cette façon vous écrivez le code partagé une seule fois et **FastAPI** se charge de l’appeler pour vos chemins d’accès. + +/// check | Vérifications + +Notez que vous n’avez pas à créer une classe spéciale et à la passer quelque part à **FastAPI** pour l’« enregistrer » ou quoi que ce soit de similaire. + +Vous la passez simplement à `Depends` et **FastAPI** sait faire le reste. + +/// + +## Partager des dépendances `Annotated` { #share-annotated-dependencies } + +Dans les exemples ci-dessus, vous voyez qu’il y a un tout petit peu de **duplication de code**. + +Lorsque vous devez utiliser la dépendance `common_parameters()`, vous devez écrire tout le paramètre avec l’annotation de type et `Depends()` : + +```Python +commons: Annotated[dict, Depends(common_parameters)] +``` + +Mais comme nous utilisons `Annotated`, nous pouvons stocker cette valeur `Annotated` dans une variable et l’utiliser à plusieurs endroits : + +{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *} + +/// tip | Astuce + +C’est simplement du Python standard, cela s’appelle un « alias de type », ce n’est en fait pas spécifique à **FastAPI**. + +Mais comme **FastAPI** est basé sur les standards Python, y compris `Annotated`, vous pouvez utiliser cette astuce dans votre code. 😎 + +/// + +Les dépendances continueront de fonctionner comme prévu, et la **meilleure partie** est que **l’information de type sera conservée**, ce qui signifie que votre éditeur pourra continuer à vous fournir **l’autocomplétion**, **des erreurs en ligne**, etc. Idem pour d’autres outils comme `mypy`. + +Cela sera particulièrement utile lorsque vous l’utiliserez dans une **grande base de code** où vous utilisez **les mêmes dépendances** encore et encore dans **de nombreux chemins d’accès**. + +## Utiliser `async` ou non { #to-async-or-not-to-async } + +Comme les dépendances seront aussi appelées par **FastAPI** (tout comme vos fonctions de chemins d’accès), les mêmes règles s’appliquent lors de la définition de vos fonctions. + +Vous pouvez utiliser `async def` ou un `def` normal. + +Et vous pouvez déclarer des dépendances avec `async def` à l’intérieur de fonctions de chemins d’accès `def` normales, ou des dépendances `def` à l’intérieur de fonctions de chemins d’accès `async def`, etc. + +Peu importe. **FastAPI** saura quoi faire. + +/// note | Remarque + +Si vous ne savez pas, consultez la section [Async : *« Pressé ? »*](../../async.md#in-a-hurry){.internal-link target=_blank} à propos de `async` et `await` dans la documentation. + +/// + +## Intégrer à OpenAPI { #integrated-with-openapi } + +Toutes les déclarations de requête, validations et exigences de vos dépendances (et sous-dépendances) seront intégrées dans le même schéma OpenAPI. + +Ainsi, la documentation interactive contiendra aussi toutes les informations issues de ces dépendances : + + + +## Utilisation simple { #simple-usage } + +Si vous y regardez de près, les fonctions de chemins d’accès sont déclarées pour être utilisées chaque fois qu’un « chemin » et une « opération » correspondent, puis **FastAPI** se charge d’appeler la fonction avec les bons paramètres, en extrayant les données de la requête. + +En réalité, tous (ou la plupart) des frameworks web fonctionnent de cette manière. + +Vous n’appelez jamais ces fonctions directement. Elles sont appelées par votre framework (dans ce cas, **FastAPI**). + +Avec le système d’injection de dépendances, vous pouvez aussi indiquer à **FastAPI** que votre fonction de chemin d’accès « dépend » également d’autre chose qui doit être exécuté avant votre fonction de chemin d’accès, et **FastAPI** se chargera de l’exécuter et d’« injecter » les résultats. + +D’autres termes courants pour cette même idée « d’injection de dépendances » sont : + +* ressources +* fournisseurs +* services +* injectables +* composants + +## Plug-ins **FastAPI** { #fastapi-plug-ins } + +Les intégrations et « plug-ins » peuvent être construits en utilisant le système d’**injection de dépendances**. Mais en réalité, il n’y a **pas besoin de créer des « plug-ins »**, car en utilisant des dépendances il est possible de déclarer un nombre infini d’intégrations et d’interactions qui deviennent disponibles pour vos fonctions de chemins d’accès. + +Et les dépendances peuvent être créées de manière très simple et intuitive, ce qui vous permet d’importer juste les packages Python dont vous avez besoin, et de les intégrer à vos fonctions d’API en quelques lignes de code, *littéralement*. + +Vous verrez des exemples de cela dans les prochains chapitres, à propos des bases de données relationnelles et NoSQL, de la sécurité, etc. + +## Compatibilité **FastAPI** { #fastapi-compatibility } + +La simplicité du système d’injection de dépendances rend **FastAPI** compatible avec : + +* toutes les bases de données relationnelles +* les bases de données NoSQL +* les packages externes +* les API externes +* les systèmes d’authentification et d’autorisation +* les systèmes de supervision d’usage d’API +* les systèmes d’injection de données de réponse +* etc. + +## Simple et puissant { #simple-and-powerful } + +Bien que le système hiérarchique d’injection de dépendances soit très simple à définir et à utiliser, il reste très puissant. + +Vous pouvez définir des dépendances qui, à leur tour, peuvent définir leurs propres dépendances. + +Au final, un arbre hiérarchique de dépendances est construit, et le système d’**injection de dépendances** se charge de résoudre toutes ces dépendances pour vous (et leurs sous-dépendances) et de fournir (injecter) les résultats à chaque étape. + +Par exemple, supposons que vous ayez 4 endpoints d’API (chemins d’accès) : + +* `/items/public/` +* `/items/private/` +* `/users/{user_id}/activate` +* `/items/pro/` + +alors vous pourriez ajouter différentes exigences d’autorisations pour chacun d’eux uniquement avec des dépendances et des sous-dépendances : + +```mermaid +graph TB + +current_user(["current_user"]) +active_user(["active_user"]) +admin_user(["admin_user"]) +paying_user(["paying_user"]) + +public["/items/public/"] +private["/items/private/"] +activate_user["/users/{user_id}/activate"] +pro_items["/items/pro/"] + +current_user --> active_user +active_user --> admin_user +active_user --> paying_user + +current_user --> public +active_user --> private +admin_user --> activate_user +paying_user --> pro_items +``` + +## Intégrer à **OpenAPI** { #integrated-with-openapi_1 } + +Toutes ces dépendances, tout en déclarant leurs exigences, ajoutent également des paramètres, des validations, etc. à vos chemins d’accès. + +**FastAPI** se chargera d’ajouter le tout au schéma OpenAPI, afin que cela apparaisse dans les systèmes de documentation interactive. diff --git a/docs/fr/docs/tutorial/dependencies/sub-dependencies.md b/docs/fr/docs/tutorial/dependencies/sub-dependencies.md new file mode 100644 index 000000000..473ff02ba --- /dev/null +++ b/docs/fr/docs/tutorial/dependencies/sub-dependencies.md @@ -0,0 +1,105 @@ +# Sous-dépendances { #sub-dependencies } + +Vous pouvez créer des dépendances qui ont des sous-dépendances. + +Elles peuvent être aussi profondes que nécessaire. + +**FastAPI** se chargera de les résoudre. + +## Créer une première dépendance « dependable » { #first-dependency-dependable } + +Vous pouvez créer une première dépendance (« dependable ») comme : + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *} + +Elle déclare un paramètre de requête optionnel `q` de type `str`, puis le retourne simplement. + +C'est assez simple (pas très utile), mais cela nous aidera à nous concentrer sur le fonctionnement des sous-dépendances. + +## Créer une seconde dépendance, « dependable » et « dependant » { #second-dependency-dependable-and-dependant } + +Vous pouvez ensuite créer une autre fonction de dépendance (un « dependable ») qui, en même temps, déclare sa propre dépendance (elle est donc aussi un « dependant ») : + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *} + +Concentrons-nous sur les paramètres déclarés : + +- Même si cette fonction est elle‑même une dépendance (« dependable »), elle déclare aussi une autre dépendance (elle « dépend » d'autre chose). + - Elle dépend de `query_extractor` et affecte la valeur renvoyée au paramètre `q`. +- Elle déclare également un cookie `last_query` optionnel, de type `str`. + - Si l'utilisateur n'a fourni aucune requête `q`, nous utilisons la dernière requête utilisée, que nous avons enregistrée auparavant dans un cookie. + +## Utiliser la dépendance { #use-the-dependency } + +Nous pouvons ensuite utiliser la dépendance avec : + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[23] *} + +/// info + +Notez que nous ne déclarons qu'une seule dépendance dans la *fonction de chemin d'accès*, `query_or_cookie_extractor`. + +Mais **FastAPI** saura qu'il doit d'abord résoudre `query_extractor`, pour passer ses résultats à `query_or_cookie_extractor` lors de son appel. + +/// + +```mermaid +graph TB + +query_extractor(["query_extractor"]) +query_or_cookie_extractor(["query_or_cookie_extractor"]) + +read_query["/items/"] + +query_extractor --> query_or_cookie_extractor --> read_query +``` + +## Utiliser la même dépendance plusieurs fois { #using-the-same-dependency-multiple-times } + +Si l'une de vos dépendances est déclarée plusieurs fois pour le même *chemin d'accès*, par exemple si plusieurs dépendances ont une sous-dépendance commune, **FastAPI** saura n'appeler cette sous-dépendance qu'une seule fois par requête. + +Et il enregistrera la valeur renvoyée dans un « cache » et la transmettra à tous les « dependants » qui en ont besoin dans cette requête spécifique, au lieu d'appeler la dépendance plusieurs fois pour la même requête. + +Dans un scénario avancé où vous savez que vous avez besoin que la dépendance soit appelée à chaque étape (éventuellement plusieurs fois) dans la même requête au lieu d'utiliser la valeur « mise en cache », vous pouvez définir le paramètre `use_cache=False` lors de l'utilisation de `Depends` : + +//// tab | Python 3.10+ + +```Python hl_lines="1" +async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): + return {"fresh_value": fresh_value} +``` + +//// + +//// tab | Python 3.10+ non annoté + +/// tip | Astuce + +Privilégiez la version `Annotated` si possible. + +/// + +```Python hl_lines="1" +async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)): + return {"fresh_value": fresh_value} +``` + +//// + +## Récapituler { #recap } + +En dehors de tout le jargon utilisé ici, le système d'**injection de dépendances** est assez simple. + +Ce ne sont que des fonctions qui ressemblent aux *fonctions de chemin d'accès*. + +Mais il est très puissant et vous permet de déclarer des « graphes » (arbres) de dépendances imbriquées aussi profondément que vous le souhaitez. + +/// tip | Astuce + +Tout cela peut ne pas sembler très utile avec ces exemples simples. + +Mais vous verrez à quel point c'est utile dans les chapitres sur la **sécurité**. + +Et vous verrez aussi la quantité de code que cela vous fera économiser. + +/// diff --git a/docs/fr/docs/tutorial/encoder.md b/docs/fr/docs/tutorial/encoder.md new file mode 100644 index 000000000..f94be429c --- /dev/null +++ b/docs/fr/docs/tutorial/encoder.md @@ -0,0 +1,35 @@ +# Encodeur compatible JSON { #json-compatible-encoder } + +Il existe des cas où vous pourriez avoir besoin de convertir un type de données (comme un modèle Pydantic) en quelque chose de compatible avec JSON (comme un `dict`, `list`, etc.). + +Par exemple, si vous devez le stocker dans une base de données. + +Pour cela, **FastAPI** fournit une fonction `jsonable_encoder()`. + +## Utiliser `jsonable_encoder` { #using-the-jsonable-encoder } + +Imaginons que vous ayez une base de données `fake_db` qui ne reçoit que des données compatibles JSON. + +Par exemple, elle ne reçoit pas d'objets `datetime`, car ceux-ci ne sont pas compatibles avec JSON. + +Ainsi, un objet `datetime` doit être converti en une `str` contenant les données au format ISO. + +De la même manière, cette base de données n'accepterait pas un modèle Pydantic (un objet avec des attributs), seulement un `dict`. + +Vous pouvez utiliser `jsonable_encoder` pour cela. + +Elle reçoit un objet, comme un modèle Pydantic, et renvoie une version compatible JSON : + +{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *} + +Dans cet exemple, elle convertirait le modèle Pydantic en `dict`, et le `datetime` en `str`. + +Le résultat de son appel est quelque chose qui peut être encodé avec la fonction standard de Python `json.dumps()`. + +Elle ne renvoie pas une grande `str` contenant les données au format JSON (sous forme de chaîne). Elle renvoie une structure de données standard de Python (par ex. un `dict`) avec des valeurs et sous-valeurs toutes compatibles avec JSON. + +/// note | Remarque + +`jsonable_encoder` est en fait utilisée par **FastAPI** en interne pour convertir des données. Mais elle est utile dans de nombreux autres scénarios. + +/// diff --git a/docs/fr/docs/tutorial/extra-data-types.md b/docs/fr/docs/tutorial/extra-data-types.md new file mode 100644 index 000000000..edaa7bd4c --- /dev/null +++ b/docs/fr/docs/tutorial/extra-data-types.md @@ -0,0 +1,62 @@ +# Types de données supplémentaires { #extra-data-types } + +Jusqu'à présent, vous avez utilisé des types de données courants, comme : + +* `int` +* `float` +* `str` +* `bool` + +Mais vous pouvez aussi utiliser des types de données plus complexes. + +Et vous bénéficierez toujours des mêmes fonctionnalités que jusqu'à présent : + +* Excellente prise en charge dans l'éditeur. +* Conversion des données à partir des requêtes entrantes. +* Conversion des données pour les données de réponse. +* Validation des données. +* Annotations et documentation automatiques. + +## Autres types de données { #other-data-types } + +Voici quelques types de données supplémentaires que vous pouvez utiliser : + +* `UUID` : + * Un « identifiant universel unique » standard, couramment utilisé comme ID dans de nombreuses bases de données et systèmes. + * Dans les requêtes et les réponses, il sera représenté sous forme de `str`. +* `datetime.datetime` : + * Un `datetime.datetime` Python. + * Dans les requêtes et les réponses, il sera représenté sous forme de `str` au format ISO 8601, par exemple : `2008-09-15T15:53:00+05:00`. +* `datetime.date` : + * `datetime.date` Python. + * Dans les requêtes et les réponses, il sera représenté sous forme de `str` au format ISO 8601, par exemple : `2008-09-15`. +* `datetime.time` : + * Un `datetime.time` Python. + * Dans les requêtes et les réponses, il sera représenté sous forme de `str` au format ISO 8601, par exemple : `14:23:55.003`. +* `datetime.timedelta` : + * Un `datetime.timedelta` Python. + * Dans les requêtes et les réponses, il sera représenté sous forme de `float` de secondes totales. + * Pydantic permet aussi de le représenter sous la forme d'un « encodage de différence de temps ISO 8601 », voir la documentation pour plus d'informations. +* `frozenset` : + * Dans les requêtes et les réponses, traité de la même manière qu'un `set` : + * Dans les requêtes, une liste sera lue, les doublons éliminés, puis convertie en `set`. + * Dans les réponses, le `set` sera converti en `list`. + * Le schéma généré indiquera que les valeurs du `set` sont uniques (en utilisant `uniqueItems` de JSON Schema). +* `bytes` : + * `bytes` Python standard. + * Dans les requêtes et les réponses, traité comme une `str`. + * Le schéma généré indiquera qu'il s'agit d'une `str` avec le « format » `binary`. +* `Decimal` : + * `Decimal` Python standard. + * Dans les requêtes et les réponses, géré de la même manière qu'un `float`. +* Vous pouvez consulter tous les types de données Pydantic valides ici : Types de données Pydantic. + +## Exemple { #example } + +Voici un exemple de *chemin d'accès* avec des paramètres utilisant certains des types ci-dessus. + +{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *} + +Notez que les paramètres à l'intérieur de la fonction ont leur type de données naturel et que vous pouvez, par exemple, effectuer des manipulations de dates normales, comme : + +{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *} diff --git a/docs/fr/docs/tutorial/extra-models.md b/docs/fr/docs/tutorial/extra-models.md new file mode 100644 index 000000000..1f9eb1561 --- /dev/null +++ b/docs/fr/docs/tutorial/extra-models.md @@ -0,0 +1,211 @@ +# Modèles supplémentaires { #extra-models } + +En poursuivant l'exemple précédent, il est courant d'avoir plusieurs modèles liés. + +C'est particulièrement vrai pour les modèles d'utilisateur, car : + +* Le modèle d'entrée doit pouvoir contenir un mot de passe. +* Le modèle de sortie ne doit pas avoir de mot de passe. +* Le modèle de base de données devra probablement avoir un mot de passe haché. + +/// danger | Danger + +Ne stockez jamais les mots de passe des utilisateurs en clair. Stockez toujours un « hachage sécurisé » que vous pourrez ensuite vérifier. + +Si vous ne savez pas ce que c'est, vous apprendrez ce qu'est un « hachage de mot de passe » dans les [chapitres sur la sécurité](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}. + +/// + +## Utiliser plusieurs modèles { #multiple-models } + +Voici une idée générale de l'apparence des modèles avec leurs champs de mot de passe et les endroits où ils sont utilisés : + +{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} + +### À propos de `**user_in.model_dump()` { #about-user-in-model-dump } + +#### La méthode `.model_dump()` de Pydantic { #pydantics-model-dump } + +`user_in` est un modèle Pydantic de classe `UserIn`. + +Les modèles Pydantic ont une méthode `.model_dump()` qui renvoie un `dict` avec les données du modèle. + +Ainsi, si nous créons un objet Pydantic `user_in` comme : + +```Python +user_in = UserIn(username="john", password="secret", email="john.doe@example.com") +``` + +et que nous appelons ensuite : + +```Python +user_dict = user_in.model_dump() +``` + +nous avons maintenant un `dict` avec les données dans la variable `user_dict` (c'est un `dict` au lieu d'un objet modèle Pydantic). + +Et si nous appelons : + +```Python +print(user_dict) +``` + +nous obtiendrions un `dict` Python contenant : + +```Python +{ + 'username': 'john', + 'password': 'secret', + 'email': 'john.doe@example.com', + 'full_name': None, +} +``` + +#### Déballer un `dict` { #unpacking-a-dict } + +Si nous prenons un `dict` comme `user_dict` et que nous le passons à une fonction (ou une classe) avec `**user_dict`, Python va « déballer » ce `dict`. Il passera les clés et valeurs de `user_dict` directement comme arguments nommés. + +Ainsi, en reprenant `user_dict` ci-dessus, écrire : + +```Python +UserInDB(**user_dict) +``` + +aurait pour résultat quelque chose d'équivalent à : + +```Python +UserInDB( + username="john", + password="secret", + email="john.doe@example.com", + full_name=None, +) +``` + +Ou plus exactement, en utilisant `user_dict` directement, quels que soient ses contenus futurs : + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], +) +``` + +#### Créer un modèle Pydantic à partir du contenu d'un autre { #a-pydantic-model-from-the-contents-of-another } + +Comme dans l'exemple ci-dessus nous avons obtenu `user_dict` depuis `user_in.model_dump()`, ce code : + +```Python +user_dict = user_in.model_dump() +UserInDB(**user_dict) +``` + +serait équivalent à : + +```Python +UserInDB(**user_in.model_dump()) +``` + +... parce que `user_in.model_dump()` est un `dict`, et nous demandons ensuite à Python de « déballer » ce `dict` en le passant à `UserInDB` précédé de `**`. + +Ainsi, nous obtenons un modèle Pydantic à partir des données d'un autre modèle Pydantic. + +#### Déballer un `dict` et ajouter des mots-clés supplémentaires { #unpacking-a-dict-and-extra-keywords } + +Et en ajoutant ensuite l'argument nommé supplémentaire `hashed_password=hashed_password`, comme ici : + +```Python +UserInDB(**user_in.model_dump(), hashed_password=hashed_password) +``` + +... revient à : + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], + hashed_password = hashed_password, +) +``` + +/// warning | Alertes + +Les fonctions auxiliaires `fake_password_hasher` et `fake_save_user` ne servent qu'à démontrer un flux de données possible, mais elles n'offrent évidemment aucune sécurité réelle. + +/// + +## Réduire la duplication { #reduce-duplication } + +Réduire la duplication de code est l'une des idées centrales de **FastAPI**. + +La duplication de code augmente les risques de bogues, de problèmes de sécurité, de désynchronisation du code (lorsque vous mettez à jour un endroit mais pas les autres), etc. + +Et ces modèles partagent beaucoup de données et dupliquent des noms et types d'attributs. + +Nous pouvons faire mieux. + +Nous pouvons déclarer un modèle `UserBase` qui sert de base à nos autres modèles. Ensuite, nous pouvons créer des sous-classes de ce modèle qui héritent de ses attributs (déclarations de type, validation, etc.). + +Toutes les conversions de données, validations, documentation, etc., fonctionneront comme d'habitude. + +De cette façon, nous pouvons ne déclarer que les différences entre les modèles (avec `password` en clair, avec `hashed_password` et sans mot de passe) : + +{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *} + +## `Union` ou `anyOf` { #union-or-anyof } + +Vous pouvez déclarer qu'une réponse est l'`Union` de deux types ou plus, ce qui signifie que la réponse peut être n'importe lequel d'entre eux. + +Cela sera défini dans OpenAPI avec `anyOf`. + +Pour ce faire, utilisez l'annotation de type Python standard `typing.Union` : + +/// note | Remarque + +Lors de la définition d'une `Union`, incluez d'abord le type le plus spécifique, suivi du type le moins spécifique. Dans l'exemple ci-dessous, le type le plus spécifique `PlaneItem` précède `CarItem` dans `Union[PlaneItem, CarItem]`. + +/// + +{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *} + +### `Union` en Python 3.10 { #union-in-python-3-10 } + +Dans cet exemple, nous passons `Union[PlaneItem, CarItem]` comme valeur de l'argument `response_model`. + +Comme nous le passons comme valeur d'un argument au lieu de l'utiliser dans une annotation de type, nous devons utiliser `Union` même en Python 3.10. + +S'il s'agissait d'une annotation de type, nous pourrions utiliser la barre verticale, comme : + +```Python +some_variable: PlaneItem | CarItem +``` + +Mais si nous écrivons cela dans l'affectation `response_model=PlaneItem | CarItem`, nous obtiendrons une erreur, car Python essaierait d'effectuer une « opération invalide » entre `PlaneItem` et `CarItem` au lieu de l'interpréter comme une annotation de type. + +## Liste de modèles { #list-of-models } + +De la même manière, vous pouvez déclarer des réponses contenant des listes d'objets. + +Pour cela, utilisez le `list` Python standard : + +{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *} + +## Réponse avec un `dict` arbitraire { #response-with-arbitrary-dict } + +Vous pouvez également déclarer une réponse en utilisant un simple `dict` arbitraire, en déclarant uniquement le type des clés et des valeurs, sans utiliser de modèle Pydantic. + +C'est utile si vous ne connaissez pas à l'avance les noms de champs/attributs valides (qui seraient nécessaires pour un modèle Pydantic). + +Dans ce cas, vous pouvez utiliser `dict` : + +{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *} + +## Récapitulatif { #recap } + +Utilisez plusieurs modèles Pydantic et héritez librement selon chaque cas. + +Vous n'avez pas besoin d'avoir un seul modèle de données par entité si cette entité doit pouvoir avoir différents « états ». Comme pour l'« entité » utilisateur, avec un état incluant `password`, `password_hash` et sans mot de passe. diff --git a/docs/fr/docs/tutorial/first-steps.md b/docs/fr/docs/tutorial/first-steps.md index b2693b3e5..ae2358468 100644 --- a/docs/fr/docs/tutorial/first-steps.md +++ b/docs/fr/docs/tutorial/first-steps.md @@ -1,8 +1,8 @@ -# Démarrage { #first-steps } +# Démarrer { #first-steps } Le fichier **FastAPI** le plus simple possible pourrait ressembler à ceci : -{* ../../docs_src/first_steps/tutorial001_py39.py *} +{* ../../docs_src/first_steps/tutorial001_py310.py *} Copiez cela dans un fichier `main.py`. @@ -56,7 +56,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) Cette ligne montre l’URL où votre application est servie, sur votre machine locale. -### Vérifiez { #check-it } +### Vérifier { #check-it } Ouvrez votre navigateur à l’adresse http://127.0.0.1:8000. @@ -183,7 +183,7 @@ C’est tout ! Vous pouvez maintenant accéder à votre application à cette URL ### Étape 1 : importer `FastAPI` { #step-1-import-fastapi } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[1] *} `FastAPI` est une classe Python qui fournit toutes les fonctionnalités nécessaires à votre API. @@ -197,7 +197,7 @@ Vous pouvez donc aussi utiliser toutes les fonctionnalités de get opération +* en utilisant une get opération -/// info | `@décorateur` Info +/// info | `@decorator` Info Cette syntaxe `@something` en Python est appelée un « décorateur ». @@ -320,7 +320,7 @@ Voici notre « fonction de chemin d’accès » : * **opération** : `get`. * **fonction** : la fonction sous le « décorateur » (sous `@app.get("/")`). -{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[7] *} C’est une fonction Python. @@ -332,9 +332,9 @@ Dans ce cas, c’est une fonction `async`. Vous pouvez aussi la définir comme une fonction normale au lieu de `async def` : -{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py310.py hl[7] *} -/// note +/// note | Remarque Si vous ne connaissez pas la différence, consultez [Asynchrone : « Pressé ? »](../async.md#in-a-hurry){.internal-link target=_blank}. @@ -342,7 +342,7 @@ Si vous ne connaissez pas la différence, consultez [Asynchrone : « Pressé ? ### Étape 5 : retourner le contenu { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[8] *} Vous pouvez retourner un `dict`, une `list`, des valeurs uniques comme `str`, `int`, etc. diff --git a/docs/fr/docs/tutorial/handling-errors.md b/docs/fr/docs/tutorial/handling-errors.md new file mode 100644 index 000000000..38935c21c --- /dev/null +++ b/docs/fr/docs/tutorial/handling-errors.md @@ -0,0 +1,244 @@ +# Gérer les erreurs { #handling-errors } + +Il existe de nombreuses situations où vous devez signaler une erreur à un client qui utilise votre API. + +Ce client peut être un navigateur avec un frontend, un code d'un tiers, un appareil IoT, etc. + +Vous pourriez avoir besoin d'indiquer au client que : + +* Le client n'a pas les privilèges suffisants pour cette opération. +* Le client n'a pas accès à cette ressource. +* L'élément auquel le client tentait d'accéder n'existe pas. +* etc. + +Dans ces cas, vous retournez normalement un **code d'état HTTP** dans la plage de **400** (de 400 à 499). + +C'est similaire aux codes d'état HTTP 200 (de 200 à 299). Ces codes « 200 » signifient que, d'une certaine manière, la requête a été un « succès ». + +Les codes d'état dans la plage des 400 signifient qu'il y a eu une erreur côté client. + +Vous souvenez-vous de toutes ces erreurs **« 404 Not Found »** (et des blagues) ? + +## Utiliser `HTTPException` { #use-httpexception } + +Pour renvoyer au client des réponses HTTP avec des erreurs, vous utilisez `HTTPException`. + +### Importer `HTTPException` { #import-httpexception } + +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[1] *} + +### Lever une `HTTPException` dans votre code { #raise-an-httpexception-in-your-code } + +`HTTPException` est une exception Python normale avec des données supplémentaires pertinentes pour les API. + +Comme il s'agit d'une exception Python, vous ne la `return` pas, vous la `raise`. + +Cela signifie aussi que si vous êtes dans une fonction utilitaire appelée depuis votre fonction de chemin d'accès, et que vous levez la `HTTPException` à l'intérieur de cette fonction utilitaire, le reste du code de la fonction de chemin d'accès ne s'exécutera pas : la requête sera immédiatement interrompue et l'erreur HTTP issue de la `HTTPException` sera envoyée au client. + +L'avantage de lever une exception plutôt que de retourner une valeur apparaîtra plus clairement dans la section sur les Dépendances et la Sécurité. + +Dans cet exemple, lorsque le client demande un élément par un ID qui n'existe pas, levez une exception avec un code d'état `404` : + +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[11] *} + +### Réponse résultante { #the-resulting-response } + +Si le client demande `http://example.com/items/foo` (un `item_id` « foo »), il recevra un code d'état HTTP 200 et une réponse JSON : + +```JSON +{ + "item": "The Foo Wrestlers" +} +``` + +Mais si le client demande `http://example.com/items/bar` (un `item_id` inexistant « bar »), il recevra un code d'état HTTP 404 (l'erreur « not found ») et une réponse JSON : + +```JSON +{ + "detail": "Item not found" +} +``` + +/// tip | Astuce + +Lorsque vous levez une `HTTPException`, vous pouvez passer n'importe quelle valeur convertible en JSON comme paramètre `detail`, pas uniquement un `str`. + +Vous pouvez passer un `dict`, une `list`, etc. + +Elles sont gérées automatiquement par **FastAPI** et converties en JSON. + +/// + +## Ajouter des en-têtes personnalisés { #add-custom-headers } + +Dans certaines situations, il est utile de pouvoir ajouter des en-têtes personnalisés à l'erreur HTTP. Par exemple, pour certains types de sécurité. + +Vous n'aurez probablement pas besoin de l'utiliser directement dans votre code. + +Mais si vous en aviez besoin pour un scénario avancé, vous pouvez ajouter des en-têtes personnalisés : + +{* ../../docs_src/handling_errors/tutorial002_py310.py hl[14] *} + +## Installer des gestionnaires d'exception personnalisés { #install-custom-exception-handlers } + +Vous pouvez ajouter des gestionnaires d'exception personnalisés avec les mêmes utilitaires d'exception de Starlette. + +Supposons que vous ayez une exception personnalisée `UnicornException` que vous (ou une bibliothèque que vous utilisez) pourriez `raise`. + +Et vous souhaitez gérer cette exception globalement avec FastAPI. + +Vous pouvez ajouter un gestionnaire d'exception personnalisé avec `@app.exception_handler()` : + +{* ../../docs_src/handling_errors/tutorial003_py310.py hl[5:7,13:18,24] *} + +Ici, si vous appelez `/unicorns/yolo`, le chemin d'accès va `raise` une `UnicornException`. + +Mais elle sera gérée par `unicorn_exception_handler`. + +Ainsi, vous recevrez une erreur propre, avec un code d'état HTTP `418` et un contenu JSON : + +```JSON +{"message": "Oops! yolo did something. There goes a rainbow..."} +``` + +/// note | Détails techniques + +Vous pourriez aussi utiliser `from starlette.requests import Request` et `from starlette.responses import JSONResponse`. + +**FastAPI** fournit les mêmes `starlette.responses` sous `fastapi.responses` par simple commodité pour vous, développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette. Il en va de même pour `Request`. + +/// + +## Remplacer les gestionnaires d'exception par défaut { #override-the-default-exception-handlers } + +**FastAPI** fournit des gestionnaires d'exception par défaut. + +Ces gestionnaires se chargent de renvoyer les réponses JSON par défaut lorsque vous `raise` une `HTTPException` et lorsque la requête contient des données invalides. + +Vous pouvez remplacer ces gestionnaires d'exception par les vôtres. + +### Remplacer les exceptions de validation de la requête { #override-request-validation-exceptions } + +Lorsqu'une requête contient des données invalides, **FastAPI** lève en interne une `RequestValidationError`. + +Et il inclut également un gestionnaire d'exception par défaut pour cela. + +Pour la remplacer, importez `RequestValidationError` et utilisez-la avec `@app.exception_handler(RequestValidationError)` pour décorer le gestionnaire d'exception. + +Le gestionnaire d'exception recevra une `Request` et l'exception. + +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[2,14:19] *} + +À présent, si vous allez sur `/items/foo`, au lieu d'obtenir l'erreur JSON par défaut suivante : + +```JSON +{ + "detail": [ + { + "loc": [ + "path", + "item_id" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ] +} +``` + +vous obtiendrez une version texte, avec : + +``` +Validation errors: +Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer +``` + +### Remplacer le gestionnaire d'erreurs `HTTPException` { #override-the-httpexception-error-handler } + +De la même manière, vous pouvez remplacer le gestionnaire de `HTTPException`. + +Par exemple, vous pourriez vouloir renvoyer une réponse en texte brut au lieu de JSON pour ces erreurs : + +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[3:4,9:11,25] *} + +/// note | Détails techniques + +Vous pourriez aussi utiliser `from starlette.responses import PlainTextResponse`. + +**FastAPI** fournit les mêmes `starlette.responses` sous `fastapi.responses` par simple commodité pour vous, le développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette. + +/// + +/// warning | Alertes + +Gardez à l'esprit que la `RequestValidationError` contient l'information du nom de fichier et de la ligne où l'erreur de validation se produit, afin que vous puissiez l'afficher dans vos journaux avec les informations pertinentes si vous le souhaitez. + +Mais cela signifie que si vous vous contentez de la convertir en chaîne et de renvoyer cette information directement, vous pourriez divulguer un peu d'information sur votre système. C'est pourquoi, ici, le code extrait et affiche chaque erreur indépendamment. + +/// + +### Utiliser le corps de `RequestValidationError` { #use-the-requestvalidationerror-body } + +La `RequestValidationError` contient le `body` qu'elle a reçu avec des données invalides. + +Vous pouvez l'utiliser pendant le développement de votre application pour journaliser le corps et le déboguer, le renvoyer à l'utilisateur, etc. + +{* ../../docs_src/handling_errors/tutorial005_py310.py hl[14] *} + +Essayez maintenant d'envoyer un élément invalide comme : + +```JSON +{ + "title": "towel", + "size": "XL" +} +``` + +Vous recevrez une réponse vous indiquant que les données sont invalides et contenant le corps reçu : + +```JSON hl_lines="12-15" +{ + "detail": [ + { + "loc": [ + "body", + "size" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ], + "body": { + "title": "towel", + "size": "XL" + } +} +``` + +#### `HTTPException` de FastAPI vs `HTTPException` de Starlette { #fastapis-httpexception-vs-starlettes-httpexception } + +**FastAPI** a sa propre `HTTPException`. + +Et la classe d'erreur `HTTPException` de **FastAPI** hérite de la classe d'erreur `HTTPException` de Starlette. + +La seule différence est que la `HTTPException` de **FastAPI** accepte toute donnée sérialisable en JSON pour le champ `detail`, tandis que la `HTTPException` de Starlette n'accepte que des chaînes. + +Ainsi, vous pouvez continuer à lever la `HTTPException` de **FastAPI** normalement dans votre code. + +Mais lorsque vous enregistrez un gestionnaire d'exception, vous devez l'enregistrer pour la `HTTPException` de Starlette. + +De cette façon, si une partie du code interne de Starlette, ou une extension ou un plug-in Starlette, lève une `HTTPException` de Starlette, votre gestionnaire pourra l'intercepter et la traiter. + +Dans cet exemple, afin de pouvoir avoir les deux `HTTPException` dans le même code, les exceptions de Starlette sont renommées en `StarletteHTTPException` : + +```Python +from starlette.exceptions import HTTPException as StarletteHTTPException +``` + +### Réutiliser les gestionnaires d'exception de **FastAPI** { #reuse-fastapis-exception-handlers } + +Si vous souhaitez utiliser l'exception avec les mêmes gestionnaires d'exception par défaut de **FastAPI**, vous pouvez importer et réutiliser les gestionnaires d'exception par défaut depuis `fastapi.exception_handlers` : + +{* ../../docs_src/handling_errors/tutorial006_py310.py hl[2:5,15,21] *} + +Dans cet exemple, vous vous contentez d'afficher l'erreur avec un message très expressif, mais vous voyez l'idée. Vous pouvez utiliser l'exception puis simplement réutiliser les gestionnaires d'exception par défaut. diff --git a/docs/fr/docs/tutorial/header-param-models.md b/docs/fr/docs/tutorial/header-param-models.md new file mode 100644 index 000000000..8fc1a3d36 --- /dev/null +++ b/docs/fr/docs/tutorial/header-param-models.md @@ -0,0 +1,72 @@ +# Modèles de paramètres d'en-tête { #header-parameter-models } + +Si vous avez un groupe de **paramètres d'en-tête** liés, vous pouvez créer un **modèle Pydantic** pour les déclarer. + +Cela vous permet de **réutiliser le modèle** à **plusieurs endroits** et aussi de déclarer des validations et des métadonnées pour tous les paramètres en une seule fois. 😎 + +/// note | Remarque + +Cela est pris en charge depuis la version `0.115.0` de FastAPI. 🤓 + +/// + +## Paramètres d'en-tête avec un modèle Pydantic { #header-parameters-with-a-pydantic-model } + +Déclarez les **paramètres d'en-tête** dont vous avez besoin dans un **modèle Pydantic**, puis déclarez le paramètre comme `Header` : + +{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *} + +**FastAPI** extrait les données de **chaque champ** depuis les **en-têtes** de la requête et vous fournit le modèle Pydantic que vous avez défini. + +## Consulter la documentation { #check-the-docs } + +Vous pouvez voir les en-têtes requis dans l'interface de la documentation à `/docs` : + +
+ +
+ +## Interdire les en-têtes supplémentaires { #forbid-extra-headers } + +Dans certains cas d'utilisation particuliers (probablement pas très courants), vous pourriez vouloir **restreindre** les en-têtes que vous souhaitez recevoir. + +Vous pouvez utiliser la configuration du modèle de Pydantic pour `forbid` tout champ `extra` : + +{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *} + +Si un client essaie d'envoyer des **en-têtes supplémentaires**, il recevra une **réponse d'erreur**. + +Par exemple, si le client essaie d'envoyer un en-tête `tool` avec la valeur `plumbus`, il recevra une **réponse d'erreur** lui indiquant que le paramètre d'en-tête `tool` n'est pas autorisé : + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["header", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ] +} +``` + +## Désactiver convert_underscores { #disable-convert-underscores } + +Comme pour les paramètres d'en-tête classiques, lorsque vous avez des caractères de soulignement dans les noms de paramètres, ils sont **automatiquement convertis en tirets**. + +Par exemple, si vous avez un paramètre d'en-tête `save_data` dans le code, l'en-tête HTTP attendu sera `save-data`, et il apparaîtra ainsi dans la documentation. + +Si, pour une raison quelconque, vous devez désactiver cette conversion automatique, vous pouvez aussi le faire pour les modèles Pydantic de paramètres d'en-tête. + +{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *} + +/// warning | Alertes + +Avant de définir `convert_underscores` à `False`, gardez à l'esprit que certains proxys et serveurs HTTP interdisent l'utilisation d'en-têtes contenant des underscores. + +/// + +## Résumé { #summary } + +Vous pouvez utiliser des **modèles Pydantic** pour déclarer des **en-têtes** dans **FastAPI**. 😎 diff --git a/docs/fr/docs/tutorial/header-params.md b/docs/fr/docs/tutorial/header-params.md new file mode 100644 index 000000000..608559c85 --- /dev/null +++ b/docs/fr/docs/tutorial/header-params.md @@ -0,0 +1,91 @@ +# Paramètres d'en-tête { #header-parameters } + +Vous pouvez définir des paramètres `Header` de la même manière que vous définissez des paramètres `Query`, `Path` et `Cookie`. + +## Importer `Header` { #import-header } + +Commencez par importer `Header` : + +{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *} + +## Déclarer des paramètres `Header` { #declare-header-parameters } + +Déclarez ensuite les paramètres d'en-tête en utilisant la même structure qu'avec `Path`, `Query` et `Cookie`. + +Vous pouvez définir la valeur par défaut ainsi que tous les paramètres supplémentaires de validation ou d'annotation : + +{* ../../docs_src/header_params/tutorial001_an_py310.py hl[9] *} + +/// note | Détails techniques + +`Header` est une classe « sœur » de `Path`, `Query` et `Cookie`. Elle hérite également de la même classe commune `Param`. + +Mais rappelez-vous que lorsque vous importez `Query`, `Path`, `Header` et d'autres depuis `fastapi`, ce sont en réalité des fonctions qui renvoient des classes spéciales. + +/// + +/// info + +Pour déclarer des en-têtes, vous devez utiliser `Header`, sinon les paramètres seraient interprétés comme des paramètres de requête. + +/// + +## Conversion automatique { #automatic-conversion } + +`Header` offre un peu de fonctionnalité supplémentaire par rapport à `Path`, `Query` et `Cookie`. + +La plupart des en-têtes standards sont séparés par un caractère « trait d'union », également appelé « signe moins » (`-`). + +Mais une variable comme `user-agent` est invalide en Python. + +Ainsi, par défaut, `Header` convertit les caractères des noms de paramètres du tiret bas (`_`) en trait d'union (`-`) pour extraire et documenter les en-têtes. + +De plus, les en-têtes HTTP ne sont pas sensibles à la casse, vous pouvez donc les déclarer avec le style Python standard (aussi appelé « snake_case »). + +Vous pouvez donc utiliser `user_agent` comme vous le feriez normalement dans du code Python, au lieu d'avoir à mettre des majuscules aux premières lettres comme `User_Agent` ou quelque chose de similaire. + +Si, pour une raison quelconque, vous devez désactiver la conversion automatique des traits bas en traits d'union, définissez le paramètre `convert_underscores` de `Header` sur `False` : + +{* ../../docs_src/header_params/tutorial002_an_py310.py hl[10] *} + +/// warning | Alertes + +Avant de définir `convert_underscores` sur `False`, gardez à l'esprit que certains proxies et serveurs HTTP interdisent l'utilisation d'en-têtes contenant des traits bas. + +/// + +## Gérer les en-têtes dupliqués { #duplicate-headers } + +Il est possible de recevoir des en-têtes en double. Autrement dit, le même en-tête avec plusieurs valeurs. + +Vous pouvez définir ces cas à l'aide d'une liste dans la déclaration de type. + +Vous recevrez toutes les valeurs de l'en-tête dupliqué sous forme de `list` Python. + +Par exemple, pour déclarer un en-tête `X-Token` qui peut apparaître plusieurs fois, vous pouvez écrire : + +{* ../../docs_src/header_params/tutorial003_an_py310.py hl[9] *} + +Si vous communiquez avec ce *chemin d'accès* en envoyant deux en-têtes HTTP comme : + +``` +X-Token: foo +X-Token: bar +``` + +La réponse ressemblerait à ceci : + +```JSON +{ + "X-Token values": [ + "bar", + "foo" + ] +} +``` + +## Récapitulatif { #recap } + +Déclarez les en-têtes avec `Header`, en suivant le même modèle que pour `Query`, `Path` et `Cookie`. + +Et ne vous souciez pas des traits bas dans vos variables, **FastAPI** s'occupe de les convertir. diff --git a/docs/fr/docs/tutorial/metadata.md b/docs/fr/docs/tutorial/metadata.md new file mode 100644 index 000000000..3ea3865ba --- /dev/null +++ b/docs/fr/docs/tutorial/metadata.md @@ -0,0 +1,120 @@ +# Métadonnées et URL des documents { #metadata-and-docs-urls } + +Vous pouvez personnaliser plusieurs configurations de métadonnées dans votre application **FastAPI**. + +## Métadonnées pour l'API { #metadata-for-api } + +Vous pouvez définir les champs suivants qui sont utilisés dans la spécification OpenAPI et les interfaces utilisateur de documentation automatique de l’API : + +| Paramètre | Type | Description | +|------------|------|-------------| +| `title` | `str` | Le titre de l’API. | +| `summary` | `str` | Un court résumé de l’API. Disponible depuis OpenAPI 3.1.0, FastAPI 0.99.0. | +| `description` | `str` | Une brève description de l’API. Elle peut utiliser Markdown. | +| `version` | `string` | La version de l’API. C’est la version de votre propre application, pas d’OpenAPI. Par exemple `2.5.0`. | +| `terms_of_service` | `str` | Une URL vers les Conditions d’utilisation de l’API. Le cas échéant, il doit s’agir d’une URL. | +| `contact` | `dict` | Les informations de contact pour l’API exposée. Cela peut contenir plusieurs champs.
champs de contact
ParamètreTypeDescription
namestrLe nom identifiant de la personne/organisation de contact.
urlstrL’URL pointant vers les informations de contact. DOIT être au format d’une URL.
emailstrL’adresse e-mail de la personne/organisation de contact. DOIT être au format d’une adresse e-mail.
| +| `license_info` | `dict` | Les informations de licence pour l’API exposée. Cela peut contenir plusieurs champs.
champs de license_info
ParamètreTypeDescription
namestrOBLIGATOIRE (si un license_info est défini). Le nom de la licence utilisée pour l’API.
identifierstrUne expression de licence SPDX pour l’API. Le champ identifier est mutuellement exclusif du champ url. Disponible depuis OpenAPI 3.1.0, FastAPI 0.99.0.
urlstrUne URL vers la licence utilisée pour l’API. DOIT être au format d’une URL.
| + +Vous pouvez les définir comme suit : + +{* ../../docs_src/metadata/tutorial001_py310.py hl[3:16, 19:32] *} + +/// tip | Astuce + +Vous pouvez écrire du Markdown dans le champ `description` et il sera rendu dans la sortie. + +/// + +Avec cette configuration, les documents API automatiques ressembleraient à : + + + +## Identifiant de licence { #license-identifier } + +Depuis OpenAPI 3.1.0 et FastAPI 0.99.0, vous pouvez également définir `license_info` avec un `identifier` au lieu d’une `url`. + +Par exemple : + +{* ../../docs_src/metadata/tutorial001_1_py310.py hl[31] *} + +## Métadonnées pour les tags { #metadata-for-tags } + +Vous pouvez également ajouter des métadonnées supplémentaires pour les différents tags utilisés pour regrouper vos chemins d'accès avec le paramètre `openapi_tags`. + +Il prend une liste contenant un dictionnaire pour chaque tag. + +Chaque dictionnaire peut contenir : + +* `name` (**requis**) : un `str` avec le même nom de tag que vous utilisez dans le paramètre `tags` de vos *chemins d'accès* et `APIRouter`s. +* `description` : un `str` avec une courte description pour le tag. Il peut contenir du Markdown et sera affiché dans l’interface utilisateur de la documentation. +* `externalDocs` : un `dict` décrivant une documentation externe avec : + * `description` : un `str` avec une courte description pour la documentation externe. + * `url` (**requis**) : un `str` avec l’URL de la documentation externe. + +### Créer des métadonnées pour les tags { #create-metadata-for-tags } + +Essayons cela avec un exemple de tags pour `users` et `items`. + +Créez des métadonnées pour vos tags et transmettez-les au paramètre `openapi_tags` : + +{* ../../docs_src/metadata/tutorial004_py310.py hl[3:16,18] *} + +Notez que vous pouvez utiliser Markdown à l’intérieur des descriptions, par exemple « login » sera affiché en gras (**login**) et « fancy » sera affiché en italique (_fancy_). + +/// tip | Astuce + +Vous n’avez pas à ajouter des métadonnées pour tous les tags que vous utilisez. + +/// + +### Utiliser vos tags { #use-your-tags } + +Utilisez le paramètre `tags` avec vos *chemins d'accès* (et `APIRouter`s) pour les affecter à différents tags : + +{* ../../docs_src/metadata/tutorial004_py310.py hl[21,26] *} + +/// info + +En savoir plus sur les tags dans [Configuration de chemins d'accès](path-operation-configuration.md#tags){.internal-link target=_blank}. + +/// + +### Consultez les documents { #check-the-docs } + +Désormais, si vous consultez les documents, ils afficheront toutes les métadonnées supplémentaires : + + + +### Définir l’ordre des tags { #order-of-tags } + +L’ordre de chaque dictionnaire de métadonnées de tag définit également l’ordre affiché dans l’interface utilisateur de la documentation. + +Par exemple, même si `users` viendrait après `items` par ordre alphabétique, il est affiché avant, car nous avons ajouté ses métadonnées comme premier dictionnaire de la liste. + +## URL OpenAPI { #openapi-url } + +Par défaut, le schéma OpenAPI est servi à `/openapi.json`. + +Mais vous pouvez le configurer avec le paramètre `openapi_url`. + +Par exemple, pour qu’il soit servi à `/api/v1/openapi.json` : + +{* ../../docs_src/metadata/tutorial002_py310.py hl[3] *} + +Si vous souhaitez désactiver complètement le schéma OpenAPI, vous pouvez définir `openapi_url=None`, cela désactivera également les interfaces utilisateur de la documentation qui l’utilisent. + +## URL des documents { #docs-urls } + +Vous pouvez configurer les deux interfaces utilisateur de documentation incluses : + +* **Swagger UI** : servie à `/docs`. + * Vous pouvez définir son URL avec le paramètre `docs_url`. + * Vous pouvez la désactiver en définissant `docs_url=None`. +* **ReDoc** : servie à `/redoc`. + * Vous pouvez définir son URL avec le paramètre `redoc_url`. + * Vous pouvez la désactiver en définissant `redoc_url=None`. + +Par exemple, pour que Swagger UI soit servi à `/documentation` et désactiver ReDoc : + +{* ../../docs_src/metadata/tutorial003_py310.py hl[3] *} diff --git a/docs/fr/docs/tutorial/middleware.md b/docs/fr/docs/tutorial/middleware.md new file mode 100644 index 000000000..6cbbc3e45 --- /dev/null +++ b/docs/fr/docs/tutorial/middleware.md @@ -0,0 +1,95 @@ +# Middleware { #middleware } + +Vous pouvez ajouter des middlewares aux applications **FastAPI**. + +Un « middleware » est une fonction qui agit sur chaque **requête** avant qu’elle ne soit traitée par un *chemin d'accès* spécifique. Et aussi sur chaque **réponse** avant son renvoi. + +* Il intercepte chaque **requête** qui parvient à votre application. +* Il peut alors faire quelque chose avec cette **requête** ou exécuter tout code nécessaire. +* Ensuite, il transmet la **requête** pour qu’elle soit traitée par le reste de l’application (par un *chemin d'accès*). +* Puis il récupère la **réponse** générée par l’application (par un *chemin d'accès*). +* Il peut faire quelque chose avec cette **réponse** ou exécuter tout code nécessaire. +* Enfin, il renvoie la **réponse**. + +/// note | Détails techniques + +Si vous avez des dépendances avec `yield`, le code de sortie s’exécutera après le middleware. + +S’il y avait des tâches d’arrière-plan (présentées dans la section [Tâches d’arrière-plan](background-tasks.md){.internal-link target=_blank}, que vous verrez plus tard), elles s’exécuteront après tous les middlewares. + +/// + +## Créer un middleware { #create-a-middleware } + +Pour créer un middleware, utilisez le décorateur `@app.middleware("http")` au-dessus d’une fonction. + +La fonction de middleware reçoit : + +* La `request`. +* Une fonction `call_next` qui recevra la `request` en paramètre. + * Cette fonction transmettra la `request` au *chemin d'accès* correspondant. + * Puis elle renverra la `response` générée par le *chemin d'accès* correspondant. +* Vous pouvez ensuite modifier la `response` avant de la renvoyer. + +{* ../../docs_src/middleware/tutorial001_py310.py hl[8:9,11,14] *} + +/// tip | Astuce + +Gardez à l’esprit que des en-têtes propriétaires personnalisés peuvent être ajoutés en utilisant le préfixe `X-`. + +Mais si vous avez des en-têtes personnalisés que vous voulez rendre visibles pour un client dans un navigateur, vous devez les ajouter à votre configuration CORS ([CORS (Partage des ressources entre origines)](cors.md){.internal-link target=_blank}) en utilisant le paramètre `expose_headers` documenté dans la documentation CORS de Starlette. + +/// + +/// note | Détails techniques + +Vous pourriez aussi utiliser `from starlette.requests import Request`. + +**FastAPI** le fournit pour votre confort de développeur. Mais cela provient directement de Starlette. + +/// + +### Avant et après la `response` { #before-and-after-the-response } + +Vous pouvez ajouter du code à exécuter avec la `request`, avant que tout *chemin d'accès* ne la reçoive. + +Et aussi après que la `response` a été générée, avant de la renvoyer. + +Par exemple, vous pourriez ajouter un en-tête personnalisé `X-Process-Time` contenant le temps en secondes nécessaire pour traiter la requête et générer une réponse : + +{* ../../docs_src/middleware/tutorial001_py310.py hl[10,12:13] *} + +/// tip | Astuce + +Ici, nous utilisons `time.perf_counter()` au lieu de `time.time()` car cela peut être plus précis pour ces cas d’usage. 🤓 + +/// + +## Ordre d’exécution de plusieurs middlewares { #multiple-middleware-execution-order } + +Quand vous ajoutez plusieurs middlewares en utilisant soit le décorateur `@app.middleware()`, soit la méthode `app.add_middleware()`, chaque nouveau middleware enveloppe l’application, formant une pile. Le dernier middleware ajouté est le plus externe, et le premier est le plus interne. + +Sur le chemin de la requête, le plus externe s’exécute en premier. + +Sur le chemin de la réponse, il s’exécute en dernier. + +Par exemple : + +```Python +app.add_middleware(MiddlewareA) +app.add_middleware(MiddlewareB) +``` + +Cela aboutit à l’ordre d’exécution suivant : + +* **Requête** : MiddlewareB → MiddlewareA → route + +* **Réponse** : route → MiddlewareA → MiddlewareB + +Ce comportement d’empilement garantit que les middlewares s’exécutent dans un ordre prévisible et contrôlable. + +## Autres middlewares { #other-middlewares } + +Vous pouvez en lire davantage sur d’autres middlewares dans le [Guide de l’utilisateur avancé : Middleware avancé](../advanced/middleware.md){.internal-link target=_blank}. + +Vous verrez comment gérer CORS avec un middleware dans la section suivante. diff --git a/docs/fr/docs/tutorial/path-operation-configuration.md b/docs/fr/docs/tutorial/path-operation-configuration.md new file mode 100644 index 000000000..f8041fa69 --- /dev/null +++ b/docs/fr/docs/tutorial/path-operation-configuration.md @@ -0,0 +1,107 @@ +# Configurer les chemins d'accès { #path-operation-configuration } + +Vous pouvez passer plusieurs paramètres à votre *décorateur de chemin d'accès* pour le configurer. + +/// warning | Alertes + +Notez que ces paramètres sont passés directement au *décorateur de chemin d'accès*, et non à votre *fonction de chemin d'accès*. + +/// + +## Définir le code d'état de la réponse { #response-status-code } + +Vous pouvez définir le `status_code` (HTTP) à utiliser dans la réponse de votre *chemin d'accès*. + +Vous pouvez passer directement le code `int`, comme `404`. + +Mais si vous ne vous souvenez pas à quoi correspond chaque code numérique, vous pouvez utiliser les constantes abrégées dans `status` : + +{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *} + +Ce code d'état sera utilisé dans la réponse et ajouté au schéma OpenAPI. + +/// note | Détails techniques + +Vous pouvez également utiliser `from starlette import status`. + +**FastAPI** fournit le même `starlette.status` sous le nom `fastapi.status` pour votre commodité, en tant que développeur. Mais cela provient directement de Starlette. + +/// + +## Ajouter des tags { #tags } + +Vous pouvez ajouter des tags à votre *chemin d'accès*, en passant le paramètre `tags` avec une `list` de `str` (généralement un seul `str`) : + +{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *} + +Ils seront ajoutés au schéma OpenAPI et utilisés par les interfaces de documentation automatiques : + + + +### Utiliser des tags avec Enum { #tags-with-enums } + +Si vous avez une grande application, vous pourriez finir par accumuler **plusieurs tags**, et vous voudrez vous assurer d'utiliser toujours le **même tag** pour les *chemins d'accès* associés. + +Dans ces cas, il peut être judicieux de stocker les tags dans un `Enum`. + +**FastAPI** le prend en charge de la même manière qu'avec des chaînes simples : + +{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *} + +## Ajouter un résumé et une description { #summary-and-description } + +Vous pouvez ajouter un `summary` et une `description` : + +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} + +## Utiliser la description depuis la docstring { #description-from-docstring } + +Comme les descriptions ont tendance à être longues et à couvrir plusieurs lignes, vous pouvez déclarer la description du *chemin d'accès* dans la docstring de la fonction et **FastAPI** la lira à partir de là. + +Vous pouvez écrire Markdown dans la docstring, il sera interprété et affiché correctement (en tenant compte de l'indentation de la docstring). + +{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *} + +Elle sera utilisée dans les documents interactifs : + + + +## Définir la description de la réponse { #response-description } + +Vous pouvez spécifier la description de la réponse avec le paramètre `response_description` : + +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} + +/// info + +Notez que `response_description` se réfère spécifiquement à la réponse, tandis que `description` se réfère au *chemin d'accès* en général. + +/// + +/// check | Vérifications + +OpenAPI spécifie que chaque *chemin d'accès* requiert une description de réponse. + +Donc, si vous n'en fournissez pas, **FastAPI** en générera automatiquement une « Réponse réussie ». + +/// + + + +## Déprécier un *chemin d'accès* { #deprecate-a-path-operation } + +Si vous devez marquer un *chemin d'accès* comme déprécié, sans pour autant le supprimer, passez le paramètre `deprecated` : + +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} + +Il sera clairement marqué comme déprécié dans les documents interactifs : + + + +Voyez à quoi ressemblent les *chemins d'accès* dépréciés et non dépréciés : + + + +## Récapitulatif { #recap } + +Vous pouvez facilement configurer et ajouter des métadonnées à vos *chemins d'accès* en passant des paramètres aux *décorateurs de chemin d'accès*. diff --git a/docs/fr/docs/tutorial/path-params-numeric-validations.md b/docs/fr/docs/tutorial/path-params-numeric-validations.md index c80710777..2dbaaa8ca 100644 --- a/docs/fr/docs/tutorial/path-params-numeric-validations.md +++ b/docs/fr/docs/tutorial/path-params-numeric-validations.md @@ -54,11 +54,11 @@ Cela n'a pas d'importance pour **FastAPI**. Il détectera les paramètres par le Ainsi, vous pouvez déclarer votre fonction comme suit : -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} Mais gardez à l'esprit que si vous utilisez `Annotated`, vous n'aurez pas ce problème, cela n'aura pas d'importance car vous n'utilisez pas les valeurs par défaut des paramètres de fonction pour `Query()` ou `Path()`. -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## Ordonner les paramètres comme vous le souhaitez, astuces { #order-the-parameters-as-you-need-tricks } @@ -81,15 +81,15 @@ Si vous voulez : Passez `*`, comme premier paramètre de la fonction. -Python ne fera rien avec ce `*`, mais il saura que tous les paramètres suivants doivent être appelés comme arguments "mots-clés" (paires clé-valeur), également connus sous le nom de kwargs. Même s'ils n'ont pas de valeur par défaut. +Python ne fera rien avec ce `*`, mais il saura que tous les paramètres suivants doivent être appelés comme arguments « mots-clés » (paires clé-valeur), également connus sous le nom de kwargs. Même s'ils n'ont pas de valeur par défaut. -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### Mieux avec `Annotated` { #better-with-annotated } Gardez à l'esprit que si vous utilisez `Annotated`, comme vous n'utilisez pas les valeurs par défaut des paramètres de fonction, vous n'aurez pas ce problème, et vous n'aurez probablement pas besoin d'utiliser `*`. -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## Validations numériques : supérieur ou égal { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ Avec `Query` et `Path` (et d'autres que vous verrez plus tard) vous pouvez décl Ici, avec `ge=1`, `item_id` devra être un nombre entier « `g`reater than or `e`qual » à `1`. -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## Validations numériques : supérieur et inférieur ou égal { #number-validations-greater-than-and-less-than-or-equal } @@ -106,7 +106,7 @@ La même chose s'applique pour : * `gt` : `g`reater `t`han * `le` : `l`ess than or `e`qual -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## Validations numériques : flottants, supérieur et inférieur { #number-validations-floats-greater-than-and-less-than } @@ -118,7 +118,7 @@ Ainsi, `0.5` serait une valeur valide. Mais `0.0` ou `0` ne le serait pas. Et la même chose pour lt. -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## Pour résumer { #recap } diff --git a/docs/fr/docs/tutorial/path-params.md b/docs/fr/docs/tutorial/path-params.md index 3b2955a95..985eff635 100644 --- a/docs/fr/docs/tutorial/path-params.md +++ b/docs/fr/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Vous pouvez déclarer des « paramètres » ou « variables » de chemin avec la même syntaxe utilisée par les chaînes de format Python : -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} La valeur du paramètre de chemin `item_id` sera transmise à votre fonction dans l'argument `item_id`. @@ -16,7 +16,7 @@ Donc, si vous exécutez cet exemple et allez sur Conversion de données { #data-conversion } +## Conversion de données { #data-conversion } Si vous exécutez cet exemple et ouvrez votre navigateur sur http://127.0.0.1:8000/items/3, vous verrez comme réponse : @@ -38,7 +38,7 @@ Si vous exécutez cet exemple et ouvrez votre navigateur sur « parsing » de la requête. +Ainsi, avec cette déclaration de type, **FastAPI** vous fournit automatiquement le « parsing » de la requête. /// @@ -118,19 +118,19 @@ Et vous pouvez aussi avoir un chemin `/users/{user_id}` pour récupérer des don Comme les *chemins d'accès* sont évalués dans l'ordre, vous devez vous assurer que le chemin `/users/me` est déclaré avant celui de `/users/{user_id}` : -{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *} Sinon, le chemin `/users/{user_id}` correspondrait aussi à `/users/me`, « pensant » qu'il reçoit un paramètre `user_id` avec la valeur « me ». De même, vous ne pouvez pas redéfinir un chemin d'accès : -{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *} Le premier sera toujours utilisé puisque le chemin correspond en premier. ## Valeurs prédéfinies { #predefined-values } -Si vous avez un *chemin d'accès* qui reçoit un *paramètre de chemin*, mais que vous voulez que les valeurs possibles de ce *paramètre de chemin* soient prédéfinies, vous pouvez utiliser une `Enum` Python standard. +Si vous avez un *chemin d'accès* qui reçoit un *paramètre de chemin*, mais que vous voulez que les valeurs possibles de ce *paramètre de chemin* soient prédéfinies, vous pouvez utiliser une `Enum` Python standard. ### Créer une classe `Enum` { #create-an-enum-class } @@ -140,11 +140,11 @@ En héritant de `str`, la documentation de l'API saura que les valeurs doivent Créez ensuite des attributs de classe avec des valeurs fixes, qui seront les valeurs valides disponibles : -{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *} /// tip | Astuce -Si vous vous demandez, « AlexNet », « ResNet » et « LeNet » sont juste des noms de modèles de Machine Learning. +Si vous vous demandez, « AlexNet », « ResNet » et « LeNet » sont juste des noms de modèles de Machine Learning. /// @@ -152,7 +152,7 @@ Si vous vous demandez, « AlexNet », « ResNet » et « LeNet » sont juste des Créez ensuite un *paramètre de chemin* avec une annotation de type utilisant la classe d'énumération que vous avez créée (`ModelName`) : -{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[16] *} ### Consulter la documentation { #check-the-docs } @@ -168,13 +168,13 @@ La valeur du *paramètre de chemin* sera un *membre d'énumération*. Vous pouvez le comparer avec le *membre d'énumération* dans votre enum `ModelName` : -{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[17] *} #### Obtenir la *valeur de l'énumération* { #get-the-enumeration-value } Vous pouvez obtenir la valeur réelle (une `str` dans ce cas) avec `model_name.value`, ou en général, `votre_membre_d_enum.value` : -{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[20] *} /// tip | Astuce @@ -188,7 +188,7 @@ Vous pouvez retourner des *membres d'énumération* depuis votre *chemin d'accè Ils seront convertis vers leurs valeurs correspondantes (des chaînes de caractères ici) avant d'être renvoyés au client : -{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[18,21,23] *} Dans votre client, vous recevrez une réponse JSON comme : @@ -227,7 +227,7 @@ Dans ce cas, le nom du paramètre est `file_path`, et la dernière partie, `:pat Vous pouvez donc l'utiliser ainsi : -{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py310.py hl[6] *} /// tip | Astuce @@ -242,7 +242,7 @@ Dans ce cas, l'URL serait : `/files//home/johndoe/myfile.txt`, avec un double sl Avec **FastAPI**, en utilisant des déclarations de type Python courtes, intuitives et standard, vous obtenez : * Support de l'éditeur : vérifications d'erreurs, autocomplétion, etc. -* Données « parsing » +* Données « parsing » * Validation de données * Annotations d'API et documentation automatique diff --git a/docs/fr/docs/tutorial/query-param-models.md b/docs/fr/docs/tutorial/query-param-models.md new file mode 100644 index 000000000..b9bb1d137 --- /dev/null +++ b/docs/fr/docs/tutorial/query-param-models.md @@ -0,0 +1,68 @@ +# Modèles de paramètres de requête { #query-parameter-models } + +Si vous avez un groupe de paramètres de requête liés, vous pouvez créer un modèle Pydantic pour les déclarer. + +Cela vous permet de réutiliser le modèle à plusieurs endroits et aussi de déclarer des validations et des métadonnées pour tous les paramètres en une seule fois. 😎 + +/// note | Remarque + +Pris en charge depuis FastAPI version `0.115.0`. 🤓 + +/// + +## Déclarer des paramètres de requête avec un modèle Pydantic { #query-parameters-with-a-pydantic-model } + +Déclarez les paramètres de requête dont vous avez besoin dans un modèle Pydantic, puis déclarez le paramètre en tant que `Query` : + +{* ../../docs_src/query_param_models/tutorial001_an_py310.py hl[9:13,17] *} + +FastAPI extrait les données pour chaque champ à partir des paramètres de requête de la requête et vous fournit le modèle Pydantic que vous avez défini. + +## Consulter les documents { #check-the-docs } + +Vous pouvez voir les paramètres de requête dans l'interface des documents à `/docs` : + +
+ +
+ +## Interdire des paramètres de requête supplémentaires { #forbid-extra-query-parameters } + +Dans certains cas d'utilisation particuliers (probablement peu courants), vous pouvez vouloir restreindre les paramètres de requête que vous souhaitez recevoir. + +Vous pouvez utiliser la configuration du modèle Pydantic pour `forbid` tout champ `extra` : + +{* ../../docs_src/query_param_models/tutorial002_an_py310.py hl[10] *} + +Si un client tente d'envoyer des données supplémentaires dans les paramètres de requête, il recevra une réponse d'erreur. + +Par exemple, si le client tente d'envoyer un paramètre de requête `tool` avec la valeur `plumbus`, comme : + +```http +https://example.com/items/?limit=10&tool=plumbus +``` + +Il recevra une réponse d'erreur lui indiquant que le paramètre de requête `tool` n'est pas autorisé : + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["query", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus" + } + ] +} +``` + +## Résumé { #summary } + +Vous pouvez utiliser des modèles Pydantic pour déclarer des paramètres de requête dans FastAPI. 😎 + +/// tip | Astuce + +Alerte spoiler : vous pouvez aussi utiliser des modèles Pydantic pour déclarer des cookies et des en-têtes, mais vous lirez cela plus tard dans le tutoriel. 🤫 + +/// diff --git a/docs/fr/docs/tutorial/query-params-str-validations.md b/docs/fr/docs/tutorial/query-params-str-validations.md index 544d10328..17b751f23 100644 --- a/docs/fr/docs/tutorial/query-params-str-validations.md +++ b/docs/fr/docs/tutorial/query-params-str-validations.md @@ -47,40 +47,16 @@ C’est le moment de l’utiliser avec FastAPI. 🚀 Nous avions cette annotation de type : -//// tab | Python 3.10+ - ```Python q: str | None = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Union[str, None] = None -``` - -//// - Ce que nous allons faire, c’est l’englober avec `Annotated`, de sorte que cela devienne : -//// tab | Python 3.10+ - ```Python q: Annotated[str | None] = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Annotated[Union[str, None]] = None -``` - -//// - Les deux versions signifient la même chose, `q` est un paramètre qui peut être une `str` ou `None`, et par défaut, c’est `None`. Passons maintenant aux choses amusantes. 🎉 @@ -109,7 +85,7 @@ FastAPI va maintenant : ## Alternative (ancienne) : `Query` comme valeur par défaut { #alternative-old-query-as-the-default-value } -Les versions précédentes de FastAPI (avant 0.95.0) exigeaient d’utiliser `Query` comme valeur par défaut de votre paramètre, au lieu de le mettre dans `Annotated`. Il y a de fortes chances que vous voyiez du code qui l’utilise encore, je vais donc vous l’expliquer. +Les versions précédentes de FastAPI (avant 0.95.0) exigeaient d’utiliser `Query` comme valeur par défaut de votre paramètre, au lieu de le mettre dans `Annotated`. Il y a de fortes chances que vous voyiez du code qui l’utilise encore, je vais donc vous l’expliquer. /// tip | Astuce @@ -191,7 +167,7 @@ Vous pouvez également ajouter un paramètre `min_length` : ## Ajouter des expressions régulières { #add-regular-expressions } -Vous pouvez définir un `pattern` d’expression régulière auquel le paramètre doit correspondre : +Vous pouvez définir un `pattern` d’expression régulière auquel le paramètre doit correspondre : {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -211,7 +187,7 @@ Vous pouvez, bien sûr, utiliser des valeurs par défaut autres que `None`. Disons que vous voulez déclarer le paramètre de requête `q` avec un `min_length` de `3`, et avec une valeur par défaut de « fixedquery » : -{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} /// note | Remarque @@ -241,7 +217,7 @@ q: Annotated[str | None, Query(min_length=3)] = None Donc, lorsque vous avez besoin de déclarer une valeur comme requise tout en utilisant `Query`, vous pouvez simplement ne pas déclarer de valeur par défaut : -{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} ### Requis, peut valoir `None` { #required-can-be-none } @@ -292,7 +268,7 @@ L’interface de documentation interactive de l’API sera mise à jour en cons Vous pouvez également définir une `list` de valeurs par défaut si aucune n’est fournie : -{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} Si vous allez à : @@ -315,7 +291,7 @@ la valeur par défaut de `q` sera : `["foo", "bar"]` et votre réponse sera : Vous pouvez aussi utiliser `list` directement au lieu de `list[str]` : -{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} /// note | Remarque @@ -371,7 +347,7 @@ Vous pouvez alors déclarer un `alias`, et cet alias sera utilisé pour trouver Disons que vous n’aimez plus ce paramètre. -Vous devez le laisser là quelque temps car des clients l’utilisent, mais vous voulez que les documents l’affichent clairement comme déprécié. +Vous devez le laisser là quelque temps car des clients l’utilisent, mais vous voulez que les documents l’affichent clairement comme déprécié. Passez alors le paramètre `deprecated=True` à `Query` : @@ -401,7 +377,7 @@ Pydantic a aussi
ISBN ou par `imdb-` pour un ID d’URL de film IMDB : +Par exemple, ce validateur personnalisé vérifie que l’ID d’item commence par `isbn-` pour un numéro de livre ISBN ou par `imdb-` pour un ID d’URL de film IMDB : {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -435,7 +411,7 @@ Avez-vous remarqué ? Une chaîne utilisant `value.startswith()` peut prendre un #### Un élément aléatoire { #a-random-item } -Avec `data.items()` nous obtenons un objet itérable avec des tuples contenant la clé et la valeur pour chaque élément du dictionnaire. +Avec `data.items()` nous obtenons un objet itérable avec des tuples contenant la clé et la valeur pour chaque élément du dictionnaire. Nous convertissons cet objet itérable en une `list` propre avec `list(data.items())`. diff --git a/docs/fr/docs/tutorial/query-params.md b/docs/fr/docs/tutorial/query-params.md index 1a4880ced..01540ad17 100644 --- a/docs/fr/docs/tutorial/query-params.md +++ b/docs/fr/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ Quand vous déclarez d'autres paramètres de fonction qui ne font pas partie des paramètres de chemin, ils sont automatiquement interprétés comme des paramètres de « query ». -{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} La query est l'ensemble des paires clé-valeur placées après le `?` dans une URL, séparées par des caractères `&`. @@ -24,7 +24,7 @@ Mais lorsque vous les déclarez avec des types Python (dans l'exemple ci-dessus, Tous les mêmes processus qui s'appliquaient aux paramètres de chemin s'appliquent aussi aux paramètres de requête : * Prise en charge de l'éditeur (évidemment) -* « parsing » des données +* « parsing » des données * Validation des données * Documentation automatique @@ -67,7 +67,7 @@ Dans ce cas, le paramètre de fonction `q` sera optionnel et vaudra `None` par d /// check | Vérifications -Notez également que FastAPI est suffisamment intelligent pour remarquer que le paramètre de chemin `item_id` est un paramètre de chemin et que `q` ne l'est pas, c'est donc un paramètre de requête. +Notez également que **FastAPI** est suffisamment intelligent pour remarquer que le paramètre de chemin `item_id` est un paramètre de chemin et que `q` ne l'est pas, c'est donc un paramètre de requête. /// @@ -127,7 +127,7 @@ Si vous ne voulez pas leur donner de valeur spécifique mais simplement les rend Mais si vous voulez rendre un paramètre de requête obligatoire, vous pouvez simplement ne déclarer aucune valeur par défaut : -{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} Ici, le paramètre de requête `needy` est un paramètre de requête requis de type `str`. diff --git a/docs/fr/docs/tutorial/request-files.md b/docs/fr/docs/tutorial/request-files.md new file mode 100644 index 000000000..01a0b72eb --- /dev/null +++ b/docs/fr/docs/tutorial/request-files.md @@ -0,0 +1,176 @@ +# Envoyer des fichiers { #request-files } + +Vous pouvez définir des fichiers à téléverser par le client en utilisant `File`. + +/// info + +Pour recevoir des fichiers téléversés, installez d'abord `python-multipart`. + +Assurez-vous de créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, de l'activer, puis d'installer le paquet, par exemple : + +```console +$ pip install python-multipart +``` + +C'est parce que les fichiers téléversés sont envoyés en « données de formulaire ». + +/// + +## Importer `File` { #import-file } + +Importez `File` et `UploadFile` depuis `fastapi` : + +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} + +## Définir des paramètres `File` { #define-file-parameters } + +Créez des paramètres de fichier de la même manière que pour `Body` ou `Form` : + +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} + +/// info + +`File` est une classe qui hérite directement de `Form`. + +Mais souvenez-vous que lorsque vous importez `Query`, `Path`, `File` et d'autres depuis `fastapi`, ce sont en réalité des fonctions qui renvoient des classes spéciales. + +/// + +/// tip | Astuce + +Pour déclarer des fichiers dans le corps de la requête, vous devez utiliser `File`, sinon les paramètres seraient interprétés comme des paramètres de requête ou des paramètres de corps (JSON). + +/// + +Les fichiers seront téléversés en « données de formulaire ». + +Si vous déclarez le type de votre paramètre de *fonction de chemin d'accès* comme `bytes`, **FastAPI** lira le fichier pour vous et vous recevrez le contenu sous forme de `bytes`. + +Gardez à l'esprit que cela signifie que tout le contenu sera stocké en mémoire. Cela fonctionnera bien pour de petits fichiers. + +Mais dans plusieurs cas, vous pourriez bénéficier de l'utilisation d'`UploadFile`. + +## Paramètres de fichier avec `UploadFile` { #file-parameters-with-uploadfile } + +Définissez un paramètre de fichier de type `UploadFile` : + +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} + +Utiliser `UploadFile` présente plusieurs avantages par rapport à `bytes` : + +- Vous n'avez pas besoin d'utiliser `File()` comme valeur par défaut du paramètre. +- Il utilise un fichier « spooled » : + - Un fichier stocké en mémoire jusqu'à une taille maximale, puis, au-delà de cette limite, stocké sur le disque. +- Cela fonctionne donc bien pour des fichiers volumineux comme des images, des vidéos, de gros binaires, etc., sans consommer toute la mémoire. +- Vous pouvez obtenir des métadonnées à partir du fichier téléversé. +- Il offre une interface `async` de type file-like. +- Il expose un véritable objet Python `SpooledTemporaryFile` que vous pouvez passer directement à d'autres bibliothèques qui attendent un objet « file-like ». + +### `UploadFile` { #uploadfile } + +`UploadFile` a les attributs suivants : + +- `filename` : une `str` contenant le nom de fichier original téléversé (par ex. `myimage.jpg`). +- `content_type` : une `str` avec le type de contenu (type MIME / type média) (par ex. `image/jpeg`). +- `file` : un `SpooledTemporaryFile` (un objet de type fichier). C'est l'objet fichier Python réel que vous pouvez passer directement à d'autres fonctions ou bibliothèques qui attendent un objet « file-like ». + +`UploadFile` a les méthodes `async` suivantes. Elles appellent toutes les méthodes correspondantes du fichier sous-jacent (en utilisant le `SpooledTemporaryFile` interne). + +- `write(data)` : écrit `data` (`str` ou `bytes`) dans le fichier. +- `read(size)` : lit `size` (`int`) octets/caractères du fichier. +- `seek(offset)` : se déplace à la position d'octet `offset` (`int`) dans le fichier. + - Par ex., `await myfile.seek(0)` irait au début du fichier. + - C'est particulièrement utile si vous exécutez `await myfile.read()` une fois puis devez relire le contenu. +- `close()` : ferme le fichier. + +Comme toutes ces méthodes sont `async`, vous devez les « await ». + +Par exemple, à l'intérieur d'une *fonction de chemin d'accès* `async`, vous pouvez obtenir le contenu avec : + +```Python +contents = await myfile.read() +``` + +Si vous êtes dans une *fonction de chemin d'accès* `def` normale, vous pouvez accéder directement à `UploadFile.file`, par exemple : + +```Python +contents = myfile.file.read() +``` + +/// note | Détails techniques `async` + +Lorsque vous utilisez les méthodes `async`, **FastAPI** exécute les méthodes de fichier dans un pool de threads et les attend. + +/// + +/// note | Détails techniques Starlette + +L'`UploadFile` de **FastAPI** hérite directement de l'`UploadFile` de **Starlette**, mais ajoute certaines parties nécessaires pour le rendre compatible avec **Pydantic** et les autres parties de FastAPI. + +/// + +## Qu'est-ce que les « données de formulaire » { #what-is-form-data } + +La façon dont les formulaires HTML (`
`) envoient les données au serveur utilise normalement un encodage « spécial » pour ces données, différent de JSON. + +**FastAPI** s'assure de lire ces données au bon endroit plutôt que depuis JSON. + +/// note | Détails techniques + +Les données des formulaires sont normalement encodées avec le « type de média » `application/x-www-form-urlencoded` lorsqu'elles n'incluent pas de fichiers. + +Mais lorsque le formulaire inclut des fichiers, il est encodé en `multipart/form-data`. Si vous utilisez `File`, **FastAPI** saura qu'il doit récupérer les fichiers depuis la partie appropriée du corps. + +Si vous souhaitez en savoir plus sur ces encodages et les champs de formulaire, consultez la MDN Web Docs pour POST. + +/// + +/// warning | Alertes + +Vous pouvez déclarer plusieurs paramètres `File` et `Form` dans un *chemin d'accès*, mais vous ne pouvez pas également déclarer des champs `Body` que vous vous attendez à recevoir en JSON, car la requête aura le corps encodé en `multipart/form-data` au lieu de `application/json`. + +Ce n'est pas une limitation de **FastAPI**, cela fait partie du protocole HTTP. + +/// + +## Téléversement de fichier facultatif { #optional-file-upload } + +Vous pouvez rendre un fichier facultatif en utilisant des annotations de type standard et en définissant une valeur par défaut à `None` : + +{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} + +## `UploadFile` avec des métadonnées supplémentaires { #uploadfile-with-additional-metadata } + +Vous pouvez aussi utiliser `File()` avec `UploadFile`, par exemple pour définir des métadonnées supplémentaires : + +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} + +## Téléverser plusieurs fichiers { #multiple-file-uploads } + +Il est possible de téléverser plusieurs fichiers en même temps. + +Ils seraient associés au même « champ de formulaire » envoyé en « données de formulaire ». + +Pour cela, déclarez une `list` de `bytes` ou d'`UploadFile` : + +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} + +Vous recevrez, comme déclaré, une `list` de `bytes` ou d'`UploadFile`. + +/// note | Détails techniques + +Vous pourriez aussi utiliser `from starlette.responses import HTMLResponse`. + +**FastAPI** fournit les mêmes `starlette.responses` sous `fastapi.responses` simplement pour votre convenance en tant que développeur. Mais la plupart des réponses disponibles proviennent directement de Starlette. + +/// + +### Téléversements multiples avec métadonnées supplémentaires { #multiple-file-uploads-with-additional-metadata } + +Et de la même manière que précédemment, vous pouvez utiliser `File()` pour définir des paramètres supplémentaires, même pour `UploadFile` : + +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} + +## Récapitulatif { #recap } + +Utilisez `File`, `bytes` et `UploadFile` pour déclarer des fichiers à téléverser dans la requête, envoyés en « données de formulaire ». diff --git a/docs/fr/docs/tutorial/request-form-models.md b/docs/fr/docs/tutorial/request-form-models.md new file mode 100644 index 000000000..3fbac9c74 --- /dev/null +++ b/docs/fr/docs/tutorial/request-form-models.md @@ -0,0 +1,78 @@ +# Modèles de formulaire { #form-models } + +Vous pouvez utiliser des **modèles Pydantic** pour déclarer des **champs de formulaire** dans FastAPI. + +/// info + +Pour utiliser les formulaires, installez d'abord `python-multipart`. + +Assurez-vous de créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, de l'activer, puis d'installer le paquet, par exemple : + +```console +$ pip install python-multipart +``` + +/// + +/// note | Remarque + +Ceci est pris en charge depuis la version `0.113.0` de FastAPI. 🤓 + +/// + +## Modèles Pydantic pour les formulaires { #pydantic-models-for-forms } + +Vous avez simplement besoin de déclarer un **modèle Pydantic** avec les champs que vous souhaitez recevoir comme **champs de formulaire**, puis de déclarer le paramètre comme `Form` : + +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} + +**FastAPI** va **extraire** les données pour **chaque champ** à partir des **données de formulaire** de la requête et vous fournir le modèle Pydantic que vous avez défini. + +## Consulter les documents { #check-the-docs } + +Vous pouvez le vérifier dans l'interface des documents à `/docs` : + +
+ +
+ +## Interdire les champs de formulaire supplémentaires { #forbid-extra-form-fields } + +Dans certains cas d'utilisation particuliers (probablement peu courants), vous pourriez vouloir **restreindre** les champs de formulaire à ceux déclarés dans le modèle Pydantic, et **interdire** tout champ **supplémentaire**. + +/// note | Remarque + +Ceci est pris en charge depuis la version `0.114.0` de FastAPI. 🤓 + +/// + +Vous pouvez utiliser la configuration du modèle Pydantic pour `forbid` tout champ `extra` : + +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} + +Si un client tente d'envoyer des données supplémentaires, il recevra une **réponse d'erreur**. + +Par exemple, si le client essaie d'envoyer les champs de formulaire : + +* `username`: `Rick` +* `password`: `Portal Gun` +* `extra`: `Mr. Poopybutthole` + +Il recevra une réponse d'erreur lui indiquant que le champ `extra` n'est pas autorisé : + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["body", "extra"], + "msg": "Extra inputs are not permitted", + "input": "Mr. Poopybutthole" + } + ] +} +``` + +## Résumer { #summary } + +Vous pouvez utiliser des modèles Pydantic pour déclarer des champs de formulaire dans FastAPI. 😎 diff --git a/docs/fr/docs/tutorial/request-forms-and-files.md b/docs/fr/docs/tutorial/request-forms-and-files.md new file mode 100644 index 000000000..6774eec8e --- /dev/null +++ b/docs/fr/docs/tutorial/request-forms-and-files.md @@ -0,0 +1,41 @@ +# Utiliser des formulaires et des fichiers de requête { #request-forms-and-files } + +Vous pouvez définir des fichiers et des champs de formulaire en même temps à l'aide de `File` et `Form`. + +/// info + +Pour recevoir des fichiers téléversés et/ou des données de formulaire, installez d'abord `python-multipart`. + +Vous devez créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, l'activer, puis installer ce paquet, par exemple : + +```console +$ pip install python-multipart +``` + +/// + +## Importer `File` et `Form` { #import-file-and-form } + +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} + +## Définir des paramètres `File` et `Form` { #define-file-and-form-parameters } + +Créez des paramètres de fichier et de formulaire de la même manière que pour `Body` ou `Query` : + +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} + +Les fichiers et les champs de formulaire seront téléversés en tant que données de formulaire et vous les recevrez. + +Et vous pouvez déclarer certains fichiers comme `bytes` et d'autres comme `UploadFile`. + +/// warning | Alertes + +Vous pouvez déclarer plusieurs paramètres `File` et `Form` dans un *chemin d'accès*, mais vous ne pouvez pas aussi déclarer des champs `Body` que vous vous attendez à recevoir en JSON, car la requête aura le corps encodé en `multipart/form-data` au lieu de `application/json`. + +Ce n'est pas une limitation de **FastAPI**, cela fait partie du protocole HTTP. + +/// + +## Récapitulatif { #recap } + +Utilisez `File` et `Form` ensemble lorsque vous devez recevoir des données et des fichiers dans la même requête. diff --git a/docs/fr/docs/tutorial/request-forms.md b/docs/fr/docs/tutorial/request-forms.md new file mode 100644 index 000000000..cea47c93e --- /dev/null +++ b/docs/fr/docs/tutorial/request-forms.md @@ -0,0 +1,73 @@ +# Données de formulaire { #form-data } + +Lorsque vous devez recevoir des champs de formulaire au lieu de JSON, vous pouvez utiliser `Form`. + +/// info + +Pour utiliser les formulaires, installez d'abord `python-multipart`. + +Assurez-vous de créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, de l'activer, puis d'installer ce paquet, par exemple : + +```console +$ pip install python-multipart +``` + +/// + +## Importer `Form` { #import-form } + +Importez `Form` depuis `fastapi` : + +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} + +## Définir les paramètres `Form` { #define-form-parameters } + +Créez des paramètres de formulaire comme vous le feriez pour `Body` ou `Query` : + +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} + +Par exemple, dans l'une des manières dont la spécification OAuth2 peut être utilisée (appelée « password flow »), il est requis d'envoyer un `username` et un `password` comme champs de formulaire. + +La spécification exige que les champs soient exactement nommés `username` et `password`, et qu'ils soient envoyés en tant que champs de formulaire, pas en JSON. + +Avec `Form`, vous pouvez déclarer les mêmes configurations que pour `Body` (ainsi que `Query`, `Path`, `Cookie`), y compris la validation, des exemples, un alias (p. ex. `user-name` au lieu de `username`), etc. + +/// info + +`Form` est une classe qui hérite directement de `Body`. + +/// + +/// tip | Astuce + +Pour déclarer des corps de formulaire, vous devez utiliser `Form` explicitement, car sinon les paramètres seraient interprétés comme des paramètres de requête ou des paramètres de corps (JSON). + +/// + +## À propos des « champs de formulaire » { #about-form-fields } + +La manière dont les formulaires HTML (`
`) envoient les données au serveur utilise normalement un encodage « spécial » pour ces données, différent de JSON. + +**FastAPI** s'assure de lire ces données au bon endroit au lieu de JSON. + +/// note | Détails techniques + +Les données issues des formulaires sont normalement encodées avec le « type de média » `application/x-www-form-urlencoded`. + +Mais lorsque le formulaire inclut des fichiers, il est encodé en `multipart/form-data`. Vous lirez la gestion des fichiers dans le chapitre suivant. + +Si vous voulez en savoir plus sur ces encodages et les champs de formulaire, consultez la MDN web docs pour POST. + +/// + +/// warning | Alertes + +Vous pouvez déclarer plusieurs paramètres `Form` dans un chemin d'accès, mais vous ne pouvez pas aussi déclarer des champs `Body` que vous vous attendez à recevoir en JSON, car la requête aura le corps encodé en `application/x-www-form-urlencoded` au lieu de `application/json`. + +Ce n'est pas une limitation de **FastAPI**, cela fait partie du protocole HTTP. + +/// + +## Récapitulatif { #recap } + +Utilisez `Form` pour déclarer les paramètres d'entrée des données de formulaire. diff --git a/docs/fr/docs/tutorial/response-model.md b/docs/fr/docs/tutorial/response-model.md new file mode 100644 index 000000000..337b1aa48 --- /dev/null +++ b/docs/fr/docs/tutorial/response-model.md @@ -0,0 +1,343 @@ +# Modèle de réponse - Type de retour { #response-model-return-type } + +Vous pouvez déclarer le type utilisé pour la réponse en annotant le **type de retour** de la *fonction de chemin d'accès*. + +Vous pouvez utiliser des **annotations de type** de la même manière que pour les données d'entrée dans les **paramètres** de fonction. Vous pouvez utiliser des modèles Pydantic, des listes, des dictionnaires, des valeurs scalaires comme des entiers, des booléens, etc. + +{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} + +FastAPI utilisera ce type de retour pour : + +* **Valider** les données renvoyées. + * Si les données sont invalides (par exemple, il manque un champ), cela signifie que le code de *votre* application est défectueux, qu'il ne renvoie pas ce qu'il devrait, et un erreur serveur sera renvoyée au lieu de renvoyer des données incorrectes. De cette façon, vous et vos clients pouvez être certains de recevoir les données attendues et avec la structure attendue. +* Ajouter un **JSON Schema** pour la réponse, dans l’OpenAPI du *chemin d'accès*. + * Ceci sera utilisé par la **documentation automatique**. + * Ceci sera également utilisé par les outils de génération automatique de code client. + +Mais surtout : + +* Il **limitera et filtrera** les données de sortie à ce qui est défini dans le type de retour. + * C'est particulièrement important pour la **sécurité**, nous verrons cela plus bas. + +## Paramètre `response_model` { #response-model-parameter } + +Il existe des cas où vous devez ou souhaitez renvoyer des données qui ne correspondent pas exactement à ce que déclare le type. + +Par exemple, vous pourriez vouloir **renvoyer un dictionnaire** ou un objet de base de données, mais **le déclarer comme un modèle Pydantic**. Ainsi, le modèle Pydantic ferait toute la documentation des données, la validation, etc. pour l'objet que vous avez renvoyé (par exemple un dictionnaire ou un objet de base de données). + +Si vous ajoutez l'annotation du type de retour, les outils et éditeurs se plaindront avec une (juste) erreur vous indiquant que votre fonction renvoie un type (par exemple un dict) différent de ce que vous avez déclaré (par exemple un modèle Pydantic). + +Dans ces cas, vous pouvez utiliser le paramètre `response_model` du *décorateur de chemin d'accès* au lieu du type de retour. + +Vous pouvez utiliser le paramètre `response_model` dans n'importe lequel des *chemins d'accès* : + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* etc. + +{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *} + +/// note | Remarque + +Notez que `response_model` est un paramètre de la méthode « decorator » (`get`, `post`, etc.). Pas de votre *fonction de chemin d'accès*, comme tous les paramètres et le corps. + +/// + +`response_model` reçoit le même type que vous déclareriez pour un champ de modèle Pydantic, il peut donc s'agir d'un modèle Pydantic, mais il peut aussi être, par exemple, une `list` de modèles Pydantic, comme `List[Item]`. + +FastAPI utilisera ce `response_model` pour toute la documentation des données, la validation, etc. et aussi pour **convertir et filtrer les données de sortie** selon sa déclaration de type. + +/// tip | Astuce + +Si vous avez des vérifications de type strictes dans votre éditeur, mypy, etc., vous pouvez déclarer le type de retour de la fonction en `Any`. + +Ainsi, vous indiquez à l'éditeur que vous renvoyez intentionnellement n'importe quoi. Mais FastAPI effectuera quand même la documentation, la validation, le filtrage, etc. des données avec `response_model`. + +/// + +### Priorité de `response_model` { #response-model-priority } + +Si vous déclarez à la fois un type de retour et un `response_model`, c'est `response_model` qui aura la priorité et sera utilisé par FastAPI. + +De cette manière, vous pouvez ajouter des annotations de type correctes à vos fonctions même si vous renvoyez un type différent du modèle de réponse, pour qu'il soit utilisé par l'éditeur et des outils comme mypy. Et vous pouvez toujours laisser FastAPI faire la validation des données, la documentation, etc. avec `response_model`. + +Vous pouvez également utiliser `response_model=None` pour désactiver la création d’un modèle de réponse pour ce *chemin d'accès* ; vous pourriez en avoir besoin si vous ajoutez des annotations de type pour des choses qui ne sont pas des champs valides Pydantic, vous verrez un exemple de cela dans une des sections ci-dessous. + +## Renvoyer les mêmes données d'entrée { #return-the-same-input-data } + +Ici, nous déclarons un modèle `UserIn`, il contiendra un mot de passe en clair : + +{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *} + +/// info | Info + +Pour utiliser `EmailStr`, installez d'abord `email-validator`. + +Assurez-vous de créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, de l'activer, puis de l'installer, par exemple : + +```console +$ pip install email-validator +``` + +ou avec : + +```console +$ pip install "pydantic[email]" +``` + +/// + +Et nous utilisons ce modèle pour déclarer notre entrée et le même modèle pour déclarer notre sortie : + +{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *} + +Désormais, chaque fois qu'un navigateur crée un utilisateur avec un mot de passe, l'API renverra le même mot de passe dans la réponse. + +Dans ce cas, cela peut ne pas poser de problème, car c'est le même utilisateur qui envoie le mot de passe. + +Mais si nous utilisons le même modèle pour un autre *chemin d'accès*, nous pourrions envoyer les mots de passe de nos utilisateurs à tous les clients. + +/// danger | Danger + +Ne stockez jamais le mot de passe en clair d'un utilisateur et ne l'envoyez pas dans une réponse de cette manière, à moins de connaître tous les écueils et de savoir exactement ce que vous faites. + +/// + +## Ajouter un modèle de sortie { #add-an-output-model } + +Nous pouvons à la place créer un modèle d'entrée avec le mot de passe en clair et un modèle de sortie sans celui-ci : + +{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *} + +Ici, même si notre *fonction de chemin d'accès* renvoie le même utilisateur d'entrée qui contient le mot de passe : + +{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *} + +... nous avons déclaré `response_model` comme étant notre modèle `UserOut`, qui n'inclut pas le mot de passe : + +{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *} + +Ainsi, **FastAPI** se chargera de filtrer toutes les données qui ne sont pas déclarées dans le modèle de sortie (en utilisant Pydantic). + +### `response_model` ou type de retour { #response-model-or-return-type } + +Dans ce cas, comme les deux modèles sont différents, si nous annotions le type de retour de la fonction en `UserOut`, l’éditeur et les outils se plaindraient que nous renvoyons un type invalide, car ce sont des classes différentes. + +C'est pourquoi, dans cet exemple, nous devons le déclarer dans le paramètre `response_model`. + +... mais continuez à lire ci-dessous pour voir comment contourner cela. + +## Type de retour et filtrage des données { #return-type-and-data-filtering } + +Continuons l'exemple précédent. Nous voulions **annoter la fonction avec un type**, mais nous voulions pouvoir renvoyer depuis la fonction quelque chose qui inclut **plus de données**. + +Nous voulons que FastAPI continue de **filtrer** les données à l’aide du modèle de réponse. Ainsi, même si la fonction renvoie plus de données, la réponse n’inclura que les champs déclarés dans le modèle de réponse. + +Dans l'exemple précédent, comme les classes étaient différentes, nous avons dû utiliser le paramètre `response_model`. Mais cela signifie aussi que nous ne bénéficions pas de la prise en charge de l'éditeur et des outils pour la vérification du type de retour de la fonction. + +Mais dans la plupart des cas où nous avons besoin de quelque chose comme cela, nous voulons que le modèle **filtre/supprime** simplement une partie des données comme dans cet exemple. + +Et dans ces cas, nous pouvons utiliser des classes et l'héritage pour tirer parti des **annotations de type** de fonction afin d'obtenir une meilleure prise en charge dans l'éditeur et les outils, tout en bénéficiant du **filtrage de données** de FastAPI. + +{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *} + +Avec cela, nous obtenons la prise en charge des outils, des éditeurs et de mypy car ce code est correct en termes de types, et nous bénéficions également du filtrage des données par FastAPI. + +Comment cela fonctionne-t-il ? Voyons cela. 🤓 + +### Annotations de type et outils { #type-annotations-and-tooling } + +Voyons d'abord comment les éditeurs, mypy et autres outils considèreraient cela. + +`BaseUser` a les champs de base. Puis `UserIn` hérite de `BaseUser` et ajoute le champ `password`, il inclura donc tous les champs des deux modèles. + +Nous annotons le type de retour de la fonction en `BaseUser`, mais nous renvoyons en réalité une instance de `UserIn`. + +L’éditeur, mypy et d'autres outils ne s’en plaindront pas car, en termes de typage, `UserIn` est une sous-classe de `BaseUser`, ce qui signifie que c’est un type *valide* lorsque ce qui est attendu est n'importe quoi de type `BaseUser`. + +### Filtrage des données par FastAPI { #fastapi-data-filtering } + +Maintenant, pour FastAPI, il verra le type de retour et s'assurera que ce que vous renvoyez inclut **uniquement** les champs qui sont déclarés dans le type. + +FastAPI fait plusieurs choses en interne avec Pydantic pour s'assurer que ces mêmes règles d'héritage de classes ne sont pas utilisées pour le filtrage des données renvoyées, sinon vous pourriez finir par renvoyer beaucoup plus de données que prévu. + +De cette façon, vous obtenez le meilleur des deux mondes : annotations de type avec **prise en charge par les outils** et **filtrage des données**. + +## Le voir dans la documentation { #see-it-in-the-docs } + +Dans la documentation automatique, vous pouvez vérifier que le modèle d'entrée et le modèle de sortie auront chacun leur propre JSON Schema : + + + +Et les deux modèles seront utilisés pour la documentation API interactive : + + + +## Autres annotations de type de retour { #other-return-type-annotations } + +Il peut y avoir des cas où vous renvoyez quelque chose qui n'est pas un champ Pydantic valide et vous l'annotez dans la fonction, uniquement pour obtenir la prise en charge fournie par les outils (l’éditeur, mypy, etc.). + +### Renvoyer directement une Response { #return-a-response-directly } + +Le cas le plus courant serait [de renvoyer directement une Response comme expliqué plus loin dans la documentation avancée](../advanced/response-directly.md){.internal-link target=_blank}. + +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} + +Ce cas simple est géré automatiquement par FastAPI car l'annotation du type de retour est la classe (ou une sous-classe de) `Response`. + +Et les outils seront également satisfaits car `RedirectResponse` et `JSONResponse` sont des sous-classes de `Response`, donc l'annotation de type est correcte. + +### Annoter une sous-classe de Response { #annotate-a-response-subclass } + +Vous pouvez aussi utiliser une sous-classe de `Response` dans l'annotation de type : + +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} + +Cela fonctionnera également car `RedirectResponse` est une sous-classe de `Response`, et FastAPI gérera automatiquement ce cas simple. + +### Annotations de type de retour invalides { #invalid-return-type-annotations } + +Mais lorsque vous renvoyez un autre objet arbitraire qui n'est pas un type Pydantic valide (par exemple un objet de base de données) et que vous l'annotez ainsi dans la fonction, FastAPI essaiera de créer un modèle de réponse Pydantic à partir de cette annotation de type, et échouera. + +Il en serait de même si vous aviez quelque chose comme une union entre différents types dont un ou plusieurs ne sont pas des types Pydantic valides, par exemple ceci échouerait 💥 : + +{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} + +... cela échoue parce que l'annotation de type n'est pas un type Pydantic et n'est pas juste une unique classe `Response` ou une sous-classe, c'est une union (l'un des deux) entre une `Response` et un `dict`. + +### Désactiver le modèle de réponse { #disable-response-model } + +En reprenant l'exemple ci-dessus, vous pourriez ne pas vouloir avoir la validation par défaut des données, la documentation, le filtrage, etc. effectués par FastAPI. + +Mais vous pourriez vouloir tout de même conserver l’annotation du type de retour dans la fonction pour bénéficier de la prise en charge des outils comme les éditeurs et les vérificateurs de type (par exemple mypy). + +Dans ce cas, vous pouvez désactiver la génération du modèle de réponse en définissant `response_model=None` : + +{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *} + +Cela fera en sorte que FastAPI ignore la génération du modèle de réponse et vous permettra ainsi d’avoir toutes les annotations de type de retour dont vous avez besoin sans que cela n’affecte votre application FastAPI. 🤓 + +## Paramètres d'encodage du modèle de réponse { #response-model-encoding-parameters } + +Votre modèle de réponse peut avoir des valeurs par défaut, par exemple : + +{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *} + +* `description: Union[str, None] = None` (ou `str | None = None` en Python 3.10) a une valeur par défaut `None`. +* `tax: float = 10.5` a une valeur par défaut `10.5`. +* `tags: List[str] = []` a une valeur par défaut de liste vide : `[]`. + +mais vous pourriez vouloir les omettre du résultat si elles n'ont pas été réellement stockées. + +Par exemple, si vous avez des modèles avec de nombreux attributs optionnels dans une base NoSQL, mais que vous ne voulez pas envoyer de très longues réponses JSON remplies de valeurs par défaut. + +### Utiliser le paramètre `response_model_exclude_unset` { #use-the-response-model-exclude-unset-parameter } + +Vous pouvez définir le paramètre du *décorateur de chemin d'accès* `response_model_exclude_unset=True` : + +{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *} + +et ces valeurs par défaut ne seront pas incluses dans la réponse, uniquement les valeurs effectivement définies. + +Ainsi, si vous envoyez une requête à ce *chemin d'accès* pour l'article avec l'ID `foo`, la réponse (sans les valeurs par défaut) sera : + +```JSON +{ + "name": "Foo", + "price": 50.2 +} +``` + +/// info | Info + +Vous pouvez également utiliser : + +* `response_model_exclude_defaults=True` +* `response_model_exclude_none=True` + +comme décrit dans la documentation Pydantic pour `exclude_defaults` et `exclude_none`. + +/// + +#### Données avec des valeurs pour des champs avec des valeurs par défaut { #data-with-values-for-fields-with-defaults } + +Mais si vos données ont des valeurs pour les champs du modèle avec des valeurs par défaut, comme l'article avec l'ID `bar` : + +```Python hl_lines="3 5" +{ + "name": "Bar", + "description": "The bartenders", + "price": 62, + "tax": 20.2 +} +``` + +elles seront incluses dans la réponse. + +#### Données avec les mêmes valeurs que les valeurs par défaut { #data-with-the-same-values-as-the-defaults } + +Si les données ont les mêmes valeurs que les valeurs par défaut, comme l'article avec l'ID `baz` : + +```Python hl_lines="3 5-6" +{ + "name": "Baz", + "description": None, + "price": 50.2, + "tax": 10.5, + "tags": [] +} +``` + +FastAPI est suffisamment intelligent (en fait, Pydantic l’est) pour comprendre que, même si `description`, `tax` et `tags` ont les mêmes valeurs que les valeurs par défaut, elles ont été définies explicitement (au lieu d'être prises depuis les valeurs par défaut). + +Elles seront donc incluses dans la réponse JSON. + +/// tip | Astuce + +Notez que les valeurs par défaut peuvent être n'importe quoi, pas seulement `None`. + +Elles peuvent être une liste (`[]`), un `float` de `10.5`, etc. + +/// + +### `response_model_include` et `response_model_exclude` { #response-model-include-and-response-model-exclude } + +Vous pouvez également utiliser les paramètres du *décorateur de chemin d'accès* `response_model_include` et `response_model_exclude`. + +Ils prennent un `set` de `str` avec les noms des attributs à inclure (en omettant le reste) ou à exclure (en incluant le reste). + +Cela peut être utilisé comme un raccourci rapide si vous n'avez qu'un seul modèle Pydantic et que vous souhaitez supprimer certaines données de la sortie. + +/// tip | Astuce + +Mais il est toujours recommandé d'utiliser les idées ci-dessus, en utilisant plusieurs classes, plutôt que ces paramètres. + +En effet, le JSON Schema généré dans l’OpenAPI de votre application (et la documentation) restera celui du modèle complet, même si vous utilisez `response_model_include` ou `response_model_exclude` pour omettre certains attributs. + +Cela s'applique également à `response_model_by_alias` qui fonctionne de manière similaire. + +/// + +{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *} + +/// tip | Astuce + +La syntaxe `{"name", "description"}` crée un `set` avec ces deux valeurs. + +Elle est équivalente à `set(["name", "description"])`. + +/// + +#### Utiliser des `list` au lieu de `set` { #using-lists-instead-of-sets } + +Si vous oubliez d'utiliser un `set` et utilisez une `list` ou un `tuple` à la place, FastAPI le convertira quand même en `set` et cela fonctionnera correctement : + +{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *} + +## Récapitulatif { #recap } + +Utilisez le paramètre du *décorateur de chemin d'accès* `response_model` pour définir les modèles de réponse et surtout pour garantir que les données privées sont filtrées. + +Utilisez `response_model_exclude_unset` pour ne renvoyer que les valeurs définies explicitement. diff --git a/docs/fr/docs/tutorial/response-status-code.md b/docs/fr/docs/tutorial/response-status-code.md new file mode 100644 index 000000000..388a53b3d --- /dev/null +++ b/docs/fr/docs/tutorial/response-status-code.md @@ -0,0 +1,101 @@ +# Code d'état de la réponse { #response-status-code } + +De la même manière que vous pouvez spécifier un modèle de réponse, vous pouvez également déclarer le code d'état HTTP utilisé pour la réponse avec le paramètre `status_code` dans n'importe lequel des chemins d'accès : + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* etc. + +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} + +/// note | Remarque + +Remarquez que `status_code` est un paramètre de la méthode « decorator » (`get`, `post`, etc.). Pas de votre fonction de chemin d'accès, comme tous les paramètres et le corps. + +/// + +Le paramètre `status_code` reçoit un nombre correspondant au code d'état HTTP. + +/// info + +`status_code` peut aussi recevoir un `IntEnum`, comme le `http.HTTPStatus` de Python. + +/// + +Il va : + +* Renvoyer ce code d'état dans la réponse. +* Le documenter comme tel dans le schéma OpenAPI (et donc dans les interfaces utilisateur) : + + + +/// note | Remarque + +Certains codes de réponse (voir la section suivante) indiquent que la réponse n'a pas de corps. + +FastAPI le sait et produira une documentation OpenAPI indiquant qu'il n'y a pas de corps de réponse. + +/// + +## À propos des codes d'état HTTP { #about-http-status-codes } + +/// note | Remarque + +Si vous savez déjà ce que sont les codes d'état HTTP, passez à la section suivante. + +/// + +En HTTP, vous envoyez un code d'état numérique de 3 chiffres dans la réponse. + +Ces codes d'état ont un nom associé pour les reconnaître, mais la partie importante est le nombre. + +En bref : + +* `100 - 199` sont pour « Information ». Vous les utilisez rarement directement. Les réponses avec ces codes d'état ne peuvent pas avoir de corps. +* **`200 - 299`** sont pour les réponses de « Succès ». Ce sont celles que vous utiliserez le plus. + * `200` est le code d'état par défaut, ce qui signifie que tout était « OK ». + * Un autre exemple est `201`, « Créé ». Il est couramment utilisé après la création d'un nouvel enregistrement dans la base de données. + * Un cas particulier est `204`, « Aucun contenu ». Cette réponse est utilisée lorsqu'il n'y a aucun contenu à renvoyer au client ; la réponse ne doit donc pas avoir de corps. +* **`300 - 399`** sont pour la « Redirection ». Les réponses avec ces codes d'état peuvent avoir ou non un corps, sauf `304`, « Non modifié », qui ne doit pas en avoir. +* **`400 - 499`** sont pour les réponses d'« Erreur côté client ». C'est probablement le deuxième type que vous utiliserez le plus. + * Un exemple est `404`, pour une réponse « Non trouvé ». + * Pour des erreurs génériques du client, vous pouvez simplement utiliser `400`. +* `500 - 599` sont pour les erreurs côté serveur. Vous ne les utilisez presque jamais directement. Lorsqu'un problème survient quelque part dans le code de votre application ou sur le serveur, il renverra automatiquement l'un de ces codes d'état. + +/// tip | Astuce + +Pour en savoir plus sur chaque code d'état et à quoi il correspond, consultez la MDN documentation about HTTP status codes. + +/// + +## Raccourci pour se souvenir des noms { #shortcut-to-remember-the-names } + +Reprenons l'exemple précédent : + +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} + +`201` est le code d'état pour « Créé ». + +Mais vous n'avez pas à mémoriser la signification de chacun de ces codes. + +Vous pouvez utiliser les variables pratiques de `fastapi.status`. + +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} + +Elles ne sont qu'une commodité, elles contiennent le même nombre, mais de cette façon vous pouvez utiliser l'autocomplétion de l'éditeur pour les trouver : + + + +/// note | Détails techniques + +Vous pourriez aussi utiliser `from starlette import status`. + +FastAPI fournit le même `starlette.status` que `fastapi.status`, uniquement pour votre commodité de développeur. Mais cela vient directement de Starlette. + +/// + +## Modifier la valeur par défaut { #changing-the-default } + +Plus tard, dans le [Guide utilisateur avancé](../advanced/response-change-status-code.md){.internal-link target=_blank}, vous verrez comment renvoyer un code d'état différent de celui par défaut que vous déclarez ici. diff --git a/docs/fr/docs/tutorial/schema-extra-example.md b/docs/fr/docs/tutorial/schema-extra-example.md new file mode 100644 index 000000000..d4403c779 --- /dev/null +++ b/docs/fr/docs/tutorial/schema-extra-example.md @@ -0,0 +1,202 @@ +# Déclarer des exemples de données de requête { #declare-request-example-data } + +Vous pouvez déclarer des exemples des données que votre application peut recevoir. + +Voici plusieurs façons de le faire. + +## Ajouter des données JSON Schema supplémentaires dans les modèles Pydantic { #extra-json-schema-data-in-pydantic-models } + +Vous pouvez déclarer `examples` pour un modèle Pydantic qui seront ajoutés au JSON Schema généré. + +{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} + +Ces informations supplémentaires seront ajoutées telles quelles au **JSON Schema** de sortie pour ce modèle, et elles seront utilisées dans la documentation de l'API. + +Vous pouvez utiliser l'attribut `model_config` qui accepte un `dict` comme décrit dans Documentation de Pydantic : Configuration. + +Vous pouvez définir `"json_schema_extra"` avec un `dict` contenant toutes les données supplémentaires que vous souhaitez voir apparaître dans le JSON Schema généré, y compris `examples`. + +/// tip | Astuce + +Vous pouvez utiliser la même technique pour étendre le JSON Schema et ajouter vos propres informations supplémentaires personnalisées. + +Par exemple, vous pourriez l'utiliser pour ajouter des métadonnées pour une interface utilisateur frontend, etc. + +/// + +/// info + +OpenAPI 3.1.0 (utilisé depuis FastAPI 0.99.0) a ajouté la prise en charge de `examples`, qui fait partie du standard **JSON Schema**. + +Avant cela, seule la clé `example` avec un exemple unique était prise en charge. Elle l'est toujours par OpenAPI 3.1.0, mais elle est dépréciée et ne fait pas partie du standard JSON Schema. Vous êtes donc encouragé à migrer de `example` vers `examples`. 🤓 + +Vous pouvez en lire davantage à la fin de cette page. + +/// + +## Arguments supplémentaires de `Field` { #field-additional-arguments } + +Lorsque vous utilisez `Field()` avec des modèles Pydantic, vous pouvez également déclarer des `examples` supplémentaires : + +{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} + +## `examples` dans JSON Schema - OpenAPI { #examples-in-json-schema-openapi } + +En utilisant l'un des éléments suivants : + +* `Path()` +* `Query()` +* `Header()` +* `Cookie()` +* `Body()` +* `Form()` +* `File()` + +vous pouvez également déclarer un groupe de `examples` avec des informations supplémentaires qui seront ajoutées à leurs **JSON Schemas** à l'intérieur d'**OpenAPI**. + +### `Body` avec `examples` { #body-with-examples } + +Ici, nous passons `examples` contenant un exemple des données attendues dans `Body()` : + +{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *} + +### Exemple dans l'interface des documents { #example-in-the-docs-ui } + +Avec l'une des méthodes ci-dessus, cela ressemblerait à ceci dans le `/docs` : + + + +### `Body` avec plusieurs `examples` { #body-with-multiple-examples } + +Vous pouvez bien sûr aussi passer plusieurs `examples` : + +{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} + +Lorsque vous faites cela, les exemples feront partie du **JSON Schema** interne pour ces données de corps. + +Néanmoins, au moment de la rédaction, Swagger UI, l'outil chargé d'afficher l'interface des documents, ne prend pas en charge l'affichage de plusieurs exemples pour les données dans **JSON Schema**. Mais lisez ci-dessous pour un contournement. + +### `examples` spécifiques à OpenAPI { #openapi-specific-examples } + +Avant que **JSON Schema** ne prenne en charge `examples`, OpenAPI prenait déjà en charge un autre champ également appelé `examples`. + +Ce `examples` **spécifique à OpenAPI** se trouve dans une autre section de la spécification OpenAPI. Il se trouve dans les **détails de chaque *chemin d'accès***, et non à l'intérieur de chaque JSON Schema. + +Et Swagger UI prend en charge ce champ particulier `examples` depuis un certain temps. Vous pouvez donc l'utiliser pour **afficher** différents **exemples dans l'interface des documents**. + +La forme de ce champ `examples` spécifique à OpenAPI est un `dict` avec **plusieurs exemples** (au lieu d'une `list`), chacun avec des informations supplémentaires qui seront également ajoutées à **OpenAPI**. + +Cela ne va pas à l'intérieur de chaque JSON Schema contenu dans OpenAPI, cela se place à l'extérieur, directement dans le *chemin d'accès*. + +### Utiliser le paramètre `openapi_examples` { #using-the-openapi-examples-parameter } + +Vous pouvez déclarer le `examples` spécifique à OpenAPI dans FastAPI avec le paramètre `openapi_examples` pour : + +* `Path()` +* `Query()` +* `Header()` +* `Cookie()` +* `Body()` +* `Form()` +* `File()` + +Les clés du `dict` identifient chaque exemple, et chaque valeur est un autre `dict`. + +Chaque `dict` d'exemple spécifique dans `examples` peut contenir : + +* `summary` : une courte description de l'exemple. +* `description` : une description longue qui peut contenir du texte Markdown. +* `value` : c'est l'exemple réel affiché, par ex. un `dict`. +* `externalValue` : alternative à `value`, une URL pointant vers l'exemple. Cependant, cela pourrait ne pas être pris en charge par autant d'outils que `value`. + +Vous pouvez l'utiliser ainsi : + +{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *} + +### Exemples OpenAPI dans l'interface des documents { #openapi-examples-in-the-docs-ui } + +Avec `openapi_examples` ajouté à `Body()`, le `/docs` ressemblerait à : + + + +## Détails techniques { #technical-details } + +/// tip | Astuce + +Si vous utilisez déjà **FastAPI** en version **0.99.0 ou supérieure**, vous pouvez probablement **passer** ces détails. + +Ils sont plus pertinents pour les versions plus anciennes, avant que OpenAPI 3.1.0 ne soit disponible. + +Vous pouvez considérer ceci comme une courte leçon d'histoire d'OpenAPI et de JSON Schema. 🤓 + +/// + +/// warning | Alertes + +Ce sont des détails très techniques au sujet des standards **JSON Schema** et **OpenAPI**. + +Si les idées ci-dessus fonctionnent déjà pour vous, cela pourrait suffire, et vous n'avez probablement pas besoin de ces détails, n'hésitez pas à les ignorer. + +/// + +Avant OpenAPI 3.1.0, OpenAPI utilisait une version plus ancienne et modifiée de **JSON Schema**. + +JSON Schema n'avait pas `examples`, donc OpenAPI a ajouté son propre champ `example` à sa version modifiée. + +OpenAPI a également ajouté les champs `example` et `examples` à d'autres parties de la spécification : + +* `Parameter Object` (dans la spécification) qui était utilisé par les éléments FastAPI : + * `Path()` + * `Query()` + * `Header()` + * `Cookie()` +* `Request Body Object`, dans le champ `content`, sur le `Media Type Object` (dans la spécification) qui était utilisé par les éléments FastAPI : + * `Body()` + * `File()` + * `Form()` + +/// info + +Ce paramètre `examples` ancien et spécifique à OpenAPI est désormais `openapi_examples` depuis FastAPI `0.103.0`. + +/// + +### Le champ `examples` de JSON Schema { #json-schemas-examples-field } + +Ensuite, JSON Schema a ajouté un champ `examples` dans une nouvelle version de la spécification. + +Puis le nouveau OpenAPI 3.1.0 s'est basé sur la dernière version (JSON Schema 2020-12) qui incluait ce nouveau champ `examples`. + +Et désormais, ce nouveau champ `examples` a priorité sur l'ancien champ unique (et personnalisé) `example`, qui est maintenant déprécié. + +Ce nouveau champ `examples` dans JSON Schema est **juste une `list`** d'exemples, et non pas un dict avec des métadonnées supplémentaires comme dans les autres endroits d'OpenAPI (décrits ci-dessus). + +/// info + +Même après la sortie d'OpenAPI 3.1.0 avec cette nouvelle intégration plus simple avec JSON Schema, pendant un temps, Swagger UI, l'outil qui fournit la documentation automatique, ne prenait pas en charge OpenAPI 3.1.0 (il le fait depuis la version 5.0.0 🎉). + +À cause de cela, les versions de FastAPI antérieures à 0.99.0 utilisaient encore des versions d'OpenAPI inférieures à 3.1.0. + +/// + +### `examples` avec Pydantic et FastAPI { #pydantic-and-fastapi-examples } + +Lorsque vous ajoutez `examples` dans un modèle Pydantic, en utilisant `schema_extra` ou `Field(examples=["something"])`, cet exemple est ajouté au **JSON Schema** de ce modèle Pydantic. + +Et ce **JSON Schema** du modèle Pydantic est inclus dans l'**OpenAPI** de votre API, puis il est utilisé dans l'interface de la documentation. + +Dans les versions de FastAPI antérieures à 0.99.0 (0.99.0 et supérieures utilisent le nouveau OpenAPI 3.1.0), lorsque vous utilisiez `example` ou `examples` avec l'une des autres utilitaires (`Query()`, `Body()`, etc.), ces exemples n'étaient pas ajoutés au JSON Schema qui décrit ces données (pas même à la version de JSON Schema propre à OpenAPI), ils étaient ajoutés directement à la déclaration du *chemin d'accès* dans OpenAPI (en dehors des parties d'OpenAPI qui utilisent JSON Schema). + +Mais maintenant que FastAPI 0.99.0 et supérieures utilisent OpenAPI 3.1.0, qui utilise JSON Schema 2020-12, et Swagger UI 5.0.0 et supérieures, tout est plus cohérent et les exemples sont inclus dans JSON Schema. + +### Swagger UI et `examples` spécifiques à OpenAPI { #swagger-ui-and-openapi-specific-examples } + +Comme Swagger UI ne prenait pas en charge plusieurs exemples JSON Schema (au 2023-08-26), les utilisateurs n'avaient pas de moyen d'afficher plusieurs exemples dans les documents. + +Pour résoudre cela, FastAPI `0.103.0` a **ajouté la prise en charge** de la déclaration du même ancien champ `examples` **spécifique à OpenAPI** avec le nouveau paramètre `openapi_examples`. 🤓 + +### Résumé { #summary } + +Je disais que je n'aimais pas trop l'histoire ... et me voilà maintenant à donner des leçons d'« tech history ». 😅 + +En bref, **mettez à niveau vers FastAPI 0.99.0 ou supérieur**, et les choses sont bien plus **simples, cohérentes et intuitives**, et vous n'avez pas besoin de connaître tous ces détails historiques. 😎 diff --git a/docs/fr/docs/tutorial/security/first-steps.md b/docs/fr/docs/tutorial/security/first-steps.md new file mode 100644 index 000000000..8c4eb50d7 --- /dev/null +++ b/docs/fr/docs/tutorial/security/first-steps.md @@ -0,0 +1,203 @@ +# Sécurité - Premiers pas { #security-first-steps } + +Imaginons que vous ayez votre API de **backend** sur un certain domaine. + +Et vous avez un **frontend** sur un autre domaine ou dans un chemin différent du même domaine (ou dans une application mobile). + +Et vous voulez que le **frontend** puisse s'authentifier auprès du **backend**, en utilisant un **username** et un **password**. + +Nous pouvons utiliser **OAuth2** pour construire cela avec **FastAPI**. + +Mais épargnons-vous le temps de lire toute la spécification complète juste pour trouver les petites informations dont vous avez besoin. + +Utilisons les outils fournis par **FastAPI** pour gérer la sécurité. + +## Voir à quoi cela ressemble { #how-it-looks } + +Commençons par utiliser le code et voir comment cela fonctionne, puis nous reviendrons pour comprendre ce qui se passe. + +## Créer `main.py` { #create-main-py } + +Copiez l'exemple dans un fichier `main.py` : + +{* ../../docs_src/security/tutorial001_an_py310.py *} + +## Exécuter { #run-it } + +/// info + +Le package `python-multipart` est installé automatiquement avec **FastAPI** lorsque vous exécutez la commande `pip install "fastapi[standard]"`. + +Cependant, si vous utilisez la commande `pip install fastapi`, le package `python-multipart` n'est pas inclus par défaut. + +Pour l'installer manuellement, vous devez vous assurer de créer un [environnement virtuel](../../virtual-environments.md){.internal-link target=_blank}, de l'activer, puis de l'installer avec : + +```console +$ pip install python-multipart +``` + +Cela est dû au fait que **OAuth2** utilise des « form data » pour envoyer le `username` et le `password`. + +/// + +Exécutez l'exemple avec : + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +## Vérifier { #check-it } + +Allez à la documentation interactive à l'adresse : http://127.0.0.1:8000/docs. + +Vous verrez quelque chose comme ceci : + + + +/// check | Bouton « Authorize » ! + +Vous avez déjà un tout nouveau bouton « Authorize ». + +Et votre *chemin d'accès* a un petit cadenas dans le coin supérieur droit sur lequel vous pouvez cliquer. + +/// + +Et si vous cliquez dessus, vous obtenez un petit formulaire d'autorisation pour saisir un `username` et un `password` (et d'autres champs optionnels) : + + + +/// note | Remarque + +Peu importe ce que vous saisissez dans le formulaire, cela ne fonctionnera pas encore. Mais nous y viendrons. + +/// + +Ce n'est bien sûr pas le frontend pour les utilisateurs finaux, mais c'est un excellent outil automatique pour documenter de manière interactive toute votre API. + +Il peut être utilisé par l'équipe frontend (qui peut aussi être vous-même). + +Il peut être utilisé par des applications et des systèmes tiers. + +Et il peut aussi être utilisé par vous-même, pour déboguer, vérifier et tester la même application. + +## Le flux `password` { #the-password-flow } + +Revenons un peu en arrière et comprenons de quoi il s'agit. + +Le « flux » `password` est l'une des manières (« flows ») définies dans OAuth2 pour gérer la sécurité et l'authentification. + +OAuth2 a été conçu pour que le backend ou l'API puisse être indépendant du serveur qui authentifie l'utilisateur. + +Mais dans ce cas, la même application **FastAPI** gérera l'API et l'authentification. + +Voyons cela selon ce point de vue simplifié : + +- L'utilisateur saisit le `username` et le `password` dans le frontend, puis appuie sur Entrée. +- Le frontend (exécuté dans le navigateur de l'utilisateur) envoie ce `username` et ce `password` vers une URL spécifique de notre API (déclarée avec `tokenUrl="token"`). +- L'API vérifie ce `username` et ce `password`, et répond avec un « token » (nous n'avons encore rien implémenté de tout cela). + - Un « token » n'est qu'une chaîne contenant des informations que nous pouvons utiliser plus tard pour vérifier cet utilisateur. + - Normalement, un token est configuré pour expirer après un certain temps. + - Ainsi, l'utilisateur devra se reconnecter à un moment donné. + - Et si le token est volé, le risque est moindre. Ce n'est pas une clé permanente qui fonctionnerait indéfiniment (dans la plupart des cas). +- Le frontend stocke ce token temporairement quelque part. +- L'utilisateur clique dans le frontend pour aller vers une autre section de l'application web frontend. +- Le frontend doit récupérer d'autres données depuis l'API. + - Mais cela nécessite une authentification pour cet endpoint spécifique. + - Donc, pour s'authentifier auprès de notre API, il envoie un en-tête `Authorization` avec une valeur `Bearer ` suivie du token. + - Si le token contient `foobar`, le contenu de l'en-tête `Authorization` serait : `Bearer foobar`. + +## Le `OAuth2PasswordBearer` de **FastAPI** { #fastapis-oauth2passwordbearer } + +**FastAPI** fournit plusieurs outils, à différents niveaux d'abstraction, pour implémenter ces fonctionnalités de sécurité. + +Dans cet exemple, nous allons utiliser **OAuth2**, avec le flux **Password**, en utilisant un token **Bearer**. Nous le faisons avec la classe `OAuth2PasswordBearer`. + +/// info + +Un token « bearer » n'est pas la seule option. + +Mais c'est la meilleure pour notre cas d'utilisation. + +Et cela pourrait être la meilleure pour la plupart des cas, sauf si vous êtes expert en OAuth2 et savez exactement pourquoi une autre option convient mieux à vos besoins. + +Dans ce cas, **FastAPI** vous fournit aussi les outils pour la construire. + +/// + +Lorsque nous créons une instance de la classe `OAuth2PasswordBearer`, nous passons le paramètre `tokenUrl`. Ce paramètre contient l'URL que le client (le frontend s'exécutant dans le navigateur de l'utilisateur) utilisera pour envoyer le `username` et le `password` afin d'obtenir un token. + +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} + +/// tip | Astuce + +Ici `tokenUrl="token"` fait référence à une URL relative `token` que nous n'avons pas encore créée. Comme c'est une URL relative, elle est équivalente à `./token`. + +Parce que nous utilisons une URL relative, si votre API se trouvait à `https://example.com/`, alors elle ferait référence à `https://example.com/token`. Mais si votre API se trouvait à `https://example.com/api/v1/`, alors elle ferait référence à `https://example.com/api/v1/token`. + +Utiliser une URL relative est important pour vous assurer que votre application continue de fonctionner même dans un cas d'usage avancé comme [Derrière un proxy](../../advanced/behind-a-proxy.md){.internal-link target=_blank}. + +/// + +Ce paramètre ne crée pas cet endpoint / *chemin d'accès*, mais déclare que l'URL `/token` sera celle que le client doit utiliser pour obtenir le token. Cette information est utilisée dans OpenAPI, puis dans les systèmes de documentation API interactifs. + +Nous créerons bientôt aussi le véritable chemin d'accès. + +/// info + +Si vous êtes un « Pythonista » très strict, vous pourriez ne pas apprécier le style du nom de paramètre `tokenUrl` au lieu de `token_url`. + +C'est parce qu'il utilise le même nom que dans la spécification OpenAPI. Ainsi, si vous devez approfondir l'un de ces schémas de sécurité, vous pouvez simplement copier-coller pour trouver plus d'informations à ce sujet. + +/// + +La variable `oauth2_scheme` est une instance de `OAuth2PasswordBearer`, mais c'est aussi un « callable ». + +Elle pourrait être appelée ainsi : + +```Python +oauth2_scheme(some, parameters) +``` + +Ainsi, elle peut être utilisée avec `Depends`. + +### Utiliser { #use-it } + +Vous pouvez maintenant passer ce `oauth2_scheme` en dépendance avec `Depends`. + +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} + +Cette dépendance fournira une `str` qui est affectée au paramètre `token` de la fonction de *chemin d'accès*. + +**FastAPI** saura qu'il peut utiliser cette dépendance pour définir un « schéma de sécurité » dans le schéma OpenAPI (et la documentation API automatique). + +/// info | Détails techniques + +**FastAPI** saura qu'il peut utiliser la classe `OAuth2PasswordBearer` (déclarée dans une dépendance) pour définir le schéma de sécurité dans OpenAPI parce qu'elle hérite de `fastapi.security.oauth2.OAuth2`, qui hérite à son tour de `fastapi.security.base.SecurityBase`. + +Tous les utilitaires de sécurité qui s'intègrent à OpenAPI (et à la documentation API automatique) héritent de `SecurityBase`, c'est ainsi que **FastAPI** sait comment les intégrer dans OpenAPI. + +/// + +## Ce que cela fait { #what-it-does } + +Il va chercher dans la requête cet en-tête `Authorization`, vérifier si la valeur est `Bearer ` plus un token, et renverra le token en tant que `str`. + +S'il ne voit pas d'en-tête `Authorization`, ou si la valeur n'a pas de token `Bearer `, il répondra directement avec une erreur de code d'état 401 (`UNAUTHORIZED`). + +Vous n'avez même pas à vérifier si le token existe pour renvoyer une erreur. Vous pouvez être sûr que si votre fonction est exécutée, elle aura une `str` dans ce token. + +Vous pouvez déjà l'essayer dans la documentation interactive : + + + +Nous ne vérifions pas encore la validité du token, mais c'est déjà un début. + +## Récapitulatif { #recap } + +Ainsi, en seulement 3 ou 4 lignes supplémentaires, vous disposez déjà d'une forme primitive de sécurité. diff --git a/docs/fr/docs/tutorial/security/get-current-user.md b/docs/fr/docs/tutorial/security/get-current-user.md new file mode 100644 index 000000000..5f73efea9 --- /dev/null +++ b/docs/fr/docs/tutorial/security/get-current-user.md @@ -0,0 +1,105 @@ +# Obtenir l'utilisateur actuel { #get-current-user } + +Dans le chapitre précédent, le système de sécurité (basé sur le système d'injection de dépendances) fournissait à la *fonction de chemin d'accès* un `token` en tant que `str` : + +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} + +Mais ce n'est pas encore très utile. + +Faisons en sorte qu'il nous fournisse l'utilisateur actuel. + +## Créer un modèle d'utilisateur { #create-a-user-model } + +Commençons par créer un modèle d'utilisateur Pydantic. + +De la même manière que nous utilisons Pydantic pour déclarer des corps de requête, nous pouvons l'utiliser ailleurs : + +{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *} + +## Créer une dépendance `get_current_user` { #create-a-get-current-user-dependency } + +Créons une dépendance `get_current_user`. + +Rappelez-vous que les dépendances peuvent avoir des sous-dépendances ? + +`get_current_user` aura une dépendance avec le même `oauth2_scheme` que nous avons créé précédemment. + +Comme nous le faisions auparavant directement dans le *chemin d'accès*, notre nouvelle dépendance `get_current_user` recevra un `token` en tant que `str` de la sous-dépendance `oauth2_scheme` : + +{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *} + +## Récupérer l'utilisateur { #get-the-user } + +`get_current_user` utilisera une fonction utilitaire (factice) que nous avons créée, qui prend un token en `str` et retourne notre modèle Pydantic `User` : + +{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *} + +## Injecter l'utilisateur actuel { #inject-the-current-user } + +Nous pouvons donc utiliser le même `Depends` avec notre `get_current_user` dans le *chemin d'accès* : + +{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *} + +Remarquez que nous déclarons le type de `current_user` comme le modèle Pydantic `User`. + +Cela nous aidera dans la fonction avec toute l'autocomplétion et les vérifications de type. + +/// tip | Astuce + +Vous vous souvenez peut-être que les corps de requête sont également déclarés avec des modèles Pydantic. + +Ici, **FastAPI** ne s'y trompera pas car vous utilisez `Depends`. + +/// + +/// check | Vérifications + +La manière dont ce système de dépendances est conçu nous permet d'avoir différentes dépendances (différents « dependables ») qui retournent toutes un modèle `User`. + +Nous ne sommes pas limités à une seule dépendance pouvant retourner ce type de données. + +/// + +## Autres modèles { #other-models } + +Vous pouvez maintenant obtenir l'utilisateur actuel directement dans les *fonctions de chemin d'accès* et gérer les mécanismes de sécurité au niveau de l'**Injection de dépendances**, en utilisant `Depends`. + +Et vous pouvez utiliser n'importe quel modèle ou données pour les exigences de sécurité (dans ce cas, un modèle Pydantic `User`). + +Mais vous n'êtes pas limité à un modèle, une classe ou un type de données spécifique. + +Voulez-vous avoir un `id` et `email` et ne pas avoir de `username` dans votre modèle ? Bien sûr. Vous pouvez utiliser ces mêmes outils. + +Voulez-vous simplement avoir un `str` ? Ou juste un `dict` ? Ou directement une instance d'un modèle de classe de base de données ? Tout fonctionne de la même manière. + +Vous n'avez en fait pas d'utilisateurs qui se connectent à votre application, mais des robots, bots ou d'autres systèmes, qui n'ont qu'un jeton d'accès ? Là encore, tout fonctionne de la même façon. + +Utilisez simplement tout type de modèle, toute sorte de classe, tout type de base de données dont vous avez besoin pour votre application. **FastAPI** vous couvre avec le système d'injection de dépendances. + +## Taille du code { #code-size } + +Cet exemple peut sembler verbeux. Gardez à l'esprit que nous mélangeons sécurité, modèles de données, fonctions utilitaires et *chemins d'accès* dans le même fichier. + +Mais voici le point clé. + +La partie sécurité et injection de dépendances est écrite une seule fois. + +Et vous pouvez la rendre aussi complexe que vous le souhaitez. Et malgré tout, ne l'écrire qu'une seule fois, en un seul endroit. Avec toute la flexibilité. + +Mais vous pouvez avoir des milliers d'endpoints (*chemins d'accès*) utilisant le même système de sécurité. + +Et tous (ou seulement une partie d'entre eux, si vous le souhaitez) peuvent profiter de la réutilisation de ces dépendances ou de toute autre dépendance que vous créez. + +Et tous ces milliers de *chemins d'accès* peuvent tenir en seulement 3 lignes : + +{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *} + +## Récapitulatif { #recap } + +Vous pouvez désormais obtenir l'utilisateur actuel directement dans votre *fonction de chemin d'accès*. + +Nous avons déjà fait la moitié du chemin. + +Il nous suffit d'ajouter un *chemin d'accès* pour que l'utilisateur/client envoie effectivement le `username` et le `password`. + +C'est pour la suite. diff --git a/docs/fr/docs/tutorial/security/index.md b/docs/fr/docs/tutorial/security/index.md new file mode 100644 index 000000000..6de75aed6 --- /dev/null +++ b/docs/fr/docs/tutorial/security/index.md @@ -0,0 +1,106 @@ +# Sécurité { #security } + +Il existe de nombreuses façons de gérer la sécurité, l'authentification et l'autorisation. + +Et c'est normalement un sujet complexe et « difficile ». + +Dans de nombreux frameworks et systèmes, le simple fait de gérer la sécurité et l'authentification demande beaucoup d'efforts et de code (dans de nombreux cas, cela peut représenter 50 % ou plus de tout le code écrit). + +**FastAPI** fournit plusieurs outils pour vous aider à gérer la **Sécurité** facilement, rapidement, de manière standard, sans avoir à étudier et apprendre toutes les spécifications de sécurité. + +Mais d'abord, voyons quelques notions. + +## Pressé ? { #in-a-hurry } + +Si ces termes ne vous intéressent pas et que vous avez simplement besoin d'ajouter une sécurité avec une authentification basée sur un nom d'utilisateur et un mot de passe immédiatement, passez aux chapitres suivants. + +## OAuth2 { #oauth2 } + +OAuth2 est une spécification qui définit plusieurs façons de gérer l'authentification et l'autorisation. + +C'est une spécification assez vaste qui couvre plusieurs cas d'utilisation complexes. + +Elle inclut des moyens de s'authentifier en utilisant un « tiers ». + +C'est ce que tous les systèmes avec « connexion avec Facebook, Google, X (Twitter), GitHub » utilisent en arrière-plan. + +### OAuth 1 { #oauth-1 } + +Il y a eu un OAuth 1, très différent d'OAuth2, et plus complexe, car il incluait des spécifications directes sur la manière de chiffrer la communication. + +Il n'est plus très populaire ni utilisé de nos jours. + +OAuth2 ne spécifie pas comment chiffrer la communication ; il suppose que votre application est servie en HTTPS. + +/// tip | Astuce + +Dans la section sur le déploiement, vous verrez comment configurer HTTPS gratuitement, en utilisant Traefik et Let's Encrypt. + +/// + +## OpenID Connect { #openid-connect } + +OpenID Connect est une autre spécification, basée sur **OAuth2**. + +Elle étend simplement OAuth2 en précisant certains points relativement ambigus dans OAuth2, afin d'essayer de la rendre plus interopérable. + +Par exemple, la connexion Google utilise OpenID Connect (qui, en arrière-plan, utilise OAuth2). + +Mais la connexion Facebook ne prend pas en charge OpenID Connect. Elle a sa propre variante d'OAuth2. + +### OpenID (pas « OpenID Connect ») { #openid-not-openid-connect } + +Il y avait aussi une spécification « OpenID ». Elle essayait de résoudre la même chose qu'**OpenID Connect**, mais n'était pas basée sur OAuth2. + +C'était donc un système totalement distinct. + +Il n'est plus très populaire ni utilisé de nos jours. + +## OpenAPI { #openapi } + +OpenAPI (précédemment connu sous le nom de Swagger) est la spécification ouverte pour construire des API (désormais partie de la Linux Foundation). + +**FastAPI** est basé sur **OpenAPI**. + +C'est ce qui rend possibles plusieurs interfaces de documentation interactive automatiques, la génération de code, etc. + +OpenAPI propose une manière de définir plusieurs « schémas » de sécurité. + +En les utilisant, vous pouvez tirer parti de tous ces outils basés sur des standards, y compris ces systèmes de documentation interactive. + +OpenAPI définit les schémas de sécurité suivants : + +* `apiKey` : une clé spécifique à l'application qui peut provenir : + * D'un paramètre de requête. + * D'un en-tête. + * D'un cookie. +* `http` : des systèmes d'authentification HTTP standards, notamment : + * `bearer` : un en-tête `Authorization` avec une valeur `Bearer ` plus un jeton. Hérité d'OAuth2. + * Authentification HTTP Basic. + * HTTP Digest, etc. +* `oauth2` : toutes les méthodes OAuth2 pour gérer la sécurité (appelées « flows »). + * Plusieurs de ces flows conviennent pour construire un fournisseur d'authentification OAuth 2.0 (comme Google, Facebook, X (Twitter), GitHub, etc.) : + * `implicit` + * `clientCredentials` + * `authorizationCode` + * Mais il existe un « flow » spécifique qui peut parfaitement être utilisé pour gérer l'authentification directement dans la même application : + * `password` : certains des prochains chapitres couvriront des exemples à ce sujet. +* `openIdConnect` : propose un moyen de définir comment découvrir automatiquement les données d'authentification OAuth2. + * Cette découverte automatique est ce qui est défini dans la spécification OpenID Connect. + + +/// tip | Astuce + +Intégrer d'autres fournisseurs d'authentification/autorisation comme Google, Facebook, X (Twitter), GitHub, etc. est également possible et relativement facile. + +Le problème le plus complexe est de construire un fournisseur d'authentification/autorisation comme ceux-là, mais **FastAPI** vous donne les outils pour le faire facilement, tout en effectuant le gros du travail pour vous. + +/// + +## Outils **FastAPI** { #fastapi-utilities } + +FastAPI propose plusieurs outils pour chacun de ces schémas de sécurité dans le module fastapi.security qui simplifient l'utilisation de ces mécanismes de sécurité. + +Dans les prochains chapitres, vous verrez comment ajouter de la sécurité à votre API en utilisant ces outils fournis par **FastAPI**. + +Et vous verrez aussi comment cela s'intègre automatiquement au système de documentation interactive. diff --git a/docs/fr/docs/tutorial/security/oauth2-jwt.md b/docs/fr/docs/tutorial/security/oauth2-jwt.md new file mode 100644 index 000000000..d35530fc9 --- /dev/null +++ b/docs/fr/docs/tutorial/security/oauth2-jwt.md @@ -0,0 +1,277 @@ +# OAuth2 avec mot de passe (et hachage), Bearer avec des jetons JWT { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens } + +Maintenant que nous avons tout le flux de sécurité, rendons réellement l'application sécurisée, en utilisant des jetons JWT et un hachage de mot de passe sécurisé. + +Ce code est utilisable dans votre application, enregistrez les hachages de mots de passe dans votre base de données, etc. + +Nous allons repartir d'où nous nous sommes arrêtés dans le chapitre précédent et l'enrichir. + +## À propos de JWT { #about-jwt } + +JWT signifie « JSON Web Tokens ». + +C'est une norme pour coder un objet JSON dans une longue chaîne compacte sans espaces. Cela ressemble à ceci : + +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +``` + +Il n'est pas chiffré ; ainsi, n'importe qui peut récupérer les informations à partir de son contenu. + +Mais il est signé. Ainsi, quand vous recevez un jeton que vous avez émis, vous pouvez vérifier que vous l'avez bien émis. + +De cette façon, vous pouvez créer un jeton avec une expiration d'une semaine, par exemple. Et quand l'utilisateur revient le lendemain avec ce jeton, vous savez qu'il est toujours connecté à votre système. + +Après une semaine, le jeton aura expiré et l'utilisateur ne sera pas autorisé et devra se reconnecter pour obtenir un nouveau jeton. Et si l'utilisateur (ou un tiers) essayait de modifier le jeton pour changer l'expiration, vous pourriez le détecter, car les signatures ne correspondraient pas. + +Si vous voulez expérimenter avec des jetons JWT et voir comment ils fonctionnent, consultez https://jwt.io. + +## Installer `PyJWT` { #install-pyjwt } + +Nous devons installer `PyJWT` pour générer et vérifier les jetons JWT en Python. + +Assurez-vous de créer un [environnement virtuel](../../virtual-environments.md){.internal-link target=_blank}, de l'activer, puis d'installer `pyjwt` : + +
+ +```console +$ pip install pyjwt + +---> 100% +``` + +
+ +/// info + +Si vous prévoyez d'utiliser des algorithmes de signature numérique comme RSA ou ECDSA, vous devez installer la dépendance de bibliothèque de cryptographie `pyjwt[crypto]`. + +Vous pouvez en lire davantage dans la documentation d'installation de PyJWT. + +/// + +## Hachage de mot de passe { #password-hashing } + +« Hachage » signifie convertir un contenu (un mot de passe dans ce cas) en une séquence d'octets (juste une chaîne) qui ressemble à du charabia. + +Chaque fois que vous fournissez exactement le même contenu (exactement le même mot de passe), vous obtenez exactement le même charabia. + +Mais vous ne pouvez pas convertir le charabia en sens inverse vers le mot de passe. + +### Pourquoi utiliser le hachage de mot de passe { #why-use-password-hashing } + +Si votre base de données est volée, le voleur n'aura pas les mots de passe en clair de vos utilisateurs, seulement les hachages. + +Ainsi, le voleur ne pourra pas essayer d'utiliser ce mot de passe dans un autre système (comme beaucoup d'utilisateurs utilisent le même mot de passe partout, ce serait dangereux). + +## Installer `pwdlib` { #install-pwdlib } + +pwdlib est un excellent package Python pour gérer les hachages de mots de passe. + +Il prend en charge de nombreux algorithmes de hachage sécurisés et des utilitaires pour travailler avec eux. + +L'algorithme recommandé est « Argon2 ». + +Assurez-vous de créer un [environnement virtuel](../../virtual-environments.md){.internal-link target=_blank}, de l'activer, puis d'installer pwdlib avec Argon2 : + +
+ +```console +$ pip install "pwdlib[argon2]" + +---> 100% +``` + +
+ +/// tip | Astuce + +Avec `pwdlib`, vous pouvez même le configurer pour pouvoir lire des mots de passe créés par **Django**, un plug-in de sécurité **Flask** ou bien d'autres. + +Ainsi, vous seriez par exemple en mesure de partager les mêmes données d'une application Django dans une base de données avec une application FastAPI. Ou de migrer progressivement une application Django en utilisant la même base de données. + +Et vos utilisateurs pourraient se connecter depuis votre application Django ou depuis votre application **FastAPI**, en même temps. + +/// + +## Hacher et vérifier les mots de passe { #hash-and-verify-the-passwords } + +Importez les outils nécessaires depuis `pwdlib`. + +Créez une instance PasswordHash avec les réglages recommandés ; elle sera utilisée pour hacher et vérifier les mots de passe. + +/// tip | Astuce + +pwdlib prend également en charge l'algorithme de hachage bcrypt, mais n'inclut pas les algorithmes hérités. Pour travailler avec des hachages obsolètes, il est recommandé d'utiliser la bibliothèque passlib. + +Par exemple, vous pourriez l'utiliser pour lire et vérifier des mots de passe générés par un autre système (comme Django), mais hacher tous les nouveaux mots de passe avec un autre algorithme comme Argon2 ou Bcrypt. + +Et rester compatible avec tous en même temps. + +/// + +Créez une fonction utilitaire pour hacher un mot de passe fourni par l'utilisateur. + +Et une autre pour vérifier si un mot de passe reçu correspond au hachage stocké. + +Et une autre pour authentifier et renvoyer un utilisateur. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +Lorsque `authenticate_user` est appelée avec un nom d'utilisateur qui n'existe pas dans la base de données, nous exécutons tout de même `verify_password` contre un hachage factice. + +Cela garantit que le point de terminaison met approximativement le même temps à répondre que le nom d'utilisateur soit valide ou non, empêchant des **attaques temporelles** qui pourraient être utilisées pour énumérer les noms d'utilisateur existants. + +/// note | Remarque + +Si vous consultez la nouvelle (fausse) base de données `fake_users_db`, vous verrez à quoi ressemble maintenant le mot de passe haché : `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`. + +/// + +## Gérer les jetons JWT { #handle-jwt-tokens } + +Importez les modules installés. + +Créez une clé secrète aléatoire qui sera utilisée pour signer les jetons JWT. + +Pour générer une clé secrète aléatoire sécurisée, utilisez la commande : + +
+ +```console +$ openssl rand -hex 32 + +09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 +``` + +
+ +Et copiez la sortie dans la variable `SECRET_KEY` (n'utilisez pas celle de l'exemple). + +Créez une variable `ALGORITHM` avec l'algorithme utilisé pour signer le jeton JWT, et définissez-la à `"HS256"`. + +Créez une variable pour l'expiration du jeton. + +Définissez un modèle Pydantic qui sera utilisé dans le point de terminaison du jeton pour la réponse. + +Créez une fonction utilitaire pour générer un nouveau jeton d'accès. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} + +## Mettre à jour les dépendances { #update-the-dependencies } + +Mettez à jour `get_current_user` pour recevoir le même jeton qu'auparavant, mais cette fois en utilisant des jetons JWT. + +Décodez le jeton reçu, vérifiez-le, et renvoyez l'utilisateur courant. + +Si le jeton est invalide, renvoyez immédiatement une erreur HTTP. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} + +## Mettre à jour le *chemin d'accès* `/token` { #update-the-token-path-operation } + +Créez un `timedelta` avec la durée d'expiration du jeton. + +Créez un véritable jeton d'accès JWT et renvoyez-le. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} + +### Détails techniques au sujet du « subject » JWT `sub` { #technical-details-about-the-jwt-subject-sub } + +La spécification JWT indique qu'il existe une clé `sub`, contenant le sujet du jeton. + +Son utilisation est facultative, mais c'est là que vous placeriez l'identifiant de l'utilisateur ; nous l'utilisons donc ici. + +Les JWT peuvent être utilisés pour d'autres choses que l'identification d'un utilisateur et l'autorisation d'effectuer des opérations directement sur votre API. + +Par exemple, vous pourriez identifier une « voiture » ou un « article de blog ». + +Vous pourriez ensuite ajouter des permissions sur cette entité, comme « conduire » (pour la voiture) ou « modifier » (pour le blog). + +Vous pourriez alors donner ce jeton JWT à un utilisateur (ou un bot), et il pourrait l'utiliser pour effectuer ces actions (conduire la voiture, ou modifier l'article de blog) sans même avoir besoin d'avoir un compte, uniquement avec le jeton JWT que votre API a généré pour cela. + +En utilisant ces idées, les JWT peuvent servir à des scénarios bien plus sophistiqués. + +Dans ces cas, plusieurs de ces entités peuvent avoir le même identifiant, disons `foo` (un utilisateur `foo`, une voiture `foo`, et un article de blog `foo`). + +Donc, pour éviter les collisions d'identifiants, lors de la création du jeton JWT pour l'utilisateur, vous pouvez préfixer la valeur de la clé `sub`, par exemple avec `username:`. Ainsi, dans cet exemple, la valeur de `sub` aurait pu être : `username:johndoe`. + +L'important à garder à l'esprit est que la clé `sub` doit contenir un identifiant unique dans toute l'application, et ce doit être une chaîne de caractères. + +## Vérifier { #check-it } + +Lancez le serveur et allez à la documentation : http://127.0.0.1:8000/docs. + +Vous verrez l'interface utilisateur suivante : + + + +Autorisez l'application de la même manière qu'auparavant. + +En utilisant les identifiants : + +Nom d'utilisateur : `johndoe` +Mot de passe : `secret` + +/// check | Vérifications + +Remarquez qu'à aucun endroit du code le mot de passe en clair « secret » n'apparaît, nous n'avons que la version hachée. + +/// + + + +Appelez le point de terminaison `/users/me/`, vous obtiendrez la réponse suivante : + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + + + +Si vous ouvrez les outils de développement, vous pouvez voir que les données envoyées n'incluent que le jeton ; le mot de passe n'est envoyé que dans la première requête pour authentifier l'utilisateur et obtenir ce jeton d'accès, mais plus ensuite : + + + +/// note | Remarque + +Remarquez l'en-tête `Authorization`, avec une valeur qui commence par `Bearer `. + +/// + +## Utilisation avancée avec `scopes` { #advanced-usage-with-scopes } + +OAuth2 comporte la notion de « scopes ». + +Vous pouvez les utiliser pour ajouter un ensemble spécifique d'autorisations à un jeton JWT. + +Vous pouvez ensuite donner ce jeton directement à un utilisateur ou à un tiers, pour interagir avec votre API avec un ensemble de restrictions. + +Vous pouvez apprendre à les utiliser et comment ils sont intégrés à **FastAPI** plus tard dans le **Guide de l'utilisateur avancé**. + +## Récapitulatif { #recap } + +Avec ce que vous avez vu jusqu'à présent, vous pouvez configurer une application **FastAPI** sécurisée en utilisant des standards comme OAuth2 et JWT. + +Dans presque n'importe quel framework, la gestion de la sécurité devient assez rapidement un sujet plutôt complexe. + +De nombreux packages qui la simplifient beaucoup doivent faire de nombreux compromis avec le modèle de données, la base de données et les fonctionnalités disponibles. Et certains de ces packages qui simplifient trop les choses comportent en fait des failles de sécurité sous-jacentes. + +--- + +**FastAPI** ne fait aucun compromis avec une base de données, un modèle de données ni un outil. + +Il vous donne toute la flexibilité pour choisir ceux qui conviennent le mieux à votre projet. + +Et vous pouvez utiliser directement de nombreux packages bien maintenus et largement utilisés comme `pwdlib` et `PyJWT`, car **FastAPI** n'exige aucun mécanisme complexe pour intégrer des packages externes. + +Mais il vous fournit les outils pour simplifier le processus autant que possible sans compromettre la flexibilité, la robustesse ou la sécurité. + +Et vous pouvez utiliser et implémenter des protocoles sécurisés et standard, comme OAuth2, de manière relativement simple. + +Vous pouvez en apprendre davantage dans le **Guide de l'utilisateur avancé** sur la façon d'utiliser les « scopes » OAuth2, pour un système d'autorisations plus fin, en suivant ces mêmes standards. OAuth2 avec scopes est le mécanisme utilisé par de nombreux grands fournisseurs d'authentification, comme Facebook, Google, GitHub, Microsoft, X (Twitter), etc., pour autoriser des applications tierces à interagir avec leurs API au nom de leurs utilisateurs. diff --git a/docs/fr/docs/tutorial/security/simple-oauth2.md b/docs/fr/docs/tutorial/security/simple-oauth2.md new file mode 100644 index 000000000..662444753 --- /dev/null +++ b/docs/fr/docs/tutorial/security/simple-oauth2.md @@ -0,0 +1,289 @@ +# OAuth2 simple avec Password et Bearer { #simple-oauth2-with-password-and-bearer } + +Construisons maintenant à partir du chapitre précédent et ajoutons les éléments manquants pour avoir un flux de sécurité complet. + +## Obtenir `username` et `password` { #get-the-username-and-password } + +Nous allons utiliser les utilitaires de sécurité de **FastAPI** pour obtenir `username` et `password`. + +OAuth2 spécifie que lorsqu'on utilise le « password flow » (ce que nous utilisons), le client/utilisateur doit envoyer des champs `username` et `password` en tant que données de formulaire. + +Et la spécification indique que les champs doivent porter exactement ces noms. Ainsi, `user-name` ou `email` ne fonctionneraient pas. + +Mais ne vous inquiétez pas, vous pouvez l'afficher comme vous le souhaitez à vos utilisateurs finaux dans le frontend. + +Et vos modèles de base de données peuvent utiliser les noms que vous voulez. + +Mais pour le chemin d'accès de connexion, nous devons utiliser ces noms pour être compatibles avec la spécification (et pouvoir, par exemple, utiliser le système de documentation API intégré). + +La spécification précise également que `username` et `password` doivent être envoyés en données de formulaire (donc pas de JSON ici). + +### `scope` { #scope } + +La spécification indique aussi que le client peut envoyer un autre champ de formulaire « scope ». + +Le nom du champ de formulaire est `scope` (au singulier), mais il s'agit en fait d'une longue chaîne contenant des « scopes » séparés par des espaces. + +Chaque « scope » n'est qu'une chaîne (sans espaces). + +Ils sont normalement utilisés pour déclarer des permissions de sécurité spécifiques, par exemple : + +* `users:read` ou `users:write` sont des exemples courants. +* `instagram_basic` est utilisé par Facebook / Instagram. +* `https://www.googleapis.com/auth/drive` est utilisé par Google. + +/// info + +En OAuth2, un « scope » est simplement une chaîne qui déclare une permission spécifique requise. + +Peu importe s'il contient d'autres caractères comme `:` ou si c'est une URL. + +Ces détails dépendent de l'implémentation. + +Pour OAuth2, ce ne sont que des chaînes. + +/// + +## Écrire le code pour obtenir `username` et `password` { #code-to-get-the-username-and-password } + +Utilisons maintenant les utilitaires fournis par **FastAPI** pour gérer cela. + +### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform } + +Tout d'abord, importez `OAuth2PasswordRequestForm`, et utilisez-la en tant que dépendance avec `Depends` dans le chemin d'accès pour `/token` : + +{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *} + +`OAuth2PasswordRequestForm` est une dépendance de classe qui déclare un corps de formulaire avec : + +* Le `username`. +* Le `password`. +* Un champ optionnel `scope` sous forme d'une grande chaîne, composée de chaînes séparées par des espaces. +* Un `grant_type` optionnel. + +/// tip | Astuce + +La spécification OAuth2 exige en réalité un champ `grant_type` avec la valeur fixe `password`, mais `OAuth2PasswordRequestForm` ne l'impose pas. + +Si vous avez besoin de l'imposer, utilisez `OAuth2PasswordRequestFormStrict` au lieu de `OAuth2PasswordRequestForm`. + +/// + +* Un `client_id` optionnel (nous n'en avons pas besoin pour notre exemple). +* Un `client_secret` optionnel (nous n'en avons pas besoin pour notre exemple). + +/// info + +La classe `OAuth2PasswordRequestForm` n'est pas une classe spéciale pour **FastAPI** comme l'est `OAuth2PasswordBearer`. + +`OAuth2PasswordBearer` indique à **FastAPI** qu'il s'agit d'un schéma de sécurité. Il est donc ajouté de cette façon à OpenAPI. + +Mais `OAuth2PasswordRequestForm` est simplement une dépendance de classe que vous auriez pu écrire vous‑même, ou vous auriez pu déclarer des paramètres `Form` directement. + +Mais comme c'est un cas d'usage courant, elle est fournie directement par **FastAPI**, simplement pour vous faciliter la vie. + +/// + +### Utiliser les données du formulaire { #use-the-form-data } + +/// tip | Astuce + +L'instance de la classe de dépendance `OAuth2PasswordRequestForm` n'aura pas d'attribut `scope` contenant la longue chaîne séparée par des espaces ; elle aura plutôt un attribut `scopes` avec la liste réelle des chaînes pour chaque scope envoyé. + +Nous n'utilisons pas `scopes` dans cet exemple, mais la fonctionnalité est disponible si vous en avez besoin. + +/// + +Récupérez maintenant les données utilisateur depuis la (fausse) base de données, en utilisant le `username` du champ de formulaire. + +S'il n'existe pas d'utilisateur, nous renvoyons une erreur indiquant « Incorrect username or password ». + +Pour l'erreur, nous utilisons l'exception `HTTPException` : + +{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *} + +### Vérifier le mot de passe { #check-the-password } + +À ce stade, nous avons les données utilisateur depuis notre base, mais nous n'avons pas encore vérifié le mot de passe. + +Mettons d'abord ces données dans le modèle Pydantic `UserInDB`. + +Vous ne devez jamais enregistrer des mots de passe en clair ; nous allons donc utiliser le système (factice) de hachage de mot de passe. + +Si les mots de passe ne correspondent pas, nous renvoyons la même erreur. + +#### Hachage de mot de passe { #password-hashing } + +Le « hachage » signifie : convertir un contenu (un mot de passe, dans ce cas) en une séquence d'octets (juste une chaîne) qui ressemble à du charabia. + +Chaque fois que vous fournissez exactement le même contenu (exactement le même mot de passe), vous obtenez exactement le même charabia. + +Mais vous ne pouvez pas convertir ce charabia pour retrouver le mot de passe. + +##### Pourquoi utiliser le hachage de mot de passe { #why-use-password-hashing } + +Si votre base de données est volée, le voleur n'aura pas les mots de passe en clair de vos utilisateurs, seulement les hachages. + +Ainsi, il ne pourra pas essayer d'utiliser ces mêmes mots de passe dans un autre système (comme beaucoup d'utilisateurs utilisent le même mot de passe partout, ce serait dangereux). + +{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *} + +#### À propos de `**user_dict` { #about-user-dict } + +`UserInDB(**user_dict)` signifie : + +Passez les clés et valeurs de `user_dict` directement comme arguments clé‑valeur, équivalent à : + +```Python +UserInDB( + username = user_dict["username"], + email = user_dict["email"], + full_name = user_dict["full_name"], + disabled = user_dict["disabled"], + hashed_password = user_dict["hashed_password"], +) +``` + +/// info + +Pour une explication plus complète de `**user_dict`, consultez [la documentation pour **Modèles supplémentaires**](../extra-models.md#about-user-in-dict){.internal-link target=_blank}. + +/// + +## Renvoyer le jeton { #return-the-token } + +La réponse de l'endpoint `token` doit être un objet JSON. + +Il doit contenir un `token_type`. Dans notre cas, comme nous utilisons des jetons « Bearer », le type de jeton doit être « bearer ». + +Et il doit contenir un `access_token`, avec une chaîne contenant notre jeton d'accès. + +Pour cet exemple simple, nous allons faire quelque chose de complètement non sécurisé et renvoyer le même `username` comme jeton. + +/// tip | Astuce + +Dans le prochain chapitre, vous verrez une véritable implémentation sécurisée, avec du hachage de mot de passe et des jetons JWT. + +Mais pour l'instant, concentrons‑nous sur les détails spécifiques dont nous avons besoin. + +/// + +{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *} + +/// tip | Astuce + +D'après la spécification, vous devez renvoyer un JSON avec un `access_token` et un `token_type`, comme dans cet exemple. + +C'est quelque chose que vous devez faire vous‑même dans votre code, et vous devez vous assurer d'utiliser ces clés JSON. + +C'est presque la seule chose que vous devez vous rappeler de faire correctement vous‑même pour être conforme aux spécifications. + +Pour le reste, **FastAPI** s'en charge pour vous. + +/// + +## Mettre à jour les dépendances { #update-the-dependencies } + +Nous allons maintenant mettre à jour nos dépendances. + +Nous voulons obtenir `current_user` uniquement si cet utilisateur est actif. + +Nous créons donc une dépendance supplémentaire `get_current_active_user` qui utilise à son tour `get_current_user` comme dépendance. + +Ces deux dépendances renverront simplement une erreur HTTP si l'utilisateur n'existe pas, ou s'il est inactif. + +Ainsi, dans notre endpoint, nous n'obtiendrons un utilisateur que si l'utilisateur existe, a été correctement authentifié et est actif : + +{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *} + +/// info + +L'en‑tête supplémentaire `WWW-Authenticate` avec la valeur `Bearer` que nous renvoyons ici fait également partie de la spécification. + +Il est prévu qu'un code d'état HTTP (d'erreur) 401 « UNAUTHORIZED » renvoie également un en‑tête `WWW-Authenticate`. + +Dans le cas des jetons bearer (notre cas), la valeur de cet en‑tête doit être `Bearer`. + +Vous pouvez en réalité omettre cet en‑tête supplémentaire et cela fonctionnerait quand même. + +Mais il est fourni ici pour être conforme aux spécifications. + +De plus, il peut exister des outils qui l'attendent et l'utilisent (maintenant ou à l'avenir) et cela pourrait vous être utile, à vous ou à vos utilisateurs, maintenant ou à l'avenir. + +C'est l'avantage des standards ... + +/// + +## Voir en action { #see-it-in-action } + +Ouvrez la documentation interactive : http://127.0.0.1:8000/docs. + +### S'authentifier { #authenticate } + +Cliquez sur le bouton « Authorize ». + +Utilisez les identifiants : + +Utilisateur : `johndoe` + +Mot de passe : `secret` + + + +Après vous être authentifié dans le système, vous verrez ceci : + + + +### Obtenir vos propres données utilisateur { #get-your-own-user-data } + +Utilisez maintenant l'opération `GET` avec le chemin `/users/me`. + +Vous obtiendrez les données de votre utilisateur, par exemple : + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" +} +``` + + + +Si vous cliquez sur l'icône de cadenas et vous vous déconnectez, puis réessayez la même opération, vous obtiendrez une erreur HTTP 401 : + +```JSON +{ + "detail": "Not authenticated" +} +``` + +### Utilisateur inactif { #inactive-user } + +Essayez maintenant avec un utilisateur inactif, authentifiez‑vous avec : + +Utilisateur : `alice` + +Mot de passe : `secret2` + +Et essayez d'utiliser l'opération `GET` avec le chemin `/users/me`. + +Vous obtiendrez une erreur « Inactive user », par exemple : + +```JSON +{ + "detail": "Inactive user" +} +``` + +## Récapitulatif { #recap } + +Vous avez maintenant les outils pour implémenter un système de sécurité complet basé sur `username` et `password` pour votre API. + +En utilisant ces outils, vous pouvez rendre le système de sécurité compatible avec n'importe quelle base de données et avec n'importe quel modèle d'utilisateur ou de données. + +Le seul détail manquant est qu'il n'est pas encore réellement « sécurisé ». + +Dans le prochain chapitre, vous verrez comment utiliser une bibliothèque de hachage de mot de passe sécurisée et des jetons JWT. diff --git a/docs/fr/docs/tutorial/sql-databases.md b/docs/fr/docs/tutorial/sql-databases.md new file mode 100644 index 000000000..75f9ae14f --- /dev/null +++ b/docs/fr/docs/tutorial/sql-databases.md @@ -0,0 +1,357 @@ +# Bases de données SQL (relationnelles) { #sql-relational-databases } + +**FastAPI** ne vous oblige pas à utiliser une base de données SQL (relationnelle). Mais vous pouvez utiliser **n'importe quelle base de données** que vous voulez. + +Ici, nous allons voir un exemple utilisant SQLModel. + +**SQLModel** est construit au-dessus de SQLAlchemy et de Pydantic. Il a été créé par le même auteur que **FastAPI** pour être l'accord parfait pour les applications FastAPI qui ont besoin d'utiliser des **bases de données SQL**. + +/// tip | Astuce + +Vous pouvez utiliser toute autre bibliothèque SQL ou NoSQL que vous voulez (dans certains cas appelées « ORMs »), FastAPI ne vous impose rien. 😎 + +/// + +Comme SQLModel est basé sur SQLAlchemy, vous pouvez facilement utiliser **toute base prise en charge** par SQLAlchemy (ce qui les rend également prises en charge par SQLModel), comme : + +* PostgreSQL +* MySQL +* SQLite +* Oracle +* Microsoft SQL Server, etc. + +Dans cet exemple, nous utiliserons **SQLite**, car il utilise un seul fichier et Python a un support intégré. Ainsi, vous pouvez copier cet exemple et l'exécuter tel quel. + +Plus tard, pour votre application de production, vous voudrez peut-être utiliser un serveur de base de données comme **PostgreSQL**. + +/// tip | Astuce + +Il existe un générateur de projet officiel avec **FastAPI** et **PostgreSQL**, incluant un frontend et plus d'outils : https://github.com/fastapi/full-stack-fastapi-template + +/// + +Il s'agit d'un tutoriel très simple et court ; si vous souhaitez apprendre sur les bases de données en général, sur SQL, ou des fonctionnalités plus avancées, allez voir la documentation SQLModel. + +## Installer `SQLModel` { #install-sqlmodel } + +D'abord, assurez-vous de créer votre [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, de l'activer, puis d'installer `sqlmodel` : + +
+ +```console +$ pip install sqlmodel +---> 100% +``` + +
+ +## Créer l'application avec un modèle unique { #create-the-app-with-a-single-model } + +Nous allons d'abord créer la première version la plus simple de l'application avec un seul modèle **SQLModel**. + +Ensuite, nous l'améliorerons en augmentant la sécurité et la polyvalence avec **plusieurs modèles** ci-dessous. 🤓 + +### Créer les modèles { #create-models } + +Importez `SQLModel` et créez un modèle de base de données : + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} + +La classe `Hero` est très similaire à un modèle Pydantic (en fait, en dessous, c'est réellement un modèle Pydantic). + +Il y a quelques différences : + +* `table=True` indique à SQLModel qu'il s'agit d'un *modèle de table*, il doit représenter une **table** dans la base SQL, ce n'est pas seulement un *modèle de données* (comme le serait n'importe quelle autre classe Pydantic classique). + +* `Field(primary_key=True)` indique à SQLModel que `id` est la **clé primaire** dans la base SQL (vous pouvez en savoir plus sur les clés primaires SQL dans la documentation SQLModel). + + Remarque : nous utilisons `int | None` pour le champ clé primaire afin qu'en Python nous puissions *créer un objet sans `id`* (`id=None`), en supposant que la base *le génère à l'enregistrement*. SQLModel comprend que la base fournira l'`id` et *définit la colonne comme un `INTEGER` non nul* dans le schéma de base. Voir la documentation SQLModel sur les clés primaires pour plus de détails. + +* `Field(index=True)` indique à SQLModel qu'il doit créer un **index SQL** pour cette colonne, ce qui permettra des recherches plus rapides dans la base lors de la lecture de données filtrées par cette colonne. + + SQLModel saura que quelque chose déclaré comme `str` sera une colonne SQL de type `TEXT` (ou `VARCHAR`, selon la base). + +### Créer un engine { #create-an-engine } + +Un `engine` SQLModel (en dessous c'est en fait un `engine` SQLAlchemy) est ce qui **détient les connexions** à la base de données. + +Vous devez avoir **un seul objet `engine`** pour tout votre code afin de se connecter à la même base. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} + +L'utilisation de `check_same_thread=False` permet à FastAPI d'utiliser la même base SQLite dans différents threads. C'est nécessaire car **une seule requête** peut utiliser **plus d'un thread** (par exemple dans des dépendances). + +Ne vous inquiétez pas, avec la structure du code, nous nous assurerons d'utiliser **une seule *session* SQLModel par requête** plus loin, c'est en fait ce que `check_same_thread` essaie d'assurer. + +### Créer les tables { #create-the-tables } + +Nous ajoutons ensuite une fonction qui utilise `SQLModel.metadata.create_all(engine)` pour **créer les tables** pour tous les *modèles de table*. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} + +### Créer une dépendance de session { #create-a-session-dependency } + +Une **`Session`** est ce qui stocke les **objets en mémoire** et suit les modifications nécessaires des données, puis **utilise l'`engine`** pour communiquer avec la base. + +Nous allons créer une **dépendance** FastAPI avec `yield` qui fournira une nouvelle `Session` pour chaque requête. C'est ce qui garantit que nous utilisons une seule session par requête. 🤓 + +Puis nous créons une dépendance `Annotated` `SessionDep` pour simplifier le reste du code qui utilisera cette dépendance. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} + +### Créer les tables de base au démarrage { #create-database-tables-on-startup } + +Nous allons créer les tables de base de données au démarrage de l'application. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} + +Ici, nous créons les tables lors d'un événement de démarrage de l'application. + +En production, vous utiliseriez probablement un script de migration qui s'exécute avant de démarrer votre application. 🤓 + +/// tip | Astuce + +SQLModel aura des utilitaires de migration enveloppant Alembic, mais pour l'instant, vous pouvez utiliser Alembic directement. + +/// + +### Créer un héros { #create-a-hero } + +Comme chaque modèle SQLModel est aussi un modèle Pydantic, vous pouvez l'utiliser dans les mêmes **annotations de type** que vous utiliseriez pour des modèles Pydantic. + +Par exemple, si vous déclarez un paramètre de type `Hero`, il sera lu depuis le **corps JSON**. + +De la même manière, vous pouvez le déclarer comme **type de retour** de la fonction, et alors la forme des données apparaîtra dans l'UI automatique de documentation de l'API. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} + +Ici, nous utilisons la dépendance `SessionDep` (une `Session`) pour ajouter le nouveau `Hero` à l'instance de `Session`, valider les changements dans la base, rafraîchir les données dans `hero`, puis le retourner. + +### Lire les héros { #read-heroes } + +Nous pouvons **lire** des `Hero` depuis la base en utilisant un `select()`. Nous pouvons inclure une `limit` et un `offset` pour paginer les résultats. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} + +### Lire un héros { #read-one-hero } + +Nous pouvons **lire** un seul `Hero`. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} + +### Supprimer un héros { #delete-a-hero } + +Nous pouvons aussi **supprimer** un `Hero`. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} + +### Exécuter l'application { #run-the-app } + +Vous pouvez exécuter l'application : + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Ensuite, allez sur l'UI `/docs`, vous verrez que **FastAPI** utilise ces **modèles** pour **documenter** l'API, et les utilisera aussi pour **sérialiser** et **valider** les données. + +
+ +
+ +## Mettre à jour l'application avec plusieurs modèles { #update-the-app-with-multiple-models } + +Maintenant, **refactorisons** un peu cette application pour augmenter la **sécurité** et la **polyvalence**. + +Si vous vérifiez l'application précédente, dans l'UI vous pouvez voir que, jusqu'à présent, elle laisse le client décider de l'`id` du `Hero` à créer. 😱 + +Nous ne devrions pas laisser cela se produire, ils pourraient écraser un `id` que nous avons déjà attribué dans la base. Décider de l'`id` doit être fait par le **backend** ou la **base**, **pas par le client**. + +De plus, nous créons un `secret_name` pour le héros, mais jusqu'ici, nous le renvoyons partout, ce n'est pas très « secret » ... 😅 + +Nous allons corriger ces choses en ajoutant quelques **modèles supplémentaires**. C'est là que SQLModel brille. ✨ + +### Créer plusieurs modèles { #create-multiple-models } + +Dans **SQLModel**, toute classe de modèle qui a `table=True` est un **modèle de table**. + +Et toute classe de modèle qui n'a pas `table=True` est un **modèle de données**, ceux-ci sont en réalité juste des modèles Pydantic (avec deux petites fonctionnalités en plus). 🤓 + +Avec SQLModel, nous pouvons utiliser **l'héritage** pour **éviter de dupliquer** tous les champs dans tous les cas. + +#### `HeroBase` - la classe de base { #herobase-the-base-class } + +Commençons avec un modèle `HeroBase` qui a tous les **champs partagés** par tous les modèles : + +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} + +#### `Hero` - le *modèle de table* { #hero-the-table-model } + +Créons ensuite `Hero`, le *modèle de table* proprement dit, avec les **champs supplémentaires** qui ne sont pas toujours dans les autres modèles : + +* `id` +* `secret_name` + +Comme `Hero` hérite de `HeroBase`, il **a aussi** les **champs** déclarés dans `HeroBase`, donc tous les champs de `Hero` sont : + +* `id` +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} + +#### `HeroPublic` - le *modèle de données* public { #heropublic-the-public-data-model } + +Ensuite, nous créons un modèle `HeroPublic`, c'est celui qui sera **retourné** aux clients de l'API. + +Il a les mêmes champs que `HeroBase`, il n'inclura donc pas `secret_name`. + +Enfin, l'identité de nos héros est protégée ! 🥷 + +Il redéclare aussi `id: int`. Ce faisant, nous faisons un **contrat** avec les clients de l'API, afin qu'ils puissent toujours s'attendre à ce que `id` soit présent et soit un `int` (il ne sera jamais `None`). + +/// tip | Astuce + +Avoir le modèle de retour qui garantit qu'une valeur est toujours disponible et toujours `int` (pas `None`) est très utile pour les clients de l'API, ils peuvent écrire un code beaucoup plus simple avec cette certitude. + +De plus, les **clients générés automatiquement** auront des interfaces plus simples, afin que les développeurs qui communiquent avec votre API puissent travailler bien plus facilement avec votre API. 😎 + +/// + +Tous les champs de `HeroPublic` sont les mêmes que dans `HeroBase`, avec `id` déclaré comme `int` (pas `None`) : + +* `id` +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} + +#### `HeroCreate` - le *modèle de données* pour créer un héros { #herocreate-the-data-model-to-create-a-hero } + +Nous créons maintenant un modèle `HeroCreate`, c'est celui qui **validera** les données provenant des clients. + +Il a les mêmes champs que `HeroBase`, et il a aussi `secret_name`. + +Maintenant, lorsque les clients **créent un nouveau héros**, ils enverront `secret_name`, il sera stocké dans la base, mais ces noms secrets ne seront pas renvoyés dans l'API aux clients. + +/// tip | Astuce + +C'est ainsi que vous géreriez les **mots de passe**. Les recevoir, mais ne pas les renvoyer dans l'API. + +Vous **hacherez** aussi les valeurs des mots de passe avant de les stocker, **ne les stockez jamais en texte en clair**. + +/// + +Les champs de `HeroCreate` sont : + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} + +#### `HeroUpdate` - le *modèle de données* pour mettre à jour un héros { #heroupdate-the-data-model-to-update-a-hero } + +Nous n'avions pas de moyen de **mettre à jour un héros** dans la version précédente de l'application, mais maintenant avec **plusieurs modèles**, nous pouvons le faire. 🎉 + +Le *modèle de données* `HeroUpdate` est un peu spécial, il a **tous les mêmes champs** qui seraient nécessaires pour créer un nouveau héros, mais tous les champs sont **optionnels** (ils ont tous une valeur par défaut). Ainsi, lorsque vous mettez à jour un héros, vous pouvez n'envoyer que les champs que vous souhaitez mettre à jour. + +Comme tous les **champs changent réellement** (le type inclut désormais `None` et ils ont maintenant une valeur par défaut de `None`), nous devons les **redéclarer**. + +Nous n'avons pas vraiment besoin d'hériter de `HeroBase` puisque nous redéclarons tous les champs. Je le laisse hériter juste pour la cohérence, mais ce n'est pas nécessaire. C'est plutôt une question de goût personnel. 🤷 + +Les champs de `HeroUpdate` sont : + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} + +### Créer avec `HeroCreate` et retourner un `HeroPublic` { #create-with-herocreate-and-return-a-heropublic } + +Maintenant que nous avons **plusieurs modèles**, nous pouvons mettre à jour les parties de l'application qui les utilisent. + +Nous recevons dans la requête un *modèle de données* `HeroCreate`, et à partir de celui-ci, nous créons un *modèle de table* `Hero`. + +Ce nouveau *modèle de table* `Hero` aura les champs envoyés par le client, et aura aussi un `id` généré par la base. + +Nous retournons ensuite le même *modèle de table* `Hero` tel quel depuis la fonction. Mais comme nous déclarons le `response_model` avec le *modèle de données* `HeroPublic`, **FastAPI** utilisera `HeroPublic` pour valider et sérialiser les données. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} + +/// tip | Astuce + +Nous utilisons maintenant `response_model=HeroPublic` au lieu de l'**annotation de type de retour** `-> HeroPublic` car la valeur que nous renvoyons n'est en réalité *pas* un `HeroPublic`. + +Si nous avions déclaré `-> HeroPublic`, votre éditeur et votre linter se plaindraient (à juste titre) que vous retournez un `Hero` au lieu d'un `HeroPublic`. + +En le déclarant dans `response_model`, nous disons à **FastAPI** de faire son travail, sans interférer avec les annotations de type et l'aide de votre éditeur et d'autres outils. + +/// + +### Lire des héros avec `HeroPublic` { #read-heroes-with-heropublic } + +Nous pouvons faire la même chose qu'avant pour **lire** des `Hero`, à nouveau, nous utilisons `response_model=list[HeroPublic]` pour garantir que les données sont correctement validées et sérialisées. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} + +### Lire un héros avec `HeroPublic` { #read-one-hero-with-heropublic } + +Nous pouvons **lire** un héros unique : + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} + +### Mettre à jour un héros avec `HeroUpdate` { #update-a-hero-with-heroupdate } + +Nous pouvons **mettre à jour un héros**. Pour cela, nous utilisons une opération HTTP `PATCH`. + +Et dans le code, nous obtenons un `dict` avec toutes les données envoyées par le client, **uniquement les données envoyées par le client**, en excluant toute valeur qui serait là simplement parce que c'est la valeur par défaut. Pour ce faire, nous utilisons `exclude_unset=True`. C'est l'astuce principale. 🪄 + +Nous utilisons ensuite `hero_db.sqlmodel_update(hero_data)` pour mettre à jour `hero_db` avec les données de `hero_data`. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} + +### Supprimer un héros (bis) { #delete-a-hero-again } + +**Supprimer** un héros reste pratiquement identique. + +Nous n'allons pas céder à l'envie de tout refactoriser pour celui-ci. 😅 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} + +### Exécuter l'application à nouveau { #run-the-app-again } + +Vous pouvez exécuter l'application à nouveau : + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Si vous allez sur l'UI `/docs` de l'API, vous verrez qu'elle est maintenant à jour, et qu'elle n'attendra plus de recevoir l'`id` du client lors de la création d'un héros, etc. + +
+ +
+ +## Récapitulatif { #recap } + +Vous pouvez utiliser **SQLModel** pour interagir avec une base SQL et simplifier le code avec des *modèles de données* et des *modèles de table*. + +Vous pouvez en apprendre beaucoup plus dans la documentation **SQLModel**, il y a un mini tutoriel plus long sur l'utilisation de SQLModel avec **FastAPI**. 🚀 diff --git a/docs/fr/docs/tutorial/static-files.md b/docs/fr/docs/tutorial/static-files.md new file mode 100644 index 000000000..3928fed9b --- /dev/null +++ b/docs/fr/docs/tutorial/static-files.md @@ -0,0 +1,40 @@ +# Fichiers statiques { #static-files } + +Vous pouvez servir des fichiers statiques automatiquement à partir d'un répertoire en utilisant `StaticFiles`. + +## Utiliser `StaticFiles` { #use-staticfiles } + +- Importer `StaticFiles`. +- « Mount » une instance `StaticFiles()` sur un chemin spécifique. + +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} + +/// note | Détails techniques + +Vous pouvez également utiliser `from starlette.staticfiles import StaticFiles`. + +**FastAPI** fournit le même `starlette.staticfiles` sous le nom `fastapi.staticfiles` uniquement pour votre commodité, en tant que développeur. Mais cela provient en réalité directement de Starlette. + +/// + +### Qu'est-ce que « Mounting » { #what-is-mounting } + +« Mounting » signifie ajouter une application complète « indépendante » sur un chemin spécifique, qui se chargera ensuite de gérer tous les sous-chemins. + +Cela diffère de l'utilisation d'un `APIRouter`, car une application montée est complètement indépendante. L'OpenAPI et les documents de votre application principale n'incluront rien provenant de l'application montée, etc. + +Vous pouvez en lire davantage à ce sujet dans le [Guide utilisateur avancé](../advanced/index.md){.internal-link target=_blank}. + +## Détails { #details } + +Le premier `"/static"` fait référence au sous-chemin sur lequel cette « sous-application » sera « montée ». Ainsi, tout chemin qui commence par `"/static"` sera géré par elle. + +Le `directory="static"` fait référence au nom du répertoire qui contient vos fichiers statiques. + +Le `name="static"` lui donne un nom utilisable en interne par **FastAPI**. + +Tous ces paramètres peuvent être différents de « `static` », adaptez-les aux besoins et aux détails spécifiques de votre propre application. + +## Plus d'informations { #more-info } + +Pour plus de détails et d'options, consultez la documentation de Starlette sur les fichiers statiques. diff --git a/docs/fr/docs/tutorial/testing.md b/docs/fr/docs/tutorial/testing.md new file mode 100644 index 000000000..8a609b644 --- /dev/null +++ b/docs/fr/docs/tutorial/testing.md @@ -0,0 +1,193 @@ +# Tester { #testing } + +Grâce à Starlette, tester des applications **FastAPI** est simple et agréable. + +C’est basé sur HTTPX, dont la conception s’inspire de Requests, ce qui le rend très familier et intuitif. + +Avec cela, vous pouvez utiliser pytest directement avec **FastAPI**. + +## Utiliser `TestClient` { #using-testclient } + +/// info + +Pour utiliser `TestClient`, installez d’abord `httpx`. + +Vous devez créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, l’activer, puis y installer le paquet, par exemple : + +```console +$ pip install httpx +``` + +/// + +Importez `TestClient`. + +Créez un `TestClient` en lui passant votre application **FastAPI**. + +Créez des fonctions dont le nom commence par `test_` (c’est la convention standard de `pytest`). + +Utilisez l’objet `TestClient` de la même manière que vous utilisez `httpx`. + +Écrivez de simples instructions `assert` avec les expressions Python standard que vous devez vérifier (là encore, standard `pytest`). + +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} + +/// tip | Astuce + +Remarquez que les fonctions de test sont des `def` normales, pas des `async def`. + +Et les appels au client sont aussi des appels normaux, sans utiliser `await`. + +Cela vous permet d’utiliser `pytest` directement sans complications. + +/// + +/// note | Détails techniques + +Vous pouvez aussi utiliser `from starlette.testclient import TestClient`. + +**FastAPI** fournit le même `starlette.testclient` sous le nom `fastapi.testclient` uniquement pour votre commodité, en tant que développeur. Mais cela vient directement de Starlette. + +/// + +/// tip | Astuce + +Si vous souhaitez appeler des fonctions `async` dans vos tests en dehors de l’envoi de requêtes à votre application FastAPI (par exemple des fonctions de base de données asynchrones), consultez les [Tests asynchrones](../advanced/async-tests.md){.internal-link target=_blank} dans le tutoriel avancé. + +/// + +## Séparer les tests { #separating-tests } + +Dans une application réelle, vous auriez probablement vos tests dans un fichier différent. + +Et votre application **FastAPI** pourrait aussi être composée de plusieurs fichiers/modules, etc. + +### Fichier d’application **FastAPI** { #fastapi-app-file } + +Supposons que vous ayez une structure de fichiers comme décrit dans [Applications plus grandes](bigger-applications.md){.internal-link target=_blank} : + +``` +. +├── app +│   ├── __init__.py +│   └── main.py +``` + +Dans le fichier `main.py`, vous avez votre application **FastAPI** : + + +{* ../../docs_src/app_testing/app_a_py310/main.py *} + +### Fichier de test { #testing-file } + +Vous pourriez alors avoir un fichier `test_main.py` avec vos tests. Il pourrait vivre dans le même package Python (le même répertoire avec un fichier `__init__.py`) : + +``` hl_lines="5" +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Comme ce fichier se trouve dans le même package, vous pouvez utiliser des imports relatifs pour importer l’objet `app` depuis le module `main` (`main.py`) : + +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} + + +… et avoir le code des tests comme précédemment. + +## Tester : exemple étendu { #testing-extended-example } + +Étendons maintenant cet exemple et ajoutons plus de détails pour voir comment tester différentes parties. + +### Fichier d’application **FastAPI** étendu { #extended-fastapi-app-file } + +Continuons avec la même structure de fichiers qu’auparavant : + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Supposons que désormais le fichier `main.py` avec votre application **FastAPI** contienne d’autres **chemins d’accès**. + +Il a une opération `GET` qui pourrait renvoyer une erreur. + +Il a une opération `POST` qui pourrait renvoyer plusieurs erreurs. + +Les deux chemins d’accès requièrent un en-tête `X-Token`. + +{* ../../docs_src/app_testing/app_b_an_py310/main.py *} + +### Fichier de test étendu { #extended-testing-file } + +Vous pourriez ensuite mettre à jour `test_main.py` avec les tests étendus : + +{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *} + + +Chaque fois que vous avez besoin que le client transmette des informations dans la requête et que vous ne savez pas comment faire, vous pouvez chercher (Google) comment le faire avec `httpx`, ou même comment le faire avec `requests`, puisque la conception de HTTPX est basée sur celle de Requests. + +Ensuite, vous faites simplement la même chose dans vos tests. + +Par exemple : + +* Pour passer un paramètre de chemin ou un paramètre de requête, ajoutez-le directement à l’URL. +* Pour passer un corps JSON, passez un objet Python (par exemple un `dict`) au paramètre `json`. +* Si vous devez envoyer des *Form Data* au lieu de JSON, utilisez le paramètre `data` à la place. +* Pour passer des en-têtes, utilisez un `dict` dans le paramètre `headers`. +* Pour les cookies, un `dict` dans le paramètre `cookies`. + +Pour plus d’informations sur la manière de transmettre des données au backend (en utilisant `httpx` ou le `TestClient`), consultez la documentation HTTPX. + +/// info + +Notez que le `TestClient` reçoit des données qui peuvent être converties en JSON, pas des modèles Pydantic. + +Si vous avez un modèle Pydantic dans votre test et que vous souhaitez envoyer ses données à l’application pendant les tests, vous pouvez utiliser le `jsonable_encoder` décrit dans [Encodeur compatible JSON](encoder.md){.internal-link target=_blank}. + +/// + +## Exécuter { #run-it } + +Après cela, vous avez simplement besoin d’installer `pytest`. + +Vous devez créer un [environnement virtuel](../virtual-environments.md){.internal-link target=_blank}, l’activer, puis y installer le paquet, par exemple : + +
+ +```console +$ pip install pytest + +---> 100% +``` + +
+ +Il détectera automatiquement les fichiers et les tests, les exécutera et vous communiquera les résultats. + +Exécutez les tests avec : + +
+ +```console +$ pytest + +================ test session starts ================ +platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 +rootdir: /home/user/code/superawesome-cli/app +plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 +collected 6 items + +---> 100% + +test_main.py ...... [100%] + +================= 1 passed in 0.03s ================= +``` + +
diff --git a/docs/fr/docs/virtual-environments.md b/docs/fr/docs/virtual-environments.md new file mode 100644 index 000000000..86b5faadc --- /dev/null +++ b/docs/fr/docs/virtual-environments.md @@ -0,0 +1,864 @@ +# Environnements virtuels { #virtual-environments } + +Lorsque vous travaillez sur des projets Python, vous devriez probablement utiliser un environnement virtuel (ou un mécanisme similaire) pour isoler les packages que vous installez pour chaque projet. + +/// info + +Si vous connaissez déjà les environnements virtuels, comment les créer et les utiliser, vous pouvez passer cette section. 🤓 + +/// + +/// tip | Astuce + +Un environnement virtuel est différent d’une variable d’environnement. + +Une variable d’environnement est une variable du système qui peut être utilisée par des programmes. + +Un environnement virtuel est un répertoire contenant certains fichiers. + +/// + +/// info + +Cette page vous apprendra à utiliser les environnements virtuels et à comprendre leur fonctionnement. + +Si vous êtes prêt à adopter un outil qui gère tout pour vous (y compris l’installation de Python), essayez uv. + +/// + +## Créer un projet { #create-a-project } + +Commencez par créer un répertoire pour votre projet. + +Ce que je fais généralement, c’est créer un répertoire nommé `code` dans mon répertoire personnel/utilisateur. + +Et à l’intérieur, je crée un répertoire par projet. + +
+ +```console +// Aller au répertoire personnel +$ cd +// Créer un répertoire pour tous vos projets de code +$ mkdir code +// Entrer dans ce répertoire code +$ cd code +// Créer un répertoire pour ce projet +$ mkdir awesome-project +// Entrer dans ce répertoire de projet +$ cd awesome-project +``` + +
+ +## Créer un environnement virtuel { #create-a-virtual-environment } + +Lorsque vous commencez à travailler sur un projet Python pour la première fois, créez un environnement virtuel dans votre projet. + +/// tip | Astuce + +Vous n’avez besoin de faire cela qu’une seule fois par projet, pas à chaque fois que vous travaillez. + +/// + +//// tab | `venv` + +Pour créer un environnement virtuel, vous pouvez utiliser le module `venv` fourni avec Python. + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | Que signifie cette commande + +* `python` : utiliser le programme nommé `python` +* `-m` : appeler un module comme un script, nous préciserons ensuite quel module +* `venv` : utiliser le module nommé `venv` qui est normalement installé avec Python +* `.venv` : créer l’environnement virtuel dans le nouveau répertoire `.venv` + +/// + +//// + +//// tab | `uv` + +Si vous avez installé `uv`, vous pouvez l’utiliser pour créer un environnement virtuel. + +
+ +```console +$ uv venv +``` + +
+ +/// tip | Astuce + +Par défaut, `uv` créera un environnement virtuel dans un répertoire appelé `.venv`. + +Mais vous pouvez le personnaliser en passant un argument supplémentaire avec le nom du répertoire. + +/// + +//// + +Cette commande crée un nouvel environnement virtuel dans un répertoire appelé `.venv`. + +/// details | `.venv` ou autre nom + +Vous pourriez créer l’environnement virtuel dans un autre répertoire, mais il est d’usage de l’appeler `.venv`. + +/// + +## Activer l’environnement virtuel { #activate-the-virtual-environment } + +Activez le nouvel environnement virtuel afin que toute commande Python que vous exécutez ou tout package que vous installez l’utilise. + +/// tip | Astuce + +Faites cela à chaque fois que vous démarrez une nouvelle session de terminal pour travailler sur le projet. + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Ou si vous utilisez Bash pour Windows (par exemple Git Bash) : + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip | Astuce + +Chaque fois que vous installez un nouveau package dans cet environnement, activez de nouveau l’environnement. + +Vous vous assurez ainsi que si vous utilisez un programme de terminal (CLI) installé par ce package, vous utilisez celui de votre environnement virtuel et non un autre qui pourrait être installé globalement, probablement avec une version différente de celle dont vous avez besoin. + +/// + +## Vérifier que l’environnement virtuel est actif { #check-the-virtual-environment-is-active } + +Vérifiez que l’environnement virtuel est actif (la commande précédente a fonctionné). + +/// tip | Astuce + +C’est facultatif, mais c’est une bonne manière de vérifier que tout fonctionne comme prévu et que vous utilisez l’environnement virtuel voulu. + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +S’il affiche le binaire `python` à `.venv/bin/python`, dans votre projet (dans cet exemple `awesome-project`), alors cela a fonctionné. 🎉 + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +S’il affiche le binaire `python` à `.venv\Scripts\python`, dans votre projet (dans cet exemple `awesome-project`), alors cela a fonctionné. 🎉 + +//// + +## Mettre à niveau `pip` { #upgrade-pip } + +/// tip | Astuce + +Si vous utilisez `uv`, vous l’utiliserez pour installer des éléments à la place de `pip`, vous n’avez donc pas besoin de mettre `pip` à niveau. 😎 + +/// + +Si vous utilisez `pip` pour installer des packages (il est fourni par défaut avec Python), vous devez le mettre à niveau vers la dernière version. + +Beaucoup d’erreurs exotiques lors de l’installation d’un package se résolvent simplement en mettant d’abord `pip` à niveau. + +/// tip | Astuce + +Vous feriez normalement cela une seule fois, juste après avoir créé l’environnement virtuel. + +/// + +Vous devez vous assurer que l’environnement virtuel est actif (avec la commande ci-dessus), puis exécuter : + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +/// tip | Astuce + +Parfois, vous pourriez obtenir une erreur **`No module named pip`** en essayant de mettre à niveau pip. + +Si cela arrive, installez et mettez à niveau pip avec la commande ci-dessous : + +
+ +```console +$ python -m ensurepip --upgrade + +---> 100% +``` + +
+ +Cette commande installera pip s’il n’est pas déjà installé et garantit aussi que la version de pip installée est au moins aussi récente que celle disponible dans `ensurepip`. + +/// + +## Ajouter `.gitignore` { #add-gitignore } + +Si vous utilisez Git (vous devriez), ajoutez un fichier `.gitignore` pour exclure tout ce qui se trouve dans votre `.venv` de Git. + +/// tip | Astuce + +Si vous avez utilisé `uv` pour créer l’environnement virtuel, il l’a déjà fait pour vous, vous pouvez passer cette étape. 😎 + +/// + +/// tip | Astuce + +Faites cela une seule fois, juste après avoir créé l’environnement virtuel. + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | Que signifie cette commande + +* `echo "*"` : va « afficher » le texte `*` dans le terminal (la partie suivante change un peu cela) +* `>` : tout ce qui est affiché dans le terminal par la commande à gauche de `>` ne doit pas être affiché mais écrit dans le fichier à droite de `>` +* `.gitignore` : le nom du fichier dans lequel le texte doit être écrit + +Et `*` signifie pour Git « tout ». Ainsi, il ignorera tout dans le répertoire `.venv`. + +Cette commande créera un fichier `.gitignore` avec le contenu : + +```gitignore +* +``` + +/// + +## Installer des packages { #install-packages } + +Après avoir activé l’environnement, vous pouvez y installer des packages. + +/// tip | Astuce + +Faites cela une seule fois lorsque vous installez ou mettez à niveau les packages nécessaires à votre projet. + +Si vous devez mettre à niveau une version ou ajouter un nouveau package, vous le referez. + +/// + +### Installer des packages directement { #install-packages-directly } + +Si vous êtes pressé et ne souhaitez pas utiliser un fichier pour déclarer les dépendances de votre projet, vous pouvez les installer directement. + +/// tip | Astuce + +C’est une très bonne idée de placer les packages et leurs versions nécessaires à votre programme dans un fichier (par exemple `requirements.txt` ou `pyproject.toml`). + +/// + +//// tab | `pip` + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Si vous avez `uv` : + +
+ +```console +$ uv pip install "fastapi[standard]" +---> 100% +``` + +
+ +//// + +### Installer depuis `requirements.txt` { #install-from-requirements-txt } + +Si vous avez un `requirements.txt`, vous pouvez maintenant l’utiliser pour installer ses packages. + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Si vous avez `uv` : + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | `requirements.txt` + +Un `requirements.txt` avec quelques packages pourrait ressembler à : + +```requirements.txt +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +/// + +## Exécuter votre programme { #run-your-program } + +Après avoir activé l’environnement virtuel, vous pouvez exécuter votre programme, et il utilisera le Python de votre environnement virtuel avec les packages que vous y avez installés. + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## Configurer votre éditeur { #configure-your-editor } + +Vous utiliserez probablement un éditeur, assurez-vous de le configurer pour utiliser le même environnement virtuel que vous avez créé (il le détectera probablement automatiquement) afin d’avoir l’autocomplétion et les erreurs inline. + +Par exemple : + +* VS Code +* PyCharm + +/// tip | Astuce + +Vous devez normalement faire cela une seule fois, lorsque vous créez l’environnement virtuel. + +/// + +## Désactiver l’environnement virtuel { #deactivate-the-virtual-environment } + +Une fois que vous avez fini de travailler sur votre projet, vous pouvez désactiver l’environnement virtuel. + +
+ +```console +$ deactivate +``` + +
+ +Ainsi, lorsque vous exécutez `python`, il n’essaiera pas de l’exécuter depuis cet environnement virtuel avec les packages qui y sont installés. + +## Prêt à travailler { #ready-to-work } + +Vous êtes maintenant prêt à commencer à travailler sur votre projet. + + + +/// tip | Astuce + +Voulez-vous comprendre tout ce qui précède ? + +Continuez la lecture. 👇🤓 + +/// + +## Pourquoi des environnements virtuels { #why-virtual-environments } + +Pour travailler avec FastAPI, vous devez installer Python. + +Ensuite, vous devrez installer FastAPI et tout autre package que vous souhaitez utiliser. + +Pour installer des packages, vous utiliseriez normalement la commande `pip` fournie avec Python (ou des alternatives similaires). + +Néanmoins, si vous utilisez simplement `pip` directement, les packages seraient installés dans votre environnement Python global (l’installation globale de Python). + +### Le problème { #the-problem } + +Alors, quel est le problème d’installer des packages dans l’environnement Python global ? + +À un moment donné, vous finirez probablement par écrire de nombreux programmes différents qui dépendent de packages différents. Et certains de ces projets sur lesquels vous travaillez dépendront de versions différentes du même package. 😱 + +Par exemple, vous pourriez créer un projet appelé `philosophers-stone`, ce programme dépend d’un autre package appelé **`harry`, en version `1`**. Vous devez donc installer `harry`. + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` + +Puis, plus tard, vous créez un autre projet appelé `prisoner-of-azkaban`, et ce projet dépend aussi de `harry`, mais il a besoin de **`harry` en version `3`**. + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] +``` + +Mais maintenant, le problème est que, si vous installez les packages globalement (dans l’environnement global) au lieu de dans un environnement virtuel local, vous devrez choisir quelle version de `harry` installer. + +Si vous voulez exécuter `philosophers-stone`, vous devrez d’abord installer `harry` en version `1`, par exemple avec : + +
+ +```console +$ pip install "harry==1" +``` + +
+ +Et vous vous retrouverez avec `harry` en version `1` installé dans votre environnement Python global. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -->|requires| harry-1 + end +``` + +Mais si vous voulez ensuite exécuter `prisoner-of-azkaban`, vous devrez désinstaller `harry` version `1` et installer `harry` version `3` (ou bien installer la version `3` désinstallerait automatiquement la version `1`). + +
+ +```console +$ pip install "harry==3" +``` + +
+ +Et vous vous retrouverez alors avec `harry` version `3` installé dans votre environnement Python global. + +Et si vous essayez d’exécuter à nouveau `philosophers-stone`, il y a une chance que cela ne fonctionne pas car il a besoin de `harry` version `1`. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -.-x|⛔️| harry-1 + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --> |requires| harry-3 + end +``` + +/// tip | Astuce + +Il est très courant que les packages Python fassent de leur mieux pour éviter les changements cassants dans les nouvelles versions, mais il vaut mieux jouer la sécurité et installer de nouvelles versions intentionnellement et lorsque vous pouvez exécuter les tests pour vérifier que tout fonctionne correctement. + +/// + +Maintenant, imaginez cela avec beaucoup d’autres packages dont tous vos projets dépendent. C’est très difficile à gérer. Et vous finiriez probablement par exécuter certains projets avec des versions incompatibles des packages, sans savoir pourquoi quelque chose ne fonctionne pas. + +De plus, selon votre système d’exploitation (par exemple Linux, Windows, macOS), il se peut qu’il soit livré avec Python déjà installé. Et dans ce cas, il avait probablement des packages préinstallés avec des versions spécifiques nécessaires à votre système. Si vous installez des packages dans l’environnement Python global, vous pourriez finir par casser certains des programmes fournis avec votre système d’exploitation. + +## Où les packages sont-ils installés { #where-are-packages-installed } + +Lorsque vous installez Python, il crée des répertoires avec des fichiers sur votre ordinateur. + +Certains de ces répertoires sont chargés de contenir tous les packages que vous installez. + +Lorsque vous exécutez : + +
+ +```console +// Ne l’exécutez pas maintenant, c’est juste un exemple 🤓 +$ pip install "fastapi[standard]" +---> 100% +``` + +
+ +Cela téléchargera un fichier compressé avec le code de FastAPI, normalement depuis PyPI. + +Il téléchargera également des fichiers pour d’autres packages dont FastAPI dépend. + +Ensuite, il extraira tous ces fichiers et les placera dans un répertoire de votre ordinateur. + +Par défaut, il placera ces fichiers téléchargés et extraits dans le répertoire fourni avec votre installation de Python, c’est l’environnement global. + +## Qu’est-ce qu’un environnement virtuel { #what-are-virtual-environments } + +La solution aux problèmes posés par le fait d’avoir tous les packages dans l’environnement global est d’utiliser un environnement virtuel pour chaque projet sur lequel vous travaillez. + +Un environnement virtuel est un répertoire, très similaire à celui global, où vous pouvez installer les packages pour un projet. + +De cette manière, chaque projet aura son propre environnement virtuel (répertoire `.venv`) avec ses propres packages. + +```mermaid +flowchart TB + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) --->|requires| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --->|requires| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## Que signifie activer un environnement virtuel { #what-does-activating-a-virtual-environment-mean } + +Lorsque vous activez un environnement virtuel, par exemple avec : + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Ou si vous utilisez Bash pour Windows (par exemple Git Bash) : + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +Cette commande créera ou modifiera certaines [variables d’environnement](environment-variables.md){.internal-link target=_blank} qui seront disponibles pour les prochaines commandes. + +L’une de ces variables est la variable `PATH`. + +/// tip | Astuce + +Vous pouvez en savoir plus sur la variable d’environnement `PATH` dans la section [Variables d’environnement](environment-variables.md#path-environment-variable){.internal-link target=_blank}. + +/// + +Activer un environnement virtuel ajoute son chemin `.venv/bin` (sur Linux et macOS) ou `.venv\Scripts` (sur Windows) à la variable d’environnement `PATH`. + +Disons qu’avant d’activer l’environnement, la variable `PATH` ressemblait à ceci : + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +Cela signifie que le système chercherait des programmes dans : + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +Cela signifie que le système chercherait des programmes dans : + +* `C:\Windows\System32` + +//// + +Après avoir activé l’environnement virtuel, la variable `PATH` ressemblerait à quelque chose comme ceci : + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Cela signifie que le système commencera maintenant par chercher des programmes dans : + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +avant de chercher dans les autres répertoires. + +Ainsi, lorsque vous tapez `python` dans le terminal, le système trouvera le programme Python dans + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +et utilisera celui-ci. + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +Cela signifie que le système commencera maintenant par chercher des programmes dans : + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +avant de chercher dans les autres répertoires. + +Ainsi, lorsque vous tapez `python` dans le terminal, le système trouvera le programme Python dans + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +et utilisera celui-ci. + +//// + +Un détail important est qu’il placera le chemin de l’environnement virtuel au début de la variable `PATH`. Le système le trouvera avant de trouver tout autre Python disponible. Ainsi, lorsque vous exécutez `python`, il utilisera le Python de l’environnement virtuel au lieu de tout autre `python` (par exemple, un `python` d’un environnement global). + +Activer un environnement virtuel change aussi deux ou trois autres choses, mais c’est l’un des points les plus importants. + +## Vérifier un environnement virtuel { #checking-a-virtual-environment } + +Lorsque vous vérifiez si un environnement virtuel est actif, par exemple avec : + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +Cela signifie que le programme `python` qui sera utilisé est celui dans l’environnement virtuel. + +Vous utilisez `which` sous Linux et macOS et `Get-Command` sous Windows PowerShell. + +La façon dont cette commande fonctionne est qu’elle va vérifier la variable d’environnement `PATH`, en parcourant chaque chemin dans l’ordre, à la recherche du programme nommé `python`. Une fois trouvé, elle vous affichera le chemin vers ce programme. + +La partie la plus importante est que lorsque vous appelez `python`, c’est exactement « `python` » qui sera exécuté. + +Ainsi, vous pouvez confirmer si vous êtes dans le bon environnement virtuel. + +/// tip | Astuce + +Il est facile d’activer un environnement virtuel, d’obtenir un Python, puis d’aller vers un autre projet. + +Et le second projet ne fonctionnerait pas parce que vous utilisez le Python incorrect, provenant d’un environnement virtuel d’un autre projet. + +Il est utile de pouvoir vérifier quel `python` est utilisé. 🤓 + +/// + +## Pourquoi désactiver un environnement virtuel { #why-deactivate-a-virtual-environment } + +Par exemple, vous pourriez travailler sur un projet `philosophers-stone`, activer cet environnement virtuel, installer des packages et travailler avec cet environnement. + +Puis vous souhaitez travailler sur un autre projet `prisoner-of-azkaban`. + +Vous allez vers ce projet : + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +Si vous ne désactivez pas l’environnement virtuel de `philosophers-stone`, lorsque vous exécutez `python` dans le terminal, il essaiera d’utiliser le Python de `philosophers-stone`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// Erreur lors de l'import de sirius, il n'est pas installé 😱 +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +Mais si vous désactivez l’environnement virtuel et activez le nouveau pour `prisoner-of-askaban`, alors lorsque vous exécuterez `python`, il utilisera le Python de l’environnement virtuel de `prisoner-of-azkaban`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// Vous n’avez pas besoin d’être dans l’ancien répertoire pour désactiver, vous pouvez le faire où que vous soyez, même après être allé dans l’autre projet 😎 +$ deactivate + +// Activer l’environnement virtuel dans prisoner-of-azkaban/.venv 🚀 +$ source .venv/bin/activate + +// Maintenant, lorsque vous exécutez python, il trouvera le package sirius installé dans cet environnement virtuel ✨ +$ python main.py + +I solemnly swear 🐺 +``` + +
+ +## Alternatives { #alternatives } + +Ceci est un guide simple pour vous lancer et vous montrer comment tout fonctionne en dessous. + +Il existe de nombreuses alternatives pour gérer les environnements virtuels, les dépendances de packages (requirements), les projets. + +Lorsque vous êtes prêt et souhaitez utiliser un outil pour gérer l’ensemble du projet, les dépendances, les environnements virtuels, etc., je vous suggère d’essayer uv. + +`uv` peut faire beaucoup de choses, il peut : + +* Installer Python pour vous, y compris différentes versions +* Gérer l’environnement virtuel pour vos projets +* Installer des packages +* Gérer les dépendances de packages et leurs versions pour votre projet +* Vous assurer d’avoir un ensemble exact de packages et de versions à installer, y compris leurs dépendances, afin que vous puissiez être certain d’exécuter votre projet en production exactement comme sur votre ordinateur pendant le développement, cela s’appelle le locking +* Et bien d’autres choses + +## Conclusion { #conclusion } + +Si vous avez lu et compris tout cela, vous en savez maintenant bien plus sur les environnements virtuels que beaucoup de développeurs. 🤓 + +Connaître ces détails vous sera très probablement utile à l’avenir lorsque vous déboguerez quelque chose qui semble complexe, mais vous saurez comment tout fonctionne en dessous. 😎 From dd97806864e5e8d9d9af9b80f7c3177a88735cc8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 14 Feb 2026 08:13:07 +0000 Subject: [PATCH 133/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 631de0611..5ba810aea 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for fr (update-all and add-missing). PR [#14920](https://github.com/fastapi/fastapi/pull/14920) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for de (update-all) . PR [#14910](https://github.com/fastapi/fastapi/pull/14910) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for ja (update-all). PR [#14916](https://github.com/fastapi/fastapi/pull/14916) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for pt (update-all). PR [#14912](https://github.com/fastapi/fastapi/pull/14912) by [@YuriiMotov](https://github.com/YuriiMotov). From 9a082312f38c7878bc9f5b5dfa6aa0c3ddee88aa Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Sat, 14 Feb 2026 09:15:26 +0100 Subject: [PATCH 134/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20zh-hant=20(update-all=20and=20add-missing)=20(#14921)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all * Add missing * 🎨 Auto format --------- Co-authored-by: github-actions[bot] --- docs/zh-hant/docs/_llm-test.md | 503 ++++++++++++++ .../docs/advanced/additional-responses.md | 247 +++++++ .../docs/advanced/additional-status-codes.md | 41 ++ .../docs/advanced/advanced-dependencies.md | 163 +++++ .../docs/advanced/advanced-python-types.md | 61 ++ docs/zh-hant/docs/advanced/async-tests.md | 99 +++ docs/zh-hant/docs/advanced/behind-a-proxy.md | 466 +++++++++++++ docs/zh-hant/docs/advanced/custom-response.md | 312 +++++++++ docs/zh-hant/docs/advanced/dataclasses.md | 87 +++ docs/zh-hant/docs/advanced/events.md | 165 +++++ .../zh-hant/docs/advanced/generate-clients.md | 208 ++++++ docs/zh-hant/docs/advanced/index.md | 21 + docs/zh-hant/docs/advanced/middleware.md | 97 +++ .../docs/advanced/openapi-callbacks.md | 186 ++++++ .../zh-hant/docs/advanced/openapi-webhooks.md | 55 ++ .../path-operation-advanced-configuration.md | 172 +++++ .../advanced/response-change-status-code.md | 31 + .../zh-hant/docs/advanced/response-cookies.md | 51 ++ .../docs/advanced/response-directly.md | 65 ++ .../zh-hant/docs/advanced/response-headers.md | 41 ++ .../docs/advanced/security/http-basic-auth.md | 107 +++ docs/zh-hant/docs/advanced/security/index.md | 19 + .../docs/advanced/security/oauth2-scopes.md | 274 ++++++++ docs/zh-hant/docs/advanced/settings.md | 302 +++++++++ .../zh-hant/docs/advanced/sub-applications.md | 67 ++ docs/zh-hant/docs/advanced/templates.md | 126 ++++ .../docs/advanced/testing-dependencies.md | 53 ++ docs/zh-hant/docs/advanced/testing-events.md | 11 + .../docs/advanced/testing-websockets.md | 13 + .../docs/advanced/using-request-directly.md | 56 ++ docs/zh-hant/docs/advanced/websockets.md | 186 ++++++ docs/zh-hant/docs/advanced/wsgi.md | 51 ++ docs/zh-hant/docs/alternatives.md | 485 ++++++++++++++ docs/zh-hant/docs/async.md | 80 +-- docs/zh-hant/docs/benchmarks.md | 4 +- docs/zh-hant/docs/deployment/concepts.md | 321 +++++++++ docs/zh-hant/docs/deployment/docker.md | 618 ++++++++++++++++++ docs/zh-hant/docs/deployment/fastapicloud.md | 65 ++ docs/zh-hant/docs/deployment/https.md | 231 +++++++ docs/zh-hant/docs/deployment/index.md | 2 +- docs/zh-hant/docs/deployment/manually.md | 157 +++++ .../zh-hant/docs/deployment/server-workers.md | 139 ++++ docs/zh-hant/docs/deployment/versions.md | 93 +++ docs/zh-hant/docs/fastapi-cli.md | 76 +-- docs/zh-hant/docs/features.md | 70 +- docs/zh-hant/docs/help-fastapi.md | 255 ++++++++ docs/zh-hant/docs/history-design-future.md | 79 +++ .../authentication-error-status-code.md | 15 + .../docs/how-to/conditional-openapi.md | 56 ++ .../docs/how-to/configure-swagger-ui.md | 70 ++ .../docs/how-to/custom-docs-ui-assets.md | 185 ++++++ .../docs/how-to/custom-request-and-route.md | 109 +++ docs/zh-hant/docs/how-to/extending-openapi.md | 80 +++ docs/zh-hant/docs/how-to/general.md | 39 ++ docs/zh-hant/docs/how-to/graphql.md | 60 ++ ...migrate-from-pydantic-v1-to-pydantic-v2.md | 135 ++++ .../docs/how-to/separate-openapi-schemas.md | 102 +++ docs/zh-hant/docs/how-to/testing-database.md | 7 + docs/zh-hant/docs/index.md | 10 +- docs/zh-hant/docs/project-generation.md | 28 + docs/zh-hant/docs/python-types.md | 348 ++++++++++ docs/zh-hant/docs/translation-banner.md | 11 + .../zh-hant/docs/tutorial/background-tasks.md | 84 +++ .../docs/tutorial/bigger-applications.md | 504 ++++++++++++++ docs/zh-hant/docs/tutorial/body-fields.md | 61 ++ .../docs/tutorial/body-multiple-params.md | 165 +++++ .../docs/tutorial/body-nested-models.md | 220 +++++++ docs/zh-hant/docs/tutorial/body-updates.md | 100 +++ docs/zh-hant/docs/tutorial/body.md | 164 +++++ .../docs/tutorial/cookie-param-models.md | 76 +++ docs/zh-hant/docs/tutorial/cookie-params.md | 45 ++ docs/zh-hant/docs/tutorial/cors.md | 88 +++ docs/zh-hant/docs/tutorial/debugging.md | 113 ++++ .../dependencies/classes-as-dependencies.md | 288 ++++++++ ...pendencies-in-path-operation-decorators.md | 69 ++ .../dependencies/dependencies-with-yield.md | 288 ++++++++ .../dependencies/global-dependencies.md | 15 + .../docs/tutorial/dependencies/index.md | 248 +++++++ .../tutorial/dependencies/sub-dependencies.md | 105 +++ docs/zh-hant/docs/tutorial/encoder.md | 35 + .../zh-hant/docs/tutorial/extra-data-types.md | 62 ++ docs/zh-hant/docs/tutorial/extra-models.md | 211 ++++++ docs/zh-hant/docs/tutorial/first-steps.md | 16 +- docs/zh-hant/docs/tutorial/handling-errors.md | 244 +++++++ .../docs/tutorial/header-param-models.md | 72 ++ docs/zh-hant/docs/tutorial/header-params.md | 91 +++ docs/zh-hant/docs/tutorial/metadata.md | 120 ++++ docs/zh-hant/docs/tutorial/middleware.md | 95 +++ .../tutorial/path-operation-configuration.md | 107 +++ .../path-params-numeric-validations.md | 154 +++++ docs/zh-hant/docs/tutorial/path-params.md | 251 +++++++ .../tutorial/query-params-str-validations.md | 449 +++++++++++++ docs/zh-hant/docs/tutorial/query-params.md | 187 ++++++ docs/zh-hant/docs/tutorial/request-files.md | 176 +++++ .../docs/tutorial/request-form-models.md | 78 +++ .../docs/tutorial/request-forms-and-files.md | 41 ++ docs/zh-hant/docs/tutorial/request-forms.md | 73 +++ docs/zh-hant/docs/tutorial/response-model.md | 343 ++++++++++ .../docs/tutorial/response-status-code.md | 101 +++ .../docs/tutorial/schema-extra-example.md | 202 ++++++ .../docs/tutorial/security/first-steps.md | 203 ++++++ .../tutorial/security/get-current-user.md | 105 +++ docs/zh-hant/docs/tutorial/security/index.md | 101 +++ .../docs/tutorial/security/oauth2-jwt.md | 277 ++++++++ .../docs/tutorial/security/simple-oauth2.md | 289 ++++++++ docs/zh-hant/docs/tutorial/sql-databases.md | 357 ++++++++++ docs/zh-hant/docs/tutorial/static-files.md | 40 ++ docs/zh-hant/docs/tutorial/testing.md | 193 ++++++ docs/zh-hant/docs/virtual-environments.md | 6 +- 109 files changed, 15439 insertions(+), 139 deletions(-) create mode 100644 docs/zh-hant/docs/_llm-test.md create mode 100644 docs/zh-hant/docs/advanced/additional-responses.md create mode 100644 docs/zh-hant/docs/advanced/additional-status-codes.md create mode 100644 docs/zh-hant/docs/advanced/advanced-dependencies.md create mode 100644 docs/zh-hant/docs/advanced/advanced-python-types.md create mode 100644 docs/zh-hant/docs/advanced/async-tests.md create mode 100644 docs/zh-hant/docs/advanced/behind-a-proxy.md create mode 100644 docs/zh-hant/docs/advanced/custom-response.md create mode 100644 docs/zh-hant/docs/advanced/dataclasses.md create mode 100644 docs/zh-hant/docs/advanced/events.md create mode 100644 docs/zh-hant/docs/advanced/generate-clients.md create mode 100644 docs/zh-hant/docs/advanced/index.md create mode 100644 docs/zh-hant/docs/advanced/middleware.md create mode 100644 docs/zh-hant/docs/advanced/openapi-callbacks.md create mode 100644 docs/zh-hant/docs/advanced/openapi-webhooks.md create mode 100644 docs/zh-hant/docs/advanced/path-operation-advanced-configuration.md create mode 100644 docs/zh-hant/docs/advanced/response-change-status-code.md create mode 100644 docs/zh-hant/docs/advanced/response-cookies.md create mode 100644 docs/zh-hant/docs/advanced/response-directly.md create mode 100644 docs/zh-hant/docs/advanced/response-headers.md create mode 100644 docs/zh-hant/docs/advanced/security/http-basic-auth.md create mode 100644 docs/zh-hant/docs/advanced/security/index.md create mode 100644 docs/zh-hant/docs/advanced/security/oauth2-scopes.md create mode 100644 docs/zh-hant/docs/advanced/settings.md create mode 100644 docs/zh-hant/docs/advanced/sub-applications.md create mode 100644 docs/zh-hant/docs/advanced/templates.md create mode 100644 docs/zh-hant/docs/advanced/testing-dependencies.md create mode 100644 docs/zh-hant/docs/advanced/testing-events.md create mode 100644 docs/zh-hant/docs/advanced/testing-websockets.md create mode 100644 docs/zh-hant/docs/advanced/using-request-directly.md create mode 100644 docs/zh-hant/docs/advanced/websockets.md create mode 100644 docs/zh-hant/docs/advanced/wsgi.md create mode 100644 docs/zh-hant/docs/alternatives.md create mode 100644 docs/zh-hant/docs/deployment/concepts.md create mode 100644 docs/zh-hant/docs/deployment/docker.md create mode 100644 docs/zh-hant/docs/deployment/fastapicloud.md create mode 100644 docs/zh-hant/docs/deployment/https.md create mode 100644 docs/zh-hant/docs/deployment/manually.md create mode 100644 docs/zh-hant/docs/deployment/server-workers.md create mode 100644 docs/zh-hant/docs/deployment/versions.md create mode 100644 docs/zh-hant/docs/help-fastapi.md create mode 100644 docs/zh-hant/docs/history-design-future.md create mode 100644 docs/zh-hant/docs/how-to/authentication-error-status-code.md create mode 100644 docs/zh-hant/docs/how-to/conditional-openapi.md create mode 100644 docs/zh-hant/docs/how-to/configure-swagger-ui.md create mode 100644 docs/zh-hant/docs/how-to/custom-docs-ui-assets.md create mode 100644 docs/zh-hant/docs/how-to/custom-request-and-route.md create mode 100644 docs/zh-hant/docs/how-to/extending-openapi.md create mode 100644 docs/zh-hant/docs/how-to/general.md create mode 100644 docs/zh-hant/docs/how-to/graphql.md create mode 100644 docs/zh-hant/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md create mode 100644 docs/zh-hant/docs/how-to/separate-openapi-schemas.md create mode 100644 docs/zh-hant/docs/how-to/testing-database.md create mode 100644 docs/zh-hant/docs/project-generation.md create mode 100644 docs/zh-hant/docs/python-types.md create mode 100644 docs/zh-hant/docs/translation-banner.md create mode 100644 docs/zh-hant/docs/tutorial/background-tasks.md create mode 100644 docs/zh-hant/docs/tutorial/bigger-applications.md create mode 100644 docs/zh-hant/docs/tutorial/body-fields.md create mode 100644 docs/zh-hant/docs/tutorial/body-multiple-params.md create mode 100644 docs/zh-hant/docs/tutorial/body-nested-models.md create mode 100644 docs/zh-hant/docs/tutorial/body-updates.md create mode 100644 docs/zh-hant/docs/tutorial/body.md create mode 100644 docs/zh-hant/docs/tutorial/cookie-param-models.md create mode 100644 docs/zh-hant/docs/tutorial/cookie-params.md create mode 100644 docs/zh-hant/docs/tutorial/cors.md create mode 100644 docs/zh-hant/docs/tutorial/debugging.md create mode 100644 docs/zh-hant/docs/tutorial/dependencies/classes-as-dependencies.md create mode 100644 docs/zh-hant/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md create mode 100644 docs/zh-hant/docs/tutorial/dependencies/dependencies-with-yield.md create mode 100644 docs/zh-hant/docs/tutorial/dependencies/global-dependencies.md create mode 100644 docs/zh-hant/docs/tutorial/dependencies/index.md create mode 100644 docs/zh-hant/docs/tutorial/dependencies/sub-dependencies.md create mode 100644 docs/zh-hant/docs/tutorial/encoder.md create mode 100644 docs/zh-hant/docs/tutorial/extra-data-types.md create mode 100644 docs/zh-hant/docs/tutorial/extra-models.md create mode 100644 docs/zh-hant/docs/tutorial/handling-errors.md create mode 100644 docs/zh-hant/docs/tutorial/header-param-models.md create mode 100644 docs/zh-hant/docs/tutorial/header-params.md create mode 100644 docs/zh-hant/docs/tutorial/metadata.md create mode 100644 docs/zh-hant/docs/tutorial/middleware.md create mode 100644 docs/zh-hant/docs/tutorial/path-operation-configuration.md create mode 100644 docs/zh-hant/docs/tutorial/path-params-numeric-validations.md create mode 100644 docs/zh-hant/docs/tutorial/path-params.md create mode 100644 docs/zh-hant/docs/tutorial/query-params-str-validations.md create mode 100644 docs/zh-hant/docs/tutorial/query-params.md create mode 100644 docs/zh-hant/docs/tutorial/request-files.md create mode 100644 docs/zh-hant/docs/tutorial/request-form-models.md create mode 100644 docs/zh-hant/docs/tutorial/request-forms-and-files.md create mode 100644 docs/zh-hant/docs/tutorial/request-forms.md create mode 100644 docs/zh-hant/docs/tutorial/response-model.md create mode 100644 docs/zh-hant/docs/tutorial/response-status-code.md create mode 100644 docs/zh-hant/docs/tutorial/schema-extra-example.md create mode 100644 docs/zh-hant/docs/tutorial/security/first-steps.md create mode 100644 docs/zh-hant/docs/tutorial/security/get-current-user.md create mode 100644 docs/zh-hant/docs/tutorial/security/index.md create mode 100644 docs/zh-hant/docs/tutorial/security/oauth2-jwt.md create mode 100644 docs/zh-hant/docs/tutorial/security/simple-oauth2.md create mode 100644 docs/zh-hant/docs/tutorial/sql-databases.md create mode 100644 docs/zh-hant/docs/tutorial/static-files.md create mode 100644 docs/zh-hant/docs/tutorial/testing.md diff --git a/docs/zh-hant/docs/_llm-test.md b/docs/zh-hant/docs/_llm-test.md new file mode 100644 index 000000000..85cfd2299 --- /dev/null +++ b/docs/zh-hant/docs/_llm-test.md @@ -0,0 +1,503 @@ +# LLM 測試檔案 { #llm-test-file } + +本文件用來測試用於翻譯文件的 LLM 是否理解 `scripts/translate.py` 中的 `general_prompt`,以及 `docs/{language code}/llm-prompt.md` 中的語言特定提示。語言特定提示會附加在 `general_prompt` 後面。 + +此處新增的測試會提供給所有語言特定提示的設計者參考。 + +使用方式: + +* 準備語言特定提示 - `docs/{language code}/llm-prompt.md`。 +* 針對本文件做一次全新的翻譯為你想要的目標語言(例如使用 `translate.py` 的 `translate-page` 指令)。這會在 `docs/{language code}/docs/_llm-test.md` 產生翻譯檔。 +* 檢查翻譯是否正確。 +* 如有需要,改進你的語言特定提示、通用提示,或英文原文。 +* 然後手動修正翻譯中剩下的問題,讓它成為一個好的譯文。 +* 重新翻譯,並保留這份好的譯文。理想結果是 LLM 不再對該譯文做任何變更。這代表通用提示與你的語言特定提示已經盡可能完善(有時它仍可能做出幾個看似隨機的變更,原因是LLMs 並非決定性演算法)。 + +測試: + +## 程式碼片段 { #code-snippets } + +//// tab | 測試 + +這是一個程式碼片段:`foo`。這是另一個程式碼片段:`bar`。還有一個:`baz quux`。 + +//// + +//// tab | 資訊 + +程式碼片段內的內容應保持原樣。 + +請見 `scripts/translate.py` 中通用提示的 `### Content of code snippets` 小節。 + +//// + +## 引號 { #quotes } + +//// tab | 測試 + +Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'". + +/// note | 注意 + +LLM 很可能會把這段翻譯錯。重點只在於重新翻譯時是否能保留已修正的翻譯。 + +/// + +//// + +//// tab | 資訊 + +提示設計者可以選擇是否將中性引號轉換為排印引號。保留原樣也可以。 + +例如請見 `docs/de/llm-prompt.md` 中的 `### Quotes` 小節。 + +//// + +## 程式碼片段中的引號 { #quotes-in-code-snippets } + +//// tab | 測試 + +`pip install "foo[bar]"` + +程式碼片段中字串常值的例子:"this"、'that'。 + +較難的程式碼片段中字串常值例子:`f"I like {'oranges' if orange else "apples"}"` + +進階:`Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` + +//// + +//// tab | 資訊 + +... 不過,程式碼片段中的引號必須保持原樣。 + +//// + +## 程式碼區塊 { #code-blocks } + +//// tab | 測試 + +一個 Bash 程式碼範例... + +```bash +# 向宇宙輸出問候 +echo "Hello universe" +``` + +...以及一個主控台範例... + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +...以及另一個主控台範例... + +```console +// 建立目錄 "code" +$ mkdir code +// 切換到該目錄 +$ cd code +``` + +...以及一個 Python 程式碼範例... + +```Python +wont_work() # 這不會運作 😱 +works(foo="bar") # 這可以運作 🎉 +``` + +...就是這樣。 + +//// + +//// tab | 資訊 + +程式碼區塊中的程式碼不應修改,註解除外。 + +請見 `scripts/translate.py` 中通用提示的 `### Content of code blocks` 小節。 + +//// + +## 分頁與色塊 { #tabs-and-colored-boxes } + +//// tab | 測試 + +/// info | 資訊 +Some text +/// + +/// note | 注意 +Some text +/// + +/// note | 技術細節 +Some text +/// + +/// check | 檢查 +Some text +/// + +/// tip | 提示 +Some text +/// + +/// warning | 警告 +Some text +/// + +/// danger | 危險 +Some text +/// + +//// + +//// tab | 資訊 + +分頁與 `Info`/`Note`/`Warning`/等區塊,應在直線(`|`)後加入其標題的翻譯。 + +請見 `scripts/translate.py` 中通用提示的 `### Special blocks` 與 `### Tab blocks` 小節。 + +//// + +## 網頁與內部連結 { #web-and-internal-links } + +//// tab | 測試 + +連結文字應被翻譯,連結位址應保持不變: + +* [連結到上方標題](#code-snippets) +* [內部連結](index.md#installation){.internal-link target=_blank} +* 外部連結 +* 連結到樣式 +* 連結到腳本 +* 連結到圖片 + +連結文字應被翻譯,連結位址應指向對應的翻譯版本: + +* FastAPI 連結 + +//// + +//// tab | 資訊 + +連結應翻譯其文字,但位址需保持不變。例外是指向 FastAPI 文件網站的絕對連結,該情況下位址應指向對應的翻譯版本。 + +請見 `scripts/translate.py` 中通用提示的 `### Links` 小節。 + +//// + +## HTML「abbr」元素 { #html-abbr-elements } + +//// tab | 測試 + +以下是一些包在 HTML「abbr」元素中的內容(部分為杜撰): + +### abbr 提供完整詞組 { #the-abbr-gives-a-full-phrase } + +* GTD +* lt +* XWT +* PSGI + +### abbr 提供完整詞組與說明 { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* MDN +* I/O. + +//// + +//// tab | 資訊 + +「abbr」元素的「title」屬性需依特定規則翻譯。 + +翻譯可以自行新增「abbr」元素(例如為解釋英文單字),LLM 不應移除它們。 + +請見 `scripts/translate.py` 中通用提示的 `### HTML abbr elements` 小節。 + +//// + +## HTML「dfn」元素 { #html-dfn-elements } + +* 叢集 +* 深度學習 + +## 標題 { #headings } + +//// tab | 測試 + +### 開發網頁應用程式 - 教學 { #develop-a-webapp-a-tutorial } + +Hello. + +### 型別提示與註解 { #type-hints-and-annotations } + +Hello again. + +### 超類與子類別 { #super-and-subclasses } + +Hello again. + +//// + +//// tab | 資訊 + +標題唯一的硬性規則是保留花括號中的雜湊片段不變,以確保連結不會失效。 + +請見 `scripts/translate.py` 中通用提示的 `### Headings` 小節。 + +關於語言特定的說明,參見例如 `docs/de/llm-prompt.md` 中的 `### Headings` 小節。 + +//// + +## 文件中使用的術語 { #terms-used-in-the-docs } + +//// tab | 測試 + +* you +* your + +* e.g. +* etc. + +* `foo` as an `int` +* `bar` as a `str` +* `baz` as a `list` + +* 教學 - 使用者指南 +* 進階使用者指南 +* SQLModel 文件 +* API 文件 +* 自動文件 + +* 資料科學 +* 深度學習 +* 機器學習 +* 相依性注入 +* HTTP 基本驗證 +* HTTP 摘要驗證 +* ISO 格式 +* JSON Schema 標準 +* JSON 結構描述 +* 結構描述定義 +* 密碼流程 +* 行動裝置 + +* 已棄用 +* 設計的 +* 無效 +* 即時 +* 標準 +* 預設 +* 區分大小寫 +* 不區分大小寫 + +* 提供應用程式服務 +* 提供頁面服務 + +* 應用程式 +* 應用程式 + +* 請求 +* 回應 +* 錯誤回應 + +* 路徑操作 +* 路徑操作裝飾器 +* 路徑操作函式 + +* 主體 +* 請求主體 +* 回應主體 +* JSON 主體 +* 表單主體 +* 檔案主體 +* 函式主體 + +* 參數 +* 主體參數 +* 路徑參數 +* 查詢參數 +* Cookie 參數 +* 標頭參數 +* 表單參數 +* 函式參數 + +* 事件 +* 啟動事件 +* 伺服器的啟動 +* 關閉事件 +* 生命週期事件 + +* 處理器 +* 事件處理器 +* 例外處理器 +* 處理 + +* 模型 +* Pydantic 模型 +* 資料模型 +* 資料庫模型 +* 表單模型 +* 模型物件 + +* 類別 +* 基底類別 +* 父類別 +* 子類別 +* 子類別 +* 同級類別 +* 類別方法 + +* 標頭 +* 標頭 +* 授權標頭 +* `Authorization` 標頭 +* 轉送標頭 + +* 相依性注入系統 +* 相依項 +* 可相依對象 +* 相依者 + +* I/O 受限 +* CPU 受限 +* 並發 +* 平行處理 +* 多處理程序 + +* 環境變數 +* 環境變數 +* `PATH` +* `PATH` 變數 + +* 驗證 +* 驗證提供者 +* 授權 +* 授權表單 +* 授權提供者 +* 使用者進行驗證 +* 系統驗證使用者 + +* CLI +* 命令列介面 + +* 伺服器 +* 用戶端 + +* 雲端提供者 +* 雲端服務 + +* 開發 +* 開發階段 + +* dict +* 字典 +* 列舉 +* enum +* 列舉成員 + +* 編碼器 +* 解碼器 +* 編碼 +* 解碼 + +* 例外 +* 拋出 + +* 運算式 +* 陳述式 + +* 前端 +* 後端 + +* GitHub 討論 +* GitHub 議題 + +* 效能 +* 效能優化 + +* 回傳型別 +* 回傳值 + +* 安全性 +* 安全性機制 + +* 任務 +* 背景任務 +* 任務函式 + +* 樣板 +* 樣板引擎 + +* 型別註解 +* 型別提示 + +* 伺服器工作進程 +* Uvicorn 工作進程 +* Gunicorn 工作進程 +* 工作進程 +* worker 類別 +* 工作負載 + +* 部署 +* 部署 + +* SDK +* 軟體開發工具組 + +* `APIRouter` +* `requirements.txt` +* Bearer Token +* 相容性破壞變更 +* 錯誤 +* 按鈕 +* 可呼叫對象 +* 程式碼 +* 提交 +* 情境管理器 +* 協程 +* 資料庫工作階段 +* 磁碟 +* 網域 +* 引擎 +* 假的 X +* HTTP GET 方法 +* 項目 +* 函式庫 +* 生命週期 +* 鎖 +* 中介軟體 +* 行動應用程式 +* 模組 +* 掛載 +* 網路 +* 來源 +* 覆寫 +* 有效負載 +* 處理器 +* 屬性 +* 代理 +* pull request +* 查詢 +* RAM +* 遠端機器 +* 狀態碼 +* 字串 +* 標籤 +* Web 框架 +* 萬用字元 +* 回傳 +* 驗證 + +//// + +//// tab | 資訊 + +這是一份不完整且非規範性的(多為)技術術語清單,來源為文件內容。它可能有助於提示設計者判斷哪些術語需要 LLM 的特別協助。例如當 LLM 反覆把好的翻譯改回成較差的版本,或在你的語言中對某詞的詞形變化處理有困難時。 + +請見例如 `docs/de/llm-prompt.md` 中的 `### List of English terms and their preferred German translations` 小節。 + +//// diff --git a/docs/zh-hant/docs/advanced/additional-responses.md b/docs/zh-hant/docs/advanced/additional-responses.md new file mode 100644 index 000000000..bb2bf259b --- /dev/null +++ b/docs/zh-hant/docs/advanced/additional-responses.md @@ -0,0 +1,247 @@ +# OpenAPI 中的額外回應 { #additional-responses-in-openapi } + +/// warning | 警告 + +這是一個偏進階的主題。 + +如果你剛開始使用 **FastAPI**,大多情況下不需要用到它。 + +/// + +你可以宣告額外的回應,包含額外的狀態碼、媒體型別、描述等。 + +這些額外回應會被包含在 OpenAPI 中,因此也會顯示在 API 文件裡。 + +但對於這些額外回應,你必須直接回傳像 `JSONResponse` 這樣的 `Response`,並包含你的狀態碼與內容。 + +## 使用 `model` 的額外回應 { #additional-response-with-model } + +你可以在你的「路徑操作裝飾器」中傳入參數 `responses`。 + +它接收一個 `dict`:鍵是各回應的狀態碼(例如 `200`),值是另一個 `dict`,其中包含每個回應的資訊。 + +每個回應的 `dict` 都可以有一個鍵 `model`,包含一個 Pydantic 模型,與 `response_model` 類似。 + +**FastAPI** 會取用該模型、產生其 JSON Schema,並把它放到 OpenAPI 中正確的位置。 + +例如,要宣告一個狀態碼為 `404` 的額外回應,並使用 Pydantic 模型 `Message`,你可以這樣寫: + +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} + +/// note | 注意 + +請記住你必須直接回傳 `JSONResponse`。 + +/// + +/// info | 說明 + +`model` 這個鍵不屬於 OpenAPI。 + +**FastAPI** 會從這裡取出 Pydantic 模型,產生 JSON Schema,並放到正確位置。 + +正確的位置是: + +* 在 `content` 這個鍵中,其值是一個 JSON 物件(`dict`),包含: + * 一個媒體型別作為鍵,例如 `application/json`,其值是另一個 JSON 物件,當中包含: + * 鍵 `schema`,其值是該模型的 JSON Schema,這裡就是正確的位置。 + * **FastAPI** 會在這裡加入一個指向你 OpenAPI 中全域 JSON Schemas 的參照,而不是直接把它嵌入。如此一來,其他應用與用戶端可以直接使用那些 JSON Schemas,提供更好的程式碼產生工具等。 + +/// + +這個路徑操作在 OpenAPI 中產生的回應將會是: + +```JSON hl_lines="3-12" +{ + "responses": { + "404": { + "description": "Additional Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } +} +``` + +這些 Schemas 會在 OpenAPI 中以參照的方式指向其他位置: + +```JSON hl_lines="4-16" +{ + "components": { + "schemas": { + "Message": { + "title": "Message", + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "title": "Message", + "type": "string" + } + } + }, + "Item": { + "title": "Item", + "required": [ + "id", + "value" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + } + }, + "ValidationError": { + "title": "ValidationError", + "required": [ + "loc", + "msg", + "type" + ], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } +} +``` + +## 主回應的其他媒體型別 { #additional-media-types-for-the-main-response } + +你可以用同一個 `responses` 參數,替相同的主回應新增不同的媒體型別。 + +例如,你可以新增 `image/png` 這種媒體型別,宣告你的「路徑操作」可以回傳 JSON 物件(媒體型別為 `application/json`)或一張 PNG 圖片: + +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} + +/// note | 注意 + +請注意你必須直接用 `FileResponse` 回傳圖片。 + +/// + +/// info | 說明 + +除非你在 `responses` 參數中明確指定不同的媒體型別,否則 FastAPI 會假設回應的媒體型別與主回應類別相同(預設為 `application/json`)。 + +但如果你指定了一個自訂的回應類別,且其媒體型別為 `None`,那麼對於任何具關聯模型的額外回應,FastAPI 會使用 `application/json`。 + +/// + +## 結合資訊 { #combining-information } + +你也可以從多個地方結合回應資訊,包括 `response_model`、`status_code` 與 `responses` 參數。 + +你可以宣告一個 `response_model`,使用預設狀態碼 `200`(或你需要的自訂狀態碼),然後在 `responses` 中直接於 OpenAPI Schema 為相同的回應宣告額外資訊。 + +**FastAPI** 會保留 `responses` 提供的額外資訊,並把它和你模型的 JSON Schema 結合。 + +例如,你可以宣告一個狀態碼為 `404` 的回應,使用一個 Pydantic 模型,並帶有自訂的 `description`。 + +以及一個狀態碼為 `200` 的回應,使用你的 `response_model`,但包含自訂的 `example`: + +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} + +以上都會被結合並包含在你的 OpenAPI 中,並顯示在 API 文件: + + + +## 結合預先定義與自訂的回應 { #combine-predefined-responses-and-custom-ones } + +你可能想要有一些適用於多個「路徑操作」的預先定義回應,但也想與每個「路徑操作」所需的自訂回應結合。 + +在這些情況下,你可以使用 Python 的「解包」`dict` 技巧,透過 `**dict_to_unpack`: + +```Python +old_dict = { + "old key": "old value", + "second old key": "second old value", +} +new_dict = {**old_dict, "new key": "new value"} +``` + +此處,`new_dict` 會包含 `old_dict` 的所有鍵值配對,再加上新的鍵值配對: + +```Python +{ + "old key": "old value", + "second old key": "second old value", + "new key": "new value", +} +``` + +你可以用這個技巧在「路徑操作」中重用一些預先定義的回應,並與其他自訂回應結合。 + +例如: + +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} + +## 關於 OpenAPI 回應的更多資訊 { #more-information-about-openapi-responses } + +若要查看回應中究竟可以包含哪些內容,你可以參考 OpenAPI 規範中的這些章節: + +* OpenAPI Responses 物件,其中包含 `Response Object`。 +* OpenAPI Response 物件,你可以把這裡的任何內容直接放到 `responses` 參數內各個回應中。包含 `description`、`headers`、`content`(在其中宣告不同的媒體型別與 JSON Schemas)、以及 `links`。 diff --git a/docs/zh-hant/docs/advanced/additional-status-codes.md b/docs/zh-hant/docs/advanced/additional-status-codes.md new file mode 100644 index 000000000..450a4705a --- /dev/null +++ b/docs/zh-hant/docs/advanced/additional-status-codes.md @@ -0,0 +1,41 @@ +# 額外的狀態碼 { #additional-status-codes } + +在預設情況下,**FastAPI** 會使用 `JSONResponse` 傳回回應,並把你從你的「路徑操作(path operation)」回傳的內容放進該 `JSONResponse` 中。 + +它會使用預設的狀態碼,或你在路徑操作中設定的狀態碼。 + +## 額外的狀態碼 { #additional-status-codes_1 } + +如果你想在主要狀態碼之外再回傳其他狀態碼,可以直接回傳一個 `Response`(例如 `JSONResponse`),並直接設定你想要的額外狀態碼。 + +例如,你想要有一個允許更新項目的路徑操作,成功時回傳 HTTP 狀態碼 200 "OK"。 + +但你也希望它能接受新項目;當項目先前不存在時就建立它們,並回傳 HTTP 狀態碼 201 "Created"。 + +要達成這點,匯入 `JSONResponse`,直接在那裡回傳內容,並設定你想要的 `status_code`: + +{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} + +/// warning + +當你直接回傳一個 `Response`(就像上面的範例),它會原封不動地被送出。 + +不會再經過模型序列化等處理。 + +請確認其中包含你要的資料,且各值是合法的 JSON(如果你使用的是 `JSONResponse`)。 + +/// + +/// note | 注意 + +你也可以使用 `from starlette.responses import JSONResponse`。 + +**FastAPI** 也將同樣的 `starlette.responses` 以 `fastapi.responses` 的形式提供,純粹是為了讓你(開發者)更方便。但大多數可用的回應類別其實都直接來自 Starlette。`status` 也一樣。 + +/// + +## OpenAPI 與 API 文件 { #openapi-and-api-docs } + +如果你直接回傳額外的狀態碼與回應,它們不會被包含進 OpenAPI 綱要(API 文件)中,因為 FastAPI 無法事先知道你會回傳什麼。 + +但你可以在程式碼中補充文件,使用:[額外的回應](additional-responses.md){.internal-link target=_blank}。 diff --git a/docs/zh-hant/docs/advanced/advanced-dependencies.md b/docs/zh-hant/docs/advanced/advanced-dependencies.md new file mode 100644 index 000000000..472cf470b --- /dev/null +++ b/docs/zh-hant/docs/advanced/advanced-dependencies.md @@ -0,0 +1,163 @@ +# 進階相依 { #advanced-dependencies } + +## 參數化的相依 { #parameterized-dependencies } + +到目前為止看到的相依都是固定的函式或類別。 + +但有些情況下,你可能想要能為相依設定參數,而不必宣告許多不同的函式或類別。 + +想像我們想要一個相依,用來檢查查詢參數 `q` 是否包含某些固定內容。 + +同時我們希望能將那個固定內容參數化。 + +## 「callable」的實例 { #a-callable-instance } + +在 Python 中有一種方式可以讓一個類別的實例變成「callable」。 + +不是類別本身(類別本來就可呼叫),而是該類別的實例。 + +要做到這點,我們宣告一個 `__call__` 方法: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} + +在這個情境中,**FastAPI** 會用這個 `__call__` 來檢查額外的參數與子相依,並在之後呼叫它,把回傳值傳遞給你的「路徑操作函式」中的參數。 + +## 讓實例可參數化 { #parameterize-the-instance } + +接著,我們可以用 `__init__` 來宣告這個實例的參數,用以「參數化」這個相依: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} + +在這裡,**FastAPI** 完全不會接觸或在意 `__init__`,我們會直接在自己的程式碼中使用它。 + +## 建立一個實例 { #create-an-instance } + +我們可以這樣建立該類別的實例: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} + +如此一來我們就能「參數化」相依,現在它內部含有 `"bar"`,作為屬性 `checker.fixed_content`。 + +## 將實例作為相依使用 { #use-the-instance-as-a-dependency } + +然後,我們可以在 `Depends(checker)` 中使用這個 `checker`,而不是 `Depends(FixedContentQueryChecker)`,因為相依是那個實例 `checker`,不是類別本身。 + +當解析相依時,**FastAPI** 會像這樣呼叫這個 `checker`: + +```Python +checker(q="somequery") +``` + +...並將其回傳值,作為相依的值,以參數 `fixed_content_included` 傳給我們的「路徑操作函式」: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} + +/// tip | 提示 + +這一切現在看起來也許有點牽強,而且目前可能還不太清楚有何用途。 + +這些範例刻意保持簡單,但展示了整個機制如何運作。 + +在關於安全性的章節裡,有一些工具函式也是用同樣的方式實作。 + +如果你理解了以上內容,你其實已經知道那些安全性工具在底層是如何運作的。 + +/// + +## 同時含有 `yield`、`HTTPException`、`except` 與背景任務的相依 { #dependencies-with-yield-httpexception-except-and-background-tasks } + +/// warning | 警告 + +你很可能不需要這些技術細節。 + +這些細節主要在於:如果你有一個 0.121.0 之前的 FastAPI 應用,並且在使用含有 `yield` 的相依時遇到問題,會對你有幫助。 + +/// + +含有 `yield` 的相依隨著時間演進,以涵蓋不同的使用情境並修正一些問題。以下是變更摘要。 + +### 含有 `yield` 與 `scope` 的相依 { #dependencies-with-yield-and-scope } + +在 0.121.0 版中,FastAPI 為含有 `yield` 的相依加入了 `Depends(scope="function")` 的支援。 + +使用 `Depends(scope="function")` 時,`yield` 之後的結束程式碼會在「路徑操作函式」執行完畢後立刻執行,在回應發送回客戶端之前。 + +而當使用 `Depends(scope="request")`(預設值)時,`yield` 之後的結束程式碼會在回應送出之後才執行。 + +你可以在文件中閱讀更多:[含有 `yield` 的相依 - 提前結束與 `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope)。 + +### 含有 `yield` 與 `StreamingResponse` 的相依,技術細節 { #dependencies-with-yield-and-streamingresponse-technical-details } + +在 FastAPI 0.118.0 之前,如果你使用含有 `yield` 的相依,它會在「路徑操作函式」回傳之後、發送回應之前,執行結束程式碼。 + +這樣做的用意是避免在等待回應穿越網路時,比必要的時間更久地占用資源。 + +但這也意味著,如果你回傳的是 `StreamingResponse`,該含有 `yield` 的相依的結束程式碼早已執行完畢。 + +例如,如果你在含有 `yield` 的相依中使用了一個資料庫 session,`StreamingResponse` 在串流資料時將無法使用該 session,因為它已在 `yield` 之後的結束程式碼中被關閉了。 + +這個行為在 0.118.0 被還原,使得 `yield` 之後的結束程式碼會在回應送出之後才被執行。 + +/// info | 資訊 + +如下所見,這與 0.106.0 之前的行為非常類似,但對一些邊界情況做了多項改進與錯誤修正。 + +/// + +#### 需要提早執行結束程式碼的情境 { #use-cases-with-early-exit-code } + +有些特定條件的使用情境,可能會受益於舊行為(在送出回應之前執行含有 `yield` 的相依的結束程式碼)。 + +例如,假設你在含有 `yield` 的相依中只用資料庫 session 來驗證使用者,而這個 session 之後並未在「路徑操作函式」中使用,僅在相依中使用,且回應需要很長時間才會送出,例如一個慢速傳送資料的 `StreamingResponse`,但它並沒有使用資料庫。 + +在這種情況下,資料庫 session 會一直被保留到回應傳送完畢為止,但如果你根本不會用到它,就沒有必要一直持有它。 + +可能會像這樣: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py *} + +結束程式碼(自動關閉 `Session`)在: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +...會在回應完成傳送這些慢速資料後才執行: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +但因為 `generate_stream()` 並未使用資料庫 session,實際上不需要在傳送回應時保持 session 開啟。 + +如果你用的是 SQLModel(或 SQLAlchemy)且有這種特定情境,你可以在不再需要時明確關閉該 session: + +{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *} + +如此一來,該 session 就會釋放資料庫連線,讓其他請求可以使用。 + +如果你有不同的情境,需要從含有 `yield` 的相依中提早結束,請建立一個 GitHub 討論問題,描述你的具體情境,以及為何提早關閉含有 `yield` 的相依對你有幫助。 + +如果有令人信服的案例需要在含有 `yield` 的相依中提前關閉,我會考慮加入一種新的選項,讓你可以選擇性啟用提前關閉。 + +### 含有 `yield` 與 `except` 的相依,技術細節 { #dependencies-with-yield-and-except-technical-details } + +在 FastAPI 0.110.0 之前,如果你使用含有 `yield` 的相依,並且在該相依中用 `except` 捕捉到例外,且沒有再次拋出,那個例外會自動被拋出/轉交給任何例外處理器或內部伺服器錯誤處理器。 + +在 0.110.0 版本中,這被修改以修復沒有處理器(內部伺服器錯誤)而被轉交的例外所造成的未處理記憶體消耗,並使其行為與一般 Python 程式碼一致。 + +### 背景任務與含有 `yield` 的相依,技術細節 { #background-tasks-and-dependencies-with-yield-technical-details } + +在 FastAPI 0.106.0 之前,不可能在 `yield` 之後拋出例外;含有 `yield` 的相依的結束程式碼會在回應送出之後才執行,因此[例外處理器](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} 早就已經跑完了。 + +當初這樣設計主要是為了允許在背景任務中使用由相依「yield」出來的同一組物件,因為結束程式碼會在背景任務結束後才執行。 + +在 FastAPI 0.106.0 中,這個行為被修改,目的是在等待回應穿越網路的期間,不要持有資源。 + +/// tip | 提示 + +此外,背景任務通常是一組獨立的邏輯,應該用自己的資源(例如自己的資料庫連線)來處理。 + +這樣你的程式碼通常會更乾淨。 + +/// + +如果你先前依賴這種行為,現在應該在背景任務本身裡建立所需資源,並且只使用不依賴含有 `yield` 的相依之資源的資料。 + +例如,不要共用同一個資料庫 session,而是在背景任務中建立一個新的資料庫 session,並用這個新的 session 從資料庫取得物件。接著,在呼叫背景任務函式時,不是傳遞資料庫物件本身,而是傳遞該物件的 ID,然後在背景任務函式內再透過這個 ID 取得物件。 diff --git a/docs/zh-hant/docs/advanced/advanced-python-types.md b/docs/zh-hant/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..ffa139427 --- /dev/null +++ b/docs/zh-hant/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# 進階 Python 型別 { #advanced-python-types } + +以下是一些在使用 Python 型別時可能有用的額外想法。 + +## 使用 `Union` 或 `Optional` { #using-union-or-optional } + +如果你的程式碼因某些原因無法使用 `|`,例如不是在型別註記中,而是在像 `response_model=` 之類的參數位置,那麼你可以用 `typing` 中的 `Union` 來取代豎線(`|`)。 + +例如,你可以宣告某個值可以是 `str` 或 `None`: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +在 `typing` 中也有用 `Optional` 宣告某個值可以是 `None` 的速記法。 + +以下是我個人(非常主觀)的建議: + +* 🚨 避免使用 `Optional[SomeType]` +* 改為 ✨ 使用 `Union[SomeType, None]` ✨。 + +兩者等價且底層相同,但我會推薦用 `Union` 而不要用 `Optional`,因為「optional」這個詞看起來會讓人以為這個值是可選的,但實際上它的意思是「可以是 `None`」,即使它不是可選的、仍然是必填。 + +我認為 `Union[SomeType, None]` 更能清楚表達其含義。 + +這只是措辭與命名問題,但這些詞會影響你與團隊成員對程式碼的理解。 + +例如,看看下面這個函式: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +參數 `name` 被標註為 `Optional[str]`,但它並不是可選的;你不能在沒有該參數的情況下呼叫這個函式: + +```Python +say_hi() # 糟了,這會拋出錯誤!😱 +``` + +參數 `name` 仍是必填(不是可選),因為它沒有預設值。不過,`name` 可以接受 `None` 作為值: + +```Python +say_hi(name=None) # 這可行,None 是有效的 🎉 +``` + +好消息是,多數情況下你可以直接用 `|` 來定義型別聯集: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +因此,通常你不必為 `Optional` 與 `Union` 這些名稱操心。😎 diff --git a/docs/zh-hant/docs/advanced/async-tests.md b/docs/zh-hant/docs/advanced/async-tests.md new file mode 100644 index 000000000..cb7430bf6 --- /dev/null +++ b/docs/zh-hant/docs/advanced/async-tests.md @@ -0,0 +1,99 @@ +# 非同步測試 { #async-tests } + +你已經看過如何使用提供的 `TestClient` 來測試你的 FastAPI 應用。到目前為止,你只看到如何撰寫同步測試,沒有使用 `async` 函式。 + +在測試中能使用非同步函式會很有用,例如當你以非同步方式查詢資料庫時。想像你想測試發送請求到 FastAPI 應用,然後在使用非同步資料庫函式庫時,驗證後端是否成功把正確資料寫入資料庫。 + +來看看怎麼做。 + +## pytest.mark.anyio { #pytest-mark-anyio } + +若要在測試中呼叫非同步函式,測試函式本身也必須是非同步的。AnyIO 為此提供了一個好用的外掛,讓我們可以標示某些測試函式以非同步方式執行。 + +## HTTPX { #httpx } + +即使你的 FastAPI 應用使用一般的 `def` 函式而非 `async def`,它在底層仍然是個 `async` 應用。 + +`TestClient` 在內部做了一些魔法,讓我們能在一般的 `def` 測試函式中,使用標準 pytest 來呼叫非同步的 FastAPI 應用。但當我們在非同步函式中使用它時,這個魔法就不再奏效了。也就是說,當以非同步方式執行測試時,就不能在測試函式內使用 `TestClient`。 + +`TestClient` 是建立在 HTTPX 之上,所幸我們可以直接使用它來測試 API。 + +## 範例 { #example } + +作為簡單範例,讓我們考慮與[更大型的應用](../tutorial/bigger-applications.md){.internal-link target=_blank}與[測試](../tutorial/testing.md){.internal-link target=_blank}中描述的類似檔案結構: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +檔案 `main.py` 會是: + +{* ../../docs_src/async_tests/app_a_py310/main.py *} + +檔案 `test_main.py` 會包含針對 `main.py` 的測試,現在可能像這樣: + +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} + +## 執行 { #run-it } + +如常執行測試: + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## 詳解 { #in-detail } + +標記 `@pytest.mark.anyio` 告訴 pytest 這個測試函式應以非同步方式執行: + +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} + +/// tip + +注意,測試函式現在是 `async def`,而不是像使用 `TestClient` 時那樣僅用 `def`。 + +/// + +接著,我們可以用該應用建立 `AsyncClient`,並以 `await` 發送非同步請求。 + +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} + +這等同於: + +```Python +response = client.get('/') +``` + +也就是先前用 `TestClient` 發送請求時所用的寫法。 + +/// tip + +注意,對新的 `AsyncClient` 需搭配 async/await —— 請求是非同步的。 + +/// + +/// warning + +如果你的應用仰賴 lifespan 事件,`AsyncClient` 不會觸發這些事件。若要確保它們被觸發,請使用 florimondmanca/asgi-lifespan 的 `LifespanManager`。 + +/// + +## 其他非同步函式呼叫 { #other-asynchronous-function-calls } + +由於測試函式現在是非同步的,你也可以在測試中呼叫(並 `await`)其他 `async` 函式,和在程式碼其他地方一樣。 + +/// tip + +如果在將非同步呼叫整合進測試時遇到 `RuntimeError: Task attached to a different loop`(例如使用 MongoDB 的 MotorClient 時),請記得:需要事件迴圈的物件只應在非同步函式內實例化,例如在 `@app.on_event("startup")` 回呼中。 + +/// diff --git a/docs/zh-hant/docs/advanced/behind-a-proxy.md b/docs/zh-hant/docs/advanced/behind-a-proxy.md new file mode 100644 index 000000000..71a0557af --- /dev/null +++ b/docs/zh-hant/docs/advanced/behind-a-proxy.md @@ -0,0 +1,466 @@ +# 在代理之後 { #behind-a-proxy } + +在許多情況下,你會在 FastAPI 應用前面放一個「代理」(proxy),例如 Traefik 或 Nginx。 + +這些代理可以處理 HTTPS 憑證等事務。 + +## 代理轉發標頭 { #proxy-forwarded-headers } + +在你的應用前方的「代理」通常會在將請求送給你的「伺服器」之前,臨時加入一些標頭,讓伺服器知道這個請求是由代理「轉發」過來的,並告訴它原始(公開)的 URL,包括網域、是否使用 HTTPS 等。 + +「伺服器」程式(例如透過 FastAPI CLI 啟動的 Uvicorn)能夠解讀這些標頭,然後把該資訊傳遞給你的應用。 + +但出於安全考量,因為伺服器並不知道自己位於受信任的代理之後,所以它不會解讀那些標頭。 + +/// note | 技術細節 + +代理相關的標頭有: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +### 啟用代理轉發標頭 { #enable-proxy-forwarded-headers } + +你可以在啟動 FastAPI CLI 時使用「CLI 選項」`--forwarded-allow-ips`,並傳入允許解析這些轉發標頭的受信任 IP 位址。 + +如果將其設為 `--forwarded-allow-ips="*"`,就會信任所有進來的 IP。 + +如果你的「伺服器」位於受信任的「代理」之後,且只有代理會與它通訊,這樣會讓它接受該「代理」的任何 IP。 + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### 使用 HTTPS 的重新導向 { #redirects-with-https } + +例如,假設你定義了一個「路徑操作(path operation)」`/items/`: + +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} + +如果用戶端嘗試前往 `/items`,預設會被重新導向到 `/items/`。 + +但在設定「CLI 選項」`--forwarded-allow-ips` 之前,它可能會被重新導向到 `http://localhost:8000/items/`。 + +不過,也許你的應用實際部署在 `https://mysuperapp.com`,那重新導向就應該是 `https://mysuperapp.com/items/`。 + +設定 `--proxy-headers` 之後,FastAPI 就能重新導向到正確的位置。😎 + +``` +https://mysuperapp.com/items/ +``` + +/// tip + +如果你想了解更多 HTTPS 的內容,請參考指南[[關於 HTTPS](../deployment/https.md){.internal-link target=_blank}]。 + +/// + +### 代理轉發標頭如何運作 { #how-proxy-forwarded-headers-work } + +以下是「代理」在用戶端與「應用伺服器」之間加入轉發標頭的視覺化示意: + +```mermaid +sequenceDiagram + participant Client + participant Proxy as Proxy/Load Balancer + participant Server as FastAPI Server + + Client->>Proxy: HTTPS Request
Host: mysuperapp.com
Path: /items + + Note over Proxy: Proxy adds forwarded headers + + Proxy->>Server: HTTP Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items + + Note over Server: Server interprets headers
(if --forwarded-allow-ips is set) + + Server->>Proxy: HTTP Response
with correct HTTPS URLs + + Proxy->>Client: HTTPS Response +``` + +「代理」會攔截原始用戶端請求,並在將其轉交給「應用伺服器」之前加入特殊的「轉發」標頭(`X-Forwarded-*`)。 + +這些標頭會保留原始請求中原本會遺失的資訊: + +* X-Forwarded-For:原始用戶端的 IP 位址 +* X-Forwarded-Proto:原始協定(`https`) +* X-Forwarded-Host:原始主機(`mysuperapp.com`) + +當以 `--forwarded-allow-ips` 設定好 FastAPI CLI 後,它會信任並使用這些標頭,例如在重新導向時產生正確的 URL。 + +## 具有移除路徑前綴的代理 { #proxy-with-a-stripped-path-prefix } + +你可能會有一個會為你的應用加入路徑前綴的代理。 + +在這些情況下,你可以使用 `root_path` 來設定你的應用。 + +`root_path` 是 ASGI 規格(FastAPI 透過 Starlette 所遵循的規格)所提供的機制。 + +`root_path` 用來處理這些特定情境。 + +在掛載子應用時,內部也會使用它。 + +這種「具有移除路徑前綴的代理」情況,代表你在程式碼中宣告了 `/app` 的路徑,但你在上面又加了一層(代理),把你的 FastAPI 應用放在像是 `/api/v1` 這樣的路徑底下。 + +在這種情況下,原本的 `/app` 路徑實際上會以 `/api/v1/app` 對外提供服務。 + +即使你的程式碼都是以只有 `/app` 為前提撰寫的。 + +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} + +而代理會在把請求轉交給應用伺服器(多半是透過 FastAPI CLI 啟動的 Uvicorn)之前,動態地「移除」這個「路徑前綴」,讓你的應用仍然以為自己是在 `/app` 底下被提供,這樣你就不需要把整個程式碼都改成包含 `/api/v1` 這個前綴。 + +到目前為止,一切都會如常運作。 + +但是,當你打開整合的文件 UI(前端)時,它會預期在 `/openapi.json` 取得 OpenAPI 模式,而不是在 `/api/v1/openapi.json`。 + +因此,前端(在瀏覽器中執行)會嘗試存取 `/openapi.json`,但無法取得 OpenAPI 模式。 + +因為我們的應用前面有一個將路徑前綴設定為 `/api/v1` 的代理,所以前端需要從 `/api/v1/openapi.json` 取得 OpenAPI 模式。 + +```mermaid +graph LR + +browser("Browser") +proxy["Proxy on http://0.0.0.0:9999/api/v1/app"] +server["Server on http://127.0.0.1:8000/app"] + +browser --> proxy +proxy --> server +``` + +/// tip + +IP `0.0.0.0` 通常用來表示程式在該機器/伺服器上的所有可用 IP 上監聽。 + +/// + +文件 UI 也需要 OpenAPI 模式宣告此 API 的 `server` 位在 `/api/v1`(代理之後)。例如: + +```JSON hl_lines="4-8" +{ + "openapi": "3.1.0", + // 其他內容 + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + // 其他內容 + } +} +``` + +在這個例子中,「Proxy」可以是 **Traefik**。而伺服器可以是以 **Uvicorn** 啟動的 FastAPI CLI,運行你的 FastAPI 應用。 + +### 提供 `root_path` { #providing-the-root-path } + +要達成這一點,你可以像這樣使用命令列選項 `--root-path`: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +如果你使用 Hypercorn,它也有 `--root-path` 這個選項。 + +/// note | 技術細節 + +ASGI 規格針對這種用例定義了 `root_path`。 + +而命令列選項 `--root-path` 就是提供該 `root_path`。 + +/// + +### 檢視目前的 `root_path` { #checking-the-current-root-path } + +你可以在每個請求中取得應用使用的 `root_path`,它是 `scope` 字典的一部分(ASGI 規格的一部分)。 + +這裡我們把它放到回傳訊息中只是為了示範。 + +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} + +接著,如果你用下列方式啟動 Uvicorn: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +回應會像是: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +### 在 FastAPI 應用中設定 `root_path` { #setting-the-root-path-in-the-fastapi-app } + +或者,如果你無法提供像 `--root-path` 這樣的命令列選項(或等效方式),你可以在建立 FastAPI 應用時設定 `root_path` 參數: + +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} + +把 `root_path` 傳給 `FastAPI` 等同於在 Uvicorn 或 Hypercorn 上使用命令列選項 `--root-path`。 + +### 關於 `root_path` { #about-root-path } + +請記住,伺服器(Uvicorn)除了把 `root_path` 傳給應用之外,不會拿它做其他用途。 + +但如果你用瀏覽器前往 http://127.0.0.1:8000/app,你會看到一般的回應: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +因此,它不會預期被以 `http://127.0.0.1:8000/api/v1/app` 的方式存取。 + +Uvicorn 會預期代理以 `http://127.0.0.1:8000/app` 來存取 Uvicorn,而由代理負責在上層加上額外的 `/api/v1` 前綴。 + +## 關於「移除路徑前綴」的代理 { #about-proxies-with-a-stripped-path-prefix } + +請記住,具有「移除路徑前綴」的代理只是其中一種設定方式。 + +在許多情況下,預設可能是不移除路徑前綴。 + +在那種情況(沒有移除路徑前綴)下,代理會監聽像是 `https://myawesomeapp.com`,然後當瀏覽器前往 `https://myawesomeapp.com/api/v1/app`,而你的伺服器(例如 Uvicorn)在 `http://127.0.0.1:8000` 監聽時,該代理(不移除路徑前綴)就會以同樣的路徑去存取 Uvicorn:`http://127.0.0.1:8000/api/v1/app`。 + +## 在本機使用 Traefik 測試 { #testing-locally-with-traefik } + +你可以很容易地用 Traefik 在本機跑一個「移除路徑前綴」的測試。 + +下載 Traefik,它是一個單一的執行檔,你可以解壓縮後直接在終端機執行。 + +然後建立一個 `traefik.toml` 檔案,內容如下: + +```TOML hl_lines="3" +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +這告訴 Traefik 監聽 9999 埠,並使用另一個檔案 `routes.toml`。 + +/// tip + +我們使用 9999 埠而不是標準的 HTTP 80 埠,這樣你就不需要以管理員(`sudo`)權限來執行。 + +/// + +接著建立另一個 `routes.toml` 檔案: + +```TOML hl_lines="5 12 20" +[http] + [http.middlewares] + + [http.middlewares.api-stripprefix.stripPrefix] + prefixes = ["/api/v1"] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + middlewares = ["api-stripprefix"] + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +這個檔案把 Traefik 設定為使用 `/api/v1` 的路徑前綴。 + +然後 Traefik 會把它的請求轉發到在 `http://127.0.0.1:8000` 上運行的 Uvicorn。 + +現在啟動 Traefik: + +
+ +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml +``` + +
+ +然後啟動你的應用,使用 `--root-path` 選項: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### 檢查回應 { #check-the-responses } + +現在,如果你前往 Uvicorn 的埠:http://127.0.0.1:8000/app,你會看到一般的回應: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +/// tip + +注意,儘管你是用 `http://127.0.0.1:8000/app` 存取,它仍然顯示從 `--root-path` 選項取得的 `root_path` 為 `/api/v1`。 + +/// + +接著打開使用 Traefik 埠且包含路徑前綴的 URL:http://127.0.0.1:9999/api/v1/app。 + +我們會得到相同的回應: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +但這次是在由代理提供的、帶有前綴路徑的 URL:`/api/v1`。 + +當然,這裡的重點是大家都會透過代理來存取應用,所以帶有 `/api/v1` 路徑前綴的版本才是「正確」的。 + +而沒有路徑前綴的版本(`http://127.0.0.1:8000/app`),也就是直接由 Uvicorn 提供的,應該只給「代理」(Traefik)來存取。 + +這展示了代理(Traefik)如何使用路徑前綴,以及伺服器(Uvicorn)如何使用 `--root-path` 選項提供的 `root_path`。 + +### 檢查文件 UI { #check-the-docs-ui } + +接下來是有趣的部分。✨ + +「正式」的存取方式應該是透過我們定義了路徑前綴的代理。因此,如我們預期,如果你直接透過 Uvicorn 供應的文件 UI、而 URL 中沒有該路徑前綴,那它不會運作,因為它預期要透過代理來存取。 + +你可以在 http://127.0.0.1:8000/docs 檢查: + + + +但如果我們改用「正式」的 URL,也就是使用埠號 `9999` 的代理、並在 `/api/v1/docs`,它就能正確運作了!🎉 + +你可以在 http://127.0.0.1:9999/api/v1/docs 檢查: + + + +正如我們所希望的那樣。✔️ + +這是因為 FastAPI 使用這個 `root_path` 來在 OpenAPI 中建立預設的 `server`,其 URL 就是 `root_path` 所提供的值。 + +## 其他 servers { #additional-servers } + +/// warning + +這是更進階的用法。你可以選擇略過。 + +/// + +預設情況下,FastAPI 會在 OpenAPI 模式中建立一個 `server`,其 URL 為 `root_path`。 + +但你也可以另外提供其他 `servers`,例如你想要用「同一份」文件 UI 來與測試(staging)與正式(production)環境互動。 + +如果你傳入自訂的 `servers` 清單,且同時存在 `root_path`(因為你的 API 位於代理之後),FastAPI 會在清單開頭插入一個 `server`,其 URL 為該 `root_path`。 + +例如: + +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} + +將會產生如下的 OpenAPI 模式: + +```JSON hl_lines="5-7" +{ + "openapi": "3.1.0", + // 其他內容 + "servers": [ + { + "url": "/api/v1" + }, + { + "url": "https://stag.example.com", + "description": "Staging environment" + }, + { + "url": "https://prod.example.com", + "description": "Production environment" + } + ], + "paths": { + // 其他內容 + } +} +``` + +/// tip + +注意自動產生的 server,其 `url` 值為 `/api/v1`,取自 `root_path`。 + +/// + +在位於 http://127.0.0.1:9999/api/v1/docs 的文件 UI 中看起來會像這樣: + + + +/// tip + +文件 UI 會與你所選擇的 server 互動。 + +/// + +/// note | 技術細節 + +OpenAPI 規格中的 `servers` 屬性是可選的。 + +如果你沒有指定 `servers` 參數,且 `root_path` 等於 `/`,則在產生的 OpenAPI 模式中會完全省略 `servers` 屬性(預設行為),這等同於只有一個 `url` 值為 `/` 的 server。 + +/// + +### 停用從 `root_path` 自動加入的 server { #disable-automatic-server-from-root-path } + +如果你不希望 FastAPI 使用 `root_path` 自動加入一個 server,你可以使用參數 `root_path_in_servers=False`: + +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} + +這樣它就不會被包含在 OpenAPI 模式中。 + +## 掛載子應用 { #mounting-a-sub-application } + +如果你需要在同時使用具有 `root_path` 的代理時,掛載一個子應用(如[[子應用 - 掛載](sub-applications.md){.internal-link target=_blank}]中所述),可以像平常一樣操作,正如你所預期的那樣。 + +FastAPI 會在內部智慧地使用 `root_path`,所以一切都能順利運作。✨ diff --git a/docs/zh-hant/docs/advanced/custom-response.md b/docs/zh-hant/docs/advanced/custom-response.md new file mode 100644 index 000000000..b723aa82f --- /dev/null +++ b/docs/zh-hant/docs/advanced/custom-response.md @@ -0,0 +1,312 @@ +# 自訂回應——HTML、串流、檔案與其他 { #custom-response-html-stream-file-others } + +預設情況下,**FastAPI** 會使用 `JSONResponse` 傳回回應。 + +你可以像在[直接回傳 Response](response-directly.md){.internal-link target=_blank} 中所示,直接回傳一個 `Response` 來覆寫它。 + +但如果你直接回傳一個 `Response`(或其子類別,如 `JSONResponse`),資料將不會被自動轉換(即使你宣告了 `response_model`),而且文件也不會自動產生(例如,在產生的 OpenAPI 中包含 HTTP 標頭 `Content-Type` 的特定「media type」)。 + +你也可以在「路徑操作裝飾器」中使用 `response_class` 參數,宣告要使用的 `Response`(例如任意 `Response` 子類別)。 + +你從「路徑操作函式」回傳的內容,會被放進該 `Response` 中。 + +若該 `Response` 的 media type 是 JSON(`application/json`),像 `JSONResponse` 與 `UJSONResponse`,則你回傳的資料會自動以你在「路徑操作裝飾器」中宣告的 Pydantic `response_model` 進行轉換(與過濾)。 + +/// note + +若你使用的回應類別沒有 media type,FastAPI 會假設你的回應沒有內容,因此不會在產生的 OpenAPI 文件中記錄回應格式。 + +/// + +## 使用 `ORJSONResponse` { #use-orjsonresponse } + +例如,若你在追求效能,你可以安裝並使用 `orjson`,並將回應設為 `ORJSONResponse`。 + +匯入你想使用的 `Response` 類別(子類),並在「路徑操作裝飾器」中宣告它。 + +對於大型回應,直接回傳 `Response` 會比回傳 `dict` 快得多。 + +這是因為預設情況下,FastAPI 會檢查每個項目並確認它能被序列化為 JSON,使用與教學中說明的相同[JSON 相容編碼器](../tutorial/encoder.md){.internal-link target=_blank}。這使你可以回傳「任意物件」,例如資料庫模型。 + +但如果你確定你回傳的內容「可以用 JSON 序列化」,你可以直接將它傳給回應類別,避免 FastAPI 在把你的回傳內容交給回應類別之前,先經過 `jsonable_encoder` 所帶來的額外開銷。 + +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} + +/// info + +參數 `response_class` 也會用來定義回應的「media type」。 + +在此情況下,HTTP 標頭 `Content-Type` 會被設為 `application/json`。 + +而且它會以此形式被記錄到 OpenAPI 中。 + +/// + +/// tip + +`ORJSONResponse` 只在 FastAPI 中可用,在 Starlette 中不可用。 + +/// + +## HTML 回應 { #html-response } + +要直接從 **FastAPI** 回傳 HTML,使用 `HTMLResponse`。 + +- 匯入 `HTMLResponse`。 +- 在「路徑操作裝飾器」中,將 `HTMLResponse` 傳給 `response_class` 參數。 + +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} + +/// info + +參數 `response_class` 也會用來定義回應的「media type」。 + +在此情況下,HTTP 標頭 `Content-Type` 會被設為 `text/html`。 + +而且它會以此形式被記錄到 OpenAPI 中。 + +/// + +### 回傳 `Response` { #return-a-response } + +如[直接回傳 Response](response-directly.md){.internal-link target=_blank} 所示,你也可以在「路徑操作」中直接回傳以覆寫回應。 + +上面的相同範例,回傳 `HTMLResponse`,可以像這樣: + +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} + +/// warning + +由你的「路徑操作函式」直接回傳的 `Response` 不會被記錄進 OpenAPI(例如不會記錄 `Content-Type`),也不會出現在自動產生的互動式文件中。 + +/// + +/// info + +當然,實際的 `Content-Type` 標頭、狀態碼等,會來自你回傳的 `Response` 物件。 + +/// + +### 在 OpenAPI 中文件化並覆寫 `Response` { #document-in-openapi-and-override-response } + +如果你想在函式內覆寫回應,同時又要在 OpenAPI 中記錄「media type」,你可以同時使用 `response_class` 參數並回傳一個 `Response` 物件。 + +此時,`response_class` 只會用於記錄該 OpenAPI「路徑操作」,而你回傳的 `Response` 將會如實使用。 + +#### 直接回傳 `HTMLResponse` { #return-an-htmlresponse-directly } + +例如,可能會像這樣: + +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} + +在這個例子中,函式 `generate_html_response()` 已經產生並回傳了一個 `Response`,而不是把 HTML 當作 `str` 回傳。 + +透過回傳 `generate_html_response()` 的結果,你其實已經回傳了一個 `Response`,這會覆寫 **FastAPI** 的預設行為。 + +但因為你同時也在 `response_class` 中傳入了 `HTMLResponse`,**FastAPI** 便能在 OpenAPI 與互動式文件中,將其以 `text/html` 的 HTML 形式記錄: + + + +## 可用的回應 { #available-responses } + +以下是一些可用的回應類別。 + +記得你可以用 `Response` 回傳其他任何東西,甚至建立自訂的子類別。 + +/// note | 技術細節 + +你也可以使用 `from starlette.responses import HTMLResponse`。 + +**FastAPI** 將 `starlette.responses` 以 `fastapi.responses` 提供給你(開發者)做為方便之用。但大多數可用的回應其實直接來自 Starlette。 + +/// + +### `Response` { #response } + +主要的 `Response` 類別,其他回應皆繼承自它。 + +你也可以直接回傳它。 + +它接受以下參數: + +- `content` - `str` 或 `bytes`。 +- `status_code` - `int` 類型的 HTTP 狀態碼。 +- `headers` - 由字串組成的 `dict`。 +- `media_type` - 描述 media type 的 `str`。例如 `"text/html"`。 + +FastAPI(實際上是 Starlette)會自動包含 Content-Length 標頭。也會根據 `media_type`(並為文字型別附加 charset)包含 Content-Type 標頭。 + +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} + +### `HTMLResponse` { #htmlresponse } + +接收文字或位元組並回傳 HTML 回應,如上所述。 + +### `PlainTextResponse` { #plaintextresponse } + +接收文字或位元組並回傳純文字回應。 + +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} + +### `JSONResponse` { #jsonresponse } + +接收資料並回傳 `application/json` 編碼的回應。 + +這是 **FastAPI** 的預設回應,如上所述。 + +### `ORJSONResponse` { #orjsonresponse } + +使用 `orjson` 的快速替代 JSON 回應,如上所述。 + +/// info + +這需要安裝 `orjson`,例如使用 `pip install orjson`。 + +/// + +### `UJSONResponse` { #ujsonresponse } + +使用 `ujson` 的替代 JSON 回應。 + +/// info + +這需要安裝 `ujson`,例如使用 `pip install ujson`。 + +/// + +/// warning + +`ujson` 在處理某些邊界情況時,沒那麼嚴謹,較 Python 內建實作更「隨意」。 + +/// + +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} + +/// tip + +`ORJSONResponse` 可能是更快的替代方案。 + +/// + +### `RedirectResponse` { #redirectresponse } + +回傳一個 HTTP 重新導向。預設使用 307 狀態碼(Temporary Redirect)。 + +你可以直接回傳 `RedirectResponse`: + +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} + +--- + +或者你可以在 `response_class` 參數中使用它: + +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} + +若這麼做,你就可以在「路徑操作函式」中直接回傳 URL。 + +在此情況下,所使用的 `status_code` 會是 `RedirectResponse` 的預設值 `307`。 + +--- + +你也可以同時搭配 `status_code` 與 `response_class` 參數: + +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} + +### `StreamingResponse` { #streamingresponse } + +接收一個 async 產生器或一般的產生器/疊代器,並以串流方式傳送回應本文。 + +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} + +#### 對「類檔案物件」使用 `StreamingResponse` { #using-streamingresponse-with-file-like-objects } + +如果你有一個類檔案(file-like)物件(例如 `open()` 回傳的物件),你可以建立一個產生器函式來疊代該類檔案物件。 + +如此一來,你不必先把它全部讀進記憶體,就能將那個產生器函式傳給 `StreamingResponse` 並回傳。 + +這也包含許多用於雲端儲存、影像/影音處理等的函式庫。 + +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} + +1. 這是產生器函式。因為它內含 `yield` 陳述式,所以是「產生器函式」。 +2. 透過 `with` 區塊,我們確保在產生器函式結束後關閉類檔案物件。因此,在完成傳送回應後就會關閉。 +3. 這個 `yield from` 告訴函式去疊代名為 `file_like` 的東西。對於每個被疊代到的部分,就把該部分當作此產生器函式(`iterfile`)的輸出進行 `yield`。 + + 因此,這是一個把「生成」工作在內部轉交給其他東西的產生器函式。 + + 透過這樣做,我們可以把它放進 `with` 區塊,藉此確保在完成後關閉類檔案物件。 + +/// tip + +注意,這裡我們使用的是標準的 `open()`,它不支援 `async` 與 `await`,因此我們用一般的 `def` 來宣告路徑操作。 + +/// + +### `FileResponse` { #fileresponse } + +以非同步串流方式將檔案作為回應。 + +它在初始化時所需的參數與其他回應型別不同: + +- `path` - 要串流的檔案路徑。 +- `headers` - 要包含的自訂標頭,字典形式。 +- `media_type` - 描述 media type 的字串。若未設定,將根據檔名或路徑推斷 media type。 +- `filename` - 若設定,會包含在回應的 `Content-Disposition` 中。 + +檔案回應會包含適當的 `Content-Length`、`Last-Modified` 與 `ETag` 標頭。 + +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} + +你也可以使用 `response_class` 參數: + +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} + +在此情況下,你可以在「路徑操作函式」中直接回傳檔案路徑。 + +## 自訂回應類別 { #custom-response-class } + +你可以建立自己的自訂回應類別,繼承自 `Response` 並加以使用。 + +例如,假設你要使用 `orjson`,但想套用一些未包含在 `ORJSONResponse` 類別中的自訂設定。 + +假設你想回傳縮排且格式化的 JSON,因此要使用 orjson 選項 `orjson.OPT_INDENT_2`。 + +你可以建立 `CustomORJSONResponse`。你主要需要做的是建立一個 `Response.render(content)` 方法,將內容以 `bytes` 回傳: + +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} + +現在,不再是回傳: + +```json +{"message": "Hello World"} +``` + +……這個回應會回傳: + +```json +{ + "message": "Hello World" +} +``` + +當然,你大概能找到比格式化 JSON 更好的方式來利用這個能力。😉 + +## 預設回應類別 { #default-response-class } + +在建立 **FastAPI** 類別實例或 `APIRouter` 時,你可以指定預設要使用哪個回應類別。 + +用來設定的是 `default_response_class` 參數。 + +在下面的例子中,**FastAPI** 會在所有「路徑操作」中預設使用 `ORJSONResponse`,而不是 `JSONResponse`。 + +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} + +/// tip + +你仍然可以在「路徑操作」中像以前一樣覆寫 `response_class`。 + +/// + +## 其他文件化選項 { #additional-documentation } + +你也可以在 OpenAPI 中使用 `responses` 宣告 media type 與其他許多細節:[在 OpenAPI 中的額外回應](additional-responses.md){.internal-link target=_blank}。 diff --git a/docs/zh-hant/docs/advanced/dataclasses.md b/docs/zh-hant/docs/advanced/dataclasses.md new file mode 100644 index 000000000..d586bd684 --- /dev/null +++ b/docs/zh-hant/docs/advanced/dataclasses.md @@ -0,0 +1,87 @@ +# 使用 Dataclasses { #using-dataclasses } + +FastAPI 建立在 **Pydantic** 之上,我之前示範過如何使用 Pydantic 模型來宣告請求與回應。 + +但 FastAPI 也同樣支援以相同方式使用 `dataclasses`: + +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} + +這之所以可行,要感謝 **Pydantic**,因為它 內建支援 `dataclasses`。 + +所以,即使上面的程式碼沒有明確使用 Pydantic,FastAPI 仍會使用 Pydantic 將那些標準的 dataclass 轉換為 Pydantic 版本的 dataclass。 + +而且當然一樣支援: + +- 資料驗證 +- 資料序列化 +- 資料文件化等 + +它的運作方式與 Pydantic 模型相同;實際上,底層就是透過 Pydantic 達成的。 + +/// info + +請記得,dataclass 無法做到 Pydantic 模型能做的一切。 + +所以你可能仍然需要使用 Pydantic 模型。 + +但如果你手邊剛好有一堆 dataclass,這是個不錯的小技巧,可以用來用 FastAPI 驅動一個 Web API。🤓 + +/// + +## 在 `response_model` 中使用 Dataclasses { #dataclasses-in-response-model } + +你也可以在 `response_model` 參數中使用 `dataclasses`: + +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} + +該 dataclass 會自動轉換為 Pydantic 的 dataclass。 + +如此一來,其結構描述(schema)會顯示在 API 文件介面中: + + + +## 巢狀資料結構中的 Dataclasses { #dataclasses-in-nested-data-structures } + +你也可以將 `dataclasses` 與其他型別註記結合,建立巢狀的資料結構。 + +在某些情況下,你可能仍需要使用 Pydantic 版本的 `dataclasses`。例如,當自動產生的 API 文件出現錯誤時。 + +這種情況下,你可以把標準的 `dataclasses` 直接換成 `pydantic.dataclasses`,它是可直接替換(drop-in replacement)的: + +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} + +1. 我們仍然從標準的 `dataclasses` 匯入 `field`。 +2. `pydantic.dataclasses` 是 `dataclasses` 的可直接替換版本。 +3. `Author` dataclass 內含一個 `Item` dataclass 的清單。 +4. `Author` dataclass 被用作 `response_model` 參數。 +5. 你可以將其他標準型別註記與 dataclass 一起用作請求本文。 + + 在此例中,它是 `Item` dataclass 的清單。 +6. 這裡我們回傳一個字典,其中的 `items` 是一個 dataclass 清單。 + + FastAPI 仍能將資料序列化為 JSON。 +7. 這裡 `response_model` 使用的是「`Author` dataclass 的清單」這種型別註記。 + + 同樣地,你可以把 `dataclasses` 與標準型別註記組合使用。 +8. 注意這個「路徑操作函式」使用的是一般的 `def` 而非 `async def`。 + + 一如往常,在 FastAPI 中你可以視需要混用 `def` 與 `async def`。 + + 如果需要複習何時用哪個,請參考文件中關於 [`async` 與 `await`](../async.md#in-a-hurry){.internal-link target=_blank} 的章節「In a hurry?」。 +9. 這個「路徑操作函式」回傳的不是 dataclass(雖然也可以),而是一個包含內部資料的字典清單。 + + FastAPI 會使用 `response_model` 參數(其中包含 dataclass)來轉換回應。 + +你可以把 `dataclasses` 與其他型別註記以多種方式組合,形成複雜的資料結構。 + +查看上面程式碼中的註解提示以了解更具體的細節。 + +## 延伸閱讀 { #learn-more } + +你也可以將 `dataclasses` 與其他 Pydantic 模型結合、從它們繼承、把它們包含進你的自訂模型等。 + +想了解更多,請參考 Pydantic 關於 dataclasses 的文件。 + +## 版本 { #version } + +自 FastAPI 版本 `0.67.0` 起可用。🔖 diff --git a/docs/zh-hant/docs/advanced/events.md b/docs/zh-hant/docs/advanced/events.md new file mode 100644 index 000000000..e5c0afe48 --- /dev/null +++ b/docs/zh-hant/docs/advanced/events.md @@ -0,0 +1,165 @@ +# 生命週期事件 { #lifespan-events } + +你可以定義在應用程式**啟動**之前要執行的邏輯(程式碼)。也就是說,這段程式碼會在應用開始接收請求**之前**、**只執行一次**。 + +同樣地,你也可以定義在應用程式**關閉**時要執行的邏輯(程式碼)。在這種情況下,這段程式碼會在處理了**許多請求**之後、**只執行一次**。 + +因為這些程式碼分別在應用開始接收請求**之前**與**完成**處理請求之後執行,所以涵蓋了整個應用的**生命週期**(「lifespan」這個詞稍後會很重要 😉)。 + +這對於為整個應用設定需要**共用**於多個請求的**資源**,以及在之後進行**清理**,非常有用。比如資料庫連線池、或載入一個共用的機器學習模型。 + +## 使用情境 { #use-case } + +先從一個**使用情境**開始,然後看看如何用這個機制解決。 + +想像你有一些要用來處理請求的**機器學習模型**。🤖 + +同一組模型會在多個請求間共用,所以不是每個請求或每個使用者各有一個模型。 + +再想像一下,載入模型**需要一段時間**,因為它必須從**磁碟**讀取大量資料。所以你不想在每個請求都做一次。 + +你可以在模組/檔案的最上層載入,但這也表示即使只是要跑一個簡單的自動化測試,也會去**載入模型**,導致測試**變慢**,因為它得等模型載入完才能執行與模型無關的程式碼部分。 + +我們要解決的正是這件事:在開始處理請求之前再載入模型,但只在應用程式即將開始接收請求時載入,而不是在匯入程式碼時就載入。 + +## 生命週期(Lifespan) { #lifespan } + +你可以使用 `FastAPI` 應用的 `lifespan` 參數,搭配「context manager」(稍後會示範),來定義這些 *startup* 與 *shutdown* 邏輯。 + +先看一個例子,接著再深入說明。 + +我們建立一個帶有 `yield` 的非同步函式 `lifespan()`,如下: + +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} + +這裡我們透過在 `yield` 之前把(假的)模型函式放進機器學習模型的字典中,來模擬昂貴的 *startup* 載入模型操作。這段程式會在應用**開始接收請求之前**執行,也就是 *startup* 階段。 + +接著,在 `yield` 之後,我們卸載模型。這段程式會在應用**完成處理請求之後**、也就是 *shutdown* 前執行。這可以用來釋放資源,例如記憶體或 GPU。 + +/// tip + +`shutdown` 會在你**停止**應用程式時發生。 + +也許你要啟動新版本,或只是不想再跑它了。🤷 + +/// + +### Lifespan 函式 { #lifespan-function } + +首先要注意的是,我們定義了一個帶有 `yield` 的 async 函式。這和帶有 `yield` 的依賴(Dependencies)非常相似。 + +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} + +函式在 `yield` 之前的部分,會在應用啟動前先執行。 + +`yield` 之後的部分,會在應用結束後再執行。 + +### 非同步內容管理器(Async Context Manager) { #async-context-manager } + +你會看到這個函式被 `@asynccontextmanager` 裝飾。 + +它會把函式轉換成所謂的「**非同步內容管理器(async context manager)**」。 + +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} + +Python 中的**內容管理器(context manager)**可以用在 `with` 陳述式中,例如 `open()` 可以作為內容管理器使用: + +```Python +with open("file.txt") as file: + file.read() +``` + +在較新的 Python 版本中,也有**非同步內容管理器**。你可以用 `async with` 來使用它: + +```Python +async with lifespan(app): + await do_stuff() +``` + +當你像上面那樣建立一個內容管理器或非同步內容管理器時,在進入 `with` 區塊之前,會先執行 `yield` 之前的程式碼;離開 `with` 區塊之後,會執行 `yield` 之後的程式碼。 + +在我們的範例中,並不是直接用它,而是把它傳給 FastAPI 來使用。 + +`FastAPI` 應用的 `lifespan` 參數需要一個**非同步內容管理器**,所以我們可以把剛寫好的 `lifespan` 非同步內容管理器傳給它。 + +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} + +## 替代事件(已棄用) { #alternative-events-deprecated } + +/// warning + +目前建議使用上面所述,透過 `FastAPI` 應用的 `lifespan` 參數來處理 *startup* 與 *shutdown*。如果你提供了 `lifespan` 參數,`startup` 與 `shutdown` 事件處理器將不會被呼叫。要嘛全用 `lifespan`,要嘛全用事件,不能同時混用。 + +你大概可以直接跳過這一節。 + +/// + +也有另一種方式可以定義在 *startup* 與 *shutdown* 期間要執行的邏輯。 + +你可以定義事件處理器(函式)來在應用啟動前或關閉時執行。 + +這些函式可以用 `async def` 或一般的 `def` 宣告。 + +### `startup` 事件 { #startup-event } + +要加入一個在應用啟動前執行的函式,使用事件 `"startup"` 來宣告: + +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} + +在這個例子中,`startup` 事件處理器函式會用一些值來初始化 items 的「資料庫」(其實就是個 `dict`)。 + +你可以註冊多個事件處理函式。 + +而且在所有 `startup` 事件處理器都完成之前,你的應用不會開始接收請求。 + +### `shutdown` 事件 { #shutdown-event } + +要加入一個在應用關閉時執行的函式,使用事件 `"shutdown"` 來宣告: + +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} + +在這裡,`shutdown` 事件處理器函式會把一行文字 `"Application shutdown"` 寫入檔案 `log.txt`。 + +/// info + +在 `open()` 函式中,`mode="a"` 表示「append(附加)」;也就是說,這行文字會加在檔案現有內容之後,而不會覆寫先前的內容。 + +/// + +/// tip + +注意這裡我們使用的是標準 Python 的 `open()` 函式來操作檔案。 + +這涉及 I/O(輸入/輸出),也就是需要「等待」資料寫入磁碟。 + +但 `open()` 並不使用 `async` 與 `await`。 + +所以我們用一般的 `def` 來宣告事件處理器,而不是 `async def`。 + +/// + +### 同時使用 `startup` 與 `shutdown` { #startup-and-shutdown-together } + +你的 *startup* 與 *shutdown* 邏輯很可能是相關聯的:你可能會先啟動某個東西再把它結束、先取得資源再釋放它,等等。 + +如果把它們拆成兩個彼此不共享邏輯或變數的獨立函式,會比較麻煩,你得把值存在全域變數或用其他技巧。 + +因此,現在建議改用上面介紹的 `lifespan`。 + +## 技術細節 { #technical-details } + +給有興趣鑽研的同好一點技術細節。🤓 + +在底層的 ASGI 技術規範中,這屬於 Lifespan Protocol 的一部分,並定義了 `startup` 與 `shutdown` 兩種事件。 + +/// info + +你可以在 Starlette 的 Lifespan 文件 讀到更多關於 Starlette `lifespan` 處理器的資訊。 + +也包含如何處理可在程式其他區域使用的 lifespan 狀態。 + +/// + +## 子應用程式 { #sub-applications } + +🚨 請記住,這些生命週期事件(startup 與 shutdown)只會在主應用程式上執行,不會在[子應用程式 - 掛載](sub-applications.md){.internal-link target=_blank}上執行。 diff --git a/docs/zh-hant/docs/advanced/generate-clients.md b/docs/zh-hant/docs/advanced/generate-clients.md new file mode 100644 index 000000000..8d374566c --- /dev/null +++ b/docs/zh-hant/docs/advanced/generate-clients.md @@ -0,0 +1,208 @@ +# 產生 SDK { #generating-sdks } + +由於 **FastAPI** 建立在 **OpenAPI** 規格之上,其 API 能以許多工具都能理解的標準格式來描述。 + +這讓你能輕鬆產生最新的**文件**、多語言的用戶端程式庫(**SDKs**),以及與程式碼保持同步的**測試**或**自動化工作流程**。 + +在本指南中,你將學會如何為你的 FastAPI 後端產生 **TypeScript SDK**。 + +## 開源 SDK 產生器 { #open-source-sdk-generators } + +其中一個相當萬用的選擇是 OpenAPI Generator,它支援**多種程式語言**,並能從你的 OpenAPI 規格產生 SDK。 + +針對 **TypeScript 用戶端**,Hey API 是專門打造的解決方案,為 TypeScript 生態系提供最佳化的體驗。 + +你可以在 OpenAPI.Tools 找到更多 SDK 產生器。 + +/// tip + +FastAPI 會自動產生 **OpenAPI 3.1** 規格,因此你使用的任何工具都必須支援這個版本。 + +/// + +## 來自 FastAPI 贊助商的 SDK 產生器 { #sdk-generators-from-fastapi-sponsors } + +本節重點介紹由贊助 FastAPI 的公司提供的**創投支持**與**公司維運**的解決方案。這些產品在高品質的自動產生 SDK 之外,還提供**額外功能**與**整合**。 + +透過 ✨ [**贊助 FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨,這些公司幫助確保框架與其**生態系**維持健康且**永續**。 + +他們的贊助也展現對 FastAPI **社群**(你)的高度承諾,不僅關心提供**優良服務**,也支持 **FastAPI** 作為一個**穩健且蓬勃的框架**。🙇 + +例如,你可以嘗試: + +* Speakeasy +* Stainless +* liblab + +其中有些方案也可能是開源或提供免費方案,讓你不需財務承諾就能試用。其他商業的 SDK 產生器也不少,你可以在網路上找到。🤓 + +## 建立 TypeScript SDK { #create-a-typescript-sdk } + +先從一個簡單的 FastAPI 應用開始: + +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} + +注意這些 *路徑操作* 為請求與回應的有效載荷定義了所用的模型,使用了 `Item` 與 `ResponseMessage` 這兩個模型。 + +### API 文件 { #api-docs } + +如果你前往 `/docs`,你會看到其中包含了請求要送出的資料與回應接收的資料之**結構(schemas)**: + + + +你之所以能看到這些結構,是因為它們在應用內以模型宣告了。 + +這些資訊都在應用的 **OpenAPI 結構**中,並顯示在 API 文件裡。 + +同樣包含在 OpenAPI 中的模型資訊,也可以用來**產生用戶端程式碼**。 + +### Hey API { #hey-api } + +當我們有含模型的 FastAPI 應用後,就能用 Hey API 來產生 TypeScript 用戶端。最快的方法是透過 npx: + +```sh +npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client +``` + +這會在 `./src/client` 產生一個 TypeScript SDK。 + +你可以在他們的網站了解如何安裝 `@hey-api/openapi-ts`,以及閱讀產生的輸出內容。 + +### 使用 SDK { #using-the-sdk } + +現在你可以匯入並使用用戶端程式碼。大致看起來會像這樣,你會發現方法有自動完成: + + + +你也會對要送出的有效載荷獲得自動完成: + + + +/// tip + +注意 `name` 與 `price` 的自動完成,這是由 FastAPI 應用中的 `Item` 模型所定義。 + +/// + +你在送出的資料上也會看到行內錯誤: + + + +回應物件同樣有自動完成: + + + +## 含標籤的 FastAPI 應用 { #fastapi-app-with-tags } + +在許多情況下,你的 FastAPI 應用會更大,你可能會用標籤將不同群組的 *路徑操作* 分開。 + +例如,你可以有一個 **items** 區塊與另一個 **users** 區塊,並透過標籤區分: + +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} + +### 使用標籤產生 TypeScript 用戶端 { #generate-a-typescript-client-with-tags } + +若你為使用標籤的 FastAPI 應用產生用戶端,產生器通常也會依標籤將用戶端程式碼分開。 + +如此一來,用戶端程式碼就能有條理地正確分組與排列: + + + +在此例中,你會有: + +* `ItemsService` +* `UsersService` + +### 用戶端方法名稱 { #client-method-names } + +目前像 `createItemItemsPost` 這樣的產生方法名稱看起來不太俐落: + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +……那是因為用戶端產生器對每個 *路徑操作* 都使用 OpenAPI 內部的**操作 ID(operation ID)**。 + +OpenAPI 要求每個操作 ID 在所有 *路徑操作* 之間必須唯一,因此 FastAPI 會用**函式名稱**、**路徑**與 **HTTP 方法/操作**來產生該操作 ID,如此便能確保操作 ID 的唯一性。 + +接下來我會示範如何把它變得更好看。🤓 + +## 自訂 Operation ID 與更好的方法名稱 { #custom-operation-ids-and-better-method-names } + +你可以**修改**這些操作 ID 的**產生方式**,讓它們更簡潔,並在用戶端中得到**更簡潔的方法名稱**。 + +在這種情況下,你需要用其他方式確保每個操作 ID 都是**唯一**的。 + +例如,你可以確保每個 *路徑操作* 都有標籤,接著根據**標籤**與 *路徑操作* 的**名稱**(函式名稱)來產生操作 ID。 + +### 自訂唯一 ID 產生函式 { #custom-generate-unique-id-function } + +FastAPI 會為每個 *路徑操作* 使用一個**唯一 ID**,它會被用於**操作 ID**,以及任何請求或回應所需的自訂模型名稱。 + +你可以自訂該函式。它接收一個 APIRoute 並回傳字串。 + +例如,下面使用第一個標籤(你通常只會有一個標籤)以及 *路徑操作* 的名稱(函式名稱)。 + +接著你可以將這個自訂函式以 `generate_unique_id_function` 參數傳給 **FastAPI**: + +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} + +### 使用自訂 Operation ID 產生 TypeScript 用戶端 { #generate-a-typescript-client-with-custom-operation-ids } + +現在,如果你再次產生用戶端,會看到方法名稱已改善: + + + +如你所見,方法名稱現在包含標籤與函式名稱,不再包含 URL 路徑與 HTTP 操作的資訊。 + +### 為用戶端產生器預處理 OpenAPI 規格 { #preprocess-the-openapi-specification-for-the-client-generator } + +產生的程式碼仍有一些**重複資訊**。 + +我們已經知道這個方法與 **items** 相關,因為該字已出現在 `ItemsService`(取自標籤)中,但方法名稱仍然加上了標籤名稱做前綴。😕 + +對於 OpenAPI 本身,我們可能仍想保留,因為那能確保操作 ID 是**唯一**的。 + +但就產生用戶端而言,我們可以在產生前**修改** OpenAPI 的操作 ID,來讓方法名稱更**簡潔**、更**乾淨**。 + +我們可以把 OpenAPI JSON 下載到 `openapi.json` 檔案,然後用像這樣的腳本**移除該標籤前綴**: + +{* ../../docs_src/generate_clients/tutorial004_py310.py *} + +//// tab | Node.js + +```Javascript +{!> ../../docs_src/generate_clients/tutorial004.js!} +``` + +//// + +如此一來,操作 ID 會從 `items-get_items` 之類的字串,變成單純的 `get_items`,讓用戶端產生器能產生更簡潔的方法名稱。 + +### 使用預處理後的 OpenAPI 產生 TypeScript 用戶端 { #generate-a-typescript-client-with-the-preprocessed-openapi } + +由於最終結果現在是在 `openapi.json` 檔案中,你需要更新輸入位置: + +```sh +npx @hey-api/openapi-ts -i ./openapi.json -o src/client +``` + +產生新的用戶端後,你現在會得到**乾淨的方法名稱**,同時保有所有的**自動完成**、**行內錯誤**等功能: + + + +## 好處 { #benefits } + +使用自動產生的用戶端時,你會得到以下項目的**自動完成**: + +* 方法 +* 本文中的請求有效載荷、查詢參數等 +* 回應的有效載荷 + +你也會對所有內容獲得**行內錯誤**提示。 + +而且每當你更新後端程式碼並**重新產生**前端(用戶端),新的 *路徑操作* 會以方法形式可用、舊的會被移除,其他任何變更也都會反映到產生的程式碼中。🤓 + +這也代表只要有任何變更,便會自動**反映**到用戶端程式碼;而當你**建置**用戶端時,如果使用的資料有任何**不匹配**,就會直接報錯。 + +因此,你能在開發週期的很早期就**偵測到許多錯誤**,而不必等到錯誤在正式環境的最終使用者那裡才出現,然後才開始追查問題所在。✨ diff --git a/docs/zh-hant/docs/advanced/index.md b/docs/zh-hant/docs/advanced/index.md new file mode 100644 index 000000000..cfbc17afe --- /dev/null +++ b/docs/zh-hant/docs/advanced/index.md @@ -0,0 +1,21 @@ +# 進階使用者指南 { #advanced-user-guide } + +## 更多功能 { #additional-features } + +主要的[教學 - 使用者指南](../tutorial/index.md){.internal-link target=_blank} 應足以帶你快速瀏覽 **FastAPI** 的所有核心功能。 + +在接下來的章節中,你會看到其他選項、設定,以及更多功能。 + +/// tip + +接下來的章節不一定「進階」。 + +而且對於你的使用情境,解法很可能就在其中某一節。 + +/// + +## 先閱讀教學 { #read-the-tutorial-first } + +只要掌握主要[教學 - 使用者指南](../tutorial/index.md){.internal-link target=_blank} 的內容,你就能使用 **FastAPI** 的大多數功能。 + +接下來的章節也假設你已經讀過,並已了解那些主要觀念。 diff --git a/docs/zh-hant/docs/advanced/middleware.md b/docs/zh-hant/docs/advanced/middleware.md new file mode 100644 index 000000000..80cda345c --- /dev/null +++ b/docs/zh-hant/docs/advanced/middleware.md @@ -0,0 +1,97 @@ +# 進階中介軟體 { #advanced-middleware } + +在主要教學中你已學過如何將[自訂中介軟體](../tutorial/middleware.md){.internal-link target=_blank}加入到你的應用程式。 + +你也讀過如何使用 `CORSMiddleware` 處理 [CORS](../tutorial/cors.md){.internal-link target=_blank}。 + +本節將示範如何使用其他中介軟體。 + +## 新增 ASGI 中介軟體 { #adding-asgi-middlewares } + +由於 **FastAPI** 建立在 Starlette 上並實作了 ASGI 規範,你可以使用任何 ASGI 中介軟體。 + +中介軟體不一定要為 FastAPI 或 Starlette 專門撰寫,只要遵循 ASGI 規範即可運作。 + +一般來說,ASGI 中介軟體是類別,預期第一個參數接收一個 ASGI 應用程式。 + +因此,在第三方 ASGI 中介軟體的文件中,通常會指示你這樣做: + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +但 FastAPI(實際上是 Starlette)提供了一種更簡單的方式,確保內部中介軟體能處理伺服器錯誤,且自訂例外處理器可正常運作。 + +為此,你可以使用 `app.add_middleware()`(如同 CORS 範例)。 + +```Python +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +`app.add_middleware()` 將中介軟體類別作為第一個引數,並接收要傳遞給該中介軟體的其他引數。 + +## 內建中介軟體 { #integrated-middlewares } + +**FastAPI** 內建數個常見用途的中介軟體,以下將示範如何使用。 + +/// note | 技術細節 + +在接下來的範例中,你也可以使用 `from starlette.middleware.something import SomethingMiddleware`。 + +**FastAPI** 在 `fastapi.middleware` 中提供了一些中介軟體,純粹是為了方便你這位開發者。但大多數可用的中介軟體直接來自 Starlette。 + +/// + +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } + +強制所有傳入請求必須使用 `https` 或 `wss`。 + +任何指向 `http` 或 `ws` 的請求都會被重新導向至對應的安全協定。 + +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} + +## `TrustedHostMiddleware` { #trustedhostmiddleware } + +強制所有傳入請求正確設定 `Host` 標頭,以防範 HTTP Host Header 攻擊。 + +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} + +支援以下參數: + +- `allowed_hosts` - 允許作為主機名稱的網域名稱清單。支援萬用字元網域(例如 `*.example.com`)以比對子網域。若要允許任意主機名稱,可使用 `allowed_hosts=["*"]`,或乾脆不要加上此中介軟體。 +- `www_redirect` - 若設為 True,對允許主機的不含 www 版本的請求會被重新導向至其 www 對應版本。預設為 `True`。 + +若傳入請求驗證失敗,將回傳 `400` 回應。 + +## `GZipMiddleware` { #gzipmiddleware } + +處理在 `Accept-Encoding` 標頭中包含 `"gzip"` 的請求之 GZip 壓縮回應。 + +此中介軟體會處理一般與串流回應。 + +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} + +支援以下參數: + +- `minimum_size` - 小於此位元組大小的回應不會進行 GZip。預設為 `500`。 +- `compresslevel` - GZip 壓縮時使用的等級。為 1 到 9 的整數。預設為 `9`。值越小壓縮越快但檔案較大,值越大壓縮較慢但檔案較小。 + +## 其他中介軟體 { #other-middlewares } + +還有許多其他 ASGI 中介軟體。 + +例如: + +- Uvicorn 的 `ProxyHeadersMiddleware` +- MessagePack + +想瞭解更多可用的中介軟體,請參考 Starlette 的中介軟體文件ASGI 精選清單。 diff --git a/docs/zh-hant/docs/advanced/openapi-callbacks.md b/docs/zh-hant/docs/advanced/openapi-callbacks.md new file mode 100644 index 000000000..b1a16be24 --- /dev/null +++ b/docs/zh-hant/docs/advanced/openapi-callbacks.md @@ -0,0 +1,186 @@ +# OpenAPI 回呼 { #openapi-callbacks } + +你可以建立一個含有「路徑操作(path operation)」的 API,該操作會觸發對某個「外部 API(external API)」的請求(通常由使用你 API 的同一位開發者提供)。 + +當你的 API 應用呼叫「外部 API」時發生的過程稱為「回呼(callback)」。因為外部開發者撰寫的軟體會先向你的 API 發出請求,接著你的 API 再「回呼」,也就是向(可能同一位開發者建立的)外部 API 發送請求。 + +在這種情況下,你可能想要文件化說明該外部 API 應該長什麼樣子。它應該有哪些「路徑操作」、應該接受什麼 body、應該回傳什麼 response,等等。 + +## 帶有回呼的應用 { #an-app-with-callbacks } + +我們用一個例子來看。 + +想像你開發了一個允許建立發票的應用。 + +這些發票會有 `id`、`title`(可選)、`customer` 和 `total`。 + +你的 API 的使用者(外部開發者)會透過一個 POST 請求在你的 API 中建立一張發票。 + +然後你的 API 會(讓我們想像): + +* 將發票寄給該外部開發者的某位客戶。 +* 代收款項。 +* 再把通知回傳給 API 使用者(外部開發者)。 + * 這會透過從「你的 API」向該外部開發者提供的「外部 API」送出 POST 請求完成(這就是「回呼」)。 + +## 一般的 **FastAPI** 應用 { #the-normal-fastapi-app } + +先看看在加入回呼之前,一個一般的 API 應用會長什麼樣子。 + +它會有一個接收 `Invoice` body 的「路徑操作」,以及一個查詢參數 `callback_url`,其中包含用於回呼的 URL。 + +這部分很正常,多數程式碼你應該已經很熟悉了: + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} + +/// tip + +`callback_url` 查詢參數使用的是 Pydantic 的 Url 型別。 + +/// + +唯一新的地方是在「路徑操作裝飾器」中加入參數 `callbacks=invoices_callback_router.routes`。我們接下來會看到那是什麼。 + +## 文件化回呼 { #documenting-the-callback } + +實際的回呼程式碼會高度依賴你的 API 應用本身。 + +而且很可能每個應用都差很多。 + +它可能就只有一兩行,例如: + +```Python +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +但回呼中最重要的部分,可能是在確保你的 API 使用者(外部開發者)能正確實作「外部 API」,符合「你的 API」在回呼請求 body 中要送出的資料格式,等等。 + +因此,接下來我們要加上用來「文件化」說明,該「外部 API」應該長什麼樣子,才能接收來自「你的 API」的回呼。 + +這份文件會出現在你的 API 的 Swagger UI `/docs`,讓外部開發者知道該如何建置「外部 API」。 + +這個範例不會實作回呼本身(那可能就只是一行程式碼),只會實作文件的部分。 + +/// tip + +實際的回呼就是一個 HTTP 請求。 + +當你自己實作回呼時,可以使用像是 HTTPXRequests。 + +/// + +## 撰寫回呼的文件化程式碼 { #write-the-callback-documentation-code } + +這段程式碼在你的應用中不會被執行,我們只需要它來「文件化」說明那個「外部 API」應該長什麼樣子。 + +不過,你已經知道如何用 **FastAPI** 輕鬆為 API 建立自動文件。 + +所以我們會用同樣的方式,來文件化「外部 API」應該長什麼樣子... 也就是建立外部 API 應該實作的「路徑操作(們)」(那些「你的 API」會去呼叫的操作)。 + +/// tip + +在撰寫回呼的文件化程式碼時,把自己想像成那位「外部開發者」會很有幫助。而且你現在是在實作「外部 API」,不是「你的 API」。 + +暫時採用這個(外部開發者)的視角,有助於讓你更直覺地決定該把參數、body 的 Pydantic 模型、response 的模型等放在哪裡,對於那個「外部 API」會更清楚。 + +/// + +### 建立一個回呼用的 `APIRouter` { #create-a-callback-apirouter } + +先建立一個新的 `APIRouter`,用來放一個或多個回呼。 + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *} + +### 建立回呼的「路徑操作」 { #create-the-callback-path-operation } + +要建立回呼的「路徑操作」,就使用你上面建立的同一個 `APIRouter`。 + +它看起來就像一般的 FastAPI「路徑操作」: + +* 可能需要宣告它應該接收的 body,例如 `body: InvoiceEvent`。 +* 也可以宣告它應該回傳的 response,例如 `response_model=InvoiceEventReceived`。 + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} + +和一般「路徑操作」相比有兩個主要差異: + +* 不需要任何實際程式碼,因為你的應用永遠不會呼叫這段程式。它只用來文件化「外部 API」。因此函式可以只有 `pass`。 +* 「路徑」可以包含一個 OpenAPI 3 表達式(見下文),可使用參數與原始送到「你的 API」的請求中的部分欄位。 + +### 回呼路徑表達式 { #the-callback-path-expression } + +回呼的「路徑」可以包含一個 OpenAPI 3 表達式,能引用原本送到「你的 API」的請求中的部分內容。 + +在這個例子中,它是一個 `str`: + +```Python +"{$callback_url}/invoices/{$request.body.id}" +``` + +所以,如果你的 API 使用者(外部開發者)向「你的 API」送出這樣的請求: + +``` +https://yourapi.com/invoices/?callback_url=https://www.external.org/events +``` + +並附上這個 JSON body: + +```JSON +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +那麼「你的 API」會處理這張發票,並在稍後某個時點,向 `callback_url`(也就是「外部 API」)送出回呼請求: + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +其 JSON body 大致包含: + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +而它會預期該「外部 API」回傳的 JSON body 例如: + +```JSON +{ + "ok": true +} +``` + +/// tip + +注意回呼所用的 URL,包含了在查詢參數 `callback_url` 中收到的 URL(`https://www.external.org/events`),以及來自 JSON body 內的發票 `id`(`2expen51ve`)。 + +/// + +### 加入回呼 router { #add-the-callback-router } + +此時你已經在先前建立的回呼 router 中,擁有所需的回呼「路徑操作(們)」(也就是「外部開發者」應該在「外部 API」中實作的那些)。 + +現在在「你的 API 的路徑操作裝飾器」中使用參數 `callbacks`,將該回呼 router 的屬性 `.routes`(實際上就是一個由路由/「路徑操作」所組成的 `list`)傳入: + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} + +/// tip + +注意你傳給 `callback=` 的不是整個 router 本身(`invoices_callback_router`),而是它的屬性 `.routes`,也就是 `invoices_callback_router.routes`。 + +/// + +### 檢查文件 { #check-the-docs } + +現在你可以啟動應用,並前往 http://127.0.0.1:8000/docs。 + +你會在文件中看到你的「路徑操作」包含一個「Callbacks」區塊,顯示「外部 API」應該長什麼樣子: + + diff --git a/docs/zh-hant/docs/advanced/openapi-webhooks.md b/docs/zh-hant/docs/advanced/openapi-webhooks.md new file mode 100644 index 000000000..ef52c3884 --- /dev/null +++ b/docs/zh-hant/docs/advanced/openapi-webhooks.md @@ -0,0 +1,55 @@ +# OpenAPI Webhook { #openapi-webhooks } + +有些情況下,你會想告訴你的 API 使用者,你的應用程式可能會攜帶一些資料去呼叫他們的應用程式(發送請求),通常是為了通知某種類型的事件。 + +這表示,與其由使用者向你的 API 發送請求,改為你的 API(或你的應用)可能會向他們的系統(他們的 API、他們的應用)發送請求。 + +這通常稱為 webhook。 + +## Webhook 步驟 { #webhooks-steps } + +流程通常是:你在程式碼中定義要發送的訊息,也就是請求的主體(request body)。 + +你也會以某種方式定義應用在哪些時刻會發送那些請求或事件。 + +而你的使用者則會以某種方式(例如在某個 Web 控制台)設定你的應用應該將這些請求送往的 URL。 + +關於如何註冊 webhook 的 URL,以及實際發送那些請求的程式碼等所有邏輯,都由你決定。你可以在自己的程式碼中用你想要的方式撰寫。 + +## 使用 FastAPI 與 OpenAPI 記錄 webhook { #documenting-webhooks-with-fastapi-and-openapi } + +在 FastAPI 中,透過 OpenAPI,你可以定義這些 webhook 的名稱、你的應用將發送的 HTTP 操作類型(例如 `POST`、`PUT` 等),以及你的應用要發送的請求主體。 + +這能讓你的使用者更容易實作他們的 API 以接收你的 webhook 請求,甚至可能自動產生部分他們自己的 API 程式碼。 + +/// info + +Webhook 功能自 OpenAPI 3.1.0 起提供,FastAPI `0.99.0` 以上版本支援。 + +/// + +## 含有 webhook 的應用 { #an-app-with-webhooks } + +建立 FastAPI 應用時,會有一個 `webhooks` 屬性可用來定義 webhook,方式與定義路徑操作相同,例如使用 `@app.webhooks.post()`。 + +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} + +你定義的 webhook 會出現在 OpenAPI 結構描述與自動產生的文件 UI 中。 + +/// info + +`app.webhooks` 其實就是一個 `APIRouter`,與你在將應用拆分為多個檔案時所使用的型別相同。 + +/// + +注意,使用 webhook 時你其實不是在宣告路徑(例如 `/items/`),你傳入的文字只是該 webhook 的識別名稱(事件名稱)。例如在 `@app.webhooks.post("new-subscription")` 中,webhook 名稱是 `new-subscription`。 + +這是因為預期由你的使用者以其他方式(例如 Web 控制台)來設定實際要接收 webhook 請求的 URL 路徑。 + +### 查看文件 { #check-the-docs } + +現在你可以啟動應用,然後前往 http://127.0.0.1:8000/docs。 + +你會在文件中看到一般的路徑操作,另外還有一些 webhook: + + diff --git a/docs/zh-hant/docs/advanced/path-operation-advanced-configuration.md b/docs/zh-hant/docs/advanced/path-operation-advanced-configuration.md new file mode 100644 index 000000000..4f25eb987 --- /dev/null +++ b/docs/zh-hant/docs/advanced/path-operation-advanced-configuration.md @@ -0,0 +1,172 @@ +# 路徑操作進階設定 { #path-operation-advanced-configuration } + +## OpenAPI operationId { #openapi-operationid } + +/// warning + +如果你不是 OpenAPI 的「專家」,大概不需要這個。 + +/// + +你可以用參數 `operation_id` 為你的*路徑操作(path operation)*設定要使用的 OpenAPI `operationId`。 + +你必須確保每個操作的 `operationId` 都是唯一的。 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} + +### 使用路徑操作函式(path operation function)的名稱作為 operationId { #using-the-path-operation-function-name-as-the-operationid } + +如果你想用 API 的函式名稱作為 `operationId`,你可以遍歷所有路徑,並使用各自的 `APIRoute.name` 覆寫每個*路徑操作*的 `operation_id`。 + +應在加入所有*路徑操作*之後再這麼做。 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} + +/// tip + +如果你會手動呼叫 `app.openapi()`,請務必先更新所有 `operationId` 再呼叫。 + +/// + +/// warning + +如果你這樣做,必須確保每個*路徑操作函式*都有唯一的名稱, + +即使它們位於不同的模組(Python 檔案)中。 + +/// + +## 從 OpenAPI 排除 { #exclude-from-openapi } + +若要從產生的 OpenAPI 結構(也就是自動文件系統)中排除某個*路徑操作*,使用參數 `include_in_schema` 並將其設為 `False`: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} + +## 從 docstring 提供進階描述 { #advanced-description-from-docstring } + +你可以限制 OpenAPI 從*路徑操作函式*的 docstring 中使用的內容行數。 + +加上一個 `\f`(跳頁字元,form feed)會讓 FastAPI 在此處截斷用於 OpenAPI 的輸出。 + +這個字元不會出現在文件中,但其他工具(例如 Sphinx)仍可使用其後的內容。 + +{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} + +## 額外回應 { #additional-responses } + +你大概已看過如何為*路徑操作*宣告 `response_model` 與 `status_code`。 + +這會定義該*路徑操作*主要回應的中繼資料。 + +你也可以宣告額外的回應及其模型、狀態碼等。 + +文件中有完整章節說明,請見 [OpenAPI 中的額外回應](additional-responses.md){.internal-link target=_blank}。 + +## OpenAPI 額外資訊 { #openapi-extra } + +當你在應用程式中宣告一個*路徑操作*時,FastAPI 會自動產生該*路徑操作*的相關中繼資料,並納入 OpenAPI 結構中。 + +/// note | 技術細節 + +在 OpenAPI 規格中,這稱為 Operation 物件。 + +/// + +它包含關於*路徑操作*的所有資訊,並用於產生自動文件。 + +其中包含 `tags`、`parameters`、`requestBody`、`responses` 等。 + +這個針對單一路徑操作的 OpenAPI 結構通常由 FastAPI 自動產生,但你也可以擴充它。 + +/// tip + +這是一個較低階的擴充介面。 + +如果你只需要宣告額外回應,更方便的方式是使用 [OpenAPI 中的額外回應](additional-responses.md){.internal-link target=_blank}。 + +/// + +你可以使用參數 `openapi_extra` 來擴充某個*路徑操作*的 OpenAPI 結構。 + +### OpenAPI 擴充 { #openapi-extensions } + +`openapi_extra` 可用來宣告例如 [OpenAPI 擴充](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions) 的資料: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *} + +打開自動產生的 API 文件時,你的擴充會顯示在該*路徑操作*頁面的底部。 + + + +而在檢視產生出的 OpenAPI(位於你的 API 的 `/openapi.json`)時,也可以在相應*路徑操作*中看到你的擴充: + +```JSON hl_lines="22" +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "x-aperture-labs-portal": "blue" + } + } + } +} +``` + +### 自訂 OpenAPI 路徑操作結構 { #custom-openapi-path-operation-schema } + +`openapi_extra` 中的字典會與自動產生的該*路徑操作*之 OpenAPI 結構進行深度合併。 + +因此你可以在自動產生的結構上加入額外資料。 + +例如,你可以選擇用自己的程式碼讀取並驗證請求,而不使用 FastAPI 與 Pydantic 的自動功能,但仍然希望在 OpenAPI 結構中定義該請求。 + +你可以透過 `openapi_extra` 辦到: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *} + +在這個範例中,我們沒有宣告任何 Pydantic 模型。事實上,請求本文甚至不會被 解析 為 JSON,而是直接以 `bytes` 讀取,並由函式 `magic_data_reader()` 以某種方式負責解析。 + +儘管如此,我們仍可宣告請求本文的預期結構。 + +### 自訂 OpenAPI Content-Type { #custom-openapi-content-type } + +用同樣的方法,你可以使用 Pydantic 模型來定義 JSON Schema,並把它包含到該*路徑操作*的自訂 OpenAPI 區段中。 + +即使請求中的資料型別不是 JSON 也可以這麼做。 + +例如,在這個應用中,我們不使用 FastAPI 內建的從 Pydantic 模型擷取 JSON Schema 的功能,也不使用 JSON 的自動驗證。實際上,我們將請求的 content type 宣告為 YAML,而非 JSON: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *} + +儘管沒有使用預設的內建功能,我們仍透過 Pydantic 模型手動產生想以 YAML 接收之資料的 JSON Schema。 + +接著我們直接使用請求,並將本文擷取為 `bytes`。這表示 FastAPI 甚至不會嘗試把請求負載解析為 JSON。 + +然後在程式中直接解析該 YAML 內容,並再次使用相同的 Pydantic 模型來驗證該 YAML 內容: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *} + +/// tip + +這裡我們重複使用同一個 Pydantic 模型。 + +不過也可以用其他方式進行驗證。 + +/// diff --git a/docs/zh-hant/docs/advanced/response-change-status-code.md b/docs/zh-hant/docs/advanced/response-change-status-code.md new file mode 100644 index 000000000..4b8d459ca --- /dev/null +++ b/docs/zh-hant/docs/advanced/response-change-status-code.md @@ -0,0 +1,31 @@ +# 回應 - 變更狀態碼 { #response-change-status-code } + +你可能已經讀過,可以設定預設的[回應狀態碼](../tutorial/response-status-code.md){.internal-link target=_blank}。 + +但有些情況你需要回傳與預設不同的狀態碼。 + +## 使用情境 { #use-case } + +例如,假設你預設想回傳 HTTP 狀態碼 "OK" `200`。 + +但如果資料不存在,你想要建立它,並回傳 HTTP 狀態碼 "CREATED" `201`。 + +同時你仍希望能用 `response_model` 過濾並轉換所回傳的資料。 + +在這些情況下,你可以使用 `Response` 參數。 + +## 使用 `Response` 參數 { #use-a-response-parameter } + +你可以在你的路徑操作函式(path operation function)中宣告一個 `Response` 型別的參數(就像你可以對 Cookies 和標頭那樣)。 + +接著你可以在那個「*暫時的*」回應物件上設定 `status_code`。 + +{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *} + +然後你可以照常回傳任何需要的物件(例如 `dict`、資料庫模型等)。 + +若你宣告了 `response_model`,它仍會被用來過濾並轉換你回傳的物件。 + +FastAPI 會使用那個「*暫時的*」回應來取得狀態碼(以及 Cookies 和標頭),並將它們放入最終回應中;最終回應包含你回傳的值,且會被任何 `response_model` 過濾。 + +你也可以在相依性(dependencies)中宣告 `Response` 參數,並在其中設定狀態碼。但請注意,最後被設定的值會生效。 diff --git a/docs/zh-hant/docs/advanced/response-cookies.md b/docs/zh-hant/docs/advanced/response-cookies.md new file mode 100644 index 000000000..b7225d571 --- /dev/null +++ b/docs/zh-hant/docs/advanced/response-cookies.md @@ -0,0 +1,51 @@ +# 回應 Cookie { #response-cookies } + +## 使用 `Response` 參數 { #use-a-response-parameter } + +你可以在路徑操作函式(path operation function)中宣告一個型別為 `Response` 的參數。 + +接著你可以在那個「暫時」的 `Response` 物件上設定 Cookie。 + +{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *} + +之後如常回傳你需要的任何物件(例如 `dict`、資料庫模型等)。 + +如果你宣告了 `response_model`,它仍會用來過濾並轉換你回傳的物件。 + +FastAPI 會使用那個暫時的 `Response` 取出 Cookie(以及標頭與狀態碼),並將它們放入最終回應;最終回應包含你回傳的值,且會套用任何 `response_model` 的過濾。 + +你也可以在相依項(dependencies)中宣告 `Response` 參數,並在其中設定 Cookie(與標頭)。 + +## 直接回傳 `Response` { #return-a-response-directly } + +當你在程式碼中直接回傳 `Response` 時,也可以建立 Cookie。 + +要這麼做,你可以依照 [直接回傳 Response](response-directly.md){.internal-link target=_blank} 中的說明建立一個回應。 + +接著在其中設定 Cookie,然後回傳它: + +{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *} + +/// tip | 提示 + +請注意,如果你不是使用 `Response` 參數,而是直接回傳一個 `Response`,FastAPI 會原樣回傳它。 + +因此你必須確保資料型別正確;例如,如果你回傳的是 `JSONResponse`,就要確保資料可與 JSON 相容。 + +同時也要確認沒有送出原本應該由 `response_model` 過濾的資料。 + +/// + +### 更多資訊 { #more-info } + +/// note | 技術細節 + +你也可以使用 `from starlette.responses import Response` 或 `from starlette.responses import JSONResponse`。 + +為了方便開發者,FastAPI 也將相同的 `starlette.responses` 透過 `fastapi.responses` 提供。不過,大多數可用的回應類別都直接來自 Starlette。 + +另外由於 `Response` 常用於設定標頭與 Cookie,FastAPI 也在 `fastapi.Response` 提供了它。 + +/// + +想查看所有可用的參數與選項,請參閱 Starlette 文件。 diff --git a/docs/zh-hant/docs/advanced/response-directly.md b/docs/zh-hant/docs/advanced/response-directly.md new file mode 100644 index 000000000..560354893 --- /dev/null +++ b/docs/zh-hant/docs/advanced/response-directly.md @@ -0,0 +1,65 @@ +# 直接回傳 Response { #return-a-response-directly } + +當你建立一個 **FastAPI** 的路徑操作 (path operation) 時,通常可以從中回傳任何資料:`dict`、`list`、Pydantic 模型、資料庫模型等。 + +預設情況下,**FastAPI** 會使用在[JSON 相容編碼器](../tutorial/encoder.md){.internal-link target=_blank}中說明的 `jsonable_encoder`,自動將回傳值轉為 JSON。 + +然後在幕後,它會把這些與 JSON 相容的資料(例如 `dict`)放進 `JSONResponse`,用來把回應傳回給用戶端。 + +但你也可以直接從路徑操作回傳 `JSONResponse`。 + +例如,當你需要回傳自訂的 headers 或 cookies 時就很有用。 + +## 回傳 `Response` { #return-a-response } + +其實,你可以回傳任何 `Response`,或其任何子類別。 + +/// tip + +`JSONResponse` 本身就是 `Response` 的子類別。 + +/// + +當你回傳一個 `Response` 時,**FastAPI** 會直接傳遞它。 + +它不會對 Pydantic 模型做任何資料轉換,也不會把內容轉成其他型別等。 + +這給了你很大的彈性。你可以回傳任何資料型別、覆寫任何資料宣告或驗證等。 + +## 在 `Response` 中使用 `jsonable_encoder` { #using-the-jsonable-encoder-in-a-response } + +因為 **FastAPI** 不會對你回傳的 `Response` 做任何更動,你需要自行確保它的內容已經準備好。 + +例如,你不能直接把一個 Pydantic 模型放進 `JSONResponse`,需要先把它轉成 `dict`,並將所有資料型別(像是 `datetime`、`UUID` 等)轉成與 JSON 相容的型別。 + +在這些情況下,你可以先用 `jsonable_encoder` 把資料轉好,再傳給回應物件: + +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} + +/// note | 技術細節 + +你也可以使用 `from starlette.responses import JSONResponse`。 + +**FastAPI** 為了方便開發者,將 `starlette.responses` 也提供為 `fastapi.responses`。但大多數可用的回應類型其實直接來自 Starlette。 + +/// + +## 回傳自訂 `Response` { #returning-a-custom-response } + +上面的範例展示了所需的各個部分,但目前還不太實用,因為你其實可以直接回傳 `item`,**FastAPI** 就會幫你把它放進 `JSONResponse`,轉成 `dict` 等,這些都是預設行為。 + +現在來看看如何用它來回傳自訂回應。 + +假設你想要回傳一個 XML 回應。 + +你可以把 XML 內容放進一個字串,把它放進 `Response`,然後回傳它: + +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} + +## 注意事項 { #notes } + +當你直接回傳 `Response` 時,其資料不會自動被驗證、轉換(序列化)或文件化。 + +但你仍然可以依照[在 OpenAPI 中的額外回應](additional-responses.md){.internal-link target=_blank}中的說明進行文件化。 + +在後續章節中,你會看到如何在仍保有自動資料轉換、文件化等的同時,使用/宣告這些自訂的 `Response`。 diff --git a/docs/zh-hant/docs/advanced/response-headers.md b/docs/zh-hant/docs/advanced/response-headers.md new file mode 100644 index 000000000..6e32ca1b3 --- /dev/null +++ b/docs/zh-hant/docs/advanced/response-headers.md @@ -0,0 +1,41 @@ +# 回應標頭 { #response-headers } + +## 使用 `Response` 參數 { #use-a-response-parameter } + +你可以在你的*路徑操作函式(path operation function)*中宣告一個 `Response` 型別的參數(就像處理 Cookie 一樣)。 + +然後你可以在那個*暫時性的* `Response` 物件上設定標頭。 + +{* ../../docs_src/response_headers/tutorial002_py310.py hl[1, 7:8] *} + +接著你可以像平常一樣回傳任何你需要的物件(`dict`、資料庫模型等)。 + +如果你宣告了 `response_model`,它仍會用來過濾並轉換你回傳的物件。 + +FastAPI 會使用那個暫時性的回應來擷取標頭(還有 Cookie 與狀態碼),並把它們放到最終回應中;最終回應包含你回傳的值,且會依任何 `response_model` 進行過濾。 + +你也可以在依賴中宣告 `Response` 參數,並在其中設定標頭(與 Cookie)。 + +## 直接回傳 `Response` { #return-a-response-directly } + +當你直接回傳 `Response` 時,也能加入標頭。 + +依照[直接回傳 Response](response-directly.md){.internal-link target=_blank}中的說明建立回應,並把標頭作為額外參數傳入: + +{* ../../docs_src/response_headers/tutorial001_py310.py hl[10:12] *} + +/// note | 技術細節 + +你也可以使用 `from starlette.responses import Response` 或 `from starlette.responses import JSONResponse`。 + +為了方便開發者,FastAPI 提供與 `starlette.responses` 相同的內容於 `fastapi.responses`。但大多數可用的回應類型其實直接來自 Starlette。 + +由於 `Response` 常用來設定標頭與 Cookie,FastAPI 也在 `fastapi.Response` 提供了它。 + +/// + +## 自訂標頭 { #custom-headers } + +請記住,專有的自訂標頭可以使用 `X-` 前綴來新增。 + +但如果你有自訂標頭並希望瀏覽器端的客戶端能看見它們,你需要把這些標頭加入到 CORS 設定中(詳見 [CORS(跨來源資源共用)](../tutorial/cors.md){.internal-link target=_blank}),使用在Starlette 的 CORS 文件中記載的 `expose_headers` 參數。 diff --git a/docs/zh-hant/docs/advanced/security/http-basic-auth.md b/docs/zh-hant/docs/advanced/security/http-basic-auth.md new file mode 100644 index 000000000..ea3d52829 --- /dev/null +++ b/docs/zh-hant/docs/advanced/security/http-basic-auth.md @@ -0,0 +1,107 @@ +# HTTP 基本認證 { #http-basic-auth } + +在最簡單的情況下,你可以使用 HTTP Basic 認證。 + +在 HTTP Basic 認證中,應用程式會期待一個包含使用者名稱與密碼的標頭。 + +如果沒有接收到,會回傳 HTTP 401「Unauthorized」錯誤。 + +並回傳一個 `WWW-Authenticate` 標頭,其值為 `Basic`,以及可選的 `realm` 參數。 + +這會告訴瀏覽器顯示內建的使用者名稱與密碼提示視窗。 + +接著,當你輸入該使用者名稱與密碼時,瀏覽器會自動在標頭中送出它們。 + +## 簡單的 HTTP 基本認證 { #simple-http-basic-auth } + +- 匯入 `HTTPBasic` 與 `HTTPBasicCredentials`。 +- 使用 `HTTPBasic` 建立一個「`security` scheme」。 +- 在你的*路徑操作*中以依賴的方式使用該 `security`。 +- 它會回傳一個 `HTTPBasicCredentials` 型別的物件: + - 其中包含傳來的 `username` 與 `password`。 + +{* ../../docs_src/security/tutorial006_an_py310.py hl[4,8,12] *} + +當你第一次嘗試開啟該 URL(或在文件中點擊 "Execute" 按鈕)時,瀏覽器會要求輸入你的使用者名稱與密碼: + + + +## 檢查使用者名稱 { #check-the-username } + +以下是一個更完整的範例。 + +使用一個依賴來檢查使用者名稱與密碼是否正確。 + +為此,使用 Python 標準模組 `secrets` 來比對使用者名稱與密碼。 + +`secrets.compare_digest()` 需要接收 `bytes`,或是只包含 ASCII 字元(英文字符)的 `str`。這表示它無法處理像 `á` 這樣的字元,例如 `Sebastián`。 + +為了處理這點,我們會先將 `username` 與 `password` 以 UTF-8 編碼成 `bytes`。 + +接著我們可以使用 `secrets.compare_digest()` 來確認 `credentials.username` 等於 `"stanleyjobson"`,而 `credentials.password` 等於 `"swordfish"`。 + +{* ../../docs_src/security/tutorial007_an_py310.py hl[1,12:24] *} + +這大致等同於: + +```Python +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # 回傳錯誤 + ... +``` + +但藉由使用 `secrets.compare_digest()`,可以防禦一種稱為「計時攻擊」的攻擊。 + +### 計時攻擊 { #timing-attacks } + +什麼是「計時攻擊」呢? + +想像有攻擊者在嘗試猜測使用者名稱與密碼。 + +他們送出一個帶有使用者名稱 `johndoe` 與密碼 `love123` 的請求。 + +接著,你的應用程式中的 Python 程式碼等同於: + +```Python +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +當 Python 比較 `johndoe` 的第一個 `j` 與 `stanleyjobson` 的第一個 `s` 時,會立刻回傳 `False`,因為已經知道兩個字串不同,覺得「沒必要浪費計算資源繼續比較剩下的字元」。你的應用程式便會回應「Incorrect username or password」。 + +但接著攻擊者改用使用者名稱 `stanleyjobsox` 與密碼 `love123` 嘗試。 + +你的應用程式程式碼會做類似: + +```Python +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Python 會必須先比較完整的 `stanleyjobso`(在 `stanleyjobsox` 與 `stanleyjobson` 之中都一樣),才會發現兩個字串不同。因此回覆「Incorrect username or password」會多花一些微秒。 + +#### 回應時間幫了攻擊者 { #the-time-to-answer-helps-the-attackers } + +此時,透過觀察伺服器回覆「Incorrect username or password」多花了幾個微秒,攻擊者就知道他們有某些地方猜對了,前幾個字母是正確的。 + +接著他們會再嘗試,知道它更可能接近 `stanleyjobsox` 而不是 `johndoe`。 + +#### 「專業」的攻擊 { #a-professional-attack } + +當然,攻擊者不會手動嘗試這一切,他們會寫程式來做,可能每秒進行上千或上百萬次測試,一次只多猜中一個正確字母。 + +但這樣做,幾分鐘或幾小時內,他們就能在我們應用程式「協助」下,僅靠回應時間就猜出正確的使用者名稱與密碼。 + +#### 用 `secrets.compare_digest()` 修正 { #fix-it-with-secrets-compare-digest } + +但在我們的程式碼中實際使用的是 `secrets.compare_digest()`。 + +簡而言之,將 `stanleyjobsox` 與 `stanleyjobson` 比較所花的時間,會與將 `johndoe` 與 `stanleyjobson` 比較所花的時間相同;密碼也一樣。 + +如此一來,在應用程式程式碼中使用 `secrets.compare_digest()`,就能安全地防禦這整類的安全攻擊。 + +### 回傳錯誤 { #return-the-error } + +在偵測到憑證不正確之後,回傳一個狀態碼為 401 的 `HTTPException`(與未提供憑證時相同),並加上 `WWW-Authenticate` 標頭,讓瀏覽器再次顯示登入提示: + +{* ../../docs_src/security/tutorial007_an_py310.py hl[26:30] *} diff --git a/docs/zh-hant/docs/advanced/security/index.md b/docs/zh-hant/docs/advanced/security/index.md new file mode 100644 index 000000000..9a4cfbbfd --- /dev/null +++ b/docs/zh-hant/docs/advanced/security/index.md @@ -0,0 +1,19 @@ +# 進階安全性 { #advanced-security } + +## 額外功能 { #additional-features } + +除了[教學 - 使用者指南:安全性](../../tutorial/security/index.md){.internal-link target=_blank}中涵蓋的內容外,還有一些用來處理安全性的額外功能。 + +/// tip + +以下各節不一定是「進階」內容。 + +而且你的情境很可能可以在其中找到解決方案。 + +/// + +## 請先閱讀教學 { #read-the-tutorial-first } + +以下各節假設你已閱讀主要的[教學 - 使用者指南:安全性](../../tutorial/security/index.md){.internal-link target=_blank}。 + +它們都建立在相同的概念之上,但提供一些額外的功能。 diff --git a/docs/zh-hant/docs/advanced/security/oauth2-scopes.md b/docs/zh-hant/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 000000000..029572d43 --- /dev/null +++ b/docs/zh-hant/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,274 @@ +# OAuth2 範圍(scopes) { #oauth2-scopes } + +你可以直接在 FastAPI 中使用 OAuth2 的 scopes,已整合可無縫運作。 + +這能讓你在 OpenAPI 應用(以及 API 文件)中,依照 OAuth2 標準,實作更細粒度的權限系統。 + +帶有 scopes 的 OAuth2 是許多大型身分驗證提供者(如 Facebook、Google、GitHub、Microsoft、X(Twitter)等)所使用的機制。他們用它來為使用者與應用程式提供特定權限。 + +每次你「使用」Facebook、Google、GitHub、Microsoft、X(Twitter)「登入」時,那個應用就是在使用帶有 scopes 的 OAuth2。 + +在本節中,你將看到如何在你的 FastAPI 應用中,用同樣的帶有 scopes 的 OAuth2 管理驗證與授權。 + +/// warning + +這一節算是進階內容。如果你剛開始,可以先跳過。 + +你不一定需要 OAuth2 scopes,你可以用任何你想要的方式處理驗證與授權。 + +但帶有 scopes 的 OAuth2 可以很漂亮地整合進你的 API(透過 OpenAPI)與 API 文件。 + +無論如何,你仍然會在程式碼中,依你的需求,強制檢查那些 scopes,或其他任何安全性/授權需求。 + +在許多情況下,帶有 scopes 的 OAuth2 可能有點大材小用。 + +但如果你確定需要,或是好奇,請繼續閱讀。 + +/// + +## OAuth2 scopes 與 OpenAPI { #oauth2-scopes-and-openapi } + +OAuth2 規格將「scopes」定義為以空白分隔的一串字串列表。 + +每個字串的內容可以有任意格式,但不應包含空白。 + +這些 scopes 代表「權限」。 + +在 OpenAPI(例如 API 文件)中,你可以定義「security schemes」。 + +當某個 security scheme 使用 OAuth2 時,你也可以宣告並使用 scopes。 + +每個「scope」就是一個(不含空白的)字串。 + +它們通常用來宣告特定的安全性權限,例如: + +- `users:read` 或 `users:write` 是常見的例子。 +- `instagram_basic` 是 Facebook / Instagram 使用的。 +- `https://www.googleapis.com/auth/drive` 是 Google 使用的。 + +/// info + +在 OAuth2 中,「scope」只是宣告所需特定權限的一個字串。 + +是否包含像 `:` 這樣的字元,或是否是一個 URL,都沒差。 + +那些細節取決於實作。 + +對 OAuth2 而言,它們就是字串。 + +/// + +## 全局概觀 { #global-view } + +先快速看看相對於主教學「使用密碼(與雜湊)、Bearer 與 JWT token 的 OAuth2」的差異([OAuth2 with Password (and hashing), Bearer with JWT tokens](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank})。現在加入了 OAuth2 scopes: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} + +接著我們一步一步檢視這些變更。 + +## OAuth2 安全性方案 { #oauth2-security-scheme } + +第一個變更是:我們現在宣告了帶有兩個可用 scope 的 OAuth2 安全性方案,`me` 與 `items`。 + +參數 `scopes` 接收一個 `dict`,以各 scope 為鍵、其描述為值: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} + +由於現在宣告了這些 scopes,當你登入/授權時,它們會出現在 API 文件中。 + +你可以選擇要授予哪些 scopes 存取權:`me` 與 `items`。 + +這與你使用 Facebook、Google、GitHub 等登入時所授與權限的機制相同: + + + +## 內含 scopes 的 JWT token { #jwt-token-with-scopes } + +現在,修改 token 的路徑操作以回傳所請求的 scopes。 + +我們仍然使用相同的 `OAuth2PasswordRequestForm`。它包含屬性 `scopes`,其為 `list` 的 `str`,列出請求中收到的每個 scope。 + +並且我們將這些 scopes 作為 JWT token 的一部分回傳。 + +/// danger + +為了簡化,這裡我們只是直接把接收到的 scopes 加進 token。 + +但在你的應用中,為了安全性,你應確保只加入該使用者實際可擁有或你預先定義的 scopes。 + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} + +## 在路徑操作與相依性中宣告 scopes { #declare-scopes-in-path-operations-and-dependencies } + +現在我們宣告 `/users/me/items/` 這個路徑操作需要 `items` 這個 scope。 + +為此,我們從 `fastapi` 匯入並使用 `Security`。 + +你可以使用 `Security` 來宣告相依性(就像 `Depends`),但 `Security` 也能接收參數 `scopes`,其為 scopes(字串)的列表。 + +在這裡,我們將相依函式 `get_current_active_user` 傳給 `Security`(就像使用 `Depends` 一樣)。 + +但同時也傳入一個 `list` 的 scopes,這裡只有一個 scope:`items`(當然也可以有更多)。 + +而相依函式 `get_current_active_user` 也能宣告子相依性,不只用 `Depends`,也能用 `Security`。它宣告了自己的子相依函式(`get_current_user`),並加入更多 scope 要求。 + +在這個例子中,它要求 `me` 這個 scope(也可以要求多個)。 + +/// note + +你不一定需要在不同地方加上不同的 scopes。 + +我們在這裡這樣做,是為了示範 FastAPI 如何處理在不同層級宣告的 scopes。 + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} + +/// info | 技術細節 + +`Security` 其實是 `Depends` 的子類別,僅多了一個我們稍後會看到的參數。 + +改用 `Security` 而不是 `Depends`,能讓 FastAPI 知道可以宣告安全性 scopes、在內部使用它們,並用 OpenAPI 文件化 API。 + +另外,當你從 `fastapi` 匯入 `Query`、`Path`、`Depends`、`Security` 等時,實際上它們是回傳特殊類別的函式。 + +/// + +## 使用 `SecurityScopes` { #use-securityscopes } + +現在更新相依性 `get_current_user`。 + +上面的相依性就是使用它。 + +這裡我們使用先前建立的相同 OAuth2 scheme,並將其宣告為相依性:`oauth2_scheme`。 + +因為此相依函式本身沒有任何 scope 要求,所以我們可以用 `Depends` 搭配 `oauth2_scheme`,當不需要指定安全性 scopes 時就不必用 `Security`。 + +我們也宣告了一個型別為 `SecurityScopes` 的特殊參數,從 `fastapi.security` 匯入。 + +這個 `SecurityScopes` 類似於 `Request`(`Request` 用來直接取得請求物件)。 + +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} + +## 使用這些 `scopes` { #use-the-scopes } + +參數 `security_scopes` 的型別是 `SecurityScopes`。 + +它會有屬性 `scopes`,包含一個列表,內含此函式本身與所有使用它為子相依性的相依性所要求的所有 scopes。也就是所有「相依者(dependants)」... 這聽起來可能有點混亂,下面會再解釋。 + +`security_scopes` 物件(類別 `SecurityScopes`)也提供 `scope_str` 屬性,為一個字串,包含那些以空白分隔的 scopes(我們會用到)。 + +我們建立一個可在多處重複丟出(`raise`)的 `HTTPException`。 + +在這個例外中,我們把所需的 scopes(若有)以空白分隔的字串形式(透過 `scope_str`)加入,並將該包含 scopes 的字串放在 `WWW-Authenticate` 標頭中(這是規格的一部分)。 + +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} + +## 驗證 `username` 與資料結構 { #verify-the-username-and-data-shape } + +我們先確認取得了 `username`,並取出 scopes。 + +接著用 Pydantic 模型驗證這些資料(捕捉 `ValidationError` 例外),若在讀取 JWT token 或用 Pydantic 驗證資料時出錯,就丟出先前建立的 `HTTPException`。 + +為此,我們更新了 Pydantic 模型 `TokenData`,加入新屬性 `scopes`。 + +透過 Pydantic 驗證資料,我們可以確保,例如,scopes 正好是 `list` 的 `str`,而 `username` 是 `str`。 + +否則若是 `dict` 或其他型別,可能在後續某處使應用壞掉,造成安全風險。 + +我們也會確認該 `username` 對應的使用者是否存在,否則同樣丟出之前建立的例外。 + +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} + +## 驗證 `scopes` { #verify-the-scopes } + +我們現在要驗證,此相依性與所有相依者(包含路徑操作)所要求的所有 scopes,是否都包含在收到的 token 內所提供的 scopes 中;否則就丟出 `HTTPException`。 + +為此,我們使用 `security_scopes.scopes`,其中包含一個 `list`,列出所有這些 `str` 形式的 scopes。 + +{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} + +## 相依性樹與 scopes { #dependency-tree-and-scopes } + +我們再回顧一次這個相依性樹與 scopes。 + +由於 `get_current_active_user` 相依於 `get_current_user`,因此在 `get_current_active_user` 宣告的 `"me"` 這個 scope 會包含在傳給 `get_current_user` 的 `security_scopes.scopes` 的必須 scopes 清單中。 + +路徑操作本身也宣告了 `"items"` 這個 scope,因此它也會包含在傳給 `get_current_user` 的 `security_scopes.scopes` 中。 + +以下是相依性與 scopes 的階層關係: + +- 路徑操作 `read_own_items` 具有: + - 需要的 scopes `["items"]`,並有相依性: + - `get_current_active_user`: + - 相依函式 `get_current_active_user` 具有: + - 需要的 scopes `["me"]`,並有相依性: + - `get_current_user`: + - 相依函式 `get_current_user` 具有: + - 自身沒有需要的 scopes。 + - 一個使用 `oauth2_scheme` 的相依性。 + - 一個型別為 `SecurityScopes` 的 `security_scopes` 參數: + - 這個 `security_scopes` 參數有屬性 `scopes`,其為一個 `list`,包含了上面宣告的所有 scopes,因此: + - 對於路徑操作 `read_own_items`,`security_scopes.scopes` 會包含 `["me", "items"]`。 + - 對於路徑操作 `read_users_me`,因為它在相依性 `get_current_active_user` 中被宣告,`security_scopes.scopes` 會包含 `["me"]`。 + - 對於路徑操作 `read_system_status`,因為它沒有宣告任何帶 `scopes` 的 `Security`,且其相依性 `get_current_user` 也未宣告任何 `scopes`,所以 `security_scopes.scopes` 會包含 `[]`(空)。 + +/// tip + +這裡重要且「神奇」的是:`get_current_user` 在每個路徑操作中,會有不同的 `scopes` 清單需要檢查。 + +這完全取決於該路徑操作與其相依性樹中每個相依性所宣告的 `scopes`。 + +/// + +## 更多關於 `SecurityScopes` 的細節 { #more-details-about-securityscopes } + +你可以在任意位置、多個地方使用 `SecurityScopes`,它不需要位於「根」相依性。 + +它會永遠帶有對於「該特定」路徑操作與「該特定」相依性樹中,目前 `Security` 相依性所宣告的安全性 scopes(以及所有相依者): + +因為 `SecurityScopes` 會擁有由相依者宣告的所有 scopes,你可以在一個集中式相依函式中用它來驗證 token 是否具有所需 scopes,然後在不同路徑操作中宣告不同的 scope 要求。 + +它們會在每個路徑操作被各自獨立檢查。 + +## 試用看看 { #check-it } + +如果你打開 API 文件,你可以先驗證並指定你要授權的 scopes。 + + + +如果你沒有選任何 scope,你仍會「通過驗證」,但當你嘗試存取 `/users/me/` 或 `/users/me/items/` 時,會收到沒有足夠權限的錯誤。你仍能存取 `/status/`。 + +若你只選了 `me` 而未選 `items`,你能存取 `/users/me/`,但無法存取 `/users/me/items/`。 + +這就是第三方應用在取得使用者提供的 token 後,嘗試存取上述路徑操作時,會依使用者授與該應用的權限多寡而有不同結果。 + +## 關於第三方整合 { #about-third-party-integrations } + +在這個範例中,我們使用 OAuth2 的「password」流程。 + +當我們登入自己的應用(可能也有自己的前端)時,這是合適的。 + +因為我們可以信任它接收 `username` 與 `password`,因為我們掌控它。 + +但如果你要打造一個讓他人連接的 OAuth2 應用(也就是你要建立一個相當於 Facebook、Google、GitHub 等的身分驗證提供者),你應該使用其他流程之一。 + +最常見的是 Implicit Flow(隱式流程)。 + +最安全的是 Authorization Code Flow(授權碼流程),但它需要更多步驟、實作也更複雜。因為較複雜,許多提供者最後會建議使用隱式流程。 + +/// note + +很常見的是,每個身分驗證提供者會用不同的方式命名他們的流程,讓它成為品牌的一部分。 + +但最終,他們實作的都是相同的 OAuth2 標準。 + +/// + +FastAPI 在 `fastapi.security.oauth2` 中提供了所有這些 OAuth2 驗證流程的工具。 + +## 在裝飾器 `dependencies` 中使用 `Security` { #security-in-decorator-dependencies } + +就像你可以在裝飾器的 `dependencies` 參數中定義一個 `Depends` 的 `list` 一樣(詳見[路徑操作裝飾器中的相依性](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}),你也可以在那裡使用帶有 `scopes` 的 `Security`。 diff --git a/docs/zh-hant/docs/advanced/settings.md b/docs/zh-hant/docs/advanced/settings.md new file mode 100644 index 000000000..1ee5ad7cc --- /dev/null +++ b/docs/zh-hant/docs/advanced/settings.md @@ -0,0 +1,302 @@ +# 設定與環境變數 { #settings-and-environment-variables } + +在許多情況下,你的應用程式可能需要一些外部設定或組態,例如密鑰、資料庫憑證、電子郵件服務的憑證等。 + +這些設定大多是可變的(可能會改變),像是資料庫 URL。也有許多可能是敏感資訊,例如密鑰。 + +因此,通常會透過環境變數提供這些設定,讓應用程式去讀取。 + +/// tip + +若想了解環境變數,你可以閱讀[環境變數](../environment-variables.md){.internal-link target=_blank}。 + +/// + +## 型別與驗證 { #types-and-validation } + +這些環境變數只能處理文字字串,因為它們在 Python 之外,必須與其他程式與系統的其餘部分相容(甚至跨作業系統,如 Linux、Windows、macOS)。 + +這表示在 Python 中自環境變數讀取到的任何值都會是 `str`,而任何轉型成其他型別或驗證都必須在程式碼中完成。 + +## Pydantic `Settings` { #pydantic-settings } + +幸好,Pydantic 提供了很好的工具,可用來處理由環境變數而來的設定:Pydantic:設定管理。 + +### 安裝 `pydantic-settings` { #install-pydantic-settings } + +首先,請先建立你的[虛擬環境](../virtual-environments.md){.internal-link target=_blank},啟用它,然後安裝 `pydantic-settings` 套件: + +
+ +```console +$ pip install pydantic-settings +---> 100% +``` + +
+ +當你用 `all` extras 安裝時,它也會一併包含在內: + +
+ +```console +$ pip install "fastapi[all]" +---> 100% +``` + +
+ +### 建立 `Settings` 物件 { #create-the-settings-object } + +從 Pydantic 匯入 `BaseSettings` 並建立子類別,與建立 Pydantic model 的方式非常類似。 + +就像使用 Pydantic model 一樣,你用型別註解宣告類別屬性,並可選擇性地提供預設值。 + +你可以使用與 Pydantic model 相同的所有驗證功能與工具,例如不同的資料型別與透過 `Field()` 進行額外驗證。 + +{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *} + +/// tip + +如果你想要可以直接複製貼上的範例,先別用這個,請改用本文最後一個範例。 + +/// + +接著,當你建立該 `Settings` 類別的實例(此處為 `settings` 物件)時,Pydantic 會以不區分大小寫的方式讀取環境變數,因此,即使環境變數是大寫的 `APP_NAME`,也會被讀入屬性 `app_name`。 + +然後它會轉換並驗證資料。因此,當你使用該 `settings` 物件時,你會得到你宣告的型別的資料(例如 `items_per_user` 會是 `int`)。 + +### 使用 `settings` { #use-the-settings } + +接著你可以在應用程式中使用新的 `settings` 物件: + +{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *} + +### 執行伺服器 { #run-the-server } + +接下來,你可以在啟動伺服器時,將設定以環境變數傳入。舉例來說,你可以設定 `ADMIN_EMAIL` 與 `APP_NAME`: + +
+ +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +/// tip + +要為單一指令設定多個環境變數,只要用空白分隔它們,並全部放在指令前面即可。 + +/// + +如此一來,`admin_email` 設定會被設為 `"deadpool@example.com"`。 + +`app_name` 會是 `"ChimichangApp"`。 + +而 `items_per_user` 則會保留其預設值 `50`。 + +## 在另一個模組中的設定 { #settings-in-another-module } + +你也可以把這些設定放在另一個模組檔案中,就像在[更大的應用程式 - 多個檔案](../tutorial/bigger-applications.md){.internal-link target=_blank}所示。 + +例如,你可以有一個 `config.py` 檔案如下: + +{* ../../docs_src/settings/app01_py310/config.py *} + +然後在 `main.py` 檔案中使用它: + +{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *} + +/// tip + +你也需要一個 `__init__.py` 檔案,詳見[更大的應用程式 - 多個檔案](../tutorial/bigger-applications.md){.internal-link target=_blank}。 + +/// + +## 在相依中的設定 { #settings-in-a-dependency } + +在某些情境中,從相依(dependency)提供設定,會比在各處使用一個全域的 `settings` 物件更有用。 + +這在測試時特別實用,因為你可以很容易用自訂的設定來覆寫一個相依。 + +### 設定檔 { #the-config-file } + +延續前一個範例,你的 `config.py` 可以像這樣: + +{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *} + +注意現在我們不再建立預設實例 `settings = Settings()`。 + +### 主應用程式檔案 { #the-main-app-file } + +現在我們建立一個相依,回傳新的 `config.Settings()`。 + +{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *} + +/// tip + +我們稍後會討論 `@lru_cache`。 + +現在你可以先把 `get_settings()` 視為一般函式。 + +/// + +接著我們可以在*路徑操作函式 (path operation function)* 中將它宣告為相依,並在需要的地方使用它。 + +{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *} + +### 設定與測試 { #settings-and-testing } + +接著,在測試時要提供不同的設定物件會非常容易,只要為 `get_settings` 建立相依覆寫(dependency override)即可: + +{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *} + +在相依覆寫中,我們在建立新的 `Settings` 物件時設定 `admin_email` 的新值,然後回傳該新物件。 + +接著我們就可以測試它是否被使用。 + +## 讀取 `.env` 檔 { #reading-a-env-file } + +如果你有許多設定,而且在不同環境中可能常常變動,將它們放在一個檔案中,然後像讀取環境變數一樣自該檔案讀取,可能會很實用。 + +這種作法很常見,這些環境變數通常放在 `.env` 檔中,而該檔案被稱為「dotenv」。 + +/// tip + +在類 Unix 系統(如 Linux 與 macOS)中,以點(`.`)開頭的檔案是隱藏檔。 + +但 dotenv 檔並不一定必須使用這個確切的檔名。 + +/// + +Pydantic 透過外部函式庫支援讀取這類型的檔案。你可以閱讀更多:Pydantic Settings:Dotenv (.env) 支援。 + +/// tip + +要讓這個功能運作,你需要 `pip install python-dotenv`。 + +/// + +### `.env` 檔 { #the-env-file } + +你可以有一個 `.env` 檔如下: + +```bash +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +### 從 `.env` 讀取設定 { #read-settings-from-env } + +然後更新你的 `config.py`: + +{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *} + +/// tip + +`model_config` 屬性僅用於 Pydantic 的設定。你可以閱讀更多:Pydantic:概念:設定。 + +/// + +在這裡我們在 Pydantic 的 `Settings` 類別中定義設定 `env_file`,並將其值設為要使用的 dotenv 檔名。 + +### 使用 `lru_cache` 只建立一次 `Settings` { #creating-the-settings-only-once-with-lru-cache } + +從磁碟讀取檔案通常是昂貴(慢)的操作,所以你可能希望只做一次,然後重複使用同一個設定物件,而不是在每個請求都讀取。 + +但每次我們這樣做: + +```Python +Settings() +``` + +都會建立一個新的 `Settings` 物件,而且在建立時會再次讀取 `.env` 檔。 + +如果相依函式只是像這樣: + +```Python +def get_settings(): + return Settings() +``` + +我們就會為每個請求建立該物件,並在每個請求都讀取 `.env` 檔。⚠️ + +但由於我們在上方使用了 `@lru_cache` 裝飾器,`Settings` 物件只會在第一次呼叫時建立一次。✔️ + +{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *} + +之後在下一批請求的相依中任何對 `get_settings()` 的呼叫,都不會再執行 `get_settings()` 的內部程式碼與建立新的 `Settings` 物件,而是會一再回傳第一次呼叫時回傳的同一個物件。 + +#### `lru_cache` 技術細節 { #lru-cache-technical-details } + +`@lru_cache` 會修改它所裝飾的函式,使其回傳第一次回傳的相同值,而不是每次都重新計算、執行函式碼。 + +因此,被裝飾的函式對於每組參數組合只會執行一次。之後,凡是以完全相同參數組合呼叫時,都會重複使用先前對應的回傳值。 + +例如,如果你有一個函式: + +```Python +@lru_cache +def say_hi(name: str, salutation: str = "Ms."): + return f"Hello {salutation} {name}" +``` + +你的程式可能會這樣執行: + +```mermaid +sequenceDiagram + +participant code as Code +participant function as say_hi() +participant execute as Execute function + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Camila") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick", salutation="Mr.") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: return stored result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end +``` + +在我們的相依 `get_settings()` 這個案例中,該函式甚至不帶任何參數,因此它總是回傳相同的值。 + +如此一來,它的行為幾乎就像全域變數。但因為它使用相依函式,因此我們可以在測試時輕鬆將其覆寫。 + +`@lru_cache` 是 `functools` 的一部分,而 `functools` 是 Python 標準程式庫的一部分。你可以在Python 文件中閱讀 `@lru_cache` 以了解更多。 + +## 回顧 { #recap } + +你可以使用 Pydantic Settings 來處理應用程式的設定或組態,並享有 Pydantic model 的全部能力。 + +- 透過相依可以讓測試更容易。 +- 你可以搭配 `.env` 檔使用。 +- 使用 `@lru_cache` 可以避免每個請求都重複讀取 dotenv 檔,同時仍可在測試時覆寫設定。 diff --git a/docs/zh-hant/docs/advanced/sub-applications.md b/docs/zh-hant/docs/advanced/sub-applications.md new file mode 100644 index 000000000..773bc4b88 --- /dev/null +++ b/docs/zh-hant/docs/advanced/sub-applications.md @@ -0,0 +1,67 @@ +# 子應用程式 - 掛載 { #sub-applications-mounts } + +若你需要兩個彼此獨立的 FastAPI 應用程式,各自擁有獨立的 OpenAPI 與文件 UI,你可以有一個主應用,並「掛載」一個(或多個)子應用程式。 + +## 掛載一個 **FastAPI** 應用程式 { #mounting-a-fastapi-application } + +「掛載」是指在某個特定路徑下加入一個完全「獨立」的應用程式,之後該應用程式會負責處理該路徑底下的一切,使用該子應用程式中宣告的*路徑操作(path operation)*。 + +### 頂層應用程式 { #top-level-application } + +先建立主(頂層)**FastAPI** 應用程式以及它的*路徑操作*: + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *} + +### 子應用程式 { #sub-application } + +接著,建立你的子應用程式及其*路徑操作*。 + +這個子應用程式就是另一個標準的 FastAPI 應用,但這個會被「掛載」: + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *} + +### 掛載子應用程式 { #mount-the-sub-application } + +在你的頂層應用程式 `app` 中,掛載子應用程式 `subapi`。 + +在此範例中,它會被掛載在路徑 `/subapi`: + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *} + +### 檢查自動 API 文件 { #check-the-automatic-api-docs } + +現在,用你的檔案執行 `fastapi` 指令: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +然後開啟位於 http://127.0.0.1:8000/docs 的文件。 + +你會看到主應用的自動 API 文件,只包含它自己的*路徑操作*: + + + +接著,開啟子應用程式的文件:http://127.0.0.1:8000/subapi/docs。 + +你會看到子應用程式的自動 API 文件,只包含它自己的*路徑操作*,而且都在正確的子路徑前綴 `/subapi` 之下: + + + +如果你嘗試在任一介面中互動,它們都會正常運作,因為瀏覽器能與各自的應用程式或子應用程式通訊。 + +### 技術細節:`root_path` { #technical-details-root-path } + +當你像上面那樣掛載子應用程式時,FastAPI 會使用 ASGI 規範中的一個機制 `root_path`,將子應用程式的掛載路徑告知它。 + +如此一來,子應用程式就會知道在文件 UI 使用該路徑前綴。 + +而且子應用程式也能再掛載自己的子應用程式,一切都能正確運作,因為 FastAPI 會自動處理所有這些 `root_path`。 + +你可以在[在代理伺服器之後](behind-a-proxy.md){.internal-link target=_blank}一節中進一步了解 `root_path` 與如何顯式使用它。 diff --git a/docs/zh-hant/docs/advanced/templates.md b/docs/zh-hant/docs/advanced/templates.md new file mode 100644 index 000000000..ffc7599ae --- /dev/null +++ b/docs/zh-hant/docs/advanced/templates.md @@ -0,0 +1,126 @@ +# 模板 { #templates } + +你可以在 **FastAPI** 中使用任意你想要的模板引擎。 + +常見的選擇是 Jinja2,與 Flask 與其他工具所使用的一樣。 + +有一些工具可讓你輕鬆設定,並可直接在你的 **FastAPI** 應用程式中使用(由 Starlette 提供)。 + +## 安裝相依套件 { #install-dependencies } + +請先建立一個[虛擬環境](../virtual-environments.md){.internal-link target=_blank}、啟用它,然後安裝 `jinja2`: + +
+ +```console +$ pip install jinja2 + +---> 100% +``` + +
+ +## 使用 `Jinja2Templates` { #using-jinja2templates } + +- 匯入 `Jinja2Templates`。 +- 建立一個可重複使用的 `templates` 物件。 +- 在會回傳模板的「*路徑操作(path operation)*」中宣告一個 `Request` 參數。 +- 使用你建立的 `templates` 來渲染並回傳 `TemplateResponse`,傳入模板名稱、`request` 物件,以及在 Jinja2 模板中使用的「context」鍵值對字典。 + +{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *} + +/// note + +在 FastAPI 0.108.0、Starlette 0.29.0 之前,`name` 是第一個參數。 + +此外,在更早的版本中,`request` 物件是作為 context 的鍵值對之一傳給 Jinja2 的。 + +/// + +/// tip + +透過宣告 `response_class=HTMLResponse`,文件 UI 能夠知道回應將會是 HTML。 + +/// + +/// note | 技術細節 + +你也可以使用 `from starlette.templating import Jinja2Templates`。 + +**FastAPI** 以 `fastapi.templating` 的形式提供與 `starlette.templating` 相同的內容,僅為了方便你(開發者)。但大多數可用的回應類別都直接來自 Starlette,`Request` 與 `StaticFiles` 也是如此。 + +/// + +## 撰寫模板 { #writing-templates } + +接著你可以在 `templates/item.html` 編寫模板,例如: + +```jinja hl_lines="7" +{!../../docs_src/templates/templates/item.html!} +``` + +### 模板 context 值 { #template-context-values } + +在包含以下內容的 HTML 中: + +{% raw %} + +```jinja +Item ID: {{ id }} +``` + +{% endraw %} + +...它會顯示你在傳入的 context `dict` 中提供的 `id`: + +```Python +{"id": id} +``` + +例如,若 ID 為 `42`,會渲染為: + +```html +Item ID: 42 +``` + +### 模板 `url_for` 參數 { #template-url-for-arguments } + +你也可以在模板中使用 `url_for()`,它所接受的參數與你的「*路徑操作函式(path operation function)*」所使用的參數相同。 + +因此,包含以下內容的區塊: + +{% raw %} + +```jinja + +``` + +{% endraw %} + +...會產生指向與「*路徑操作函式*」`read_item(id=id)` 相同 URL 的連結。 + +例如,若 ID 為 `42`,會渲染為: + +```html + +``` + +## 模板與靜態檔案 { #templates-and-static-files } + +你也可以在模板中使用 `url_for()`,例如搭配你以 `name="static"` 掛載的 `StaticFiles` 使用。 + +```jinja hl_lines="4" +{!../../docs_src/templates/templates/item.html!} +``` + +在這個例子中,它會連結到 `static/styles.css` 的 CSS 檔案,內容為: + +```CSS hl_lines="4" +{!../../docs_src/templates/static/styles.css!} +``` + +而且因為你使用了 `StaticFiles`,該 CSS 檔案會由你的 **FastAPI** 應用程式在 URL `/static/styles.css` 自動提供。 + +## 更多細節 { #more-details } + +想了解更多細節(包含如何測試模板),請參考 Starlette 的模板說明文件。 diff --git a/docs/zh-hant/docs/advanced/testing-dependencies.md b/docs/zh-hant/docs/advanced/testing-dependencies.md new file mode 100644 index 000000000..3ce72ccab --- /dev/null +++ b/docs/zh-hant/docs/advanced/testing-dependencies.md @@ -0,0 +1,53 @@ +# 用覆寫測試相依 { #testing-dependencies-with-overrides } + +## 在測試期間覆寫相依 { #overriding-dependencies-during-testing } + +有些情境你可能想在測試時覆寫(override)某個相依(dependency)。 + +你不希望執行原本的相依(以及它可能具有的任何子相依)。 + +相反地,你想提供一個只在測試期間使用的不同相依(可能只在特定測試中),並回傳一個可以在原本相依值被使用之處使用的值。 + +### 使用情境:外部服務 { #use-cases-external-service } + +例如你有一個需要呼叫的外部驗證提供者。 + +你傳送一個 token,它會回傳一個已驗證的使用者。 + +這個提供者可能按每個請求收費,而且呼叫它可能比在測試中使用固定的模擬使用者多花一些時間。 + +你大概只想對外部提供者測試一次,而不需要在每個測試都呼叫它。 + +在這種情況下,你可以覆寫用來呼叫該提供者的相依,並在測試中使用自訂的相依來回傳一個模擬使用者。 + +### 使用 `app.dependency_overrides` 屬性 { #use-the-app-dependency-overrides-attribute } + +對這些情況,你的 FastAPI 應用程式有一個屬性 `app.dependency_overrides`,它是一個簡單的 `dict`。 + +要在測試時覆寫某個相依,把原始相依(函式)作為鍵,並把你的覆寫相依(另一個函式)作為值。 + +接著 FastAPI 會呼叫這個覆寫,而不是原本的相依。 + +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} + +/// tip + +你可以為應用程式中任何地方使用到的相依設定覆寫。 + +原始相依可以用在*路徑操作函式*、*路徑操作裝飾器*(當你不使用其回傳值時)、`.include_router()` 呼叫等。 + +FastAPI 仍然能夠將其覆寫。 + +/// + +然後你可以將 `app.dependency_overrides` 設為空的 `dict` 以重設(移除)所有覆寫: + +```Python +app.dependency_overrides = {} +``` + +/// tip + +如果只想在某些測試中覆寫相依,你可以在測試開始時(測試函式內)設定覆寫,並在結束時(測試函式結尾)重設。 + +/// diff --git a/docs/zh-hant/docs/advanced/testing-events.md b/docs/zh-hant/docs/advanced/testing-events.md new file mode 100644 index 000000000..db678977a --- /dev/null +++ b/docs/zh-hant/docs/advanced/testing-events.md @@ -0,0 +1,11 @@ +# 測試事件:lifespan 與 startup - shutdown { #testing-events-lifespan-and-startup-shutdown } + +當你需要在測試中執行 lifespan(生命週期)時,你可以使用 TestClient 並搭配 with 陳述式: + +{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *} + +你可以閱讀更多細節:[在測試中執行 lifespan](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)(Starlette 官方文件)。 + +對於已棄用的 `startup` 和 `shutdown` 事件,你可以這樣使用 TestClient: + +{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *} diff --git a/docs/zh-hant/docs/advanced/testing-websockets.md b/docs/zh-hant/docs/advanced/testing-websockets.md new file mode 100644 index 000000000..7348e60fd --- /dev/null +++ b/docs/zh-hant/docs/advanced/testing-websockets.md @@ -0,0 +1,13 @@ +# 測試 WebSocket { #testing-websockets } + +你可以使用相同的 `TestClient` 來測試 WebSocket。 + +為此,你可以在 `with` 陳述式中使用 `TestClient`,連線到該 WebSocket: + +{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *} + +/// note | 注意 + +想了解更多,請參考 Starlette 的 測試 WebSocket 文件。 + +/// diff --git a/docs/zh-hant/docs/advanced/using-request-directly.md b/docs/zh-hant/docs/advanced/using-request-directly.md new file mode 100644 index 000000000..f1dae9bf2 --- /dev/null +++ b/docs/zh-hant/docs/advanced/using-request-directly.md @@ -0,0 +1,56 @@ +# 直接使用 Request { #using-the-request-directly } + +到目前為止,你都是用對應的型別來宣告你需要的請求各部分。 + +例如從以下來源取得資料: + +- 路徑中的參數。 +- 標頭。 +- Cookies。 +- 等等。 + +這麼做時,FastAPI 會自動驗證並轉換這些資料,還會為你的 API 產生文件。 + +但有些情況你可能需要直接存取 `Request` 物件。 + +## 關於 `Request` 物件的細節 { #details-about-the-request-object } + +由於 FastAPI 底層其實是 Starlette,再加上一層工具,因此在需要時你可以直接使用 Starlette 的 `Request` 物件。 + +同時也代表,如果你直接從 `Request` 物件取得資料(例如讀取 body),FastAPI 不會替它做驗證、轉換或文件化(透過 OpenAPI 為自動化的 API 介面產生文件)。 + +不過,其他以一般方式宣告的參數(例如以 Pydantic 模型宣告的 body)仍然會被驗證、轉換、加上標註等。 + +但在某些特定情境下,直接取得 `Request` 物件會很實用。 + +## 直接使用 `Request` 物件 { #use-the-request-object-directly } + +假設你想在你的 路徑操作函式(path operation function) 中取得用戶端的 IP 位址/主機。 + +為此,你需要直接存取請求。 + +{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *} + +只要在 路徑操作函式 中宣告一個型別為 `Request` 的參數,FastAPI 就會將當前的 `Request` 傳入該參數。 + +/// tip + +注意在這個例子中,除了 request 參數之外,我們也宣告了一個路徑參數。 + +因此,路徑參數會被擷取、驗證、轉換為指定型別,並在 OpenAPI 中加入標註。 + +同理,你可以照常宣告其他參數,並另外同時取得 `Request`。 + +/// + +## `Request` 文件 { #request-documentation } + +你可以在 Starlette 官方文件站點中的 `Request` 物件 了解更多細節。 + +/// note | 技術細節 + +你也可以使用 `from starlette.requests import Request`。 + +FastAPI 之所以直接提供它,是為了讓開發者更方便;但它本身是來自 Starlette。 + +/// diff --git a/docs/zh-hant/docs/advanced/websockets.md b/docs/zh-hant/docs/advanced/websockets.md new file mode 100644 index 000000000..c4e904f6f --- /dev/null +++ b/docs/zh-hant/docs/advanced/websockets.md @@ -0,0 +1,186 @@ +# WebSockets { #websockets } + +你可以在 **FastAPI** 中使用 WebSockets。 + +## 安裝 `websockets` { #install-websockets } + +請先建立[虛擬環境](../virtual-environments.md){.internal-link target=_blank}、啟用它,然後安裝 `websockets`(一個讓你更容易使用「WebSocket」通訊協定的 Python 套件): + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## WebSockets 用戶端 { #websockets-client } + +### 在生產環境 { #in-production } + +在你的生產系統中,你很可能有一個使用現代框架(如 React、Vue.js 或 Angular)建立的前端。 + +而為了透過 WebSockets 與後端通訊,你通常會使用前端的工具。 + +或者你可能有一個原生行動應用,使用原生程式碼直接與 WebSocket 後端通訊。 + +又或者你有其他任何方式與 WebSocket 端點通訊。 + +--- + +但在這個範例中,我們會用一個非常簡單的 HTML 文件與一些 JavaScript,全都寫在一個長字串裡。 + +當然,這並不理想,你不會在生產環境這樣做。 + +在生產環境你通常會用上述其中一種方式。 + +但這是能讓我們專注於 WebSocket 伺服端並跑起一個可運作範例的最簡單方式: + +{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} + +## 建立一個 `websocket` { #create-a-websocket } + +在你的 **FastAPI** 應用中,建立一個 `websocket`: + +{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} + +/// note | 技術細節 + +你也可以使用 `from starlette.websockets import WebSocket`。 + +**FastAPI** 直接提供相同的 `WebSocket` 只是為了方便你這位開發者,但它其實是直接來自 Starlette。 + +/// + +## 等待與傳送訊息 { #await-for-messages-and-send-messages } + +在你的 WebSocket 路由中,你可以 `await` 接收訊息並傳送訊息。 + +{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} + +你可以接收與傳送二進位、文字與 JSON 資料。 + +## 試試看 { #try-it } + +如果你的檔案名為 `main.py`,用以下指令執行應用: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +在瀏覽器開啟 http://127.0.0.1:8000。 + +你會看到一個像這樣的簡單頁面: + + + +你可以在輸入框輸入訊息並送出: + + + +你的 **FastAPI** 應用會透過 WebSockets 回應: + + + +你可以傳送(與接收)多則訊息: + + + +而且它們都會使用同一個 WebSocket 連線。 + +## 使用 `Depends` 與其他功能 { #using-depends-and-others } + +在 WebSocket 端點中,你可以從 `fastapi` 匯入並使用: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +它們的運作方式與其他 FastAPI 端點/*路徑操作* 相同: + +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} + +/// info + +因為這是 WebSocket,拋出 `HTTPException` 並沒有意義,因此我們改為拋出 `WebSocketException`。 + +你可以使用規範中定義的有效關閉代碼之一。 + +/// + +### 用依賴試用 WebSocket { #try-the-websockets-with-dependencies } + +如果你的檔案名為 `main.py`,用以下指令執行應用: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +在瀏覽器開啟 http://127.0.0.1:8000。 + +在那裡你可以設定: + +* "Item ID",用於路徑。 +* "Token",作為查詢參數。 + +/// tip + +注意查詢參數 `token` 會由一個依賴處理。 + +/// + +之後你就能連線到 WebSocket,並開始收發訊息: + + + +## 處理斷線與多個用戶端 { #handling-disconnections-and-multiple-clients } + +當 WebSocket 連線關閉時,`await websocket.receive_text()` 會拋出 `WebSocketDisconnect` 例外,你可以像範例中那樣捕捉並處理。 + +{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} + +試用方式: + +* 用多個瀏覽器分頁開啟該應用。 +* 從每個分頁傳送訊息。 +* 然後關閉其中一個分頁。 + +這會引發 `WebSocketDisconnect` 例外,其他所有用戶端都會收到類似以下的訊息: + +``` +Client #1596980209979 left the chat +``` + +/// tip + +上面的應用是一個極簡範例,用來示範如何處理並向多個 WebSocket 連線廣播訊息。 + +但請注意,因為所有狀態都在記憶體中的單一 list 裡管理,它只會在該程序執行期間生效,且僅適用於單一程序。 + +如果你需要一個容易與 FastAPI 整合、但更健壯,且可由 Redis、PostgreSQL 等後端支援的方案,請參考 encode/broadcaster。 + +/// + +## 更多資訊 { #more-info } + +想了解更多選項,請參考 Starlette 的文件: + +* `WebSocket` 類別。 +* 以類別為基礎的 WebSocket 處理。 diff --git a/docs/zh-hant/docs/advanced/wsgi.md b/docs/zh-hant/docs/advanced/wsgi.md new file mode 100644 index 000000000..9d03b5692 --- /dev/null +++ b/docs/zh-hant/docs/advanced/wsgi.md @@ -0,0 +1,51 @@ +# 包含 WSGI:Flask、Django 等 { #including-wsgi-flask-django-others } + +你可以像在 [子應用程式 - 掛載](sub-applications.md){.internal-link target=_blank}、[在 Proxy 後方](behind-a-proxy.md){.internal-link target=_blank} 中所見那樣掛載 WSGI 應用。 + +為此,你可以使用 `WSGIMiddleware` 來包住你的 WSGI 應用,例如 Flask、Django 等。 + +## 使用 `WSGIMiddleware` { #using-wsgimiddleware } + +/// info + +這需要先安裝 `a2wsgi`,例如使用 `pip install a2wsgi`。 + +/// + +你需要從 `a2wsgi` 匯入 `WSGIMiddleware`。 + +然後用該 middleware 包住 WSGI(例如 Flask)應用。 + +接著把它掛載到某個路徑下。 + +{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *} + +/// note + +先前建議使用來自 `fastapi.middleware.wsgi` 的 `WSGIMiddleware`,但現在已棄用。 + +建議改用 `a2wsgi` 套件。用法保持相同。 + +只要確保已安裝 `a2wsgi`,並從 `a2wsgi` 正確匯入 `WSGIMiddleware` 即可。 + +/// + +## 試試看 { #check-it } + +現在,位於路徑 `/v1/` 底下的所有請求都會由 Flask 應用處理。 + +其餘則由 **FastAPI** 處理。 + +如果你啟動它並前往 http://localhost:8000/v1/,你會看到來自 Flask 的回應: + +```txt +Hello, World from Flask! +``` + +如果你前往 http://localhost:8000/v2,你會看到來自 FastAPI 的回應: + +```JSON +{ + "message": "Hello World" +} +``` diff --git a/docs/zh-hant/docs/alternatives.md b/docs/zh-hant/docs/alternatives.md new file mode 100644 index 000000000..340c47d8b --- /dev/null +++ b/docs/zh-hant/docs/alternatives.md @@ -0,0 +1,485 @@ +# 替代方案、靈感與比較 { #alternatives-inspiration-and-comparisons } + +啟發 FastAPI 的來源、與其他方案的比較,以及從中學到的內容。 + +## 介紹 { #intro } + +沒有前人的工作,就不會有 **FastAPI**。 + +在它誕生之前,已經有許多工具啟發了它的設計。 + +我多年來一直避免打造新框架。起初我嘗試用許多不同的框架、外掛與工具,來實作 **FastAPI** 涵蓋的所有功能。 + +但在某個時間點,除了創建一個能提供所有這些功能、汲取前人工具的優點,並以最佳方式組合起來、同時運用過去甚至不存在的語言特性(Python 3.6+ 的型別提示)之外,已別無他法。 + +## 先前的工具 { #previous-tools } + +### Django { #django } + +它是最受歡迎且廣受信任的 Python 框架。像 Instagram 等系統就是用它打造的。 + +它與關聯式資料庫(如 MySQL 或 PostgreSQL)相對緊密耦合,因此要以 NoSQL 資料庫(如 Couchbase、MongoDB、Cassandra 等)作為主要儲存引擎並不容易。 + +它一開始是為在後端產生 HTML 而設計,而非為了建立提供現代前端(如 React、Vue.js、Angular)或其他系統(如 IoT 裝置)使用的 API。 + +### Django REST Framework { #django-rest-framework } + +Django REST framework 的目標是成為一套在 Django 之上構建 Web API 的彈性工具組,以強化其 API 能力。 + +它被 Mozilla、Red Hat、Eventbrite 等眾多公司使用。 + +它是「自動 API 文件」的早期典範之一,而這正是啟發我「尋找」**FastAPI** 的第一個想法。 + +/// note | 注意 + +Django REST Framework 由 Tom Christie 創建。他同時也是 Starlette 與 Uvicorn 的作者,而 **FastAPI** 就是建立在它們之上。 + +/// + +/// check | 啟發 **FastAPI** + +提供自動化的 API 文件網頁使用者介面。 + +/// + +### Flask { #flask } + +Flask 是一個「微框架」,它不包含資料庫整合,也沒有像 Django 那樣內建許多功能。 + +這種簡單與彈性,讓你可以把 NoSQL 資料庫作為主要的資料儲存系統。 + +由於它非常簡單,學起來相對直觀,儘管文件在某些地方會變得較技術性。 + +它也常用於其他不一定需要資料庫、使用者管理或 Django 內建眾多功能的應用程式。雖然這些功能中的許多都可以用外掛新增。 + +這種元件的解耦,以及作為可擴充以精準滿足需求的「微框架」,是我想要保留的關鍵特性。 + +基於 Flask 的簡潔,它看起來很適合用來構建 API。接下來要找的,就是 Flask 世界裡的「Django REST Framework」。 + +/// check | 啟發 **FastAPI** + +成為一個微框架,讓所需的工具與元件能輕鬆搭配組合。 + +具備簡單、易用的路由系統。 + +/// + +### Requests { #requests } + +**FastAPI** 其實不是 **Requests** 的替代品。兩者的範疇截然不同。 + +在 FastAPI 應用程式「內部」使用 Requests 其實很常見。 + +儘管如此,FastAPI 仍從 Requests 得到了不少啟發。 + +**Requests** 是一個「與 API 互動」(作為用戶端)的程式庫,而 **FastAPI** 是一個「建立 API」(作為伺服端)的程式庫。 + +它們大致位於相反兩端,彼此互補。 + +Requests 設計非常簡單直觀、容易使用,且有合理的預設值。同時它也非常強大且可自訂。 + +因此,如其官網所言: + +> Requests is one of the most downloaded Python packages of all time + +用法非常簡單。例如,發出一個 `GET` 請求,你會寫: + +```Python +response = requests.get("http://example.com/some/url") +``` + +相對地,FastAPI 的 API 路徑操作(path operation)可能像這樣: + +```Python hl_lines="1" +@app.get("/some/url") +def read_url(): + return {"message": "Hello World"} +``` + +看看 `requests.get(...)` 與 `@app.get(...)` 的相似之處。 + +/// check | 啟發 **FastAPI** + +* 擁有簡單直觀的 API。 +* 直接使用 HTTP 方法名稱(操作),以直接、直觀的方式表達。 +* 具備合理的預設值,同時提供強大的自訂能力。 + +/// + +### Swagger / OpenAPI { #swagger-openapi } + +我想從 Django REST Framework 得到的主要功能是自動 API 文件。 + +後來我發現有一個使用 JSON(或 YAML,JSON 的延伸)來描述 API 的標準,叫做 Swagger。 + +而且已有對 Swagger API 的網頁使用者介面。因此,只要能為 API 產生 Swagger 文件,就可以自動使用這個網頁介面。 + +之後 Swagger 交由 Linux 基金會管理,並更名為 OpenAPI。 + +因此,談到 2.0 版時常說「Swagger」,而 3+ 版則是「OpenAPI」。 + +/// check | 啟發 **FastAPI** + +採用並使用開放的 API 規格標準,而非自訂格式。 + +並整合基於標準的使用者介面工具: + +* Swagger UI +* ReDoc + +選擇這兩個是因為它們相當受歡迎且穩定,但稍加搜尋,你會發現有數十種 OpenAPI 的替代使用者介面(都能與 **FastAPI** 一起使用)。 + +/// + +### Flask 的 REST 框架 { #flask-rest-frameworks } + +有幾個 Flask 的 REST 框架,但在投入時間調查後,我發現許多已停止維護或被棄置,且存在一些關鍵問題使之不適用。 + +### Marshmallow { #marshmallow } + +API 系統需要的主要功能之一是資料「序列化」,也就是把程式中的資料(Python)轉成能透過網路傳輸的形式。例如,將含有資料庫資料的物件轉成 JSON 物件、把 `datetime` 物件轉成字串等等。 + +API 需要的另一個重要功能是資料驗證,確保資料在特定條件下有效。例如,某個欄位必須是 `int`,而不是隨便的字串。這對於輸入資料特別有用。 + +沒有資料驗證系統的話,你就得在程式碼中手動逐一檢查。 + +這些功能正是 Marshmallow 所要提供的。它是很棒的函式庫,我之前也大量使用。 + +但它誕生於 Python 型別提示出現之前。因此,為了定義每個 結構(schema),你需要使用 Marshmallow 提供的特定工具與類別。 + +/// check | 啟發 **FastAPI** + +用程式碼定義能自動提供資料型別與驗證的「schemas」。 + +/// + +### Webargs { #webargs } + +API 所需的另一項大功能,是從傳入請求中解析資料。 + +Webargs 是在多個框架(包含 Flask)之上提供該功能的工具。 + +它底層使用 Marshmallow 來做資料驗證,且由同一群開發者建立。 + +它是一個很棒的工具,在有 **FastAPI** 之前我也經常使用。 + +/// info + +Webargs 由與 Marshmallow 相同的開發者創建。 + +/// + +/// check | 啟發 **FastAPI** + +自動驗證傳入請求資料。 + +/// + +### APISpec { #apispec } + +Marshmallow 與 Webargs 以外掛提供驗證、解析與序列化。 + +但文件仍然缺失,於是 APISpec 出現了。 + +它是多個框架的外掛(Starlette 也有對應外掛)。 + +它的作法是:你在處理路由的每個函式的 docstring 中,用 YAML 格式撰寫結構定義。 + +然後它會產生 OpenAPI schemas。 + +在 Flask、Starlette、Responder 等框架中都是這樣運作。 + +但這又帶來一個問題:在 Python 字串中(大型 YAML)加入一段微語法。 + +編輯器幫不上太多忙。而且如果我們修改了參數或 Marshmallow 的 schemas 卻忘了同步修改 YAML docstring,產生的結構就會過時。 + +/// info + +APISpec 由與 Marshmallow 相同的開發者創建。 + +/// + +/// check | 啟發 **FastAPI** + +支援 API 的開放標準 OpenAPI。 + +/// + +### Flask-apispec { #flask-apispec } + +這是一個 Flask 外掛,把 Webargs、Marshmallow 與 APISpec 串在一起。 + +它使用 Webargs 與 Marshmallow 的資訊,透過 APISpec 自動產生 OpenAPI 結構。 + +它是個很棒但被低估的工具。它理應比許多 Flask 外掛更受歡迎,可能因為它的文件過於簡潔與抽象。 + +這解決了在 Python 文件字串中撰寫 YAML(另一種語法)的问题。 + +在打造 **FastAPI** 前,我最喜歡的後端技術組合就是 Flask、Flask-apispec、Marshmallow 與 Webargs。 + +使用它促成了數個 Flask 全端(full-stack)產生器。這些是我(以及若干外部團隊)至今主要使用的技術組合: + +* https://github.com/tiangolo/full-stack +* https://github.com/tiangolo/full-stack-flask-couchbase +* https://github.com/tiangolo/full-stack-flask-couchdb + +而這些全端產生器,也成為了 [**FastAPI** 專案產生器](project-generation.md){.internal-link target=_blank} 的基礎。 + +/// info + +Flask-apispec 由與 Marshmallow 相同的開發者創建。 + +/// + +/// check | 啟發 **FastAPI** + +從定義序列化與驗證的相同程式碼,自動產生 OpenAPI 結構(schema)。 + +/// + +### NestJS(與 Angular) { #nestjs-and-angular } + +這甚至不是 Python。NestJS 是受 Angular 啟發的 JavaScript(TypeScript)NodeJS 框架。 + +它達成的效果與 Flask-apispec 能做的有點相似。 + +它有一套受 Angular 2 啟發的整合式相依性注入(Dependency Injection)系統。需要預先註冊「可注入」元件(就像我所知的其他相依性注入系統一樣),因此會增加冗長與重複程式碼。 + +由於參數以 TypeScript 型別描述(與 Python 型別提示相似),編輯器支援相當不錯。 + +但因為 TypeScript 的型別在編譯成 JavaScript 後不會被保留,它無法僅靠型別同時定義驗證、序列化與文件。由於這點以及部分設計決定,若要取得驗證、序列化與自動結構產生,就需要在許多地方加上裝飾器,因此會相當冗長。 + +它無法很好地處理巢狀模型。若請求的 JSON 主體中有內層欄位,且這些內層欄位又是巢狀 JSON 物件,就無法被妥善地文件化與驗證。 + +/// check | 啟發 **FastAPI** + +使用 Python 型別以獲得優秀的編輯器支援。 + +提供強大的相依性注入系統,並想辦法將重複程式碼降到最低。 + +/// + +### Sanic { #sanic } + +它是最早基於 `asyncio` 的極高速 Python 框架之一,並做得很像 Flask。 + +/// note | 技術細節 + +它使用 `uvloop` 取代預設的 Python `asyncio` 事件圈。這也是它如此之快的原因。 + +它明顯啟發了 Uvicorn 與 Starlette,而在公開的基準測試中,它們目前比 Sanic 更快。 + +/// + +/// check | 啟發 **FastAPI** + +想辦法達到瘋狂的效能。 + +這就是為什麼 **FastAPI** 建立於 Starlette 之上,因為它是可用的最快框架(由第三方評測)。 + +/// + +### Falcon { #falcon } + +Falcon 是另一個高效能 Python 框架,設計上極簡,並作為其他框架(如 Hug)的基礎。 + +它設計為函式接收兩個參數,一個是「request」,一個是「response」。然後你從 request「讀取」資料、往 response「寫入」資料。由於這種設計,無法使用標準的 Python 型別提示,直接以函式參數宣告請求參數與主體。 + +因此,資料驗證、序列化與文件必須以程式碼手動完成,無法自動化。或者需在 Falcon 之上實作另一層框架(如 Hug)。其他受 Falcon 設計啟發的框架也有同樣的區別:將 request 與 response 物件作為參數。 + +/// check | 啟發 **FastAPI** + +設法取得優秀的效能。 + +連同 Hug(Hug 建立於 Falcon 之上)一起,也啟發 **FastAPI** 在函式中宣告一個 `response` 參數。 + +不過在 FastAPI 中它是可選的,主要用來設定標頭、Cookie 與替代狀態碼。 + +/// + +### Molten { #molten } + +我在 **FastAPI** 打造的早期發現了 Molten。它有一些相當類似的想法: + +* 基於 Python 型別提示。 +* 從這些型別取得驗證與文件。 +* 相依性注入系統。 + +它沒有使用像 Pydantic 這樣的第三方資料驗證、序列化與文件庫,而是有自己的。因此,這些資料型別定義較不容易重複使用。 + +它需要更為冗長的設定。而且因為它基於 WSGI(而非 ASGI),並未設計來享受如 Uvicorn、Starlette、Sanic 等工具所提供的高效能。 + +其相依性注入系統需要預先註冊依賴,並且依據宣告的型別來解析依賴。因此,無法宣告多個能提供相同型別的「元件」。 + +路由需要在單一地方宣告,使用在其他地方宣告的函式(而不是用可以直接放在端點處理函式上方的裝飾器)。這更接近 Django 的作法,而不是 Flask(與 Starlette)的作法。它在程式碼中分離了其實相當緊密耦合的事物。 + +/// check | 啟發 **FastAPI** + +用模型屬性的「預設值」來定義資料型別的額外驗證。這提升了編輯器支援,而這在當時的 Pydantic 還不支援。 + +這實際上也啟發了 Pydantic 的部分更新,以支援相同的驗證宣告風格(這些功能現在已在 Pydantic 中可用)。 + +/// + +### Hug { #hug } + +Hug 是最早使用 Python 型別提示來宣告 API 參數型別的框架之一。這是個很棒的點子,也啟發了其他工具。 + +它在宣告中使用自訂型別而非標準 Python 型別,但仍然是巨大的一步。 + +它也是最早能以 JSON 產出自訂結構、描述整個 API 的框架之一。 + +它不是基於 OpenAPI 與 JSON Schema 等標準。因此,與其他工具(如 Swagger UI)的整合並不直覺。但它仍是一個非常創新的想法。 + +它有個有趣、少見的功能:同一個框架可同時建立 API 與 CLI。 + +由於它基於同步 Python 網頁框架的舊標準(WSGI),無法處理 WebSocket 與其他功能,儘管效能仍然很高。 + +/// info + +Hug 由 Timothy Crosley 創建,他同時也是 `isort` 的作者,一個自動排序 Python 匯入的好工具。 + +/// + +/// check | 啟發 **FastAPI** 的想法 + +Hug 啟發了 APIStar 的部分設計,也是我覺得最有前景的工具之一,與 APIStar 並列。 + +Hug 啟發 **FastAPI** 使用 Python 型別提示宣告參數,並自動產生定義 API 的結構。 + +Hug 啟發 **FastAPI** 在函式中宣告 `response` 參數以設定標頭與 Cookie。 + +/// + +### APIStar (<= 0.5) { #apistar-0-5 } + +在決定打造 **FastAPI** 之前,我找到了 **APIStar** 伺服器。它幾乎具備我所尋找的一切,而且設計很出色。 + +它是我見過最早使用 Python 型別提示來宣告參數與請求的框架實作之一(早於 NestJS 與 Molten)。我與 Hug 幾乎在同時間發現它。不過 APIStar 使用的是 OpenAPI 標準。 + +它基於相同的型別提示,在多處自動進行資料驗證、資料序列化與 OpenAPI 結構產生。 + +主體結構(body schema)的定義並未使用像 Pydantic 那樣的 Python 型別提示,更像 Marshmallow,因此編輯器支援沒有那麼好,但整體而言,APIStar 是當時最好的選擇。 + +它在當時的效能評測中名列前茅(僅被 Starlette 超越)。 + +一開始它沒有自動 API 文件的網頁 UI,但我知道我可以替它加上 Swagger UI。 + +它有相依性注入系統。需要預先註冊元件,與上面提到的其他工具相同。不過這仍是很棒的功能。 + +我從未能在完整專案中使用它,因為它沒有安全性整合,所以無法取代我用 Flask-apispec 全端產生器所擁有的全部功能。我曾把新增該功能的 pull request 放在待辦清單中。 + +但之後,專案的重心改變了。 + +它不再是 API 網頁框架,因為作者需要專注於 Starlette。 + +現在的 APIStar 是一套用於驗證 OpenAPI 規格的工具,不是網頁框架。 + +/// info + +APIStar 由 Tom Christie 創建。他也創建了: + +* Django REST Framework +* Starlette(**FastAPI** 建立於其上) +* Uvicorn(Starlette 與 **FastAPI** 使用) + +/// + +/// check | 啟發 **FastAPI** + +存在。 + +用相同的 Python 型別同時宣告多件事(資料驗證、序列化與文件),並同時提供出色的編輯器支援,這是一個極好的點子。 + +在長時間尋找並測試多種不同替代方案後,APIStar 是最好的可用選擇。 + +當 APIStar 不再作為伺服器存在,而 Starlette 誕生並成為更好的基礎時,這成為打造 **FastAPI** 的最後一個靈感。 + +我將 **FastAPI** 視為 APIStar 的「精神繼承者」,同時基於所有這些先前工具的經驗,改進並擴增了功能、型別系統與其他部分。 + +/// + +## **FastAPI** 所採用的工具 { #used-by-fastapi } + +### Pydantic { #pydantic } + +Pydantic 是基於 Python 型別提示,定義資料驗證、序列化與文件(使用 JSON Schema)的函式庫。 + +這讓它非常直覺。 + +它可與 Marshmallow 相提並論。儘管在效能測試中它比 Marshmallow 更快。而且因為它基於相同的 Python 型別提示,編輯器支援也很出色。 + +/// check | **FastAPI** 用於 + +處理所有資料驗證、資料序列化與自動模型文件(基於 JSON Schema)。 + +**FastAPI** 接著會把這些 JSON Schema 資料放入 OpenAPI 中,此外還有其他許多功能。 + +/// + +### Starlette { #starlette } + +Starlette 是一個輕量的 ASGI 框架/工具集,非常適合用來建構高效能的 asyncio 服務。 + +它非常簡單直觀。設計上易於擴充,且元件化。 + +它具備: + +* 令人印象深刻的效能。 +* WebSocket 支援。 +* 行程內(in-process)背景任務。 +* 啟動與關閉事件。 +* 建立在 HTTPX 上的測試用戶端。 +* CORS、GZip、靜態檔案、串流回應。 +* Session 與 Cookie 支援。 +* 100% 測試涵蓋率。 +* 100% 型別註解的程式碼庫。 +* 幾乎沒有硬性相依。 + +Starlette 目前是測試中最快的 Python 框架。僅次於 Uvicorn(它不是框架,而是伺服器)。 + +Starlette 提供所有網頁微框架的基礎功能。 + +但它不提供自動的資料驗證、序列化或文件。 + +這正是 **FastAPI** 在其上方加入的主要功能之一,且全部基於 Python 型別提示(使用 Pydantic)。此外還有相依性注入系統、安全性工具、OpenAPI 結構產生等。 + +/// note | 技術細節 + +ASGI 是由 Django 核心團隊成員正在開發的新「標準」。它尚未成為「Python 標準」(PEP),但他們正著手進行中。 + +儘管如此,它已被多個工具作為「標準」採用。這大幅提升了互通性,例如你可以把 Uvicorn 換成其他 ASGI 伺服器(如 Daphne 或 Hypercorn),或加入相容 ASGI 的工具,如 `python-socketio`。 + +/// + +/// check | **FastAPI** 用於 + +處理所有核心網頁部分,並在其上加上功能。 + +`FastAPI` 這個類別本身直接繼承自 `Starlette` 類別。 + +因此,凡是你能用 Starlette 做的事,你幾乎都能直接用 **FastAPI** 完成,因為它基本上就是加強版的 Starlette。 + +/// + +### Uvicorn { #uvicorn } + +Uvicorn 是基於 uvloop 與 httptools 的極速 ASGI 伺服器。 + +它不是網頁框架,而是伺服器。例如,它不提供依據路徑路由的工具。這是像 Starlette(或 **FastAPI**)這樣的框架在其上方提供的功能。 + +它是 Starlette 與 **FastAPI** 推薦使用的伺服器。 + +/// check | **FastAPI** 建議用作 + +執行 **FastAPI** 應用的主要網頁伺服器。 + +你也可以使用 `--workers` 命令列選項,取得非同步的多製程伺服器。 + +更多細節請見[部署](deployment/index.md){.internal-link target=_blank}章節。 + +/// + +## 效能與速度 { #benchmarks-and-speed } + +想了解、比較並看出 Uvicorn、Starlette 與 FastAPI 之間的差異,請參考[效能評測](benchmarks.md){.internal-link target=_blank}。 diff --git a/docs/zh-hant/docs/async.md b/docs/zh-hant/docs/async.md index 09e2bf994..a03d71815 100644 --- a/docs/zh-hant/docs/async.md +++ b/docs/zh-hant/docs/async.md @@ -1,10 +1,10 @@ -# 並行與 async / await +# 並行與 async / await { #concurrency-and-async-await } 有關*路徑操作函式*的 `async def` 語法的細節與非同步 (asynchronous) 程式碼、並行 (concurrency) 與平行 (parallelism) 的一些背景知識。 -## 趕時間嗎? +## 趕時間嗎? { #in-a-hurry } -TL;DR: +TL;DR: 如果你正在使用要求你以 `await` 語法呼叫的第三方函式庫,例如: @@ -41,7 +41,7 @@ def results(): --- -如果你的應用程式不需要與外部資源進行任何通訊並等待其回應,請使用 `async def`。 +如果你的應用程式不需要與外部資源進行任何通訊並等待其回應,請使用 `async def`,即使內部不需要使用 `await` 也可以。 --- @@ -55,7 +55,7 @@ def results(): 但透過遵循上述步驟,它將能進行一些效能最佳化。 -## 技術細節 +## 技術細節 { #technical-details } 現代版本的 Python 支援使用 **「協程」** 的 **`async` 和 `await`** 語法來寫 **「非同步程式碼」**。 @@ -65,7 +65,7 @@ def results(): * **`async` 和 `await`** * **協程** -## 非同步程式碼 +## 非同步程式碼 { #asynchronous-code } 非同步程式碼僅意味著程式語言 💬 有辦法告訴電腦/程式 🤖 在程式碼中的某個點,它 🤖 需要等待某些事情完成。讓我們假設這些事情被稱為「慢速檔案」📝。 @@ -74,7 +74,7 @@ def results(): 接著程式 🤖 會在有空檔時回來查看是否有等待的工作已經完成,並執行必要的後續操作。 接下來,它 🤖 完成第一個工作(例如我們的「慢速檔案」📝)並繼續執行相關的所有操作。 -這個「等待其他事情」通常指的是一些相對較慢的(與處理器和 RAM 記憶體的速度相比)的 I/O 操作,比如說: +這個「等待其他事情」通常指的是一些相對較慢的(與處理器和 RAM 記憶體的速度相比)的 I/O 操作,比如說: * 透過網路傳送來自用戶端的資料 * 從網路接收來自用戶端的資料 @@ -85,7 +85,7 @@ def results(): * 資料庫查詢 * 等等 -由於大部分的執行時間都消耗在等待 I/O 操作上,因此這些操作被稱為 "I/O 密集型" 操作。 +由於大部分的執行時間都消耗在等待 I/O 操作上,因此這些操作被稱為 "I/O 密集型" 操作。 之所以稱為「非同步」,是因為電腦/程式不需要與那些耗時的任務「同步」,等待任務完成的精確時間,然後才能取得結果並繼續工作。 @@ -93,7 +93,7 @@ def results(): 相對於「非同步」(asynchronous),「同步」(synchronous)也常被稱作「順序性」(sequential),因為電腦/程式會依序執行所有步驟,即便這些步驟涉及等待,才會切換到其他任務。 -### 並行與漢堡 +### 並行與漢堡 { #concurrency-and-burgers } 上述非同步程式碼的概念有時也被稱為「並行」,它不同於「平行」。 @@ -103,7 +103,7 @@ def results(): 為了理解差異,請想像以下有關漢堡的故事: -### 並行漢堡 +### 並行漢堡 { #concurrent-burgers } 你和你的戀人去速食店,排隊等候時,收銀員正在幫排在你前面的人點餐。😍 @@ -163,7 +163,7 @@ def results(): 然後你走向櫃檯 🔀,回到已經完成的最初任務 ⏯,拿起漢堡,說聲謝謝,並帶回桌上。這就結束了與櫃檯的互動步驟/任務 ⏹,接下來會產生一個新的任務,「吃漢堡」 🔀 ⏯,而先前的「拿漢堡」任務已經完成了 ⏹。 -### 平行漢堡 +### 平行漢堡 { #parallel-burgers } 現在,讓我們來想像這裡不是「並行漢堡」,而是「平行漢堡」。 @@ -233,7 +233,7 @@ def results(): 所以,你不會想帶你的戀人 😍 一起去銀行辦事 🏦。 -### 漢堡結論 +### 漢堡結論 { #burger-conclusion } 在「和戀人一起吃速食漢堡」的這個場景中,由於有大量的等待 🕙,使用並行系統 ⏸🔀⏯ 更有意義。 @@ -253,7 +253,7 @@ def results(): 你可以同時利用並行性和平行性,進一步提升效能,這比大多數已測試的 NodeJS 框架都更快,並且與 Go 語言相當,而 Go 是一種更接近 C 的編譯語言(感謝 Starlette)。 -### 並行比平行更好嗎? +### 並行比平行更好嗎? { #is-concurrency-better-than-parallelism } 不是的!這不是故事的本意。 @@ -277,7 +277,7 @@ def results(): 在這個場景中,每個清潔工(包括你)都是一個處理器,完成工作的一部分。 -由於大多數的執行時間都花在實際的工作上(而不是等待),而電腦中的工作由 CPU 完成,因此這些問題被稱為「CPU 密集型」。 +由於大多數的執行時間都花在實際的工作上(而不是等待),而電腦中的工作由 CPU 完成,因此這些問題被稱為「CPU 密集型」。 --- @@ -290,7 +290,7 @@ def results(): * **機器學習**: 通常需要大量的「矩陣」和「向量」運算。想像一個包含數字的巨大電子表格,並所有的數字同時相乘; * **深度學習**: 這是機器學習的子領域,同樣適用。只不過這不僅僅是一張數字表格,而是大量的數據集合,並且在很多情況下,你會使用特殊的處理器來構建或使用這些模型。 -### 並行 + 平行: Web + 機器學習 +### 並行 + 平行: Web + 機器學習 { #concurrency-parallelism-web-machine-learning } 使用 **FastAPI**,你可以利用並行的優勢,這在 Web 開發中非常常見(這也是 NodeJS 的最大吸引力)。 @@ -300,9 +300,9 @@ def results(): 想了解如何在生產環境中實現這種平行性,請參見 [部屬](deployment/index.md){.internal-link target=_blank}。 -## `async` 和 `await` +## `async` 和 `await` { #async-and-await } -現代 Python 版本提供一種非常直觀的方式定義非同步程式碼。這使得它看起來就像正常的「順序」程式碼,並在適當的時機「等待」。 +現代 Python 版本提供一種非常直觀的方式定義非同步程式碼。這使得它看起來就像正常的「順序」程式碼,並在適當的時機替你「等待」。 當某個操作需要等待才能回傳結果,並且支援這些新的 Python 特性時,你可以像這樣編寫程式碼: @@ -329,7 +329,7 @@ def get_sequential_burgers(number: int): return burgers ``` -使用 `async def`,Python Python 知道在該函式內需要注意 `await`,並且它可以「暫停」 ⏸ 執行該函式,然後執行其他任務 🔀 後回來。 +使用 `async def`,Python 知道在該函式內需要注意 `await`,並且它可以「暫停」 ⏸ 執行該函式,然後執行其他任務 🔀 後回來。 當你想要呼叫 `async def` 函式時,必須使用「await」。因此,這樣寫將無法運行: @@ -349,11 +349,11 @@ async def read_burgers(): return burgers ``` -### 更多技術細節 +### 更多技術細節 { #more-technical-details } 你可能已經注意到,`await` 只能在 `async def` 定義的函式內使用。 -但同時,使用 `async def` 定義的函式本身也必須被「等待」。所以,帶有 `async def` 函式只能在其他使用 `async def` 定義的函式內呼叫。 +但同時,使用 `async def` 定義的函式本身也必須被「等待」。所以,帶有 `async def` 的函式只能在其他使用 `async def` 定義的函式內呼叫。 那麼,這就像「先有雞還是先有蛋」的問題,要如何呼叫第一個 `async` 函式呢? @@ -361,35 +361,37 @@ async def read_burgers(): 但如果你想在沒有 FastAPI 的情況下使用 `async` / `await`,你也可以這樣做。 -### 編寫自己的非同步程式碼 +### 編寫自己的非同步程式碼 { #write-your-own-async-code } -Starlette (和 **FastAPI**) 是基於 AnyIO 實作的,這使得它們與 Python 標準函式庫相容 asyncioTrio。 +Starlette(和 **FastAPI**)是基於 AnyIO 實作的,這使得它們與 Python 標準函式庫 asyncioTrio 相容。 特別是,你可以直接使用 AnyIO 來處理更複雜的並行使用案例,這些案例需要你在自己的程式碼中使用更高階的模式。 -即使你不使用 **FastAPI**,你也可以使用 AnyIO 來撰寫自己的非同步應用程式,並獲得高相容性及一些好處(例如結構化並行)。 +即使你不使用 **FastAPI**,你也可以使用 AnyIO 來撰寫自己的非同步應用程式,並獲得高相容性及一些好處(例如「結構化並行」)。 -### 其他形式的非同步程式碼 +我另外在 AnyIO 之上做了一個薄封裝的函式庫,稍微改進型別註解以獲得更好的**自動補全**、**即時錯誤**等。同時它也提供友善的介紹與教學,幫助你**理解**並撰寫**自己的非同步程式碼**:Asyncer。當你需要**將非同步程式碼與一般**(阻塞/同步)**程式碼整合**時,它特別實用。 + +### 其他形式的非同步程式碼 { #other-forms-of-asynchronous-code } 使用 `async` 和 `await` 的風格在語言中相對較新。 -但它使處理異步程式碼變得更加容易。 +但它使處理非同步程式碼變得更加容易。 相同的語法(或幾乎相同的語法)最近也被包含在現代 JavaScript(無論是瀏覽器還是 NodeJS)中。 -但在此之前,處理異步程式碼要更加複雜和困難。 +但在此之前,處理非同步程式碼要更加複雜和困難。 在較舊的 Python 版本中,你可能會使用多執行緒或 Gevent。但這些程式碼要更難以理解、調試和思考。 在較舊的 NodeJS / 瀏覽器 JavaScript 中,你會使用「回呼」,這可能會導致“回呼地獄”。 -## 協程 +## 協程 { #coroutines } -**協程** 只是 `async def` 函式所回傳的非常特殊的事物名稱。Python 知道它是一個類似函式的東西,可以啟動它,並且在某個時刻它會結束,但它也可能在內部暫停 ⏸,只要遇到 `await`。 +「協程」只是 `async def` 函式所回傳的非常特殊的事物名稱。Python 知道它是一個類似函式的東西,可以啟動它,並且在某個時刻它會結束,但它也可能在內部暫停 ⏸,只要遇到 `await`。 這種使用 `async` 和 `await` 的非同步程式碼功能通常被概括為「協程」。這與 Go 語言的主要特性「Goroutines」相似。 -## 結論 +## 結論 { #conclusion } 讓我們再次回顧之前的句子: @@ -397,9 +399,9 @@ Starlette (和 **FastAPI**) 是基於 I/O 的程式碼。 +如果你來自於其他不以這種方式運作的非同步框架,而且你習慣於使用普通的 `def` 定義僅進行簡單計算的*路徑操作函式*,目的是獲得微小的性能增益(大約 100 奈秒),請注意,在 FastAPI 中,效果會完全相反。在這些情況下,最好使用 `async def`,除非你的*路徑操作函式*執行阻塞的 I/O 的程式碼。 -不過,在這兩種情況下,**FastAPI** [仍然很快](index.md#_11){.internal-link target=_blank}至少與你之前的框架相當(或者更快)。 +不過,在這兩種情況下,**FastAPI** [仍然很快](index.md#performance){.internal-link target=_blank},至少與你之前的框架相當(或者更快)。 -### 依賴項(Dependencies) +### 依賴項(Dependencies) { #dependencies } 同樣適用於[依賴項](tutorial/dependencies/index.md){.internal-link target=_blank}。如果依賴項是一個標準的 `def` 函式,而不是 `async def`,那麼它在外部的執行緒池被運行。 -### 子依賴項 +### 子依賴項 { #sub-dependencies } -你可以擁有多個相互依賴的依賴項和[子依賴項](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} (作為函式定義的參數),其中一些可能是用 `async def` 宣告,也可能是用 `def` 宣告。它們仍然可以正常運作,用 `def` 定義的那些將會在外部的執行緒中呼叫(來自執行緒池),而不是被「等待」。 +你可以擁有多個相互依賴的依賴項和[子依賴項](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}(作為函式定義的參數),其中一些可能是用 `async def` 宣告,也可能是用 `def` 宣告。它們仍然可以正常運作,用 `def` 定義的那些將會在外部的執行緒中呼叫(來自執行緒池),而不是被「等待」。 -### 其他輔助函式 +### 其他輔助函式 { #other-utility-functions } 你可以直接呼叫任何使用 `def` 或 `async def` 建立的其他輔助函式,FastAPI 不會影響你呼叫它們的方式。 @@ -439,4 +441,4 @@ Starlette (和 **FastAPI**) 是基於 趕時間嗎?. +否則,只需遵循上面提到的指引即可:趕時間嗎?。 diff --git a/docs/zh-hant/docs/benchmarks.md b/docs/zh-hant/docs/benchmarks.md index df49621c5..c5b76700b 100644 --- a/docs/zh-hant/docs/benchmarks.md +++ b/docs/zh-hant/docs/benchmarks.md @@ -6,7 +6,7 @@ ## 基準測試和速度 { #benchmarks-and-speed } -當你查看基準測試時,時常會見到幾個不同類型的工具被同時進行測試。 +當你查看基準測試時,常見到不同類型的多個工具被視為等同來比較。 具體來說,是將 Uvicorn、Starlette 和 FastAPI 同時進行比較(以及許多其他工具)。 @@ -20,7 +20,7 @@ * **Uvicorn**: * 具有最佳效能,因為除了伺服器本身之外,它沒有太多額外的程式碼。 - * 你不會直接在 Uvicorn 中編寫應用程式。這意味著你的程式碼必須或多或少地包含 Starlette(或 **FastAPI**)提供的所有程式碼。如果你這樣做,你的最終應用程式將具有與使用框架相同的開銷並最大限度地減少應用程式程式碼和錯誤。 + * 你不會直接在 Uvicorn 中編寫應用程式。這意味著你的程式碼必須或多或少地包含 Starlette(或 **FastAPI**)提供的所有程式碼。如果你這樣做,你的最終應用程式將具有與使用框架相同的開銷,且無法像使用框架那樣減少應用程式程式碼與錯誤。 * 如果你要比較 Uvicorn,請將其與 Daphne、Hypercorn、uWSGI 等應用程式伺服器進行比較。 * **Starlette**: * 繼 Uvicorn 之後的次佳表現。事實上,Starlette 使用 Uvicorn 來運行。因此它將可能只透過執行更多程式碼而變得比 Uvicorn「慢」。 diff --git a/docs/zh-hant/docs/deployment/concepts.md b/docs/zh-hant/docs/deployment/concepts.md new file mode 100644 index 000000000..0cca31d26 --- /dev/null +++ b/docs/zh-hant/docs/deployment/concepts.md @@ -0,0 +1,321 @@ +# 部署概念 { #deployments-concepts } + +當你要部署一個 FastAPI 應用,或其實任何類型的 Web API 時,有幾個你可能在意的概念。掌握這些概念後,你就能找出最適合部署你應用的方式。 + +一些重要的概念包括: + +- 安全性 - HTTPS +- 開機自動執行 +- 重新啟動 +- 複本(執行中的行程數量) +- 記憶體 +- 啟動前的前置步驟 + +我們將看看它們如何影響部署。 + +最終目標是能夠以安全、避免中斷,並盡可能高效使用運算資源(例如遠端伺服器/虛擬機)的方式來服務你的 API 用戶端。🚀 + +我會在這裡多介紹一些這些觀念,希望能幫你建立必要的直覺,讓你能在非常不同、甚至尚未出現的未來環境中決定要如何部署你的 API。 + +在思考這些概念之後,你將能夠評估與設計最適合部署你自己 API 的方式。 + +在接下來的章節,我會提供更具體的部署 FastAPI 應用的食譜。 + +但現在,先來看看這些重要的概念想法。這些概念同樣適用於任何其他類型的 Web API。💡 + +## 安全性 - HTTPS { #security-https } + +在[前一章關於 HTTPS](https.md){.internal-link target=_blank} 中,我們學到 HTTPS 如何為你的 API 提供加密。 + +我們也看到,HTTPS 通常由應用伺服器外部的元件提供,即 TLS Termination Proxy。 + +而且必須有某個東西負責續期 HTTPS 憑證,可能是同一個元件,也可能是不同的東西。 + +### HTTPS 工具範例 { #example-tools-for-https } + +你可以用來作為 TLS Termination Proxy 的工具包括: + +- Traefik + - 自動處理憑證續期 ✨ +- Caddy + - 自動處理憑證續期 ✨ +- Nginx + - 搭配像 Certbot 這類外部元件進行憑證續期 +- HAProxy + - 搭配像 Certbot 這類外部元件進行憑證續期 +- Kubernetes,使用如 Nginx 的 Ingress Controller + - 搭配像 cert-manager 這類外部元件進行憑證續期 +- 由雲端供應商在其服務內部處理(見下文 👇) + +另一個選項是使用能幫你做更多事情的雲端服務(包含設定 HTTPS)。它可能有一些限制或要額外付費等。但在那種情況下,你就不必自己設定 TLS Termination Proxy。 + +我會在後續章節展示一些具體例子。 + +--- + +接下來要考慮的概念都與實際執行你的 API 的程式(例如 Uvicorn)有關。 + +## 程式與行程 { #program-and-process } + +我們會常提到執行中的「行程(process)」,因此先釐清它的意思,以及與「程式(program)」的差異很有幫助。 + +### 什麼是程式 { #what-is-a-program } + +「程式(program)」一詞常用來描述許多東西: + +- 你寫的原始碼,也就是 Python 檔案。 +- 可由作業系統執行的檔案,例如:`python`、`python.exe` 或 `uvicorn`。 +- 在作業系統上執行中的特定程式,使用 CPU 並將資料存於記憶體。這也稱為「行程」。 + +### 什麼是行程 { #what-is-a-process } + +「行程(process)」通常以更特定的方式使用,只指作業系統中正在執行的東西(如上面最後一點): + +- 在作業系統上「執行中」的特定程式。 + - 這不是指檔案或原始碼,而是特指正在被作業系統執行並管理的那個東西。 +- 任何程式、任何程式碼,只有在「被執行」時才能做事。所以,當有「行程在執行」時才能運作。 +- 行程可以被你或作業系統終止(kill)。此時它就停止執行,無法再做任何事。 +- 你電腦上執行的每個應用程式、每個視窗等,背後都有一些行程。而且在電腦開機時,通常會同時有許多行程在跑。 +- 同一個程式可以同時有多個行程在執行。 + +如果你打開作業系統的「工作管理員」或「系統監控器」(或類似工具),就能看到許多正在執行的行程。 + +例如,你大概會看到同一個瀏覽器(Firefox、Chrome、Edge 等)會有多個行程在執行。它們通常每個分頁一個行程,外加其他一些額外行程。 + + + +--- + +現在我們知道「行程」與「程式」的差異了,繼續談部署。 + +## 開機自動執行 { #running-on-startup } + +多數情況下,當你建立一個 Web API,你會希望它「一直在執行」,不中斷,讓客戶端隨時可用。除非你有特定理由只在某些情況下才執行,但大部分時候你會希望它持續運作並且可用。 + +### 在遠端伺服器上 { #in-a-remote-server } + +當你設定一台遠端伺服器(雲端伺服器、虛擬機等),最簡單的作法就是像本機開發時一樣,手動使用 `fastapi run`(它使用 Uvicorn)或類似的方式。 + +這在「開發期間」會運作良好而且有用。 + +但如果你與伺服器的連線中斷,正在執行的行程很可能會死掉。 + +而如果伺服器被重新啟動(例如更新後、或雲端供應商進行遷移),你大概「不會注意到」。因此你甚至不知道要手動重啟行程。你的 API 就會一直掛著。😱 + +### 開機自動啟動 { #run-automatically-on-startup } + +通常你會希望伺服器程式(例如 Uvicorn)在伺服器開機時自動啟動,且不需任何「人工介入」,讓你的 API(例如 Uvicorn 執行你的 FastAPI 應用)總是有行程在跑。 + +### 獨立程式 { #separate-program } + +為了達成這點,你通常會有一個「獨立的程式」來確保你的應用在開機時會被啟動。很多情況下,它也會確保其他元件或應用一併啟動,例如資料庫。 + +### 開機自動啟動的工具範例 { #example-tools-to-run-at-startup } + +能做到這件事的工具包括: + +- Docker +- Kubernetes +- Docker Compose +- Docker 的 Swarm 模式 +- Systemd +- Supervisor +- 由雲端供應商在其服務內部處理 +- 其他... + +我會在後續章節給出更具體的例子。 + +## 重新啟動 { #restarts } + +和確保你的應用在開機時會執行一樣,你大概也會希望在發生失敗之後,它能「自動重新啟動」。 + +### 人都會犯錯 { #we-make-mistakes } + +我們身為人,常常會犯錯。軟體幾乎總是有藏在各處的「臭蟲(bugs)」🐛 + +而我們開發者會在發現這些 bug 後持續改進程式碼、實作新功能(也可能順便加進新的 bug 😅)。 + +### 小錯誤自動處理 { #small-errors-automatically-handled } + +使用 FastAPI 建構 Web API 時,如果我們的程式碼出錯,FastAPI 通常會把它限制在觸發該錯誤的單次請求之中。🛡 + +用戶端會收到「500 Internal Server Error」,但應用會繼續處理之後的請求,而不是整個崩潰。 + +### 更嚴重的錯誤 - 當機 { #bigger-errors-crashes } + +然而,仍可能有一些情況,我們寫的程式碼「讓整個應用當機」,使 Uvicorn 與 Python 都崩潰。💥 + +即便如此,你大概也不會希望應用因為某處錯誤就一直處於死亡狀態,你可能會希望它「繼續運作」,至少讓沒有壞掉的「路徑操作(path operations)」能持續服務。 + +### 當機後重新啟動 { #restart-after-crash } + +在這些會讓「執行中行程」整個崩潰的嚴重錯誤案例裡,你會希望有個外部元件負責「重新啟動」該行程,至少嘗試幾次... + +/// tip + +...不過,如果整個應用「一啟動就立刻」崩潰,那持續無止境地重啟大概沒有意義。但在這類情況下,你很可能會在開發過程中就發現,或至少在部署後馬上注意到。 + +所以讓我們專注在主要情境:應用在未來某些特定案例中可能會整體崩潰,但此時重新啟動仍然是有意義的。 + +/// + +你大概會希望把負責重新啟動應用的東西放在「外部元件」,因為那個時候,應用本身連同 Uvicorn 與 Python 都已經掛了,在同一個應用的程式碼裡也無法做什麼。 + +### 自動重新啟動的工具範例 { #example-tools-to-restart-automatically } + +多數情況下,用來「在開機時啟動程式」的同一套工具,也會負責處理自動「重新啟動」。 + +例如,可以由下列工具處理: + +- Docker +- Kubernetes +- Docker Compose +- Docker 的 Swarm 模式 +- Systemd +- Supervisor +- 由雲端供應商在其服務內部處理 +- 其他... + +## 複本:行程與記憶體 { #replication-processes-and-memory } + +在 FastAPI 應用中,使用像 `fastapi` 指令(執行 Uvicorn)的伺服器程式,即使只在「一個行程」中執行,也能同時服務多個客戶端。 + +但很多情況下,你會想同時執行多個工作行程(workers)。 + +### 多個行程 - Workers { #multiple-processes-workers } + +如果你的客戶端比單一行程所能處理的更多(例如虛擬機規格不大),而伺服器 CPU 有「多核心」,那你可以同時執行「多個行程」載入相同的應用,並把所有請求分散給它們。 + +當你執行同一個 API 程式的「多個行程」時,通常稱為「workers(工作行程)」。 + +### 工作行程與連接埠 { #worker-processes-and-ports } + +還記得文件中[關於 HTTPS](https.md){.internal-link target=_blank} 的說明嗎:在一台伺服器上,一組 IP 與連接埠的組合只能由「一個行程」監聽? + +這裡同樣適用。 + +因此,若要同時擁有「多個行程」,就必須有「單一行程」在該連接埠上監聽,然後以某種方式把通信傳遞給各個工作行程。 + +### 每個行程的記憶體 { #memory-per-process } + +當程式把東西載入記憶體時,例如把機器學習模型存到變數中,或把大型檔案內容讀到變數中,這些都會「消耗一些伺服器的記憶體(RAM)」。 + +而多個行程通常「不共享記憶體」。這表示每個執行中的行程都有自己的東西、變數與記憶體。如果你的程式碼會用掉大量記憶體,「每個行程」都會消耗等量的記憶體。 + +### 伺服器記憶體 { #server-memory } + +例如,如果你的程式碼載入一個「1 GB 大小」的機器學習模型,當你用一個行程執行你的 API,它就會至少吃掉 1 GB 的 RAM。若你啟動「4 個行程」(4 個 workers),每個會吃 1 GB RAM。總計你的 API 會吃掉「4 GB RAM」。 + +如果你的遠端伺服器或虛擬機只有 3 GB RAM,嘗試載入超過 4 GB 的 RAM 就會出問題。🚨 + +### 多個行程 - 範例 { #multiple-processes-an-example } + +在這個例子中,有一個「管理行程(Manager Process)」會啟動並控制兩個「工作行程(Worker Processes)」。 + +這個管理行程大概就是在 IP 的「連接埠」上監聽的那個。它會把所有通信轉發到各個工作行程。 + +那些工作行程才是實際執行你的應用的,它們會完成主要的計算,接收「請求」並回傳「回應」,也會把你放在變數中的東西載入 RAM。 + + + +當然,同一台機器上除了你的應用之外,通常也會有「其他行程」在執行。 + +有個有趣的細節是,每個行程的「CPU 使用率」百分比會隨時間大幅「變動」,但「記憶體(RAM)」通常維持相對「穩定」。 + +如果你的 API 每次做的計算量相近,且客戶很多,那「CPU 使用率」也可能「相對穩定」(而不是快速上下起伏)。 + +### 複本與擴展的工具與策略範例 { #examples-of-replication-tools-and-strategies } + +要達成這些有很多種作法。我會在後續章節(例如談到 Docker 與容器時)介紹更具體的策略。 + +主要的限制是:必須有「單一」元件負責處理「公開 IP」上的「連接埠」。接著它必須能把通信「轉發」到被複製的「行程/workers」。 + +以下是一些可能的組合與策略: + +- Uvicorn 搭配 `--workers` + - 一個 Uvicorn「管理行程」會在「IP」與「連接埠」上監聽,並啟動「多個 Uvicorn 工作行程」。 +- Kubernetes 與其他分散式「容器系統」 + - 由「Kubernetes」層在「IP」與「連接埠」上監聽。複本的方式是有「多個容器」,每個容器內執行「一個 Uvicorn 行程」。 +- 由「雲端服務」替你處理 + - 雲端服務很可能「替你處理複本」。它可能讓你定義「要執行的行程」或「容器映像」,無論如何,多半會是「單一 Uvicorn 行程」,而由雲端服務負責進行複製。 + +/// tip + +先別擔心這裡提到的「容器」、Docker 或 Kubernetes 如果現在還不太懂。 + +我會在未來的章節進一步說明容器映像、Docker、Kubernetes 等等:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。 + +/// + +## 啟動前的前置步驟 { #previous-steps-before-starting } + +很多情況下,你會希望在應用「啟動之前」先執行一些步驟。 + +例如,你可能想先執行「資料庫遷移」。 + +但在多數情況下,你會希望這些步驟只執行「一次」。 + +所以,你會希望用「單一行程」來執行那些「前置步驟」,在啟動應用之前完成。 + +而且即使之後你要為應用本身啟動「多個行程」(多個 workers),你也必須確保只有一個行程在跑那些前置步驟。若由「多個行程」去跑,會在「平行」中重複同樣的工作;而如果那些步驟像是資料庫遷移這類敏感操作,它們之間可能會互相衝突。 + +當然,也有一些情況,重複執行前置步驟不會有問題;在那種情況下就容易處理得多。 + +/// tip + +另外請記住,依照你的設定,在某些情況下你「甚至可能不需要任何前置步驟」才能啟動應用。 + +這種情況下,你就不用為此費心了。🤷 + +/// + +### 前置步驟策略範例 { #examples-of-previous-steps-strategies } + +這會「高度取決於」你「部署系統」的方式,而且很可能與你如何啟動程式、處理重新啟動等有關。 + +以下是一些可能的做法: + +- 在 Kubernetes 中使用一個「Init Container」,它會在你的應用容器之前先執行 +- 一個 bash 腳本先跑前置步驟,然後再啟動你的應用 + - 你仍然需要有機制來啟動/重新啟動「那個」bash 腳本、偵測錯誤等 + +/// tip + +我會在未來關於容器的章節提供更具體的範例:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。 + +/// + +## 資源使用率 { #resource-utilization } + +你的伺服器(群)是可以被「消耗/利用」的「資源」,你的程式會使用 CPU 的計算時間,以及可用的 RAM 記憶體。 + +你想要消耗/利用多少系統資源?直覺上可能會想「不要太多」,但實際上,你大概會希望在「不當機」的前提下「盡可能用多一點」。 + +如果你花錢租了 3 台伺服器,卻只用了它們少量的 RAM 與 CPU,那你可能是在「浪費金錢」💸、也「浪費伺服器電力」🌎 等。 + +在那種情況下,可能更好的是只用 2 台伺服器,並以更高的比例使用它們的資源(CPU、記憶體、磁碟、網路頻寬等)。 + +另一方面,如果你有 2 台伺服器,且你使用了它們「100% 的 CPU 與 RAM」,某個時刻一個行程會要求更多記憶體,伺服器就得用磁碟當作「記憶體」(這可能慢上數千倍),甚至「當機」。或是某個行程需要做計算時,必須等到 CPU 再度空閒。 + +這種情況下,最好是再加一台伺服器,並在上面跑部分行程,讓所有行程都有「足夠的 RAM 與 CPU 時間」。 + +也有機會因為某些原因,你的 API 使用量出現「尖峰」。也許它爆紅了,或是其他服務或機器人開始使用它。在這些情況下,你可能會希望留有一些額外資源以策安全。 + +你可以設定一個「目標數字」,例如資源使用率落在「50% 到 90%」之間。重點是,這些大概就是你會想測量並用來調校部署的主要指標。 + +你可以用像 `htop` 這樣的簡單工具,查看伺服器的 CPU 與 RAM 使用量,或各行程的使用量。也可以用更複雜的監控工具,分散式地監看多台伺服器,等等。 + +## 重點回顧 { #recap } + +這裡介紹了一些你在決定如何部署應用時應該記住的主要概念: + +- 安全性 - HTTPS +- 開機自動執行 +- 重新啟動 +- 複本(執行中的行程數量) +- 記憶體 +- 啟動前的前置步驟 + +理解這些想法與如何套用它們,應能給你足夠的直覺,在設定與調整部署時做出各種決策。🤓 + +在接下來的章節,我會提供更多可行策略的具體範例。🚀 diff --git a/docs/zh-hant/docs/deployment/docker.md b/docs/zh-hant/docs/deployment/docker.md new file mode 100644 index 000000000..87f4e6f9f --- /dev/null +++ b/docs/zh-hant/docs/deployment/docker.md @@ -0,0 +1,618 @@ +# 在容器中使用 FastAPI - Docker { #fastapi-in-containers-docker } + +部署 FastAPI 應用時,一個常見做法是建置一個「Linux 容器映像(container image)」。通常使用 Docker 來完成。之後你可以用多種方式部署該容器映像。 + +使用 Linux 容器有多種優點,包括安全性、可重現性、簡單性等。 + +/// tip | 提示 + +趕時間而且已經懂這些?直接跳到下面的 [`Dockerfile` 👇](#build-a-docker-image-for-fastapi)。 + +/// + +
+Dockerfile 預覽 👀 + +```Dockerfile +FROM python:3.14 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +CMD ["fastapi", "run", "app/main.py", "--port", "80"] + +# 若在 Nginx 或 Traefik 等代理伺服器後方執行,請加入 --proxy-headers +# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"] +``` + +
+ +## 什麼是容器 { #what-is-a-container } + +容器(主要是 Linux 容器)是一種非常輕量的方式,用來封裝應用及其所有相依與必要檔案,並讓其與同一系統中的其他容器(其他應用或元件)隔離。 + +Linux 容器使用與主機(機器、虛擬機、雲端伺服器等)相同的 Linux kernel。這意味著它們非常輕量(相較於完整模擬整個作業系統的虛擬機)。 + +因此,容器只消耗很少的資源,與直接執行行程相當(而虛擬機會消耗更多)。 + +容器也有其各自隔離的執行行程(通常只有一個行程)、檔案系統與網路,簡化部署、安全性與開發等。 + +## 什麼是容器映像 { #what-is-a-container-image } + +容器是由容器映像啟動執行的。 + +容器映像是所有檔案、環境變數,以及在容器中應該執行的預設指令/程式的靜態版本。這裡的「靜態」意指容器映像不在執行,它只是被封裝的檔案與 metadata。 + +相對於儲存的靜態內容「容器映像」,「容器」通常指執行中的實例,也就是正在被執行的東西。 + +當容器啟動並執行時(自容器映像啟動),它可以建立或變更檔案、環境變數等。這些變更只會存在於該容器中,不會持久化回底層的容器映像(不會寫回磁碟)。 + +容器映像可類比為程式檔與其內容,例如 `python` 與某個 `main.py` 檔案。 + +而容器本身(相對於容器映像)是映像的實際執行實例,類比為「行程」。事實上,容器只有在有行程執行時才在運作(通常只有單一行程)。當其中沒有行程在執行時,容器就會停止。 + +## 容器映像 { #container-images } + +Docker 是用來建立與管理容器映像與容器的主要工具之一。 + +也有一個公開的 Docker Hub,內含許多工具、環境、資料庫與應用的預先製作「官方映像」。 + +例如,有官方的 Python 映像。 + +也有許多其他針對不同用途的映像,例如資料庫: + +* PostgreSQL +* MySQL +* MongoDB +* Redis 等。 + +使用預製的容器映像很容易「組合」並使用不同工具。例如,嘗試一個新資料庫。多數情況下,你可以使用官方映像,並僅用環境變數加以設定。 + +如此,你可以學會關於容器與 Docker 的知識,並將這些知識重複運用到許多不同工具與元件上。 + +因此,你會執行多個容器,內容各不相同,例如一個資料庫、一個 Python 應用、一個帶有 React 前端應用的網頁伺服器,並透過它們的內部網路把它們連接在一起。 + +所有容器管理系統(例如 Docker 或 Kubernetes)都內建了這些網路功能。 + +## 容器與行程 { #containers-and-processes } + +容器映像通常在其 metadata 中包含當容器啟動時應執行的預設程式或指令,以及要傳給該程式的參數。這與在命令列要執行的內容非常類似。 + +當容器啟動時,它會執行該指令/程式(雖然你可以覆寫它,讓它執行不同的指令/程式)。 + +只要主要行程(指令或程式)在執行,容器就會運作。 + +容器通常只有單一行程,但也可以由主要行程啟動子行程,如此你會在同一個容器內有多個行程。 + +但不可能在沒有至少一個執行中行程的情況下讓容器運作。若主要行程停止,容器也會停止。 + +## 建置 FastAPI 的 Docker 映像 { #build-a-docker-image-for-fastapi } + +好了,現在來動手做點東西吧!🚀 + +我會示範如何從零開始,基於官方的 Python 映像,為 FastAPI 建置一個 Docker 映像。 + +這是你在多數情況下會想做的事,例如: + +* 使用 Kubernetes 或類似工具 +* 在 Raspberry Pi 上執行 +* 使用會替你執行容器映像的雲端服務等 + +### 套件需求 { #package-requirements } + +你的應用通常會把「套件需求」放在某個檔案中。 + +這主要取決於你用什麼工具來安裝那些需求。 + +最常見的方式是準備一個 `requirements.txt` 檔案,逐行列出套件名稱與版本。 + +當然,你會用與在 [關於 FastAPI 版本](versions.md){.internal-link target=_blank} 中讀到的相同概念,來設定版本範圍。 + +例如,你的 `requirements.txt` 可能像這樣: + +``` +fastapi[standard]>=0.113.0,<0.114.0 +pydantic>=2.7.0,<3.0.0 +``` + +接著你通常會用 `pip` 來安裝這些套件相依,例如: + +
+ +```console +$ pip install -r requirements.txt +---> 100% +Successfully installed fastapi pydantic +``` + +
+ +/// info | 資訊 + +還有其他格式與工具可以用來定義與安裝套件相依。 + +/// + +### 建立 FastAPI 程式碼 { #create-the-fastapi-code } + +* 建立一個 `app` 目錄並進入。 +* 建立一個空的 `__init__.py` 檔案。 +* 建立一個 `main.py` 檔案,內容如下: + +```Python +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: str | None = None): + return {"item_id": item_id, "q": q} +``` + +### Dockerfile { #dockerfile } + +現在在同一個專案目錄建立一個 `Dockerfile` 檔案,內容如下: + +```{ .dockerfile .annotate } +# (1)! +FROM python:3.14 + +# (2)! +WORKDIR /code + +# (3)! +COPY ./requirements.txt /code/requirements.txt + +# (4)! +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (5)! +COPY ./app /code/app + +# (6)! +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +1. 從官方的 Python 基底映像開始。 + +2. 將目前工作目錄設為 `/code`。 + + 我們會把 `requirements.txt` 檔案與 `app` 目錄放在這裡。 + +3. 將需求檔案複製到 `/code` 目錄。 + + 先只複製需求檔案,不要複製其他程式碼。 + + 因為這個檔案不常變動,Docker 能偵測並在此步驟使用快取,也能啟用下一步的快取。 + +4. 安裝需求檔案中的套件相依。 + + `--no-cache-dir` 選項告訴 `pip` 不要把下載的套件保存在本機,因為那只在 `pip` 之後還會再次安裝相同套件時才有用,而在使用容器時並非如此。 + + /// note | 注意 + + `--no-cache-dir` 只跟 `pip` 有關,與 Docker 或容器無關。 + + /// + + `--upgrade` 選項告訴 `pip` 若套件已安裝則升級它們。 + + 因為前一步複製檔案可能被 Docker 快取偵測到,這一步也會在可用時使用 Docker 快取。 + + 在此步驟使用快取可以在開發期間反覆建置映像時,為你省下大量時間,而不必每次都重新下載並安裝所有相依。 + +5. 將 `./app` 目錄複製到 `/code` 目錄中。 + + 由於這包含了所有程式碼,也是最常變動的部分,Docker 的快取在這一步或之後的步驟將不容易被使用。 + + 因此,重要的是把這一步放在 `Dockerfile` 的接近結尾處,以最佳化容器映像的建置時間。 + +6. 設定指令使用 `fastapi run`,其底層使用 Uvicorn。 + + `CMD` 接受字串清單,每個字串對應你在命令列中用空白分隔所輸入的內容。 + + 這個指令會從目前的工作目錄執行,也就是你先前用 `WORKDIR /code` 設定的 `/code` 目錄。 + +/// tip | 提示 + +點擊程式碼中的每個數字泡泡來查看每一行在做什麼。👆 + +/// + +/// warning | 警告 + +務必「總是」使用 `CMD` 指令的「exec 形式」,如下所述。 + +/// + +#### 使用 `CMD` 的 Exec 形式 { #use-cmd-exec-form } + +Docker 的 `CMD` 指令可以有兩種寫法: + +✅ Exec 形式: + +```Dockerfile +# ✅ 請這樣做 +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +⛔️ Shell 形式: + +```Dockerfile +# ⛔️ 請不要這樣做 +CMD fastapi run app/main.py --port 80 +``` + +務必總是使用 exec 形式,以確保 FastAPI 能夠優雅地關閉,並觸發 [lifespan events](../advanced/events.md){.internal-link target=_blank}。 + +你可以在 Docker 關於 shell 與 exec 形式的文件閱讀更多。 + +使用 `docker compose` 時這會特別明顯。技術細節請見這段 Docker Compose 常見問題:為什麼我的服務要花 10 秒才重新建立或停止? + +#### 目錄結構 { #directory-structure } + +你現在應該會有如下的目錄結構: + +``` +. +├── app +│   ├── __init__.py +│ └── main.py +├── Dockerfile +└── requirements.txt +``` + +#### 位於 TLS 終止代理之後 { #behind-a-tls-termination-proxy } + +如果你在 TLS 終止代理(負載平衡器)如 Nginx 或 Traefik 之後執行容器,請加上 `--proxy-headers` 選項,這會告訴 Uvicorn(透過 FastAPI CLI)信任該代理所送來的標頭,表示應用在 HTTPS 後方執行等。 + +```Dockerfile +CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] +``` + +#### Docker 快取 { #docker-cache } + +這個 `Dockerfile` 中有個重要技巧:我們先只複製「相依檔案」,而不是其他程式碼。原因如下。 + +```Dockerfile +COPY ./requirements.txt /code/requirements.txt +``` + +Docker 與其他工具會「增量式」建置容器映像,從 `Dockerfile` 頂端開始,逐層加入,每個指令所建立的檔案都會形成一層。 + +Docker 與類似工具在建置映像時也會使用內部快取;如果某檔案自上次建置以來未變更,則會重用上次建立的同一層,而不是再次複製並從零建立新層。 + +僅僅避免複製檔案本身或許幫助不大,但因為該步驟使用了快取,接下來的步驟也就能「使用快取」。例如,安裝相依的這個指令就能使用快取: + +```Dockerfile +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt +``` + +套件相依的檔案「不會經常變動」。因此,只複製該檔案,Docker 就能在此步驟「使用快取」。 + +接著,Docker 也就能對下一步「下載並安裝這些相依」使用快取。這正是我們能「省下大量時間」的地方。✨ 也能避免無聊的等待。😪😆 + +下載與安裝套件相依「可能要花好幾分鐘」,但使用「快取」最多只需幾秒。 + +在開發期間,你會一再建置容器映像以測試程式碼變更是否生效,累積下來這能省下許多時間。 + +之後,在 `Dockerfile` 的接近結尾處,我們才複製所有程式碼。由於這是「最常變動」的部分,我們把它放在接近結尾,因為幾乎總是此步驟之後的任何步驟都無法使用快取。 + +```Dockerfile +COPY ./app /code/app +``` + +### 建置 Docker 映像 { #build-the-docker-image } + +現在所有檔案就緒,來建置容器映像。 + +* 進到專案目錄(你的 `Dockerfile` 所在,且包含 `app` 目錄)。 +* 建置你的 FastAPI 映像: + +
+ +```console +$ docker build -t myimage . + +---> 100% +``` + +
+ +/// tip | 提示 + +注意最後的 `.`,等同於 `./`,它告訴 Docker 要用哪個目錄來建置容器映像。 + +這裡是目前的目錄(`.`)。 + +/// + +### 啟動 Docker 容器 { #start-the-docker-container } + +* 以你的映像為基礎執行一個容器: + +
+ +```console +$ docker run -d --name mycontainer -p 80:80 myimage +``` + +
+ +## 檢查 { #check-it } + +你應該可以透過 Docker 容器的網址檢查,例如:http://192.168.99.100/items/5?q=somequeryhttp://127.0.0.1/items/5?q=somequery(或等效的、使用你的 Docker 主機)。 + +你會看到類似這樣: + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +## 互動式 API 文件 { #interactive-api-docs } + +現在你可以前往 http://192.168.99.100/docshttp://127.0.0.1/docs(或等效的、使用你的 Docker 主機)。 + +你會看到自動產生的互動式 API 文件(由 Swagger UI 提供): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +## 替代的 API 文件 { #alternative-api-docs } + +你也可以前往 http://192.168.99.100/redochttp://127.0.0.1/redoc(或等效的、使用你的 Docker 主機)。 + +你會看到另一種自動產生的文件(由 ReDoc 提供): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## 為單檔 FastAPI 建置 Docker 映像 { #build-a-docker-image-with-a-single-file-fastapi } + +如果你的 FastAPI 是單一檔案,例如沒有 `./app` 目錄的 `main.py`,你的檔案結構可能像這樣: + +``` +. +├── Dockerfile +├── main.py +└── requirements.txt +``` + +接著你只需要在 `Dockerfile` 中調整對應的路徑以複製該檔案: + +```{ .dockerfile .annotate hl_lines="10 13" } +FROM python:3.14 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (1)! +COPY ./main.py /code/ + +# (2)! +CMD ["fastapi", "run", "main.py", "--port", "80"] +``` + +1. 將 `main.py` 直接複製到 `/code` 目錄(不需要 `./app` 目錄)。 + +2. 使用 `fastapi run` 來服務單檔的 `main.py` 應用。 + +當你把檔案傳給 `fastapi run`,它會自動偵測這是一個單一檔案而非套件的一部分,並知道如何匯入並服務你的 FastAPI 應用。😎 + +## 部署概念 { #deployment-concepts } + +我們用容器的角度再談一次部分相同的[部署概念](concepts.md){.internal-link target=_blank}。 + +容器主要是簡化應用「建置與部署」流程的工具,但它們不強制特定的方式來處理這些「部署概念」,而是有多種策略可選。 + +好消息是,每種不同的策略都能涵蓋所有部署概念。🎉 + +讓我們用容器的角度回顧這些部署概念: + +* HTTPS +* 開機自動執行 +* 失敗重啟 +* 複本(執行的行程數量) +* 記憶體 +* 啟動前的前置步驟 + +## HTTPS { #https } + +若僅聚焦於 FastAPI 應用的「容器映像」(以及稍後的執行中「容器」),HTTPS 通常會由另一個工具在「外部」處理。 + +它可以是另一個容器,例如使用 Traefik,來處理「HTTPS」以及「自動」取得「憑證」。 + +/// tip | 提示 + +Traefik 與 Docker、Kubernetes 等整合良好,因此為你的容器設定與配置 HTTPS 非常容易。 + +/// + +或者,HTTPS 也可能由雲端供應商以其服務來處理(同時應用仍以容器執行)。 + +## 開機自動執行與重啟 { #running-on-startup-and-restarts } + +通常會有另一個工具負責「啟動並執行」你的容器。 + +可能是直接用 Docker、Docker Compose、Kubernetes、某個雲端服務等。 + +在大多數(或全部)情況下,都有簡單的選項可以在開機時自動執行容器,並在失敗時重啟。例如,在 Docker 中,可用命令列選項 `--restart`。 + +如果不使用容器,讓應用在開機時自動執行並支援重啟可能既繁瑣又困難。但在「使用容器」時,這類功能在多數情況下都是預設包含的。✨ + +## 複本 - 行程數量 { #replication-number-of-processes } + +如果你在有 Kubernetes、Docker Swarm Mode、Nomad,或其他類似的分散式容器管理系統的「叢集」上運作,那你大概會希望在「叢集層級」處理「複本」,而不是在每個容器內使用「行程管理器」(例如帶有 workers 的 Uvicorn)。 + +像 Kubernetes 這類的分散式容器管理系統,通常內建處理「容器複本」以及支援進入請求的「負載平衡」的能力——全部都在「叢集層級」。 + +在這些情況下,你大概會想要如[上面所述](#dockerfile)從零開始建置一個 Docker 映像,安裝你的相依,並且只執行「單一 Uvicorn 行程」,而不是使用多個 Uvicorn workers。 + +### 負載平衡器 { #load-balancer } + +使用容器時,通常會有某個元件在「主埠口」上監聽。它也可能是另一個同時做為「TLS 終止代理」的容器來處理「HTTPS」,或類似的工具。 + +由於這個元件會承接請求的「負載」,並將其分配給 workers,使其(希望)「平衡」,因此也常被稱為「負載平衡器(Load Balancer)」。 + +/// tip | 提示 + +用於 HTTPS 的同一個「TLS 終止代理」元件通常也會是「負載平衡器」。 + +/// + +而在使用容器時,你用來啓動與管理它們的系統,已內建把「網路通訊」(例如 HTTP 請求)從該「負載平衡器」(也可能是「TLS 終止代理」)傳遞到你的應用容器的工具。 + +### 一個負載平衡器 - 多個工作容器 { #one-load-balancer-multiple-worker-containers } + +使用 Kubernetes 或類似的分散式容器管理系統時,使用其內部網路機制可以讓在主「埠口」上監聽的單一「負載平衡器」,把通訊(請求)傳遞給可能的「多個執行你應用的容器」。 + +每個執行你應用的容器通常只有「單一行程」(例如執行你的 FastAPI 應用的 Uvicorn 行程)。它們都是「相同的容器」,執行相同的東西,但各自擁有自己的行程、記憶體等。如此即可在 CPU 的「不同核心」、甚至是「不同機器」上發揮「平行化」的效益。 + +而分散式容器系統中的「負載平衡器」會「輪流」把請求分配給各個執行你應用的容器。因此,每個請求都可能由多個「複製的容器」中的其中一個來處理。 + +通常這個「負載平衡器」也能處理送往叢集中「其他」應用的請求(例如不同網域,或不同 URL 路徑前綴),並把通訊轉送到該「其他」應用對應的容器。 + +### 每個容器一個行程 { #one-process-per-container } + +在這種情境中,你大概會希望「每個容器只有一個(Uvicorn)行程」,因為你已在叢集層級處理了複本。 + +所以這種情況下,你「不會」想在容器中使用多個 workers(例如用 `--workers` 命令列選項)。你會想每個容器只執行「一個 Uvicorn 行程」(但可能有多個容器)。 + +在容器內再放一個行程管理器(如同多 workers 的情況)只會增加「不必要的複雜度」,而你很可能已用叢集系統處理好了。 + +### 多行程容器與特殊情境 { #containers-with-multiple-processes-and-special-cases } + +當然,有些「特殊情況」你可能會想在「一個容器內」執行數個「Uvicorn worker 行程」。 + +在這些情況中,你可以用 `--workers` 命令列選項來設定要啟動的 workers 數量: + +```{ .dockerfile .annotate } +FROM python:3.14 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +# (1)! +CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"] +``` + +1. 這裡我們使用 `--workers` 命令列選項把 worker 數量設定為 4。 + +以下是一些合理的例子: + +#### 簡單應用 { #a-simple-app } + +如果你的應用「足夠簡單」,可以在「單一伺服器」而非叢集上執行,你可能會希望在容器內使用行程管理器。 + +#### Docker Compose { #docker-compose } + +如果你部署到「單一伺服器」(非叢集),且使用「Docker Compose」,那麼你無法輕易(用 Docker Compose)在保有共用網路與「負載平衡」的同時管理容器複本。 + +那你可能會想要「單一容器」搭配「行程管理器」,在其中啟動「多個 worker 行程」。 + +--- + +重點是,這些「都不是」必須盲目遵守的「鐵律」。你可以用這些想法來「評估你的使用情境」,並決定對你的系統最好的做法,看看如何管理以下概念: + +* 安全性 - HTTPS +* 開機自動執行 +* 失敗重啟 +* 複本(執行的行程數量) +* 記憶體 +* 啟動前的前置步驟 + +## 記憶體 { #memory } + +如果你採用「每個容器一個行程」,那每個容器(若有複本則多個容器)所消耗的記憶體會是相對明確、穩定且有限的。 + +接著你可以在容器管理系統(例如 Kubernetes)的設定中為容器設定相同的記憶體限制與需求。如此,它就能在「可用的機器」上「複製容器」,並考量容器所需的記憶體量與叢集中機器的可用記憶體。 + +若你的應用「很簡單」,這可能「不是問題」,你可能不需要指定嚴格的記憶體限制。但如果你「使用大量記憶體」(例如使用機器學習模型),你應該檢查實際消耗的記憶體,並調整「每台機器上執行的容器數量」(也許還要為叢集加機器)。 + +若你採用「每個容器多個行程」,你就得確保啟動的行程數量不會「超過可用記憶體」。 + +## 啟動前的前置步驟與容器 { #previous-steps-before-starting-and-containers } + +如果你使用容器(例如 Docker、Kubernetes),那有兩種主要做法可用。 + +### 多個容器 { #multiple-containers } + +如果你有「多個容器」,且每個容器大概都只執行「單一行程」(例如在一個 Kubernetes 叢集中),那你可能會想要一個「獨立的容器」來完成「前置步驟」的工作,並只在單一容器、單一行程中執行,接著才啟動多個複本的工作容器。 + +/// info | 資訊 + +如果你使用 Kubernetes,這大概會是一個 Init Container。 + +/// + +如果你的情境中,讓那些前置步驟「平行重複執行多次」沒有問題(例如不是在跑資料庫遷移,而只是檢查資料庫是否就緒),那也可以把這些步驟放在每個容器中、在啟動主要行程前執行。 + +### 單一容器 { #single-container } + +如果你的架構很簡單,只有「單一容器」,接著在其中啟動多個「worker 行程」(或也可能就一個行程),那你可以在相同的容器中、於啟動應用行程前先執行這些前置步驟。 + +### 基底 Docker 映像 { #base-docker-image } + +曾經有一個官方的 FastAPI Docker 映像:tiangolo/uvicorn-gunicorn-fastapi。但現在已被棄用。⛔️ + +你大概「不應該」使用這個基底 Docker 映像(或其他類似的)。 + +如果你使用 Kubernetes(或其他)並已在叢集層級設定「複本」、使用多個「容器」。在這些情況下,更好的做法是如上所述[從零建置映像](#build-a-docker-image-for-fastapi)。 + +若你需要多個 workers,只要使用 `--workers` 命令列選項即可。 + +/// note | 技術細節 + +這個 Docker 映像是在 Uvicorn 尚未支援管理與重啟死亡 workers 的年代所建立,因此需要用 Gunicorn 搭配 Uvicorn,為了讓 Gunicorn 管理並重啟 Uvicorn workers,而引入了相當多的複雜度。 + +但現在 Uvicorn(以及 `fastapi` 指令)已支援使用 `--workers`,因此沒有理由使用一個基底 Docker 映像,而不是建置你自己的(而且實際上程式碼量也差不多 😅)。 + +/// + +## 部署容器映像 { #deploy-the-container-image } + +擁有容器(Docker)映像後,有多種部署方式。 + +例如: + +* 在單一伺服器上使用 Docker Compose +* 使用 Kubernetes 叢集 +* 使用 Docker Swarm Mode 叢集 +* 使用像 Nomad 之類的其他工具 +* 使用會接收你的容器映像並代為部署的雲端服務 + +## 使用 `uv` 的 Docker 映像 { #docker-image-with-uv } + +如果你使用 uv 來安裝與管理專案,你可以參考他們的 uv Docker 指南。 + +## 總結 { #recap } + +使用容器系統(例如 Docker 與 Kubernetes)可以相對直接地處理所有「部署概念」: + +* HTTPS +* 開機自動執行 +* 失敗重啟 +* 複本(執行的行程數量) +* 記憶體 +* 啟動前的前置步驟 + +多數情況下,你大概不會想用任何基底映像,而是「從零建置容器映像」,以官方的 Python Docker 映像為基礎。 + +善用 `Dockerfile` 中指令的「順序」與「Docker 快取」,你可以「最小化建置時間」,提升生產力(並避免無聊)。😎 diff --git a/docs/zh-hant/docs/deployment/fastapicloud.md b/docs/zh-hant/docs/deployment/fastapicloud.md new file mode 100644 index 000000000..5663590d1 --- /dev/null +++ b/docs/zh-hant/docs/deployment/fastapicloud.md @@ -0,0 +1,65 @@ +# FastAPI Cloud { #fastapi-cloud } + +你可以用「一行指令」把你的 FastAPI 應用程式部署到 FastAPI Cloud。如果你還沒加入,快去登記等候名單吧!🚀 + +## 登入 { #login } + +請先確認你已經有 **FastAPI Cloud** 帳號(我們已從等候名單邀請你 😉)。 + +然後登入: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +## 部署 { #deploy } + +現在用「一行指令」部署你的應用: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +就這樣!現在你可以透過該 URL 造訪你的應用。✨ + +## 關於 FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** 由 **FastAPI** 的作者與團隊打造。 + +它以最少的心力,精簡化建立、部署與存取 API 的流程。 + +它把使用 FastAPI 開發應用的優異開發體驗,延伸到將它們部署到雲端。🎉 + +它也會為你處理部署應用時多數需要面對的事項,例如: + +* HTTPS +* 多副本,並可依據請求自動擴縮 +* 等等。 + +FastAPI Cloud 是 *FastAPI and friends* 開源專案的主要贊助者與資金提供者。✨ + +## 部署到其他雲端供應商 { #deploy-to-other-cloud-providers } + +FastAPI 是基於標準的開源專案。你可以把 FastAPI 應用部署到你選擇的任何雲端供應商。 + +請依照你的雲端供應商的指南,使用他們的方式部署 FastAPI 應用。🤓 + +## 部署到你自己的伺服器 { #deploy-your-own-server } + +在這份「部署」指南的後續內容中,我也會教你所有細節,讓你了解背後發生了什麼、需要做哪些事,以及如何自行部署 FastAPI 應用,包括在你自己的伺服器上。🤓 diff --git a/docs/zh-hant/docs/deployment/https.md b/docs/zh-hant/docs/deployment/https.md new file mode 100644 index 000000000..0934b0283 --- /dev/null +++ b/docs/zh-hant/docs/deployment/https.md @@ -0,0 +1,231 @@ +# 關於 HTTPS { #about-https } + +人們很容易以為 HTTPS 只是「啟用或未啟用」的功能。 + +但實際上複雜得多。 + +/// tip + +如果你趕時間或不在意細節,可以直接看後續章節,依照逐步指引用不同方式完成設定。 + +/// + +想從使用者角度學習 HTTPS 基礎,請參考 https://howhttps.works/。 + +接著以開發者角度,談幾個關於 HTTPS 需要注意的重點: + +* 對於 HTTPS,伺服器需要擁有由**第三方**簽發的**「憑證」**。 + * 這些憑證實際上是向第三方**取得**,不是「自己產生」。 +* 憑證有**有效期**。 + * 會**過期**。 + * 過期後需要**續期**,也就是再向第三方**重新取得**。 +* 連線加密發生在 **TCP 層**。 + * 那是在 **HTTP 的下一層**。 + * 因此,**憑證與加密**的處理會在 **進入 HTTP 之前**完成。 +* **TCP 不知道「網域」**,只知道 IP 位址。 + * 關於**特定網域**的資訊會放在 **HTTP 資料**中。 +* **HTTPS 憑證**是為某個**特定網域**背書,但通訊協定與加密在 TCP 層進行,發生在**尚未知道**要處理哪個網域之前。 +* **預設**情況下,這表示你每個 IP 位址**只能**使用**一張 HTTPS 憑證**。 + * 不論你的伺服器多強或你在上面跑的應用多小。 + * 不過,這是有**解法**的。 +* 在 **TLS** 協定(在 HTTP 之前於 TCP 層處理加密的協定)上有個**擴充**稱為 **SNI**。 + * 這個 SNI 擴充讓單一伺服器(單一 IP 位址)可以擁有**多張 HTTPS 憑證**,並服務**多個 HTTPS 網域/應用**。 + * 要讓它運作,伺服器上必須有一個**單一**的元件(程式)在**公用 IP**上監聽,且持有伺服器上的**所有 HTTPS 憑證**。 +* 在取得安全連線**之後**,通訊協定**仍然是 HTTP**。 + * 雖然透過 **HTTP 協定**傳送,但內容是**加密**的。 + +常見做法是讓伺服器(機器、主機等)上跑**一個程式/HTTP 伺服器**來**管理所有 HTTPS 相關工作**:接收**加密的 HTTPS 請求**、將其**解密**成**純 HTTP 請求**轉交給同台伺服器上實際運行的 HTTP 應用(本例為 **FastAPI** 應用)、從應用取得 **HTTP 回應**、再用合適的 **HTTPS 憑證**將其**加密**並以 **HTTPS** 傳回給用戶端。這類伺服器常被稱為 **TLS 終止代理 (TLS Termination Proxy)**。 + +可作為 TLS 終止代理的選項包括: + +* Traefik(也可處理憑證續期) +* Caddy(也可處理憑證續期) +* Nginx +* HAProxy + +## Let's Encrypt { #lets-encrypt } + +在 Let's Encrypt 之前,這些 **HTTPS 憑證**是由受信任的第三方販售。 + +取得這些憑證的流程過去相當繁瑣,需要許多手續,且憑證相當昂貴。 + +之後出現了 **Let's Encrypt**。 + +它是 Linux Foundation 的專案,能**免費**且自動化地提供 **HTTPS 憑證**。這些憑證採用標準的密碼學安全機制,且有效期較短(約 3 個月),因此因為壽命短,**安全性其實更好**。 + +網域會被安全驗證,憑證會自動產生。這也讓憑證續期得以自動化。 + +目標是讓憑證的申請與續期自動化,讓你**永遠免費使用安全的 HTTPS**。 + +## 給開發者的 HTTPS { #https-for-developers } + +以下以逐步範例說明一個 HTTPS API 可能長什麼樣子,著重於對開發者重要的概念。 + +### 網域名稱 { #domain-name } + +通常會先**取得**一個**網域名稱**,接著在 DNS 伺服器(可能是同一個雲端供應商)中設定它。 + +你可能會租一台雲端伺服器(虛擬機)或類似的服務,並擁有一個固定的**公用 IP 位址**。 + +在 DNS 伺服器中,你會設定一個紀錄(「`A record`」)指向**你的網域**所對應的**伺服器公用 IP 位址**。 + +這通常在初次建置時設定一次即可。 + +/// tip + +「網域名稱」是發生在 HTTPS 之前的事情,但一切都依賴網域與 IP 位址,因此在此一併說明。 + +/// + +### DNS { #dns } + +現在聚焦在實際的 HTTPS 部分。 + +首先,瀏覽器會向 **DNS 伺服器**查詢該**網域的 IP**,例如 `someapp.example.com`。 + +DNS 伺服器會回覆要使用的**IP 位址**,那就是你在 DNS 伺服器中設定的、伺服器對外的公用 IP 位址。 + + + +### TLS 握手開始 { #tls-handshake-start } + +接著瀏覽器會連線到該 IP 的 **443 埠**(HTTPS 預設埠)。 + +通訊的第一部分是建立用戶端與伺服器之間的連線,並協商要使用哪些金鑰等密碼參數。 + + + +用戶端與伺服器為建立 TLS 連線而進行的這段互動稱為 **TLS 握手**。 + +### 帶 SNI 擴充的 TLS { #tls-with-sni-extension } + +在特定的**IP 位址**與特定**埠**上,同一時間**只能有一個行程**在監聽。可以在同一個 IP 上監聽不同埠,但每個 IP 與埠的組合只能有一個行程。 + +TLS(HTTPS)預設使用 `443` 埠,因此我們需要用到這個埠。 + +由於只能有一個行程監聽該埠,負責監聽的會是 **TLS 終止代理**。 + +TLS 終止代理會存取一張或多張 **TLS 憑證**(HTTPS 憑證)。 + +透過上面提到的 **SNI 擴充**,TLS 終止代理會根據用戶端預期的網域,從可用的 TLS(HTTPS)憑證中挑選本次連線要用的憑證。 + +在這個例子中,會使用 `someapp.example.com` 的憑證。 + + + +用戶端**信任**簽發該 TLS 憑證的單位(本例為 Let's Encrypt,稍後會再談),因此可以**驗證**憑證有效。 + +接著,用戶端與 TLS 終止代理會以該憑證為基礎,**協商後續如何加密**整段 **TCP 通訊**。至此完成 **TLS 握手**。 + +之後,用戶端與伺服器之間就有一條**已加密的 TCP 連線**,這就是 TLS 所提供的能力。接著他們可以在這條連線上開始實際的 **HTTP** 通訊。 + +這也就是 **HTTPS** 的本質:在**安全的 TLS 連線**內傳送一般的 **HTTP**,而非在純(未加密)的 TCP 連線上。 + +/// tip + +請注意,加密發生在 **TCP 層**,不是在 HTTP 層。 + +/// + +### HTTPS 請求 { #https-request } + +現在用戶端與伺服器(更精確地說,是瀏覽器與 TLS 終止代理)之間已有**加密的 TCP 連線**,他們可以開始進行 **HTTP** 通訊。 + +因此,用戶端送出一個 **HTTPS 請求**。它其實就是透過加密的 TLS 連線發送的一個 HTTP 請求。 + + + +### 解密請求 { #decrypt-the-request } + +TLS 終止代理會依照先前協商的方式**解密請求**,並將**純(已解密)的 HTTP 請求**轉交給運行應用的行程(例如以 Uvicorn 執行的 FastAPI 應用行程)。 + + + +### HTTP 回應 { #http-response } + +應用會處理該請求,並將**純(未加密)的 HTTP 回應**送回 TLS 終止代理。 + + + +### HTTPS 回應 { #https-response } + +TLS 終止代理接著會依照先前協商(起點是 `someapp.example.com` 的憑證)的方式**加密回應**,並傳回給瀏覽器。 + +接著,瀏覽器會驗證回應是否合法、是否使用正確的金鑰加密等。然後**解密回應**並處理。 + + + +用戶端(瀏覽器)會知道回應來自正確的伺服器,因為它使用了先前依據 **HTTPS 憑證**所協商的密碼機制。 + +### 多個應用 { #multiple-applications } + +同一台(或多台)伺服器上可以有**多個應用**,例如其他 API 程式或資料庫。 + +雖然只有一個行程可以處理特定 IP 與埠的組合(本例中的 TLS 終止代理),但其他應用/行程也都能在伺服器上運行,只要它們不使用相同的**公用 IP 與埠**組合即可。 + + + +如此一來,TLS 終止代理就能為**多個網域**、多個應用處理 HTTPS 與憑證,並把請求轉發到對應的應用。 + +### 憑證續期 { #certificate-renewal } + +在未來某個時間點,每張憑證都會**過期**(自取得起約 3 個月)。 + +之後,會有另一個程式(有時是另一個程式,有時也可能就是同一個 TLS 終止代理)與 Let's Encrypt 溝通並續期憑證。 + + + +**TLS 憑證**是**綁定網域名稱**,不是綁定 IP 位址。 + +因此,要續期憑證時,續期程式需要向憑證機構(Let's Encrypt)**證明**它的確**擁有並控制該網域**。 + +為了達成這點、並兼顧不同應用情境,有幾種常見方式: + +* **修改部分 DNS 紀錄**。 + * 為此,續期程式需要支援該 DNS 供應商的 API,因此依你使用的 DNS 供應商不同,這方式可能可行或不可行。 +* **作為伺服器運行**(至少在憑證申請過程中)於該網域對應的公用 IP 上。 + * 如前所述,同一時間只有一個行程能在特定 IP 與埠上監聽。 + * 這也是為什麼讓同一個 TLS 終止代理一併處理憑證續期非常實用的原因之一。 + * 否則你可能得暫停 TLS 終止代理、啟動續期程式申請憑證、將憑證配置到 TLS 終止代理,然後再重啟 TLS 終止代理。這並不理想,因為在 TLS 終止代理停機期間,你的應用將不可用。 + +在不中斷服務的同時完成整個續期流程,是你會想用**獨立系統(TLS 終止代理)來處理 HTTPS**、而不是直接把 TLS 憑證掛在應用伺服器(例如 Uvicorn)上的主要原因之一。 + +## 代理轉發標頭 { #proxy-forwarded-headers } + +當你使用代理處理 HTTPS 時,你的**應用伺服器**(例如透過 FastAPI CLI 啟動的 Uvicorn)其實不知道任何 HTTPS 的處理流程,它是用純 HTTP 與 **TLS 終止代理**通訊。 + +這個**代理**通常會在把請求轉發給**應用伺服器**之前,臨時加入一些 HTTP 標頭,讓應用伺服器知道該請求是由代理**轉發**過來的。 + +/// note | 技術細節 + +這些代理標頭包括: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +然而,因為**應用伺服器**並不知道自己在受信任的**代理**之後,預設情況下它不會信任這些標頭。 + +但你可以設定**應用伺服器**去信任由**代理**送來的「轉發」標頭。若你使用 FastAPI CLI,可以用 *CLI 參數* `--forwarded-allow-ips` 指定應信任哪些 IP 來的「轉發」標頭。 + +例如,如果**應用伺服器**只會接收來自受信任**代理**的連線,你可以設定 `--forwarded-allow-ips="*"`,也就是信任所有來源 IP,因為實際上它只會收到**代理**那個 IP 送來的請求。 + +如此一來,應用就能知道自己的對外 URL、是否使用 HTTPS、網域為何等資訊。 + +這在正確處理重新導向等情境時很有用。 + +/// tip + +你可以在文件 [在代理後方 - 啟用代理轉發標頭](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} 中了解更多。 + +/// + +## 重點回顧 { #recap } + +擁有 **HTTPS** 非常重要,而且在多數情況都相當**關鍵**。作為開發者,你在 HTTPS 上的大部分投入其實是**理解這些概念**及其運作方式。 + +一旦掌握了**給開發者的 HTTPS 基礎**,你就能輕鬆組合並設定不同工具,讓一切管理變得簡單。 + +在接下來的章節中,我會示範幾個為 **FastAPI** 應用設定 **HTTPS** 的具體例子。🔒 diff --git a/docs/zh-hant/docs/deployment/index.md b/docs/zh-hant/docs/deployment/index.md index 9edd3368b..ddf533efc 100644 --- a/docs/zh-hant/docs/deployment/index.md +++ b/docs/zh-hant/docs/deployment/index.md @@ -8,7 +8,7 @@ 對於一個 **Web API**,部署通常涉及將其放置在**遠端伺服器**上,並使用性能優良且穩定的**伺服器程式**,確保使用者能夠高效、無中斷地存取應用程式,且不會遇到問題。 -這與**開發**階段形成鮮明對比,在**開發**階段,你會不斷更改程式碼、破壞程式碼、修復程式碼,然後停止和重新啟動伺服器等。 +這與**開發**階段形成鮮明對比,在**開發**階段,你會不斷更改程式碼、破壞程式碼、修復程式碼,然後停止和重新啟動開發伺服器等。 ## 部署策略 { #deployment-strategies } diff --git a/docs/zh-hant/docs/deployment/manually.md b/docs/zh-hant/docs/deployment/manually.md new file mode 100644 index 000000000..bf9f6a93f --- /dev/null +++ b/docs/zh-hant/docs/deployment/manually.md @@ -0,0 +1,157 @@ +# 手動執行伺服器 { #run-a-server-manually } + +## 使用 `fastapi run` 指令 { #use-the-fastapi-run-command } + +簡而言之,使用 `fastapi run` 來啟動你的 FastAPI 應用: + +
+ +```console +$ fastapi run main.py + + FastAPI Starting production 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://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) +``` + +
+ +這在多數情況下都適用。😎 + +你可以用這個指令在容器、伺服器等環境中啟動你的 FastAPI 應用。 + +## ASGI 伺服器 { #asgi-servers } + +我們再深入一些細節。 + +FastAPI 採用建立 Python 網頁框架與伺服器的標準 ASGI。FastAPI 是一個 ASGI 網頁框架。 + +在遠端伺服器機器上執行 FastAPI 應用(或任何 ASGI 應用)所需的關鍵是 ASGI 伺服器程式,例如 Uvicorn;`fastapi` 指令預設就是使用它。 + +有數個替代方案,包括: + +* Uvicorn:高效能 ASGI 伺服器。 +* Hypercorn:支援 HTTP/2 與 Trio 等功能的 ASGI 伺服器。 +* Daphne:為 Django Channels 打造的 ASGI 伺服器。 +* Granian:針對 Python 應用的 Rust HTTP 伺服器。 +* NGINX Unit:NGINX Unit 是輕量且多功能的網頁應用執行環境。 + +## 伺服器機器與伺服器程式 { #server-machine-and-server-program } + +有個命名上的小細節請留意。💡 + +「server(伺服器)」一詞常同時用來指遠端/雲端電腦(實體或虛擬機器),也用來指在該機器上執行的程式(例如 Uvicorn)。 + +因此看到「server」時,文意可能指這兩者之一。 + +指涉遠端機器時,常稱為 server、machine、VM(虛擬機器)、node 等,這些都指某種遠端機器(通常執行 Linux),你會在其上執行程式。 + +## 安裝伺服器程式 { #install-the-server-program } + +安裝 FastAPI 時會附帶一個可用於生產環境的伺服器 Uvicorn,你可以用 `fastapi run` 來啟動它。 + +但你也可以手動安裝 ASGI 伺服器。 + +請先建立並啟用一個 [虛擬環境](../virtual-environments.md){.internal-link target=_blank},接著再安裝伺服器程式。 + +例如,安裝 Uvicorn: + +
+ +```console +$ pip install "uvicorn[standard]" + +---> 100% +``` + +
+ +其他 ASGI 伺服器的安裝流程也大致相同。 + +/// tip + +加入 `standard` 會讓 Uvicorn 安裝並使用一些建議的額外相依套件。 + +其中包含 `uvloop`,它是 `asyncio` 的高效能替代實作,可大幅提升並行效能。 + +當你用 `pip install "fastapi[standard]"` 安裝 FastAPI 時,也會一併取得 `uvicorn[standard]`。 + +/// + +## 執行伺服器程式 { #run-the-server-program } + +如果你是手動安裝 ASGI 伺服器,通常需要提供特定格式的 import 字串,讓它能匯入你的 FastAPI 應用: + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 80 + +INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) +``` + +
+ +/// note + +指令 `uvicorn main:app` 指的是: + +* `main`:檔案 `main.py`(Python「模組」)。 +* `app`:在 `main.py` 中以 `app = FastAPI()` 建立的物件。 + +等同於: + +```Python +from main import app +``` + +/// + +其他 ASGI 伺服器也有類似的指令,詳見各自的文件。 + +/// warning + +Uvicorn 與其他伺服器支援 `--reload` 選項,對開發期間很有幫助。 + +`--reload` 會消耗更多資源,也較不穩定等。 + +它在開發階段很實用,但在生產環境中不應使用。 + +/// + +## 部署觀念 { #deployment-concepts } + +上述範例會啟動伺服器程式(如 Uvicorn),以單一行程在指定連接埠(如 `80`)上監聽所有 IP(`0.0.0.0`)。 + +這是基本概念。但你很可能還需要處理一些額外事項,例如: + +* 安全性 - HTTPS +* 開機自動啟動 +* 自動重啟 +* 多副本(執行的行程數量) +* 記憶體 +* 啟動前需要執行的前置步驟 + +在下一章節我會進一步說明這些觀念、思考方式,以及對應的處理策略與實作範例。🚀 diff --git a/docs/zh-hant/docs/deployment/server-workers.md b/docs/zh-hant/docs/deployment/server-workers.md new file mode 100644 index 000000000..56569e49a --- /dev/null +++ b/docs/zh-hant/docs/deployment/server-workers.md @@ -0,0 +1,139 @@ +# 伺服器工作處理序 - 使用 Uvicorn Workers { #server-workers-uvicorn-with-workers } + +我們回顧一下先前提到的部署概念: + +* 安全 - HTTPS +* 系統啟動時執行 +* 重啟 +* **副本(正在執行的處理序數量)** +* 記憶體 +* 啟動前的前置作業 + +到目前為止,依照文件中的教學,你大多是透過 `fastapi` 指令啟動一個執行 Uvicorn 的伺服器程式,且只跑單一處理序。 + +在部署應用時,你通常會希望有一些處理序的複製來善用多核心,並能處理更多請求。 + +如同前一章關於 [部署概念](concepts.md){.internal-link target=_blank} 所示,你可以採用多種策略。 + +這裡會示範如何使用 `fastapi` 指令或直接使用 `uvicorn` 指令,搭配 Uvicorn 的工作處理序(worker processes)。 + +/// info + +如果你使用容器(例如 Docker 或 Kubernetes),我會在下一章說明更多:[容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank}。 + +特別是,在 **Kubernetes** 上執行時,你多半會選擇不要使用 workers,而是每個容器只跑一個 **Uvicorn 單一處理序**。我會在該章節中進一步說明。 + +/// + +## 多個工作處理序 { #multiple-workers } + +你可以用命令列選項 `--workers` 來啟動多個 workers: + +//// tab | `fastapi` + +如果你使用 `fastapi` 指令: + +
+ +```console +$ fastapi run --workers 4 main.py + + FastAPI Starting production 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://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. +``` + +
+ +//// + +//// tab | `uvicorn` + +如果你偏好直接使用 `uvicorn` 指令: + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 +INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit) +INFO: Started parent process [27365] +INFO: Started server process [27368] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27369] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27370] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27367] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +//// + +這裡唯一新增的選項是 `--workers`,告訴 Uvicorn 要啟動 4 個工作處理序。 + +你也會看到它顯示每個處理序的 **PID**,`27365` 是父處理序(這是**處理序管理器**),另外每個工作處理序各有一個:`27368`、`27369`、`27370`、`27367`。 + +## 部署概念 { #deployment-concepts } + +你已經看到如何使用多個 **workers** 來將應用的執行進行**平行化**,善用 CPU 的**多核心**,並能服務**更多請求**。 + +在上面的部署概念清單中,使用 workers 主要能幫助到**副本**這一塊,並對**重啟**也有一點幫助,但你仍需要處理其他部分: + +* **安全 - HTTPS** +* **系統啟動時執行** +* ***重啟*** +* 副本(正在執行的處理序數量) +* **記憶體** +* **啟動前的前置作業** + +## 容器與 Docker { #containers-and-docker } + +在下一章 [容器中的 FastAPI - Docker](docker.md){.internal-link target=_blank} 我會說明一些策略,幫你處理其他的**部署概念**。 + +我會示範如何**從零建立你的映像檔**來執行單一 Uvicorn 處理序。這個流程相當簡單,而且在使用像 **Kubernetes** 這類分散式容器管理系統時,大多情況也會這麼做。 + +## 重點回顧 { #recap } + +你可以在 `fastapi` 或 `uvicorn` 指令中使用 `--workers` 這個 CLI 選項來啟動多個工作處理序,以善用**多核心 CPU**,**平行**執行多個處理序。 + +如果你要自行建置**自己的部署系統**,你可以運用這些工具與想法,同時自行處理其他部署概念。 + +接著看看下一章關於在容器(例如 Docker 與 Kubernetes)中使用 **FastAPI**。你會看到那些工具也有簡單的方法來解決其他**部署概念**。✨ diff --git a/docs/zh-hant/docs/deployment/versions.md b/docs/zh-hant/docs/deployment/versions.md new file mode 100644 index 000000000..c4f9c5262 --- /dev/null +++ b/docs/zh-hant/docs/deployment/versions.md @@ -0,0 +1,93 @@ +# 關於 FastAPI 版本 { #about-fastapi-versions } + +**FastAPI** 已經在許多應用與系統的生產環境中使用,且測試涵蓋率維持在 100%。同時開發仍在快速推進。 + +經常加入新功能、定期修復錯誤,程式碼也在持續改進。 + +這就是為什麼目前版本仍為 `0.x.x`,這表示每個版本都可能包含破壞性變更。這遵循 語意化版本(Semantic Versioning) 的慣例。 + +你現在就可以用 **FastAPI** 建置生產環境的應用(而且你可能已經這麼做一段時間了),只要確保你使用的版本能與其餘程式碼正確相容。 + +## 鎖定你的 `fastapi` 版本 { #pin-your-fastapi-version } + +首先,你應該將你使用的 **FastAPI** 版本「鎖定(pin)」到你知道對你的應用可正常運作的最新特定版本。 + +例如,假設你的應用使用 `0.112.0` 版本。 + +如果你使用 `requirements.txt` 檔案,可以這樣指定版本: + +```txt +fastapi[standard]==0.112.0 +``` + +這表示你會使用完全相同的 `0.112.0` 版本。 + +或你也可以這樣鎖定: + +```txt +fastapi[standard]>=0.112.0,<0.113.0 +``` + +這表示會使用 `0.112.0`(含)以上但小於 `0.113.0` 的版本,例如 `0.112.2` 也會被接受。 + +如果你使用其他安裝管理工具,例如 `uv`、Poetry、Pipenv 等,它們也都有可用來指定套件特定版本的方法。 + +## 可用版本 { #available-versions } + +你可以在 [發行說明](../release-notes.md){.internal-link target=_blank} 查看可用版本(例如用來確認目前最新版本)。 + +## 關於版本 { #about-versions } + +依照語意化版本的慣例,任何低於 `1.0.0` 的版本都可能加入破壞性變更。 + +FastAPI 也遵循慣例:任何「PATCH」版本變更僅用於修正錯誤與非破壞性變更。 + +/// tip + +「PATCH」是最後一個數字,例如在 `0.2.3` 中,PATCH 版本是 `3`。 + +/// + +因此,你可以將版本鎖定為如下形式: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +破壞性變更與新功能會在「MINOR」版本加入。 + +/// tip + +「MINOR」是中間的數字,例如在 `0.2.3` 中,MINOR 版本是 `2`。 + +/// + +## 升級 FastAPI 版本 { #upgrading-the-fastapi-versions } + +你應該為你的應用撰寫測試。 + +在 **FastAPI** 中這很容易(感謝 Starlette),請參考文件:[測試](../tutorial/testing.md){.internal-link target=_blank} + +有了測試之後,你就可以將 **FastAPI** 升級到較新的版本,並透過執行測試來確保所有程式碼都能正確運作。 + +如果一切正常,或在完成必要調整且所有測試通過之後,就可以把你的 `fastapi` 鎖定到該新的版本。 + +## 關於 Starlette { #about-starlette } + +你不應鎖定 `starlette` 的版本。 + +不同的 **FastAPI** 版本會使用特定較新的 Starlette 版本。 + +因此,你可以直接讓 **FastAPI** 使用正確的 Starlette 版本。 + +## 關於 Pydantic { #about-pydantic } + +Pydantic 在其測試中涵蓋了 **FastAPI** 的測試,因此 Pydantic 的新版本(高於 `1.0.0`)一律與 FastAPI 相容。 + +你可以將 Pydantic 鎖定到任何高於 `1.0.0`、適合你的版本。 + +例如: + +```txt +pydantic>=2.7.0,<3.0.0 +``` diff --git a/docs/zh-hant/docs/fastapi-cli.md b/docs/zh-hant/docs/fastapi-cli.md index b107e7e73..57b5ec98f 100644 --- a/docs/zh-hant/docs/fastapi-cli.md +++ b/docs/zh-hant/docs/fastapi-cli.md @@ -1,4 +1,4 @@ -# FastAPI CLI +# FastAPI CLI { #fastapi-cli } **FastAPI CLI** 是一個命令列程式,能用來運行你的 FastAPI 應用程式、管理你的 FastAPI 專案等。 @@ -9,72 +9,64 @@
```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 +$ fastapi dev main.py - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ + FastAPI Starting development server 🚀 -INFO Importing module main -INFO Found importable FastAPI app + Searching for package file structure from directories with + __init__.py files + Importing from /home/user/code/awesomeapp - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ + module 🐍 main.py -INFO Using import string main:app + code Importing the FastAPI app object from the module with the + following code: - ╭────────── 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 - │ │ - ╰─────────────────────────────────────────────────────╯ + from main import app -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. + 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 CLI** 內部使用了 Uvicorn,這是一個高效能、適合生產環境的 ASGI 伺服器。 😎 -## `fastapi dev` +## `fastapi dev` { #fastapi-dev } 執行 `fastapi dev` 會啟動開發模式。 預設情況下,**auto-reload** 功能是啟用的,當你對程式碼進行修改時,伺服器會自動重新載入。這會消耗較多資源,並且可能比禁用時更不穩定。因此,你應該只在開發環境中使用此功能。它也會在 IP 位址 `127.0.0.1` 上監聽,這是用於你的機器與自身通訊的 IP 位址(`localhost`)。 -## `fastapi run` +## `fastapi run` { #fastapi-run } 執行 `fastapi run` 會以生產模式啟動 FastAPI。 -預設情況下,**auto-reload** 功能是禁用的。它也會在 IP 位址 `0.0.0.0` 上監聽,表示會監聽所有可用的 IP 地址,這樣任何能與該機器通訊的人都可以公開存取它。這通常是你在生產環境中運行應用程式的方式,例如在容器中運行時。 +預設情況下,**auto-reload** 功能是禁用的。它也會在 IP 位址 `0.0.0.0` 上監聽,表示會監聽所有可用的 IP 位址,這樣任何能與該機器通訊的人都可以公開存取它。這通常是你在生產環境中運行應用程式的方式,例如在容器中運行時。 -在大多數情況下,你會(也應該)有一個「終止代理」來處理 HTTPS,這取決於你如何部署你的應用程式,你的服務供應商可能會為你做這件事,或者你需要自己設置它。 +在大多數情況下,你會(也應該)有一個「終止代理」在外層幫你處理 HTTPS;這取決於你如何部署應用程式,你的服務供應商可能會幫你處理,或者你需要自己設置。 /// tip diff --git a/docs/zh-hant/docs/features.md b/docs/zh-hant/docs/features.md index f44d28a7f..77c020307 100644 --- a/docs/zh-hant/docs/features.md +++ b/docs/zh-hant/docs/features.md @@ -1,17 +1,17 @@ -# 特性 +# 特性 { #features } -## FastAPI 特性 +## FastAPI 特性 { #fastapi-features } -**FastAPI** 提供了以下内容: +**FastAPI** 提供了以下內容: -### 建立在開放標準的基礎上 +### 建立在開放標準的基礎上 { #based-on-open-standards } -* 使用 OpenAPI 來建立 API,包含路徑操作、參數、請求內文、安全性等聲明。 +* 使用 OpenAPI 來建立 API,包含 路徑操作、參數、請求內文、安全性等宣告。 * 使用 JSON Schema(因為 OpenAPI 本身就是基於 JSON Schema)自動生成資料模型文件。 * 經過縝密的研究後圍繞這些標準進行設計,而不是事後在已有系統上附加的一層功能。 * 這也讓我們在多種語言中可以使用自動**用戶端程式碼生成**。 -### 能夠自動生成文件 +### 能夠自動生成文件 { #automatic-docs } FastAPI 能生成互動式 API 文件和探索性的 Web 使用者介面。由於該框架基於 OpenAPI,因此有多種選擇,預設提供了兩種。 @@ -19,12 +19,11 @@ FastAPI 能生成互動式 API 文件和探索性的 Web 使用者介面。由 ![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) -* ReDoc 提供結構性的文件,讓你可以在瀏覽器中查看。 +* 使用 ReDoc 的替代 API 文件。 ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### 現代 Python +### 現代 Python { #just-modern-python } 這一切都基於標準的 **Python 型別**宣告(感謝 Pydantic)。無需學習新的語法,只需使用標準的現代 Python。 @@ -32,7 +31,7 @@ FastAPI 能生成互動式 API 文件和探索性的 Web 使用者介面。由 如果你寫帶有 Python 型別的程式碼: -```python +```Python from datetime import date from pydantic import BaseModel @@ -50,10 +49,9 @@ class User(BaseModel): joined: date ``` - 可以像這樣來使用: -```python +```Python my_user: User = User(id=3, name="John Doe", joined="2018-07-19") second_user_data = { @@ -65,7 +63,6 @@ second_user_data = { my_second_user: User = User(**second_user_data) ``` - /// info `**second_user_data` 意思是: @@ -74,11 +71,11 @@ my_second_user: User = User(**second_user_data) /// -### 多種編輯器支援 +### 多種編輯器支援 { #editor-support } 整個框架的設計是為了讓使用變得簡單且直觀,在開始開發之前,所有決策都在多個編輯器上進行了測試,以確保提供最佳的開發體驗。 -在最近的 Python 開發者調查中,我們能看到 被使用最多的功能是 autocompletion,此功能可以預測將要輸入文字,並自動補齊。 +在最近的 Python 開發者調查中,我們能看到 被使用最多的功能是 autocompletion。 整個 **FastAPI** 框架就是基於這一點,任何地方都可以進行自動補齊。 @@ -86,11 +83,11 @@ my_second_user: User = User(**second_user_data) 在這裡,你的編輯器可能會這樣幫助你: -* Visual Studio Code 中: +* 在 Visual Studio Code 中: ![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) -* PyCharm 中: +* 在 PyCharm 中: ![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) @@ -98,17 +95,13 @@ my_second_user: User = User(**second_user_data) 這樣比較不會輸錯鍵名,不用來回翻看文件,也不用來回滾動尋找你最後使用的 `username` 或者 `user_name`。 - - -### 簡潔 +### 簡潔 { #short } FastAPI 為你提供了**預設值**,讓你不必在初期進行繁瑣的配置,一切都可以自動運作。如果你有更具體的需求,則可以進行調整和自定義, -但在大多數情況下,你只需要直接使用預設值,就能順利完成 API 開發。 +但預設情況下,一切都「直接可用」。 -### 驗證 - -所有的驗證都由完善且強大的 **Pydantic** 處理。 +### 驗證 { #validation } * 驗證大部分(甚至所有?)的 Python **資料型別**,包括: * JSON 物件 (`dict`)。 @@ -120,9 +113,11 @@ FastAPI 為你提供了**預設值**,讓你不必在初期進行繁瑣的配 * URL * Email * UUID + * ...等等。 +所有的驗證都由完善且強大的 **Pydantic** 處理。 -### 安全性及身份驗證 +### 安全性及身份驗證 { #security-and-authentication } FastAPI 已經整合了安全性和身份驗證的功能,但不會強制與特定的資料庫或資料模型進行綁定。 @@ -139,10 +134,9 @@ OpenAPI 中定義的安全模式,包括: 所有的這些都是可重複使用的工具和套件,可以輕鬆與你的系統、資料儲存(Data Stores)、關聯式資料庫(RDBMS)以及非關聯式資料庫(NoSQL)等等整合。 +### 依賴注入(Dependency Injection) { #dependency-injection } -### 依賴注入(Dependency Injection) - -FastAPI 有一個使用簡單,但是非常強大的依賴注入系統。 +FastAPI 有一個使用簡單,但是非常強大的 依賴注入 系統。 * 依賴項甚至可以有自己的依賴,從而形成一個層級或**依賴圖**的結構。 * 所有**自動化處理**都由框架完成。 @@ -151,23 +145,21 @@ FastAPI 有一個使用簡單,但是非常強大的Pydantic。所以,你有其他 Pydantic 程式碼也能正常工作。 -相容包括基於 Pydantic 的外部函式庫, 例如用於資料庫的 ORMs, ODMs。 +相容包括基於 Pydantic 的外部函式庫,例如用於資料庫的 ORMs、ODMs。 這也意味著在很多情況下,你可以把從請求中獲得的物件**直接傳到資料庫**,因為所有資料都會自動進行驗證。 @@ -196,7 +188,7 @@ FastAPI 有一個使用簡單,但是非常強大的發一則關於 **FastAPI** 的推文,讓我與其他人知道你為什麼喜歡它。🎉 + +我很樂於聽到 **FastAPI** 是如何被使用、你喜歡它的哪些地方、在哪個專案/公司使用它等等。 + +## 為 FastAPI 投票 { #vote-for-fastapi } + +* 在 Slant 為 **FastAPI** 投票。 +* 在 AlternativeTo 為 **FastAPI** 投票。 +* 在 StackShare 表示你使用 **FastAPI**。 + +## 在 GitHub 幫助他人解答問題 { #help-others-with-questions-in-github } + +你可以嘗試幫助他人回答以下地方的問題: + +* GitHub Discussions +* GitHub Issues + +很多時候你可能已經知道這些問題的解答。🤓 + +如果你經常幫大家解決問題,你會成為官方的 [FastAPI 專家](fastapi-people.md#fastapi-experts){.internal-link target=_blank}。🎉 + +請記得,最重要的是:盡量友善。大家可能帶著挫折而來,很多時候提問方式不夠理想,但請盡你所能保持友善。🤗 + +**FastAPI** 社群的理念是友善且歡迎大家。同時,也不要接受霸凌或對他人不尊重的行為。我們要彼此照顧。 + +--- + +以下是在(Discussions 或 Issues)中幫助他人解決問題的方式: + +### 先理解問題 { #understand-the-question } + +* 先確認你是否能理解提問者的**目的**與使用情境。 + +* 接著看看問題(絕大多數是提問)是否**清楚**。 + +* 很多時候,提問是基於使用者自己想像中的解法,但可能有**更好**的方法。如果你能更理解他們的問題與使用情境,你也許能提出更好的**替代解法**。 + +* 如果你無法理解問題,請要求提供更多**細節**。 + +### 重現問題 { #reproduce-the-problem } + +在大多數情況與問題中,都會與對方的**原始程式碼**有關。 + +很多時候他們只會貼出一小段程式碼,但那不足以**重現問題**。 + +* 你可以請他們提供一個最小可重現範例,讓你可以**複製貼上**並在本機執行,看到與他們相同的錯誤或行為,或更好地理解他們的使用情境。 + +* 如果你有心力,你也可以嘗試自己**建立一個範例**,僅依據問題描述來還原。不過請記得這可能很耗時,或許更好的是先請他們把問題說清楚。 + +### 提出解法建議 { #suggest-solutions } + +* 在能夠理解問題後,你可以給出可能的**答案**。 + +* 很多時候,最好能理解他們的**底層問題或使用情境**,因為可能有比他們嘗試的方法更好的解決之道。 + +### 請求關閉議題 { #ask-to-close } + +如果他們回覆了,你很可能已經解決了他們的問題,恭喜,**你是英雄**!🦸 + +* 現在,如果問題已解決,你可以請他們: + * 在 GitHub Discussions:把某則留言標記為**答案**。 + * 在 GitHub Issues:**關閉**該 issue。 + +## 追蹤 GitHub 儲存庫 { #watch-the-github-repository } + +你可以在 GitHub「watch」FastAPI(點擊右上角的「watch」按鈕):https://github.com/fastapi/fastapi。👀 + +如果你選擇「Watching」而不是「Releases only」,當有人建立新的 issue 或問題時你會收到通知。你也可以指定只想被通知新的 issues、discussions、PR 等等。 + +接著你就可以嘗試幫忙解決那些問題。 + +## 提問 { #ask-questions } + +你可以在 GitHub 儲存庫建立一個新的問題(Question),例如用來: + +* 提出**問題**或詢問某個**疑難**。 +* 建議一個新的**功能**。 + +**注意**:如果你這麼做,那我也會請你去幫助其他人。😉 + +## 審核 Pull Request { #review-pull-requests } + +你可以幫我審查他人的 Pull Request。 + +再強調一次,請盡量保持友善。🤗 + +--- + +以下是審查 Pull Request 時需要注意與如何進行: + +### 先理解要解的問題 { #understand-the-problem } + +* 首先,確認你**理解了該 Pull Request 想解決的問題**。可能在 GitHub Discussion 或 issue 中有更長的討論。 + +* 也很有可能這個 Pull Request 其實不需要,因為問題可以用**不同的方法**解決。那你就可以提出或詢問那個方向。 + +### 不用太在意風格 { #dont-worry-about-style } + +* 不要太擔心像是提交訊息(commit message)的風格,我會用 squash and merge 並手動調整提交內容。 + +* 也不用太在意程式碼風格規範,已經有自動化工具在檢查。 + +如果還有其他風格或一致性的需求,我會直接提出請求,或是在上面再補上需要的修改提交。 + +### 檢查程式碼 { #check-the-code } + +* 檢查並閱讀程式碼,看看是否合理,**在本機執行**並確認它是否真的解決了問題。 + +* 然後**留言**說你做了這些,這樣我才知道你真的檢查過了。 + +/// info + +很遺憾,我不能僅因為一個 PR 有好幾個核可就直接信任它。 + +發生過好幾次,PR 有 3、5 個甚至更多核可,可能是因為描述很吸引人,但當我實際檢查時,它其實是壞的、有 bug,或是根本沒有解決它宣稱要解決的問題。😅 + +所以,真的很重要的是你要實際閱讀並執行程式碼,並在留言中讓我知道你做過了。🤓 + +/// + +* 如果 PR 有機會再被簡化,你可以提出要求,但沒必要太過挑剔,很多事情是主觀的(我自己也會有主觀看法 🙈),所以最好聚焦在關鍵的事情上。 + +### 測試 { #tests } + +* 幫我確認 PR 有**測試**。 + +* 檢查在 PR 之前,測試會**失敗**。🚨 + +* 然後檢查在 PR 之後,測試會**通過**。✅ + +* 很多 PR 並沒有測試,你可以**提醒**他們加上測試,或甚至**建議**一些測試。這是最花時間的事之一,而你可以在這方面幫上很大的忙。 + +* 接著也請留言你嘗試了什麼,這樣我就知道你有檢查過。🤓 + +## 建立 Pull Request { #create-a-pull-request } + +你可以透過 Pull Request 來[貢獻](contributing.md){.internal-link target=_blank}原始碼,例如: + +* 修正文檔中你發現的錯字。 +* 分享你建立或發現的 FastAPI 相關文章、影片或 podcast,方法是編輯這個檔案。 + * 請確保把你的連結加到對應章節的開頭。 +* 協助把[文件翻譯](contributing.md#translations){.internal-link target=_blank}成你的語言。 + * 你也可以幫忙審查他人提交的翻譯。 +* 提議新的文件章節。 +* 修復既有的 issue/bug。 + * 記得要加上測試。 +* 新增一個功能。 + * 記得要加上測試。 + * 若相關,請記得補上文件。 + +## 協助維護 FastAPI { #help-maintain-fastapi } + +幫我一起維護 **FastAPI**!🤓 + +有很多事情要做,而其中大多數其實**你**就能完成。 + +你現在就能做的主要任務有: + +* [在 GitHub 幫助他人解答問題](#help-others-with-questions-in-github){.internal-link target=_blank}(見上方章節)。 +* [審核 Pull Request](#review-pull-requests){.internal-link target=_blank}(見上方章節)。 + +這兩件事是**最耗時**的。這也是維護 FastAPI 的主要工作。 + +如果你能在這方面幫我,**你就是在幫我維護 FastAPI**,並確保它能**更快更好地前進**。🚀 + +## 加入聊天室 { #join-the-chat } + +加入 👥 Discord 聊天伺服器 👥,與 FastAPI 社群的其他人一起交流。 + +/// tip + +若要提問,請到 GitHub Discussions,在那裡更有機會獲得[FastAPI 專家](fastapi-people.md#fastapi-experts){.internal-link target=_blank}的協助。 + +聊天室請僅用於其他一般性的交流。 + +/// + +### 不要在聊天室提問 { #dont-use-the-chat-for-questions } + +請記得,由於聊天室允許較「自由的對話」,很容易提出過於籠統、較難回答的問題,因此你可能不會得到答案。 + +在 GitHub 上,模板會引導你寫出合適的提問,讓你更容易得到好的解答,甚至在提問前就自己解決問題。而且在 GitHub 上,我能確保最終都會回覆(即使需要一些時間)。我個人無法在聊天系統做到這一點。😅 + +聊天系統中的對話也不像 GitHub 那樣容易被搜尋,因此問題與答案可能在對話中淹沒。而且只有 GitHub 上的問題與回答才會被計入成為[FastAPI 專家](fastapi-people.md#fastapi-experts){.internal-link target=_blank},因此你在 GitHub 上更有機會獲得關注。 + +另一方面,聊天室裡有成千上萬的使用者,所以幾乎隨時都有很大的機會能找到人聊天。😄 + +## 贊助作者 { #sponsor-the-author } + +如果你的**產品/公司**依賴或與 **FastAPI** 相關,且你想觸及它的使用者,你可以透過 GitHub sponsors 贊助作者(我)。依據不同級別,你可能會得到一些額外福利,例如在文件中顯示徽章。🎁 + +--- + +感謝!🚀 diff --git a/docs/zh-hant/docs/history-design-future.md b/docs/zh-hant/docs/history-design-future.md new file mode 100644 index 000000000..b7f57a363 --- /dev/null +++ b/docs/zh-hant/docs/history-design-future.md @@ -0,0 +1,79 @@ +# 歷史、設計與未來 { #history-design-and-future } + +不久之前,一位 **FastAPI** 使用者提問: + +> 這個專案的歷史是什麼?看起來它在短短幾週內從默默無名變得非常厲害 [...] + +以下是其中一小段歷史。 + +## 替代方案 { #alternatives } + +多年來我一直在打造具有複雜需求的 API(機器學習、分散式系統、非同步工作、NoSQL 資料庫等),並帶領多個開發團隊。 + +在此過程中,我需要調查、測試並使用許多替代方案。 + +**FastAPI** 的歷史,在很大程度上也是其前身工具的歷史。 + +如在[替代方案](alternatives.md){.internal-link target=_blank}章節所述: + +
+ +若沒有他人的前期成果,就不會有 **FastAPI**。 + +先前已有許多工具啟發了它的誕生。 + +我曾經多年避免再去打造一個新框架。起初我嘗試用各種不同的框架、外掛與工具,來滿足 **FastAPI** 涵蓋的所有功能。 + +但在某個時刻,別無選擇,只能打造一個同時提供所有這些功能的東西,取過去工具之長,並以可能的最佳方式加以結合,還運用了以往甚至不存在的語言功能(Python 3.6+ 的型別提示)。 + +
+ +## 調研 { #investigation } + +透過實際使用這些替代方案,我得以向它們學習、汲取想法,並以我能為自己與合作開發團隊找到的最佳方式加以整合。 + +例如,很清楚理想上應以標準的 Python 型別提示為基礎。 + +同時,最佳做法就是採用現有標準。 + +因此,在開始撰寫 **FastAPI** 之前,我花了好幾個月研究 OpenAPI、JSON Schema、OAuth2 等規範,了解它們之間的關係、重疊與差異。 + +## 設計 { #design } + +接著,我花時間設計作為使用者(作為使用 FastAPI 的開發者)時希望擁有的開發者「API」。 + +我在最受歡迎的 Python 編輯器中測試了多個想法:PyCharm、VS Code、基於 Jedi 的編輯器。 + +根據最新的 Python 開發者調查,這些工具涵蓋約 80% 的使用者。 + +這表示 **FastAPI** 已針對 80% 的 Python 開發者所使用的編輯器進行過專門測試。而由於其他多數編輯器的行為也類似,這些優點幾乎在所有編輯器上都能生效。 + +藉此我找到了盡可能減少程式碼重複、在各處提供自動補全、型別與錯誤檢查等的最佳方式。 + +一切都是為了讓所有開發者都能擁有最佳的開發體驗。 + +## 需求 { #requirements } + +在測試多種替代方案後,我決定採用 **Pydantic**,因為它的優勢。 + +隨後我也對它做出貢獻,使其完全符合 JSON Schema、支援以不同方式定義約束,並依據在多款編輯器中的測試結果改進編輯器支援(型別檢查、自動補全)。 + +在開發過程中,我也對 **Starlette**(另一個關鍵依賴)做出貢獻。 + +## 開發 { #development } + +當我開始著手實作 **FastAPI** 本身時,多數拼圖已經就緒,設計已定,需求與工具已備齊,對各項標準與規範的理解也清晰且新鮮。 + +## 未來 { #future } + +到目前為止,**FastAPI** 及其理念已經對許多人有幫助。 + +相較先前的替代方案,它更適合許多使用情境,因而被選用。 + +許多開發者與團隊(包括我和我的團隊)已經在他們的專案中依賴 **FastAPI**。 + +但仍有許多改進與功能即將到來。 + +**FastAPI** 的前景非常光明。 + +也非常感謝[你的幫助](help-fastapi.md){.internal-link target=_blank}。 diff --git a/docs/zh-hant/docs/how-to/authentication-error-status-code.md b/docs/zh-hant/docs/how-to/authentication-error-status-code.md new file mode 100644 index 000000000..9a8de4c91 --- /dev/null +++ b/docs/zh-hant/docs/how-to/authentication-error-status-code.md @@ -0,0 +1,15 @@ +# 使用舊的 403 身分驗證錯誤狀態碼 { #use-old-403-authentication-error-status-codes } + +在 FastAPI 版本 `0.122.0` 之前,當內建的安全工具在身分驗證失敗後回傳錯誤給用戶端時,會使用 HTTP 狀態碼 `403 Forbidden`。 + +從 FastAPI 版本 `0.122.0` 起,改為使用更合適的 HTTP 狀態碼 `401 Unauthorized`,並在回應中依據 HTTP 規範加上合理的 `WWW-Authenticate` 標頭,參考 RFC 7235RFC 9110。 + +但如果你的用戶端因某些原因依賴於舊行為,你可以在你的 security 類別中覆寫 `make_not_authenticated_error` 方法以恢復舊的行為。 + +例如,你可以建立 `HTTPBearer` 的子類別,讓它回傳 `403 Forbidden` 錯誤,而不是預設的 `401 Unauthorized` 錯誤: + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} + +/// tip +注意這個函式回傳的是例外物件本身,而不是直接拋出它。拋出的動作會在其餘的內部程式碼中處理。 +/// diff --git a/docs/zh-hant/docs/how-to/conditional-openapi.md b/docs/zh-hant/docs/how-to/conditional-openapi.md new file mode 100644 index 000000000..5c091e5b7 --- /dev/null +++ b/docs/zh-hant/docs/how-to/conditional-openapi.md @@ -0,0 +1,56 @@ +# 條件式 OpenAPI { #conditional-openapi } + +如果需要,你可以用設定與環境變數,依據執行環境有條件地調整 OpenAPI,甚至完全停用它。 + +## 關於安全性、API 與文件 { #about-security-apis-and-docs } + +在正式環境中隱藏文件 UI *不應該* 是用來保護 API 的方式。 + +這並不會為你的 API 增添任何額外的安全性,*路徑操作* 仍舊照常可用。 + +若你的程式碼有安全性缺陷,它依然會存在。 + +隱藏文件只會讓他人更難理解如何與你的 API 互動,也可能讓你在正式環境除錯更困難。這通常僅被視為一種 以隱匿求安全。 + +如果你想保護 API,有許多更好的作法,例如: + +- 確保針對請求本文與回應,具備定義良好的 Pydantic 模型。 +- 透過依賴設定所需的權限與角色。 +- 切勿儲存明文密碼,只儲存密碼雜湊。 +- 實作並使用成熟且廣為人知的密碼學工具,例如 pwdlib 與 JWT 權杖等。 +- 視需要以 OAuth2 scopes 新增更細緻的權限控管。 +- ...等。 + +儘管如此,在某些特定情境下,你可能確實需要在某些環境(例如正式環境)停用 API 文件,或依據環境變數的設定來決定是否啟用。 + +## 透過設定與環境變數的條件式 OpenAPI { #conditional-openapi-from-settings-and-env-vars } + +你可以用相同的 Pydantic 設定,來配置產生的 OpenAPI 與文件 UI。 + +例如: + +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} + +這裡我們宣告 `openapi_url` 設定,預設值同樣是 `"/openapi.json"`。 + +接著在建立 `FastAPI` 應用時使用它。 + +然後你可以將環境變數 `OPENAPI_URL` 設為空字串,以停用 OpenAPI(包含文件 UI),如下: + +
+ +```console +$ OPENAPI_URL= uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +之後若你造訪 `/openapi.json`、`/docs` 或 `/redoc`,會看到如下的 `404 Not Found` 錯誤: + +```JSON +{ + "detail": "Not Found" +} +``` diff --git a/docs/zh-hant/docs/how-to/configure-swagger-ui.md b/docs/zh-hant/docs/how-to/configure-swagger-ui.md new file mode 100644 index 000000000..e5b6d7d1e --- /dev/null +++ b/docs/zh-hant/docs/how-to/configure-swagger-ui.md @@ -0,0 +1,70 @@ +# 設定 Swagger UI { #configure-swagger-ui } + +你可以設定一些額外的 Swagger UI 參數。 + +要設定它們,建立 `FastAPI()` 應用物件時,或呼叫 `get_swagger_ui_html()` 函式時,傳入參數 `swagger_ui_parameters`。 + +`swagger_ui_parameters` 接受一個 dict,內容會直接傳給 Swagger UI 作為設定。 + +FastAPI 會把這些設定轉換成 JSON,以便與 JavaScript 相容,因為 Swagger UI 需要的是這種格式。 + +## 停用語法醒目提示 { #disable-syntax-highlighting } + +例如,你可以在 Swagger UI 中停用語法醒目提示(syntax highlighting)。 + +不更動設定時,語法醒目提示預設為啟用: + + + +但你可以將 `syntaxHighlight` 設為 `False` 來停用它: + +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} + +...然後 Swagger UI 就不會再顯示語法醒目提示: + + + +## 更換主題 { #change-the-theme } + +同樣地,你可以用鍵 `"syntaxHighlight.theme"` 設定語法醒目提示主題(注意中間有一個點): + +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} + +這個設定會變更語法醒目提示的配色主題: + + + +## 更改預設的 Swagger UI 參數 { #change-default-swagger-ui-parameters } + +FastAPI 內建一些預設參數,適用於大多數情境。 + +包含以下預設設定: + +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} + +你可以在 `swagger_ui_parameters` 參數中提供不同的值來覆蓋其中任一項。 + +例如,要停用 `deepLinking`,可以在 `swagger_ui_parameters` 傳入以下設定: + +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} + +## 其他 Swagger UI 參數 { #other-swagger-ui-parameters } + +若要查看所有可用的設定,請參考官方的 Swagger UI 參數文件。 + +## 僅限 JavaScript 的設定 { #javascript-only-settings } + +Swagger UI 也允許某些設定是僅限 JavaScript 的物件(例如 JavaScript 函式)。 + +FastAPI 也包含以下僅限 JavaScript 的 `presets` 設定: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +這些是 JavaScript 物件,而不是字串,因此無法直接從 Python 程式碼傳遞。 + +若需要使用這類僅限 JavaScript 的設定,你可以使用上面介紹的方法:覆寫所有 Swagger UI 的路徑操作(path operation),並手動撰寫所需的 JavaScript。 diff --git a/docs/zh-hant/docs/how-to/custom-docs-ui-assets.md b/docs/zh-hant/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 000000000..4b5394589 --- /dev/null +++ b/docs/zh-hant/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,185 @@ +# 自訂文件 UI 靜態資源(自我託管) { #custom-docs-ui-static-assets-self-hosting } + +API 文件使用 Swagger UI 與 ReDoc,它們各自需要一些 JavaScript 與 CSS 檔案。 + +預設情況下,這些檔案會從 CDN 提供。 + +但你可以自訂:你可以指定特定的 CDN,或自行提供這些檔案。 + +## 為 JavaScript 和 CSS 使用自訂 CDN { #custom-cdn-for-javascript-and-css } + +假設你想使用不同的 CDN,例如使用 `https://unpkg.com/`。 + +若你所在的國家限制部分網址,這會很有用。 + +### 停用自動產生的文件 { #disable-the-automatic-docs } + +第一步是停用自動文件,因為預設會使用預設的 CDN。 + +要停用它們,建立 `FastAPI` 應用時把相關 URL 設為 `None`: + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} + +### 加入自訂文件 { #include-the-custom-docs } + +現在你可以為自訂文件建立「路徑操作(path operation)」。 + +你可以重用 FastAPI 的內部函式來建立文件的 HTML 頁面,並傳入所需參數: + +* `openapi_url`:文件 HTML 頁面用來取得你 API 的 OpenAPI schema 的 URL。可使用屬性 `app.openapi_url`。 +* `title`:你的 API 標題。 +* `oauth2_redirect_url`:可使用 `app.swagger_ui_oauth2_redirect_url` 以沿用預設值。 +* `swagger_js_url`:Swagger UI 文件 HTML 用來取得「JavaScript」檔案的 URL。這是你的自訂 CDN 位址。 +* `swagger_css_url`:Swagger UI 文件 HTML 用來取得「CSS」檔案的 URL。這是你的自訂 CDN 位址。 + +ReDoc 也類似... + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} + +/// tip + +當你使用 OAuth2 時,`swagger_ui_redirect` 的路徑操作是個輔助端點。 + +如果你把 API 與 OAuth2 提供者整合,便能完成認證並帶著取得的憑證回到 API 文件,接著以真正的 OAuth2 驗證與之互動。 + +Swagger UI 會在背後幫你處理,不過它需要這個「redirect」輔助端點。 + +/// + +### 建立路徑操作以進行測試 { #create-a-path-operation-to-test-it } + +現在,為了測試一切是否正常,建立一個路徑操作: + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} + +### 測試 { #test-it } + +現在你應該能造訪 http://127.0.0.1:8000/docs,重新載入頁面後,資源會從新的 CDN 載入。 + +## 自行託管文件所需的 JavaScript 與 CSS { #self-hosting-javascript-and-css-for-docs } + +若你需要應用在離線、無公共網路或僅在區域網路中也能運作,自行託管 JavaScript 與 CSS 會很實用。 + +以下示範如何在同一個 FastAPI 應用中自行提供這些檔案,並設定文件使用它們。 + +### 專案檔案結構 { #project-file-structure } + +假設你的專案檔案結構如下: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +現在建立一個目錄來存放這些靜態檔案。 + +新的檔案結構可能如下: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### 下載檔案 { #download-the-files } + +下載文件所需的靜態檔案並放到 `static/` 目錄中。 + +你可以在各連結上按右鍵,選擇類似「另存連結為...」的選項。 + +Swagger UI 需要以下檔案: + +* `swagger-ui-bundle.js` +* `swagger-ui.css` + +而 ReDoc 需要以下檔案: + +* `redoc.standalone.js` + +之後,你的檔案結構可能如下: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### 提供靜態檔案 { #serve-the-static-files } + +* 匯入 `StaticFiles`。 +* 在特定路徑「掛載」一個 `StaticFiles()` 實例。 + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} + +### 測試靜態檔案 { #test-the-static-files } + +啟動你的應用並前往 http://127.0.0.1:8000/static/redoc.standalone.js。 + +你應該會看到一個很長的 **ReDoc** JavaScript 檔案。 + +它可能會以如下內容開頭: + +```JavaScript +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): +... +``` + +這表示你的應用已能提供靜態檔案,且文件用的靜態檔已放在正確位置。 + +接著把應用設定為讓文件使用這些靜態檔。 + +### 為靜態檔案停用自動文件 { #disable-the-automatic-docs-for-static-files } + +和使用自訂 CDN 一樣,第一步是停用自動文件,因為預設會使用 CDN。 + +要停用它們,建立 `FastAPI` 應用時把相關 URL 設為 `None`: + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} + +### 加入使用靜態檔案的自訂文件 { #include-the-custom-docs-for-static-files } + +同樣地,現在你可以為自訂文件建立路徑操作。 + +再次重用 FastAPI 的內部函式來建立文件的 HTML 頁面,並傳入所需參數: + +* `openapi_url`:文件 HTML 頁面用來取得你 API 的 OpenAPI schema 的 URL。可使用屬性 `app.openapi_url`。 +* `title`:你的 API 標題。 +* `oauth2_redirect_url`:可使用 `app.swagger_ui_oauth2_redirect_url` 以沿用預設值。 +* `swagger_js_url`:Swagger UI 文件 HTML 用來取得「JavaScript」檔案的 URL。這就是你的應用現在提供的檔案。 +* `swagger_css_url`:Swagger UI 文件 HTML 用來取得「CSS」檔案的 URL。這就是你的應用現在提供的檔案。 + +ReDoc 也類似... + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} + +/// tip + +當你使用 OAuth2 時,`swagger_ui_redirect` 的路徑操作是個輔助端點。 + +如果你把 API 與 OAuth2 提供者整合,便能完成認證並帶著取得的憑證回到 API 文件,接著以真正的 OAuth2 驗證與之互動。 + +Swagger UI 會在背後幫你處理,不過它需要這個「redirect」輔助端點。 + +/// + +### 建立路徑操作以測試靜態檔案 { #create-a-path-operation-to-test-static-files } + +現在,為了測試一切是否正常,建立一個路徑操作: + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} + +### 測試使用靜態檔案的 UI { #test-static-files-ui } + +現在,你應該可以關閉 WiFi,造訪你的文件 http://127.0.0.1:8000/docs,並重新載入頁面。 + +即使沒有網際網路,也能看到你的 API 文件並與之互動。 diff --git a/docs/zh-hant/docs/how-to/custom-request-and-route.md b/docs/zh-hant/docs/how-to/custom-request-and-route.md new file mode 100644 index 000000000..99c3410e4 --- /dev/null +++ b/docs/zh-hant/docs/how-to/custom-request-and-route.md @@ -0,0 +1,109 @@ +# 自訂 Request 與 APIRoute 類別 { #custom-request-and-apiroute-class } + +在某些情況下,你可能想要覆寫 `Request` 與 `APIRoute` 類別所使用的邏輯。 + +特別是,這可能是替代中介軟體(middleware)中實作邏輯的一個好方法。 + +例如,如果你想在應用程式處理之前讀取或操作請求本文(request body)。 + +/// danger + +這是進階功能。 + +如果你剛開始使用 **FastAPI**,可以先跳過本節。 + +/// + +## 使用情境 { #use-cases } + +可能的使用情境包括: + +* 將非 JSON 的請求本文轉換為 JSON(例如 `msgpack`)。 +* 解壓縮以 gzip 壓縮的請求本文。 +* 自動記錄所有請求本文。 + +## 處理自訂請求本文編碼 { #handling-custom-request-body-encodings } + +讓我們看看如何使用自訂的 `Request` 子類別來解壓縮 gzip 請求。 + +並透過 `APIRoute` 子類別來使用該自訂的請求類別。 + +### 建立自訂的 `GzipRequest` 類別 { #create-a-custom-gziprequest-class } + +/// tip + +這是一個示範用的簡化範例;如果你需要 Gzip 支援,可以直接使用提供的 [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}。 + +/// + +首先,我們建立 `GzipRequest` 類別,它會覆寫 `Request.body()` 方法,當存在對應的標頭時解壓縮本文。 + +如果標頭中沒有 `gzip`,它就不會嘗試解壓縮本文。 + +如此一來,相同的路由類別即可同時處理經 gzip 壓縮與未壓縮的請求. + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} + +### 建立自訂的 `GzipRoute` 類別 { #create-a-custom-gziproute-class } + +接著,我們建立 `fastapi.routing.APIRoute` 的自訂子類別,讓它使用 `GzipRequest`。 + +這次,它會覆寫 `APIRoute.get_route_handler()` 方法。 + +這個方法會回傳一個函式,而該函式會接收請求並回傳回應。 + +在這裡,我們用它將原始的請求包裝成 `GzipRequest`。 + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} + +/// note | 技術細節 + +`Request` 具有 `request.scope` 屬性,它其實就是一個 Python 的 `dict`,包含與該請求相關的中繼資料。 + +`Request` 也有 `request.receive`,那是一個用來「接收」請求本文的函式。 + +`scope` 這個 `dict` 與 `receive` 函式都是 ASGI 規格的一部分。 + +而 `scope` 與 `receive` 這兩者,就是建立一個新的 `Request` 實例所需的資料。 + +想了解更多 `Request`,請參考 Starlette 的 Request 文件。 + +/// + +由 `GzipRequest.get_route_handler` 回傳的函式,唯一不同之處在於它會把 `Request` 轉換成 `GzipRequest`。 + +這麼做之後,`GzipRequest` 會在把資料交給 *路徑操作* 之前(若有需要)先負責解壓縮。 + +之後的處理邏輯完全相同。 + +但由於我們修改了 `GzipRequest.body`,在 **FastAPI** 需要讀取本文時,請求本文會自動解壓縮。 + +## 在例外處理器中存取請求本文 { #accessing-the-request-body-in-an-exception-handler } + +/// tip + +要解決相同問題,使用針對 `RequestValidationError` 的自訂處理器來讀取 `body` 通常更簡單([處理錯誤](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank})。 + +但本範例仍然有效,並示範了如何與內部元件互動。 + +/// + +我們也可以用同樣的方法,在例外處理器中存取請求本文。 + +我們只需要在 `try`/`except` 區塊中處理請求即可: + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} + +若發生例外,`Request` 實例依然在作用域內,因此在處理錯誤時我們仍可讀取並使用請求本文: + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} + +## 在路由器中自訂 `APIRoute` 類別 { #custom-apiroute-class-in-a-router } + +你也可以在 `APIRouter` 上設定 `route_class` 參數: + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} + +在此範例中,`router` 底下的路徑操作會使用自訂的 `TimedRoute` 類別,並在回應中多加上一個 `X-Response-Time` 標頭,標示產生該回應所花費的時間: + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} diff --git a/docs/zh-hant/docs/how-to/extending-openapi.md b/docs/zh-hant/docs/how-to/extending-openapi.md new file mode 100644 index 000000000..c51e896f3 --- /dev/null +++ b/docs/zh-hant/docs/how-to/extending-openapi.md @@ -0,0 +1,80 @@ +# 擴充 OpenAPI { #extending-openapi } + +有些情況你可能需要修改自動產生的 OpenAPI 結構(schema)。 + +本章將示範如何做。 + +## 一般流程 { #the-normal-process } + +一般(預設)的流程如下。 + +`FastAPI` 應用程式(實例)有一個 `.openapi()` 方法,會回傳 OpenAPI 結構。 + +在建立應用物件時,會同時註冊一個 `/openapi.json`(或你在 `openapi_url` 設定的路徑)的路徑操作(path operation)。 + +這個路徑只會回傳一個 JSON 回應,內容就是應用的 `.openapi()` 方法結果。 + +預設情況下,`.openapi()` 會先檢查 `.openapi_schema` 屬性是否已有內容,有的話就直接回傳。 + +若沒有,則會呼叫 `fastapi.openapi.utils.get_openapi` 這個工具函式來產生。 + +`get_openapi()` 函式會接收以下參數: + +* `title`:OpenAPI 的標題,會顯示在文件中。 +* `version`:你的 API 版本,例如 `2.5.0`。 +* `openapi_version`:所使用的 OpenAPI 規格版本。預設為最新版本:`3.1.0`。 +* `summary`:API 的簡短摘要。 +* `description`:API 的描述,可包含 Markdown,會顯示在文件中。 +* `routes`:路由列表,也就是所有已註冊的路徑操作。來源為 `app.routes`。 + +/// info + +`summary` 參數在 OpenAPI 3.1.0 以上可用,且需 FastAPI 0.99.0 以上版本支援。 + +/// + +## 覆寫預設行為 { #overriding-the-defaults } + +基於上述資訊,你可以用相同的工具函式來產生 OpenAPI 結構,並覆寫你需要客製的部分。 + +例如,我們要加入 ReDoc 的 OpenAPI 擴充,插入自訂 logo。 + +### 一般的 **FastAPI** { #normal-fastapi } + +先照常撰寫你的 **FastAPI** 應用: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *} + +### 產生 OpenAPI 結構 { #generate-the-openapi-schema } + +接著,在 `custom_openapi()` 函式內,使用相同的工具函式來產生 OpenAPI 結構: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *} + +### 修改 OpenAPI 結構 { #modify-the-openapi-schema } + +現在可以加入 ReDoc 擴充,在 OpenAPI 結構的 `info`「物件」中新增自訂的 `x-logo`: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *} + +### 快取 OpenAPI 結構 { #cache-the-openapi-schema } + +你可以把 `.openapi_schema` 屬性當作「快取」來儲存已產生的結構。 + +這樣使用者每次開啟 API 文件時,應用就不必重複產生結構。 + +結構只會產生一次,之後的請求都會使用相同的快取結果。 + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *} + +### 覆寫方法 { #override-the-method } + +現在你可以用新的函式取代 `.openapi()` 方法。 + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *} + +### 檢查看看 { #check-it } + +造訪 http://127.0.0.1:8000/redoc 後,你會看到自訂的 logo(此例為 **FastAPI** 的 logo): + + diff --git a/docs/zh-hant/docs/how-to/general.md b/docs/zh-hant/docs/how-to/general.md new file mode 100644 index 000000000..96a71d62d --- /dev/null +++ b/docs/zh-hant/docs/how-to/general.md @@ -0,0 +1,39 @@ +# 通用 - 操作指南 - 實用範例 { #general-how-to-recipes } + +以下是文件中其他位置的指引連結,適用於一般或常見問題。 + +## 篩選資料 - 安全性 { #filter-data-security } + +為確保你不會回傳超出應有的資料,請參閱[教學 - 回應模型 - 回傳型別](../tutorial/response-model.md){.internal-link target=_blank}。 + +## 文件標籤 - OpenAPI { #documentation-tags-openapi } + +要在你的*路徑操作(path operation)*加入標籤,並在文件 UI 中分組,請參閱[教學 - 路徑操作設定 - 標籤](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}。 + +## 文件摘要與描述 - OpenAPI { #documentation-summary-and-description-openapi } + +要為你的*路徑操作*加入摘要與描述,並在文件 UI 中顯示,請參閱[教學 - 路徑操作設定 - 摘要與描述](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}。 + +## 文件回應描述 - OpenAPI { #documentation-response-description-openapi } + +要定義在文件 UI 中顯示的回應描述,請參閱[教學 - 路徑操作設定 - 回應描述](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}。 + +## 文件將*路徑操作*標記為已棄用 - OpenAPI { #documentation-deprecate-a-path-operation-openapi } + +要將*路徑操作*標記為已棄用,並在文件 UI 中顯示,請參閱[教學 - 路徑操作設定 - 棄用](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}。 + +## 將任意資料轉換為 JSON 相容格式 { #convert-any-data-to-json-compatible } + +要將任意資料轉換為 JSON 相容格式,請參閱[教學 - JSON 相容編碼器](../tutorial/encoder.md){.internal-link target=_blank}。 + +## OpenAPI 中繼資料 - 文件 { #openapi-metadata-docs } + +要在你的 OpenAPI 綱要中加入中繼資料(包含授權、版本、聯絡方式等),請參閱[教學 - 中繼資料與文件 URL](../tutorial/metadata.md){.internal-link target=_blank}。 + +## 自訂 OpenAPI URL { #openapi-custom-url } + +要自訂(或移除)OpenAPI 的 URL,請參閱[教學 - 中繼資料與文件 URL](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}。 + +## OpenAPI 文件 URL { #openapi-docs-urls } + +要更新自動產生的文件使用者介面所使用的 URL,請參閱[教學 - 中繼資料與文件 URL](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}。 diff --git a/docs/zh-hant/docs/how-to/graphql.md b/docs/zh-hant/docs/how-to/graphql.md new file mode 100644 index 000000000..51933210c --- /dev/null +++ b/docs/zh-hant/docs/how-to/graphql.md @@ -0,0 +1,60 @@ +# GraphQL { #graphql } + +由於 FastAPI 基於 ASGI 標準,整合任何與 ASGI 相容的 GraphQL 函式庫都很容易。 + +你可以在同一個應用程式中同時使用一般的 FastAPI 路徑操作 (path operation) 與 GraphQL。 + +/// tip + +GraphQL 解決某些非常特定的使用情境。 + +與一般的 Web API 相比,它有優點也有缺點。 + +請確認在你的使用情境中,這些效益是否足以彌補其限制。 🤓 + +/// + +## GraphQL 函式庫 { #graphql-libraries } + +下面是支援 ASGI 的部分 GraphQL 函式庫,你可以與 FastAPI 一起使用: + +* Strawberry 🍓 + * 提供 FastAPI 文件 +* Ariadne + * 提供 FastAPI 文件 +* Tartiflette + * 使用 Tartiflette ASGI 提供 ASGI 整合 +* Graphene + * 搭配 starlette-graphene3 + +## 使用 Strawberry 的 GraphQL { #graphql-with-strawberry } + +如果你需要或想使用 GraphQL,Strawberry 是推薦的函式庫,因為它的設計與 FastAPI 最接近,全部都基於型別註解 (type annotations)。 + +視你的使用情境而定,你可能會偏好其他函式庫,但如果你問我,我大概會建議你先試試 Strawberry。 + +以下是如何將 Strawberry 與 FastAPI 整合的一個小例子: + +{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *} + +你可以在 Strawberry 文件 中進一步了解 Strawberry。 + +也可以參考關於 Strawberry 與 FastAPI 的文件。 + +## 來自 Starlette 的較舊 `GraphQLApp` { #older-graphqlapp-from-starlette } + +早期版本的 Starlette 提供 `GraphQLApp` 類別以整合 Graphene。 + +它已在 Starlette 中被棄用,但如果你的程式碼使用了它,可以輕鬆遷移到 starlette-graphene3,涵蓋相同的使用情境,且介面幾乎相同。 + +/// tip + +如果你需要 GraphQL,我仍建議你看看 Strawberry,因為它基於型別註解,而不是自訂的類別與型別。 + +/// + +## 進一步了解 { #learn-more } + +你可以在 官方 GraphQL 文件 中進一步了解 GraphQL。 + +你也可以透過上述連結閱讀各個函式庫的更多內容。 diff --git a/docs/zh-hant/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/zh-hant/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md new file mode 100644 index 000000000..109e57bc8 --- /dev/null +++ b/docs/zh-hant/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -0,0 +1,135 @@ +# 從 Pydantic v1 遷移到 Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 } + +如果你有一個舊的 FastAPI 應用,可能正在使用 Pydantic 1 版。 + +FastAPI 0.100.0 同時支援 Pydantic v1 或 v2,會使用你已安裝的那個版本。 + +FastAPI 0.119.0 透過 Pydantic v2 內的 `pydantic.v1` 提供對 Pydantic v1 的部分支援,以便遷移到 v2。 + +FastAPI 0.126.0 移除了對 Pydantic v1 的支援,但在一段時間內仍支援 `pydantic.v1`。 + +/// warning + +Pydantic 團隊自 **Python 3.14** 起,已停止在最新的 Python 版本中支援 Pydantic v1。 + +這也包含 `pydantic.v1`,在 Python 3.14 及以上版本不再支援。 + +如果你想使用最新的 Python 功能,就需要確保使用 Pydantic v2。 + +/// + +如果你的舊 FastAPI 應用仍使用 Pydantic v1,這裡會示範如何遷移到 Pydantic v2,並介紹 **FastAPI 0.119.0** 中可協助你逐步遷移的功能。 + +## 官方指南 { #official-guide } + +Pydantic 提供從 v1 遷移到 v2 的官方遷移指南。 + +其中包含變更內容、驗證如何更正確且更嚴格、可能的注意事項等。 + +你可以先閱讀以更好理解具體變更。 + +## 測試 { #tests } + +確保你的應用有[測試](../tutorial/testing.md){.internal-link target=_blank},並在 CI(持續整合)上執行。 + +如此一來,你可以升級後確認一切仍如預期運作。 + +## `bump-pydantic` { #bump-pydantic } + +在許多情況下,若你使用的是未自訂的標準 Pydantic 模型,多數遷移步驟都能自動化完成。 + +你可以使用 Pydantic 團隊提供的 `bump-pydantic`。 + +這個工具會自動修改大部分需要變更的程式碼。 + +之後執行測試確認一切正常即可完成。😎 + +## v2 中的 Pydantic v1 { #pydantic-v1-in-v2 } + +Pydantic v2 內含子模組 `pydantic.v1`,提供 Pydantic v1 的所有內容。但在 Python 3.13 以上版本不再支援。 + +這表示你可以安裝最新的 Pydantic v2,並從該子模組匯入並使用舊的 Pydantic v1 元件,就像安裝了舊版 Pydantic v1 一樣。 + +{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} + +### FastAPI 對 v2 中 Pydantic v1 的支援 { #fastapi-support-for-pydantic-v1-in-v2 } + +自 FastAPI 0.119.0 起,也支援透過 Pydantic v2 內的 Pydantic v1(部分)以協助遷移至 v2。 + +因此,你可以先升級到最新的 Pydantic v2,並將匯入改為使用 `pydantic.v1` 子模組,在多數情況下即可正常運作。 + +{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} + +/// warning + +請注意,由於 Pydantic 團隊自 Python 3.14 起不再支援 Pydantic v1,因此在 Python 3.14 及以上版本中也不支援使用 `pydantic.v1`。 + +/// + +### 同一應用同時使用 Pydantic v1 與 v2 { #pydantic-v1-and-v2-on-the-same-app } + +Pydantic 不支援在 Pydantic v2 模型的欄位中使用 Pydantic v1 模型,反之亦然。 + +```mermaid +graph TB + subgraph "❌ Not Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V1Field["Pydantic v1 Model"] + end + subgraph V1["Pydantic v1 Model"] + V2Field["Pydantic v2 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +...但你可以在同一應用中同時存在分開的 Pydantic v1 與 v2 模型。 + +```mermaid +graph TB + subgraph "✅ Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V2Field["Pydantic v2 Model"] + end + subgraph V1["Pydantic v1 Model"] + V1Field["Pydantic v1 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +在某些情況下,你甚至可以在同一個 FastAPI 路徑操作(path operation)中同時使用 Pydantic v1 與 v2 模型: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} + +在上面的範例中,輸入模型是 Pydantic v1,輸出模型(於 `response_model=ItemV2` 定義)是 Pydantic v2。 + +### Pydantic v1 參數 { #pydantic-v1-parameters } + +若你需要在 Pydantic v1 模型上使用 FastAPI 的參數工具(例如 `Body`、`Query`、`Form` 等),在完成遷移到 Pydantic v2 之前,可以從 `fastapi.temp_pydantic_v1_params` 匯入: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} + +### 分步遷移 { #migrate-in-steps } + +/// tip + +先嘗試使用 `bump-pydantic`,如果測試通過且一切正常,你就能用一條指令完成遷移。✨ + +/// + +若 `bump-pydantic` 不適用於你的情境,可以利用在同一應用同時支援 Pydantic v1 與 v2 的能力,逐步完成遷移。 + +你可以先升級 Pydantic 到最新 v2,並將所有模型的匯入改為使用 `pydantic.v1`。 + +接著按群組逐步把模型從 Pydantic v1 遷移到 v2。🚶 diff --git a/docs/zh-hant/docs/how-to/separate-openapi-schemas.md b/docs/zh-hant/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 000000000..2ecb6afbc --- /dev/null +++ b/docs/zh-hant/docs/how-to/separate-openapi-schemas.md @@ -0,0 +1,102 @@ +# 是否將輸入與輸出使用不同的 OpenAPI 結構描述 { #separate-openapi-schemas-for-input-and-output-or-not } + +自從 Pydantic v2 發佈後,生成的 OpenAPI 比以往更精確也更正確。😎 + +實際上,在某些情況下,同一個 Pydantic 模型在 OpenAPI 中會同時有兩個 JSON Schema:分別用於輸入與輸出,這取決於它是否有預設值。 + +來看看它如何運作,以及若需要時該如何調整。 + +## 作為輸入與輸出的 Pydantic 模型 { #pydantic-models-for-input-and-output } + +假設你有一個帶有預設值的 Pydantic 模型,如下所示: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} + +### 輸入用模型 { #model-for-input } + +如果你把這個模型用作輸入,如下所示: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} + +...則 `description` 欄位將不是必填。因為它的預設值是 `None`。 + +### 文件中的輸入模型 { #input-model-in-docs } + +你可以在文件中確認,`description` 欄位沒有紅色星號,表示不是必填: + +
+ +
+ +### 輸出用模型 { #model-for-output } + +但如果你把同一個模型用作輸出,如下所示: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} + +...由於 `description` 有預設值,就算你沒有為該欄位回傳任何內容,它仍會有那個預設值。 + +### 輸出回應資料的模型 { #model-for-output-response-data } + +在互動式文件中試用並檢視回應時,儘管程式碼沒有為其中一個 `description` 欄位加入任何內容,JSON 回應仍包含預設值(`null`): + +
+ +
+ +這代表該欄位一定會有值,只是有時候值可能是 `None`(在 JSON 中為 `null`)。 + +因此,使用你 API 的用戶端不必檢查值是否存在,可以假設該欄位一定存在;只是有些情況下它的值會是預設的 `None`。 + +在 OpenAPI 中,描述這種情況的方式是將該欄位標記為必填,因為它一定存在。 + +因此,同一個模型的 JSON Schema 會依用於輸入或輸出而不同: + +- 用於輸入時,`description` 不是必填 +- 用於輸出時,`description` 是必填(且可能為 `None`,在 JSON 中為 `null`) + +### 文件中的輸出模型 { #model-for-output-in-docs } + +你也可以在文件中檢視輸出模型,`name` 與 `description` 都以紅色星號標示為必填: + +
+ +
+ +### 文件中的輸入與輸出模型 { #model-for-input-and-output-in-docs } + +如果你查看 OpenAPI 中所有可用的結構描述(JSON Schema),會看到有兩個:`Item-Input` 與 `Item-Output`。 + +對於 `Item-Input`,`description` 不是必填,沒有紅色星號。 + +但對於 `Item-Output`,`description` 是必填,有紅色星號。 + +
+ +
+ +有了 Pydantic v2 的這個特性,你的 API 文件會更精確;若你有自動產生的用戶端與 SDK,它們也會更精確,提供更好的開發者體驗與一致性。🎉 + +## 不要分開結構描述 { #do-not-separate-schemas } + +不過,在某些情況下,你可能會希望輸入與輸出使用相同的結構描述。 + +最常見的情境是:你已經有一些自動產生的用戶端程式碼/SDK,目前還不想全部更新;也許之後會做,但不是現在。 + +在這種情況下,你可以在 FastAPI 中透過參數 `separate_input_output_schemas=False` 停用這個功能。 + +/// info + +自 FastAPI `0.102.0` 起新增 `separate_input_output_schemas` 的支援。🤓 + +/// + +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} + +### 文件中輸入與輸出使用相同結構描述的模型 { #same-schema-for-input-and-output-models-in-docs } + +此時輸入與輸出將共用同一個模型結構描述,只有 `Item`,其中 `description` 不是必填: + +
+ +
diff --git a/docs/zh-hant/docs/how-to/testing-database.md b/docs/zh-hant/docs/how-to/testing-database.md new file mode 100644 index 000000000..cbaecc238 --- /dev/null +++ b/docs/zh-hant/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# 測試資料庫 { #testing-a-database } + +你可以在 SQLModel 文件 中學習關於資料庫、SQL 與 SQLModel。 🤓 + +有一個迷你 將 SQLModel 與 FastAPI 搭配使用的教學。 ✨ + +該教學包含一節介紹 測試 SQL 資料庫。 😎 diff --git a/docs/zh-hant/docs/index.md b/docs/zh-hant/docs/index.md index a31647f3c..77eba9b14 100644 --- a/docs/zh-hant/docs/index.md +++ b/docs/zh-hant/docs/index.md @@ -40,7 +40,7 @@ FastAPI 是一個現代、快速(高效能)的 Web 框架,用於以 Python * **快速**:非常高的效能,可與 **NodeJS** 和 **Go** 相當(歸功於 Starlette 和 Pydantic)。[最快的 Python 框架之一](#performance)。 * **極速開發**:開發功能的速度可提升約 200% 至 300%。* * **更少的 Bug**:減少約 40% 的人為(開發者)錯誤。* -* **直覺**:具有出色的編輯器支援,處處都有 自動補全。更少的偵錯時間。 +* **直覺**:具有出色的編輯器支援,處處都有 自動補全。更少的偵錯時間。 * **簡單**:設計上易於使用與學習。更少的讀文件時間。 * **簡潔**:最小化程式碼重複性。每個參數宣告可帶來多個功能。更少的錯誤。 * **穩健**:立即獲得可投入生產的程式碼,並自動生成互動式文件。 @@ -368,7 +368,7 @@ item: Item * 資料驗證: * 當資料無效時,自動且清楚的錯誤。 * 即使是深度巢狀的 JSON 物件也能驗證。 -* 輸入資料的 轉換:從網路讀入到 Python 資料與型別。包含: +* 輸入資料的 轉換:從網路讀入到 Python 資料與型別。包含: * JSON。 * 路徑參數。 * 查詢參數。 @@ -376,7 +376,7 @@ item: Item * 標頭。 * 表單。 * 檔案。 -* 輸出資料的 轉換:從 Python 資料與型別轉換為網路資料(JSON): +* 輸出資料的 轉換:從 Python 資料與型別轉換為網路資料(JSON): * 轉換 Python 型別(`str`、`int`、`float`、`bool`、`list` 等)。 * `datetime` 物件。 * `UUID` 物件。 @@ -439,7 +439,7 @@ item: Item * 來自不同來源的**參數**宣告:例如 **headers**、**cookies**、**form fields** 和 **files**。 * 如何設定**驗證限制**,如 `maximum_length` 或 `regex`。 -* 一個非常強大且易用的 **依賴注入** 系統。 +* 一個非常強大且易用的 **依賴注入** 系統。 * 安全與驗證,包含支援 **OAuth2** 搭配 **JWT tokens** 與 **HTTP Basic** 驗證。 * 宣告**深度巢狀 JSON 模型**的進階(但同樣簡單)技巧(感謝 Pydantic)。 * 與 Strawberry 及其他函式庫的 **GraphQL** 整合。 @@ -524,7 +524,7 @@ Starlette 會使用: * httpx - 若要使用 `TestClient` 必須安裝。 * jinja2 - 若要使用預設的模板設定必須安裝。 -* python-multipart - 若要支援表單 "解析",搭配 `request.form()`。 +* python-multipart - 若要支援表單 "解析",搭配 `request.form()`。 FastAPI 會使用: diff --git a/docs/zh-hant/docs/project-generation.md b/docs/zh-hant/docs/project-generation.md new file mode 100644 index 000000000..7fa92ce55 --- /dev/null +++ b/docs/zh-hant/docs/project-generation.md @@ -0,0 +1,28 @@ +# 全端 FastAPI 範本 { #full-stack-fastapi-template } + +範本通常附帶特定的設定,但設計上具有彈性且可自訂。這讓你可以依專案需求調整與擴充,因此非常適合作為起點。🏁 + +你可以使用此範本快速起步,裡面已替你完成大量初始設定、安全性、資料庫,以及部分 API 端點。 + +GitHub 儲存庫:全端 FastAPI 範本 + +## 全端 FastAPI 範本 - 技術堆疊與功能 { #full-stack-fastapi-template-technology-stack-and-features } + +- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/zh-hant) 作為 Python 後端 API。 + - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) 作為 Python 與 SQL 資料庫互動(ORM)。 + - 🔍 [Pydantic](https://docs.pydantic.dev)(由 FastAPI 使用)用於資料驗證與設定管理。 + - 💾 [PostgreSQL](https://www.postgresql.org) 作為 SQL 資料庫。 +- 🚀 [React](https://react.dev) 作為前端。 + - 💃 使用 TypeScript、hooks、Vite,以及現代前端技術堆疊的其他組件。 + - 🎨 [Tailwind CSS](https://tailwindcss.com) 與 [shadcn/ui](https://ui.shadcn.com) 作為前端元件。 + - 🤖 自動產生的前端用戶端。 + - 🧪 [Playwright](https://playwright.dev) 用於端到端測試。 + - 🦇 支援深色模式。 +- 🐋 [Docker Compose](https://www.docker.com) 用於開發與正式環境。 +- 🔒 預設即採用安全的密碼雜湊。 +- 🔑 JWT(JSON Web Token)驗證。 +- 📫 以 Email 為基礎的密碼重設。 +- ✅ 使用 [Pytest](https://pytest.org) 的測試。 +- 📞 [Traefik](https://traefik.io) 作為反向代理/負載平衡器。 +- 🚢 使用 Docker Compose 的部署指引,包含如何設定前端 Traefik 代理以自動處理 HTTPS 憑證。 +- 🏭 基於 GitHub Actions 的 CI(持續整合)與 CD(持續部署)。 diff --git a/docs/zh-hant/docs/python-types.md b/docs/zh-hant/docs/python-types.md new file mode 100644 index 000000000..4f498ab73 --- /dev/null +++ b/docs/zh-hant/docs/python-types.md @@ -0,0 +1,348 @@ +# Python 型別入門 { #python-types-intro } + +Python 支援可選用的「型別提示」(也稱為「型別註記」)。 + +這些「型別提示」或註記是一種特殊語法,用來宣告變數的型別。 + +為你的變數宣告型別後,編輯器與工具就能提供更好的支援。 + +這裡只是關於 Python 型別提示的快速教學/複習。它只涵蓋使用在 **FastAPI** 時所需的最低限度...其實非常少。 + +**FastAPI** 完全是以這些型別提示為基礎,並因此帶來許多優勢與好處。 + +但就算你從不使用 **FastAPI**,學一點型別提示也會有幫助。 + +/// note | 注意 + +如果你是 Python 專家,而且已經完全了解型別提示,可以直接跳到下一章。 + +/// + +## 動機 { #motivation } + +先從一個簡單的例子開始: + +{* ../../docs_src/python_types/tutorial001_py310.py *} + +執行這個程式會輸出: + +``` +John Doe +``` + +這個函式會做以下事情: + +* 接收 `first_name` 與 `last_name`。 +* 用 `title()` 把每個字的第一個字母轉成大寫。 +* 用一個空白把它們串接起來。 + +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} + +### 編輯它 { #edit-it } + +這是一個非常簡單的程式。 + +但現在想像你正從零開始寫它。 + +在某個時間點你會開始定義函式,參數都準備好了... + +接著你需要呼叫「那個把第一個字母轉大寫的方法」。 + +是 `upper`?還是 `uppercase`?`first_uppercase`?`capitalize`? + +然後你試著用老牌程式設計師的好朋友——編輯器自動完成。 + +你輸入函式的第一個參數 `first_name`,接著打一個點(`.`),然後按下 `Ctrl+Space` 觸發自動完成。 + +但很遺憾,你什麼有用的也沒得到: + + + +### 加上型別 { #add-types } + +我們來修改前一版中的一行。 + +我們只要把函式參數這一段,從: + +```Python + first_name, last_name +``` + +改成: + +```Python + first_name: str, last_name: str +``` + +就這樣。 + +那些就是「型別提示」: + +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} + +這和宣告預設值不同,例如: + +```Python + first_name="john", last_name="doe" +``` + +這是不同的東西。 + +我們使用的是冒號(`:`),不是等號(`=`)。 + +而且加上型別提示通常不會改變執行結果,和不加時是一樣的。 + +不過現在,想像你又在撰寫那個函式,但這次有型別提示。 + +在同樣的地方,你按 `Ctrl+Space` 嘗試自動完成,然後你會看到: + + + +有了這些,你可以往下捲動查看選項,直到找到一個「看起來眼熟」的: + + + +## 更多動機 { #more-motivation } + +看這個函式,它已經有型別提示了: + +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} + +因為編輯器知道變數的型別,你不只會得到自動完成,還會得到錯誤檢查: + + + +現在你知道要修正它,把 `age` 用 `str(age)` 轉成字串: + +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} + +## 宣告型別 { #declaring-types } + +你剛剛看到宣告型別提示的主要位置:函式參數。 + +這也是你在 **FastAPI** 中最常使用它們的地方。 + +### 簡單型別 { #simple-types } + +你可以宣告所有標準的 Python 型別,不只 `str`。 + +例如你可以用: + +* `int` +* `float` +* `bool` +* `bytes` + +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} + +### `typing` 模組 { #typing-module } + +在一些其他情境中,你可能需要從標準程式庫的 `typing` 模組匯入一些東西,比如當你想宣告某個東西可以是「任何型別」時,可以用 `typing` 裡的 `Any`: + +```python +from typing import Any + + +def some_function(data: Any): + print(data) +``` + +### 泛型(Generic types) { #generic-types } + +有些型別可以在方括號中接收「型別參數」,以定義其內部元素的型別,例如「字串的 list」可以宣告為 `list[str]`。 + +這些能接收型別參數的型別稱為「泛型(Generic types)」或「Generics」。 + +你可以將相同的內建型別用作泛型(使用方括號並在裡面放型別): + +* `list` +* `tuple` +* `set` +* `dict` + +#### List { #list } + +例如,讓我們定義一個變數是 `str` 的 `list`。 + +宣告變數,使用相同的冒號(`:`)語法。 + +型別填 `list`。 + +由於 list 是一種包含內部型別的型別,你要把內部型別放在方括號中: + +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} + +/// info | 資訊 + +方括號裡的那些內部型別稱為「型別參數」。 + +在這個例子中,`str` 是傳給 `list` 的型別參數。 + +/// + +這表示:「變數 `items` 是一個 `list`,而這個清單中的每個元素都是 `str`」。 + +這麼做之後,你的編輯器甚至在處理清單裡的項目時也能提供支援: + + + +沒有型別時,幾乎不可能做到這點。 + +請注意,變數 `item` 是清單 `items` 中的一個元素。 + +即便如此,編輯器仍然知道它是 `str`,並提供相應的支援。 + +#### Tuple 與 Set { #tuple-and-set } + +你也可以用相同方式來宣告 `tuple` 與 `set`: + +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} + +這代表: + +* 變數 `items_t` 是一個有 3 個項目的 `tuple`,分別是 `int`、`int` 和 `str`。 +* 變數 `items_s` 是一個 `set`,而其中每個項目都是 `bytes` 型別。 + +#### Dict { #dict } + +定義 `dict` 時,你需要傳入 2 個以逗號分隔的型別參數。 + +第一個型別參數是 `dict` 的鍵(key)。 + +第二個型別參數是 `dict` 的值(value): + +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} + +這代表: + +* 變數 `prices` 是個 `dict`: + * 這個 `dict` 的鍵是 `str`(例如每個項目的名稱)。 + * 這個 `dict` 的值是 `float`(例如每個項目的價格)。 + +#### Union { #union } + +你可以宣告一個變數可以是「多種型別」中的任一種,例如 `int` 或 `str`。 + +要這麼定義,你使用豎線(`|`)來分隔兩種型別。 + +這稱為「union」,因為變數可以是這兩種型別集合的聯集中的任一種。 + +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial008b_py310.py!} +``` + +這表示 `item` 可以是 `int` 或 `str`。 + +#### 可能為 `None` { #possibly-none } + +你可以宣告某個值可以是某個型別(例如 `str`),但它也可能是 `None`。 + +//// tab | Python 3.10+ + +```Python hl_lines="1" +{!> ../../docs_src/python_types/tutorial009_py310.py!} +``` + +//// + +使用 `str | None` 而不是單純的 `str`,可以讓編輯器幫你偵測錯誤,例如你以為某個值一定是 `str`,但它其實也可能是 `None`。 + +### 類別作為型別 { #classes-as-types } + +你也可以用類別來宣告變數的型別。 + +假設你有一個 `Person` 類別,帶有名稱: + +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} + +接著你可以宣告一個變數為 `Person` 型別: + +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} + +然後,你一樣會得到完整的編輯器支援: + + + +請注意,這表示「`one_person` 是類別 `Person` 的『實例(instance)』」。 + +並不是「`one_person` 就是名為 `Person` 的『類別(class)』」。 + +## Pydantic 模型 { #pydantic-models } + +Pydantic 是一個用來做資料驗證的 Python 程式庫。 + +你以帶有屬性的類別來宣告資料的「形狀」。 + +而每個屬性都有其型別。 + +接著你用一些值建立這個類別的實例,它會驗證這些值、在需要時把它們轉換成適當的型別,然後回給你一個包含所有資料的物件。 + +你也會對這個產生的物件得到完整的編輯器支援。 + +以下是來自 Pydantic 官方文件的例子: + +{* ../../docs_src/python_types/tutorial011_py310.py *} + +/// info | 資訊 + +想了解更多 Pydantic,請查看它的文件。 + +/// + +**FastAPI** 完全是以 Pydantic 為基礎。 + +你會在[教學 - 使用者指南](tutorial/index.md){.internal-link target=_blank}中看到更多實際範例。 + +## 含中繼資料的型別提示 { #type-hints-with-metadata-annotations } + +Python 也有一個功能,允許使用 `Annotated` 在這些型別提示中放入額外的中繼資料。 + +你可以從 `typing` 匯入 `Annotated`。 + +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} + +Python 本身不會對這個 `Annotated` 做任何事。對編輯器與其他工具而言,該型別仍然是 `str`。 + +但你可以利用 `Annotated` 這個空間,來提供 **FastAPI** 額外的中繼資料,告訴它你希望應用程式如何運作。 + +重要的是要記住,傳給 `Annotated` 的「第一個型別參數」才是「真正的型別」。其餘的,都是給其他工具用的中繼資料。 + +目前你只需要知道 `Annotated` 的存在,而且它是標準的 Python。😎 + +之後你會看到它有多「強大」。 + +/// tip | 提示 + +因為這是「標準 Python」,所以你在編輯器、分析與重構程式碼的工具等方面,仍然能獲得「最佳的開發體驗」。✨ + +而且你的程式碼也會與許多其他 Python 工具與程式庫非常相容。🚀 + +/// + +## 在 **FastAPI** 中的型別提示 { #type-hints-in-fastapi } + +**FastAPI** 善用這些型別提示來完成多項工作。 + +在 **FastAPI** 中,你用型別提示來宣告參數,然後你會得到: + +* 編輯器支援 +* 型別檢查 + +...而 **FastAPI** 也會用同樣的宣告來: + +* 定義需求:來自請求的路徑參數、查詢參數、標頭、主體(body)、相依性等 +* 轉換資料:把請求中的資料轉成所需型別 +* 驗證資料:來自每個請求的資料: + * 當資料無效時,自動產生錯誤並回傳給用戶端 +* 使用 OpenAPI 書寫 API 文件: + * 之後會由自動的互動式文件介面所使用 + +這些現在聽起來可能有點抽象。別擔心。你會在[教學 - 使用者指南](tutorial/index.md){.internal-link target=_blank}中看到它們的實際運作。 + +重點是,透過在單一位置使用標準的 Python 型別(而不是新增更多類別、裝飾器等),**FastAPI** 會幫你完成很多工作。 + +/// info | 資訊 + +如果你已經完整讀完整個教學,並回來想多看一些關於型別的內容,一個不錯的資源是 `mypy` 的「小抄」。 + +/// diff --git a/docs/zh-hant/docs/translation-banner.md b/docs/zh-hant/docs/translation-banner.md new file mode 100644 index 000000000..58b49965a --- /dev/null +++ b/docs/zh-hant/docs/translation-banner.md @@ -0,0 +1,11 @@ +/// details | 🌐 AI 與人類共同完成的翻譯 + +此翻譯由人類指導的 AI 完成。🤝 + +可能會有對原意的誤解,或讀起來不自然等問題。🤖 + +你可以透過[協助我們更好地引導 AI LLM](https://fastapi.tiangolo.com/zh-hant/contributing/#translations)來改進此翻譯。 + +[英文版](ENGLISH_VERSION_URL) + +/// diff --git a/docs/zh-hant/docs/tutorial/background-tasks.md b/docs/zh-hant/docs/tutorial/background-tasks.md new file mode 100644 index 000000000..63e4e5a16 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/background-tasks.md @@ -0,0 +1,84 @@ +# 背景任務 { #background-tasks } + +你可以定義背景任務,讓它們在傳回回應之後執行。 + +這對於那些需要在請求之後發生、但用戶端其實不必在收到回應前等它完成的操作很有用。 + +例如: + +* 在執行某個動作後發送電子郵件通知: + * 由於連線到郵件伺服器並寄送郵件通常較「慢」(數秒),你可以先立即回應,並在背景中發送郵件通知。 +* 處理資料: + * 例如,收到一個需要經過較慢處理流程的檔案時,你可以先回應「Accepted」(HTTP 202),再在背景處理該檔案。 + +## 使用 `BackgroundTasks` { #using-backgroundtasks } + +首先,匯入 `BackgroundTasks`,並在你的路徑操作函式中定義一個型別為 `BackgroundTasks` 的參數: + +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} + +**FastAPI** 會為你建立 `BackgroundTasks` 物件,並以該參數傳入。 + +## 建立任務函式 { #create-a-task-function } + +建立一個作為背景任務執行的函式。 + +它只是個可接收參數的一般函式。 + +它可以是 `async def`,也可以是一般的 `def`,**FastAPI** 都能正確處理。 + +在此例中,任務函式會寫入檔案(模擬寄送電子郵件)。 + +由於寫入操作未使用 `async` 與 `await`,因此以一般的 `def` 定義該函式: + +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} + +## 新增背景任務 { #add-the-background-task } + +在路徑操作函式內,使用 `.add_task()` 將任務函式加入背景任務物件: + +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} + +`.add_task()` 的引數包括: + +* 要在背景執行的任務函式(`write_notification`)。 +* 依序傳給任務函式的位置引數(`email`)。 +* 要傳給任務函式的關鍵字引數(`message="some notification"`)。 + +## 相依性注入 { #dependency-injection } + +在相依性注入系統中也可使用 `BackgroundTasks`。你可以在多個層級宣告 `BackgroundTasks` 型別的參數:路徑操作函式、相依項(dependable)、次級相依項等。 + +**FastAPI** 會在各種情況下正確處理並重用同一個物件,將所有背景任務合併,並在之後於背景執行: + +{* ../../docs_src/background_tasks/tutorial002_an_py310.py hl[13,15,22,25] *} + +在此範例中,訊息會在回應送出之後寫入 `log.txt` 檔案。 + +如果請求中有查詢參數,會以背景任務寫入日誌。 + +接著,在路徑操作函式中建立的另一個背景任務會使用 `email` 路徑參數寫入訊息。 + +## 技術細節 { #technical-details } + +類別 `BackgroundTasks` 直接來自 `starlette.background`。 + +它被直接匯入/包含到 FastAPI 中,因此你可以從 `fastapi` 匯入它,並避免不小心從 `starlette.background` 匯入另一個同名的 `BackgroundTask`(結尾沒有 s)。 + +只使用 `BackgroundTasks`(而非 `BackgroundTask`)時,你就能把它當作路徑操作函式的參數,並讓 **FastAPI** 幫你處理其餘部分,就像直接使用 `Request` 物件一樣。 + +在 FastAPI 中仍可單獨使用 `BackgroundTask`,但你需要在程式碼中自行建立該物件,並回傳包含它的 Starlette `Response`。 + +更多細節請參閱 Starlette 官方的 Background Tasks 文件。 + +## 注意事項 { #caveat } + +如果你需要執行繁重的背景計算,且不一定要由同一個行程執行(例如不需要共用記憶體、變數等),可以考慮使用更大型的工具,例如 Celery。 + +這類工具通常需要較複雜的設定,以及訊息/工作佇列管理器(如 RabbitMQ 或 Redis),但它們允許你在多個行程,甚至多台伺服器上執行背景任務。 + +但如果你需要存取同一個 **FastAPI** 應用中的變數與物件,或只需執行小型的背景任務(例如寄送郵件通知),僅使用 `BackgroundTasks` 即可。 + +## 重點回顧 { #recap } + +在路徑操作函式與相依項中匯入並使用 `BackgroundTasks` 參數,以新增背景任務。 diff --git a/docs/zh-hant/docs/tutorial/bigger-applications.md b/docs/zh-hant/docs/tutorial/bigger-applications.md new file mode 100644 index 000000000..d8b8c9bff --- /dev/null +++ b/docs/zh-hant/docs/tutorial/bigger-applications.md @@ -0,0 +1,504 @@ +# 更大型的應用程式 - 多個檔案 { #bigger-applications-multiple-files } + +如果你正在建置一個應用程式或 Web API,很少會把所有東西都放在單一檔案裡。 + +FastAPI 提供了一個方便的工具,讓你在維持彈性的同時,幫你組織應用程式的結構。 + +/// info | 資訊 + +如果你來自 Flask,這相當於 Flask 的 Blueprints。 + +/// + +## 範例檔案結構 { #an-example-file-structure } + +假設你有如下的檔案結構: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   ├── dependencies.py +│   └── routers +│   │ ├── __init__.py +│   │ ├── items.py +│   │ └── users.py +│   └── internal +│   ├── __init__.py +│   └── admin.py +``` + +/// tip | 提示 + +有好幾個 `__init__.py` 檔案:每個目錄或子目錄各一個。 + +這讓我們可以把一個檔案中的程式碼匯入到另一個檔案。 + +例如,在 `app/main.py` 你可以有一行: + +``` +from app.routers import items +``` + +/// + +* `app` 目錄包含所有內容。它有一個空的 `app/__init__.py` 檔案,所以它是一個「Python 套件」(「Python 模組」的集合):`app`。 +* 它包含一個 `app/main.py` 檔案。因為它在一個 Python 套件中(有 `__init__.py` 檔案的目錄),它是該套件的一個「模組」:`app.main`。 +* 還有一個 `app/dependencies.py` 檔案,就像 `app/main.py` 一樣,它是一個「模組」:`app.dependencies`。 +* 有一個子目錄 `app/routers/`,裡面有另一個 `__init__.py` 檔案,所以它是一個「Python 子套件」:`app.routers`。 +* 檔案 `app/routers/items.py` 在一個套件 `app/routers/` 內,因此它是一個子模組:`app.routers.items`。 +* 同樣地,`app/routers/users.py` 是另一個子模組:`app.routers.users`。 +* 還有一個子目錄 `app/internal/`,裡面有另一個 `__init__.py` 檔案,所以它又是一個「Python 子套件」:`app.internal`。 +* 檔案 `app/internal/admin.py` 是另一個子模組:`app.internal.admin`。 + + + +同樣的檔案結構,附上註解: + +```bash +. +├── app # 「app」是一個 Python 套件 +│   ├── __init__.py # 這個檔案讓「app」成為「Python 套件」 +│   ├── main.py # 「main」模組,例如 import app.main +│   ├── dependencies.py # 「dependencies」模組,例如 import app.dependencies +│   └── routers # 「routers」是一個「Python 子套件」 +│   │ ├── __init__.py # 讓「routers」成為「Python 子套件」 +│   │ ├── items.py # 「items」子模組,例如 import app.routers.items +│   │ └── users.py # 「users」子模組,例如 import app.routers.users +│   └── internal # 「internal」是一個「Python 子套件」 +│   ├── __init__.py # 讓「internal」成為「Python 子套件」 +│   └── admin.py # 「admin」子模組,例如 import app.internal.admin +``` + +## `APIRouter` { #apirouter } + +假設專門處理使用者的檔案是位於 `/app/routers/users.py` 的子模組。 + +你希望把與使用者相關的「路徑操作 (path operation)」從其他程式碼分離,讓結構更有條理。 + +但它仍然是同一個 FastAPI 應用程式 / Web API 的一部分(屬於同一個「Python 套件」)。 + +你可以使用 `APIRouter` 為該模組建立路徑操作。 + +### 匯入 `APIRouter` { #import-apirouter } + +你可以像對 `FastAPI` 類別那樣匯入並建立一個「實例」: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} + +### 使用 `APIRouter` 宣告路徑操作 { #path-operations-with-apirouter } + +然後用它來宣告你的路徑操作。 + +用法就和 `FastAPI` 類別一樣: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} + +你可以把 `APIRouter` 想成是「迷你版的 `FastAPI`」類別。 + +所有相同的選項都支援。 + +同樣的 `parameters`、`responses`、`dependencies`、`tags` 等全都可用。 + +/// tip | 提示 + +在這個範例中,變數名叫 `router`,但你可以用任何你想用的名稱。 + +/// + +我們稍後會把這個 `APIRouter` 加進主要的 `FastAPI` 應用程式中,但先來看看相依性與另一個 `APIRouter`。 + +## 相依性 { #dependencies } + +我們發現應用程式的多個地方會用到一些相依性。 + +所以把它們放進獨立的 `dependencies` 模組(`app/dependencies.py`)。 + +接下來我們會用一個簡單的相依性來讀取自訂的 `X-Token` 標頭: + +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} + +/// tip | 提示 + +為了簡化範例,我們使用了一個虛構的標頭。 + +但在真實情況下,使用內建的[安全工具](security/index.md){.internal-link target=_blank}會有更好的效果。 + +/// + +## 另一個帶有 `APIRouter` 的模組 { #another-module-with-apirouter } + +假設你還有一個模組 `app/routers/items.py`,專門處理應用程式中的「items」。 + +你有以下路徑操作: + +* `/items/` +* `/items/{item_id}` + +其結構與 `app/routers/users.py` 相同。 + +但我們想要更聰明地簡化一些程式碼。 + +我們知道這個模組中的所有路徑操作都有相同的: + +* 路徑 `prefix`:`/items` +* `tags`:(只有一個標籤:`items`) +* 額外的 `responses` +* `dependencies`:它們都需要我們先前建立的 `X-Token` 相依性 + +因此,我們可以不必把這些都加在每個路徑操作上,而是把它們加在 `APIRouter` 上。 + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} + +由於每個路徑操作的路徑都必須以 `/` 開頭,例如: + +```Python hl_lines="1" +@router.get("/{item_id}") +async def read_item(item_id: str): + ... +``` + +...所以 prefix 末尾不能帶有 `/`。 + +因此,此處的 prefix 是 `/items`。 + +我們也可以加上一個 `tags` 清單,以及會套用在此 router 內所有路徑操作上的額外 `responses`。 + +我們還可以加上一個 `dependencies` 清單,這些相依性會加入此 router 內所有的路徑操作,並在對它們的每個請求上執行 / 解決。 + +/// tip | 提示 + +請注意,就像在[路徑操作裝飾器中的相依性](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}一樣,不會把任何值傳遞給你的路徑操作函式(path operation function)。 + +/// + +最後的結果是這些 item 的路徑如下: + +* `/items/` +* `/items/{item_id}` + +...正如我們預期的。 + +* 它們會被標記為只有一個字串 `"items"` 的標籤清單。 + * 這些「標籤」對自動互動式文件系統(使用 OpenAPI)特別有用。 +* 它們都會包含預先定義的 `responses`。 +* 這些路徑操作都會在執行前評估 / 執行其 `dependencies` 清單。 + * 如果你也在特定的路徑操作中宣告了相依性,這些相依性也會被執行。 + * Router 的相依性會先執行,然後是[裝飾器中的 `dependencies`](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank},最後是一般參數相依性。 + * 你也可以加入帶有 `scopes` 的 [`Security` 相依性](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}。 + +/// tip | 提示 + +在 `APIRouter` 中設定 `dependencies`,例如可以用來對一整組路徑操作要求驗證。即使沒有在每個路徑操作個別加入相依性也沒關係。 + +/// + +/// check | 檢查 + +`prefix`、`tags`、`responses` 與 `dependencies` 參數(就像許多其他情況一樣)只是 FastAPI 提供的功能,幫助你避免重複程式碼。 + +/// + +### 匯入相依性 { #import-the-dependencies } + +這段程式碼在模組 `app.routers.items`(檔案 `app/routers/items.py`)中。 + +我們需要從模組 `app.dependencies`(檔案 `app/dependencies.py`)取得相依性函式。 + +因此我們用 `..` 做相對匯入相依性: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} + +#### 相對匯入如何運作 { #how-relative-imports-work } + +/// tip | 提示 + +如果你對匯入的運作方式十分了解,可以直接跳到下一節。 + +/// + +單一的點號 `.`,如下: + +```Python +from .dependencies import get_token_header +``` + +代表: + +* 從此模組(檔案 `app/routers/items.py`)所在的相同套件(目錄 `app/routers/`)開始... +* 找到模組 `dependencies`(想像的檔案 `app/routers/dependencies.py`)... +* 並從中匯入函式 `get_token_header`。 + +但那個檔案不存在,我們的相依性在 `app/dependencies.py`。 + +回想一下我們的應用 / 檔案結構長這樣: + + + +--- + +兩個點號 `..`,如下: + +```Python +from ..dependencies import get_token_header +``` + +代表: + +* 從此模組(檔案 `app/routers/items.py`)所在的相同套件(目錄 `app/routers/`)開始... +* 前往其父套件(目錄 `app/`)... +* 然後在那裡找到模組 `dependencies`(檔案 `app/dependencies.py`)... +* 並從中匯入函式 `get_token_header`。 + +這就正確了!🎉 + +--- + +同樣地,如果我們用三個點號 `...`,如下: + +```Python +from ...dependencies import get_token_header +``` + +就代表: + +* 從此模組(檔案 `app/routers/items.py`)所在的相同套件(目錄 `app/routers/`)開始... +* 前往其父套件(目錄 `app/`)... +* 再前往那個套件的父層(沒有更上層的套件了,`app` 已是最上層 😱)... +* 然後在那裡找到模組 `dependencies`(檔案 `app/dependencies.py`)... +* 並從中匯入函式 `get_token_header`。 + +那會指向 `app/` 之上的某個套件,該套件需有自己的 `__init__.py` 等等。但我們沒有。所以在這個例子中會丟出錯誤。🚨 + +不過現在你知道它的運作方式了,因此無論你的應用有多複雜,你都可以使用相對匯入。🤓 + +### 加上一些自訂的 `tags`、`responses` 與 `dependencies` { #add-some-custom-tags-responses-and-dependencies } + +我們沒有把 `/items` 的 prefix 以及 `tags=["items"]` 加在每個路徑操作上,因為我們已經把它們加在 `APIRouter` 上了。 + +但我們仍可以在特定的路徑操作上再加上更多的 `tags`,以及一些只屬於該路徑操作的額外 `responses`: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} + +/// tip | 提示 + +這最後一個路徑操作會有組合後的標籤:`["items", "custom"]`。 + +而且在文件中同時會有 `404` 與 `403` 兩種回應。 + +/// + +## 主程式 `FastAPI` { #the-main-fastapi } + +現在,來看看 `app/main.py` 這個模組。 + +你會在這裡匯入並使用 `FastAPI` 類別。 + +這會是你的應用程式中把一切串起來的主檔案。 + +而隨著大多數的邏輯都放在各自的模組中,主檔案會相當簡潔。 + +### 匯入 `FastAPI` { #import-fastapi } + +照常匯入並建立 `FastAPI` 類別。 + +我們甚至可以宣告[全域相依性](dependencies/global-dependencies.md){.internal-link target=_blank},它們會與各 `APIRouter` 的相依性合併: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} + +### 匯入 `APIRouter` { #import-the-apirouter } + +現在我們匯入包含 `APIRouter` 的其他子模組: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} + +由於 `app/routers/users.py` 與 `app/routers/items.py` 是同一個 Python 套件 `app` 的子模組,我們可以用單一的點號 `.` 來進行「相對匯入」。 + +### 匯入如何運作 { #how-the-importing-works } + +這段: + +```Python +from .routers import items, users +``` + +代表: + +* 從此模組(檔案 `app/main.py`)所在的相同套件(目錄 `app/`)開始... +* 尋找子套件 `routers`(目錄 `app/routers/`)... +* 並從中匯入子模組 `items`(檔案 `app/routers/items.py`)與 `users`(檔案 `app/routers/users.py`)... + +模組 `items` 會有一個變數 `router`(`items.router`)。這就是我們在 `app/routers/items.py` 建立的那個 `APIRouter` 物件。 + +接著我們對 `users` 模組做一樣的事。 + +我們也可以這樣匯入: + +```Python +from app.routers import items, users +``` + +/// info | 資訊 + +第一種是「相對匯入」: + +```Python +from .routers import items, users +``` + +第二種是「絕對匯入」: + +```Python +from app.routers import items, users +``` + +想了解更多關於 Python 套件與模組,請閱讀官方的模組說明文件。 + +/// + +### 避免名稱衝突 { #avoid-name-collisions } + +我們直接匯入子模組 `items`,而不是只匯入它的變數 `router`。 + +這是因為在子模組 `users` 中也有另一個名為 `router` 的變數。 + +如果我們像下面這樣一個接一個匯入: + +```Python +from .routers.items import router +from .routers.users import router +``` + +來自 `users` 的 `router` 會覆蓋掉 `items` 的 `router`,我們就無法同時使用兩者。 + +因此,為了能在同一個檔案中同時使用它們,我們直接匯入子模組: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} + +### 將 `users` 與 `items` 的 `APIRouter` 納入 { #include-the-apirouters-for-users-and-items } + +現在,把子模組 `users` 與 `items` 的 `router` 納入: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} + +/// info | 資訊 + +`users.router` 是位於 `app/routers/users.py` 檔案內的 `APIRouter`。 + +而 `items.router` 是位於 `app/routers/items.py` 檔案內的 `APIRouter`。 + +/// + +透過 `app.include_router()`,我們可以把每個 `APIRouter` 加到主要的 `FastAPI` 應用程式。 + +它會把該 router 的所有路由都納入成為應用的一部分。 + +/// note | 技術細節 + +實際上,它會在內部為 `APIRouter` 中宣告的每一個「路徑操作」建立一個對應的「路徑操作」。 + +所以在幕後,它實際運作起來就像是一個單一的應用。 + +/// + +/// check | 檢查 + +把 router 納入時不需要擔心效能。 + +這只會在啟動時花費微秒等級,且只發生一次。 + +因此不會影響效能。⚡ + +/// + +### 以自訂的 `prefix`、`tags`、`responses` 與 `dependencies` 納入一個 `APIRouter` { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies } + +現在,假設你的組織提供了一個 `app/internal/admin.py` 檔案給你。 + +它包含一個帶有一些管理員路徑操作的 `APIRouter`,並在組織內多個專案之間共用。 + +為了這個範例它會非常簡單。但假設因為它會與組織內的其他專案共用,我們不能直接修改它並把 `prefix`、`dependencies`、`tags` 等加在 `APIRouter` 上: + +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} + +但當我們把這個 `APIRouter` 納入時,仍然希望設定自訂的 `prefix`,讓它所有的路徑操作都以 `/admin` 開頭;我們想用這個專案已經有的 `dependencies` 來保護它,並且要加入 `tags` 與 `responses`。 + +我們可以在不修改原始 `APIRouter` 的情況下,將這些參數傳給 `app.include_router()` 來達成: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} + +如此一來,原始的 `APIRouter` 將保持不變,因此我們仍然可以把同一個 `app/internal/admin.py` 檔案與組織中的其他專案共用。 + +結果是在我們的應用中,來自 `admin` 模組的每個路徑操作都會有: + +* 前綴 `/admin` +* 標籤 `admin` +* 相依性 `get_token_header` +* 回應 `418` 🍵 + +但這只會影響我們應用中的那個 `APIRouter`,不會影響任何其他使用它的程式碼。 + +例如,其他專案可以用不同的驗證方式搭配相同的 `APIRouter`。 + +### 加上一個路徑操作 { #include-a-path-operation } + +我們也可以直接把路徑操作加到 `FastAPI` 應用中。 + +這裡我們就加一下... 只是為了示範可以這麼做 🤷: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} + +而且它會和透過 `app.include_router()` 加入的其他路徑操作正確地一起運作。 + +/// info | 非常技術細節 + +注意:這是個非常技術性的細節,你大概可以直接略過。 + +--- + +`APIRouter` 不是被「掛載 (mount)」的,它們不會與應用的其他部分隔離開來。 + +這是因為我們要把它們的路徑操作包含進 OpenAPI 結構與使用者介面中。 + +由於無法將它們隔離並獨立「掛載」,所以這些路徑操作會被「複製」(重新建立),而不是直接包含進來。 + +/// + +## 檢查自動產生的 API 文件 { #check-the-automatic-api-docs } + +現在,執行你的應用: + +
+ +```console +$ fastapi dev app/main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +然後開啟位於 http://127.0.0.1:8000/docs 的文件。 + +你會看到自動產生的 API 文件,包含來自所有子模組的路徑,使用正確的路徑(與前綴)與正確的標籤: + + + +## 以不同的 `prefix` 多次納入同一個 router { #include-the-same-router-multiple-times-with-different-prefix } + +你也可以用不同的前綴,對同一個 router 多次呼叫 `.include_router()`。 + +例如,這對於在不同前綴下提供相同的 API 很有用,如 `/api/v1` 與 `/api/latest`。 + +這是進階用法,你可能不會需要,但若有需要它就在那裡。 + +## 在另一個 `APIRouter` 中納入一個 `APIRouter` { #include-an-apirouter-in-another } + +就像你可以在 `FastAPI` 應用中納入一個 `APIRouter` 一樣,你也可以在另一個 `APIRouter` 中納入一個 `APIRouter`,用法如下: + +```Python +router.include_router(other_router) +``` + +請確保在把 `router` 納入 `FastAPI` 應用之前先這麼做,這樣 `other_router` 的路徑操作也會被包含進去。 diff --git a/docs/zh-hant/docs/tutorial/body-fields.md b/docs/zh-hant/docs/tutorial/body-fields.md new file mode 100644 index 000000000..a16e756b7 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/body-fields.md @@ -0,0 +1,61 @@ +# Body - 欄位 { #body-fields } + +就像你可以在「路徑操作函式 (path operation function)」的參數中用 `Query`、`Path` 和 `Body` 宣告額外的驗證與中繼資料一樣,你也可以在 Pydantic 模型中使用 Pydantic 的 `Field` 來宣告驗證與中繼資料。 + +## 匯入 `Field` { #import-field } + +首先,你需要匯入它: + +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[4] *} + + +/// warning + +請注意,`Field` 是直接從 `pydantic` 匯入的,不像其他(如 `Query`、`Path`、`Body` 等)是從 `fastapi` 匯入。 + +/// + +## 宣告模型屬性 { #declare-model-attributes } + +接著你可以在模型屬性上使用 `Field`: + +{* ../../docs_src/body_fields/tutorial001_an_py310.py hl[11:14] *} + +`Field` 的用法與 `Query`、`Path`、`Body` 相同,擁有相同的參數等。 + +/// note | 技術細節 + +實際上,你接下來會看到的 `Query`、`Path` 等,會建立共同父類別 `Param` 的子類別物件,而 `Param` 本身是 Pydantic 的 `FieldInfo` 類別的子類別。 + +而 Pydantic 的 `Field` 也會回傳一個 `FieldInfo` 的實例。 + +`Body` 也會直接回傳 `FieldInfo` 子類別的物件。稍後你會看到還有其他類別是 `Body` 類別的子類別。 + +記得,當你從 `fastapi` 匯入 `Query`、`Path` 等時,它們其實是回傳特殊類別的函式。 + +/// + +/// tip + +注意,每個帶有型別、預設值與 `Field` 的模型屬性,其結構和「路徑操作函式」的參數相同,只是把 `Path`、`Query`、`Body` 換成了 `Field`。 + +/// + +## 加入額外資訊 { #add-extra-information } + +你可以在 `Field`、`Query`、`Body` 等中宣告額外資訊。這些資訊會被包含在產生的 JSON Schema 中。 + +你會在後續文件中學到更多關於加入額外資訊的內容,特別是在宣告範例時。 + +/// warning + +傳入 `Field` 的額外鍵也會出現在你的應用程式所產生的 OpenAPI schema 中。 +由於這些鍵不一定屬於 OpenAPI 規格的一部分,一些 OpenAPI 工具(例如 [OpenAPI 驗證器](https://validator.swagger.io/))可能無法處理你產生的 schema。 + +/// + +## 回顧 { #recap } + +你可以使用 Pydantic 的 `Field` 為模型屬性宣告額外的驗證與中繼資料。 + +你也可以使用額外的關鍵字引數來傳遞額外的 JSON Schema 中繼資料。 diff --git a/docs/zh-hant/docs/tutorial/body-multiple-params.md b/docs/zh-hant/docs/tutorial/body-multiple-params.md new file mode 100644 index 000000000..1c334f51f --- /dev/null +++ b/docs/zh-hant/docs/tutorial/body-multiple-params.md @@ -0,0 +1,165 @@ +# Body - 多個參數 { #body-multiple-parameters } + +現在我們已經知道如何使用 `Path` 與 `Query`,接下來看看更進階的請求主體(request body)宣告用法。 + +## 混用 `Path`、`Query` 與 Body 參數 { #mix-path-query-and-body-parameters } + +首先,當然你可以自由混用 `Path`、`Query` 與請求 Body 參數的宣告,**FastAPI** 會知道該怎麼做。 + +你也可以將 Body 參數宣告為可選,方法是將預設值設為 `None`: + +{* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} + +/// note | 注意 + +請注意,在此情況下,從 body 取得的 `item` 是可選的,因為它的預設值是 `None`。 + +/// + +## 多個 Body 參數 { #multiple-body-parameters } + +在前一個範例中,路徑操作(path operation)會期望一個包含 `Item` 屬性的 JSON 主體,例如: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +但你也可以宣告多個 Body 參數,例如 `item` 與 `user`: + +{* ../../docs_src/body_multiple_params/tutorial002_py310.py hl[20] *} + +在此情況下,**FastAPI** 會注意到函式中有多個 Body 參數(有兩個參數是 Pydantic 模型)。 + +因此,它會使用參數名稱作為 body 中的鍵(欄位名稱),並期望如下的主體: + +```JSON +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + } +} +``` + +/// note | 注意 + +儘管 `item` 的宣告方式與先前相同,現在預期它會位於 body 內,且鍵為 `item`。 + +/// + +**FastAPI** 會自動從請求中進行轉換,讓參數 `item` 收到對應內容,`user` 亦同。 + +它會對複合資料進行驗證,並在 OpenAPI 結構與自動文件中如此描述。 + +## Body 中的單一值 { #singular-values-in-body } + +就像你可以用 `Query` 與 `Path` 為查詢與路徑參數定義額外資訊一樣,**FastAPI** 也提供對應的 `Body`。 + +例如,延伸前述模型,你可以決定在相同的 Body 中,除了 `item` 與 `user` 外,還要有另一個鍵 `importance`。 + +如果直接這樣宣告,因為它是單一值,**FastAPI** 會將其視為查詢參數。 + +但你可以使用 `Body` 指示 **FastAPI** 將其視為另一個 Body 鍵: + +{* ../../docs_src/body_multiple_params/tutorial003_an_py310.py hl[23] *} + +在此情況下,**FastAPI** 會期望如下的主體: + +```JSON +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + }, + "importance": 5 +} +``` + +同樣地,它會進行型別轉換、驗證、文件化等。 + +## 多個 Body 參數與 Query { #multiple-body-params-and-query } + +當然,你也可以在任何 Body 參數之外,視需要宣告額外的查詢參數。 + +由於預設情況下,單一值會被解讀為查詢參數,你不必明確使用 `Query`,直接這樣寫即可: + +```Python +q: str | None = None +``` + +例如: + +{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} + +/// info | 注意 + +`Body` 也具有與 `Query`、`Path` 以及之後你會看到的其他工具相同的額外驗證與中繼資料參數。 + +/// + +## 嵌入單一 Body 參數 { #embed-a-single-body-parameter } + +假設你只有一個來自 Pydantic 模型 `Item` 的單一 `item` Body 參數。 + +預設情況下,**FastAPI** 會直接期望該模型的內容作為請求主體。 + +但如果你想讓它像宣告多個 Body 參數時那樣,期望一個帶有 `item` 鍵、其內含模型內容的 JSON,你可以使用 `Body` 的特殊參數 `embed`: + +```Python +item: Item = Body(embed=True) +``` + +如下: + +{* ../../docs_src/body_multiple_params/tutorial005_an_py310.py hl[17] *} + +在此情況下 **FastAPI** 會期望如下的主體: + +```JSON hl_lines="2" +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + } +} +``` + +而不是: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +## 小結 { #recap } + +即便一個請求只能有單一主體,你仍可在你的路徑操作函式中宣告多個 Body 參數。 + +但 **FastAPI** 會處理好這一切,在你的函式中提供正確的資料,並在路徑操作中驗證與文件化正確的結構。 + +你也可以將單一值宣告為 Body 的一部分來接收。 + +即使只宣告了一個參數,也可以指示 **FastAPI** 將 Body 以某個鍵進行嵌入。 diff --git a/docs/zh-hant/docs/tutorial/body-nested-models.md b/docs/zh-hant/docs/tutorial/body-nested-models.md new file mode 100644 index 000000000..df4aff697 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/body-nested-models.md @@ -0,0 +1,220 @@ +# Body - 巢狀模型 { #body-nested-models } + +使用 **FastAPI**,你可以定義、驗證、文件化,並使用任意深度的巢狀模型(感謝 Pydantic)。 + +## 列表欄位 { #list-fields } + +你可以將屬性定義為某個子型別。例如,Python 的 `list`: + +{* ../../docs_src/body_nested_models/tutorial001_py310.py hl[12] *} + +這會讓 `tags` 成為一個列表,儘管尚未宣告列表元素的型別。 + +## 具有型別參數的列表欄位 { #list-fields-with-type-parameter } + +不過,Python 有一種專門的方式來宣告具有內部型別(「型別參數」)的列表: + +### 宣告帶有型別參數的 `list` { #declare-a-list-with-a-type-parameter } + +要宣告具有型別參數(內部型別)的型別,例如 `list`、`dict`、`tuple`,使用方括號 `[` 與 `]` 傳入內部型別作為「型別參數」: + +```Python +my_list: list[str] +``` + +以上都是標準的 Python 型別宣告語法。 + +對於具有內部型別的模型屬性,也使用相同的標準語法。 + +因此,在我們的範例中,可以讓 `tags` 明確成為「字串的列表」: + +{* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *} + +## 集合型別 { #set-types } + +但進一步思考後,我們會意識到 `tags` 不應該重覆,應該是唯一的字串。 + +而 Python 有一種用於唯一元素集合的特殊資料型別:`set`。 + +因此我們可以將 `tags` 宣告為字串的 `set`: + +{* ../../docs_src/body_nested_models/tutorial003_py310.py hl[12] *} + +這樣一來,即使收到包含重覆資料的請求,也會被轉換為由唯一元素組成的 `set`。 + +之後只要輸出該資料,即使來源有重覆,也會以唯一元素的 `set` 輸出。 + +並且也會在註解/文件中相應標示。 + +## 巢狀模型 { #nested-models } + +每個 Pydantic 模型的屬性都有型別。 + +而該型別本身也可以是另一個 Pydantic 模型。 + +因此,你可以宣告具有特定屬性名稱、型別與驗證的深度巢狀 JSON「物件」。 + +而且可以任意深度巢狀。 + +### 定義子模型 { #define-a-submodel } + +例如,我們可以定義一個 `Image` 模型: + +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *} + +### 將子模型作為型別使用 { #use-the-submodel-as-a-type } + +然後把它作為某個屬性的型別使用: + +{* ../../docs_src/body_nested_models/tutorial004_py310.py hl[18] *} + +這表示 **FastAPI** 會期望一個類似如下的本文: + +```JSON +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": ["rock", "metal", "bar"], + "image": { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + } +} +``` + +只需進行上述宣告,使用 **FastAPI** 你就能獲得: + +- 編輯器支援(自動完成等),即使是巢狀模型 +- 資料轉換 +- 資料驗證 +- 自動產生文件 + +## 特殊型別與驗證 { #special-types-and-validation } + +除了 `str`、`int`、`float` 等一般的單一型別外,你也可以使用繼承自 `str` 的更複雜單一型別。 + +若要查看所有可用選項,請參閱 Pydantic 的型別總覽。你會在下一章看到一些範例。 + +例如,在 `Image` 模型中有一個 `url` 欄位,我們可以將其宣告為 Pydantic 的 `HttpUrl`,而不是 `str`: + +{* ../../docs_src/body_nested_models/tutorial005_py310.py hl[2,8] *} + +該字串會被檢查是否為有效的 URL,並在 JSON Schema / OpenAPI 中相應註記。 + +## 具有子模型列表的屬性 { #attributes-with-lists-of-submodels } + +你也可以將 Pydantic 模型作為 `list`、`set` 等的子型別使用: + +{* ../../docs_src/body_nested_models/tutorial006_py310.py hl[18] *} + +這會期望(並進行轉換、驗證、文件化等)如下的 JSON 本文: + +```JSON hl_lines="11" +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": [ + "rock", + "metal", + "bar" + ], + "images": [ + { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + }, + { + "url": "http://example.com/dave.jpg", + "name": "The Baz" + } + ] +} +``` + +/// info + +注意 `images` 鍵現在是一個由 image 物件組成的列表。 + +/// + +## 深度巢狀模型 { #deeply-nested-models } + +你可以定義任意深度的巢狀模型: + +{* ../../docs_src/body_nested_models/tutorial007_py310.py hl[7,12,18,21,25] *} + +/// info + +請注意,`Offer` 具有一個 `Item` 的列表,而每個 `Item` 又有一個可選的 `Image` 列表。 + +/// + +## 純列表的本文 { #bodies-of-pure-lists } + +如果你期望的 JSON 本文頂層值是一個 JSON `array`(Python 的 `list`),可以像在 Pydantic 模型中那樣,直接在函式參數上宣告其型別: + +```Python +images: list[Image] +``` + +如下所示: + +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} + +## 隨處可得的編輯器支援 { #editor-support-everywhere } + +你將在各處獲得編輯器支援。 + +即使是列表中的項目也一樣: + + + +若直接操作 `dict` 而不是使用 Pydantic 模型,就無法獲得這種等級的編輯器支援。 + +但你也不必擔心,傳入的 dict 會自動被轉換,而你的輸出也會自動轉換為 JSON。 + +## 任意 `dict` 的本文 { #bodies-of-arbitrary-dicts } + +你也可以將本文宣告為一個 `dict`,其鍵為某種型別、值為另一種型別。 + +如此一來,你無需事先知道有效的欄位/屬性名稱為何(不像使用 Pydantic 模型時需要)。 + +這在你想接收尚未預知的鍵時很有用。 + +--- + +另一個實用情境是當你希望鍵是其他型別(例如,`int`)時。 + +這正是我們在此要示範的。 + +在此情況下,只要是擁有 `int` 鍵且對應 `float` 值的 `dict` 都會被接受: + +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} + +/// tip + +請記住,JSON 只支援 `str` 作為鍵。 + +但 Pydantic 具有自動資料轉換。 + +這表示即使你的 API 用戶端只能以字串作為鍵,只要這些字串是純整數,Pydantic 會自動轉換並驗證它們。 + +而你作為 `weights` 所接收的 `dict`,實際上會擁有 `int` 鍵與 `float` 值。 + +/// + +## 總結 { #recap } + +使用 **FastAPI**,你在保持程式碼簡潔優雅的同時,亦可擁有 Pydantic 模型所提供的高度彈性。 + +同時還具備以下優點: + +- 編輯器支援(到處都有自動完成!) +- 資料轉換(亦即 parsing/serialization) +- 資料驗證 +- 結構描述(Schema)文件 +- 自動產生文件 diff --git a/docs/zh-hant/docs/tutorial/body-updates.md b/docs/zh-hant/docs/tutorial/body-updates.md new file mode 100644 index 000000000..a309e3522 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/body-updates.md @@ -0,0 +1,100 @@ +# Body - 更新 { #body-updates } + +## 使用 `PUT` 取代式更新 { #update-replacing-with-put } + +要更新一個項目,你可以使用 HTTP `PUT` 操作。 + +你可以使用 `jsonable_encoder` 將輸入資料轉換為可儲存為 JSON 的資料(例如用於 NoSQL 資料庫)。例如把 `datetime` 轉成 `str`。 + +{* ../../docs_src/body_updates/tutorial001_py310.py hl[28:33] *} + +`PUT` 用於接收應該取代現有資料的資料。 + +### 關於取代的警告 { #warning-about-replacing } + +這表示,如果你想用 `PUT` 並在 body 中包含以下內容來更新項目 `bar`: + +```Python +{ + "name": "Barz", + "price": 3, + "description": None, +} +``` + +由於這裡沒有包含已儲存的屬性 `"tax": 20.2`,輸入的模型會採用預設值 `"tax": 10.5`。 + +最終資料會以這個「新的」 `tax` 值 `10.5` 被儲存。 + +## 使用 `PATCH` 進行部分更新 { #partial-updates-with-patch } + +你也可以使用 HTTP `PATCH` 操作來進行*部分*更新。 + +這表示你只需傳送想要更新的資料,其餘保持不變。 + +/// note | 注意 + +`PATCH` 相較於 `PUT` 較少被使用、也較不為人知。 + +許多團隊甚至在部分更新時也只用 `PUT`。 + +你可以依需求自由選用,**FastAPI** 不會強制規範。 + +但本指南會大致示範它們各自的設計用法。 + +/// + +### 使用 Pydantic 的 `exclude_unset` 參數 { #using-pydantics-exclude-unset-parameter } + +如果要接收部分更新,在 Pydantic 模型的 `.model_dump()` 中使用 `exclude_unset` 參數非常實用。 + +例如 `item.model_dump(exclude_unset=True)`。 + +這會產生一個只包含建立 `item` 模型時實際設定過之欄位的 `dict`,不含預設值。 + +接著你可以用它來生成只包含實際設定(請求中傳來)的資料之 `dict`,省略預設值: + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *} + +### 使用 Pydantic 的 `update` 參數 { #using-pydantics-update-parameter } + +接著,你可以用 `.model_copy()` 建立現有模型的副本,並傳入含有要更新資料之 `dict` 到 `update` 參數。 + +例如 `stored_item_model.model_copy(update=update_data)`: + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} + +### 部分更新摘要 { #partial-updates-recap } + +總結一下,若要套用部分更新,你可以: + +*(可選)使用 `PATCH` 取代 `PUT`。 +* 取回已儲存的資料。 +* 將該資料放入一個 Pydantic 模型。 +* 從輸入模型產生一個不含預設值的 `dict`(使用 `exclude_unset`)。 + * 如此即可只更新使用者實際設定的值,而不會以模型的預設值覆寫已儲存的值。 +* 建立已儲存模型的副本,並以收到的部分更新值更新其屬性(使用 `update` 參數)。 +* 將該副本模型轉成可儲存到資料庫的型別(例如使用 `jsonable_encoder`)。 + * 這與再次使用模型的 `.model_dump()` 類似,但它會確保(並轉換)所有值為可轉為 JSON 的資料型別,例如把 `datetime` 轉為 `str`。 +* 將資料儲存到資料庫。 +* 回傳更新後的模型。 + +{* ../../docs_src/body_updates/tutorial002_py310.py hl[28:35] *} + +/// tip | 提示 + +其實你也可以在 HTTP `PUT` 操作中使用同一套技巧。 + +但此處示例使用 `PATCH`,因為它正是為這類情境設計的。 + +/// + +/// note | 注意 + +請注意,輸入的模型依然會被驗證。 + +因此,如果你希望接收可以省略所有屬性的部分更新,你需要一個所有屬性皆為可選(具預設值或為 `None`)的模型。 + +為了區分用於更新(全部可選)與用於建立(欄位為必填)的模型,你可以參考 [額外模型](extra-models.md){.internal-link target=_blank} 中的做法。 + +/// diff --git a/docs/zh-hant/docs/tutorial/body.md b/docs/zh-hant/docs/tutorial/body.md new file mode 100644 index 000000000..bddcbbf43 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/body.md @@ -0,0 +1,164 @@ +# 請求本文 { #request-body } + +當你需要從用戶端(例如瀏覽器)將資料傳送到你的 API 時,會把它作為**請求本文**送出。 + +**請求**本文是用戶端傳給你的 API 的資料。**回應**本文是你的 API 傳回給用戶端的資料。 + +你的 API 幾乎總是需要傳回**回應**本文。但用戶端不一定每次都要送出**請求本文**,有時只會請求某個路徑,可能帶一些查詢參數,但不會傳送本文。 + +要宣告**請求**本文,你會使用 Pydantic 模型,享受其完整的功能與優點。 + +/// info + +要傳送資料,應使用下列其中一種方法:`POST`(最常見)、`PUT`、`DELETE` 或 `PATCH`。 + +在規範中,於 `GET` 請求中攜帶本文的行為是未定義的。不過,FastAPI 仍支援它,但僅適用於非常複雜/極端的情境。 + +由於不建議這麼做,使用 Swagger UI 的互動式文件在使用 `GET` 時不會顯示本文的文件,而且中間的代理伺服器也可能不支援。 + +/// + +## 匯入 Pydantic 的 `BaseModel` { #import-pydantics-basemodel } + +首先,從 `pydantic` 匯入 `BaseModel`: + +{* ../../docs_src/body/tutorial001_py310.py hl[2] *} + +## 建立你的資料模型 { #create-your-data-model } + +接著,你將資料模型宣告為繼承自 `BaseModel` 的類別。 + +對所有屬性使用標準的 Python 型別: + +{* ../../docs_src/body/tutorial001_py310.py hl[5:9] *} + +就和宣告查詢參數時一樣,當模型屬性有預設值時,它就不是必填;否則就是必填。使用 `None` 可使其成為選填。 + +例如,上述模型對應的 JSON「`object`」(或 Python `dict`)如下: + +```JSON +{ + "name": "Foo", + "description": "An optional description", + "price": 45.2, + "tax": 3.5 +} +``` + +...由於 `description` 與 `tax` 是選填(預設為 `None`),以下這個 JSON「`object`」也有效: + +```JSON +{ + "name": "Foo", + "price": 45.2 +} +``` + +## 將它宣告為參數 { #declare-it-as-a-parameter } + +要把它加到你的*路徑操作(path operation)*中,宣告方式與路徑與查詢參數相同: + +{* ../../docs_src/body/tutorial001_py310.py hl[16] *} + +...並將其型別宣告為你建立的模型 `Item`。 + +## 效果 { #results } + +只靠這樣的 Python 型別宣告,**FastAPI** 會: + +- 將請求本文讀取為 JSON。 +- (必要時)轉換為對應的型別。 +- 驗證資料。 + - 若資料無效,會回傳清楚易懂的錯誤,指出哪裡、哪筆資料不正確。 +- 把接收到的資料放在參數 `item` 中提供給你。 + - 由於你在函式中將其宣告為 `Item` 型別,你也會獲得完整的編輯器支援(自動完成等)以及所有屬性與其型別。 +- 為你的模型產生 JSON Schema 定義,如有需要,你也可以在專案中的其他地方使用。 +- 這些 schema 會成為產生的 OpenAPI schema 的一部分,並由自動文件 UIs 使用。 + +## 自動文件 { #automatic-docs } + +你的模型的 JSON Schema 會納入產生的 OpenAPI schema,並顯示在互動式 API 文件中: + + + +也會用於每個需要它們的*路徑操作*內的 API 文件: + + + +## 編輯器支援 { #editor-support } + +在編輯器裡、於你的函式中,你會在各處獲得型別提示與自動完成(如果你接收的是 `dict` 而不是 Pydantic 模型,就不會有這些): + + + +你也會獲得對不正確型別操作的錯誤檢查: + + + +這不是偶然,整個框架就是圍繞這個設計而建。 + +而且在實作之前的設計階段就已徹底測試,確保能在各種編輯器中運作良好。 + +甚至為了支援這點,Pydantic 本身也做了些修改。 + +前面的螢幕截圖是使用 Visual Studio Code 拍的。 + +但你在 PyCharm 與大多數其它 Python 編輯器中也會得到相同的編輯器支援: + + + +/// tip + +如果你使用 PyCharm 作為編輯器,可以安裝 Pydantic PyCharm Plugin。 + +它能增強 Pydantic 模型的編輯器支援,包含: + +- 自動完成 +- 型別檢查 +- 重構 +- 搜尋 +- 程式碼檢查 + +/// + +## 使用該模型 { #use-the-model } + +在函式內,你可以直接存取模型物件的所有屬性: + +{* ../../docs_src/body/tutorial002_py310.py *} + +## 請求本文 + 路徑參數 { #request-body-path-parameters } + +你可以同時宣告路徑參數與請求本文。 + +**FastAPI** 會辨識出與路徑參數相符的函式參數應該從**路徑**取得,而宣告為 Pydantic 模型的函式參數應該從**請求本文**取得。 + +{* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} + +## 請求本文 + 路徑 + 查詢參數 { #request-body-path-query-parameters } + +你也可以同時宣告**本文**、**路徑**與**查詢**參數。 + +**FastAPI** 會分別辨識並從正確的位置取得資料。 + +{* ../../docs_src/body/tutorial004_py310.py hl[16] *} + +函式參數的辨識方式如下: + +- 如果參數同時在**路徑**中宣告,則作為路徑參數。 +- 如果參數是**單一型別**(像是 `int`、`float`、`str`、`bool` 等),會被視為**查詢**參數。 +- 如果參數宣告為 **Pydantic 模型** 型別,會被視為請求**本文**。 + +/// note + +FastAPI 會因為預設值 `= None` 而知道 `q` 的值不是必填。 + +`str | None` 並非 FastAPI 用來判斷是否必填的依據;它會因為有預設值 `= None` 而知道不是必填。 + +但加入這些型別註解能讓你的編輯器提供更好的支援與錯誤偵測。 + +/// + +## 不使用 Pydantic { #without-pydantic } + +若你不想使用 Pydantic 模型,也可以使用 **Body** 參數。請參考[Body - 多個參數:本文中的單一值](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}。 diff --git a/docs/zh-hant/docs/tutorial/cookie-param-models.md b/docs/zh-hant/docs/tutorial/cookie-param-models.md new file mode 100644 index 000000000..8997903e3 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/cookie-param-models.md @@ -0,0 +1,76 @@ +# Cookie 參數模型 { #cookie-parameter-models } + +如果你有一組彼此相關的「**Cookie**」,你可以建立一個「**Pydantic 模型**」來宣告它們。🍪 + +這樣你就能在**多處**重複使用該模型,並且能一次性為所有參數宣告**驗證**與**中繼資料**。😎 + +/// note | 注意 + +自 FastAPI 版本 `0.115.0` 起支援。🤓 + +/// + +/// tip + +同樣的技巧也適用於 `Query`、`Cookie` 與 `Header`。😎 + +/// + +## 以 Pydantic 模型宣告 Cookie { #cookies-with-a-pydantic-model } + +在 **Pydantic 模型**中宣告所需的 **Cookie** 參數,接著將參數宣告為 `Cookie`: + +{* ../../docs_src/cookie_param_models/tutorial001_an_py310.py hl[9:12,16] *} + +**FastAPI** 會從請求收到的 **Cookie** 中擷取 **每個欄位** 的資料,並交給你定義的 Pydantic 模型。 + +## 查看文件 { #check-the-docs } + +你可以在 `/docs` 的文件介面中看到已定義的 Cookie: + +
+ +
+ +/// info + +請注意,由於**瀏覽器會以特殊且在背景進行的方式處理 Cookie**,因此**不會**輕易允許 **JavaScript** 存取它們。 + +當你前往位於 `/docs` 的 **API 文件介面**時,可以看到路徑操作的 Cookie 說明。 + +但即使你**填入資料**並點擊「Execute」,因為該文件介面是以 **JavaScript** 運作,Cookie 不會被送出,你會看到**錯誤**訊息,就像完全沒有填任何值一樣。 + +/// + +## 禁止額外的 Cookie { #forbid-extra-cookies } + +在某些特殊情境(可能不太常見)下,你可能會想**限制**允許接收的 Cookie。 + +你的 API 現在也能掌控自己的 Cookie 同意。🤪🍪 + +你可以使用 Pydantic 的模型設定來 `forbid` 任何 `extra` 欄位: + +{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *} + +如果客戶端嘗試送出**額外的 Cookie**,會收到**錯誤**回應。 + +可憐的 Cookie 橫幅辛苦收集你的同意,最後卻是為了讓 API 拒絕它。🍪 + +例如,若客戶端嘗試送出名為 `santa_tracker`、值為 `good-list-please` 的 Cookie,客戶端會收到**錯誤**回應,告知 `santa_tracker` 這個 Cookie 不被允許: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["cookie", "santa_tracker"], + "msg": "Extra inputs are not permitted", + "input": "good-list-please", + } + ] +} +``` + +## 摘要 { #summary } + +你可以在 **FastAPI** 中使用 **Pydantic 模型**來宣告 **Cookie**。😎 diff --git a/docs/zh-hant/docs/tutorial/cookie-params.md b/docs/zh-hant/docs/tutorial/cookie-params.md new file mode 100644 index 000000000..cc9d4b682 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/cookie-params.md @@ -0,0 +1,45 @@ +# Cookie 參數 { #cookie-parameters } + +你可以用與定義 `Query` 與 `Path` 參數相同的方式定義 Cookie 參數。 + +## 匯入 `Cookie` { #import-cookie } + +先匯入 `Cookie`: + +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *} + +## 宣告 `Cookie` 參數 { #declare-cookie-parameters } + +然後用與 `Path`、`Query` 相同的結構宣告 `Cookie` 參數。 + +你可以設定預設值,以及所有額外的驗證或註解參數: + +{* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[9] *} + +/// note | 技術細節 + +`Cookie` 是 `Path` 與 `Query` 的「姊妹」類別。它同樣繼承自共同的 `Param` 類別。 + +但請記住,當你從 `fastapi` 匯入 `Query`、`Path`、`Cookie` 等時,它們實際上是回傳特殊類別的函式。 + +/// + +/// info + +要宣告 cookies,你需要使用 `Cookie`,否則參數會被當作查詢參數(query parameters)來解析。 + +/// + +/// info + +請注意,由於瀏覽器以特殊且在背後處理的方式管理 cookies,它們通常不允許 JavaScript 輕易存取它們。 + +如果你前往位於 `/docs` 的 API 文件介面,你可以在你的路徑操作(path operations)的文件中看到 cookies 的說明。 + +但即使你填入資料並點擊「Execute」,由於該文件介面是以 JavaScript 運作,cookies 不會被送出,你會看到一則錯誤訊息,就好像你沒有填任何值一樣。 + +/// + +## 總結 { #recap } + +使用 `Cookie` 來宣告 cookies,遵循與 `Query`、`Path` 相同的通用寫法。 diff --git a/docs/zh-hant/docs/tutorial/cors.md b/docs/zh-hant/docs/tutorial/cors.md new file mode 100644 index 000000000..5fa497c08 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/cors.md @@ -0,0 +1,88 @@ +# CORS(跨來源資源共用) { #cors-cross-origin-resource-sharing } + +CORS 或「Cross-Origin Resource Sharing」指的是:當在瀏覽器中執行的前端以 JavaScript 與後端通訊,而後端與前端位於不同「來源(origin)」時的情境。 + +## 來源(Origin) { #origin } + +一個來源是由通訊協定(`http`、`https`)、網域(`myapp.com`、`localhost`、`localhost.tiangolo.com`)與連接埠(`80`、`443`、`8080`)三者組合而成。 + +因此,以下皆是不同的來源: + +* `http://localhost` +* `https://localhost` +* `http://localhost:8080` + +即使它們都在 `localhost`,但使用了不同的通訊協定或連接埠,所以它們是不同的「來源」。 + +## 步驟 { #steps } + +假設你的前端在瀏覽器中執行於 `http://localhost:8080`,其 JavaScript 嘗試與執行在 `http://localhost` 的後端通訊(因為未指定連接埠,瀏覽器會假設預設連接埠為 `80`)。 + +接著,瀏覽器會向 `:80` 的後端送出一個 HTTP `OPTIONS` 請求;若後端回傳適當的標頭,授權此不同來源(`http://localhost:8080`)的通訊,則在 `:8080` 的瀏覽器就會允許前端的 JavaScript 向 `:80` 的後端送出它的請求。 + +為了達成這點,`:80` 的後端必須有一份「允許的來源」清單。 + +在此情況下,該清單必須包含 `http://localhost:8080`,` :8080` 的前端才能正確運作。 + +## 萬用字元 { #wildcards } + +也可以將清單宣告為 `"*"`(「wildcard」萬用字元),表示全部都允許。 + +但那只會允許某些類型的通訊,凡是涉及憑證(credentials)的都會被排除:例如 Cookie、Authorization 標頭(像 Bearer Token 會用到的)等。 + +因此,為了讓一切正常運作,最好明確指定被允許的來源。 + +## 使用 `CORSMiddleware` { #use-corsmiddleware } + +你可以在 **FastAPI** 應用程式中使用 `CORSMiddleware` 來設定: + +* 匯入 `CORSMiddleware`。 +* 建立允許的來源清單(字串)。 +* 將它加入到你的 **FastAPI** 應用程式做為「中介軟體(middleware)」。 + +你也可以指定你的後端是否允許: + +* 憑證(credentials,例如 Authorization 標頭、Cookie 等)。 +* 特定的 HTTP 方法(如 `POST`、`PUT`),或使用萬用字元 `"*"` 表示全部。 +* 特定的 HTTP 標頭,或使用萬用字元 `"*"` 表示全部。 + +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} + +`CORSMiddleware` 的實作在預設參數上相當嚴格,因此你需要明確啟用特定的來源、方法或標頭,瀏覽器才會允許在跨網域情境中使用它們。 + +支援以下參數: + +* `allow_origins` - 允許進行跨來源請求的來源清單。例如 `['https://example.org', 'https://www.example.org']`。你可以使用 `['*']` 來允許任何來源。 +* `allow_origin_regex` - 允許進行跨來源請求的來源,使用正規表示式字串比對。例如 `'https://.*\.example\.org'`。 +* `allow_methods` - 允許跨來源請求的 HTTP 方法清單。預設為 `['GET']`。你可以使用 `['*']` 來允許所有標準方法。 +* `allow_headers` - 允許跨來源請求所支援的 HTTP 請求標頭清單。預設為 `[]`。你可以使用 `['*']` 來允許所有標頭。對於簡單 CORS 請求,`Accept`、`Accept-Language`、`Content-Language` 與 `Content-Type` 標頭一律被允許。 +* `allow_credentials` - 指示是否支援跨來源請求的 Cookie。預設為 `False`。 + + 當 `allow_credentials` 設為 `True` 時,`allow_origins`、`allow_methods` 與 `allow_headers` 都不能設為 `['*']`。上述各項必須明確指定。 + +* `expose_headers` - 指示哪些回應標頭應該讓瀏覽器可存取。預設為 `[]`。 +* `max_age` - 設定瀏覽器快取 CORS 回應的最長秒數。預設為 `600`。 + +此中介軟體會回應兩種特定的 HTTP 請求類型... + +### CORS 預檢請求 { #cors-preflight-requests } + +任何帶有 `Origin` 與 `Access-Control-Request-Method` 標頭的 `OPTIONS` 請求。 + +在這種情況下,中介軟體會攔截傳入的請求並回應適當的 CORS 標頭,並回傳 `200` 或 `400`(僅供資訊參考)。 + +### 簡單請求 { #simple-requests } + +任何帶有 `Origin` 標頭的請求。在這種情況下,中介軟體會如常將請求往下傳遞,但會在回應上加入適當的 CORS 標頭。 + +## 更多資訊 { #more-info } + +想進一步了解 CORS,請參考 Mozilla 的 CORS 文件。 + +/// note | 技術細節 + +你也可以使用 `from starlette.middleware.cors import CORSMiddleware`。 + +**FastAPI** 在 `fastapi.middleware` 中提供了幾個中介軟體,做為開發者的便利性。但多數可用的中介軟體其實直接來自 Starlette。 + +/// diff --git a/docs/zh-hant/docs/tutorial/debugging.md b/docs/zh-hant/docs/tutorial/debugging.md new file mode 100644 index 000000000..cb82ee417 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/debugging.md @@ -0,0 +1,113 @@ +# 偵錯 { #debugging } + +你可以在編輯器中連接偵錯器,例如 Visual Studio Code 或 PyCharm。 + +## 呼叫 `uvicorn` { #call-uvicorn } + +在你的 FastAPI 應用程式中,直接匯入並執行 `uvicorn`: + +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} + +### 關於 `__name__ == "__main__"` { #about-name-main } + +`__name__ == "__main__"` 的主要目的是,當你的檔案以以下方式呼叫時,執行某些程式碼: + +
+ +```console +$ python myapp.py +``` + +
+ +但當其他檔案匯入它時不會執行,例如: + +```Python +from myapp import app +``` + +#### 更多細節 { #more-details } + +假設你的檔名是 `myapp.py`。 + +如果你用以下方式執行它: + +
+ +```console +$ python myapp.py +``` + +
+ +那麼在你的檔案中,由 Python 自動建立的內部變數 `__name__`,其值會是字串 `"__main__"`。 + +因此,這段: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +會被執行。 + +--- + +如果你是匯入該模組(檔案),就不會發生這件事。 + +所以,如果你有另一個檔案 `importer.py`,內容如下: + +```Python +from myapp import app + +# Some more code +``` + +在那種情況下,`myapp.py` 中自動建立的變數 `__name__` 就不會是 `"__main__"`。 + +因此,這一行: + +```Python + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +就不會被執行。 + +/// info | 說明 + +想了解更多,參考 Python 官方文件。 + +/// + +## 用偵錯器執行你的程式碼 { #run-your-code-with-your-debugger } + +因為你是直接從程式碼中執行 Uvicorn 伺服器,所以你可以直接從偵錯器呼叫你的 Python 程式(你的 FastAPI 應用程式)。 + +--- + +例如,在 Visual Studio Code 中,你可以: + +* 前往 "Debug" 面板 +* 點選 "Add configuration..." +* 選擇 "Python" +* 使用選項 "`Python: Current File (Integrated Terminal)`" 啟動偵錯器 + +接著它會用你的 **FastAPI** 程式碼啟動伺服器、在你的中斷點停下等。 + +可能會長這樣: + + + +--- + +如果你使用 PyCharm,你可以: + +* 開啟 "Run" 選單 +* 選擇 "Debug..." +* 會出現一個情境選單 +* 選擇要偵錯的檔案(此例為 `main.py`) + +接著它會用你的 **FastAPI** 程式碼啟動伺服器、在你的中斷點停下等。 + +可能會長這樣: + + diff --git a/docs/zh-hant/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/zh-hant/docs/tutorial/dependencies/classes-as-dependencies.md new file mode 100644 index 000000000..34b873210 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/dependencies/classes-as-dependencies.md @@ -0,0 +1,288 @@ +# 以類別作為相依性 { #classes-as-dependencies } + +在更深入了解 **相依性注入(Dependency Injection)** 系統之前,我們先把前一個範例升級一下。 + +## 前一個範例中的 `dict` { #a-dict-from-the-previous-example } + +在前一個範例中,我們從相依項("dependable")回傳了一個 `dict`: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *} + +但接著我們在路徑操作函式(*path operation function*)的參數 `commons` 中取得的是一個 `dict`。 + +而我們知道,編輯器對 `dict` 無法提供太多輔助(例如自動完成),因為它無法預先知道其中的鍵與值的型別。 + +我們可以做得更好... + +## 什麼算是相依性 { #what-makes-a-dependency } + +到目前為止,你看到的相依性都是宣告成函式。 + +但那不是宣告相依性的唯一方式(雖然那大概是最常見的)。 + +關鍵在於,相依性應該要是「callable」。 + +在 Python 中,「**callable**」指的是任何可以像函式一樣被 Python「呼叫」的東西。 + +因此,如果你有一個物件 `something`(它可能不是函式),而你可以像這樣「呼叫」(執行)它: + +```Python +something() +``` + +或是 + +```Python +something(some_argument, some_keyword_argument="foo") +``` + +那它就是一個「callable」。 + +## 以類別作為相依性 { #classes-as-dependencies_1 } + +你可能已經注意到,建立一個 Python 類別的實例時,你用的語法也是一樣的。 + +例如: + +```Python +class Cat: + def __init__(self, name: str): + self.name = name + + +fluffy = Cat(name="Mr Fluffy") +``` + +在這個例子中,`fluffy` 是 `Cat` 類別的一個實例。 + +而要建立 `fluffy`,你其實是在「呼叫」`Cat`。 + +所以,Python 類別本身也是一種 **callable**。 + +因此,在 **FastAPI** 中,你可以將 Python 類別作為相依性。 + +FastAPI 其實檢查的是它是否為「callable」(函式、類別或其他),以及它所定義的參數。 + +如果你在 **FastAPI** 中傳入一個「callable」作為相依性,FastAPI 會分析該「callable」的參數,並以與路徑操作函式參數相同的方式來處理它們,包括子相依性。 + +這也適用於完全沒有參數的 callable,就和沒有參數的路徑操作函式一樣。 + +接著,我們可以把上面的相依項(dependable)`common_parameters` 改成類別 `CommonQueryParams`: + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *} + +注意用來建立該類別實例的 `__init__` 方法: + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *} + +...它的參數與我們之前的 `common_parameters` 相同: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *} + +**FastAPI** 會用這些參數來「解析」該相依性。 + +兩種情況下都會有: + +- 一個可選的查詢參數 `q`,型別為 `str`。 +- 一個查詢參數 `skip`,型別為 `int`,預設為 `0`。 +- 一個查詢參數 `limit`,型別為 `int`,預設為 `100`。 + +兩種情況下,資料都會被轉換、驗證,並記錄到 OpenAPI schema 中等。 + +## 如何使用 { #use-it } + +現在你可以用這個類別來宣告你的相依性。 + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *} + +**FastAPI** 會呼叫 `CommonQueryParams` 類別。這會建立該類別的一個「實例」,而該實例會以參數 `commons` 的形式傳給你的函式。 + +## 型別註解與 `Depends` { #type-annotation-vs-depends } + +注意上面程式碼裡我們寫了兩次 `CommonQueryParams`: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ 非 Annotated + +/// tip + +如有可能,優先使用 `Annotated` 版本。 + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +最後面的 `CommonQueryParams`,在: + +```Python +... Depends(CommonQueryParams) +``` + +...才是 **FastAPI** 實際用來知道相依性是什麼的依據。 + +FastAPI 會從這個物件中提取宣告的參數,並且實際呼叫它。 + +--- + +在這個例子中,前面的 `CommonQueryParams`,於: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, ... +``` + +//// + +//// tab | Python 3.10+ 非 Annotated + +/// tip + +如有可能,優先使用 `Annotated` 版本。 + +/// + +```Python +commons: CommonQueryParams ... +``` + +//// + +...對 **FastAPI** 來說沒有任何特殊意義。FastAPI 不會用它來做資料轉換、驗證等(因為這部分由 `Depends(CommonQueryParams)` 處理)。 + +其實你可以只寫: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[Any, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ 非 Annotated + +/// tip + +如有可能,優先使用 `Annotated` 版本。 + +/// + +```Python +commons = Depends(CommonQueryParams) +``` + +//// + +...像是: + +{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *} + +但仍建議宣告型別,這樣你的編輯器就知道會以何種型別作為參數 `commons` 傳入,進而幫助你做自動完成、型別檢查等: + + + +## 捷徑 { #shortcut } + +不過你會發現這裡有些重複程式碼,我們寫了兩次 `CommonQueryParams`: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ 非 Annotated + +/// tip + +如有可能,優先使用 `Annotated` 版本。 + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +**FastAPI** 為這類情況提供了一個捷徑:當相依性「明確」是一個類別,且 **FastAPI** 會「呼叫」它來建立該類別的實例時。 + +對這些特定情況,你可以這樣做: + +不要寫: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ 非 Annotated + +/// tip + +如有可能,優先使用 `Annotated` 版本。 + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +...而是改為: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends()] +``` + +//// + +//// tab | Python 3.10+ 非 Annotated + +/// tip + +如有可能,優先使用 `Annotated` 版本。 + +/// + +```Python +commons: CommonQueryParams = Depends() +``` + +//// + +你把相依性宣告為參數的型別,並使用不帶任何參數的 `Depends()`,而不用在 `Depends(CommonQueryParams)` 裡「再」寫一次整個類別。 + +整個範例就會變成: + +{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *} + +...而 **FastAPI** 會知道該怎麼做。 + +/// tip + +如果你覺得這樣比幫助更令人困惑,那就忽略它吧,你並不「需要」這個技巧。 + +這只是個捷徑。因為 **FastAPI** 在意幫你減少重複的程式碼。 + +/// diff --git a/docs/zh-hant/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/zh-hant/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md new file mode 100644 index 000000000..e30c38537 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -0,0 +1,69 @@ +# 路徑操作裝飾器中的依賴 { #dependencies-in-path-operation-decorators } + +有時在你的路徑操作函式中,其實不需要某個依賴的回傳值。 + +或是該依賴根本沒有回傳值。 + +但你仍需要它被執行/解析。 + +這種情況下,你可以不在路徑操作函式的參數上使用 `Depends`,而是在路徑操作裝飾器加入一個 `dependencies` 的 `list`。 + +## 在路徑操作裝飾器加入 `dependencies` { #add-dependencies-to-the-path-operation-decorator } + +路徑操作裝飾器可接受一個可選參數 `dependencies`。 + +它應該是由 `Depends()` 組成的 `list`: + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} + +這些依賴會以與一般依賴相同的方式被執行/解析。但它們的值(如果有回傳)不會傳遞給你的路徑操作函式。 + +/// tip + +有些編輯器會檢查未使用的函式參數,並將其標示為錯誤。 + +把這些依賴放在路徑操作裝飾器中,可以確保它們被執行,同時避免編輯器/工具報錯。 + +這也有助於避免讓新加入的開發者看到未使用的參數時,以為它是不必要的而感到困惑。 + +/// + +/// info + +在這個範例中我們使用了自訂的(虛構的)標頭 `X-Key` 與 `X-Token`。 + +但在實際情況下,當你實作安全機制時,使用整合的 [Security utilities(下一章)](../security/index.md){.internal-link target=_blank} 會獲得更多好處。 + +/// + +## 依賴的錯誤與回傳值 { #dependencies-errors-and-return-values } + +你可以使用與平常相同的依賴函式。 + +### 依賴的需求 { #dependency-requirements } + +它們可以宣告請求需求(例如標頭(headers))或其他子依賴: + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} + +### 拋出例外 { #raise-exceptions } + +這些依賴可以 `raise` 例外,與一般依賴相同: + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} + +### 回傳值 { #return-values } + +它們可以回傳值,也可以不回傳;無論如何,回傳值都不會被使用。 + +因此,你可以重複使用在其他地方已使用過的一般依賴(會回傳值),即使回傳值不會被使用,該依賴仍會被執行: + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} + +## 一組路徑操作的依賴 { #dependencies-for-a-group-of-path-operations } + +之後在閱讀如何組織較大的應用程式([較大型應用程式——多個檔案](../../tutorial/bigger-applications.md){.internal-link target=_blank})時,你會學到如何為一組路徑操作宣告一個共同的 `dependencies` 參數。 + +## 全域依賴 { #global-dependencies } + +接著我們會看看如何把依賴加到整個 `FastAPI` 應用程式,使其套用到每個路徑操作。 diff --git a/docs/zh-hant/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/zh-hant/docs/tutorial/dependencies/dependencies-with-yield.md new file mode 100644 index 000000000..2339c6ef3 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/dependencies/dependencies-with-yield.md @@ -0,0 +1,288 @@ +# 使用 yield 的相依 { #dependencies-with-yield } + +FastAPI 支援在完成後執行一些額外步驟的相依。 + +要做到這點,使用 `yield` 取代 `return`,並把額外步驟(程式碼)寫在其後。 + +/// tip + +請確保每個相依內只使用一次 `yield`。 + +/// + +/// note | 技術細節 + +任何可用於下列裝飾器的函式: + +* `@contextlib.contextmanager` 或 +* `@contextlib.asynccontextmanager` + +都可以作為 **FastAPI** 的相依。 + +事實上,FastAPI 內部就是使用這兩個裝飾器。 + +/// + +## 使用 `yield` 的資料庫相依 { #a-database-dependency-with-yield } + +例如,你可以用它建立一個資料庫 session,並在完成後關閉。 + +只有 `yield` 之前(含 `yield` 本身)的程式碼會在產生回應之前執行: + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[2:4] *} + +由 `yield` 產生的值會被注入到路徑操作(path operation)與其他相依中: + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[4] *} + +位於 `yield` 之後的程式碼會在回應之後執行: + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[5:6] *} + +/// tip + +你可以使用 `async` 或一般函式。 + +**FastAPI** 都會正確處理,和一般相依相同。 + +/// + +## 同時使用 `yield` 與 `try` 的相依 { #a-dependency-with-yield-and-try } + +如果在含 `yield` 的相依中使用 `try` 區塊,你會接收到使用該相依時拋出的任何例外。 + +例如,如果在中途的某段程式碼、其他相依,或某個路徑操作中,讓資料庫交易「rollback」或產生了任何例外,你都會在你的相依中接收到該例外。 + +因此,你可以在相依內用 `except SomeException` 來攔截特定例外。 + +同樣地,你可以使用 `finally` 來確保無論是否有例外都會執行結束步驟。 + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[3,5] *} + +## 含 `yield` 的子相依 { #sub-dependencies-with-yield } + +你可以擁有任何大小與形狀的子相依與相依樹,而它們都可以(或不)使用 `yield`。 + +**FastAPI** 會確保每個使用 `yield` 的相依,其「結束程式碼」會以正確的順序執行。 + +例如,`dependency_c` 可以相依於 `dependency_b`,而 `dependency_b` 相依於 `dependency_a`: + +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[6,14,22] *} + +而且它們都可以使用 `yield`。 + +在這個例子中,`dependency_c` 為了執行它的結束程式碼,需要來自 `dependency_b`(此處命名為 `dep_b`)的值仍然可用。 + +同理,`dependency_b` 為了執行它的結束程式碼,需要來自 `dependency_a`(此處命名為 `dep_a`)的值可用。 + +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[18:19,26:27] *} + +同樣地,你可以同時擁有使用 `yield` 的相依與使用 `return` 的相依,並讓其中一些相依彼此相依。 + +你也可以有一個相依同時需要多個使用 `yield` 的其他相依,等等。 + +你可以擁有任何你需要的相依組合。 + +**FastAPI** 會確保一切都以正確的順序執行。 + +/// note | 技術細節 + +這能運作,多虧了 Python 的 Context Managers。 + +**FastAPI** 在內部使用它們來達成這點。 + +/// + +## 含 `yield` 與 `HTTPException` 的相依 { #dependencies-with-yield-and-httpexception } + +你已看到可以在含 `yield` 的相依中使用 `try` 區塊,嘗試執行一些程式碼,然後在 `finally` 後執行結束程式碼。 + +你也可以用 `except` 來攔截被拋出的例外並加以處理。 + +例如,你可以拋出不同的例外,如 `HTTPException`。 + +/// tip + +這算是進階技巧;多數情況你並不需要,因為你可以在應用程式其他程式碼中(例如在路徑操作函式(path operation function)中)直接拋出例外(包含 `HTTPException`)。 + +但如果你需要,它就在這裡。🤓 + +/// + +{* ../../docs_src/dependencies/tutorial008b_an_py310.py hl[18:22,31] *} + +如果你想攔截例外並據此回傳自訂回應,請建立一個[自訂例外處理器](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}。 + +## 含 `yield` 與 `except` 的相依 { #dependencies-with-yield-and-except } + +如果你在含 `yield` 的相依中用 `except` 攔截了例外,且沒有再次拋出它(或拋出新的例外),FastAPI 將無法察覺有例外發生,就像在一般的 Python 中一樣: + +{* ../../docs_src/dependencies/tutorial008c_an_py310.py hl[15:16] *} + +在這種情況下,客戶端會如預期地看到一個 *HTTP 500 Internal Server Error* 回應(因為我們沒有拋出 `HTTPException` 或類似的東西),但伺服器將不會有任何日誌或其他錯誤線索。😱 + +### 在含 `yield` 與 `except` 的相依中務必 `raise` { #always-raise-in-dependencies-with-yield-and-except } + +如果你在含 `yield` 的相依中攔截到了例外,除非你要拋出另一個 `HTTPException` 或類似的例外,否則**你應該重新拋出原本的例外**。 + +你可以使用 `raise` 重新拋出同一個例外: + +{* ../../docs_src/dependencies/tutorial008d_an_py310.py hl[17] *} + +現在客戶端仍會獲得同樣的 *HTTP 500 Internal Server Error* 回應,但伺服器的日誌中會有我們自訂的 `InternalError`。😎 + +## 含 `yield` 的相依執行順序 { #execution-of-dependencies-with-yield } + +執行順序大致如下圖。時間從上往下流動,每一欄代表一個互動或執行程式碼的部分。 + +```mermaid +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,operation: Can raise exceptions, including HTTPException + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise Exception + dep -->> handler: Raise Exception + handler -->> client: HTTP error response + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise Exception (e.g. HTTPException) + opt handle + dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception + end + handler -->> client: HTTP error response + end + + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> tasks: Handle exceptions in the background task code + end +``` + +/// info + +只會向用戶端送出「一個回應」。可能是其中一個錯誤回應,或是來自該路徑操作的回應。 + +一旦送出了其中一個回應,就不能再送出其他回應。 + +/// + +/// tip + +如果你在路徑操作函式的程式碼中拋出任何例外,它會被傳遞到使用 `yield` 的相依中(包含 `HTTPException`)。大多數情況你會想在該使用 `yield` 的相依中重新拋出相同的例外或一個新的例外,以確保它被正確處理。 + +/// + +## 提早關閉與 `scope` { #early-exit-and-scope } + +通常,含 `yield` 的相依之結束程式碼會在回應送出給用戶端之後才執行。 + +但如果你確定在從路徑操作函式返回後就不會再使用該相依,你可以使用 `Depends(scope="function")`,告訴 FastAPI 應在路徑操作函式返回之後、但在回應送出之前關閉該相依。 + +{* ../../docs_src/dependencies/tutorial008e_an_py310.py hl[12,16] *} + +`Depends()` 接受一個 `scope` 參數,可以是: + +* `"function"`:在處理請求的路徑操作函式之前啟動相依,在路徑操作函式結束之後結束相依,但在回應送回用戶端之前。所以,相依函式會在路徑操作**函式**的「周圍」執行。 +* `"request"`:在處理請求的路徑操作函式之前啟動相依(與使用 `"function"` 類似),但在回應送回用戶端之後才結束相依。所以,相依函式會在整個**請求**與回應循環的「周圍」執行。 + +如果未指定且相依使用了 `yield`,則預設 `scope` 為 `"request"`。 + +### 子相依的 `scope` { #scope-for-sub-dependencies } + +當你宣告一個 `scope="request"`(預設值)的相依時,任何子相依也需要有 `"request"` 的 `scope`。 + +但一個 `scope` 為 `"function"` 的相依,可以擁有 `scope` 為 `"function"` 或 `"request"` 的子相依。 + +這是因為任何相依都需要能在子相依之前執行其結束程式碼,因為它可能在結束程式碼中仍需要使用那些子相依。 + +```mermaid +sequenceDiagram + +participant client as Client +participant dep_req as Dep scope="request" +participant dep_func as Dep scope="function" +participant operation as Path Operation + + client ->> dep_req: Start request + Note over dep_req: Run code up to yield + dep_req ->> dep_func: Pass dependency + Note over dep_func: Run code up to yield + dep_func ->> operation: Run path operation with dependency + operation ->> dep_func: Return from path operation + Note over dep_func: Run code after yield + Note over dep_func: ✅ Dependency closed + dep_func ->> client: Send response to client + Note over client: Response sent + Note over dep_req: Run code after yield + Note over dep_req: ✅ Dependency closed +``` + +## 含 `yield`、`HTTPException`、`except` 與背景任務的相依 { #dependencies-with-yield-httpexception-except-and-background-tasks } + +含 `yield` 的相依隨時間演進,以涵蓋不同的使用情境並修正一些問題。 + +如果你想了解在不同 FastAPI 版本中改了哪些內容,可以在進階指南中閱讀:[進階相依 — 含 `yield`、`HTTPException`、`except` 與背景任務的相依](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}。 +## 情境管理器 { #context-managers } + +### 什麼是「情境管理器」 { #what-are-context-managers } + +「情境管理器」是那些你可以在 `with` 陳述式中使用的 Python 物件。 + +例如,你可以用 `with` 來讀取檔案: + +```Python +with open("./somefile.txt") as f: + contents = f.read() + print(contents) +``` + +在底層,`open("./somefile.txt")` 會建立一個稱為「情境管理器」的物件。 + +當 `with` 區塊結束時,它會確保關閉檔案,即使發生了例外也一樣。 + +當你建立一個含 `yield` 的相依時,**FastAPI** 會在內部為它建立一個情境管理器,並與其他相關工具結合。 + +### 在含 `yield` 的相依中使用情境管理器 { #using-context-managers-in-dependencies-with-yield } + +/// warning + +這大致算是一個「進階」概念。 + +如果你剛開始學習 **FastAPI**,此處可以先跳過。 + +/// + +在 Python 中,你可以透過建立一個擁有 `__enter__()` 與 `__exit__()` 兩個方法的類別來建立情境管理器。 + +你也可以在 **FastAPI** 的含 `yield` 相依中,於相依函式內使用 `with` 或 `async with` 陳述式來使用它們: + +{* ../../docs_src/dependencies/tutorial010_py310.py hl[1:9,13] *} + +/// tip + +建立情境管理器的另一種方式是: + +* `@contextlib.contextmanager` 或 +* `@contextlib.asynccontextmanager` + +用它們裝飾一個只包含單一 `yield` 的函式。 + +這正是 **FastAPI** 在內部為含 `yield` 的相依所使用的方法。 + +但你不需要(而且也不該)在 FastAPI 的相依上使用這些裝飾器。 + +FastAPI 會在內部替你處理好。 + +/// diff --git a/docs/zh-hant/docs/tutorial/dependencies/global-dependencies.md b/docs/zh-hant/docs/tutorial/dependencies/global-dependencies.md new file mode 100644 index 000000000..3aac1a228 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/dependencies/global-dependencies.md @@ -0,0 +1,15 @@ +# 全域依賴 { #global-dependencies } + +在某些類型的應用程式中,你可能想為整個應用程式新增依賴。 + +類似於你可以在[路徑操作(path operation)的裝飾器中新增 `dependencies`](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 的方式,你也可以把它們加到 `FastAPI` 應用程式上。 + +在這種情況下,它們會套用到應用程式中的所有路徑操作: + +{* ../../docs_src/dependencies/tutorial012_an_py310.py hl[17] *} + +而且,在[將 `dependencies` 新增到路徑操作裝飾器](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} 那一節中的所有概念依然適用,只是這裡是套用到整個應用中的所有路徑操作。 + +## 路徑操作群組的依賴 { #dependencies-for-groups-of-path-operations } + +之後,在閱讀如何組織更大的應用程式([更大的應用程式 - 多個檔案](../../tutorial/bigger-applications.md){.internal-link target=_blank})時,可能會有多個檔案,你將會學到如何為一組路徑操作宣告單一的 `dependencies` 參數。 diff --git a/docs/zh-hant/docs/tutorial/dependencies/index.md b/docs/zh-hant/docs/tutorial/dependencies/index.md new file mode 100644 index 000000000..39c05ab05 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/dependencies/index.md @@ -0,0 +1,248 @@ +# 依賴 { #dependencies } + +**FastAPI** 內建一套強大且直覺的 **依賴注入** 系統。 + +它被設計為易於使用,使任何開發者都能輕鬆將其他元件與 **FastAPI** 整合。 + +## 什麼是「依賴注入」 { #what-is-dependency-injection } + +在程式設計中,「依賴注入」的意思是:你的程式碼(此處指你的「路徑操作函式 (path operation functions)」)可以宣告它為了正常運作所需要的各種東西:也就是「依賴」。 + +接著,這個系統(此處是 **FastAPI**)會負責做任何必要的事,將這些所需的依賴提供給你的程式碼(「注入」依賴)。 + +當你需要以下情境時,這特別有用: + +* 共享邏輯(相同的邏輯一次又一次地使用)。 +* 共用資料庫連線。 +* 強制套用安全性、驗證、角色要求等。 +* 以及許多其他事情... + +同時把重複的程式碼降到最低。 + +## 入門 { #first-steps } + +先看一個非常簡單的範例。它現在還不太實用,但夠簡單,讓我們能專注在 **依賴注入** 的運作方式。 + +### 建立一個依賴,或稱「dependable」 { #create-a-dependency-or-dependable } + +先專注在依賴本身。 + +它就是一個函式,可以接受與「路徑操作函式」相同的各種參數: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *} + +就這樣。 + +僅僅兩行。 + +而且它的外觀與結構和你的所有「路徑操作函式」一樣。 + +你可以把它想成一個沒有「裝飾器」(沒有 `@app.get("/some-path")`)的「路徑操作函式」。 + +它可以回傳你想要的任何東西。 + +在這個例子中,這個依賴會期望: + +* 一個選用的查詢參數 `q`,型別為 `str`。 +* 一個選用的查詢參數 `skip`,型別為 `int`,預設為 `0`。 +* 一個選用的查詢參數 `limit`,型別為 `int`,預設為 `100`。 + +然後它只會回傳一個包含這些值的 `dict`。 + +/// info | 說明 + +FastAPI 在 0.95.0 版新增了對 `Annotated` 的支援(並開始建議使用)。 + +如果你使用較舊的版本,嘗試使用 `Annotated` 時會出現錯誤。 + +在使用 `Annotated` 之前,請先[升級 FastAPI 版本](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}到至少 0.95.1。 + +/// + +### 匯入 `Depends` { #import-depends } + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *} + +### 在「被依賴者」(dependant)中宣告依賴 { #declare-the-dependency-in-the-dependant } + +和你在「路徑操作函式」參數上使用 `Body`、`Query` 等方式一樣,針對新參數使用 `Depends`: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *} + +雖然你在函式參數上使用 `Depends` 的方式和 `Body`、`Query` 等相同,但 `Depends` 的運作方式有點不同。 + +你只需要傳給 `Depends` 一個參數。 + +這個參數必須是類似函式的東西。 + +你不需要直接呼叫它(不要在後面加上括號),只要把它作為參數傳給 `Depends()` 即可。 + +而該函式的參數宣告方式與「路徑操作函式」相同。 + +/// tip | 提示 + +除了函式之外,還有其他「東西」也能當作依賴,會在下一章介紹。 + +/// + +當有新的請求到達時,**FastAPI** 會負責: + +* 以正確的參數呼叫你的依賴(dependable)函式。 +* 取得該函式的回傳結果。 +* 將結果指定給你的「路徑操作函式」中的對應參數。 + +```mermaid +graph TB + +common_parameters(["common_parameters"]) +read_items["/items/"] +read_users["/users/"] + +common_parameters --> read_items +common_parameters --> read_users +``` + +如此一來,你只需撰寫一次共用程式碼,**FastAPI** 會替你的各個「路徑操作」呼叫它。 + +/// check | 檢查 + +注意,你不必建立特殊的類別並把它傳到 **FastAPI** 去「註冊」或做類似的事。 + +只要把它傳給 `Depends`,**FastAPI** 就知道該怎麼處理其餘的部分。 + +/// + +## 共用 `Annotated` 依賴 { #share-annotated-dependencies } + +在上面的範例中,可以看到有一點點的「重複程式碼」。 + +當你要使用 `common_parameters()` 這個依賴時,你得寫出完整的型別註解搭配 `Depends()`: + +```Python +commons: Annotated[dict, Depends(common_parameters)] +``` + +但因為我們在使用 `Annotated`,我們可以把這個 `Annotated` 的值存到一個變數中,並在多個地方重複使用: + +{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *} + +/// tip | 提示 + +這只是標準的 Python,用的是所謂的「型別別名 (type alias)」,其實和 **FastAPI** 本身無關。 + +但因為 **FastAPI** 是建立在 Python 標準之上(包含 `Annotated`),你就可以在程式碼中使用這個技巧。😎 + +/// + +這些依賴依然會如預期運作,而最棒的是「型別資訊會被保留」,代表你的編輯器仍能提供「自動完成」、「即時錯誤」等功能,像 `mypy` 等其他工具也一樣受益。 + +當你在「大型程式碼庫」中,於許多「路徑操作」反覆使用「相同的依賴」時,這會特別有用。 + +## 要不要使用 `async` { #to-async-or-not-to-async } + +因為依賴也會由 **FastAPI** 呼叫(就像你的「路徑操作函式」),所以在定義函式時套用相同的規則。 + +你可以使用 `async def` 或一般的 `def`。 + +而且你可以在一般 `def` 的「路徑操作函式」中宣告 `async def` 的依賴,或在 `async def` 的「路徑操作函式」中宣告 `def` 的依賴,等等。 + +都沒關係。**FastAPI** 會知道該怎麼做。 + +/// note | 注意 + +如果你不熟悉,請參考文件中的 [Async: "In a hurry?"](../../async.md#in-a-hurry){.internal-link target=_blank} 一節,瞭解 `async` 與 `await`。 + +/// + +## 與 OpenAPI 整合 { #integrated-with-openapi } + +你的依賴(以及其子依賴)所宣告的所有請求參數、驗證與需求,都會整合進同一份 OpenAPI 結構中。 + +因此,互動式文件也會包含來自這些依賴的所有資訊: + + + +## 簡單用法 { #simple-usage } + +想一想,「路徑操作函式」是宣告好讓框架在「路徑」與「操作」符合時使用的,然後 **FastAPI** 會負責帶入正確的參數、從請求中擷取資料,並呼叫該函式。 + +其實,所有(或大多數)Web 框架都是這樣運作。 + +你從不會直接呼叫這些函式。它們是由框架(此處是 **FastAPI**)呼叫的。 + +透過依賴注入系統,你也可以告訴 **FastAPI**:你的「路徑操作函式」還「依賴」其他東西,應在你的「路徑操作函式」之前執行,**FastAPI** 會負責執行它並「注入」其結果。 + +這個「依賴注入」概念的其他常見稱呼包括: + +* 資源 +* 提供者 +* 服務 +* 可注入項 +* 元件 + +## **FastAPI** 外掛 { #fastapi-plug-ins } + +各種整合與「外掛」都能以 **依賴注入** 系統來構建。但事實上,並不需要真的去打造「外掛」,因為透過依賴,你可以宣告無數的整合與互動,並讓它們供你的「路徑操作函式」使用。 + +而且依賴可以用非常簡單直覺的方式建立,你只需要匯入所需的 Python 套件,然後用幾行程式碼就能把它們與你的 API 函式整合,真的是「只要幾行」。 + +在接下來的章節中你會看到這方面的例子,例如關聯式與 NoSQL 資料庫、安全性等。 + +## **FastAPI** 相容性 { #fastapi-compatibility } + +依賴注入系統的簡潔,使 **FastAPI** 能與以下事物相容: + +* 所有關聯式資料庫 +* NoSQL 資料庫 +* 外部套件 +* 外部 API +* 驗證與授權系統 +* API 使用監控系統 +* 回應資料注入系統 +* 等等 + +## 簡單而強大 { #simple-and-powerful } + +雖然階層式的依賴注入系統相當容易定義與使用,但它依然非常強大。 + +你可以定義會進一步依賴其他依賴的依賴。 + +最終會形成一棵階層式的依賴樹,而 **依賴注入** 系統會負責為你解決所有這些依賴(以及它們的子依賴),並在每一步提供(注入)對應的結果。 + +例如,假設你有 4 個 API 端點(「路徑操作」): + +* `/items/public/` +* `/items/private/` +* `/users/{user_id}/activate` +* `/items/pro/` + +那麼你就能只透過依賴與子依賴,為每個端點加入不同的權限需求: + +```mermaid +graph TB + +current_user(["current_user"]) +active_user(["active_user"]) +admin_user(["admin_user"]) +paying_user(["paying_user"]) + +public["/items/public/"] +private["/items/private/"] +activate_user["/users/{user_id}/activate"] +pro_items["/items/pro/"] + +current_user --> active_user +active_user --> admin_user +active_user --> paying_user + +current_user --> public +active_user --> private +admin_user --> activate_user +paying_user --> pro_items +``` + +## 與 **OpenAPI** 整合 { #integrated-with-openapi_1 } + +所有這些依賴在宣告其需求的同時,也會為你的「路徑操作」新增參數、驗證等。 + +**FastAPI** 會負責把這一切加入 OpenAPI 結構中,讓它們顯示在互動式文件系統裡。 diff --git a/docs/zh-hant/docs/tutorial/dependencies/sub-dependencies.md b/docs/zh-hant/docs/tutorial/dependencies/sub-dependencies.md new file mode 100644 index 000000000..50c4e1790 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/dependencies/sub-dependencies.md @@ -0,0 +1,105 @@ +# 子相依 { #sub-dependencies } + +你可以建立具有「子相依」的相依項。 + +它們可以按你的需要,層級任意加深。 + +**FastAPI** 會負責解析它們。 + +## 第一個相依項 "dependable" { #first-dependency-dependable } + +你可以建立第一個相依項("dependable")如下: + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *} + +它宣告了一個可選的查詢參數 `q`(型別為 `str`),然後直接回傳它。 + +這很簡單(不太實用),但有助於我們專注於子相依如何運作。 + +## 第二個相依,同時是 "dependable" 也是 "dependant" { #second-dependency-dependable-and-dependant } + +接著你可以建立另一個相依函式("dependable"),同時它也宣告了自己的相依(因此它同時也是 "dependant"): + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *} + +來看它所宣告的參數: + +- 即使這個函式本身是個相依項("dependable"),它也宣告了另一個相依(它「相依於」其他東西)。 + - 它相依 `query_extractor`,並把其回傳值指定給參數 `q`。 +- 它還宣告了一個可選的 `last_query` cookie,型別為 `str`。 + - 如果使用者沒有提供查詢 `q`,我們就使用先前儲存在 cookie 中的最後一次查詢值。 + +## 使用相依項 { #use-the-dependency } + +然後我們可以這樣使用這個相依項: + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[23] *} + +/// info + +注意,在路徑操作函式中我們只宣告了一個相依項 `query_or_cookie_extractor`。 + +但 **FastAPI** 會知道它必須先解析 `query_extractor`,在呼叫 `query_or_cookie_extractor` 時把其結果傳入。 + +/// + +```mermaid +graph TB + +query_extractor(["query_extractor"]) +query_or_cookie_extractor(["query_or_cookie_extractor"]) + +read_query["/items/"] + +query_extractor --> query_or_cookie_extractor --> read_query +``` + +## 多次使用同一個相依項 { #using-the-same-dependency-multiple-times } + +如果你的某個相依項在同一個路徑操作中被宣告了多次,例如多個相依共用同一個子相依,**FastAPI** 會知道只需在每次請求中呼叫該子相依一次。 + +它會把回傳值儲存在一個 「快取」 中,並在該次請求中傳遞給所有需要它的「相依者」,而不是為同一個請求多次呼叫相同的相依項。 + +在進階情境下,如果你確定需要在同一次請求的每個步驟都呼叫該相依(可能呼叫多次),而不是使用「快取」的值,你可以在使用 `Depends` 時設定參數 `use_cache=False`: + +//// tab | Python 3.10+ + +```Python hl_lines="1" +async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): + return {"fresh_value": fresh_value} +``` + +//// + +//// tab | Python 3.10+ 未使用 Annotated + +/// tip + +若可行,建議使用 `Annotated` 的版本。 + +/// + +```Python hl_lines="1" +async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)): + return {"fresh_value": fresh_value} +``` + +//// + +## 回顧 { #recap } + +撇開這裡用到的術語不談,**相依性注入(Dependency Injection)** 系統其實很簡單。 + +它只是一些與路徑操作函式外觀相同的函式。 + +但它非常強大,允許你宣告任意深度巢狀的相依「圖」(樹)。 + +/// tip + +用這些簡單的例子看起來可能不那麼有用。 + +但在關於安全性的章節中,你會看到它有多實用。 + +你也會看到它能為你省下多少程式碼。 + +/// diff --git a/docs/zh-hant/docs/tutorial/encoder.md b/docs/zh-hant/docs/tutorial/encoder.md new file mode 100644 index 000000000..03b7db639 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/encoder.md @@ -0,0 +1,35 @@ +# JSON 相容編碼器 { #json-compatible-encoder } + +在某些情況下,你可能需要將某種資料型別(例如 Pydantic 模型)轉換為與 JSON 相容的類型(例如 `dict`、`list` 等)。 + +例如,當你需要把它儲存在資料庫中。 + +為此,**FastAPI** 提供了 `jsonable_encoder()` 函式。 + +## 使用 `jsonable_encoder` { #using-the-jsonable-encoder } + +想像你有一個只接受與 JSON 相容資料的資料庫 `fake_db`。 + +例如,它不接受 `datetime` 物件,因為那與 JSON 不相容。 + +因此,必須將 `datetime` 物件轉為一個以 ISO 格式 表示資料的 `str`。 + +同樣地,這個資料庫不會接受 Pydantic 模型(帶有屬性的物件),只接受 `dict`。 + +你可以使用 `jsonable_encoder` 來處理。 + +它接收一個物件(例如 Pydantic 模型),並回傳一個與 JSON 相容的版本: + +{* ../../docs_src/encoder/tutorial001_py310.py hl[4,21] *} + +在此範例中,它會把 Pydantic 模型轉成 `dict`,並將 `datetime` 轉成 `str`。 + +呼叫後的結果可以用 Python 標準的 `json.dumps()` 進行編碼。 + +它不會回傳一個包含 JSON 內容的大型 `str`(字串)。它會回傳 Python 標準的資料結構(例如 `dict`),其中的值與子值都與 JSON 相容。 + +/// note + +事實上,`jsonable_encoder` 在 **FastAPI** 內部也被用來轉換資料。不過在許多其他情境中它同樣實用。 + +/// diff --git a/docs/zh-hant/docs/tutorial/extra-data-types.md b/docs/zh-hant/docs/tutorial/extra-data-types.md new file mode 100644 index 000000000..f516d965a --- /dev/null +++ b/docs/zh-hant/docs/tutorial/extra-data-types.md @@ -0,0 +1,62 @@ +# 額外的資料型別 { #extra-data-types } + +到目前為止,你一直在使用常見的資料型別,例如: + +* `int` +* `float` +* `str` +* `bool` + +但你也可以使用更複雜的資料型別。 + +而且你仍然會擁有目前為止所見的同樣功能: + +* 極佳的編輯器支援。 +* 將傳入請求的資料轉換。 +* 回應資料的轉換。 +* 資料驗證。 +* 自動產生註解與文件。 + +## 其他資料型別 { #other-data-types } + +以下是你可以使用的一些其他資料型別: + +* `UUID`: + * 一種標準的「通用唯一識別碼 (Universally Unique Identifier)」,常見於許多資料庫與系統的 ID。 + * 在請求與回應中會以 `str` 表示。 +* `datetime.datetime`: + * Python 的 `datetime.datetime`。 + * 在請求與回應中會以 ISO 8601 格式的 `str` 表示,例如:`2008-09-15T15:53:00+05:00`。 +* `datetime.date`: + * Python 的 `datetime.date`。 + * 在請求與回應中會以 ISO 8601 格式的 `str` 表示,例如:`2008-09-15`。 +* `datetime.time`: + * Python 的 `datetime.time`。 + * 在請求與回應中會以 ISO 8601 格式的 `str` 表示,例如:`14:23:55.003`。 +* `datetime.timedelta`: + * Python 的 `datetime.timedelta`。 + * 在請求與回應中會以總秒數的 `float` 表示。 + * Pydantic 也允許用「ISO 8601 time diff encoding」來表示,詳情見文件。 +* `frozenset`: + * 在請求與回應中與 `set` 相同處理: + * 在請求中,會讀取一個 list,去除重複並轉為 `set`。 + * 在回應中,`set` 會被轉為 `list`。 + * 生成的 schema 會指定 `set` 的值為唯一(使用 JSON Schema 的 `uniqueItems`)。 +* `bytes`: + * 標準的 Python `bytes`。 + * 在請求與回應中會被當作 `str` 處理。 + * 生成的 schema 會指定其為 `str`,且 "format" 為 `binary`。 +* `Decimal`: + * 標準的 Python `Decimal`。 + * 在請求與回應中,與 `float` 的處理方式相同。 +* 你可以在此查閱所有可用的 Pydantic 資料型別:Pydantic 資料型別。 + +## 範例 { #example } + +以下是一個帶有參數、使用上述部分型別的 *路徑操作 (path operation)* 範例。 + +{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[1,3,12:16] *} + +請注意,函式內的參數會是它們的自然資料型別,因此你可以進行一般的日期運算,例如: + +{* ../../docs_src/extra_data_types/tutorial001_an_py310.py hl[18:19] *} diff --git a/docs/zh-hant/docs/tutorial/extra-models.md b/docs/zh-hant/docs/tutorial/extra-models.md new file mode 100644 index 000000000..8aae62f8e --- /dev/null +++ b/docs/zh-hant/docs/tutorial/extra-models.md @@ -0,0 +1,211 @@ +# 額外的模型 { #extra-models } + +延續前一個範例,通常會有不只一個彼此相關的模型。 + +對使用者模型尤其如此,因為: + +* 「輸入模型」需要能包含密碼。 +* 「輸出模型」不應包含密碼。 +* 「資料庫模型」通常需要儲存雜湊後的密碼。 + +/// danger + +切勿儲存使用者的明文密碼。務必只儲存可供驗證的「安全雜湊」。 + +若你還不清楚,稍後會在[安全性章節](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}學到什麼是「密碼雜湊」。 + +/// + +## 多個模型 { #multiple-models } + +以下是模型大致長相、與其密碼欄位以及它們被使用的位置: + +{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} + +### 關於 `**user_in.model_dump()` { #about-user-in-model-dump } + +#### Pydantic 的 `.model_dump()` { #pydantics-model-dump } + +`user_in` 是一個 `UserIn` 類別的 Pydantic 模型。 + +Pydantic 模型有 `.model_dump()` 方法,會回傳包含該模型資料的 `dict`。 + +因此,若我們建立一個 Pydantic 物件 `user_in` 如: + +```Python +user_in = UserIn(username="john", password="secret", email="john.doe@example.com") +``` + +接著呼叫: + +```Python +user_dict = user_in.model_dump() +``` + +此時變數 `user_dict` 會是一個承載資料的 `dict`(也就是 `dict`,而非 Pydantic 模型物件)。 + +若再呼叫: + +```Python +print(user_dict) +``` + +我們會得到一個 Python `dict`: + +```Python +{ + 'username': 'john', + 'password': 'secret', + 'email': 'john.doe@example.com', + 'full_name': None, +} +``` + +#### 解包 `dict` { #unpacking-a-dict } + +若將像 `user_dict` 這樣的 `dict` 以 `**user_dict` 傳給函式(或類別),Python 會將其「解包」,把 `user_dict` 的鍵和值直接當作具名引數傳入。 + +因此,延續上面的 `user_dict`,寫成: + +```Python +UserInDB(**user_dict) +``` + +效果等同於: + +```Python +UserInDB( + username="john", + password="secret", + email="john.doe@example.com", + full_name=None, +) +``` + +更精確地說,直接使用 `user_dict`(未來內容可能有所不同)則等同於: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], +) +``` + +#### 由另一個模型內容建立 Pydantic 模型 { #a-pydantic-model-from-the-contents-of-another } + +如上例我們從 `user_in.model_dump()` 得到 `user_dict`,以下程式碼: + +```Python +user_dict = user_in.model_dump() +UserInDB(**user_dict) +``` + +等同於: + +```Python +UserInDB(**user_in.model_dump()) +``` + +...因為 `user_in.model_dump()` 回傳的是 `dict`,接著在傳給 `UserInDB` 時以 `**` 前綴讓 Python 進行解包。 + +因此,我們可以用一個 Pydantic 模型的資料建立另一個 Pydantic 模型。 + +#### 解包 `dict` 並加入額外參數 { #unpacking-a-dict-and-extra-keywords } + +接著加入額外的具名引數 `hashed_password=hashed_password`,如下: + +```Python +UserInDB(**user_in.model_dump(), hashed_password=hashed_password) +``` + +...結果等同於: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], + hashed_password = hashed_password, +) +``` + +/// warning + +輔助函式 `fake_password_hasher` 與 `fake_save_user` 只是用來示範資料流程,並不提供任何實際的安全性。 + +/// + +## 減少重複 { #reduce-duplication } + +減少程式碼重複是 FastAPI 的核心理念之一。 + +因為重複的程式碼會提高發生錯誤、安全性問題、程式不同步(某處更新但其他處未更新)等風險。 + +而這些模型共享大量資料,重複了屬性名稱與型別。 + +我們可以做得更好。 + +我們可以宣告一個作為基底的 `UserBase` 模型,其他模型繼承它成為子類別,沿用其屬性(型別宣告、驗證等)。 + +所有資料轉換、驗證、文件產生等仍可正常運作。 + +如此一來,我們只需要宣告模型之間的差異(含明文 `password`、含 `hashed_password`、或不含密碼): + +{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *} + +## `Union` 或 `anyOf` { #union-or-anyof } + +你可以將回應宣告為多個型別的 `Union`,表示回應可能是其中任一型別。 + +在 OpenAPI 中會以 `anyOf` 定義。 + +要達成這點,使用標準的 Python 型別提示 `typing.Union`: + +/// note + +在定義 `Union` 時,請先放置「更具體」的型別,再放「較不具體」的型別。以下範例中,較具體的 `PlaneItem` 置於 `CarItem` 之前:`Union[PlaneItem, CarItem]`。 + +/// + +{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *} + +### Python 3.10 中的 `Union` { #union-in-python-3-10 } + +此範例中,我們將 `Union[PlaneItem, CarItem]` 作為引數 `response_model` 的值。 + +由於這裡是把它當作引數的「值」傳入,而非用於型別註記,因此即使在 Python 3.10 也必須使用 `Union`。 + +若用於型別註記,則可以使用直線(|),如下: + +```Python +some_variable: PlaneItem | CarItem +``` + +但若寫成指定值 `response_model=PlaneItem | CarItem` 會發生錯誤,因為 Python 會嘗試在 `PlaneItem` 與 `CarItem` 之間執行「無效運算」,而非將其視為型別註記。 + +## 模型的清單 { #list-of-models } + +同樣地,你可以將回應宣告為物件的 `list`。 + +為此,使用標準的 Python `list`: + +{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *} + +## 以任意 `dict` 作為回應 { #response-with-arbitrary-dict } + +你也可以用一般的任意 `dict` 宣告回應,只需指定鍵和值的型別,而不必使用 Pydantic 模型。 + +當你事先不知道可用的欄位/屬性名稱(定義 Pydantic 模型所需)時,這很實用。 + +此時可使用 `dict`: + +{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *} + +## 重點回顧 { #recap } + +依情境使用多個 Pydantic 模型並靈活繼承。 + +當一個實體需要呈現不同「狀態」時,不必侷限於一個資料模型。例如使用者這個實體,可能有包含 `password`、包含 `password_hash`,或不含密碼等不同狀態。 diff --git a/docs/zh-hant/docs/tutorial/first-steps.md b/docs/zh-hant/docs/tutorial/first-steps.md index 6bcb453bf..3aa2d39ae 100644 --- a/docs/zh-hant/docs/tutorial/first-steps.md +++ b/docs/zh-hant/docs/tutorial/first-steps.md @@ -2,7 +2,7 @@ 最簡單的 FastAPI 檔案可能看起來像這樣: -{* ../../docs_src/first_steps/tutorial001_py39.py *} +{* ../../docs_src/first_steps/tutorial001_py310.py *} 將其複製到一個名為 `main.py` 的文件中。 @@ -183,7 +183,7 @@ Deploying to FastAPI Cloud... ### 第一步:引入 `FastAPI` { #step-1-import-fastapi } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[1] *} `FastAPI` 是一個 Python 類別,提供所有 API 的全部功能。 @@ -197,7 +197,7 @@ Deploying to FastAPI Cloud... ### 第二步:建立一個 `FastAPI`「實例」 { #step-2-create-a-fastapi-instance } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[3] *} 這裡的 `app` 變數將會是 `FastAPI` 類別的「實例」。 @@ -266,12 +266,12 @@ https://example.com/items/foo #### 定義一個「路徑操作裝飾器」 { #define-a-path-operation-decorator } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[6] *} `@app.get("/")` 告訴 **FastAPI** 那個函式負責處理請求: * 路徑 `/` -* 使用 get操作 +* 使用 get 操作 /// info | `@decorator` 說明 @@ -320,7 +320,7 @@ Python 中的 `@something` 語法被稱為「裝飾器」。 * **operation**:是 `get`。 * **function**:是裝飾器下面的函式(在 `@app.get("/")` 下面)。 -{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[7] *} 這就是一個 Python 函式。 @@ -332,7 +332,7 @@ Python 中的 `@something` 語法被稱為「裝飾器」。 你也可以將它定義為一般函式,而不是 `async def`: -{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py310.py hl[7] *} /// note @@ -342,7 +342,7 @@ Python 中的 `@something` 語法被稱為「裝飾器」。 ### 第五步:回傳內容 { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py310.py hl[8] *} 你可以返回一個 `dict`、`list`、單個值作為 `str`、`int` 等。 diff --git a/docs/zh-hant/docs/tutorial/handling-errors.md b/docs/zh-hant/docs/tutorial/handling-errors.md new file mode 100644 index 000000000..f3a7573cd --- /dev/null +++ b/docs/zh-hant/docs/tutorial/handling-errors.md @@ -0,0 +1,244 @@ +# 錯誤處理 { #handling-errors } + +在許多情況下,你需要通知使用你 API 的用戶端發生錯誤。 + +這個用戶端可能是帶有前端的瀏覽器、他人的程式碼、IoT 裝置等。 + +你可能需要告訴用戶端: + +* 用戶端沒有足夠權限執行該操作。 +* 用戶端沒有權限存取該資源。 +* 用戶端嘗試存取的項目不存在。 +* 等等。 + +在這些情況下,通常會回傳範圍為 400(400 到 499)的 HTTP 狀態碼。 + +這類似於 200 範圍的 HTTP 狀態碼(200 到 299)。那些「200」狀態碼表示請求在某種程度上是「成功」的。 + +400 範圍的狀態碼表示用戶端錯誤。 + +還記得那些「404 Not Found」錯誤(和梗)嗎? + +## 使用 `HTTPException` { #use-httpexception } + +要向用戶端回傳帶有錯誤的 HTTP 回應,使用 `HTTPException`。 + +### 匯入 `HTTPException` { #import-httpexception } + +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[1] *} + +### 在程式中 raise 一個 `HTTPException` { #raise-an-httpexception-in-your-code } + +`HTTPException` 是一般的 Python 例外,但包含與 API 相關的附加資料。 + +因為它是 Python 的例外,你不是 `return`,而是 `raise`。 + +這也表示,如果你在某個工具函式中(該函式被你的「路徑操作函式」呼叫),並在該工具函式裡 raise `HTTPException`,那麼「路徑操作函式」剩下的程式碼將不會執行;該請求會立刻被終止,並將 `HTTPException` 的 HTTP 錯誤傳回給用戶端。 + +為何選擇 raise 例外而非回傳值的好處,會在相依性與安全性章節更為明顯。 + +在這個範例中,當用戶端以不存在的 ID 請求項目時,raise 一個狀態碼為 `404` 的例外: + +{* ../../docs_src/handling_errors/tutorial001_py310.py hl[11] *} + +### 回應結果 { #the-resulting-response } + +如果用戶端請求 `http://example.com/items/foo`(`item_id` 為 `"foo"`),會收到 200 的 HTTP 狀態碼,以及以下 JSON 回應: + +```JSON +{ + "item": "The Foo Wrestlers" +} +``` + +但如果用戶端請求 `http://example.com/items/bar`(不存在的 `item_id` `"bar"`),會收到 404("not found")的 HTTP 狀態碼,以及以下 JSON 回應: + +```JSON +{ + "detail": "Item not found" +} +``` + +/// tip + +在 raise 一個 `HTTPException` 時,你可以將任何可轉為 JSON 的值作為 `detail` 參數,不只限於 `str`。 + +你可以傳入 `dict`、`list` 等。 + +**FastAPI** 會自動處理並轉為 JSON。 + +/// + +## 新增自訂標頭 { #add-custom-headers } + +有些情況需要在 HTTP 錯誤回應中加入自訂標頭,例如某些安全性情境。 + +你大概不需要在程式碼中直接使用。 + +但若你在進階情境中需要,可以這樣加入自訂標頭: + +{* ../../docs_src/handling_errors/tutorial002_py310.py hl[14] *} + +## 安裝自訂例外處理器 { #install-custom-exception-handlers } + +你可以使用 Starlette 的相同例外工具 來加入自訂例外處理器。 + +假設你有一個自訂例外 `UnicornException`,你(或你使用的函式庫)可能會 `raise` 它。 + +而你想用 FastAPI 全域處理這個例外。 + +你可以使用 `@app.exception_handler()` 加入自訂例外處理器: + +{* ../../docs_src/handling_errors/tutorial003_py310.py hl[5:7,13:18,24] *} + +在這裡,如果你請求 `/unicorns/yolo`,該「路徑操作」會 `raise` 一個 `UnicornException`。 + +但它會被 `unicorn_exception_handler` 所處理。 + +因此你會得到一個乾淨的錯誤回應,HTTP 狀態碼為 `418`,JSON 內容如下: + +```JSON +{"message": "Oops! yolo did something. There goes a rainbow..."} +``` + +/// note | 技術細節 + +你也可以使用 `from starlette.requests import Request` 與 `from starlette.responses import JSONResponse`。 + +**FastAPI** 以便利性為由,提供和 `starlette.responses` 相同的介面於 `fastapi.responses`。但大多數可用的回應類型其實直接來自 Starlette。`Request` 也一樣。 + +/// + +## 覆寫預設例外處理器 { #override-the-default-exception-handlers } + +**FastAPI** 內建了一些預設例外處理器。 + +這些處理器負責在你 `raise` 一個 `HTTPException` 或請求帶有無效資料時,回傳預設的 JSON 回應。 + +你可以用自己的處理器來覆寫它們。 + +### 覆寫請求驗證例外 { #override-request-validation-exceptions } + +當請求包含無效資料時,**FastAPI** 會在內部 raise 一個 `RequestValidationError`。 + +它同時也包含了對應的預設例外處理器。 + +要覆寫它,匯入 `RequestValidationError`,並用 `@app.exception_handler(RequestValidationError)` 來裝飾你的例外處理器。 + +例外處理器會接收一個 `Request` 和該例外。 + +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[2,14:19] *} + +現在,如果你前往 `/items/foo`,預設的 JSON 錯誤本應為: + +```JSON +{ + "detail": [ + { + "loc": [ + "path", + "item_id" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ] +} +``` + +你將會改而得到文字版: + +``` +Validation errors: +Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer +``` + +### 覆寫 `HTTPException` 的錯誤處理器 { #override-the-httpexception-error-handler } + +同樣地,你也可以覆寫 `HTTPException` 的處理器。 + +例如,你可能想在這些錯誤時回傳純文字而非 JSON: + +{* ../../docs_src/handling_errors/tutorial004_py310.py hl[3:4,9:11,25] *} + +/// note | 技術細節 + +你也可以使用 `from starlette.responses import PlainTextResponse`。 + +**FastAPI** 以便利性為由,提供和 `starlette.responses` 相同的介面於 `fastapi.responses`。但大多數可用的回應類型其實直接來自 Starlette。 + +/// + +/// warning + +請注意,`RequestValidationError` 內含驗證錯誤發生的檔名與行號,如果你願意,可以在日誌中顯示這些相關資訊。 + +但這也代表如果你只是把它轉成字串並直接回傳,可能會洩漏一些關於你系統的資訊。因此這裡的程式碼會分別擷取並顯示每個錯誤。 + +/// + +### 使用 `RequestValidationError` 的 body { #use-the-requestvalidationerror-body } + +`RequestValidationError` 包含它收到的(但無效的)`body`。 + +在開發應用時,你可以用它來記錄 body 並除錯、回傳給使用者等。 + +{* ../../docs_src/handling_errors/tutorial005_py310.py hl[14] *} + +現在嘗試送出一個無效的項目,例如: + +```JSON +{ + "title": "towel", + "size": "XL" +} +``` + +你會收到一個告知資料無效、且包含所收到 body 的回應: + +```JSON hl_lines="12-15" +{ + "detail": [ + { + "loc": [ + "body", + "size" + ], + "msg": "value is not a valid integer", + "type": "type_error.integer" + } + ], + "body": { + "title": "towel", + "size": "XL" + } +} +``` + +#### FastAPI 的 `HTTPException` 與 Starlette 的 `HTTPException` { #fastapis-httpexception-vs-starlettes-httpexception } + +**FastAPI** 有自己定義的 `HTTPException`。 + +而 **FastAPI** 的 `HTTPException` 錯誤類別是繼承自 Starlette 的 `HTTPException` 錯誤類別。 + +唯一的差異是,**FastAPI** 的 `HTTPException` 在 `detail` 欄位接受任何可轉為 JSON 的資料,而 Starlette 的 `HTTPException` 只接受字串。 + +因此,在你的程式碼中,你可以一如往常地 raise **FastAPI** 的 `HTTPException`。 + +但當你註冊例外處理器時,應該針對 Starlette 的 `HTTPException` 來註冊。 + +如此一來,如果 Starlette 的內部程式碼,或任何 Starlette 擴充/外掛 raise 了 Starlette 的 `HTTPException`,你的處理器就能攔截並處理它。 + +在這個範例中,為了能在同一份程式碼中同時使用兩種 `HTTPException`,我們把 Starlette 的例外重新命名為 `StarletteHTTPException`: + +```Python +from starlette.exceptions import HTTPException as StarletteHTTPException +``` + +### 重用 **FastAPI** 的例外處理器 { #reuse-fastapis-exception-handlers } + +如果你想在使用例外的同時,沿用 **FastAPI** 的預設例外處理器,你可以從 `fastapi.exception_handlers` 匯入並重用預設的處理器: + +{* ../../docs_src/handling_errors/tutorial006_py310.py hl[2:5,15,21] *} + +在這個範例中,你只是用一段很生動的訊息把錯誤印出來,不過重點是:你可以使用該例外,然後直接重用預設的例外處理器。 diff --git a/docs/zh-hant/docs/tutorial/header-param-models.md b/docs/zh-hant/docs/tutorial/header-param-models.md new file mode 100644 index 000000000..ca2b1f8de --- /dev/null +++ b/docs/zh-hant/docs/tutorial/header-param-models.md @@ -0,0 +1,72 @@ +# 標頭參數模型 { #header-parameter-models } + +如果你有一組相關的標頭參數,可以建立一個 Pydantic 模型來宣告它們。 + +這能讓你在多處重複使用該模型,並一次性為所有參數宣告驗證與中繼資料。😎 + +/// note | 注意 + +自 FastAPI 版本 `0.115.0` 起支援。🤓 + +/// + +## 以 Pydantic 模型宣告標頭參數 { #header-parameters-with-a-pydantic-model } + +在 Pydantic 模型中宣告你需要的標頭參數,然後將參數宣告為 `Header`: + +{* ../../docs_src/header_param_models/tutorial001_an_py310.py hl[9:14,18] *} + +FastAPI 會從請求的標頭為每個欄位擷取資料,並交給你已定義的 Pydantic 模型實例。 + +## 檢視文件 { #check-the-docs } + +你可以在 `/docs` 的文件介面看到所需的標頭: + +
+ +
+ +## 禁止額外標頭 { #forbid-extra-headers } + +在某些特殊情境(可能不常見)下,你可能想限制允許接收的標頭。 + +你可以使用 Pydantic 的模型設定來 `forbid` 任何 `extra` 欄位: + +{* ../../docs_src/header_param_models/tutorial002_an_py310.py hl[10] *} + +如果用戶端嘗試傳送額外的標頭,會收到錯誤回應。 + +例如,用戶端若傳送名為 `tool`、值為 `plumbus` 的標頭,會收到錯誤回應,指出不允許標頭參數 `tool`: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["header", "tool"], + "msg": "Extra inputs are not permitted", + "input": "plumbus", + } + ] +} +``` + +## 停用底線轉換 { #disable-convert-underscores } + +與一般標頭參數相同,當參數名稱包含底線字元時,會自動轉換為連字號。 + +例如,若在程式碼中有標頭參數 `save_data`,實際期望的 HTTP 標頭為 `save-data`,在文件中也會如此顯示。 + +如果因某些原因需要停用這個自動轉換,你也可以在標頭參數的 Pydantic 模型上設定。 + +{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *} + +/// warning | 警告 + +在將 `convert_underscores` 設為 `False` 之前,請注意有些 HTTP 代理與伺服器不允許含有底線的標頭。 + +/// + +## 摘要 { #summary } + +你可以在 FastAPI 中使用 Pydantic 模型宣告標頭。😎 diff --git a/docs/zh-hant/docs/tutorial/header-params.md b/docs/zh-hant/docs/tutorial/header-params.md new file mode 100644 index 000000000..2d6aade4b --- /dev/null +++ b/docs/zh-hant/docs/tutorial/header-params.md @@ -0,0 +1,91 @@ +# Header 參數 { #header-parameters } + +你可以用與定義 `Query`、`Path`、`Cookie` 參數相同的方式來定義 Header 參數。 + +## 匯入 `Header` { #import-header } + +先匯入 `Header`: + +{* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *} + +## 宣告 `Header` 參數 { #declare-header-parameters } + +接著使用與 `Path`、`Query`、`Cookie` 相同的結構來宣告標頭參數。 + +你可以設定預設值,以及所有額外的驗證或註解參數: + +{* ../../docs_src/header_params/tutorial001_an_py310.py hl[9] *} + +/// note | 注意 + +`Header` 與 `Path`、`Query`、`Cookie` 是「姊妹」類別,同樣繼承自共同的 `Param` 類別。 + +但請記得,當你從 `fastapi` 匯入 `Query`、`Path`、`Header` 等時,它們其實是會回傳特殊類別的函式。 + +/// + +/// info | 說明 + +要宣告標頭,必須使用 `Header`,否則參數會被解讀為查詢參數。 + +/// + +## 自動轉換 { #automatic-conversion } + +在 `Path`、`Query`、`Cookie` 提供的功能之上,`Header` 還有一些額外的小功能。 + +大多數標準標頭的單字以連字號(減號,`-`)分隔。 + +但像 `user-agent` 這樣的變數名稱在 Python 中是無效的。 + +因此,`Header` 會在預設情況下把參數名稱中的底線(`_`)轉換為連字號(`-`),以便讀取並在文件中顯示該標頭。 + +此外,HTTP 標頭不區分大小寫,所以你可以使用標準的 Python 命名風格(snake_case)來宣告。 + +因此,你可以像在 Python 程式中一樣使用 `user_agent`,不需要把首字母大寫成 `User_Agent` 或類似寫法。 + +若因某些原因需要停用底線自動轉連字號的行為,將 `Header` 的 `convert_underscores` 參數設為 `False`: + +{* ../../docs_src/header_params/tutorial002_an_py310.py hl[10] *} + +/// warning | 警告 + +在將 `convert_underscores` 設為 `False` 之前,請注意有些 HTTP 代理與伺服器不允許使用帶有底線的標頭。 + +/// + +## 重複的標頭 { #duplicate-headers } + +有時可能會收到重複的標頭,也就是同一個標頭會有多個值。 + +可以在型別宣告中使用 list 來定義這種情況。 + +你會以 Python 的 `list` 形式收到該重複標頭的所有值。 + +例如,要宣告可以出現多次的 `X-Token` 標頭,可以這樣寫: + +{* ../../docs_src/header_params/tutorial003_an_py310.py hl[9] *} + +如果你在與該*路徑操作 (path operation)* 溝通時送出兩個 HTTP 標頭如下: + +``` +X-Token: foo +X-Token: bar +``` + +回應會像這樣: + +```JSON +{ + "X-Token values": [ + "bar", + "foo" + ] +} +``` + +## 小結 { #recap } + +使用 `Header` 宣告標頭,套用與 `Query`、`Path`、`Cookie` 相同的通用模式。 + +而且別擔心變數名稱中的底線,**FastAPI** 會自動幫你轉換。 diff --git a/docs/zh-hant/docs/tutorial/metadata.md b/docs/zh-hant/docs/tutorial/metadata.md new file mode 100644 index 000000000..3243efa89 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/metadata.md @@ -0,0 +1,120 @@ +# 中繼資料與文件 URL { #metadata-and-docs-urls } + +你可以在你的 FastAPI 應用程式中自訂多項中繼資料設定。 + +## API 的中繼資料 { #metadata-for-api } + +你可以設定下列欄位,這些欄位會用在 OpenAPI 規格與自動產生的 API 文件介面中: + +| 參數 | 型別 | 說明 | +|------------|------|-------------| +| `title` | `str` | API 的標題。 | +| `summary` | `str` | API 的簡短摘要。自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。 | +| `description` | `str` | API 的簡短說明。可使用 Markdown。 | +| `version` | `string` | API 的版本號。這是你自己的應用程式版本,不是 OpenAPI 的版本,例如 `2.5.0`。 | +| `terms_of_service` | `str` | 指向 API 服務條款的 URL。若提供,必須是 URL。 | +| `contact` | `dict` | 對外公開的 API 聯絡資訊。可包含多個欄位。
contact 欄位
參數型別說明
namestr聯絡人/組織的識別名稱。
urlstr指向聯絡資訊的 URL。必須是 URL 格式。
emailstr聯絡人/組織的電子郵件地址。必須是電子郵件格式。
| +| `license_info` | `dict` | 對外公開的 API 授權資訊。可包含多個欄位。
license_info 欄位
參數型別說明
namestr必填(若有設定 license_info)。API 使用的授權名稱。
identifierstrAPI 的 SPDX 授權表示式。identifier 欄位與 url 欄位互斥。自 OpenAPI 3.1.0、FastAPI 0.99.0 起可用。
urlstrAPI 所採用授權的 URL。必須是 URL 格式。
| + +你可以這樣設定它們: + +{* ../../docs_src/metadata/tutorial001_py310.py hl[3:16, 19:32] *} + +/// tip | 提示 + +你可以在 `description` 欄位中撰寫 Markdown,輸出時會被正確渲染。 + +/// + +使用這些設定後,自動產生的 API 文件會像這樣: + + + +## 授權識別碼 { #license-identifier } + +自 OpenAPI 3.1.0 與 FastAPI 0.99.0 起,你也可以在 `license_info` 中使用 `identifier` 來取代 `url`。 + +例如: + +{* ../../docs_src/metadata/tutorial001_1_py310.py hl[31] *} + +## 標籤的中繼資料 { #metadata-for-tags } + +你也可以透過 `openapi_tags` 參數,為用來分組你的路徑操作(path operation)的各個標籤加入額外中繼資料。 + +它接收一個 list,其中每個標籤對應一個 dictionary。 + +每個 dictionary 可包含: + +* `name`(**必填**):一個 `str`,其值需與你在路徑操作與 `APIRouter`s 的 `tags` 參數中使用的標籤名稱相同。 +* `description`:一個 `str`,為該標籤的簡短描述。可使用 Markdown,並會顯示在文件介面中。 +* `externalDocs`:一個 `dict`,描述外部文件,包含: + * `description`:一個 `str`,外部文件的簡短描述。 + * `url`(**必填**):一個 `str`,外部文件的 URL。 + +### 建立標籤的中繼資料 { #create-metadata-for-tags } + +我們用 `users` 與 `items` 兩個標籤來示範。 + +先為你的標籤建立中繼資料,然後將它傳給 `openapi_tags` 參數: + +{* ../../docs_src/metadata/tutorial004_py310.py hl[3:16,18] *} + +注意你可以在描述中使用 Markdown,例如「login」會以粗體(**login**)顯示,而「fancy」會以斜體(_fancy_)顯示。 + +/// tip | 提示 + +你不必為你使用到的每個標籤都加入中繼資料。 + +/// + +### 使用你的標籤 { #use-your-tags } + +在你的路徑操作(以及 `APIRouter`s)上使用 `tags` 參數,將它們歸類到不同標籤下: + +{* ../../docs_src/metadata/tutorial004_py310.py hl[21,26] *} + +/// info | 資訊 + +在[路徑操作設定]中閱讀更多關於標籤的內容:[Path Operation Configuration](path-operation-configuration.md#tags){.internal-link target=_blank}。 + +/// + +### 檢視文件 { #check-the-docs } + +現在檢視文件時,會看到所有額外的中繼資料: + + + +### 標籤順序 { #order-of-tags } + +每個標籤中繼資料 dictionary 在清單中的順序,也會決定它們在文件介面中的顯示順序。 + +例如,雖然按字母排序時 `users` 會排在 `items` 之後,但因為我們在清單中將它的中繼資料放在第一個,所以它會先顯示。 + +## OpenAPI URL { #openapi-url } + +預設情況下,OpenAPI 綱要(schema)會提供在 `/openapi.json`。 + +但你可以用 `openapi_url` 參數來調整。 + +例如,將它設定為提供在 `/api/v1/openapi.json`: + +{* ../../docs_src/metadata/tutorial002_py310.py hl[3] *} + +如果你想完全停用 OpenAPI 綱要,可以設定 `openapi_url=None`,同時也會停用依賴它的文件使用者介面。 + +## 文件 URL { #docs-urls } + +你可以設定內建的兩個文件使用者介面: + +* Swagger UI:提供於 `/docs`。 + * 可用 `docs_url` 參數設定其 URL。 + * 設定 `docs_url=None` 可停用。 +* ReDoc:提供於 `/redoc`。 + * 可用 `redoc_url` 參數設定其 URL。 + * 設定 `redoc_url=None` 可停用。 + +例如,將 Swagger UI 提供於 `/documentation`,並停用 ReDoc: + +{* ../../docs_src/metadata/tutorial003_py310.py hl[3] *} diff --git a/docs/zh-hant/docs/tutorial/middleware.md b/docs/zh-hant/docs/tutorial/middleware.md new file mode 100644 index 000000000..ac6f5367d --- /dev/null +++ b/docs/zh-hant/docs/tutorial/middleware.md @@ -0,0 +1,95 @@ +# 中介軟體 { #middleware } + +你可以在 **FastAPI** 應用程式中加入中介軟體。 + +「中介軟體」是一個函式,在任何特定的*路徑操作*處理之前先處理每個**請求**;在回傳之前,也會處理每個**回應**。 + +- 它會攔截進到應用程式的每個**請求**。 +- 然後可以對該**請求**做一些處理或執行所需的程式碼。 +- 接著把**請求**傳遞給應用程式的其餘部分(某個*路徑操作*)處理。 +- 之後再接收應用程式(某個*路徑操作*)所產生的**回應**。 +- 可以對該**回應**做一些處理或執行所需的程式碼。 +- 然後回傳**回應**。 + +/// note | 技術細節 + +如果你有使用帶有 `yield` 的相依性,其釋放階段的程式碼會在中介軟體之後執行。 + +若有背景工作(在[背景工作](background-tasks.md){.internal-link target=_blank}一節會介紹,你稍後會看到),它們會在所有中介軟體之後執行。 + +/// + +## 建立中介軟體 { #create-a-middleware } + +要建立中介軟體,將裝飾器 `@app.middleware("http")` 加在函式上方。 + +中介軟體函式會接收: + +- `request`。 +- 一個函式 `call_next`,會以 `request` 作為參數。 + - 這個函式會把 `request` 傳給對應的*路徑操作*。 + - 然後回傳對應*路徑操作*所產生的 `response`。 +- 然後你可以在回傳之前進一步修改 `response`。 + +{* ../../docs_src/middleware/tutorial001_py310.py hl[8:9,11,14] *} + +/// tip + +請記得,自訂的非標準標頭可以使用 `X-` 前綴。 + +但如果你有自訂標頭並希望瀏覽器端的用戶端能看到它們,你需要在 CORS 設定([CORS(Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank})中使用 Starlette 的 CORS 文件所記載的參數 `expose_headers` 將它們加入。 + +/// + +/// note | 技術細節 + +你也可以使用 `from starlette.requests import Request`。 + +**FastAPI** 為了方便開發者而提供了它,但實際上它直接來自 Starlette。 + +/// + +### 在 `response` 之前與之後 { #before-and-after-the-response } + +你可以在任何*路徑操作*接收 `request` 之前,加入要執行的程式碼。 + +也可以在產生出 `response` 之後、回傳之前執行程式碼。 + +例如,你可以新增一個自訂標頭 `X-Process-Time`,其內容為處理請求並產生回應所花費的秒數: + +{* ../../docs_src/middleware/tutorial001_py310.py hl[10,12:13] *} + +/// tip + +這裡我們使用 `time.perf_counter()` 而不是 `time.time()`,因為在這些用例中它可能更精確。🤓 + +/// + +## 多個中介軟體的執行順序 { #multiple-middleware-execution-order } + +當你使用 `@app.middleware()` 裝飾器或 `app.add_middleware()` 方法加入多個中介軟體時,每個新的中介軟體都會包裹應用程式,形成一個堆疊。最後加入的中介軟體位於最外層,最先加入的位於最內層。 + +在請求路徑上,最外層的中介軟體最先執行。 + +在回應路徑上,它最後執行。 + +例如: + +```Python +app.add_middleware(MiddlewareA) +app.add_middleware(MiddlewareB) +``` + +執行順序如下: + +- **請求**:MiddlewareB → MiddlewareA → 路由 + +- **回應**:路由 → MiddlewareA → MiddlewareB + +這種堆疊行為可確保中介軟體以可預期且可控制的順序執行。 + +## 其他中介軟體 { #other-middlewares } + +你之後可以在[進階使用者指南:進階中介軟體](../advanced/middleware.md){.internal-link target=_blank}閱讀更多關於其他中介軟體的內容。 + +下一節你將會讀到如何使用中介軟體處理 CORS。 diff --git a/docs/zh-hant/docs/tutorial/path-operation-configuration.md b/docs/zh-hant/docs/tutorial/path-operation-configuration.md new file mode 100644 index 000000000..45c101434 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/path-operation-configuration.md @@ -0,0 +1,107 @@ +# 路徑操作設定 { #path-operation-configuration } + +你可以在你的「路徑操作裝飾器」中傳入多個參數來進行設定。 + +/// warning | 警告 + +請注意,這些參數是直接傳給「路徑操作裝飾器」,而不是傳給你的「路徑操作函式」。 + +/// + +## 回應狀態碼 { #response-status-code } + +你可以為「路徑操作」的回應設定 (HTTP) `status_code`。 + +你可以直接傳入整數代碼,例如 `404`。 + +如果不記得每個數字代碼代表什麼,你可以使用 `status` 中的速記常數: + +{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *} + +該狀態碼會用於回應,並被加入至 OpenAPI 結構描述中。 + +/// note | 技術細節 + +你也可以使用 `from starlette import status`。 + +**FastAPI** 提供與 `starlette.status` 相同的 `fastapi.status`,僅為了方便你這位開發者,但它其實直接來自 Starlette。 + +/// + +## 標籤 { #tags } + +你可以為「路徑操作」加入標籤,傳入參數 `tags`,其值為由 `str` 組成的 `list`(通常只是一個 `str`): + +{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *} + +這些標籤會被加入到 OpenAPI 結構描述,並由自動化文件介面使用: + + + +### 含 Enum 的標籤 { #tags-with-enums } + +如果你的應用很大,可能會累積數個標籤,你會希望對相關的「路徑操作」始終使用相同的標籤。 + +在這種情況下,可以考慮把標籤存放在 `Enum` 中。 + +**FastAPI** 對此的支援方式與使用普通字串相同: + +{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *} + +## 摘要與描述 { #summary-and-description } + +你可以加入 `summary` 與 `description`: + +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} + +## 從 docstring 取得描述 { #description-from-docstring } + +由於描述常常較長、跨越多行,你可以在函式的 文件字串(docstring) 中宣告「路徑操作」的描述,**FastAPI** 會從那裡讀取。 + +你可以在 docstring 中書寫 Markdown,它會被正確解析並顯示(會考慮 docstring 的縮排)。 + +{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *} + +這會用於互動式文件: + + + +## 回應描述 { #response-description } + +你可以用參數 `response_description` 指定回應的描述: + +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} + +/// info | 資訊 + +請注意,`response_description` 專指回應,而 `description` 則是針對整個「路徑操作」的一般描述。 + +/// + +/// check | 檢查 + +OpenAPI 規範要求每個「路徑操作」都必須有一個回應描述。 + +因此,如果你未提供,**FastAPI** 會自動產生 "Successful response"。 + +/// + + + +## 將「路徑操作」標記為已棄用 { #deprecate-a-path-operation } + +若需要將「路徑操作」標記為 已棄用,但不移除它,請傳入參數 `deprecated`: + +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} + +在互動式文件中,它會被清楚標示為已棄用: + + + +比較已棄用與未棄用的「路徑操作」在文件中的呈現: + + + +## 總結 { #recap } + +你可以透過將參數傳給「路徑操作裝飾器」,輕鬆地設定並為你的「路徑操作」加入中繼資料。 diff --git a/docs/zh-hant/docs/tutorial/path-params-numeric-validations.md b/docs/zh-hant/docs/tutorial/path-params-numeric-validations.md new file mode 100644 index 000000000..a07f825b0 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/path-params-numeric-validations.md @@ -0,0 +1,154 @@ +# 路徑參數與數值驗證 { #path-parameters-and-numeric-validations } + +就像使用 `Query` 為查詢參數宣告更多驗證與中繼資料一樣,你也可以用 `Path` 為路徑參數宣告相同類型的驗證與中繼資料。 + +## 匯入 `Path` { #import-path } + +首先,從 `fastapi` 匯入 `Path`,並匯入 `Annotated`: + +{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[1,3] *} + +/// info + +FastAPI 在 0.95.0 版加入並開始推薦使用 `Annotated`。 + +如果你使用更舊的版本,嘗試使用 `Annotated` 會出錯。 + +請確保在使用 `Annotated` 前,先[升級 FastAPI 版本](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank}到至少 0.95.1。 + +/// + +## 宣告中繼資料 { #declare-metadata } + +你可以宣告與 `Query` 相同的所有參數。 + +例如,若要為路徑參數 `item_id` 宣告 `title` 中繼資料,可以這樣寫: + +{* ../../docs_src/path_params_numeric_validations/tutorial001_an_py310.py hl[10] *} + +/// note + +路徑參數一定是必填,因為它必須是路徑的一部分。即使你將其宣告為 `None` 或設定預設值,也不會有任何影響,它仍然是必填。 + +/// + +## 依需求調整參數順序 { #order-the-parameters-as-you-need } + +/// tip + +如果你使用 `Annotated`,這點大概沒那麼重要或必要。 + +/// + +假設你想把查詢參數 `q` 宣告為必要的 `str`。 + +而你不需要為該參數宣告其他內容,所以其實不需要用 `Query`。 + +但你仍需要為路徑參數 `item_id` 使用 `Path`,而且基於某些理由你不想用 `Annotated`。 + +如果你把有「預設值」的參數放在沒有「預設值」的參數之前,Python 會抱怨。 + +但你可以調整它們的順序,先放沒有預設值(查詢參數 `q`)的參數。 + +對 **FastAPI** 來說沒差。它會依參數名稱、型別與預設宣告(`Query`、`Path` 等)來辨識參數,並不在意順序。 + +因此,你可以這樣宣告你的函式: + +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} + +但請記住,若使用 `Annotated`,你就不會有這個問題,因為你不是用函式參數預設值來放 `Query()` 或 `Path()`。 + +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} + +## 依需求調整參數順序的技巧 { #order-the-parameters-as-you-need-tricks } + +/// tip + +如果你使用 `Annotated`,這點大概沒那麼重要或必要。 + +/// + +這裡有個小技巧偶爾很好用,不過你大概不常需要。 + +如果你想要: + +* 不用 `Query`、也不給預設值就宣告查詢參數 `q` +* 使用 `Path` 宣告路徑參數 `item_id` +* 讓它們的順序不同 +* 不使用 `Annotated` + +…Python 有個小語法可以做到。 + +在函式的參數列表最前面放一個 `*`。 + +Python 不會對這個 `*` 做任何事,但它會知道後續的所有參數都必須以關鍵字引數(key-value pairs)方式呼叫,也就是所謂的 kwargs。即便它們沒有預設值也一樣。 + +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} + +### 使用 `Annotated` 更好 { #better-with-annotated } + +記住,如果你使用 `Annotated`,因為不是用函式參數預設值,所以你不會遇到這個問題,也可能不需要使用 `*`。 + +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} + +## 數值驗證:大於或等於 { #number-validations-greater-than-or-equal } + +使用 `Query` 和 `Path`(以及你之後會看到的其他類別)可以宣告數值限制。 + +這裡用 `ge=1`,代表 `item_id` 必須是「大於(`g`reater)或等於(`e`qual)」`1` 的整數。 + +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} + +## 數值驗證:大於與小於或等於 { #number-validations-greater-than-and-less-than-or-equal } + +同樣也適用於: + +* `gt`:大於(`g`reater `t`han) +* `le`:小於或等於(`l`ess than or `e`qual) + +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} + +## 數值驗證:浮點數、大於與小於 { #number-validations-floats-greater-than-and-less-than } + +數值驗證也適用於 `float`。 + +這時就能看出能宣告 gt(不只是 ge)的重要性。因為你可以要求數值必須大於 `0`,即便它小於 `1`。 + +所以,`0.5` 是有效的值,但 `0.0` 或 `0` 則無效。 + +lt 也是同樣的道理。 + +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} + +## 小結 { #recap } + +使用 `Query`、`Path`(以及你尚未看到的其他類別)時,你可以像在[查詢參數與字串驗證](query-params-str-validations.md){.internal-link target=_blank}中一樣,宣告中繼資料與字串驗證。 + +你也可以宣告數值驗證: + +* `gt`:大於(`g`reater `t`han) +* `ge`:大於或等於(`g`reater than or `e`qual) +* `lt`:小於(`l`ess `t`han) +* `le`:小於或等於(`l`ess than or `e`qual) + +/// info + +你之後會看到的 `Query`、`Path` 與其他類別,都是共同父類別 `Param` 的子類別。 + +它們共享你已經看到的那些用於額外驗證與中繼資料的參數。 + +/// + +/// note | 技術細節 + +當你從 `fastapi` 匯入 `Query`、`Path` 等時,它們其實是函式。 + +呼叫它們時,會回傳同名類別的實例。 + +也就是說,你匯入的是名為 `Query` 的函式,而當你呼叫它時,它會回傳同名的 `Query` 類別實例。 + +這些函式之所以存在(而不是直接使用類別),是為了避免編輯器標記它們的型別錯誤。 + +如此一來,你就能使用一般的編輯器與開發工具,而不需要額外設定來忽略那些錯誤。 + +/// diff --git a/docs/zh-hant/docs/tutorial/path-params.md b/docs/zh-hant/docs/tutorial/path-params.md new file mode 100644 index 000000000..373f430cd --- /dev/null +++ b/docs/zh-hant/docs/tutorial/path-params.md @@ -0,0 +1,251 @@ +# 路徑參數 { #path-parameters } + +你可以用與 Python 格式化字串相同的語法來宣告路徑「參數」或「變數」: + +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} + +路徑參數 `item_id` 的值會作為引數 `item_id` 傳入你的函式。 + +所以,如果你執行這個範例並前往 http://127.0.0.1:8000/items/foo,你會看到這樣的回應: + +```JSON +{"item_id":"foo"} +``` + +## 具型別的路徑參數 { #path-parameters-with-types } + +你可以在函式中使用標準的 Python 型別註記為路徑參數宣告型別: + +{* ../../docs_src/path_params/tutorial002_py310.py hl[7] *} + +在這個例子裡,`item_id` 被宣告為 `int`。 + +/// check + +這會在你的函式中提供編輯器支援,包括錯誤檢查、自動完成等。 + +/// + +## 資料 轉換 { #data-conversion } + +如果你執行這個範例並在瀏覽器開啟 http://127.0.0.1:8000/items/3,你會看到這樣的回應: + +```JSON +{"item_id":3} +``` + +/// check + +注意你的函式接收(並回傳)的值是 `3`,也就是 Python 的 `int`,而不是字串 `"3"`。 + +因此,有了這個型別宣告,**FastAPI** 會自動為你處理請求的 「解析」。 + +/// + +## 資料驗證 { #data-validation } + +但如果你在瀏覽器前往 http://127.0.0.1:8000/items/foo,你會看到漂亮的 HTTP 錯誤: + +```JSON +{ + "detail": [ + { + "type": "int_parsing", + "loc": [ + "path", + "item_id" + ], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "foo" + } + ] +} +``` + +因為路徑參數 `item_id` 的值是 `"foo"`,它不是 `int`。 + +同樣的錯誤也會在你提供 `float` 而不是 `int` 時出現,例如:http://127.0.0.1:8000/items/4.2 + +/// check + +因此,搭配相同的 Python 型別宣告,**FastAPI** 會為你進行資料驗證。 + +注意錯誤也清楚指出驗證未通過的確切位置。 + +這在開發與除錯與你的 API 互動的程式碼時非常有幫助。 + +/// + +## 文件 { #documentation } + +當你在瀏覽器開啟 http://127.0.0.1:8000/docs,你會看到自動產生、可互動的 API 文件,例如: + + + +/// check + +同樣地,只要使用那個 Python 型別宣告,**FastAPI** 就會提供自動、互動式的文件(整合 Swagger UI)。 + +注意路徑參數被宣告為整數。 + +/// + +## 基於標準的優勢與替代文件 { #standards-based-benefits-alternative-documentation } + +而且因為產生的 schema 來自 OpenAPI 標準,有很多相容的工具可用。 + +因此,**FastAPI** 本身也提供另一種 API 文件(使用 ReDoc),你可以在 http://127.0.0.1:8000/redoc 存取: + + + +同樣地,也有許多相容工具可用,包括支援多種語言的程式碼產生工具。 + +## Pydantic { #pydantic } + +所有資料驗證都由 Pydantic 在底層處理,因此你能直接受惠。而且你可以放心使用。 + +你可以用相同的型別宣告搭配 `str`、`float`、`bool` 與許多更複雜的資料型別。 + +這些之中的好幾個會在接下來的教學章節中介紹。 + +## 順序很重要 { #order-matters } + +在建立「路徑操作」時,你可能會遇到有固定路徑的情況。 + +像是 `/users/me`,假設它用來取得目前使用者的資料。 + +然後你也可能有一個路徑 `/users/{user_id}` 用來依使用者 ID 取得特定使用者的資料。 + +因為「路徑操作」會依宣告順序來比對,你必須確保 `/users/me` 的路徑在 `/users/{user_id}` 之前宣告: + +{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *} + +否則,`/users/{user_id}` 的路徑也會匹配 `/users/me`,並「認為」它收到一個值為 `"me"` 的 `user_id` 參數。 + +同樣地,你不能重新定義同一路徑操作: + +{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *} + +因為第一個會被優先使用(路徑先匹配到)。 + +## 預先定義的值 { #predefined-values } + +如果你有一個接收「路徑參數」的「路徑操作」,但你希望可用的「路徑參數」值是預先定義好的,你可以使用標準的 Python `Enum`。 + +### 建立 `Enum` 類別 { #create-an-enum-class } + +匯入 `Enum` 並建立一個同時繼承自 `str` 與 `Enum` 的子類別。 + +繼承自 `str` 之後,API 文件就能知道這些值的型別必須是 `string`,並能正確地呈現。 + +然後建立具有固定值的類別屬性,這些就是可用的有效值: + +{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *} + +/// tip + +如果你在想,「AlexNet」、「ResNet」和「LeNet」只是一些機器學習 模型 的名字。 + +/// + +### 宣告一個「路徑參數」 { #declare-a-path-parameter } + +接著使用你建立的列舉類別(`ModelName`)作為型別註記,建立一個「路徑參數」: + +{* ../../docs_src/path_params/tutorial005_py310.py hl[16] *} + +### 查看文件 { #check-the-docs } + +因為「路徑參數」的可用值是預先定義的,互動式文件可以很漂亮地顯示它們: + + + +### 使用 Python「列舉」 { #working-with-python-enumerations } + +「路徑參數」的值會是一個「列舉成員」。 + +#### 比較「列舉成員」 { #compare-enumeration-members } + +你可以把它與你建立的列舉 `ModelName` 中的「列舉成員」比較: + +{* ../../docs_src/path_params/tutorial005_py310.py hl[17] *} + +#### 取得「列舉值」 { #get-the-enumeration-value } + +你可以用 `model_name.value` 取得實際的值(在這個例子中是 `str`),一般而言就是 `your_enum_member.value`: + +{* ../../docs_src/path_params/tutorial005_py310.py hl[20] *} + +/// tip + +你也可以用 `ModelName.lenet.value` 取得值 `"lenet"`。 + +/// + +#### 回傳「列舉成員」 { #return-enumeration-members } + +你可以從「路徑操作」回傳「列舉成員」,即使是巢狀在 JSON 主體(例如 `dict`)裡。 + +在回傳給用戶端之前,它們會被轉成對應的值(此例為字串): + +{* ../../docs_src/path_params/tutorial005_py310.py hl[18,21,23] *} + +你的用戶端會收到像這樣的 JSON 回應: + +```JSON +{ + "model_name": "alexnet", + "message": "Deep Learning FTW!" +} +``` + +## 包含路徑的路徑參數 { #path-parameters-containing-paths } + +假設你有一個路徑為 `/files/{file_path}` 的「路徑操作」。 + +但你需要 `file_path` 本身就包含一個「路徑」,像是 `home/johndoe/myfile.txt`。 + +所以,該檔案的 URL 會是:`/files/home/johndoe/myfile.txt`。 + +### OpenAPI 支援 { #openapi-support } + +OpenAPI 並不支援直接宣告一個「路徑參數」內再包含一個「路徑」,因為那會導致難以測試與定義的情況。 + +然而,你仍可在 **FastAPI** 中這樣做,方法是使用 Starlette 的其中一個內部工具。 + +而文件依然能運作,只是它不會額外說明該參數應該包含一個路徑。 + +### 路徑轉換器 { #path-convertor } + +使用 Starlette 提供的選項,你可以用像這樣的 URL 來宣告一個包含「路徑」的「路徑參數」: + +``` +/files/{file_path:path} +``` + +在這個例子裡,參數名稱是 `file_path`,而最後面的 `:path` 表示該參數應該匹配任意「路徑」。 + +所以你可以這樣使用它: + +{* ../../docs_src/path_params/tutorial004_py310.py hl[6] *} + +/// tip + +你可能需要這個參數包含 `/home/johndoe/myfile.txt`,也就是前面帶有斜線(`/`)。 + +在那種情況下,URL 會是:`/files//home/johndoe/myfile.txt`,在 `files` 與 `home` 之間有兩個斜線(`//`)。 + +/// + +## 回顧 { #recap } + +使用 **FastAPI**,只要撰寫簡短、直覺且標準的 Python 型別宣告,你就能獲得: + +* 編輯器支援:錯誤檢查、自動完成等 +* 資料 "解析" +* 資料驗證 +* API 註解與自動產生文件 + +而且你只要宣告一次就好。 + +這大概是 **FastAPI** 相較於其他框架最明顯的優勢之一(除了原始效能之外)。 diff --git a/docs/zh-hant/docs/tutorial/query-params-str-validations.md b/docs/zh-hant/docs/tutorial/query-params-str-validations.md new file mode 100644 index 000000000..ea84234c0 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/query-params-str-validations.md @@ -0,0 +1,449 @@ +# 查詢參數與字串驗證 { #query-parameters-and-string-validations } + +FastAPI 允許你為參數宣告額外的資訊與驗證。 + +以下面這個應用為例: + +{* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} + +查詢參數 `q` 的型別是 `str | None`,表示它是 `str` 但也可以是 `None`,而且預設值就是 `None`,因此 FastAPI 會知道它不是必填。 + +/// note | 注意 + +FastAPI 會因為預設值是 `= None` 而知道 `q` 不是必填。 + +使用 `str | None` 也能讓你的編輯器提供更好的支援並偵測錯誤。 + +/// + +## 額外驗證 { #additional-validation } + +我們要強制:即使 `q` 是可選,只要提供了,長度就不能超過 50 個字元。 + +### 匯入 `Query` 與 `Annotated` { #import-query-and-annotated } + +要達成這點,先匯入: + +- 從 `fastapi` 匯入 `Query` +- 從 `typing` 匯入 `Annotated` + +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *} + +/// info | 說明 + +FastAPI 自 0.95.0 版起加入並開始推薦使用 `Annotated`。 + +如果你的版本較舊,嘗試使用 `Annotated` 會出錯。 + +請先至少 [升級 FastAPI 版本](../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} 到 0.95.1 再使用 `Annotated`。 + +/// + +## 在 `q` 參數的型別中使用 `Annotated` { #use-annotated-in-the-type-for-the-q-parameter } + +還記得先前在 [Python 型別介紹](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank} 提到可以用 `Annotated` 為參數加入中繼資料嗎? + +現在就用在 FastAPI 上吧。🚀 + +我們原本的型別註記是: + +```Python +q: str | None = None +``` + +接著把它用 `Annotated` 包起來,變成: + +```Python +q: Annotated[str | None] = None +``` + +這兩種寫法代表的意思相同:`q` 可以是 `str` 或 `None`,且預設是 `None`。 + +現在來做有趣的部分。🎉 + +## 在 `q` 參數的 `Annotated` 中加入 `Query` { #add-query-to-annotated-in-the-q-parameter } + +既然我們有可以放更多資訊的 `Annotated`(在此是額外驗證),就把 `Query` 放進 `Annotated`,並把參數 `max_length` 設為 `50`: + +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[9] *} + +注意預設值仍然是 `None`,所以這個參數仍是可選。 + +不過,現在在 `Annotated` 裡有 `Query(max_length=50)`,我們就告訴 FastAPI 要對這個值做「額外驗證」,最多 50 個字元即可。😎 + +/// tip | 提示 + +這裡用的是 `Query()`,因為這是「查詢參數」。稍後你會看到 `Path()`、`Body()`、`Header()`、`Cookie()` 等,它們也接受與 `Query()` 相同的參數。 + +/// + +FastAPI 現在會: + +- 驗證資料,確保長度最多 50 個字元 +- 當資料不合法時,回給用戶端清楚的錯誤 +- 在 OpenAPI 的路徑操作中文件化該參數(因此會出現在自動文件 UI) + +## 替代方式(舊):將 `Query` 作為預設值 { #alternative-old-query-as-the-default-value } + +舊版 FastAPI(0.95.0 以前)需要把 `Query` 當成參數的預設值,而不是放在 `Annotated` 裡。你很可能會在各處看到這種寫法,所以我也會說明一下。 + +/// tip | 提示 + +新程式碼且在可能的情況下,請依上面說明使用 `Annotated`。它有多項優點(如下所述),而沒有缺點。🍰 + +/// + +這是把 `Query()` 作為函式參數預設值的寫法,並把參數 `max_length` 設為 50: + +{* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *} + +在這種情況下(未使用 `Annotated`),我們必須用 `Query()` 取代函式中的預設值 `None`,因此需要用 `Query(default=None)` 來設定預設值;對 FastAPI 而言,這達到相同目的。 + +所以: + +```Python +q: str | None = Query(default=None) +``` + +…會讓參數變為可選、預設值是 `None`,等同於: + +```Python +q: str | None = None +``` + +但用 `Query` 的版本會明確宣告它是查詢參數。 + +接著,我們可以傳更多參數給 `Query`。此例中是用於字串的 `max_length` 參數: + +```Python +q: str | None = Query(default=None, max_length=50) +``` + +這一樣會驗證資料、在資料不合法時顯示清楚錯誤,並在 OpenAPI 的路徑操作中文件化該參數。 + +### 將 `Query` 作為預設值或放在 `Annotated` 中 { #query-as-the-default-value-or-in-annotated } + +注意,把 `Query` 放在 `Annotated` 內時,不能使用 `Query` 的 `default` 參數。 + +請改用函式參數的實際預設值。否則會不一致。 + +例如,這是不允許的: + +```Python +q: Annotated[str, Query(default="rick")] = "morty" +``` + +…因為不清楚預設值到底該是 `"rick"` 還是 `"morty"`。 + +因此,你可以(且更推薦)這樣寫: + +```Python +q: Annotated[str, Query()] = "rick" +``` + +…或在較舊的程式碼中你會看到: + +```Python +q: str = Query(default="rick") +``` + +### `Annotated` 的優點 { #advantages-of-annotated } + +建議使用 `Annotated`,而不是在函式參數上使用(舊式的)預設值寫法,理由很多,且更好。🤓 + +函式參數的「預設值」就是「實際的預設值」,這在 Python 的直覺上更一致。😌 + +你也可以在沒有 FastAPI 的其他地方「直接呼叫」同一個函式,而且能「如預期」運作。若有「必填」參數(沒有預設值),你的「編輯器」會提示錯誤,「Python」在執行時也會抱怨你未傳遞必填參數。 + +若不使用 `Annotated`、改用「(舊式)預設值」寫法,你在沒有 FastAPI 的「其他地方」呼叫該函式時,就得「記得」傳入正確參數,否則值會和預期不同(例如會得到 `QueryInfo` 或類似的東西,而不是 `str`)。你的編輯器不會提示,Python 執行該函式時也不會抱怨,只有在內部操作失敗時才會出錯。 + +因為 `Annotated` 可以有多個中繼資料註解,你甚至可以用同一個函式配合其他工具,例如 Typer。🚀 + +## 加入更多驗證 { #add-more-validations } + +你也可以加入 `min_length` 參數: + +{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *} + +## 加入正規表示式 { #add-regular-expressions } + +你可以定義參數必須符合的 regular expression `pattern`: + +{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} + +這個特定的正規表示式樣式會檢查收到的參數值是否: + +- `^`:以後續的字元開頭,前面不能有其他字元。 +- `fixedquery`:必須正好等於 `fixedquery`。 +- `$`:在此結束,`fixedquery` 後面不能再有其他字元。 + +如果你對「正規表示式」感到困惑,別擔心。這對很多人來說都不容易。你仍然可以先不使用正規表示式就完成很多事情。 + +現在你知道,當你需要它們時,可以在 FastAPI 中使用它們。 + +## 預設值 { #default-values } + +當然,你可以使用 `None` 以外的預設值。 + +假設你想宣告查詢參數 `q` 的 `min_length` 是 `3`,且預設值是 `"fixedquery"`: + +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} + +/// note | 注意 + +只要有任何型別的預設值(包含 `None`),參數就是可選(非必填)。 + +/// + +## 必填參數 { #required-parameters } + +當我們不需要宣告更多的驗證或中繼資料時,只要不提供預設值,就能讓查詢參數 `q` 成為必填,例如: + +```Python +q: str +``` + +而不是: + +```Python +q: str | None = None +``` + +但現在我們要搭配 `Query` 來宣告,例如: + +```Python +q: Annotated[str | None, Query(min_length=3)] = None +``` + +因此,在使用 `Query` 時若要宣告值為必填,只要不要宣告預設值即可: + +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} + +### 必填,但可為 `None` { #required-can-be-none } + +你可以宣告參數可以接受 `None`,但仍然是必填。這會強制用戶端一定要送出一個值,即使該值是 `None`。 + +要做到這點,你可以宣告 `None` 是合法型別,但不要宣告預設值: + +{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} + +## 查詢參數清單/多個值 { #query-parameter-list-multiple-values } + +當你用 `Query` 明確定義查詢參數時,也可以讓它接收一個值的清單;換句話說,就是「多個值」。 + +例如,要宣告查詢參數 `q` 可以在 URL 中出現多次,你可以這樣寫: + +{* ../../docs_src/query_params_str_validations/tutorial011_an_py310.py hl[9] *} + +若使用這樣的 URL: + +``` +http://localhost:8000/items/?q=foo&q=bar +``` + +你會在路徑操作函式的參數 `q` 中,收到多個 `q` 查詢參數的值(`foo` 與 `bar`),以 Python 的 `list` 形式。 + +因此,對該 URL 的回應會是: + +```JSON +{ + "q": [ + "foo", + "bar" + ] +} +``` + +/// tip | 提示 + +要宣告型別為 `list` 的查詢參數(如上例),需要明確使用 `Query`,否則它會被解讀為請求本文。 + +/// + +互動式 API 文件也會相應更新,以便支援多個值: + + + +### 查詢參數清單/多個值的預設值 { #query-parameter-list-multiple-values-with-defaults } + +也可以在未提供任何值時,定義 `list` 型別的預設值: + +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} + +如果你前往: + +``` +http://localhost:8000/items/ +``` + +`q` 的預設值會是:`["foo", "bar"]`,而回應會是: + +```JSON +{ + "q": [ + "foo", + "bar" + ] +} +``` + +#### 只使用 `list` { #using-just-list } + +你也可以直接使用 `list`,而不是 `list[str]`: + +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} + +/// note | 注意 + +注意,在這種情況下,FastAPI 不會檢查清單的內容。 + +例如,`list[int]` 會檢查(並文件化)清單內容為整數;但單獨使用 `list` 則不會。 + +/// + +## 宣告更多中繼資料 { #declare-more-metadata } + +你可以為參數加入更多資訊。 + +這些資訊會被包含在產生的 OpenAPI 中,供文件 UI 與外部工具使用。 + +/// note | 注意 + +請留意,不同工具對 OpenAPI 的支援程度可能不同。 + +有些工具可能暫時還不會顯示所有額外資訊;不過多半這些缺漏功能已在開發規劃中。 + +/// + +你可以加入 `title`: + +{* ../../docs_src/query_params_str_validations/tutorial007_an_py310.py hl[10] *} + +以及 `description`: + +{* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *} + +## 別名參數 { #alias-parameters } + +想像你希望參數名稱是 `item-query`。 + +像這樣: + +``` +http://127.0.0.1:8000/items/?item-query=foobaritems +``` + +但 `item-query` 不是合法的 Python 變數名稱。 + +最接近的大概是 `item_query`。 + +但你仍然需要它就是 `item-query`... + +那你可以宣告一個 `alias`,實際上就會用這個別名來讀取參數值: + +{* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *} + +## 棄用參數 { #deprecating-parameters } + +現在假設你不再喜歡這個參數了。 + +你必須暫時保留它,因為還有用戶端在用,但你希望文件能清楚標示它是已棄用。 + +接著把參數 `deprecated=True` 傳給 `Query`: + +{* ../../docs_src/query_params_str_validations/tutorial010_an_py310.py hl[19] *} + +文件會這樣顯示: + + + +## 從 OpenAPI 排除參數 { #exclude-parameters-from-openapi } + +若要把某個查詢參數從產生的 OpenAPI(以及自動文件系統)中排除,將 `Query` 的 `include_in_schema` 設為 `False`: + +{* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *} + +## 自訂驗證 { #custom-validation } + +有時你需要做一些上述參數無法處理的「自訂驗證」。 + +這種情況下,你可以使用「自訂驗證函式」,它會在一般驗證之後套用(例如先確認值是 `str` 之後)。 + +你可以在 `Annotated` 中使用 Pydantic 的 `AfterValidator` 來達成。 + +/// tip | 提示 + +Pydantic 也有 `BeforeValidator` 等等。🤓 + +/// + +例如,以下自訂驗證器會檢查項目 ID 是否以 `isbn-` 開頭(ISBN 書籍編號),或以 `imdb-` 開頭(IMDB 電影 URL 的 ID): + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} + +/// info | 說明 + +這需搭配 Pydantic 2 或以上版本。😎 + +/// + +/// tip | 提示 + +如果你需要做任何需要與「外部元件」溝通的驗證(例如資料庫或其他 API),應該改用「FastAPI 依賴」(FastAPI Dependencies),你稍後會學到。 + +這些自訂驗證器適用於只需使用請求中「同一份資料」即可完成的檢查。 + +/// + +### 理解這段程式碼 { #understand-that-code } + +重點就是在 `Annotated` 中使用「`AfterValidator` 搭配函式」。如果你願意,可以略過這一節。🤸 + +--- + +但如果你對這個範例感到好奇且仍有興致,以下是一些額外細節。 + +#### 使用 `value.startswith()` 的字串 { #string-with-value-startswith } + +你注意到了嗎?字串的 `value.startswith()` 可以接收一個 tuple,並逐一檢查 tuple 中的每個值: + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *} + +#### 隨機項目 { #a-random-item } + +透過 `data.items()` 我們會得到一個包含每個字典項目鍵值對 tuple 的 iterable object。 + +我們用 `list(data.items())` 把這個可疊代物件轉成正式的 `list`。 + +接著用 `random.choice()` 從清單中取得一個「隨機值」,也就是一個 `(id, name)` 的 tuple。可能像是 `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`。 + +然後把這個 tuple 的兩個值分別指定給變數 `id` 和 `name`。 + +因此,即使使用者沒有提供 item ID,仍然會收到一個隨機建議。 + +……而這全部只用一行簡單的程式碼完成。🤯 你不愛 Python 嗎?🐍 + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} + +## 重點回顧 { #recap } + +你可以為參數宣告額外的驗證與中繼資料。 + +通用的驗證與中繼資料: + +- `alias` +- `title` +- `description` +- `deprecated` + +字串專用的驗證: + +- `min_length` +- `max_length` +- `pattern` + +使用 `AfterValidator` 的自訂驗證。 + +在這些範例中,你看到了如何為 `str` 值宣告驗證。 + +接下來的章節會示範如何為其他型別(像是數字)宣告驗證。 diff --git a/docs/zh-hant/docs/tutorial/query-params.md b/docs/zh-hant/docs/tutorial/query-params.md new file mode 100644 index 000000000..f21bf4050 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/query-params.md @@ -0,0 +1,187 @@ +# 查詢參數 { #query-parameters } + +當你宣告不是路徑參數的其他函式參數時,會自動被視為「查詢(query)」參數。 + +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} + +查詢是出現在 URL 中 `?` 之後的一組鍵值對,以 `&` 字元分隔。 + +例如,URL: + +``` +http://127.0.0.1:8000/items/?skip=0&limit=10 +``` + +...查詢參數為: + +* `skip`:值為 `0` +* `limit`:值為 `10` + +因為它們是 URL 的一部分,天生是字串。 + +但當你以 Python 型別宣告它們(如上例中的 `int`),它們會被轉換成該型別並據此驗證。 + +對於查詢參數,會套用與路徑參數相同的處理流程: + +* 編輯器支援(當然) +* 資料 「解析」 +* 資料驗證 +* 自動文件 + +## 預設值 { #defaults } + +由於查詢參數不是路徑的固定部分,因此可以是選填並具有預設值。 + +在上面的例子中,預設值為 `skip=0` 與 `limit=10`。 + +因此,造訪下列 URL: + +``` +http://127.0.0.1:8000/items/ +``` + +等同於造訪: + +``` +http://127.0.0.1:8000/items/?skip=0&limit=10 +``` + +但如果你改為造訪: + +``` +http://127.0.0.1:8000/items/?skip=20 +``` + +函式中的參數值會是: + +* `skip=20`:因為你在 URL 中設定了它 +* `limit=10`:因為那是預設值 + +## 選用參數 { #optional-parameters } + +同樣地,你可以將預設值設為 `None` 來宣告選用的查詢參數: + +{* ../../docs_src/query_params/tutorial002_py310.py hl[7] *} + +在這種情況下,函式參數 `q` 為選用,且預設為 `None`。 + +/// check | 注意 + +另外請注意,FastAPI 能辨識出路徑參數 `item_id` 是路徑參數,而 `q` 不是,因此 `q` 會被當作查詢參數。 + +/// + +## 查詢參數型別轉換 { #query-parameter-type-conversion } + +你也可以宣告 `bool` 型別,值會被自動轉換: + +{* ../../docs_src/query_params/tutorial003_py310.py hl[7] *} + +在這種情況下,如果你造訪: + +``` +http://127.0.0.1:8000/items/foo?short=1 +``` + +或 + +``` +http://127.0.0.1:8000/items/foo?short=True +``` + +或 + +``` +http://127.0.0.1:8000/items/foo?short=true +``` + +或 + +``` +http://127.0.0.1:8000/items/foo?short=on +``` + +或 + +``` +http://127.0.0.1:8000/items/foo?short=yes +``` + +或任何其他大小寫變化(全大寫、首字母大寫等),你的函式會將參數 `short` 視為 `bool` 值 `True`。否則為 `False`。 + +## 多個路徑與查詢參數 { #multiple-path-and-query-parameters } + +你可以同時宣告多個路徑參數與查詢參數,FastAPI 會自動分辨。 + +而且不必按特定順序宣告。 + +會依名稱辨識: + +{* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *} + +## 必填查詢參數 { #required-query-parameters } + +當你為非路徑參數(目前我們只看到查詢參數)宣告了預設值時,它就不是必填。 + +若你不想提供特定預設值、只想讓它為選填,將預設值設為 `None`。 + +但若你要讓查詢參數成為必填,只要不要宣告任何預設值: + +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} + +此處查詢參數 `needy` 是必填的 `str`。 + +如果你在瀏覽器中開啟如下的 URL: + +``` +http://127.0.0.1:8000/items/foo-item +``` + +...沒有加上必填的 `needy` 參數,你會看到類似這樣的錯誤: + +```JSON +{ + "detail": [ + { + "type": "missing", + "loc": [ + "query", + "needy" + ], + "msg": "Field required", + "input": null + } + ] +} +``` + +由於 `needy` 是必填參數,你需要在 URL 中設定它: + +``` +http://127.0.0.1:8000/items/foo-item?needy=sooooneedy +``` + +...這樣就會成功: + +```JSON +{ + "item_id": "foo-item", + "needy": "sooooneedy" +} +``` + +當然,你可以同時定義部分參數為必填、部分有預設值、部分為選填: + +{* ../../docs_src/query_params/tutorial006_py310.py hl[8] *} + +在此例中,有 3 個查詢參數: + +* `needy`,必填的 `str`。 +* `skip`,具有預設值 `0` 的 `int`。 +* `limit`,選填的 `int`。 + +/// tip | 提示 + +你也可以像在[路徑參數](path-params.md#predefined-values){.internal-link target=_blank}中一樣使用 `Enum`。 + +/// diff --git a/docs/zh-hant/docs/tutorial/request-files.md b/docs/zh-hant/docs/tutorial/request-files.md new file mode 100644 index 000000000..c8606a3f2 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/request-files.md @@ -0,0 +1,176 @@ +# 請求中的檔案 { #request-files } + +你可以使用 `File` 定義由用戶端上傳的檔案。 + +/// info + +若要接收上傳的檔案,請先安裝 `python-multipart`。 + +請先建立並啟用一個[虛擬環境](../virtual-environments.md){.internal-link target=_blank},然後安裝,例如: + +```console +$ pip install python-multipart +``` + +因為上傳的檔案是以「表單資料」送出的。 + +/// + +## 匯入 `File` { #import-file } + +從 `fastapi` 匯入 `File` 與 `UploadFile`: + +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} + +## 定義 `File` 參數 { #define-file-parameters } + +和 `Body` 或 `Form` 一樣的方式建立檔案參數: + +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} + +/// info + +`File` 是直接繼承自 `Form` 的類別。 + +但請記住,當你從 `fastapi` 匯入 `Query`、`Path`、`File` 等時,它們其實是回傳特殊類別的函式。 + +/// + +/// tip + +要宣告檔案本文,必須使用 `File`,否則參數會被解讀為查詢參數或本文(JSON)參數。 + +/// + +檔案會以「表單資料」上傳。 + +如果你將路徑操作函式(path operation function)的參數型別宣告為 `bytes`,**FastAPI** 會替你讀取檔案,你會以 `bytes` 取得內容。 + +請注意,這表示整個內容會存放在記憶體中,適合小檔案。 + +但在許多情況下,使用 `UploadFile` 會更好。 + +## 使用 `UploadFile` 的檔案參數 { #file-parameters-with-uploadfile } + +將檔案參數型別設為 `UploadFile`: + +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} + +使用 `UploadFile` 相較於 `bytes` 有數個優點: + +* 你不必在參數的預設值使用 `File()`。 +* 它使用「spooled」檔案: + * 檔案在記憶體中保存到某個大小上限,超過上限後會存到磁碟。 +* 因此適合處理大型檔案(例如圖片、影片、大型二進位檔等),而不會耗盡記憶體。 +* 你可以取得上傳檔案的中繼資料。 +* 它提供一個類檔案的 `async` 介面。 +* 它會提供實際的 Python `SpooledTemporaryFile` 物件,你可以直接傳給需要類檔案物件的其他函式或函式庫。 + +### `UploadFile` { #uploadfile } + +`UploadFile` 具有以下屬性: + +* `filename`:一個 `str`,為上傳的原始檔名(例如 `myimage.jpg`)。 +* `content_type`:一個 `str`,為內容類型(MIME type / media type)(例如 `image/jpeg`)。 +* `file`:一個 `SpooledTemporaryFile`類檔案物件)。這是真正的 Python 檔案物件,你可以直接傳給期待「類檔案」物件的其他函式或函式庫。 + +`UploadFile` 有以下 `async` 方法。它們底層會呼叫對應的檔案方法(使用內部的 `SpooledTemporaryFile`)。 + +* `write(data)`:將 `data`(`str` 或 `bytes`)寫入檔案。 +* `read(size)`:讀取檔案的 `size`(`int`)個位元組/字元。 +* `seek(offset)`:移動到檔案中的位元組位置 `offset`(`int`)。 + * 例如,`await myfile.seek(0)` 會移到檔案開頭。 + * 當你已經執行過 `await myfile.read()`,之後需要再次讀取內容時特別有用。 +* `close()`:關閉檔案。 + +由於這些都是 `async` 方法,你需要以 await 呼叫它們。 + +例如,在 `async` 的路徑操作函式中可這樣讀取內容: + +```Python +contents = await myfile.read() +``` + +若是在一般的 `def` 路徑操作函式中,你可以直接存取 `UploadFile.file`,例如: + +```Python +contents = myfile.file.read() +``` + +/// note | `async` 技術細節 + +當你使用這些 `async` 方法時,**FastAPI** 會在執行緒池中執行對應的檔案方法並等待結果。 + +/// + +/// note | Starlette 技術細節 + +**FastAPI** 的 `UploadFile` 直接繼承自 **Starlette** 的 `UploadFile`,但新增了一些必要部分,使其與 **Pydantic** 及 FastAPI 其他部分相容。 + +/// + +## 什麼是「表單資料」 { #what-is-form-data } + +HTML 表單(`
`)送到伺服器的資料通常使用一種「特殊」編碼,與 JSON 不同。 + +**FastAPI** 會從正確的位置讀取該資料,而不是當作 JSON。 + +/// note | 技術細節 + +表單資料在不包含檔案時,通常使用媒體型別 `application/x-www-form-urlencoded` 編碼。 + +但當表單包含檔案時,會使用 `multipart/form-data` 編碼。若你使用 `File`,**FastAPI** 會知道要從請求本文的正確部分取得檔案。 + +若想進一步了解這些編碼與表單欄位,請參考 MDN Web Docs 的 POST。 + +/// + +/// warning + +你可以在一個路徑操作中宣告多個 `File` 與 `Form` 參數,但不能同時宣告預期以 JSON 接收的 `Body` 欄位,因為此請求的本文會使用 `multipart/form-data` 而不是 `application/json`。 + +這不是 **FastAPI** 的限制,而是 HTTP 協定本身的規範。 + +/// + +## 可選的檔案上傳 { #optional-file-upload } + +可透過一般型別註解並將預設值設為 `None` 使檔案成為可選: + +{* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} + +## `UploadFile` 搭配額外中繼資料 { #uploadfile-with-additional-metadata } + +你也可以在 `UploadFile` 上搭配 `File()`,例如用來設定額外的中繼資料: + +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} + +## 多檔案上傳 { #multiple-file-uploads } + +可以同時上傳多個檔案。 + +它們會同屬於以「表單資料」送出的同一個表單欄位。 + +要這麼做,將型別宣告為 `bytes` 或 `UploadFile` 的 `list`: + +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} + +你會如宣告所示,收到由 `bytes` 或 `UploadFile` 組成的 `list`。 + +/// note | 技術細節 + +你也可以使用 `from starlette.responses import HTMLResponse`。 + +**FastAPI** 為了讓你(開發者)更方便,提供與 `starlette.responses` 相同的內容作為 `fastapi.responses`。但大多數可用的回應類型其實直接來自 Starlette。 + +/// + +### 多檔案上傳且包含額外中繼資料 { #multiple-file-uploads-with-additional-metadata } + +同樣地,即使對 `UploadFile`,你也可以用 `File()` 設定額外參數: + +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} + +## 小結 { #recap } + +使用 `File`、`bytes` 與 `UploadFile` 來宣告請求中要上傳的檔案,這些檔案會以表單資料送出。 diff --git a/docs/zh-hant/docs/tutorial/request-form-models.md b/docs/zh-hant/docs/tutorial/request-form-models.md new file mode 100644 index 000000000..8cf4a7c5e --- /dev/null +++ b/docs/zh-hant/docs/tutorial/request-form-models.md @@ -0,0 +1,78 @@ +# 表單模型 { #form-models } + +你可以使用 **Pydantic 模型** 在 FastAPI 中宣告 **表單欄位**。 + +/// info | 說明 + +要使用表單,首先安裝 `python-multipart`。 + +請先建立[虛擬環境](../virtual-environments.md){.internal-link target=_blank}、啟用後再安裝,例如: + +```console +$ pip install python-multipart +``` + +/// + +/// note | 注意 + +此功能自 FastAPI 版本 `0.113.0` 起支援。🤓 + +/// + +## 針對表單的 Pydantic 模型 { #pydantic-models-for-forms } + +你只需要宣告一個 **Pydantic 模型**,包含你要接收為 **表單欄位** 的欄位,然後將參數宣告為 `Form`: + +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} + +**FastAPI** 會從請求中的 **表單資料** 擷取 **各欄位** 的資料,並將這些資料組成你定義的 Pydantic 模型實例。 + +## 檢視文件 { #check-the-docs } + +你可以在 `/docs` 的文件 UI 中驗證: + +
+ +
+ +## 禁止額外的表單欄位 { #forbid-extra-form-fields } + +在某些特殊情況(可能不常見)下,你可能希望僅允許 Pydantic 模型中宣告的表單欄位,並禁止任何額外欄位。 + +/// note | 注意 + +此功能自 FastAPI 版本 `0.114.0` 起支援。🤓 + +/// + +你可以使用 Pydantic 的模型設定來 `forbid` 任何 `extra` 欄位: + +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} + +如果用戶端嘗試傳送額外資料,將會收到錯誤回應。 + +例如,用戶端若送出以下表單欄位: + +* `username`: `Rick` +* `password`: `Portal Gun` +* `extra`: `Mr. Poopybutthole` + +他們會收到一個錯誤回應,告知欄位 `extra` 不被允許: + +```json +{ + "detail": [ + { + "type": "extra_forbidden", + "loc": ["body", "extra"], + "msg": "Extra inputs are not permitted", + "input": "Mr. Poopybutthole" + } + ] +} +``` + +## 摘要 { #summary } + +你可以使用 Pydantic 模型在 FastAPI 中宣告表單欄位。😎 diff --git a/docs/zh-hant/docs/tutorial/request-forms-and-files.md b/docs/zh-hant/docs/tutorial/request-forms-and-files.md new file mode 100644 index 000000000..6dcc1c260 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/request-forms-and-files.md @@ -0,0 +1,41 @@ +# 請求中的表單與檔案 { #request-forms-and-files } + +你可以使用 `File` 與 `Form` 同時定義檔案與表單欄位。 + +/// info + +要接收上傳的檔案與/或表單資料,請先安裝 `python-multipart`。 + +請先建立並啟用一個 [虛擬環境](../virtual-environments.md){.internal-link target=_blank},然後再安裝,例如: + +```console +$ pip install python-multipart +``` + +/// + +## 匯入 `File` 與 `Form` { #import-file-and-form } + +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} + +## 定義 `File` 與 `Form` 參數 { #define-file-and-form-parameters } + +以與 `Body` 或 `Query` 相同的方式建立檔案與表單參數: + +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} + +檔案與表單欄位會作為表單資料上傳,而你將能接收到這些檔案與欄位。 + +你也可以將部分檔案宣告為 `bytes`,另一些宣告為 `UploadFile`。 + +/// warning + +你可以在一個路徑操作 (path operation) 中宣告多個 `File` 與 `Form` 參數,但不能同時再宣告預期以 JSON 接收的 `Body` 欄位,因為該請求的本文會使用 `multipart/form-data` 而非 `application/json` 進行編碼。 + +這不是 **FastAPI** 的限制,這是 HTTP 通訊協定本身的規範。 + +/// + +## 小結 { #recap } + +當你需要在同一個請求中同時接收資料與檔案時,請搭配使用 `File` 與 `Form`。 diff --git a/docs/zh-hant/docs/tutorial/request-forms.md b/docs/zh-hant/docs/tutorial/request-forms.md new file mode 100644 index 000000000..733a3e286 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/request-forms.md @@ -0,0 +1,73 @@ +# 表單資料 { #form-data } + +當你需要接收表單欄位而不是 JSON 時,可以使用 `Form`。 + +/// info + +要使用表單,請先安裝 `python-multipart`。 + +請先建立並啟用一個[虛擬環境](../virtual-environments.md){.internal-link target=_blank},然後再安裝,例如: + +```console +$ pip install python-multipart +``` + +/// + +## 匯入 `Form` { #import-form } + +從 `fastapi` 匯入 `Form`: + +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} + +## 定義 `Form` 參數 { #define-form-parameters } + +以與 `Body` 或 `Query` 相同的方式建立表單參數: + +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} + +例如,在 OAuth2 規範的一種用法(稱為「password flow」)中,必須以表單欄位傳送 `username` 與 `password`。 + +該 規範 要求欄位名稱必須正好是 `username` 和 `password`,而且必須以表單欄位傳送,而不是 JSON。 + +使用 `Form` 時,你可以宣告與 `Body`(以及 `Query`、`Path`、`Cookie`)相同的設定,包括驗證、範例、別名(例如用 `user-name` 取代 `username`)等。 + +/// info + +`Form` 是一個直接繼承自 `Body` 的類別。 + +/// + +/// tip + +要宣告表單的請求本文,你需要明確使用 `Form`,否則這些參數會被解讀為查詢參數或請求本文(JSON)參數。 + +/// + +## 關於「表單欄位」 { #about-form-fields } + +HTML 表單(`
`)向伺服器傳送資料時,通常會使用一種「特殊」的編碼方式,與 JSON 不同。 + +**FastAPI** 會從正確的位置讀取那些資料,而不是從 JSON。 + +/// note | 技術細節 + +表單資料通常會使用「媒體類型」`application/x-www-form-urlencoded` 進行編碼。 + +但當表單包含檔案時,會使用 `multipart/form-data`。你會在下一章閱讀如何處理檔案。 + +若想進一步了解這些編碼與表單欄位,請參考 MDNPOST 網頁文件。 + +/// + +/// warning + +你可以在一個 *路徑操作(path operation)* 中宣告多個 `Form` 參數,但不能同時再宣告期望以 JSON 接收的 `Body` 欄位,因為該請求的本文會使用 `application/x-www-form-urlencoded` 編碼,而不是 `application/json`。 + +這不是 **FastAPI** 的限制,而是 HTTP 協定本身的規定。 + +/// + +## 回顧 { #recap } + +使用 `Form` 來宣告表單資料的輸入參數。 diff --git a/docs/zh-hant/docs/tutorial/response-model.md b/docs/zh-hant/docs/tutorial/response-model.md new file mode 100644 index 000000000..d22402e18 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/response-model.md @@ -0,0 +1,343 @@ +# 回應模型 - 回傳型別 { #response-model-return-type } + +你可以在「路徑操作函式」的回傳型別上加上註解,宣告用於回應的型別。 + +你可以像在函式「參數」的輸入資料那樣使用型別註解,你可以使用 Pydantic 模型、list、dictionary、整數、布林等純量值。 + +{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} + +FastAPI 會使用這個回傳型別來: + +* 驗證回傳的資料。 + * 如果資料無效(例如缺少欄位),代表你的應用程式程式碼有問題,沒有回傳應該回傳的內容,FastAPI 會回傳伺服器錯誤,而不是回傳不正確的資料。如此你和你的用戶端都能確定會收到預期的資料與資料結構。 +* 在 OpenAPI 的「路徑操作」中為回應新增 JSON Schema。 + * 這會被自動文件使用。 + * 也會被自動用戶端程式碼產生工具使用。 + +但更重要的是: + +* 它會將輸出資料限制並過濾為回傳型別中定義的內容。 + * 這對安全性特別重要,下面會再看到更多細節。 + +## `response_model` 參數 { #response-model-parameter } + +有些情況下,你需要或想要回傳的資料與你宣告的型別不完全相同。 + +例如,你可能想要回傳一個 dictionary 或資料庫物件,但把回應宣告為一個 Pydantic 模型。這樣 Pydantic 模型就會替你回傳的物件(例如 dictionary 或資料庫物件)處理所有的資料文件、驗證等。 + +如果你加了回傳型別註解,工具與編輯器會(正確地)抱怨你的函式回傳的型別(例如 dict)與你宣告的(例如 Pydantic 模型)不同。 + +在這些情況下,你可以使用「路徑操作裝飾器」參數 `response_model`,而不是函式的回傳型別。 + +你可以在任何「路徑操作」上使用 `response_model` 參數: + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* 等等。 + +{* ../../docs_src/response_model/tutorial001_py310.py hl[17,22,24:27] *} + +/// note | 注意 + +注意 `response_model` 是「裝飾器」方法(`get`、`post` 等)的參數。不是你的「路徑操作函式」的參數(像其他參數與請求主體那樣)。 + +/// + +`response_model` 接受的型別與你在 Pydantic 模型欄位中宣告的相同,所以它可以是一個 Pydantic 模型,也可以是例如由 Pydantic 模型組成的 `list`,像是 `List[Item]`。 + +FastAPI 會使用這個 `response_model` 來做所有的資料文件、驗證等,並且也會將輸出資料轉換與過濾為其型別宣告。 + +/// tip | 提示 + +如果你在編輯器、mypy 等中有嚴格型別檢查,你可以把函式回傳型別宣告為 `Any`。 + +這樣你是在告訴編輯器你是刻意回傳任意型別。但 FastAPI 仍會用 `response_model` 做資料文件、驗證、過濾等。 + +/// + +### `response_model` 優先權 { #response-model-priority } + +如果同時宣告了回傳型別與 `response_model`,`response_model` 會有優先權並由 FastAPI 使用。 + +如此一來,即便你回傳的實際型別與回應模型不同,你仍可在函式上加上正確的型別註解,供編輯器與如 mypy 的工具使用。同時仍由 FastAPI 使用 `response_model` 做資料驗證、文件化等。 + +你也可以使用 `response_model=None` 來停用該「路徑操作」的回應模型產生;當你為不是有效 Pydantic 欄位的東西加上型別註解時,可能需要這麼做,你會在下方某節看到範例。 + +## 回傳與輸入相同的資料 { #return-the-same-input-data } + +這裡我們宣告一個 `UserIn` 模型,其中會包含明文密碼: + +{* ../../docs_src/response_model/tutorial002_py310.py hl[7,9] *} + +/// info | 說明 + +要使用 `EmailStr`,請先安裝 `email-validator`。 + +請先建立一個[虛擬環境](../virtual-environments.md){.internal-link target=_blank}、啟用它,然後安裝,例如: + +```console +$ pip install email-validator +``` + +或: + +```console +$ pip install "pydantic[email]" +``` + +/// + +而我們使用這個模型同時宣告輸入與輸出: + +{* ../../docs_src/response_model/tutorial002_py310.py hl[16] *} + +現在,當瀏覽器建立一個帶有密碼的使用者時,API 會在回應中回傳相同的密碼。 + +在這個例子中可能不是問題,因為是同一個使用者送出該密碼。 + +但如果我們對其他「路徑操作」使用相同的模型,我們可能會把使用者密碼送給所有用戶端。 + +/// danger | 警告 + +除非你非常清楚所有影響並確定自己在做什麼,否則永遠不要儲存使用者的明文密碼,也不要像這樣在回應中傳送。 + +/// + +## 新增一個輸出模型 { #add-an-output-model } + +我們可以改為建立一個包含明文密碼的輸入模型,以及一個不含密碼的輸出模型: + +{* ../../docs_src/response_model/tutorial003_py310.py hl[9,11,16] *} + +在這裡,雖然「路徑操作函式」回傳的是同一個包含密碼的輸入使用者: + +{* ../../docs_src/response_model/tutorial003_py310.py hl[24] *} + +...我們把 `response_model` 宣告為不包含密碼的 `UserOut` 模型: + +{* ../../docs_src/response_model/tutorial003_py310.py hl[22] *} + +因此,FastAPI 會負責(透過 Pydantic)過濾掉輸出模型中未宣告的所有資料。 + +### `response_model` 或回傳型別 { #response-model-or-return-type } + +在這種情況下,因為兩個模型不同,如果我們把函式回傳型別註解為 `UserOut`,編輯器和工具會抱怨我們回傳了無效的型別,因為它們是不同的類別。 + +這就是為什麼在這個例子中我們必須在 `response_model` 參數中宣告它。 + +...但繼續往下讀看看如何克服這個問題。 + +## 回傳型別與資料過濾 { #return-type-and-data-filtering } + +讓我們延續前一個範例。我們想要用一種型別來註解函式,但實際上希望能夠從函式回傳包含更多資料的內容。 + +我們希望 FastAPI 仍然用回應模型來過濾資料。這樣即使函式回傳更多資料,回應中也只會包含回應模型中宣告的欄位。 + +在前一個例子中,因為類別不同,我們必須使用 `response_model` 參數。但這也代表我們失去了編輯器與工具對函式回傳型別的檢查支援。 + +不過在大多數需要這樣做的情況下,我們只是想要像這個例子一樣,讓模型過濾/移除部分資料。 + +在這些情況下,我們可以利用類別與繼承,搭配函式的型別註解,取得更好的編輯器與工具支援,同時仍能讓 FastAPI 做資料過濾。 + +{* ../../docs_src/response_model/tutorial003_01_py310.py hl[7:10,13:14,18] *} + +這樣我們能得到工具支援,對於編輯器與 mypy 來說,這段程式碼在型別上是正確的,同時我們也能得到 FastAPI 的資料過濾。 + +這是怎麼運作的?來看一下。🤓 + +### 型別註解與工具支援 { #type-annotations-and-tooling } + +先看看編輯器、mypy 與其他工具會怎麼看這件事。 + +`BaseUser` 有基礎欄位。然後 `UserIn` 繼承自 `BaseUser` 並新增 `password` 欄位,因此它會包含兩個模型的所有欄位。 + +我們把函式回傳型別註解為 `BaseUser`,但實際上回傳的是 `UserIn` 實例。 + +編輯器、mypy 與其他工具不會抱怨,因為就型別學而言,`UserIn` 是 `BaseUser` 的子類別,這代表當預期任何 `BaseUser` 時,`UserIn` 是一個有效的型別。 + +### FastAPI 的資料過濾 { #fastapi-data-filtering } + +對 FastAPI 而言,它會查看回傳型別,並確保你回傳的內容只包含該型別中宣告的欄位。 + +FastAPI 在內部會搭配 Pydantic 做一些事情,來確保不會把類別繼承的那些規則直接用在回傳資料的過濾上,否則你可能會回傳比預期更多的資料。 + +如此,你就能同時擁有兩種好處:具備工具支援的型別註解,以及資料過濾。 + +## 在文件中查看 { #see-it-in-the-docs } + +在自動文件中,你可以看到輸入模型與輸出模型各自都有自己的 JSON Schema: + + + +而且兩個模型都會用在互動式 API 文件中: + + + +## 其他回傳型別註解 { #other-return-type-annotations } + +有時你回傳的東西不是有效的 Pydantic 欄位,你仍會在函式上加上註解,只為了獲得工具(編輯器、mypy 等)提供的支援。 + +### 直接回傳 Response { #return-a-response-directly } + +最常見的情況是[直接回傳 Response(在進階文件中稍後會解釋)](../advanced/response-directly.md){.internal-link target=_blank}。 + +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} + +這個簡單情境會由 FastAPI 自動處理,因為回傳型別註解是 `Response` 類別(或其子類別)。 + +而工具也會滿意,因為 `RedirectResponse` 與 `JSONResponse` 都是 `Response` 的子類別,所以型別註解是正確的。 + +### 註解為某個 Response 的子類別 { #annotate-a-response-subclass } + +你也可以在型別註解中使用 `Response` 的子類別: + +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} + +這同樣可行,因為 `RedirectResponse` 是 `Response` 的子類別,而 FastAPI 會自動處理這種簡單情況。 + +### 無效的回傳型別註解 { #invalid-return-type-annotations } + +但當你回傳其他任意物件(例如資料庫物件),它不是有效的 Pydantic 型別,並且你在函式上也這樣註解時,FastAPI 會嘗試從該型別註解建立一個 Pydantic 回應模型,因而失敗。 + +如果你有像是多種型別的聯集,其中一個或多個不是有效的 Pydantic 型別,也會發生相同的事情,例如這個就會失敗 💥: + +{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} + +...這會失敗,因為該型別註解不是 Pydantic 型別,且它也不只是一個單一的 `Response` 類別或其子類別,而是 `Response` 與 `dict` 的聯集(兩者任一)。 + +### 停用回應模型 { #disable-response-model } + +延續上面的例子,你可能不想要 FastAPI 執行預設的資料驗證、文件化、過濾等動作。 + +但你可能仍想在函式上保留回傳型別註解,以獲得編輯器與型別檢查工具(例如 mypy)的支援。 + +這種情況下,你可以設定 `response_model=None` 來停用回應模型的產生: + +{* ../../docs_src/response_model/tutorial003_05_py310.py hl[7] *} + +這會讓 FastAPI 略過回應模型的產生,如此你就能使用任何你需要的回傳型別註解,而不會影響你的 FastAPI 應用程式。🤓 + +## 回應模型編碼參數 { #response-model-encoding-parameters } + +你的回應模型可能有預設值,例如: + +{* ../../docs_src/response_model/tutorial004_py310.py hl[9,11:12] *} + +* `description: Union[str, None] = None`(或在 Python 3.10 中的 `str | None = None`)預設為 `None`。 +* `tax: float = 10.5` 預設為 `10.5`。 +* `tags: List[str] = []` 預設為空的 list:`[]`。 + +但如果這些值其實沒有被儲存,你可能想要在結果中省略它們。 + +例如,如果你在 NoSQL 資料庫中有包含許多選擇性屬性的模型,但你不想傳送充滿預設值的冗長 JSON 回應。 + +### 使用 `response_model_exclude_unset` 參數 { #use-the-response-model-exclude-unset-parameter } + +你可以在「路徑操作裝飾器」上設定 `response_model_exclude_unset=True`: + +{* ../../docs_src/response_model/tutorial004_py310.py hl[22] *} + +如此這些預設值就不會被包含在回應中,只有實際被設定的值才會包含。 + +因此,如果你對該「路徑操作」發送針對 ID 為 `foo` 的項目的請求,回應(不包含預設值)會是: + +```JSON +{ + "name": "Foo", + "price": 50.2 +} +``` + +/// info | 說明 + +你也可以使用: + +* `response_model_exclude_defaults=True` +* `response_model_exclude_none=True` + +如 Pydantic 文件中對 `exclude_defaults` 與 `exclude_none` 的說明。 + +/// + +#### 對於有預設值欄位也有實際值的資料 { #data-with-values-for-fields-with-defaults } + +但如果你的資料在模型中對於有預設值的欄位也有實際值,例如 ID 為 `bar` 的項目: + +```Python hl_lines="3 5" +{ + "name": "Bar", + "description": "The bartenders", + "price": 62, + "tax": 20.2 +} +``` + +它們會被包含在回應中。 + +#### 與預設值相同的資料 { #data-with-the-same-values-as-the-defaults } + +如果資料的值與預設值相同,例如 ID 為 `baz` 的項目: + +```Python hl_lines="3 5-6" +{ + "name": "Baz", + "description": None, + "price": 50.2, + "tax": 10.5, + "tags": [] +} +``` + +FastAPI 足夠聰明(其實是 Pydantic 足夠聰明)去判斷,儘管 `description`、`tax` 與 `tags` 的值與預設值相同,但它們是被明確設定的(而不是取自預設值)。 + +因此,它們會被包含在 JSON 回應中。 + +/// tip | 提示 + +注意預設值可以是任何東西,不只有 `None`。 + +它們可以是一個 list(`[]`)、一個 `float` 的 `10.5`,等等。 + +/// + +### `response_model_include` 與 `response_model_exclude` { #response-model-include-and-response-model-exclude } + +你也可以使用「路徑操作裝飾器」參數 `response_model_include` 與 `response_model_exclude`。 + +它們接受一個由屬性名稱字串所組成的 `set`,分別用來包含(省略其他)或排除(包含其他)屬性。 + +如果你只有一個 Pydantic 模型並且想從輸出移除部分資料,這可以作為一個快速捷徑。 + +/// tip | 提示 + +但仍建議使用上面提到的作法,使用多個類別,而不是這些參數。 + +因為在你的應用程式 OpenAPI(與文件)中所產生的 JSON Schema 仍會是完整模型的,即便你使用 `response_model_include` 或 `response_model_exclude` 省略了一些屬性。 + +`response_model_by_alias` 也有類似的情況。 + +/// + +{* ../../docs_src/response_model/tutorial005_py310.py hl[29,35] *} + +/// tip | 提示 + +語法 `{"name", "description"}` 會建立一個包含這兩個值的 `set`。 + +它等同於 `set(["name", "description"])`。 + +/// + +#### 使用 `list` 來代替 `set` { #using-lists-instead-of-sets } + +如果你忘了使用 `set` 而用了 `list` 或 `tuple`,FastAPI 仍會把它轉換成 `set`,並能正確運作: + +{* ../../docs_src/response_model/tutorial006_py310.py hl[29,35] *} + +## 重點回顧 { #recap } + +使用「路徑操作裝飾器」的 `response_model` 參數來定義回應模型,特別是為了確保私有資料被過濾掉。 + +使用 `response_model_exclude_unset` 僅回傳被明確設定的值。 diff --git a/docs/zh-hant/docs/tutorial/response-status-code.md b/docs/zh-hant/docs/tutorial/response-status-code.md new file mode 100644 index 000000000..cbcc67ca5 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/response-status-code.md @@ -0,0 +1,101 @@ +# 回應狀態碼 { #response-status-code } + +就像你可以指定回應模型一樣,你也可以在任一個「路徑操作(path operation)」的參數 `status_code` 中宣告回應所使用的 HTTP 狀態碼: + +* `@app.get()` +* `@app.post()` +* `@app.put()` +* `@app.delete()` +* 等等 + +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} + +/// note | 注意 + +請注意,`status_code` 是「裝飾器(decorator)」方法(`get`、`post` 等等)的參數,而不是你的「路徑操作函式」的參數,就像所有的參數與 body 一樣。 + +/// + +參數 `status_code` 接受一個數字作為 HTTP 狀態碼。 + +/// info | 資訊 + +`status_code` 也可以接收一個 `IntEnum`,例如 Python 的 `http.HTTPStatus`。 + +/// + +它會: + +* 在回應中傳回該狀態碼。 +* 在 OpenAPI 結構中如此記錄(因此也會反映在使用者介面中): + + + +/// note | 注意 + +有些回應碼(見下一節)表示回應不包含本文(body)。 + +FastAPI 知道這點,並會產生聲明「無回應本文」的 OpenAPI 文件。 + +/// + +## 關於 HTTP 狀態碼 { #about-http-status-codes } + +/// note | 注意 + +如果你已經知道什麼是 HTTP 狀態碼,可以直接跳到下一節。 + +/// + +在 HTTP 中,你會在回應的一部分傳回 3 位數的狀態碼。 + +這些狀態碼有對應的名稱以便辨識,但重點是數字本身。 + +簡而言之: + +* `100 - 199` 表示「資訊」。你很少會直接使用它們。這些狀態碼的回應不可包含本文。 +* **`200 - 299`** 表示「成功」。這是你最常使用的一組。 + * `200` 是預設狀態碼,表示一切「OK」。 + * 另一個例子是 `201`,代表「已建立」。常用於在資料庫中建立新紀錄之後。 + * 一個特殊情況是 `204`,代表「無內容」。當沒有內容要回傳給用戶端時使用,因此回應不得有本文。 +* **`300 - 399`** 表示「重新導向」。這些狀態碼的回應可能有或沒有本文,唯獨 `304`(「未修改」)必須沒有本文。 +* **`400 - 499`** 表示「用戶端錯誤」。這大概是你第二常用的一組。 + * 例如 `404`,代表「找不到」。 + * 對於一般性的用戶端錯誤,你可以使用 `400`。 +* `500 - 599` 表示伺服器錯誤。你幾乎不會直接使用它們。當你的應用程式或伺服器某處出錯時,會自動回傳其中一個狀態碼。 + +/// tip | 提示 + +想深入瞭解各狀態碼與其用途,請參考 MDN 關於 HTTP 狀態碼的文件。 + +/// + +## 快速記住名稱 { #shortcut-to-remember-the-names } + +再看一次前面的範例: + +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} + +`201` 是「已建立(Created)」的狀態碼。 + +但你不需要背下每個代碼代表什麼。 + +你可以使用 `fastapi.status` 提供的便利變數。 + +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} + +它們只是方便用的常數,值與數字相同,但這樣你可以用編輯器的自動完成來找到它們: + + + +/// note | 技術細節 + +你也可以使用 `from starlette import status`。 + +**FastAPI** 將同一個 `starlette.status` 以 `fastapi.status` 形式提供,純粹是為了讓你(開發者)方便。但它直接來自 Starlette。 + +/// + +## 變更預設值 { #changing-the-default } + +稍後在 [進階使用者指南](../advanced/response-change-status-code.md){.internal-link target=_blank} 中,你會看到如何回傳一個不同於此處所宣告預設值的狀態碼。 diff --git a/docs/zh-hant/docs/tutorial/schema-extra-example.md b/docs/zh-hant/docs/tutorial/schema-extra-example.md new file mode 100644 index 000000000..661938ac2 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/schema-extra-example.md @@ -0,0 +1,202 @@ +# 宣告請求範例資料 { #declare-request-example-data } + +你可以宣告你的應用程式可接收資料的 examples。 + +以下有數種方式可達成。 + +## Pydantic 模型中的額外 JSON Schema 資料 { #extra-json-schema-data-in-pydantic-models } + +你可以為 Pydantic 模型宣告 `examples`,它們會加入到產生出的 JSON Schema 中。 + +{* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} + +這些額外資訊會原封不動加入該模型輸出的 JSON Schema,並且會用在 API 文件裡。 + +你可以使用屬性 `model_config`(接收一個 `dict`),詳見 Pydantic 文件:Configuration。 + +你可以將 `"json_schema_extra"` 設為一個 `dict`,其中包含你想在產生的 JSON Schema 中出現的任何額外資料,包括 `examples`。 + +/// tip + +你可以用相同技巧擴充 JSON Schema,加入你自己的自訂額外資訊。 + +例如,你可以用它為前端使用者介面新增中繼資料等。 + +/// + +/// info + +OpenAPI 3.1.0(自 FastAPI 0.99.0 起使用)新增了對 `examples` 的支援,這是 **JSON Schema** 標準的一部分。 + +在那之前,只支援使用單一範例的關鍵字 `example`。OpenAPI 3.1.0 仍然支援 `example`,但它已被棄用,且不是 JSON Schema 標準的一部分。因此建議你將 `example` 遷移為 `examples`。🤓 + +你可以在本頁結尾閱讀更多。 + +/// + +## `Field` 其他參數 { #field-additional-arguments } + +在 Pydantic 模型中使用 `Field()` 時,你也可以宣告額外的 `examples`: + +{* ../../docs_src/schema_extra_example/tutorial002_py310.py hl[2,8:11] *} + +## JSON Schema 的 `examples` - OpenAPI { #examples-in-json-schema-openapi } + +當使用下列任一項: + +* `Path()` +* `Query()` +* `Header()` +* `Cookie()` +* `Body()` +* `Form()` +* `File()` + +你也可以宣告一組 `examples`,包含會加入到 **OpenAPI** 中它們各自 **JSON Schemas** 的額外資訊。 + +### `Body` 搭配 `examples` { #body-with-examples } + +這裡我們傳入 `examples`,其中包含 `Body()` 預期資料的一個範例: + +{* ../../docs_src/schema_extra_example/tutorial003_an_py310.py hl[22:29] *} + +### 文件 UI 中的範例 { #example-in-the-docs-ui } + +使用以上任一方法,在 `/docs` 中看起來會像這樣: + + + +### `Body` 搭配多個 `examples` { #body-with-multiple-examples } + +當然,你也可以傳入多個 `examples`: + +{* ../../docs_src/schema_extra_example/tutorial004_an_py310.py hl[23:38] *} + +這麼做時,這些範例會成為該 body 資料內部 **JSON Schema** 的一部分。 + +然而,撰寫本文時,負責呈現文件 UI 的工具 Swagger UI 並不支援在 **JSON Schema** 中顯示多個範例。不過請繼續往下閱讀以取得變通方式。 + +### OpenAPI 特定的 `examples` { #openapi-specific-examples } + +在 **JSON Schema** 支援 `examples` 之前,OpenAPI 就已支援另一個同名的欄位 `examples`。 + +這個「OpenAPI 特定」的 `examples` 位於 OpenAPI 規範的另一個區塊:在每個「路徑操作」的詳細資訊中,而不是在各個 JSON Schema 內。 + +而 Swagger UI 早已支援這個欄位,因此你可以用它在文件 UI 中顯示不同的範例。 + +這個 OpenAPI 特定欄位 `examples` 的結構是一個包含「多個範例」的 `dict`(而非 `list`),每個範例都可包含會一併加入到 **OpenAPI** 的額外資訊。 + +它不會出現在 OpenAPI 所含的各個 JSON Schema 內,而是直接放在對應的「路徑操作」上。 + +### 使用 `openapi_examples` 參數 { #using-the-openapi-examples-parameter } + +你可以在 FastAPI 中透過參數 `openapi_examples` 為下列項目宣告 OpenAPI 特定的 `examples`: + +* `Path()` +* `Query()` +* `Header()` +* `Cookie()` +* `Body()` +* `Form()` +* `File()` + +該 `dict` 的鍵用來識別各個範例,而每個值則是另一個 `dict`。 + +在 `examples` 中,每個範例的 `dict` 可以包含: + +* `summary`:範例的簡短描述。 +* `description`:較長的描述,可包含 Markdown 文字。 +* `value`:實際顯示的範例,例如一個 `dict`。 +* `externalValue`:`value` 的替代方案,為指向範例的 URL。儘管這可能不如 `value` 被工具廣泛支援。 + +你可以這樣使用: + +{* ../../docs_src/schema_extra_example/tutorial005_an_py310.py hl[23:49] *} + +### 文件 UI 中的 OpenAPI 範例 { #openapi-examples-in-the-docs-ui } + +當在 `Body()` 加上 `openapi_examples`,`/docs` 會顯示為: + + + +## 技術細節 { #technical-details } + +/// tip + +如果你已經在使用 **FastAPI** **0.99.0 或以上**的版本,大概可以略過這些細節。 + +這些內容比較與舊版(在 OpenAPI 3.1.0 可用之前)相關。 + +你可以把這段當作一小堂 OpenAPI 與 JSON Schema 的歷史課。🤓 + +/// + +/// warning + +以下是關於 **JSON Schema** 與 **OpenAPI** 標準的技術細節。 + +如果上面的做法對你已經足夠可用,就不需要這些細節,儘管直接跳過。 + +/// + +在 OpenAPI 3.1.0 之前,OpenAPI 使用的是較舊且經過修改的 **JSON Schema** 版本。 + +當時 JSON Schema 沒有 `examples`,因此 OpenAPI 在它自訂修改的版本中新增了自己的 `example` 欄位。 + +OpenAPI 也在規範的其他部分新增了 `example` 與 `examples` 欄位: + +* `Parameter Object`(規範),對應到 FastAPI 的: + * `Path()` + * `Query()` + * `Header()` + * `Cookie()` +* `Request Body Object` 中的 `content` 欄位裡的 `Media Type Object`(規範),對應到 FastAPI 的: + * `Body()` + * `File()` + * `Form()` + +/// info + +這個舊的、OpenAPI 特定的 `examples` 參數,從 FastAPI `0.103.0` 起改名為 `openapi_examples`。 + +/// + +### JSON Schema 的 `examples` 欄位 { #json-schemas-examples-field } + +後來 JSON Schema 在新版本規範中新增了 `examples` 欄位。 + +接著新的 OpenAPI 3.1.0 以最新版本(JSON Schema 2020-12)為基礎,該版本就包含這個新的 `examples` 欄位。 + +現在這個新的 `examples` 欄位優先於舊的單一(且客製)`example` 欄位,後者已被棄用。 + +JSON Schema 中新的 `examples` 欄位「就是一個 `list`」的範例集合,而不是像 OpenAPI 其他地方(如上所述)那樣附帶額外中繼資料的 `dict`。 + +/// info + +即使 OpenAPI 3.1.0 已發佈並與 JSON Schema 有更簡潔的整合,一段時間內提供自動文件的 Swagger UI 並不支援 OpenAPI 3.1.0(自 5.0.0 版起支援 🎉)。 + +因此,FastAPI 0.99.0 之前的版本仍使用 3.1.0 以下的 OpenAPI 版本。 + +/// + +### Pydantic 與 FastAPI 的 `examples` { #pydantic-and-fastapi-examples } + +當你在 Pydantic 模型中加入 `examples`,不論是用 `schema_extra` 或 `Field(examples=["something"])`,該範例都會被加入該 Pydantic 模型的 **JSON Schema**。 + +而該 Pydantic 模型的 **JSON Schema** 會被包含到你的 API 的 **OpenAPI** 中,接著用於文件 UI。 + +在 FastAPI 0.99.0 之前的版本(0.99.0 起使用較新的 OpenAPI 3.1.0)中,當你對其他工具(`Query()`、`Body()` 等)使用 `example` 或 `examples` 時,這些範例不會被加入描述該資料的 JSON Schema(甚至不會加入到 OpenAPI 自己版本的 JSON Schema 中),而是直接加入到 OpenAPI 中的「路徑操作」宣告(在 OpenAPI 使用 JSON Schema 的那些部分之外)。 + +但現在 FastAPI 0.99.0 以上使用的 OpenAPI 3.1.0 搭配 JSON Schema 2020-12,以及 Swagger UI 5.0.0 以上版本,整體更加一致,範例會包含在 JSON Schema 中。 + +### Swagger UI 與 OpenAPI 特定的 `examples` { #swagger-ui-and-openapi-specific-examples } + +由於在(2023-08-26 時)Swagger UI 不支援多個 JSON Schema 範例,使用者無法在文件中顯示多個範例。 + +為了解決此問題,FastAPI `0.103.0` 透過新參數 `openapi_examples` **新增支援** 宣告舊的「OpenAPI 特定」`examples` 欄位。🤓 + +### 總結 { #summary } + +我以前常說我不太喜歡歷史……結果現在在這裡講「科技史」。😅 + +簡而言之,**升級到 FastAPI 0.99.0 或以上**,事情會更**簡單、一致又直覺**,而且你不需要了解這些歷史細節。😎 diff --git a/docs/zh-hant/docs/tutorial/security/first-steps.md b/docs/zh-hant/docs/tutorial/security/first-steps.md new file mode 100644 index 000000000..109f59a37 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/security/first-steps.md @@ -0,0 +1,203 @@ +# 安全性 - 入門 { #security-first-steps } + +想像你有一個部署在某個網域的後端 API。 + +還有一個前端在另一個網域,或同一網域的不同路徑(或是行動應用程式)。 + +你希望前端能用使用者名稱與密碼向後端進行身分驗證。 + +我們可以用 OAuth2 搭配 FastAPI 來實作。 + +但不必通讀整份冗長規格只為了找出你需要的幾個重點。 + +就用 FastAPI 提供的工具處理安全性。 + +## 看起來如何 { #how-it-looks } + +先直接跑範例看效果,再回頭理解其原理。 + +## 建立 `main.py` { #create-main-py } + +將範例複製到檔案 `main.py`: + +{* ../../docs_src/security/tutorial001_an_py310.py *} + +## 執行 { #run-it } + +/// info + +當你使用 `pip install "fastapi[standard]"` 指令安裝時,`python-multipart` 套件會隨 FastAPI 自動安裝。 + +不過若只執行 `pip install fastapi`,預設不會包含 `python-multipart`。 + +若要手動安裝,請先建立並啟用一個[虛擬環境](../../virtual-environments.md){.internal-link target=_blank},接著執行: + +```console +$ pip install python-multipart +``` + +因為 OAuth2 會以「form data」傳送 `username` 與 `password`。 + +/// + +用以下指令執行範例: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +## 檢查 { #check-it } + +開啟互動式文件:http://127.0.0.1:8000/docs。 + +你會看到類似這樣: + + + +/// check | Authorize 按鈕! + +你會看到一個新的「Authorize」按鈕。 + +而你的「路徑操作」右上角也會出現一個小鎖頭可以點擊。 + +/// + +點擊後會跳出一個小視窗,讓你輸入 `username` 與 `password`(以及其他可選欄位): + + + +/// note | 注意 + +不管你在表單輸入什麼,現在都還不會成功;等等我們會把它完成。 + +/// + +這當然不是給最終使用者用的前端,但它是用來互動式文件化整個 API 的極佳自動化工具。 + +前端團隊(也可能就是你)可以使用它。 + +第三方應用或系統也能使用它。 + +你也能用它來除錯、檢查與測試同一個應用。 + +## `password` 流程 { #the-password-flow } + +現在回頭理解剛剛那些是什麼。 + +在 OAuth2 中,`password` 是處理安全與身分驗證的其中一種「流程」(flow)。 + +OAuth2 的設計讓後端或 API 可以獨立於執行使用者驗證的伺服器。 + +但在這個例子中,同一個 FastAPI 應用會同時處理 API 與驗證。 + +簡化來看流程如下: + +- 使用者在前端輸入 `username` 與 `password`,按下 `Enter`。 +- 前端(在使用者的瀏覽器中執行)把 `username` 與 `password` 傳到我們 API 的特定 URL(在程式中宣告為 `tokenUrl="token"`)。 +- API 檢查 `username` 與 `password`,並回傳一個「token(權杖)」(我們還沒實作這部分)。 + - 「token(權杖)」就是一段字串,之後可用來識別並驗證此使用者。 + - 通常 token 會設定一段時間後失效。 + - 因此使用者之後需要重新登入。 + - 若 token 被竊取,風險也較低;它不像永遠有效的萬用鑰匙(多數情況下)。 +- 前端會暫存這個 token。 +- 使用者在前端點擊前往其他頁面/區段。 +- 前端需要再向 API 取得資料。 + - 但該端點需要驗證。 + - 因此為了向 API 驗證,請求會帶上一個 `Authorization` 標頭,值為 `Bearer ` 加上 token。 + - 例如 token 是 `foobar`,則 `Authorization` 標頭內容為:`Bearer foobar`。 + +## FastAPI 的 `OAuth2PasswordBearer` { #fastapis-oauth2passwordbearer } + +FastAPI 提供多層抽象的工具來實作這些安全機制。 + +本例將使用 OAuth2 的 Password 流程,並以 Bearer token 進行驗證;我們會用 `OAuth2PasswordBearer` 類別來完成。 + +/// info + +「Bearer」token 不是唯一選項。 + +但對本例最合適。 + +通常對多數情境也足夠,除非你是 OAuth2 專家並確信有更適合你的選項。 + +在那種情況下,FastAPI 也提供相應工具讓你自行組合。 + +/// + +當我們建立 `OAuth2PasswordBearer` 類別的實例時,會傳入 `tokenUrl` 參數。這個參數包含了客戶端(在使用者瀏覽器中執行的前端)用來送出 `username` 與 `password` 以取得 token 的 URL。 + +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} + +/// tip + +這裡的 `tokenUrl="token"` 指的是尚未建立的相對 URL `token`。因為是相對 URL,所以等同於 `./token`。 + +由於使用了相對 URL,若你的 API 位於 `https://example.com/`,那它會指向 `https://example.com/token`;但若你的 API 位於 `https://example.com/api/v1/`,那它會指向 `https://example.com/api/v1/token`。 + +使用相對 URL 很重要,能確保你的應用在像是[在 Proxy 後方](../../advanced/behind-a-proxy.md){.internal-link target=_blank}這類進階情境中仍能正常運作。 + +/// + +這個參數不會建立該端點/「路徑操作」,而是宣告 `/token` 將是客戶端用來取得 token 的 URL。這些資訊會出現在 OpenAPI,並被互動式 API 文件系統使用。 + +我們很快也會建立實際的路徑操作。 + +/// info + +如果你是非常嚴格的「Pythonista」,可能不喜歡參數名稱用 `tokenUrl` 而不是 `token_url`。 + +那是因為它沿用了 OpenAPI 規格中的名稱。如此一來,若你要深入查閱這些安全方案,便能直接複製貼上去搜尋更多資訊。 + +/// + +變數 `oauth2_scheme` 是 `OAuth2PasswordBearer` 的實例,但同時它也是「可呼叫的」(callable)。 + +它可以這樣被呼叫: + +```Python +oauth2_scheme(some, parameters) +``` + +因此它可以配合 `Depends` 使用。 + +### 如何使用 { #use-it } + +現在你可以在相依性中傳入 `oauth2_scheme` 與 `Depends` 搭配。 + +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} + +此相依性會提供一個 `str`,指派給「路徑操作函式」的參數 `token`。 + +FastAPI 會知道可以使用這個相依性,在 OpenAPI(以及自動產生的 API 文件)中定義一個「安全性方案」。 + +/// info | 技術細節 + +FastAPI 之所以知道可以用(相依性中宣告的)`OAuth2PasswordBearer` 類別,在 OpenAPI 中定義安全性方案,是因為它繼承自 `fastapi.security.oauth2.OAuth2`,而後者又繼承自 `fastapi.security.base.SecurityBase`。 + +所有能與 OpenAPI(以及自動 API 文件)整合的安全工具都繼承自 `SecurityBase`,FastAPI 才能知道如何把它們整合進 OpenAPI。 + +/// + +## 它做了什麼 { #what-it-does } + +它會從請求中尋找 `Authorization` 標頭,檢查其值是否為 `Bearer ` 加上一段 token,並將該 token 以 `str` 回傳。 + +若未找到 `Authorization` 標頭,或其值不是 `Bearer ` token,則會直接回傳 401(`UNAUTHORIZED`)錯誤。 + +你不必再自行檢查 token 是否存在;你可以確信只要你的函式被執行,該 token 參數就一定會是 `str`。 + +你可以在互動式文件中試試看: + + + +我們還沒驗證 token 是否有效,但這已是個開始。 + +## 小結 { #recap } + +只需多寫 3、4 行,就能有一個基本的安全機制。 diff --git a/docs/zh-hant/docs/tutorial/security/get-current-user.md b/docs/zh-hant/docs/tutorial/security/get-current-user.md new file mode 100644 index 000000000..b223d4823 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/security/get-current-user.md @@ -0,0 +1,105 @@ +# 取得目前使用者 { #get-current-user } + +在前一章,基於依賴注入系統的安全機制會把一個 `token`(作為 `str`)提供給*路徑操作函式*: + +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} + +但這還不太有用。 + +讓它改為回傳目前使用者吧。 + +## 建立使用者模型 { #create-a-user-model } + +先建立一個 Pydantic 的使用者模型。 + +就像用 Pydantic 宣告請求體一樣,我們也可以在其他地方使用它: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *} + +## 建立 `get_current_user` 依賴 { #create-a-get-current-user-dependency } + +讓我們建立一個依賴 `get_current_user`。 + +記得依賴可以有子依賴嗎? + +`get_current_user` 會依賴我們先前建立的相同 `oauth2_scheme`。 + +如同先前在*路徑操作*中直接做的一樣,新的依賴 `get_current_user` 會從子依賴 `oauth2_scheme` 接收一個作為 `str` 的 `token`: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *} + +## 取得使用者 { #get-the-user } + +`get_current_user` 會使用我們建立的(假的)工具函式,它接收一個作為 `str` 的 token,並回傳我們的 Pydantic `User` 模型: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *} + +## 注入目前使用者 { #inject-the-current-user } + +現在我們可以在*路徑操作*中用相同的 `Depends` 來使用 `get_current_user`: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *} + +注意我們把 `current_user` 的型別宣告為 Pydantic 的 `User` 模型。 + +這能在函式內提供自動補全與型別檢查的協助。 + +/// tip | 提示 + +你可能記得,請求體也會用 Pydantic 模型宣告。 + +這裡因為你使用了 `Depends`,**FastAPI** 不會混淆。 + +/// + +/// check | 檢查 + +這個依賴系統的設計讓我們可以有不同的依賴(不同的 "dependables"),都回傳 `User` 模型。 + +我們不受限於只能有一個能回傳該類型資料的依賴。 + +/// + +## 其他模型 { #other-models } + +現在你可以在*路徑操作函式*中直接取得目前使用者,並在**依賴注入**層處理安全機制,使用 `Depends`。 + +而且你可以為安全需求使用任意模型或資料(本例中是 Pydantic 模型 `User`)。 + +但你不受限於某個特定的資料模型、類別或型別。 + +想在模型中只有 `id` 與 `email` 而沒有任何 `username`?當然可以。你可以用同樣的工具達成。 + +想只用一個 `str`?或只用一個 `dict`?或直接使用資料庫類別的模型實例?都可以,一樣運作。 + +你的應用其實沒有真人使用者登入,而是機器人、bot,或其他系統,只持有 access token?同樣沒有問題。 + +只要用任何你的應用需要的模型、類別或資料庫即可。**FastAPI** 的依賴注入系統都支援。 + +## 程式碼大小 { #code-size } + +這個範例看起來可能有點冗長。記住我們把安全、資料模型、工具函式與*路徑操作*混在同一個檔案中。 + +但重點在這裡。 + +安全與依賴注入相關的內容只需要寫一次。 + +你可以把它設計得再複雜都沒問題,仍然只需在單一位置寫一次,依然具備完整的彈性。 + +但你可以有成千上萬個端點(*路徑操作*)共用同一套安全系統。 + +而且它們全部(或你想要的一部分)都可以重用這些依賴,或你建立的其他依賴。 + +而所有這些上千個*路徑操作*都可以小到只要 3 行: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *} + +## 回顧 { #recap } + +現在你可以在*路徑操作函式*中直接取得目前使用者。 + +我們已經完成一半了。 + +我們只需要再新增一個*路徑操作*,讓使用者/用戶端實際送出 `username` 與 `password`。 + +下一步就會做。 diff --git a/docs/zh-hant/docs/tutorial/security/index.md b/docs/zh-hant/docs/tutorial/security/index.md new file mode 100644 index 000000000..b5e3738bc --- /dev/null +++ b/docs/zh-hant/docs/tutorial/security/index.md @@ -0,0 +1,101 @@ +# 安全性 { #security } + +有許多方式可以處理安全性、身分驗證與授權。 + +而且這通常是一個複雜且「困難」的主題。 + +在許多框架與系統中,光是處理安全性與身分驗證就要花費大量心力與程式碼(很多情況下可能佔了全部程式碼的 50% 以上)。 + +**FastAPI** 提供多種工具,讓你能以標準方式輕鬆、快速地處理「安全性」,而不必先研究並學會所有安全性規範。 + +但在此之前,先釐清幾個小概念。 + +## 急著上手? { #in-a-hurry } + +如果你不在意這些術語,只需要立刻加入以使用者名稱與密碼為基礎的身分驗證與安全性,就直接跳到後續章節。 + +## OAuth2 { #oauth2 } + +OAuth2 是一套規範,定義了多種處理身分驗證與授權的方法。 + +它相當龐大,涵蓋許多複雜的使用情境。 + +它也包含使用「第三方」進行身分驗證的方式。 + +這正是各種「使用 Facebook、Google、X(Twitter)、GitHub 登入」系統在底層採用的機制。 + +### OAuth 1 { #oauth-1 } + +過去有 OAuth 1,和 OAuth2 非常不同,也更複雜,因為它直接規範了如何加密通訊。 + +它現在並不流行,也很少被使用。 + +OAuth2 不規範通訊如何加密,而是假設你的應用會透過 HTTPS 提供服務。 + +/// tip | 提示 +在部署相關章節中,你會看到如何使用 Traefik 與 Let's Encrypt 免費設定 HTTPS。 +/// + +## OpenID Connect { #openid-connect } + +OpenID Connect 是基於 **OAuth2** 的另一套規範。 + +它只是擴充了 OAuth2,釐清了 OAuth2 中相對模糊的部份,以提升互通性。 + +例如,Google 登入使用的是 OpenID Connect(其底層使用 OAuth2)。 + +但 Facebook 登入不支援 OpenID Connect,它有自己風格的 OAuth2。 + +### OpenID(不是「OpenID Connect」) { #openid-not-openid-connect } + +過去也有一個「OpenID」規範。它試圖解決與 **OpenID Connect** 相同的問題,但不是建立在 OAuth2 之上。 + +因此,它是一套完全額外、獨立的系統。 + +它現在並不流行,也很少被使用。 + +## OpenAPI { #openapi } + +OpenAPI(先前稱為 Swagger)是一套用於構建 API 的開放規範(現為 Linux 基金會的一部分)。 + +**FastAPI** 建立在 **OpenAPI** 之上。 + +這使得它能提供多種自動化的互動式文件介面、程式碼產生等功能。 + +OpenAPI 提供定義多種安全性「方案」。 + +透過使用它們,你可以善用這些基於標準的工具,包括這些互動式文件系統。 + +OpenAPI 定義了下列安全性方案: + +* `apiKey`:應用程式特定的金鑰,來源可以是: + * 查詢參數。 + * 標頭(header)。 + * Cookie。 +* `http`:標準的 HTTP 驗證系統,包括: + * `bearer`:使用 `Authorization` 標頭,值為 `Bearer ` 加上一個 token。這是從 OAuth2 延伸而來。 + * HTTP Basic 驗證。 + * HTTP Digest 等。 +* `oauth2`:所有 OAuth2 的安全性處理方式(稱為「flows」)。 + * 其中數個 flow 適合用來建立 OAuth 2.0 身分驗證提供者(如 Google、Facebook、X(Twitter)、GitHub 等): + * `implicit` + * `clientCredentials` + * `authorizationCode` + * 但有一個特定的 flow 可直接在同一個應用中處理身分驗證: + * `password`:後續幾個章節會示範這個。 +* `openIdConnect`:提供一種方式來定義如何自動發現 OAuth2 的身分驗證資訊。 + * 這種自動探索機制即由 OpenID Connect 規範定義。 + +/// tip | 提示 +整合像 Google、Facebook、X(Twitter)、GitHub 等其他身分驗證/授權提供者也是可行而且相對容易。 + +最複雜的部分其實是打造一個類似那樣的身分驗證/授權提供者,但 **FastAPI** 提供了工具,能替你處理繁重工作,讓你更輕鬆完成。 +/// + +## **FastAPI** 工具 { #fastapi-utilities } + +FastAPI 在 `fastapi.security` 模組中為上述各種安全性方案提供了多種工具,讓這些機制更容易使用。 + +接下來的章節會示範如何使用這些 **FastAPI** 提供的工具,為你的 API 加入安全性。 + +你也會看到它如何自動整合到互動式文件系統中。 diff --git a/docs/zh-hant/docs/tutorial/security/oauth2-jwt.md b/docs/zh-hant/docs/tutorial/security/oauth2-jwt.md new file mode 100644 index 000000000..d6761a005 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/security/oauth2-jwt.md @@ -0,0 +1,277 @@ +# 使用密碼(與雜湊)的 OAuth2、以 Bearer 搭配 JWT 權杖 { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens } + +現在我們已經有完整的安全流程了,接下來用 JWT 權杖與安全的密碼雜湊,讓應用真正安全。 + +這份程式碼可以直接用在你的應用中,把密碼雜湊存進資料庫等等。 + +我們會從上一章的內容繼續往下擴充。 + +## 關於 JWT { #about-jwt } + +JWT 的意思是「JSON Web Tokens」。 + +它是一種把 JSON 物件編碼成一段長且緊密(沒有空白)的字串的標準。看起來像這樣: + +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +``` + +它不是加密的,所以任何人都可以從內容還原出資訊。 + +但它是簽名過的。因此當你收到一個你所簽發的權杖時,你可以驗證確實是你簽發的。 + +如此一來,你可以建立一個(例如)有效期為 1 週的權杖。當使用者隔天帶著這個權杖回來時,你就知道該使用者仍然登入在你的系統中。 + +一週後,權杖會過期,使用者就不再被授權,需要再次登入以取得新的權杖。而如果使用者(或第三方)試圖修改權杖來改變有效期,你也能發現,因為簽名不會相符。 + +如果你想玩玩看 JWT 權杖並了解其運作,請參考 https://jwt.io。 + +## 安裝 `PyJWT` { #install-pyjwt } + +我們需要安裝 `PyJWT` 才能在 Python 中產生與驗證 JWT 權杖。 + +請先建立並啟用一個[虛擬環境](../../virtual-environments.md){.internal-link target=_blank},然後安裝 `pyjwt`: + +
+ +```console +$ pip install pyjwt + +---> 100% +``` + +
+ +/// info | 說明 + +如果你打算使用像 RSA 或 ECDSA 這類的數位簽章演算法,應該安裝帶有加密函式庫相依的 `pyjwt[crypto]`。 + +更多內容可參考 PyJWT 安裝文件。 + +/// + +## 密碼雜湊 { #password-hashing } + +「雜湊」是指把某些內容(此處為密碼)轉換成一串看起來像亂碼的位元組序列(其實就是字串)。 + +每當你輸入完全相同的內容(完全相同的密碼),就會得到完全相同的亂碼。 + +但你無法從這串亂碼再反推回原本的密碼。 + +### 為什麼要用密碼雜湊 { #why-use-password-hashing } + +如果你的資料庫被偷了,竊賊拿到的不是使用者的明文密碼,而只是雜湊值。 + +因此,竊賊無法直接拿該密碼去嘗試登入其他系統(由於許多使用者在各處都用同一組密碼,這會很危險)。 + +## 安裝 `pwdlib` { #install-pwdlib } + +pwdlib 是一個很棒的 Python 套件,用來處理密碼雜湊。 + +它支援多種安全的雜湊演算法與相關工具。 + +建議使用的演算法是「Argon2」。 + +請先建立並啟用一個[虛擬環境](../../virtual-environments.md){.internal-link target=_blank},然後以 Argon2 支援安裝 pwdlib: + +
+ +```console +$ pip install "pwdlib[argon2]" + +---> 100% +``` + +
+ +/// tip | 提示 + +使用 `pwdlib`,你甚至可以把它設定為能讀取由 **Django**、**Flask** 的安全外掛或其他許多系統所建立的密碼。 + +例如,你可以讓 Django 應用與 FastAPI 應用共用同一個資料庫中的資料。或者逐步遷移一個 Django 應用,同樣使用該資料庫。 + +而你的使用者可以同時從 Django 應用或 **FastAPI** 應用登入。 + +/// + +## 雜湊與驗證密碼 { #hash-and-verify-the-passwords } + +從 `pwdlib` 匯入我們需要的工具。 + +用建議設定建立一個 PasswordHash 執行個體——它會用於雜湊與驗證密碼。 + +/// tip | 提示 + +pwdlib 也支援 bcrypt 雜湊演算法,但不包含傳統(legacy)演算法——若需要處理過時的雜湊,建議使用 passlib 函式庫。 + +例如,你可以用它讀取並驗證由其他系統(如 Django)產生的密碼,但針對任何新密碼則改用像 Argon2 或 Bcrypt 這類的不同演算法來雜湊。 + +並同時與所有這些格式相容。 + +/// + +建立一個工具函式來雜湊使用者送來的密碼。 + +再建立另一個工具來驗證收到的密碼是否符合已儲存的雜湊。 + +以及另一個用於驗證並回傳使用者的工具。 + +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +當以不存在於資料庫的使用者名稱呼叫 `authenticate_user` 時,我們仍然會拿一個假的雜湊去跑一次 `verify_password`。 + +這可確保無論使用者名稱是否有效,端點的回應時間都大致相同,避免可用來枚舉既有使用者名稱的「計時攻擊」(timing attacks)。 + +/// note | 注意 + +如果你查看新的(假)資料庫 `fake_users_db`,你會看到雜湊後的密碼現在長這樣:`"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`。 + +/// + +## 處理 JWT 權杖 { #handle-jwt-tokens } + +匯入剛安裝的模組。 + +建立一把隨機的密鑰(secret key)用於簽署 JWT 權杖。 + +要產生安全的隨機密鑰可使用以下指令: + +
+ +```console +$ openssl rand -hex 32 + +09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 +``` + +
+ +將輸出的值複製到變數 `SECRET_KEY`(不要使用範例中的那一組)。 + +建立變數 `ALGORITHM` 指定用來簽署 JWT 權杖的演算法,設為 `"HS256"`。 + +建立一個權杖有效期的變數。 + +定義一個用於權杖端點回應的 Pydantic Model。 + +建立一個工具函式來產生新的 access token。 + +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} + +## 更新相依項目 { #update-the-dependencies } + +更新 `get_current_user`,讓它仍接收相同的權杖,但這次改用 JWT 權杖。 + +解碼收到的權杖、驗證它,並回傳目前的使用者。 + +如果權杖無效,立即回傳一個 HTTP 錯誤。 + +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} + +## 更新 `/token` 路徑操作 { #update-the-token-path-operation } + +用權杖有效期建立一個 `timedelta`。 + +建立真正的 JWT access token 並回傳它。 + +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} + +### 關於 JWT「主體」`sub` 的技術細節 { #technical-details-about-the-jwt-subject-sub } + +JWT 規範說有個鍵 `sub`,代表權杖的主體(subject)。 + +使用它是可選的,但通常會把使用者的識別資訊放在這裡,所以我們在此採用。 + +JWT 除了用來識別使用者並允許他直接對你的 API 執行操作外,也可用於其他用途。 + +例如,你可以識別一台「車」或一篇「部落格文章」。 + +接著可以替該實體加上權限,如「drive」(對車而言)或「edit」(對文章而言)。 + +然後你可以把該 JWT 權杖交給某個使用者(或機器人),他們就能用它來執行那些動作(開車、或編輯文章),甚至不需要有帳號,只要使用你的 API 所產生的 JWT 權杖即可。 + +基於這些概念,JWT 能用在更複雜的情境中。 + +在那些情境裡,數個實體可能擁有相同的 ID,例如 `foo`(使用者 `foo`、車 `foo`、以及文章 `foo`)。 + +為了避免 ID 衝突,在為使用者建立 JWT 權杖時,你可以替 `sub` 的值加上前綴,例如 `username:`。因此在這個例子中,`sub` 的值可以是:`username:johndoe`。 + +要記住的重要點是:`sub` 必須是在整個應用中唯一的識別子,並且必須是字串。 + +## 試試看 { #check-it } + +啟動伺服器並前往文件頁:http://127.0.0.1:8000/docs。 + +你會看到這樣的介面: + + + +用和先前相同的方式授權應用。 + +使用下列認證資訊: + +Username: `johndoe` +Password: `secret` + +/// check | 檢查 + +注意在程式碼中完全沒有明文密碼「`secret`」,我們只有雜湊後的版本。 + +/// + + + +呼叫端點 `/users/me/`,你會得到類似這樣的回應: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + + + +如果你打開開發者工具,可以看到送出的資料只包含權杖;密碼只會在第一次請求(用來驗證使用者並取得 access token)時送出,之後就不會再送: + + + +/// note | 注意 + +留意標頭 `Authorization`,其值是以 `Bearer ` 開頭。 + +/// + +## 進階用法:`scopes` { #advanced-usage-with-scopes } + +OAuth2 有「scopes」的概念。 + +你可以用它們替 JWT 權杖加上一組特定的權限。 + +接著你可以把這個權杖直接交給某個使用者或第三方,讓他們在一組受限條件下與你的 API 互動。 + +你可以在之後的「進階使用者指南」學到如何使用它們,以及它們如何整合進 **FastAPI**。 + +## 小結 { #recap } + +依照你目前學到的內容,你可以用 OAuth2 與 JWT 等標準,設定一個安全的 **FastAPI** 應用。 + +在幾乎任何框架中,安全性處理都會很快變得相當複雜。 + +許多能大幅簡化工作的套件,往往必須在資料模型、資料庫與可用功能上做出很多取捨。而有些過度簡化的套件底層其實存在安全弱點。 + +--- + +**FastAPI** 不會在任何資料庫、資料模型或工具上做妥協。 + +它給你完全的彈性,讓你挑選最適合你專案的組合。 + +而且你可以直接使用許多維護良好且被廣泛採用的套件,例如 `pwdlib` 與 `PyJWT`,因為 **FastAPI** 不需要任何複雜機制就能整合外部套件。 + +同時它也提供工具來在不犧牲彈性、穩健或安全的前提下,盡可能地簡化流程。 + +你可以用相對簡單的方式使用並實作像 OAuth2 這樣的安全標準協定。 + +你可以在「進階使用者指南」進一步了解如何使用 OAuth2 的「scopes」,以實作更細緻的權限系統,並遵循相同的標準。帶有 scopes 的 OAuth2 是許多大型身份驗證供應商(如 Facebook、Google、GitHub、Microsoft、X(Twitter)等)用來授權第三方應用代表其使用者與其 API 互動的機制。 diff --git a/docs/zh-hant/docs/tutorial/security/simple-oauth2.md b/docs/zh-hant/docs/tutorial/security/simple-oauth2.md new file mode 100644 index 000000000..3b9aae341 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/security/simple-oauth2.md @@ -0,0 +1,289 @@ +# 簡易 OAuth2:Password 與 Bearer { #simple-oauth2-with-password-and-bearer } + +現在從上一章延伸,補上缺少的部分,完成整個安全流程。 + +## 取得 `username` 與 `password` { #get-the-username-and-password } + +我們要使用 **FastAPI** 提供的安全性工具來取得 `username` 與 `password`。 + +OAuth2 規範中,當使用「password flow」(我們現在使用的)時,用戶端/使用者必須以表單資料送出 `username` 與 `password` 欄位。 + +而且規範要求欄位名稱必須就是這兩個,所以像是 `user-name` 或 `email` 都不行。 + +但別擔心,你在前端要怎麼呈現給最終使用者都可以。 + +而你的資料庫模型也可以使用任何你想要的欄位名稱。 + +不過在登入的路徑操作(path operation)裡,我們需要使用這些名稱,才能符合規範(例如才能使用整合的 API 文件系統)。 + +規範也說明 `username` 與 `password` 必須以表單資料傳送(也就是這裡不能用 JSON)。 + +### `scope` { #scope } + +規範也說用戶端可以再送一個表單欄位「`scope`」。 + +欄位名稱是單數的 `scope`,但實際上是由多個以空白分隔的「scopes」組成的一長串字串。 + +每個「scope」就是一個(不含空白的)字串。 + +它們通常用來宣告特定的權限,例如: + +- `users:read` 或 `users:write` 是常見的例子 +- `instagram_basic` 用在 Facebook / Instagram +- `https://www.googleapis.com/auth/drive` 用在 Google + +/// info + +在 OAuth2 裡,「scope」只是用來宣告特定所需權限的一個字串。 + +不論裡面是否包含像 `:` 之類的字元,或是否是一個 URL,都沒差。 + +那些都是實作細節。 + +對 OAuth2 而言,它們就是字串而已。 + +/// + +## 取得 `username` 與 `password` 的程式碼 { #code-to-get-the-username-and-password } + +現在用 **FastAPI** 提供的工具來處理。 + +### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform } + +先匯入 `OAuth2PasswordRequestForm`,並在 `/token` 的路徑操作中,搭配 `Depends` 當作依賴使用: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *} + +`OAuth2PasswordRequestForm` 是一個類別型依賴,它宣告了一個表單本文,包含: + +- `username` +- `password` +- 可選的 `scope` 欄位,內容是一個由空白分隔的長字串 +- 可選的 `grant_type` + +/// tip + +依規範,實際上需要一個 `grant_type` 欄位且固定值為 `password`,但 `OAuth2PasswordRequestForm` 並不會強制檢查。 + +如果你需要強制檢查,請改用 `OAuth2PasswordRequestFormStrict` 取代 `OAuth2PasswordRequestForm`。 + +/// + +- 可選的 `client_id`(本例不需要) +- 可選的 `client_secret`(本例不需要) + +/// info + +`OAuth2PasswordRequestForm` 並不是像 `OAuth2PasswordBearer` 那樣對 **FastAPI** 來說的特殊類別。 + +`OAuth2PasswordBearer` 會讓 **FastAPI** 知道它是一個 security scheme,因此會以那種方式加入 OpenAPI。 + +但 `OAuth2PasswordRequestForm` 只是你也可以自己撰寫的一個類別型依賴,或是你也可以直接宣告 `Form` 參數。 + +只是因為這是很常見的用例,所以 **FastAPI** 直接內建提供,讓事情更簡單。 + +/// + +### 使用表單資料 { #use-the-form-data } + +/// tip + +`OAuth2PasswordRequestForm` 這個依賴類別的實例不會有以空白分隔長字串的 `scope` 屬性,而是會有一個 `scopes` 屬性,裡面是各個 scope 的實際字串清單。 + +本示例沒有使用 `scopes`,但如果你需要,功能已經在那裡了。 + +/// + +現在,從(假的)資料庫裡用表單欄位的 `username` 取得使用者資料。 + +如果沒有該使用者,就回傳「Incorrect username or password」的錯誤。 + +我們用 `HTTPException` 這個例外來回傳錯誤: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *} + +### 檢查密碼 { #check-the-password } + +這時我們已經有來自資料庫的使用者資料,但還沒檢查密碼。 + +先把那些資料放進 Pydantic 的 `UserInDB` 模型。 + +你絕對不要以純文字儲存密碼,所以我們會使用(假的)密碼雜湊系統。 + +如果密碼不匹配,我們回傳同樣的錯誤。 + +#### 密碼雜湊(hashing) { #password-hashing } + +「雜湊」的意思是:把一些內容(這裡是密碼)轉換成一串看起來像亂碼的位元組序列(就是字串)。 + +只要你輸入完全相同的內容(完全相同的密碼),就會得到完全相同的亂碼。 + +但你無法從這串亂碼還原回原本的密碼。 + +##### 為何要做密碼雜湊 { #why-use-password-hashing } + +如果你的資料庫被竊取,攻擊者拿到的不是使用者的純文字密碼,而只是雜湊值。 + +因此攻擊者無法嘗試把那些密碼用在其他系統上(因為很多使用者在各處都用同一組密碼,這會很危險)。 + +{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *} + +#### 關於 `**user_dict**` { #about-user-dict } + +`UserInDB(**user_dict)` 的意思是: + +把 `user_dict` 的鍵和值直接當作具名參數傳入,等同於: + +```Python +UserInDB( + username = user_dict["username"], + email = user_dict["email"], + full_name = user_dict["full_name"], + disabled = user_dict["disabled"], + hashed_password = user_dict["hashed_password"], +) +``` + +/// info + +想更完整地了解 `**user_dict`,請回到[**額外模型** 的文件](../extra-models.md#about-user-in-dict){.internal-link target=_blank}。 + +/// + +## 回傳 token { #return-the-token } + +`token` 端點的回應必須是 JSON 物件。 + +它應該有一個 `token_type`。在本例中,我們使用「Bearer」tokens,token 類型應該是「`bearer`」。 + +而且它還應該有一個 `access_token`,其值為包含我們存取權杖的字串。 + +在這個簡單示例中,我們會不安全地直接回傳相同的 `username` 當作 token。 + +/// tip + +下一章你會看到真正安全的實作,包含密碼雜湊與 JWT tokens。 + +但現在先把注意力放在我們需要的這些細節上。 + +/// + +{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *} + +/// tip + +依照規範,你應該回傳一個包含 `access_token` 與 `token_type` 的 JSON,就像這個範例。 + +這部分需要你自己在程式中完成,並確保使用這些 JSON key。 + +這幾乎是你為了符合規範而必須自行記得正確處理的唯一事情。 + +其餘的 **FastAPI** 都會幫你處理。 + +/// + +## 更新依賴項 { #update-the-dependencies } + +接著我們要更新依賴項。 + +我們只想在使用者為啟用狀態時取得 `current_user`。 + +所以,我們新增一個依賴 `get_current_active_user`,而它本身又依賴 `get_current_user`。 + +這兩個依賴會在使用者不存在或未啟用時回傳 HTTP 錯誤。 + +因此,在端點中,只有在使用者存在、已正確驗證且為啟用狀態時,我們才會取得使用者: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *} + +/// info + +這裡我們一併回傳值為 `Bearer` 的額外標頭 `WWW-Authenticate`,這也是規範的一部分。 + +任何 HTTP(錯誤)狀態碼 401「UNAUTHORIZED」都應該同時回傳 `WWW-Authenticate` 標頭。 + +在 bearer tokens(我們的情況)下,該標頭的值應該是 `Bearer`。 + +其實你可以省略這個額外標頭,功能仍會正常。 + +但此處加上它是為了遵循規範。 + +同時也可能有工具會期待並使用它(現在或未來),而這可能對你或你的使用者有幫助,現在或未來皆然。 + +這就是標準的好處... + +/// + +## 實際操作看看 { #see-it-in-action } + +開啟互動式文件:http://127.0.0.1:8000/docs。 + +### 驗證身分 { #authenticate } + +點選「Authorize」按鈕。 + +使用下列帳密: + +User: `johndoe` + +Password: `secret` + + + +在系統中完成驗證後,你會看到如下畫面: + + + +### 取得自己的使用者資料 { #get-your-own-user-data } + +現在使用 `GET` 方法呼叫路徑 `/users/me`。 + +你會取得自己的使用者資料,如: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" +} +``` + + + +如果你點擊鎖頭圖示登出,然後再次嘗試相同操作,你會得到 HTTP 401 錯誤: + +```JSON +{ + "detail": "Not authenticated" +} +``` + +### 未啟用的使用者 { #inactive-user } + +現在改用一個未啟用的使用者,使用以下帳密驗證: + +User: `alice` + +Password: `secret2` + +然後再呼叫 `GET` 方法的 `/users/me`。 + +你會得到「Inactive user」的錯誤,例如: + +```JSON +{ + "detail": "Inactive user" +} +``` + +## 小結 { #recap } + +你現在已經有足夠的工具,能為你的 API 以 `username` 與 `password` 實作一個完整的安全性系統。 + +使用這些工具,你可以讓安全性系統相容於任何資料庫,以及任何使用者或資料模型。 + +唯一尚未補上的細節是:它現在其實還不「安全」。 + +在下一章,你會看到如何使用安全的密碼雜湊函式庫與 JWT tokens。 diff --git a/docs/zh-hant/docs/tutorial/sql-databases.md b/docs/zh-hant/docs/tutorial/sql-databases.md new file mode 100644 index 000000000..930dc4e8a --- /dev/null +++ b/docs/zh-hant/docs/tutorial/sql-databases.md @@ -0,0 +1,357 @@ +# SQL(關聯式)資料庫 { #sql-relational-databases } + +FastAPI 不強制你使用 SQL(關聯式)資料庫。你可以使用任何你想要的資料庫。 + +這裡我們會用 SQLModel 作為範例。 + +SQLModel 建立在 SQLAlchemy 與 Pydantic 之上。它由 FastAPI 的作者開發,非常適合需要使用 SQL 資料庫的 FastAPI 應用。 + +/// tip | 提示 + +你可以使用任何你想用的 SQL 或 NoSQL 資料庫函式庫(有時稱為 "ORMs"),FastAPI 不會強迫你使用特定工具。😎 + +/// + +因為 SQLModel 建立在 SQLAlchemy 之上,你可以輕鬆使用 SQLAlchemy 所支援的任何資料庫(因此 SQLModel 也支援),例如: + +* PostgreSQL +* MySQL +* SQLite +* Oracle +* Microsoft SQL Server,等等。 + +在這個範例中,我們會使用 SQLite,因為它只用到單一檔案,而且 Python 內建支援。你可以直接複製這個範例並原樣執行。 + +之後,在你的正式環境應用中,你可能會想使用像 PostgreSQL 這類的資料庫伺服器。 + +/// tip | 提示 + +有一個包含 FastAPI 與 PostgreSQL 的官方專案腳手架,還有前端與更多工具:https://github.com/fastapi/full-stack-fastapi-template + +/// + +這是一份非常簡短的教學,如果你想更全面學習資料庫、SQL,或更進階的功能,請參考 SQLModel 文件。 + +## 安裝 `SQLModel` { #install-sqlmodel } + +首先,請先建立你的[虛擬環境](../virtual-environments.md){.internal-link target=_blank}、啟用它,然後安裝 `sqlmodel`: + +
+ +```console +$ pip install sqlmodel +---> 100% +``` + +
+ +## 建立只有單一模型的應用 { #create-the-app-with-a-single-model } + +我們先用單一 SQLModel 模型建立這個應用的最簡版。 + +接著我們會在下方用多個模型來提升安全性與彈性。🤓 + +### 建立模型 { #create-models } + +匯入 `SQLModel` 並建立一個資料庫模型: + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} + +`Hero` 類別與 Pydantic 模型非常相似(事實上,在底層它就是一個 Pydantic 模型)。 + +有幾點差異: + +* `table=True` 告訴 SQLModel 這是一個「資料表模型」(table model),它應該代表 SQL 資料庫中的一個資料表,而不僅僅是「資料模型」(就像一般的 Pydantic 類別)。 + +* `Field(primary_key=True)` 告訴 SQLModel,`id` 是 SQL 資料庫中的「主鍵」。 (你可以在 SQLModel 文件中進一步了解 SQL 主鍵) + + 注意:我們在主鍵欄位使用 `int | None`,這樣在 Python 程式碼中我們可以「在沒有 `id` 的情況下建立物件」(`id=None`),假設資料庫在儲存時會「自動產生」。SQLModel 瞭解資料庫會提供 `id`,並且在資料庫綱要中「將該欄位定義為非空的 `INTEGER`」。詳情請見 SQLModel 文件:主鍵。 + +* `Field(index=True)` 告訴 SQLModel 應為此欄位建立「SQL 索引」,以便在用此欄位過濾讀取資料時更快查詢。 + + SQLModel 會知道宣告為 `str` 的欄位在 SQL 中會是 `TEXT`(或 `VARCHAR`,依資料庫而定)。 + +### 建立引擎 { #create-an-engine } + +SQLModel 的 `engine`(底層實際上是 SQLAlchemy 的 `engine`)是用來「維護與資料庫連線」的東西。 + +你的程式中應該只有「單一 `engine` 物件」來連到同一個資料庫。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} + +使用 `check_same_thread=False` 允許 FastAPI 在不同執行緒中使用同一個 SQLite 資料庫。這是必要的,因為「單一請求」可能會使用「多個執行緒」(例如在依賴項中)。 + +別擔心,依照我們的程式結構,稍後我們會確保「每個請求只使用單一 SQLModel 的 session」,這其實就是 `check_same_thread` 想要達成的事。 + +### 建立資料表 { #create-the-tables } + +接著我們新增一個函式,使用 `SQLModel.metadata.create_all(engine)` 為所有「資料表模型」建立資料表。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} + +### 建立 Session 依賴 { #create-a-session-dependency } + +「`Session`」會在記憶體中保存物件並追蹤資料需要的任何變更,然後透過「`engine`」與資料庫溝通。 + +我們會用 `yield` 建立一個 FastAPI 的「依賴」,為每個請求提供一個新的 `Session`。這可確保每個請求只使用單一的 session。🤓 + +接著我們建立一個 `Annotated` 的依賴 `SessionDep`,讓後續使用這個依賴的程式碼更簡潔。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} + +### 在啟動時建立資料表 { #create-database-tables-on-startup } + +我們會在應用啟動時建立資料庫的資料表。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} + +這裡我們在應用的啟動事件中建立資料表。 + +在正式環境中,你大概會在啟動應用前使用遷移腳本來處理。🤓 + +/// tip | 提示 + +SQLModel 之後會提供包裝 Alembic 的遷移工具,但目前你可以直接使用 Alembic。 + +/// + +### 建立 Hero { #create-a-hero } + +因為每個 SQLModel 模型同時也是一個 Pydantic 模型,你可以在「型別標註」中像使用 Pydantic 模型一樣使用它。 + +例如,如果你宣告一個參數型別為 `Hero`,它會從「JSON body」中讀取。 + +同樣地,你也可以將它宣告為函式的「回傳型別」,然後在自動產生的 API 文件 UI 中就會顯示其資料結構。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} + +這裡我們使用 `SessionDep` 依賴(即一個 `Session`),把新的 `Hero` 加入 `Session` 實例,提交變更到資料庫,刷新 `hero` 的資料,然後回傳它。 + +### 讀取多個 Hero { #read-heroes } + +我們可以用 `select()` 從資料庫「讀取」多個 `Hero`。可以加入 `limit` 與 `offset` 來分頁。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} + +### 讀取單一 Hero { #read-one-hero } + +我們可以「讀取」單一的 `Hero`。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} + +### 刪除 Hero { #delete-a-hero } + +我們也可以「刪除」一個 `Hero`。 + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} + +### 執行應用 { #run-the-app } + +你可以執行應用: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +然後前往 `/docs` 的 UI,你會看到 FastAPI 使用這些模型來「文件化」API,也會用它們來「序列化」與「驗證」資料。 + +
+ +
+ +## 用多個模型更新應用 { #update-the-app-with-multiple-models } + +現在我們稍微「重構」一下這個應用,以提升「安全性」與「彈性」。 + +如果你檢查前一版的應用,在 UI 中你會看到,到目前為止它讓用戶端自己決定要建立的 `Hero` 的 `id`。😱 + +我們不該允許這樣,因為他們可能會覆蓋資料庫中我們已分配的 `id`。決定 `id` 應該由「後端」或「資料庫」來做,「不是用戶端」。 + +另外,我們為 hero 建立了 `secret_name`,但目前我們在各處都把它回傳出去,這一點都不「保密」... 😅 + +我們會透過加入一些「額外模型」來修正這些問題。這正是 SQLModel 大放異彩的地方。✨ + +### 建立多個模型 { #create-multiple-models } + +在 SQLModel 中,任何設了 `table=True` 的模型類別都是「資料表模型」。 + +而沒有設 `table=True` 的模型類別就是「資料模型」,這些其實就是 Pydantic 模型(只有一點小增強)。🤓 + +使用 SQLModel,我們可以利用「繼承」來「避免重複」在各種情況下一再宣告所有欄位。 + +#### `HeroBase` - 基底類別 { #herobase-the-base-class } + +先從 `HeroBase` 模型開始,它包含所有模型「共享」的欄位: + +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} + +#### `Hero` - 資料表模型 { #hero-the-table-model } + +接著建立 `Hero`,也就是實際的「資料表模型」,它包含不一定會出現在其他模型中的「額外欄位」: + +* `id` +* `secret_name` + +因為 `Hero` 繼承自 `HeroBase`,它「也」擁有 `HeroBase` 中宣告的「欄位」,因此 `Hero` 的完整欄位為: + +* `id` +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} + +#### `HeroPublic` - 公開的資料模型 { #heropublic-the-public-data-model } + +接下來建立 `HeroPublic` 模型,它是要「回傳」給 API 用戶端的模型。 + +它擁有與 `HeroBase` 相同的欄位,因此不會包含 `secret_name`。 + +終於,我們英雄的真實身分受保護了!🥷 + +它也重新宣告了 `id: int`。這麼做是與 API 用戶端訂立一個「契約」,讓他們可以確定 `id` 一定存在而且是 `int`(不會是 `None`)。 + +/// tip | 提示 + +讓回傳模型保證某個值一定存在、而且一定是 `int`(不是 `None`),對 API 用戶端非常有幫助。他們在有這個確信下可以寫出更簡單的程式碼。 + +此外,透過「自動產生的客戶端」也會有更簡潔的介面,讓要使用你 API 的開發者能有更好的開發體驗。😎 + +/// + +`HeroPublic` 中的欄位與 `HeroBase` 相同,僅 `id` 宣告為 `int`(非 `None`): + +* `id` +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} + +#### `HeroCreate` - 用於建立 Hero 的資料模型 { #herocreate-the-data-model-to-create-a-hero } + +現在我們建立 `HeroCreate` 模型,這是用來「驗證」用戶端送來資料的模型。 + +它具有與 `HeroBase` 相同的欄位,並且還有 `secret_name`。 + +接下來,當用戶端「建立新 hero」時,他們會送上 `secret_name`,它會被儲存在資料庫中,但這些祕密名稱不會在 API 中回傳給用戶端。 + +/// tip | 提示 + +這也就是你處理「密碼」的方式。接收它們,但不要在 API 中回傳。 + +你也應該在儲存前先對密碼做「雜湊」,「永遠不要以明文儲存」。 + +/// + +`HeroCreate` 的欄位有: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} + +#### `HeroUpdate` - 用於更新 Hero 的資料模型 { #heroupdate-the-data-model-to-update-a-hero } + +在前一版的應用中,我們沒有「更新 hero」的方式,但現在有了「多個模型」,我們就能做到。🎉 + +`HeroUpdate` 這個資料模型有點特別,它包含「建立新 hero 所需的所有欄位」,但所有欄位都是「可選的」(都有預設值)。這樣在更新時,你只需要送出想要更新的欄位即可。 + +因為所有欄位的「型別其實都改變了」(型別現在包含 `None`,而且預設值為 `None`),我們需要「重新宣告」它們。 + +其實不一定要繼承 `HeroBase`,因為我們會重新宣告所有欄位。我這裡保留繼承只是為了一致性,並非必要。這主要是個人偏好的問題。🤷 + +`HeroUpdate` 的欄位有: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} + +### 用 `HeroCreate` 建立並回傳 `HeroPublic` { #create-with-herocreate-and-return-a-heropublic } + +現在我們有了「多個模型」,可以更新應用中使用它們的部分。 + +我們在請求中接收 `HeroCreate`(資料模型),並由它建立一個 `Hero`(資料表模型)。 + +這個新的資料表模型 `Hero` 會有用戶端傳來的欄位,並且會由資料庫產生一個 `id`。 + +然後我們直接從函式回傳這個資料表模型 `Hero`。但因為我們用 `HeroPublic` 當作 `response_model`,FastAPI 會用 `HeroPublic` 來驗證與序列化資料。 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} + +/// tip | 提示 + +現在我們用 `response_model=HeroPublic`,而不是用回傳型別標註 `-> HeroPublic`,因為我們實際回傳的值其實「不是」`HeroPublic`。 + +如果我們宣告 `-> HeroPublic`,你的編輯器與 linter 會(理所當然地)抱怨你回傳的是 `Hero` 而不是 `HeroPublic`。 + +在 `response_model` 中宣告,就是要讓 FastAPI 去做它該做的事,而不影響型別標註,以及你的編輯器與其他工具提供的協助。 + +/// + +### 使用 `HeroPublic` 讀取多個 Hero { #read-heroes-with-heropublic } + +我們可以像先前一樣「讀取」多個 `Hero`。同樣地,我們使用 `response_model=list[HeroPublic]` 來確保資料被正確驗證與序列化。 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} + +### 使用 `HeroPublic` 讀取單一 Hero { #read-one-hero-with-heropublic } + +我們可以「讀取」單一 hero: + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} + +### 使用 `HeroUpdate` 更新 Hero { #update-a-hero-with-heroupdate } + +我們可以「更新 hero」。為此我們使用 HTTP 的 `PATCH` 操作。 + +在程式碼中,我們會取得一個只包含用戶端有傳送的資料的 `dict`,不包含只是因為有預設值而存在的欄位。為了達成這點,我們使用 `exclude_unset=True`。這是關鍵。🪄 + +然後我們使用 `hero_db.sqlmodel_update(hero_data)` 以 `hero_data` 的資料更新 `hero_db`。 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} + +### 再次刪除 Hero { #delete-a-hero-again } + +「刪除」 hero 基本上維持不變。 + +我們不會為了重構而重構一切。😅 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} + +### 再次執行應用 { #run-the-app-again } + +你可以再次執行應用: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +如果你前往 `/docs` 的 API UI,你會看到它已更新,建立 hero 時不再期待從用戶端接收 `id`,等等。 + +
+ +
+ +## 總結 { #recap } + +你可以使用 SQLModel 與 SQL 資料庫互動,並用「資料模型」與「資料表模型」讓程式碼更簡潔。 + +你可以在 SQLModel 文件學到更多內容,這裡還有一份更長的 使用 SQLModel 與 FastAPI 的教學。🚀 diff --git a/docs/zh-hant/docs/tutorial/static-files.md b/docs/zh-hant/docs/tutorial/static-files.md new file mode 100644 index 000000000..06b617676 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/static-files.md @@ -0,0 +1,40 @@ +# 靜態檔案 { #static-files } + +你可以使用 `StaticFiles` 從某個目錄自動提供靜態檔案。 + +## 使用 `StaticFiles` { #use-staticfiles } + +- 匯入 `StaticFiles`。 +- 在特定路徑上「掛載」一個 `StaticFiles()` 實例。 + +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} + +/// note | 技術細節 + +你也可以使用 `from starlette.staticfiles import StaticFiles`。 + +**FastAPI** 為了方便開發者,提供與 `starlette.staticfiles` 相同的介面作為 `fastapi.staticfiles`。但它其實是直接來自 Starlette。 + +/// + +### 什麼是「掛載」 { #what-is-mounting } + +「掛載(mounting)」是指在特定路徑下加入一個完整且「獨立」的應用,之後所有該路徑下的子路徑都由它處理。 + +這與使用 `APIRouter` 不同,因為被掛載的應用是完全獨立的。主應用的 OpenAPI 與文件不會包含掛載應用的任何內容,等等。 + +你可以在[進階使用者指南](../advanced/index.md){.internal-link target=_blank}中閱讀更多相關內容。 + +## 細節 { #details } + +第一個 `"/static"` 指的是這個「子應用」要被「掛載」的子路徑。因此,任何以 `"/static"` 開頭的路徑都會由它處理。 + +`directory="static"` 指向包含你靜態檔案的目錄名稱。 + +`name="static"` 為它指定一個可供 **FastAPI** 內部使用的名稱。 + +以上參數都不一定要是 "`static`",請依你的應用需求與細節調整。 + +## 更多資訊 { #more-info } + +如需更多細節與選項,請參考 Starlette 關於靜態檔案的文件。 diff --git a/docs/zh-hant/docs/tutorial/testing.md b/docs/zh-hant/docs/tutorial/testing.md new file mode 100644 index 000000000..0d49fd5d7 --- /dev/null +++ b/docs/zh-hant/docs/tutorial/testing.md @@ -0,0 +1,193 @@ +# 測試 { #testing } + +多虧了 Starlette,測試 **FastAPI** 應用既簡單又好用。 + +它是基於 HTTPX 打造,而 HTTPX 的設計又參考了 Requests,所以用起來非常熟悉、直覺。 + +借助它,你可以直接用 pytest 來測試 **FastAPI**。 + +## 使用 `TestClient` { #using-testclient } + +/// info + +要使用 `TestClient`,請先安裝 `httpx`。 + +請先建立並啟用一個[虛擬環境](../virtual-environments.md){.internal-link target=_blank},然後安裝,例如: + +```console +$ pip install httpx +``` + +/// + +匯入 `TestClient`。 + +建立一個 `TestClient`,把你的 **FastAPI** 應用傳入其中。 + +建立名稱以 `test_` 開頭的函式(這是 `pytest` 的慣例)。 + +像使用 `httpx` 一樣使用 `TestClient` 物件。 + +用簡單的 `assert` 敘述搭配標準的 Python 運算式來檢查(同樣是 `pytest` 的標準用法)。 + +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} + +/// tip + +注意測試函式是一般的 `def`,不是 `async def`。 + +而且對 client 的呼叫也都是一般呼叫,不需要使用 `await`。 + +這讓你可以直接使用 `pytest`,不必費心處理非同步。 + +/// + +/// note | 技術細節 + +你也可以使用 `from starlette.testclient import TestClient`。 + +**FastAPI** 為了方便開發者,也提供與 `starlette.testclient` 相同的 `fastapi.testclient`。但它其實直接來自 Starlette。 + +/// + +/// tip + +如果你想在測試中呼叫其他 `async` 函式,而不只是對 FastAPI 應用發送請求(例如非同步的資料庫函式),請參考進階教學中的[非同步測試](../advanced/async-tests.md){.internal-link target=_blank}。 + +/// + +## 分離測試 { #separating-tests } + +在真實專案中,你大概會把測試放在不同的檔案中。 + +你的 **FastAPI** 應用也可能由多個檔案/模組組成,等等。 + +### **FastAPI** 應用檔案 { #fastapi-app-file } + +假設你的檔案結構如[更大型的應用](bigger-applications.md){.internal-link target=_blank}所述: + +``` +. +├── app +│   ├── __init__.py +│   └── main.py +``` + +在 `main.py` 檔案中有你的 **FastAPI** 應用: + + +{* ../../docs_src/app_testing/app_a_py310/main.py *} + +### 測試檔案 { #testing-file } + +然後你可以建立一個 `test_main.py` 放你的測試。它可以與應用位於同一個 Python 套件(同一個包含 `__init__.py` 的目錄): + +``` hl_lines="5" +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +因為這個檔案在同一個套件中,你可以使用相對匯入,從 `main` 模組(`main.py`)匯入 `app` 這個物件: + +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} + + +...然後測試的程式碼就和先前一樣。 + +## 測試:進階範例 { #testing-extended-example } + +現在我們延伸這個範例並加入更多細節,看看如何測試不同部分。 + +### 擴充的 **FastAPI** 應用檔案 { #extended-fastapi-app-file } + +沿用先前相同的檔案結構: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +假設現在你的 **FastAPI** 應用所在的 `main.py` 有一些其他的路徑操作(path operations)。 + +它有一個可能回傳錯誤的 `GET` 操作。 + +它有一個可能回傳多種錯誤的 `POST` 操作。 + +兩個路徑操作都需要一個 `X-Token` 標頭(header)。 + +{* ../../docs_src/app_testing/app_b_an_py310/main.py *} + +### 擴充的測試檔案 { #extended-testing-file } + +接著你可以把 `test_main.py` 更新為擴充後的測試: + +{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *} + + +每當你需要在請求中讓 client 帶一些資料,但不確定該怎麼做時,你可以搜尋(Google)在 `httpx` 要如何傳遞,甚至用 Requests 怎麼做,因為 HTTPX 的設計是基於 Requests。 + +然後在你的測試中做一樣的事即可。 + +例如: + +* 要傳遞路徑或查詢參數,直接把它加在 URL 上。 +* 要傳遞 JSON 本文,將 Python 物件(例如 `dict`)傳給 `json` 參數。 +* 如果需要送出表單資料(Form Data)而不是 JSON,改用 `data` 參數。 +* 要傳遞標頭(headers),在 `headers` 參數中放一個 `dict`。 +* 對於 Cookie(cookies),在 `cookies` 參數中放一個 `dict`。 + +關於如何把資料傳給後端(使用 `httpx` 或 `TestClient`),更多資訊請參考 HTTPX 文件。 + +/// info + +請注意,`TestClient` 接收的是可轉為 JSON 的資料,而不是 Pydantic models。 + +如果你的測試裡有一個 Pydantic model,並想在測試時把它的資料送給應用,你可以使用[JSON 相容編碼器](encoder.md){.internal-link target=_blank}中介紹的 `jsonable_encoder`。 + +/// + +## 執行 { #run-it } + +接下來,你只需要安裝 `pytest`。 + +請先建立並啟用一個[虛擬環境](../virtual-environments.md){.internal-link target=_blank},然後安裝,例如: + +
+ +```console +$ pip install pytest + +---> 100% +``` + +
+ +它會自動偵測檔案與測試、執行它們,並把結果回報給你。 + +用以下指令執行測試: + +
+ +```console +$ pytest + +================ test session starts ================ +platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 +rootdir: /home/user/code/superawesome-cli/app +plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 +collected 6 items + +---> 100% + +test_main.py ...... [100%] + +================= 1 passed in 0.03s ================= +``` + +
diff --git a/docs/zh-hant/docs/virtual-environments.md b/docs/zh-hant/docs/virtual-environments.md index acd1d914e..74b8b9f8c 100644 --- a/docs/zh-hant/docs/virtual-environments.md +++ b/docs/zh-hant/docs/virtual-environments.md @@ -30,7 +30,7 @@ 首先,為你的專案建立一個目錄。 -我(指原作者 —— 譯者注)通常會在我的主目錄下建立一個名為 `code` 的目錄。 +我通常會在我的主目錄下建立一個名為 `code` 的目錄。 在這個目錄下,我再為每個專案建立一個目錄。 @@ -53,7 +53,7 @@ $ cd awesome-project ## 建立一個虛擬環境 { #create-a-virtual-environment } -在開始一個 Python 專案的**第一時間**,**在你的專案內部**建立一個虛擬環境。 +在開始一個 Python 專案的**第一時間**,**在你的專案內部**建立一個虛擬環境。 /// tip @@ -412,7 +412,7 @@ Hello World ## 設定編輯器 { #configure-your-editor } -你可能會用到編輯器,請確保設定它使用你建立的相同虛擬環境(它可能會自動偵測到),以便你可以獲得自動完成和内嵌錯誤提示。 +你可能會用到編輯器,請確保設定它使用你建立的相同虛擬環境(它可能會自動偵測到),以便你可以獲得自動完成和內嵌錯誤提示。 例如: From 12f7e656709d570110dd26e6187b27e40442b8a9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 14 Feb 2026 08:15:47 +0000 Subject: [PATCH 135/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5ba810aea..5e1eaf937 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for zh-hant (update-all and add-missing). PR [#14921](https://github.com/fastapi/fastapi/pull/14921) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for fr (update-all and add-missing). PR [#14920](https://github.com/fastapi/fastapi/pull/14920) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for de (update-all) . PR [#14910](https://github.com/fastapi/fastapi/pull/14910) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for ja (update-all). PR [#14916](https://github.com/fastapi/fastapi/pull/14916) by [@YuriiMotov](https://github.com/YuriiMotov). From ec35d6d380a243fa693633fd40d60a465c47f7c9 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Sat, 14 Feb 2026 09:43:14 +0100 Subject: [PATCH 136/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20uk=20(add-missing)=20(#14922)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add missing * 🎨 Auto format --------- Co-authored-by: github-actions[bot] --- docs/uk/docs/_llm-test.md | 502 ++++++++++ docs/uk/docs/about/index.md | 3 + docs/uk/docs/advanced/additional-responses.md | 247 +++++ .../docs/advanced/additional-status-codes.md | 41 + .../uk/docs/advanced/advanced-dependencies.md | 163 ++++ .../uk/docs/advanced/advanced-python-types.md | 61 ++ docs/uk/docs/advanced/async-tests.md | 99 ++ docs/uk/docs/advanced/behind-a-proxy.md | 466 ++++++++++ docs/uk/docs/advanced/custom-response.md | 312 +++++++ docs/uk/docs/advanced/dataclasses.md | 95 ++ docs/uk/docs/advanced/events.md | 165 ++++ docs/uk/docs/advanced/generate-clients.md | 208 +++++ docs/uk/docs/advanced/index.md | 21 + docs/uk/docs/advanced/middleware.md | 97 ++ docs/uk/docs/advanced/openapi-callbacks.md | 186 ++++ docs/uk/docs/advanced/openapi-webhooks.md | 55 ++ .../path-operation-advanced-configuration.md | 172 ++++ .../advanced/response-change-status-code.md | 31 + docs/uk/docs/advanced/response-cookies.md | 51 ++ docs/uk/docs/advanced/response-directly.md | 65 ++ docs/uk/docs/advanced/response-headers.md | 41 + .../docs/advanced/security/http-basic-auth.md | 107 +++ docs/uk/docs/advanced/security/index.md | 19 + .../docs/advanced/security/oauth2-scopes.md | 274 ++++++ docs/uk/docs/advanced/settings.md | 302 ++++++ docs/uk/docs/advanced/sub-applications.md | 67 ++ docs/uk/docs/advanced/templates.md | 126 +++ docs/uk/docs/advanced/testing-dependencies.md | 53 ++ docs/uk/docs/advanced/testing-events.md | 11 + docs/uk/docs/advanced/testing-websockets.md | 13 + .../docs/advanced/using-request-directly.md | 55 ++ docs/uk/docs/advanced/websockets.md | 186 ++++ docs/uk/docs/advanced/wsgi.md | 51 ++ docs/uk/docs/async.md | 444 +++++++++ docs/uk/docs/benchmarks.md | 34 + docs/uk/docs/deployment/cloud.md | 24 + docs/uk/docs/deployment/concepts.md | 321 +++++++ docs/uk/docs/deployment/docker.md | 618 +++++++++++++ docs/uk/docs/deployment/fastapicloud.md | 65 ++ docs/uk/docs/deployment/https.md | 231 +++++ docs/uk/docs/deployment/index.md | 23 + docs/uk/docs/deployment/manually.md | 157 ++++ docs/uk/docs/deployment/server-workers.md | 139 +++ docs/uk/docs/deployment/versions.md | 93 ++ docs/uk/docs/environment-variables.md | 298 ++++++ docs/uk/docs/help-fastapi.md | 256 ++++++ docs/uk/docs/history-design-future.md | 79 ++ .../authentication-error-status-code.md | 17 + docs/uk/docs/how-to/conditional-openapi.md | 56 ++ docs/uk/docs/how-to/configure-swagger-ui.md | 70 ++ docs/uk/docs/how-to/custom-docs-ui-assets.md | 185 ++++ .../docs/how-to/custom-request-and-route.md | 109 +++ docs/uk/docs/how-to/extending-openapi.md | 80 ++ docs/uk/docs/how-to/general.md | 39 + docs/uk/docs/how-to/graphql.md | 60 ++ docs/uk/docs/how-to/index.md | 13 + ...migrate-from-pydantic-v1-to-pydantic-v2.md | 135 +++ .../docs/how-to/separate-openapi-schemas.md | 101 ++ docs/uk/docs/how-to/testing-database.md | 7 + docs/uk/docs/project-generation.md | 28 + docs/uk/docs/resources/index.md | 3 + docs/uk/docs/translation-banner.md | 11 + docs/uk/docs/tutorial/bigger-applications.md | 504 ++++++++++ .../dependencies/classes-as-dependencies.md | 288 ++++++ ...pendencies-in-path-operation-decorators.md | 69 ++ .../dependencies/dependencies-with-yield.md | 289 ++++++ .../dependencies/global-dependencies.md | 15 + docs/uk/docs/tutorial/dependencies/index.md | 250 +++++ .../tutorial/dependencies/sub-dependencies.md | 105 +++ docs/uk/docs/tutorial/extra-models.md | 211 +++++ .../tutorial/path-operation-configuration.md | 107 +++ docs/uk/docs/tutorial/security/first-steps.md | 203 +++++ .../tutorial/security/get-current-user.md | 105 +++ docs/uk/docs/tutorial/security/oauth2-jwt.md | 277 ++++++ .../docs/tutorial/security/simple-oauth2.md | 289 ++++++ docs/uk/docs/tutorial/sql-databases.md | 357 ++++++++ docs/uk/docs/virtual-environments.md | 862 ++++++++++++++++++ 77 files changed, 11972 insertions(+) create mode 100644 docs/uk/docs/_llm-test.md create mode 100644 docs/uk/docs/about/index.md create mode 100644 docs/uk/docs/advanced/additional-responses.md create mode 100644 docs/uk/docs/advanced/additional-status-codes.md create mode 100644 docs/uk/docs/advanced/advanced-dependencies.md create mode 100644 docs/uk/docs/advanced/advanced-python-types.md create mode 100644 docs/uk/docs/advanced/async-tests.md create mode 100644 docs/uk/docs/advanced/behind-a-proxy.md create mode 100644 docs/uk/docs/advanced/custom-response.md create mode 100644 docs/uk/docs/advanced/dataclasses.md create mode 100644 docs/uk/docs/advanced/events.md create mode 100644 docs/uk/docs/advanced/generate-clients.md create mode 100644 docs/uk/docs/advanced/index.md create mode 100644 docs/uk/docs/advanced/middleware.md create mode 100644 docs/uk/docs/advanced/openapi-callbacks.md create mode 100644 docs/uk/docs/advanced/openapi-webhooks.md create mode 100644 docs/uk/docs/advanced/path-operation-advanced-configuration.md create mode 100644 docs/uk/docs/advanced/response-change-status-code.md create mode 100644 docs/uk/docs/advanced/response-cookies.md create mode 100644 docs/uk/docs/advanced/response-directly.md create mode 100644 docs/uk/docs/advanced/response-headers.md create mode 100644 docs/uk/docs/advanced/security/http-basic-auth.md create mode 100644 docs/uk/docs/advanced/security/index.md create mode 100644 docs/uk/docs/advanced/security/oauth2-scopes.md create mode 100644 docs/uk/docs/advanced/settings.md create mode 100644 docs/uk/docs/advanced/sub-applications.md create mode 100644 docs/uk/docs/advanced/templates.md create mode 100644 docs/uk/docs/advanced/testing-dependencies.md create mode 100644 docs/uk/docs/advanced/testing-events.md create mode 100644 docs/uk/docs/advanced/testing-websockets.md create mode 100644 docs/uk/docs/advanced/using-request-directly.md create mode 100644 docs/uk/docs/advanced/websockets.md create mode 100644 docs/uk/docs/advanced/wsgi.md create mode 100644 docs/uk/docs/async.md create mode 100644 docs/uk/docs/benchmarks.md create mode 100644 docs/uk/docs/deployment/cloud.md create mode 100644 docs/uk/docs/deployment/concepts.md create mode 100644 docs/uk/docs/deployment/docker.md create mode 100644 docs/uk/docs/deployment/fastapicloud.md create mode 100644 docs/uk/docs/deployment/https.md create mode 100644 docs/uk/docs/deployment/index.md create mode 100644 docs/uk/docs/deployment/manually.md create mode 100644 docs/uk/docs/deployment/server-workers.md create mode 100644 docs/uk/docs/deployment/versions.md create mode 100644 docs/uk/docs/environment-variables.md create mode 100644 docs/uk/docs/help-fastapi.md create mode 100644 docs/uk/docs/history-design-future.md create mode 100644 docs/uk/docs/how-to/authentication-error-status-code.md create mode 100644 docs/uk/docs/how-to/conditional-openapi.md create mode 100644 docs/uk/docs/how-to/configure-swagger-ui.md create mode 100644 docs/uk/docs/how-to/custom-docs-ui-assets.md create mode 100644 docs/uk/docs/how-to/custom-request-and-route.md create mode 100644 docs/uk/docs/how-to/extending-openapi.md create mode 100644 docs/uk/docs/how-to/general.md create mode 100644 docs/uk/docs/how-to/graphql.md create mode 100644 docs/uk/docs/how-to/index.md create mode 100644 docs/uk/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md create mode 100644 docs/uk/docs/how-to/separate-openapi-schemas.md create mode 100644 docs/uk/docs/how-to/testing-database.md create mode 100644 docs/uk/docs/project-generation.md create mode 100644 docs/uk/docs/resources/index.md create mode 100644 docs/uk/docs/translation-banner.md create mode 100644 docs/uk/docs/tutorial/bigger-applications.md create mode 100644 docs/uk/docs/tutorial/dependencies/classes-as-dependencies.md create mode 100644 docs/uk/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md create mode 100644 docs/uk/docs/tutorial/dependencies/dependencies-with-yield.md create mode 100644 docs/uk/docs/tutorial/dependencies/global-dependencies.md create mode 100644 docs/uk/docs/tutorial/dependencies/index.md create mode 100644 docs/uk/docs/tutorial/dependencies/sub-dependencies.md create mode 100644 docs/uk/docs/tutorial/extra-models.md create mode 100644 docs/uk/docs/tutorial/path-operation-configuration.md create mode 100644 docs/uk/docs/tutorial/security/first-steps.md create mode 100644 docs/uk/docs/tutorial/security/get-current-user.md create mode 100644 docs/uk/docs/tutorial/security/oauth2-jwt.md create mode 100644 docs/uk/docs/tutorial/security/simple-oauth2.md create mode 100644 docs/uk/docs/tutorial/sql-databases.md create mode 100644 docs/uk/docs/virtual-environments.md diff --git a/docs/uk/docs/_llm-test.md b/docs/uk/docs/_llm-test.md new file mode 100644 index 000000000..0dbfaa3db --- /dev/null +++ b/docs/uk/docs/_llm-test.md @@ -0,0 +1,502 @@ +# Тестовий файл LLM { #llm-test-file } + +Цей документ перевіряє, чи розуміє LLM, який перекладає документацію, `general_prompt` у `scripts/translate.py` та мовно-специфічну підсказку в `docs/{language code}/llm-prompt.md`. Мовно-специфічна підсказка додається до `general_prompt`. + +Тести, додані тут, побачать усі автори мовно-специфічних підсказок. + +Використовуйте так: + +* Майте мовно-специфічну підсказку - `docs/{language code}/llm-prompt.md`. +* Зробіть свіжий переклад цього документа на бажану цільову мову (див., наприклад, команду `translate-page` у `translate.py`). Це створить переклад у `docs/{language code}/docs/_llm-test.md`. +* Перевірте, чи все гаразд у перекладі. +* За потреби покращіть вашу мовно-специфічну підсказку, загальну підсказку або англійський документ. +* Потім вручну виправте решту проблем у перекладі, щоб він був якісним. +* Перекладіть повторно, маючи якісний переклад на місці. Ідеальний результат - коли LLM більше не вносить змін до перекладу. Це означає, що загальна підсказка та ваша мовно-специфічна підсказка настільки добрі, наскільки це можливо (інколи він робитиме кілька, здавалося б, випадкових змін, причина в тому, що LLM-и не є детерміністичними алгоритмами). + +Тести: + +## Фрагменти коду { #code-snippets } + +//// tab | Тест + +Це фрагмент коду: `foo`. А це ще один фрагмент коду: `bar`. І ще один: `baz quux`. + +//// + +//// tab | Інформація + +Вміст фрагментів коду слід залишати без змін. + +Див. розділ `### Content of code snippets` в загальній підсказці в `scripts/translate.py`. + +//// + +## Цитати { #quotes } + +//// tab | Тест + +Вчора мій друг написав: «Якщо ви правильно напишете "incorrectly", ви написали це неправильно». На що я відповів: «Правильно, але 'incorrectly' - це неправильно, а не '"incorrectly"'». + +/// note | Примітка + +LLM, ймовірно, перекладе це неправильно. Цікаво лише те, чи збереже він виправлений переклад під час повторного перекладу. + +/// + +//// + +//// tab | Інформація + +Автор підсказки може обрати, чи конвертувати нейтральні лапки в типографські. Можна залишити як є. + +Див., наприклад, розділ `### Quotes` у `docs/de/llm-prompt.md`. + +//// + +## Лапки у фрагментах коду { #quotes-in-code-snippets } + +//// tab | Тест + +`pip install "foo[bar]"` + +Приклади строкових літералів у фрагментах коду: `"this"`, `'that'`. + +Складний приклад строкових літералів у фрагментах коду: `f"I like {'oranges' if orange else "apples"}"` + +Хардкор: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` + +//// + +//// tab | Інформація + +... Однак лапки всередині фрагментів коду мають залишатися як є. + +//// + +## Блоки коду { #code-blocks } + +//// tab | Тест + +Приклад коду Bash... + +```bash +# Вивести привітання всесвіту +echo "Hello universe" +``` + +...і приклад коду консолі... + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +...і ще один приклад коду консолі... + +```console +// Створіть каталог "code" +$ mkdir code +// Перейдіть до цього каталогу +$ cd code +``` + +...і приклад коду Python... + +```Python +wont_work() # Це не спрацює 😱 +works(foo="bar") # Це працює 🎉 +``` + +...і це все. + +//// + +//// tab | Інформація + +Код у блоках коду не слід змінювати, за винятком коментарів. + +Див. розділ `### Content of code blocks` в загальній підсказці в `scripts/translate.py`. + +//// + +## Вкладки та кольорові блоки { #tabs-and-colored-boxes } + +//// tab | Тест + +/// info | Інформація +Some text +/// + +/// note | Примітка +Some text +/// + +/// note | Технічні деталі +Some text +/// + +/// check | Перевірте +Some text +/// + +/// tip | Порада +Some text +/// + +/// warning | Попередження +Some text +/// + +/// danger | Обережно +Some text +/// + +//// + +//// tab | Інформація + +У вкладках і блоках `Info`/`Note`/`Warning`/тощо необхідно додати переклад їхнього заголовка після вертикальної риски (`|`). + +Див. розділи `### Special blocks` і `### Tab blocks` в загальній підсказці в `scripts/translate.py`. + +//// + +## Веб- і внутрішні посилання { #web-and-internal-links } + +//// tab | Тест + +Текст посилання має бути перекладений, адреса посилання має залишитися незмінною: + +* [Посилання на заголовок вище](#code-snippets) +* [Внутрішнє посилання](index.md#installation){.internal-link target=_blank} +* Зовнішнє посилання +* Посилання на стиль +* Посилання на скрипт +* Посилання на зображення + +Текст посилання має бути перекладений, адреса посилання має вказувати на переклад: + +* Посилання на FastAPI + +//// + +//// tab | Інформація + +Посилання слід перекладати, але їхні адреси мають лишатися незмінними. Виняток - абсолютні посилання на сторінки документації FastAPI. У такому випадку вони мають вказувати на переклад. + +Див. розділ `### Links` в загальній підсказці в `scripts/translate.py`. + +//// + +## Елементи HTML «abbr» { #html-abbr-elements } + +//// tab | Тест + +Ось деякі речі, обгорнуті в елементи HTML «abbr» (деякі вигадані): + +### Скорочення містить повну фразу { #the-abbr-gives-a-full-phrase } + +* GTD +* lt +* XWT +* PSGI + +### Скорочення містить повну фразу та пояснення { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* MDN +* I/O. + +//// + +//// tab | Інформація + +Атрибути "title" елементів "abbr" перекладаються за певними інструкціями. + +Переклади можуть додавати власні елементи "abbr", які LLM не повинен прибирати. Наприклад, щоб пояснити англійські слова. + +Див. розділ `### HTML abbr elements` в загальній підсказці в `scripts/translate.py`. + +//// + +## Елементи HTML «dfn» { #html-dfn-elements } + +* кластер +* Глибоке навчання + +## Заголовки { #headings } + +//// tab | Тест + +### Розробка вебзастосунку - навчальний посібник { #develop-a-webapp-a-tutorial } + +Привіт. + +### Підказки типів та - анотації { #type-hints-and-annotations } + +Ще раз привіт. + +### Супер- та підкласи { #super-and-subclasses } + +Ще раз привіт. + +//// + +//// tab | Інформація + +Єдине жорстке правило для заголовків - LLM має залишати хеш-частину в фігурних дужках незмінною, щоб посилання не ламалися. + +Див. розділ `### Headings` у загальній підсказці в `scripts/translate.py`. + +Для деяких мовно-специфічних інструкцій див., наприклад, розділ `### Headings` у `docs/de/llm-prompt.md`. + +//// + +## Терміни, що використовуються в документації { #terms-used-in-the-docs } + +//// tab | Тест + +* ви +* ваш + +* напр. +* тощо + +* `foo` як `int` +* `bar` як `str` +* `baz` як `list` + +* навчальний посібник - Керівництво користувача +* просунутий посібник користувача +* документація SQLModel +* документація API +* автоматична документація + +* Наука про дані +* глибоке навчання +* машинне навчання +* впровадження залежностей +* HTTP базова автентифікація +* HTTP дайджест +* формат ISO +* стандарт Схеми JSON +* Схема JSON +* визначення схеми +* потік паролю +* мобільний + +* застаріле +* спроєктовано +* недійсний +* на льоту +* стандарт +* типове +* чутливий до регістру +* нечутливий до регістру + +* обслуговувати застосунок +* обслуговувати сторінку + +* застосунок +* застосунок + +* запит +* відповідь +* відповідь з помилкою + +* операція шляху +* декоратор операції шляху +* функція операції шляху + +* тіло +* тіло запиту +* тіло відповіді +* тіло JSON +* тіло форми +* тіло функції + +* параметр +* параметр тіла +* параметр шляху +* параметр запиту +* параметр кукі +* параметр заголовка +* параметр форми +* параметр функції + +* подія +* подія запуску +* запуск сервера +* подія вимкнення +* подія тривалості життя + +* обробник +* обробник події +* обробник винятків +* обробляти + +* модель +* модель Pydantic +* модель даних +* модель бази даних +* модель форми +* об'єкт моделі + +* клас +* базовий клас +* батьківський клас +* підклас +* дочірній клас +* споріднений клас +* метод класу + +* заголовок +* заголовки +* заголовок авторизації +* заголовок `Authorization` +* направлений заголовок + +* система впровадження залежностей +* залежність +* залежний +* залежний + +* I/O-обмежений +* CPU-обмежений +* рівночасність +* паралелізм +* багатопроцесорність + +* змінна оточення +* змінна оточення +* `PATH` +* змінна `PATH` + +* автентифікація +* постачальник автентифікації +* авторизація +* форма авторизації +* постачальник авторизації +* користувач автентифікується +* система автентифікує користувача + +* CLI +* інтерфейс командного рядка + +* сервер +* клієнт + +* хмарний постачальник +* хмарний сервіс + +* розробка +* етапи розробки + +* словник +* словник +* перелік +* перелік +* елемент переліку + +* кодувальник +* декодувальник +* кодувати +* декодувати + +* виняток +* породжувати + +* вираз +* оператор + +* фронтенд +* бекенд + +* обговорення GitHub +* проблема GitHub + +* продуктивність +* оптимізація продуктивності + +* тип, що повертається +* повернене значення + +* безпека +* схема безпеки + +* завдання +* фонове завдання +* функція завдання + +* шаблон +* рушій шаблонів + +* анотація типу +* підказка типу + +* серверний працівник +* працівник Uvicorn +* працівник Gunicorn +* процес працівника +* клас працівника +* робоче навантаження + +* розгортання +* розгортати + +* SDK +* набір для розробки програмного забезпечення + +* `APIRouter` +* `requirements.txt` +* токен носія +* несумісна зміна +* помилка +* кнопка +* викликаємий +* код +* фіксація +* менеджер контексту +* співпрограма +* сеанс бази даних +* диск +* домен +* рушій +* фальшивий X +* метод HTTP GET +* предмет +* бібліотека +* тривалість життя +* блокування +* проміжне програмне забезпечення +* мобільний застосунок +* модуль +* монтування +* мережа +* джерело +* переписування +* корисне навантаження +* процесор +* властивість +* представник +* запит на витяг +* запит +* пам'ять з довільним доступом +* віддалена машина +* код статусу +* строка +* мітка +* веб-фреймворк +* дика карта +* повертати +* перевіряти + +//// + +//// tab | Інформація + +Це неповний і не нормативний список (переважно) технічних термінів, що зустрічаються в документації. Він може бути корисним автору підсказки, щоб зрозуміти, для яких термінів LLM потрібна допомога. Наприклад, коли він постійно повертає хороший переклад до менш вдалого. Або коли йому складно відмінювати термін вашою мовою. + +Див., наприклад, розділ `### List of English terms and their preferred German translations` у `docs/de/llm-prompt.md`. + +//// diff --git a/docs/uk/docs/about/index.md b/docs/uk/docs/about/index.md new file mode 100644 index 000000000..198fd828e --- /dev/null +++ b/docs/uk/docs/about/index.md @@ -0,0 +1,3 @@ +# Про { #about } + +Про FastAPI, його проєктування, натхнення та інше. 🤓 diff --git a/docs/uk/docs/advanced/additional-responses.md b/docs/uk/docs/advanced/additional-responses.md new file mode 100644 index 000000000..089967a51 --- /dev/null +++ b/docs/uk/docs/advanced/additional-responses.md @@ -0,0 +1,247 @@ +# Додаткові відповіді в OpenAPI { #additional-responses-in-openapi } + +/// warning | Попередження + +Це доволі просунута тема. + +Якщо ви лише починаєте з **FastAPI**, ймовірно, вам це не потрібно. + +/// + +Ви можете оголосити додаткові відповіді з додатковими кодами статусу, типами медіа, описами тощо. + +Ці додаткові відповіді буде включено до схеми OpenAPI, тож вони з'являться і в документації API. + +Але для таких додаткових відповідей потрібно повертати `Response` на кшталт `JSONResponse` безпосередньо, із потрібним кодом статусу та вмістом. + +## Додаткова відповідь з `model` { #additional-response-with-model } + +Ви можете передати вашим декораторам операцій шляху параметр `responses`. + +Він приймає `dict`: ключі - це коди статусу для кожної відповіді (наприклад, `200`), а значення - інші `dict` з інформацією для кожної з них. + +Кожен із цих словників відповіді може мати ключ `model`, що містить Pydantic-модель, подібно до `response_model`. + +**FastAPI** візьме цю модель, згенерує її Схему JSON і додасть у відповідне місце в OpenAPI. + +Наприклад, щоб оголосити іншу відповідь з кодом статусу `404` і Pydantic-моделлю `Message`, ви можете написати: + +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} + +/// note | Примітка + +Майте на увазі, що потрібно повертати `JSONResponse` безпосередньо. + +/// + +/// info | Інформація + +Ключ `model` не є частиною OpenAPI. + +**FastAPI** візьме звідти Pydantic-модель, згенерує Схему JSON і помістить у відповідне місце. + +Відповідне місце це: + +- У ключі `content`, значенням якого є інший JSON-об'єкт (`dict`), що містить: + - Ключ із типом медіа, напр. `application/json`, значенням якого є інший JSON-об'єкт, що містить: + - Ключ `schema`, значенням якого є Схема JSON з моделі - ось це і є правильне місце. + - **FastAPI** додає тут посилання на глобальні Схеми JSON в іншому місці вашого OpenAPI замість прямого включення. Так інші застосунки та клієнти можуть напряму використовувати ці Схеми JSON, надавати кращі інструменти генерації коду тощо. + +/// + +Згенеровані відповіді в OpenAPI для цієї операції шляху будуть такими: + +```JSON hl_lines="3-12" +{ + "responses": { + "404": { + "description": "Additional Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } +} +``` + +Схеми посилаються на інше місце всередині схеми OpenAPI: + +```JSON hl_lines="4-16" +{ + "components": { + "schemas": { + "Message": { + "title": "Message", + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "title": "Message", + "type": "string" + } + } + }, + "Item": { + "title": "Item", + "required": [ + "id", + "value" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + } + }, + "ValidationError": { + "title": "ValidationError", + "required": [ + "loc", + "msg", + "type" + ], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } +} +``` + +## Додаткові типи медіа для основної відповіді { #additional-media-types-for-the-main-response } + +Можна використати цей самий параметр `responses`, щоб додати різні типи медіа для тієї ж основної відповіді. + +Наприклад, можна додати додатковий тип медіа `image/png`, оголосивши, що ваша операція шляху може повертати JSON-об'єкт (з типом медіа `application/json`) або PNG-зображення: + +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} + +/// note | Примітка + +Зверніть увагу, що потрібно повертати зображення безпосередньо за допомогою `FileResponse`. + +/// + +/// info | Інформація + +Поки ви явно не вкажете інший тип медіа в параметрі `responses`, FastAPI вважатиме, що відповідь має той самий тип медіа, що й основний клас відповіді (типово `application/json`). + +Але якщо ви вказали власний клас відповіді з `None` як типом медіа, FastAPI використає `application/json` для будь-якої додаткової відповіді, що має пов'язану модель. + +/// + +## Комбінування інформації { #combining-information } + +Ви також можете поєднувати інформацію про відповіді з кількох місць, зокрема з параметрів `response_model`, `status_code` і `responses`. + +Ви можете оголосити `response_model`, використовуючи типовий код статусу `200` (або власний за потреби), а потім оголосити додаткову інформацію для цієї ж відповіді в `responses`, безпосередньо в схемі OpenAPI. + +**FastAPI** збереже додаткову інформацію з `responses` і поєднає її зі Схемою JSON з вашої моделі. + +Наприклад, ви можете оголосити відповідь з кодом статусу `404`, яка використовує Pydantic-модель і має власний `description`. + +І відповідь з кодом статусу `200`, яка використовує ваш `response_model`, але містить власний `example`: + +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} + +Усе це буде поєднано та включено до вашого OpenAPI і показано в документації API: + + + +## Комбінуйте попередньо визначені та власні відповіді { #combine-predefined-responses-and-custom-ones } + +Можливо, ви захочете мати кілька попередньо визначених відповідей, що застосовуються до багатьох операцій шляху, але поєднувати їх із власними відповідями, потрібними для кожної операції шляху. + +Для таких випадків можна скористатися прийомом Python «розпакування» `dict` за допомогою `**dict_to_unpack`: + +```Python +old_dict = { + "old key": "old value", + "second old key": "second old value", +} +new_dict = {**old_dict, "new key": "new value"} +``` + +Тут `new_dict` міститиме всі пари ключ-значення з `old_dict` плюс нову пару ключ-значення: + +```Python +{ + "old key": "old value", + "second old key": "second old value", + "new key": "new value", +} +``` + +Цей прийом можна використати, щоб перевикористовувати деякі попередньо визначені відповіді у ваших операціях шляху та поєднувати їх із додатковими власними. + +Наприклад: + +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} + +## Докладніше про відповіді OpenAPI { #more-information-about-openapi-responses } + +Щоб побачити, що саме можна включати у відповіді, ознайомтеся з цими розділами специфікації OpenAPI: + +- Об'єкт відповідей OpenAPI, він включає `Response Object`. +- Об'єкт відповіді OpenAPI, ви можете включити будь-що з цього безпосередньо в кожну відповідь у параметрі `responses`. Зокрема `description`, `headers`, `content` (усередині нього ви оголошуєте різні типи медіа та Схеми JSON) і `links`. diff --git a/docs/uk/docs/advanced/additional-status-codes.md b/docs/uk/docs/advanced/additional-status-codes.md new file mode 100644 index 000000000..afba933e2 --- /dev/null +++ b/docs/uk/docs/advanced/additional-status-codes.md @@ -0,0 +1,41 @@ +# Додаткові коди статусу { #additional-status-codes } + +За замовчуванням **FastAPI** повертатиме відповіді за допомогою `JSONResponse`, поміщаючи вміст, який ви повертаєте з вашої *операції шляху*, у цей `JSONResponse`. + +Він використовуватиме код статусу за замовчуванням або той, який ви встановите у своїй *операції шляху*. + +## Додаткові коди статусу { #additional-status-codes_1 } + +Якщо ви хочете повертати додаткові коди статусу, окрім основного, зробіть це, повертаючи `Response` безпосередньо, наприклад `JSONResponse`, і встановіть додатковий код статусу напряму. + +Наприклад, припустімо, ви хочете мати *операцію шляху*, яка дозволяє оновлювати предмети та повертає код статусу HTTP 200 «OK» у разі успіху. + +Але ви також хочете приймати нові предмети. І коли таких предметів раніше не існувало, операція створює їх і повертає код статусу HTTP 201 «Created». + +Щоб це реалізувати, імпортуйте `JSONResponse` і поверніть ваш вміст безпосередньо там, встановивши потрібний `status_code`: + +{* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} + +/// warning | Попередження + +Коли ви повертаєте `Response` безпосередньо, як у прикладі вище, він і буде повернений безпосередньо. + +Він не буде серіалізований за допомогою моделі тощо. + +Переконайтеся, що він містить саме ті дані, які вам потрібні, і що значення є коректним JSON (якщо ви використовуєте `JSONResponse`). + +/// + +/// note | Технічні деталі + +Ви також можете використати `from starlette.responses import JSONResponse`. + +**FastAPI** надає ті самі `starlette.responses` як `fastapi.responses` просто для вашої зручності як розробника. Але більшість доступних відповідей надходить безпосередньо зі Starlette. Те саме і зі `status`. + +/// + +## OpenAPI і документація API { #openapi-and-api-docs } + +Якщо ви повертаєте додаткові коди статусу та відповіді безпосередньо, вони не будуть включені до схеми OpenAPI (документації API), адже FastAPI не має способу заздалегідь знати, що саме ви повернете. + +Але ви можете задокументувати це у своєму коді, використовуючи: [Додаткові відповіді](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/uk/docs/advanced/advanced-dependencies.md b/docs/uk/docs/advanced/advanced-dependencies.md new file mode 100644 index 000000000..0c6f8cbb3 --- /dev/null +++ b/docs/uk/docs/advanced/advanced-dependencies.md @@ -0,0 +1,163 @@ +# Просунуті залежності { #advanced-dependencies } + +## Параметризовані залежності { #parameterized-dependencies } + +Усі залежності, які ми бачили, - це фіксована функція або клас. + +Але можуть бути випадки, коли ви хочете мати змогу задавати параметри залежності, не оголошуючи багато різних функцій або класів. + +Уявімо, що ми хочемо мати залежність, яка перевіряє, чи параметр запиту `q` містить певний фіксований вміст. + +Але ми хочемо мати змогу параметризувати цей фіксований вміст. + +## Екземпляр «callable» { #a-callable-instance } + +У Python є спосіб зробити екземпляр класу «callable». + +Не сам клас (який уже є «callable»), а екземпляр цього класу. + +Щоб це зробити, оголошуємо метод `__call__`: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} + +У цьому випадку саме `__call__` **FastAPI** використає для перевірки додаткових параметрів і підзалежностей, і саме його буде викликано, щоб передати значення параметру у вашу *функцію операції шляху* пізніше. + +## Параметризувати екземпляр { #parameterize-the-instance } + +Тепер ми можемо використати `__init__`, щоб оголосити параметри екземпляра, які можна застосувати для «параметризації» залежності: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} + +У цьому випадку **FastAPI** ніколи не торкається `__init__` і не покладається на нього - ми використовуватимемо його безпосередньо у своєму коді. + +## Створити екземпляр { #create-an-instance } + +Ми можемо створити екземпляр цього класу так: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} + +Таким чином ми «параметризуємо» нашу залежність, яка тепер містить «bar» як атрибут `checker.fixed_content`. + +## Використовувати екземпляр як залежність { #use-the-instance-as-a-dependency } + +Далі ми можемо використати цей `checker` у `Depends(checker)` замість `Depends(FixedContentQueryChecker)`, адже залежністю є екземпляр `checker`, а не сам клас. + +Коли залежність розв'язується, **FastAPI** викличе цей `checker` так: + +```Python +checker(q="somequery") +``` + +...і передасть усе, що він поверне, як значення залежності у нашій *функції операції шляху* як параметр `fixed_content_included`: + +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} + +/// tip | Порада + +Усе це може здаватися надуманим. І поки що може бути не дуже зрозуміло, навіщо це корисно. + +Ці приклади навмисно прості, але показують, як усе працює. + +У розділах про безпеку є утилітарні функції, реалізовані таким самим способом. + +Якщо ви все це зрозуміли, ви вже знаєте, як під капотом працюють ті утиліти для безпеки. + +/// + +## Залежності з `yield`, `HTTPException`, `except` та фоновими задачами { #dependencies-with-yield-httpexception-except-and-background-tasks } + +/// warning | Попередження + +Найімовірніше, вам не знадобляться ці технічні деталі. + +Вони корисні головно, якщо у вас був застосунок FastAPI старіший за 0.121.0 і ви стикаєтеся з проблемами залежностей із `yield`. + +/// + +Залежності з `yield` еволюціонували з часом, щоб охопити різні сценарії використання та виправити деякі проблеми, ось підсумок змін. + +### Залежності з `yield` і `scope` { #dependencies-with-yield-and-scope } + +У версії 0.121.0 **FastAPI** додано підтримку `Depends(scope="function")` для залежностей з `yield`. + +З `Depends(scope="function")` завершальний код після `yield` виконується одразу після завершення *функції операції шляху*, до того як відповідь буде надіслана клієнту. + +А при використанні `Depends(scope="request")` (типове значення) завершальний код після `yield` виконується після відправлення відповіді. + +Докладніше читайте в документації: [Залежності з `yield` - Ранній вихід і `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope). + +### Залежності з `yield` і `StreamingResponse`, технічні деталі { #dependencies-with-yield-and-streamingresponse-technical-details } + +До **FastAPI** 0.118.0, якщо ви використовували залежність із `yield`, завершальний код виконувався після повернення з *функції операції шляху*, але безпосередньо перед відправленням відповіді. + +Метою було уникнути утримання ресурсів довше, ніж потрібно, очікуючи, поки відповідь пройде мережею. + +Це також означало, що якщо ви повертали `StreamingResponse`, завершальний код залежності з `yield` уже було б виконано. + +Наприклад, якщо у залежності з `yield` була сесія бази даних, `StreamingResponse` не зміг би використовувати цю сесію під час потокової передачі даних, тому що сесію вже закрито в завершальному коді після `yield`. + +Цю поведінку змінено у 0.118.0: завершальний код після `yield` знову виконується після відправлення відповіді. + +/// info | Інформація + +Як побачите нижче, це дуже схоже на поведінку до версії 0.106.0, але з кількома покращеннями та виправленнями помилок у крайових випадках. + +/// + +#### Випадки використання з раннім завершальним кодом { #use-cases-with-early-exit-code } + +Є кілька сценаріїв із певними умовами, які можуть виграти від старої поведінки - виконувати завершальний код залежностей з `yield` до надсилання відповіді. + +Наприклад, уявіть, що у вас є код, який використовує сесію бази даних у залежності з `yield` лише для перевірки користувача, але сесія більше не використовується у *функції операції шляху*, тільки в залежності, і відправлення відповіді триває довго - як у `StreamingResponse`, що повільно надсилає дані, але з якоїсь причини не використовує базу даних. + +У такому разі сесія БД утримувалася б до завершення відправлення відповіді, але якщо ви її не використовуєте, утримувати її немає потреби. + +Ось як це може виглядати: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py *} + +Завершальний код - автоматичне закриття `Session` у: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +...виконається після того, як відповідь завершить надсилати повільні дані: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +Але оскільки `generate_stream()` не використовує сесію бази даних, немає реальної потреби тримати сесію відкритою під час надсилання відповіді. + +Якщо у вас саме такий випадок із SQLModel (або SQLAlchemy), ви можете явно закрити сесію, коли вона більше не потрібна: + +{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *} + +Так сесія звільнить з'єднання з базою даних, і його зможуть використовувати інші запити. + +Якщо у вас інший сценарій, де потрібно раннє завершення залежності з `yield`, створіть, будь ласка, питання в обговореннях GitHub із вашим конкретним випадком і поясненням, чому вам корисне раннє закриття для залежностей з `yield`. + +Якщо будуть переконливі приклади для раннього закриття в залежностях з `yield`, я розгляну додавання нового способу увімкнути раннє закриття. + +### Залежності з `yield` і `except`, технічні деталі { #dependencies-with-yield-and-except-technical-details } + +До **FastAPI** 0.110.0, якщо ви використовували залежність із `yield`, перехоплювали виняток через `except` у цій залежності і не піднімали його знову, виняток автоматично піднімався/пересилався до будь-яких обробників винятків або внутрішнього обробника помилок сервера. + +Це змінено у версії 0.110.0, щоб усунути неконтрольоване споживання пам'яті від пересланих винятків без обробника (внутрішні помилки сервера) та зробити поведінку узгодженою зі звичайним Python-кодом. + +### Фонові задачі та залежності з `yield`, технічні деталі { #background-tasks-and-dependencies-with-yield-technical-details } + +До **FastAPI** 0.106.0 піднімати винятки після `yield` було неможливо: завершальний код у залежностях з `yield` виконувався після надсилання відповіді, тож [обробники винятків](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} уже відпрацювали б. + +Так було спроєктовано головно для того, щоб дозволити використовувати ті самі об'єкти, «віддані» залежностями через `yield`, усередині фонових задач, оскільки завершальний код виконувався після завершення фонових задач. + +У **FastAPI** 0.106.0 це змінено, щоб не утримувати ресурси під час очікування, поки відповідь піде мережею. + +/// tip | Порада + +Крім того, фонова задача зазвичай є незалежним набором логіки, який слід обробляти окремо, з власними ресурсами (наприклад, власним з'єднанням з базою даних). + +Тож так у вас, ймовірно, буде чистіший код. + +/// + +Якщо ви раніше покладалися на цю поведінку, тепер слід створювати ресурси для фонових задач усередині самої фонової задачі та використовувати всередині лише дані, що не залежать від ресурсів залежностей із `yield`. + +Наприклад, замість використання тієї самої сесії бази даних ви створюватимете нову сесію в самій фоновій задачі та отримуватимете об'єкти з бази даних, використовуючи цю нову сесію. І далі, замість передавання об'єкта з бази даних як параметра у функцію фонової задачі, ви передасте ідентифікатор цього об'єкта, а потім отримаєте об'єкт знову всередині функції фонової задачі. diff --git a/docs/uk/docs/advanced/advanced-python-types.md b/docs/uk/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..9eedc4856 --- /dev/null +++ b/docs/uk/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# Просунуті типи Python { #advanced-python-types } + +Ось кілька додаткових ідей, які можуть бути корисні під час роботи з типами в Python. + +## Використання `Union` або `Optional` { #using-union-or-optional } + +Якщо ваш код з якоїсь причини не може використовувати `|`, наприклад, якщо це не анотація типів, а щось на кшталт `response_model=`, замість вертикальної риски (`|`) ви можете використати `Union` з `typing`. + +Наприклад, ви можете оголосити, що щось може бути `str` або `None`: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +У `typing` також є скорочення, щоб оголосити, що щось може бути `None`, - `Optional`. + +Ось порада з моєї дуже «суб'єктивної» точки зору: + +- 🚨 Уникайте використання `Optional[SomeType]` +- Натомість ✨ **використовуйте `Union[SomeType, None]`** ✨. + +Обидва варіанти еквівалентні і під капотом однакові, але я рекомендую `Union` замість `Optional`, тому що слово «**optional**» ніби натякає, що значення є необов'язковим, а насправді означає «може бути `None`», навіть якщо воно не є необов'язковим і все ще обов'язкове. + +Вважаю, `Union[SomeType, None]` більш явно це передає. + +Йдеться лише про слова та назви. Але ці слова можуть впливати на те, як ви та ваша команда думаєте про код. + +Як приклад, розгляньмо цю функцію: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +Параметр `name` визначено як `Optional[str]`, але він не є необов'язковим, ви не можете викликати функцію без цього параметра: + +```Python +say_hi() # О ні, це викликає помилку! 😱 +``` + +Параметр `name` все ще обов'язковий (не «необов'язковий»), тому що не має значення за замовчуванням. Водночас `name` приймає `None` як значення: + +```Python +say_hi(name=None) # Це працює, None припустимий 🎉 +``` + +Гарна новина: у більшості випадків ви зможете просто використовувати `|`, щоб визначати об'єднання типів: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +Тож зазвичай вам не доведеться перейматися такими назвами, як `Optional` і `Union`. 😎 diff --git a/docs/uk/docs/advanced/async-tests.md b/docs/uk/docs/advanced/async-tests.md new file mode 100644 index 000000000..51d0d5761 --- /dev/null +++ b/docs/uk/docs/advanced/async-tests.md @@ -0,0 +1,99 @@ +# Асинхронні тести { #async-tests } + +Ви вже бачили, як тестувати ваші застосунки **FastAPI** за допомогою наданого `TestClient`. До цього часу ви бачили лише, як писати синхронні тести, без використання функцій `async`. + +Можливість використовувати асинхронні функції у тестах може бути корисною, наприклад, коли ви асинхронно звертаєтеся до бази даних. Уявіть, що ви хочете протестувати надсилання запитів до вашого застосунку FastAPI, а потім перевірити, що ваш бекенд успішно записав коректні дані в базу даних, використовуючи асинхронну бібліотеку для бази даних. + +Розгляньмо, як це реалізувати. + +## pytest.mark.anyio { #pytest-mark-anyio } + +Якщо ми хочемо викликати асинхронні функції у тестах, самі тестові функції мають бути асинхронними. AnyIO надає зручний плагін, який дозволяє вказати, що деякі тестові функції слід виконувати асинхронно. + +## HTTPX { #httpx } + +Навіть якщо ваш застосунок **FastAPI** використовує звичайні функції `def` замість `async def`, під капотом це все одно `async`-застосунок. + +`TestClient` робить певну «магію» всередині, щоб викликати асинхронний застосунок FastAPI у ваших звичайних тестових функціях `def`, використовуючи стандартний pytest. Але ця «магія» більше не працює, коли ми використовуємо його всередині асинхронних функцій. Запускаючи тести асинхронно, ми більше не можемо використовувати `TestClient` у наших тестових функціях. + +`TestClient` побудований на основі HTTPX, і на щастя, ми можемо використовувати його безпосередньо для тестування API. + +## Приклад { #example } + +Для простого прикладу розгляньмо структуру файлів, подібну до описаної в [Більші застосунки](../tutorial/bigger-applications.md){.internal-link target=_blank} та [Тестування](../tutorial/testing.md){.internal-link target=_blank}: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   └── test_main.py +``` + +Файл `main.py` міститиме: + +{* ../../docs_src/async_tests/app_a_py310/main.py *} + +Файл `test_main.py` міститиме тести для `main.py`, тепер це може виглядати так: + +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} + +## Запуск { #run-it } + +Ви можете запустити тести як зазвичай: + +
+ +```console +$ pytest + +---> 100% +``` + +
+ +## Докладно { #in-detail } + +Маркер `@pytest.mark.anyio` повідомляє pytest, що цю тестову функцію слід викликати асинхронно: + +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} + +/// tip | Порада + +Зауважте, що тестова функція тепер `async def` замість просто `def`, як це було раніше при використанні `TestClient`. + +/// + +Далі ми можемо створити `AsyncClient` із застосунком і надсилати до нього асинхронні запити, використовуючи `await`. + +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} + +Це еквівалентно: + +```Python +response = client.get('/') +``` + +...що ми раніше використовували для надсилання запитів за допомогою `TestClient`. + +/// tip | Порада + +Зауважте, що ми використовуємо async/await із новим `AsyncClient` - запит є асинхронним. + +/// + +/// warning | Попередження + +Якщо ваш застосунок залежить від подій тривалості життя, `AsyncClient` не ініціюватиме ці події. Щоб гарантувати їх ініціалізацію, використовуйте `LifespanManager` з florimondmanca/asgi-lifespan. + +/// + +## Інші асинхронні виклики функцій { #other-asynchronous-function-calls } + +Оскільки тестова функція тепер асинхронна, ви також можете викликати (і `await`) інші `async`-функції окрім надсилання запитів до вашого застосунку FastAPI у тестах - так само, як ви робили б це будь-де у вашому коді. + +/// tip | Порада + +Якщо ви натрапили на `RuntimeError: Task attached to a different loop` під час інтеграції асинхронних викликів у ваші тести (наприклад, при використанні MongoDB's MotorClient), пам'ятайте створювати об'єкти, яким потрібен цикл подій, лише всередині асинхронних функцій, наприклад, у зворотному виклику `@app.on_event("startup")`. + +/// diff --git a/docs/uk/docs/advanced/behind-a-proxy.md b/docs/uk/docs/advanced/behind-a-proxy.md new file mode 100644 index 000000000..66bb4c082 --- /dev/null +++ b/docs/uk/docs/advanced/behind-a-proxy.md @@ -0,0 +1,466 @@ +# За представником { #behind-a-proxy } + +У багатьох випадках ви будете використовувати **представника** на кшталт Traefik або Nginx перед вашим застосунком FastAPI. + +Такі представники можуть обробляти сертифікати HTTPS та інші речі. + +## Направлені заголовки представника { #proxy-forwarded-headers } + +**Представник** перед вашим застосунком зазвичай встановлює деякі заголовки на льоту перед відправленням запитів до вашого **сервера**, щоб повідомити серверу, що запит було **направлено** через представника, вказати оригінальну (публічну) URL-адресу, включно з доменом, що використовується HTTPS тощо. + +Програмний **сервер** (наприклад, **Uvicorn** через **FastAPI CLI**) здатний інтерпретувати ці заголовки і передавати цю інформацію вашому застосунку. + +Але з міркувань безпеки, оскільки сервер не знає, що він працює за довіреним представником, він не інтерпретуватиме ці заголовки. + +/// note | Технічні деталі + +Заголовки представника: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +### Увімкнути направлені заголовки представника { #enable-proxy-forwarded-headers } + +Ви можете запустити FastAPI CLI з *опцією CLI* `--forwarded-allow-ips` і передати IP-адреси, яким слід довіряти для читання цих направлених заголовків. + +Якщо ви встановите `--forwarded-allow-ips="*"`, це означатиме довіру до всіх вхідних IP. + +Якщо ваш **сервер** знаходиться за довіреним **представником** і тільки представник спілкується з ним, це змусить сервер приймати будь-яку IP-адресу цього **представника**. + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Перенаправлення з HTTPS { #redirects-with-https } + +Наприклад, припустимо, ви визначили *операцію шляху* `/items/`: + +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} + +Якщо клієнт спробує перейти до `/items`, за замовчуванням буде виконано перенаправлення на `/items/`. + +Але до встановлення *опції CLI* `--forwarded-allow-ips` це могло б перенаправити на `http://localhost:8000/items/`. + +Втім, можливо ваш застосунок розміщено на `https://mysuperapp.com`, і перенаправлення має бути на `https://mysuperapp.com/items/`. + +Налаштувавши `--proxy-headers`, FastAPI зможе перенаправляти на правильну адресу. 😎 + +``` +https://mysuperapp.com/items/ +``` + +/// tip | Порада + +Якщо хочете дізнатися більше про HTTPS, перегляньте посібник [Про HTTPS](../deployment/https.md){.internal-link target=_blank}. + +/// + +### Як працюють направлені заголовки представника { #how-proxy-forwarded-headers-work } + +Ось візуальне подання того, як **представник** додає направлені заголовки між клієнтом і **сервером застосунку**: + +```mermaid +sequenceDiagram + participant Client + participant Proxy as Proxy/Load Balancer + participant Server as FastAPI Server + + Client->>Proxy: HTTPS Request
Host: mysuperapp.com
Path: /items + + Note over Proxy: Proxy adds forwarded headers + + Proxy->>Server: HTTP Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items + + Note over Server: Server interprets headers
(if --forwarded-allow-ips is set) + + Server->>Proxy: HTTP Response
with correct HTTPS URLs + + Proxy->>Client: HTTPS Response +``` + +**Представник** перехоплює початковий запит клієнта і додає спеціальні *направлені* заголовки (`X-Forwarded-*`) перед передачею запиту на **сервер застосунку**. + +Ці заголовки зберігають інформацію про оригінальний запит, яка інакше була б втрачена: + +- X-Forwarded-For: оригінальна IP-адреса клієнта +- X-Forwarded-Proto: оригінальний протокол (`https`) +- X-Forwarded-Host: оригінальний хост (`mysuperapp.com`) + +Коли **FastAPI CLI** налаштовано з `--forwarded-allow-ips`, він довіряє цим заголовкам і використовує їх, наприклад, для побудови коректних URL-адрес у перенаправленнях. + +## Представник із вилученим префіксом шляху { #proxy-with-a-stripped-path-prefix } + +У вас може бути представник, що додає префікс шляху до вашого застосунку. + +У таких випадках ви можете використати `root_path` для конфігурації застосунку. + +`root_path` - це механізм, наданий специфікацією ASGI (на якій побудовано FastAPI через Starlette). + +`root_path` використовується для обробки саме таких випадків. + +Його також використано внутрішньо під час монтування підзастосунків. + +Наявність представника з вилученим префіксом шляху означає, що ви могли б оголосити шлях `/app` у вашому коді, але потім додати шар зверху (представника), який розмістить ваш застосунок **FastAPI** під шляхом на кшталт `/api/v1`. + +У такому разі початковий шлях `/app` фактично буде доступний за `/api/v1/app`. + +Хоча весь ваш код написано з припущенням, що є лише `/app`. + +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} + +І представник буде **«зрізати»** **префікс шляху** на льоту перед передачею запиту серверу застосунку (ймовірно, Uvicorn через FastAPI CLI), зберігаючи переконання вашого застосунку, що його обслуговують за `/app`, тож вам не доведеться оновлювати весь код, щоб додати префікс `/api/v1`. + +Дотепер усе працювало б як зазвичай. + +Але коли ви відкриєте вбудований інтерфейс документації (фронтенд), він очікуватиме отримати схему OpenAPI за `/openapi.json`, замість `/api/v1/openapi.json`. + +Отже, фронтенд (що працює у браузері) спробує звернутися до `/openapi.json` і не зможе отримати схему OpenAPI. + +Оскільки у нас є представник з префіксом шляху `/api/v1` для нашого застосунку, фронтенду потрібно отримувати схему OpenAPI за `/api/v1/openapi.json`. + +```mermaid +graph LR + +browser("Browser") +proxy["Proxy on http://0.0.0.0:9999/api/v1/app"] +server["Server on http://127.0.0.1:8000/app"] + +browser --> proxy +proxy --> server +``` + +/// tip | Порада + +IP `0.0.0.0` зазвичай означає, що програма слухає на всіх IP-адресах, доступних на цій машині/сервері. + +/// + +Інтерфейсу документації також потрібно, щоб схема OpenAPI оголошувала, що цей `server` API розташований за `/api/v1` (за представником). Наприклад: + +```JSON hl_lines="4-8" +{ + "openapi": "3.1.0", + // Ще дещо тут + "servers": [ + { + "url": "/api/v1" + } + ], + "paths": { + // Ще дещо тут + } +} +``` + +У цьому прикладі «Proxy» може бути чимось на кшталт **Traefik**. А сервером буде щось на кшталт FastAPI CLI з **Uvicorn**, який запускає ваш застосунок FastAPI. + +### Задання `root_path` { #providing-the-root-path } + +Щоб цього досягти, ви можете використати опцію командного рядка `--root-path`, наприклад: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Якщо ви використовуєте Hypercorn, у нього також є опція `--root-path`. + +/// note | Технічні деталі + +Специфікація ASGI визначає `root_path` для такого випадку використання. + +А опція командного рядка `--root-path` надає цей `root_path`. + +/// + +### Перевірка поточного `root_path` { #checking-the-current-root-path } + +Ви можете отримати поточний `root_path`, який використовує ваш застосунок для кожного запиту, він є частиною словника `scope` (це частина специфікації ASGI). + +Тут ми додаємо його у повідомлення лише для демонстрації. + +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} + +Потім, якщо ви запустите Uvicorn так: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Відповідь буде приблизно такою: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +### Встановлення `root_path` у застосунку FastAPI { #setting-the-root-path-in-the-fastapi-app } + +Альтернативно, якщо у вас немає способу передати опцію командного рядка на кшталт `--root-path` чи еквівалентну, ви можете встановити параметр `root_path` під час створення вашого застосунку FastAPI: + +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} + +Передача `root_path` до `FastAPI` еквівалентна передачі опції командного рядка `--root-path` до Uvicorn або Hypercorn. + +### Про `root_path` { #about-root-path } + +Майте на увазі, що сервер (Uvicorn) не використовуватиме `root_path` ні для чого, окрім передачі його застосунку. + +Але якщо ви перейдете у вашому браузері на http://127.0.0.1:8000/app, ви побачите звичайну відповідь: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +Тобто він не очікуватиме доступу за адресою `http://127.0.0.1:8000/api/v1/app`. + +Uvicorn очікуватиме, що представник буде звертатися до Uvicorn на `http://127.0.0.1:8000/app`, і тоді саме представник відповідатиме за додавання додаткового префікса `/api/v1` зверху. + +## Про представників із вилученим префіксом шляху { #about-proxies-with-a-stripped-path-prefix } + +Майте на увазі, що представник із вилученим префіксом шляху - це лише один зі способів його налаштування. + +Ймовірно, в багатьох випадках за замовчуванням представник не матиме вилученого префікса шляху. + +У такому випадку (без вилученого префікса шляху) представник слухатиме щось на кшталт `https://myawesomeapp.com`, і коли браузер перейде на `https://myawesomeapp.com/api/v1/app`, а ваш сервер (наприклад, Uvicorn) слухає на `http://127.0.0.1:8000`, представник (без вилученого префікса) звернеться до Uvicorn за тим самим шляхом: `http://127.0.0.1:8000/api/v1/app`. + +## Локальне тестування з Traefik { #testing-locally-with-traefik } + +Ви можете легко провести експеримент локально з вилученим префіксом шляху, використовуючи Traefik. + +Завантажте Traefik, це один бінарний файл, ви можете розпакувати архів і запустити його безпосередньо з термінала. + +Потім створіть файл `traefik.toml` з таким вмістом: + +```TOML hl_lines="3" +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +Це вказує Traefik слухати порт 9999 і використовувати інший файл `routes.toml`. + +/// tip | Порада + +Ми використовуємо порт 9999 замість стандартного HTTP-порту 80, щоб вам не довелося запускати його з адмін-повноваженнями (`sudo`). + +/// + +Тепер створіть інший файл `routes.toml`: + +```TOML hl_lines="5 12 20" +[http] + [http.middlewares] + + [http.middlewares.api-stripprefix.stripPrefix] + prefixes = ["/api/v1"] + + [http.routers] + + [http.routers.app-http] + entryPoints = ["http"] + service = "app" + rule = "PathPrefix(`/api/v1`)" + middlewares = ["api-stripprefix"] + + [http.services] + + [http.services.app] + [http.services.app.loadBalancer] + [[http.services.app.loadBalancer.servers]] + url = "http://127.0.0.1:8000" +``` + +Цей файл налаштовує Traefik на використання префікса шляху `/api/v1`. + +Потім Traefik переспрямовуватиме свої запити до вашого Uvicorn, який працює на `http://127.0.0.1:8000`. + +Тепер запустіть Traefik: + +
+ +```console +$ ./traefik --configFile=traefik.toml + +INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml +``` + +
+ +А тепер запустіть ваш застосунок, використовуючи опцію `--root-path`: + +
+ +```console +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Перевірте відповіді { #check-the-responses } + +Тепер, якщо ви перейдете за URL із портом Uvicorn: http://127.0.0.1:8000/app, ви побачите звичайну відповідь: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +/// tip | Порада + +Зверніть увагу, що хоча ви звертаєтеся за адресою `http://127.0.0.1:8000/app`, відображається `root_path` `/api/v1`, взятий із опції `--root-path`. + +/// + +А тепер відкрийте URL із портом Traefik, включно з префіксом шляху: http://127.0.0.1:9999/api/v1/app. + +Ми отримуємо ту саму відповідь: + +```JSON +{ + "message": "Hello World", + "root_path": "/api/v1" +} +``` + +але цього разу за URL із префіксом шляху, який надає представник: `/api/v1`. + +Звісно, ідея полягає в тому, що всі мають доступатися до застосунку через представника, тож варіант із префіксом шляху `/api/v1` є «правильним». + +А варіант без префікса шляху (`http://127.0.0.1:8000/app`), який надає безпосередньо Uvicorn, має бути виключно для того, щоб _представник_ (Traefik) звертався до нього. + +Це демонструє, як Представник (Traefik) використовує префікс шляху і як сервер (Uvicorn) використовує `root_path` з опції `--root-path`. + +### Перевірте інтерфейс документації { #check-the-docs-ui } + +А тепер до цікавої частини. ✨ + +«Офіційний» спосіб доступу до застосунку - через представника з префіксом шляху, який ми визначили. Тож, як і очікується, якщо ви спробуєте інтерфейс документації, який обслуговує безпосередньо Uvicorn без префікса шляху в URL, це не запрацює, оскільки він очікує доступу через представника. + +Ви можете перевірити це на http://127.0.0.1:8000/docs: + + + +Але якщо ми звернемося до інтерфейсу документації за «офіційним» URL, використовуючи представника з портом `9999`, за адресою `/api/v1/docs`, усе працює коректно! 🎉 + +Ви можете перевірити це на http://127.0.0.1:9999/api/v1/docs: + + + +Саме так, як ми хотіли. ✔️ + +Це тому, що FastAPI використовує `root_path`, щоб створити типовий `server` в OpenAPI з URL, наданою `root_path`. + +## Додаткові сервери { #additional-servers } + +/// warning | Попередження + +Це більш просунутий випадок використання. Можете пропустити його. + +/// + +За замовчуванням **FastAPI** створить `server` у схемі OpenAPI з URL для `root_path`. + +Але ви також можете надати інші альтернативні `servers`, наприклад, якщо хочете, щоб той самий інтерфейс документації взаємодіяв і зі стейджингом, і з продакшном. + +Якщо ви передасте власний список `servers`, і є `root_path` (тому що ваш API знаходиться за представником), **FastAPI** вставить «server» з цим `root_path` на початок списку. + +Наприклад: + +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} + +Буде згенерована схема OpenAPI на кшталт: + +```JSON hl_lines="5-7" +{ + "openapi": "3.1.0", + // Ще дещо тут + "servers": [ + { + "url": "/api/v1" + }, + { + "url": "https://stag.example.com", + "description": "Staging environment" + }, + { + "url": "https://prod.example.com", + "description": "Production environment" + } + ], + "paths": { + // Ще дещо тут + } +} +``` + +/// tip | Порада + +Зверніть увагу на автоматично згенерований сервер із значенням `url` `/api/v1`, взятим із `root_path`. + +/// + +В інтерфейсі документації за адресою http://127.0.0.1:9999/api/v1/docs це виглядатиме так: + + + +/// tip | Порада + +Інтерфейс документації взаємодіятиме з сервером, який ви оберете. + +/// + +/// note | Технічні деталі + +Властивість `servers` у специфікації OpenAPI є необовʼязковою. + +Якщо ви не вкажете параметр `servers`, і `root_path` дорівнює `/`, властивість `servers` у згенерованій схемі OpenAPI буде повністю пропущено за замовчуванням, що еквівалентно одному серверу зі значенням `url` рівним `/`. + +/// + +### Вимкнути автоматичний сервер із `root_path` { #disable-automatic-server-from-root-path } + +Якщо ви не хочете, щоб **FastAPI** додавав автоматичний сервер, використовуючи `root_path`, скористайтеся параметром `root_path_in_servers=False`: + +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} + +і тоді він не буде включений у схему OpenAPI. + +## Монтування підзастосунку { #mounting-a-sub-application } + +Якщо вам потрібно змонтувати підзастосунок (як описано в [Підзастосунки - монтування](sub-applications.md){.internal-link target=_blank}), одночасно використовуючи представника з `root_path`, ви можете робити це звичайним чином, як і очікуєте. + +FastAPI внутрішньо розумно використовуватиме `root_path`, тож усе просто працюватиме. ✨ diff --git a/docs/uk/docs/advanced/custom-response.md b/docs/uk/docs/advanced/custom-response.md new file mode 100644 index 000000000..ebd0ec24a --- /dev/null +++ b/docs/uk/docs/advanced/custom-response.md @@ -0,0 +1,312 @@ +# Користувацька відповідь - HTML, стрім, файл, інше { #custom-response-html-stream-file-others } + +Типово **FastAPI** повертатиме відповіді, використовуючи `JSONResponse`. + +Ви можете переписати це, повернувши безпосередньо `Response`, як показано в [Повернути відповідь безпосередньо](response-directly.md){.internal-link target=_blank}. + +Але якщо ви повертаєте `Response` безпосередньо (або будь-який його підклас, як-от `JSONResponse`), дані не будуть автоматично конвертовані (навіть якщо ви оголосите `response_model`), і документація не буде автоматично згенерована (наприклад, із включенням конкретного «медіа-типу» в HTTP-заголовку `Content-Type` як частини згенерованого OpenAPI). + +Ви також можете оголосити `Response`, який слід використовувати (наприклад, будь-який підклас `Response`), у декораторі операції шляху через параметр `response_class`. + +Вміст, який ви повертаєте з вашої функції операції шляху, буде поміщений усередину цього `Response`. + +І якщо цей `Response` має JSON медіа-тип (`application/json`), як у випадку з `JSONResponse` та `UJSONResponse`, дані, що повертаються, будуть автоматично перетворені (і відфільтровані) з урахуванням будь-якого Pydantic `response_model`, який ви оголосили в декораторі операції шляху. + +/// note | Примітка + +Якщо ви використовуєте клас відповіді без медіа-типу, FastAPI очікуватиме, що у вашої відповіді не буде вмісту, тож формат відповіді не буде задокументовано в згенерованих OpenAPI-документах. + +/// + +## Використовуйте `ORJSONResponse` { #use-orjsonresponse } + +Наприклад, якщо ви максимально оптимізуєте продуктивність, можете встановити та використовувати `orjson` і встановити відповідь як `ORJSONResponse`. + +Імпортуйте потрібний клас `Response` (підклас) і оголосіть його в декораторі операції шляху. + +Для великих відповідей повернення `Response` безпосередньо значно швидше, ніж повернення словника. + +Це тому, що за замовчуванням FastAPI перевірятиме кожен елемент усередині та переконуватиметься, що його можна серіалізувати як JSON, використовуючи той самий [Сумісний кодувальник JSON](../tutorial/encoder.md){.internal-link target=_blank}, описаний у навчальному посібнику. Саме це дозволяє повертати довільні об'єкти, наприклад моделі бази даних. + +Але якщо ви впевнені, що вміст, який повертається, серіалізується в JSON, ви можете передати його безпосередньо класу відповіді та уникнути додаткових витрат FastAPI на пропускання вашого вмісту через `jsonable_encoder` перед передаванням його класу відповіді. + +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} + +/// info | Інформація + +Параметр `response_class` також визначатиме «медіа-тип» відповіді. + +У цьому випадку HTTP-заголовок `Content-Type` буде встановлено в `application/json`. + +І це буде задокументовано відповідно в OpenAPI. + +/// + +/// tip | Порада + +`ORJSONResponse` доступний лише у FastAPI, не в Starlette. + +/// + +## HTML-відповідь { #html-response } + +Щоб повернути відповідь із HTML безпосередньо з **FastAPI**, використовуйте `HTMLResponse`. + +- Імпортуйте `HTMLResponse`. +- Передайте `HTMLResponse` як параметр `response_class` вашого декоратора операції шляху. + +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} + +/// info | Інформація + +Параметр `response_class` також визначатиме «медіа-тип» відповіді. + +У цьому випадку HTTP-заголовок `Content-Type` буде встановлено в `text/html`. + +І це буде задокументовано відповідно в OpenAPI. + +/// + +### Повернути `Response` { #return-a-response } + +Як показано в [Повернути відповідь безпосередньо](response-directly.md){.internal-link target=_blank}, ви також можете переписати відповідь безпосередньо у вашій операції шляху, просто повернувши її. + +Той самий приклад вище, що повертає `HTMLResponse`, може виглядати так: + +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} + +/// warning | Попередження + +`Response`, повернений безпосередньо вашою функцією операції шляху, не буде задокументовано в OpenAPI (наприклад, `Content-Type` не буде задокументовано) і не буде видно в автоматичній інтерактивній документації. + +/// + +/// info | Інформація + +Звісно, фактичні заголовок `Content-Type`, код статусу тощо прийдуть з об'єкта `Response`, який ви повернули. + +/// + +### Задокументуйте в OpenAPI і перепишіть `Response` { #document-in-openapi-and-override-response } + +Якщо ви хочете переписати відповідь усередині функції, але водночас задокументувати «медіа-тип» в OpenAPI, ви можете використати параметр `response_class` І повернути об'єкт `Response`. + +Тоді `response_class` буде використано лише для документування операції шляху в OpenAPI, а ваша `Response` буде використана як є. + +#### Повернути `HTMLResponse` безпосередньо { #return-an-htmlresponse-directly } + +Наприклад, це може бути щось на кшталт: + +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} + +У цьому прикладі функція `generate_html_response()` уже генерує та повертає `Response` замість повернення HTML як `str`. + +Повертаючи результат виклику `generate_html_response()`, ви вже повертаєте `Response`, яка перепише типову поведінку **FastAPI**. + +Але оскільки ви також передали `HTMLResponse` у `response_class`, **FastAPI** знатиме, як задокументувати це в OpenAPI та інтерактивній документації як HTML з `text/html`: + + + +## Доступні відповіді { #available-responses } + +Ось деякі з доступних відповідей. + +Майте на увазі, що ви можете використовувати `Response`, щоб повертати що завгодно інше, або навіть створити власний підклас. + +/// note | Технічні деталі + +Ви також можете використати `from starlette.responses import HTMLResponse`. + +**FastAPI** надає ті ж `starlette.responses` як `fastapi.responses` лише для вашої зручності як розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette. + +/// + +### `Response` { #response } + +Головний клас `Response`, від якого успадковуються всі інші відповіді. + +Ви можете повертати його безпосередньо. + +Він приймає такі параметри: + +- `content` - `str` або `bytes`. +- `status_code` - `int` - код статусу HTTP. +- `headers` - `dict` строк. +- `media_type` - `str`, що задає медіа-тип, напр. `"text/html"`. + +FastAPI (насправді Starlette) автоматично додасть заголовок Content-Length. Також буде додано заголовок Content-Type, на основі `media_type` з додаванням набору символів для текстових типів. + +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} + +### `HTMLResponse` { #htmlresponse } + +Приймає текст або байти та повертає HTML-відповідь, як описано вище. + +### `PlainTextResponse` { #plaintextresponse } + +Приймає текст або байти та повертає відповідь звичайним текстом. + +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} + +### `JSONResponse` { #jsonresponse } + +Приймає дані та повертає відповідь, закодовану як `application/json`. + +Це типова відповідь, яку використовує **FastAPI**, як зазначено вище. + +### `ORJSONResponse` { #orjsonresponse } + +Швидка альтернативна JSON-відповідь з використанням `orjson`, як описано вище. + +/// info | Інформація + +Потрібно встановити `orjson`, наприклад `pip install orjson`. + +/// + +### `UJSONResponse` { #ujsonresponse } + +Альтернативна JSON-відповідь з використанням `ujson`. + +/// info | Інформація + +Потрібно встановити `ujson`, наприклад `pip install ujson`. + +/// + +/// warning | Попередження + +`ujson` менш обережний, ніж вбудована реалізація Python, у поводженні з деякими крайніми випадками. + +/// + +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} + +/// tip | Порада + +Ймовірно, `ORJSONResponse` може бути швидшою альтернативою. + +/// + +### `RedirectResponse` { #redirectresponse } + +Повертає HTTP-перенаправлення. Типово використовує код статусу 307 (Temporary Redirect). + +Ви можете повернути `RedirectResponse` безпосередньо: + +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} + +--- + +Або ви можете використати його в параметрі `response_class`: + +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} + +У такому разі ви можете повертати URL безпосередньо з вашої функції операції шляху. + +У цьому випадку `status_code` буде типовим для `RedirectResponse`, тобто `307`. + +--- + +Ви також можете використати параметр `status_code` разом із параметром `response_class`: + +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} + +### `StreamingResponse` { #streamingresponse } + +Приймає async-генератор або звичайний генератор/ітератор і транслює тіло відповіді потоково. + +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} + +#### Використання `StreamingResponse` з об'єктами типу file-like { #using-streamingresponse-with-file-like-objects } + +Якщо у вас є file-like об'єкт (наприклад, об'єкт, що повертається `open()`), ви можете створити генераторну функцію для ітерації по цьому file-like об'єкту. + +Таким чином, вам не потрібно спочатку читати все в пам'ять, і ви можете передати цю генераторну функцію в `StreamingResponse` і повернути її. + +Сюди входить багато бібліотек для взаємодії з хмарними сховищами, обробки відео та інші. + +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} + +1. Це генераторна функція. Вона є «генераторною функцією», бо містить оператори `yield` усередині. +2. Використовуючи блок `with`, ми гарантуємо, що file-like об'єкт буде закрито після завершення роботи генераторної функції. Тобто після того, як вона завершить надсилання відповіді. +3. Цей `yield from` вказує функції ітеруватися по об'єкту з назвою `file_like`. А потім, для кожної ітерованої частини, повертати цю частину, ніби вона надходить з цієї генераторної функції (`iterfile`). + + Тож це генераторна функція, яка всередині передає роботу «генерації» чомусь іншому. + + Роблячи це таким чином, ми можемо помістити її в блок `with` і таким чином гарантувати, що file-like об'єкт буде закрито після завершення. + +/// tip | Порада + +Зверніть увагу, що тут ми використовуємо стандартний `open()`, який не підтримує `async` та `await`, тому ми оголошуємо операцію шляху звичайною `def`. + +/// + +### `FileResponse` { #fileresponse } + +Асинхронно транслює файл як відповідь. + +Приймає інший набір аргументів для створення екземпляра, ніж інші типи відповідей: + +- `path` - шлях до файлу для трансляції. +- `headers` - будь-які користувацькі заголовки як словник. +- `media_type` - строка, що задає медіа-тип. Якщо не встановлено, медіа-тип буде виведено з імені файлу або шляху. +- `filename` - якщо встановлено, буде включено до `Content-Disposition` відповіді. + +Відповіді з файлами включатимуть відповідні заголовки `Content-Length`, `Last-Modified` і `ETag`. + +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} + +Ви також можете використати параметр `response_class`: + +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} + +У цьому випадку ви можете повертати шлях до файлу безпосередньо з вашої функції операції шляху. + +## Власний клас відповіді { #custom-response-class } + +Ви можете створити власний клас відповіді, успадкувавши його від `Response`, і використовувати його. + +Наприклад, скажімо, ви хочете використовувати `orjson`, але з деякими користувацькими налаштуваннями, які не використовуються у вбудованому класі `ORJSONResponse`. + +Припустімо, ви хочете, щоб повертався відформатований із відступами JSON, тож ви хочете використати опцію orjson `orjson.OPT_INDENT_2`. + +Ви можете створити `CustomORJSONResponse`. Головне, що потрібно зробити, це створити метод `Response.render(content)`, який повертає вміст як `bytes`: + +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} + +Тепер замість повернення: + +```json +{"message": "Hello World"} +``` + +...ця відповідь повертатиме: + +```json +{ + "message": "Hello World" +} +``` + +Звісно, ви, ймовірно, знайдете значно кращі способи скористатися цим, ніж просто форматування JSON. 😉 + +## Типова відповідь за замовчуванням { #default-response-class } + +Створюючи екземпляр класу **FastAPI** або `APIRouter`, ви можете вказати, який клас відповіді використовувати за замовчуванням. + +Параметр, що це визначає, - `default_response_class`. + +У прикладі нижче **FastAPI** використовуватиме `ORJSONResponse` за замовчуванням в усіх операціях шляху замість `JSONResponse`. + +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} + +/// tip | Порада + +Ви все одно можете переписати `response_class` в операціях шляху, як і раніше. + +/// + +## Додаткова документація { #additional-documentation } + +Ви також можете оголосити медіа-тип і багато інших деталей в OpenAPI, використовуючи `responses`: [Додаткові відповіді в OpenAPI](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/uk/docs/advanced/dataclasses.md b/docs/uk/docs/advanced/dataclasses.md new file mode 100644 index 000000000..a41e6e589 --- /dev/null +++ b/docs/uk/docs/advanced/dataclasses.md @@ -0,0 +1,95 @@ +# Використання dataclasses { #using-dataclasses } + +FastAPI побудовано поверх **Pydantic**, і я показував вам, як використовувати моделі Pydantic для оголошення запитів і відповідей. + +Але FastAPI також підтримує використання `dataclasses` таким самим чином: + +{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *} + +Це підтримується завдяки **Pydantic**, адже він має внутрішню підтримку `dataclasses`. + +Тож навіть із наведеним вище кодом, який явно не використовує Pydantic, FastAPI використовує Pydantic, щоб перетворити стандартні dataclasses у власний варіант dataclasses Pydantic. + +І, звісно, підтримується те саме: + +* валідація даних +* серіалізація даних +* документація даних тощо + +Це працює так само, як із моделями Pydantic. Насправді під капотом це також досягається за допомогою Pydantic. + +/// info | Інформація + +Майте на увазі, що dataclasses не можуть робити все те, що можуть моделі Pydantic. + +Тож вам усе ще може знадобитися використовувати моделі Pydantic. + +Але якщо у вас вже є чимало dataclasses, це зручний трюк, щоб задіяти їх для веб-API на FastAPI. 🤓 + +/// + +## Dataclasses у `response_model` { #dataclasses-in-response-model } + +Ви також можете використовувати `dataclasses` у параметрі `response_model`: + +{* ../../docs_src/dataclasses_/tutorial002_py310.py hl[1,6:12,18] *} + +Dataclass буде автоматично перетворено на dataclass Pydantic. + +Таким чином його схема з'явиться в інтерфейсі користувача документації API: + + + +## Dataclasses у вкладених структурах даних { #dataclasses-in-nested-data-structures } + +Можна поєднувати `dataclasses` з іншими анотаціями типів, щоб створювати вкладені структури даних. + +У деяких випадках вам усе ж доведеться використовувати варіант `dataclasses` від Pydantic. Наприклад, якщо виникають помилки з автоматично згенерованою документацією API. + +У такому разі ви можете просто замінити стандартні `dataclasses` на `pydantic.dataclasses`, що є взаємозамінником: + +{* ../../docs_src/dataclasses_/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} + +1. Ми все ще імпортуємо `field` зі стандартних `dataclasses`. + +2. `pydantic.dataclasses` - це взаємозамінник для `dataclasses`. + +3. Dataclass `Author` містить список dataclass `Item`. + +4. Dataclass `Author` використовується як параметр `response_model`. + +5. Ви можете використовувати інші стандартні анотації типів із dataclasses як тіло запиту. + + У цьому випадку це список dataclass `Item`. + +6. Тут ми повертаємо словник, що містить `items`, який є списком dataclass. + + FastAPI усе ще здатний серіалізувати дані до JSON. + +7. Тут у `response_model` використано анотацію типу список dataclass `Author`. + + Знову ж, ви можете поєднувати `dataclasses` зі стандартними анотаціями типів. + +8. Зверніть увагу, що ця *функція операції шляху* використовує звичайний `def` замість `async def`. + + Як завжди, у FastAPI ви можете поєднувати `def` і `async def` за потреби. + + Якщо вам потрібне коротке нагадування, коли що використовувати, перегляньте розділ _«Поспішаєте?»_ у документації про [`async` та `await`](../async.md#in-a-hurry){.internal-link target=_blank}. + +9. Ця *функція операції шляху* не повертає dataclasses (хоча могла б), а список словників із внутрішніми даними. + + FastAPI використає параметр `response_model` (що включає dataclasses), щоб перетворити відповідь. + +Ви можете поєднувати `dataclasses` з іншими анотаціями типів у багатьох поєднаннях, щоб формувати складні структури даних. + +Перегляньте підказки щодо анотацій у коді вище, щоб побачити більше деталей. + +## Дізнатися більше { #learn-more } + +Можна поєднувати `dataclasses` з іншими моделями Pydantic, наслідувати їх, включати у власні моделі тощо. + +Щоб дізнатися більше, перегляньте документацію Pydantic про dataclasses. + +## Версія { #version } + +Доступно починаючи з версії FastAPI `0.67.0`. 🔖 diff --git a/docs/uk/docs/advanced/events.md b/docs/uk/docs/advanced/events.md new file mode 100644 index 000000000..7c05ee4a4 --- /dev/null +++ b/docs/uk/docs/advanced/events.md @@ -0,0 +1,165 @@ +# Події тривалості життя { #lifespan-events } + +Ви можете визначити логіку (код), яку слід виконати перед тим, як застосунок запуститься. Це означає, що цей код буде виконано один раз, перед тим як застосунок почне отримувати запити. + +Так само ви можете визначити логіку (код), яку слід виконати під час вимкнення застосунку. У цьому випадку код буде виконано один раз, після обробки можливо багатьох запитів. + +Оскільки цей код виконується перед тим, як застосунок почне приймати запити, і одразу після того, як він завершить їх обробку, він охоплює всю тривалість життя застосунку (слово «lifespan» буде важливим за мить 😉). + +Це дуже корисно для налаштування ресурсів, які потрібні для всього застосунку, які спільні між запитами, та/або які потрібно потім прибрати. Наприклад, пул з’єднань з базою даних або завантаження спільної моделі машинного навчання. + +## Випадок використання { #use-case } + +Почнемо з прикладу випадку використання, а потім подивимось, як це вирішити. + +Уявімо, що у вас є моделі машинного навчання, якими ви хочете обробляти запити. 🤖 + +Ті самі моделі спільні між запитами, тобто це не окрема модель на запит чи на користувача. + +Уявімо, що завантаження моделі може займати чимало часу, бо треба читати багато даних з диска. Тож ви не хочете робити це для кожного запиту. + +Ви могли б завантажити її на верхньому рівні модуля/файлу, але це означало б, що модель завантажиться навіть якщо ви просто запускаєте простий автоматизований тест - тоді тест буде повільним, бо йому доведеться чекати завантаження моделі перед виконанням незалежної частини коду. + +Ось це ми й вирішимо: завантажимо модель перед обробкою запитів, але лише безпосередньо перед тим, як застосунок почне отримувати запити, а не під час завантаження коду. + +## Тривалість життя { #lifespan } + +Ви можете визначити цю логіку запуску і вимкнення за допомогою параметра `lifespan` застосунку `FastAPI` та «менеджера контексту» (зараз покажу, що це). + +Почнемо з прикладу, а потім розберемо детально. + +Ми створюємо асинхронну функцію `lifespan()` з `yield` так: + +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} + +Тут ми імітуємо дорогу операцію запуску із завантаженням моделі, поміщаючи (фальшиву) функцію моделі у словник з моделями машинного навчання перед `yield`. Цей код буде виконано перед тим, як застосунок почне приймати запити, під час запуску. + +А одразу після `yield` ми розвантажуємо модель. Цей код буде виконано після того, як застосунок завершить обробку запитів, безпосередньо перед вимкненням. Це, наприклад, може звільнити ресурси на кшталт пам’яті або GPU. + +/// tip | Порада + +Подія `shutdown` відбувається, коли ви зупиняєте застосунок. + +Можливо, вам треба запустити нову версію, або ви просто втомилися її запускати. 🤷 + +/// + +### Функція тривалості життя { #lifespan-function } + +Перше, на що слід звернути увагу: ми визначаємо асинхронну функцію з `yield`. Це дуже схоже на залежності з `yield`. + +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} + +Перша частина функції до `yield` буде виконана перед запуском застосунку. + +А частина після `yield` буде виконана після завершення роботи застосунку. + +### Асинхронний менеджер контексту { #async-context-manager } + +Якщо придивитися, функція задекорована за допомогою `@asynccontextmanager`. + +Це перетворює функцію на так званий «асинхронний менеджер контексту». + +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} + +Менеджер контексту в Python - це те, що можна використовувати в операторі `with`, наприклад, `open()` можна використовувати як менеджер контексту: + +```Python +with open("file.txt") as file: + file.read() +``` + +У новіших версіях Python також є асинхронний менеджер контексту. Його використовують з `async with`: + +```Python +async with lifespan(app): + await do_stuff() +``` + +Коли ви створюєте менеджер контексту або асинхронний менеджер контексту, як вище, перед входом у блок `with` буде виконано код перед `yield`, а після виходу з блоку `with` буде виконано код після `yield`. + +У нашому прикладі коду вище ми не використовуємо його напряму, а передаємо його до FastAPI, щоб він його використав. + +Параметр `lifespan` застосунку `FastAPI` приймає асинхронний менеджер контексту, тож ми можемо передати йому наш новий асинхронний менеджер контексту `lifespan`. + +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} + +## Альтернативні події (застаріло) { #alternative-events-deprecated } + +/// warning | Попередження + +Рекомендований спосіб обробляти запуск і вимкнення - використовувати параметр `lifespan` застосунку `FastAPI`, як описано вище. Якщо ви надаєте параметр `lifespan`, обробники подій `startup` і `shutdown` більше не будуть викликані. Або все через `lifespan`, або все через події - не обидва одночасно. + +Можете, ймовірно, пропустити цю частину. + +/// + +Є альтернативний спосіб визначити логіку, яку слід виконати під час запуску і під час вимкнення. + +Ви можете визначити обробники подій (функції), які потрібно виконати перед запуском застосунку або коли застосунок вимикається. + +Ці функції можна оголошувати як з `async def`, так і звичайним `def`. + +### Подія `startup` { #startup-event } + +Щоб додати функцію, яку слід виконати перед запуском застосунку, оголосіть її з подією `"startup"`: + +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} + +У цьому випадку функція-обробник події `startup` ініціалізує «базу даних» предметів (це лише `dict`) деякими значеннями. + +Ви можете додати більше ніж один обробник події. + +І ваш застосунок не почне приймати запити, доки всі обробники події `startup` не завершаться. + +### Подія `shutdown` { #shutdown-event } + +Щоб додати функцію, яку слід виконати, коли застосунок вимикається, оголосіть її з подією `"shutdown"`: + +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} + +Тут функція-обробник події `shutdown` запише текстовий рядок `"Application shutdown"` у файл `log.txt`. + +/// info | Інформація + +У функції `open()` параметр `mode="a"` означає «append», тож рядок буде додано після всього, що є у файлі, без перезапису попереднього вмісту. + +/// + +/// tip | Порада + +Зауважте, що в цьому випадку ми використовуємо стандартну Python-функцію `open()`, яка працює з файлом. + +Тобто вона включає I/O (input/output), де потрібно «чекати», поки дані буде записано на диск. + +Але `open()` не використовує `async` і `await`. + +Тому ми оголошуємо функцію-обробник події зі стандартним `def`, а не `async def`. + +/// + +### Разом `startup` і `shutdown` { #startup-and-shutdown-together } + +Велика ймовірність, що логіка для вашого запуску і вимкнення пов’язана: ви можете хотіти щось запустити, а потім завершити, отримати ресурс, а потім звільнити його тощо. + +Робити це в окремих функціях, які не діляться логікою чи змінними, складніше - доведеться зберігати значення у глобальних змінних або вдаватися до подібних трюків. + +Тому зараз рекомендується натомість використовувати `lifespan`, як пояснено вище. + +## Технічні деталі { #technical-details } + +Невелика технічна деталь для допитливих нердів. 🤓 + +Під капотом, у технічній специфікації ASGI, це частина Протоколу тривалості життя, і там визначені події `startup` і `shutdown`. + +/// info | Інформація + +Ви можете прочитати більше про обробники `lifespan` у документації Starlette про Lifespan. + +Зокрема, як працювати зі станом тривалості життя, який можна використовувати в інших ділянках вашого коду. + +/// + +## Підзастосунки { #sub-applications } + +🚨 Майте на увазі, що ці події тривалості життя (startup і shutdown) виконуються лише для головного застосунку, а не для [Підзастосунки - монтування](sub-applications.md){.internal-link target=_blank}. diff --git a/docs/uk/docs/advanced/generate-clients.md b/docs/uk/docs/advanced/generate-clients.md new file mode 100644 index 000000000..66e9193ac --- /dev/null +++ b/docs/uk/docs/advanced/generate-clients.md @@ -0,0 +1,208 @@ +# Генерація SDK { #generating-sdks } + +Оскільки **FastAPI** базується на специфікації **OpenAPI**, його API можна описати у стандартному форматі, який розуміють багато інструментів. + +Це спрощує створення актуальної **документації**, клієнтських бібліотек (**SDKs**) багатьма мовами, а також **тестування** чи **автоматизованих робочих процесів**, що залишаються синхронізованими з вашим кодом. + +У цьому посібнику ви дізнаєтеся, як згенерувати **TypeScript SDK** для вашого бекенда на FastAPI. + +## Генератори SDK з відкритим кодом { #open-source-sdk-generators } + +Універсальним варіантом є OpenAPI Generator, який підтримує **багато мов програмування** та може генерувати SDK з вашої специфікації OpenAPI. + +Для **клієнтів TypeScript** Hey API — спеціалізоване рішення, що надає оптимізований досвід для екосистеми TypeScript. + +Більше генераторів SDK ви можете знайти на OpenAPI.Tools. + +/// tip | Порада + +FastAPI автоматично генерує специфікації **OpenAPI 3.1**, тож будь-який інструмент, який ви використовуєте, має підтримувати цю версію. + +/// + +## Генератори SDK від спонсорів FastAPI { #sdk-generators-from-fastapi-sponsors } + +У цьому розділі представлено рішення від компаній, що спонсорують FastAPI: вони мають **венчурну підтримку** та **корпоративну підтримку**. Ці продукти надають **додаткові можливості** та **інтеграції** поверх високоякісно згенерованих SDK. + +Завдяки ✨ [**спонсорству FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ ці компанії допомагають підтримувати фреймворк та його **екосистему** здоровими та **сталими**. + +Їхня підтримка також демонструє сильну відданість **спільноті** FastAPI (вам), показуючи, що їм важливо не лише надавати **відмінний сервіс**, а й підтримувати **міцний і процвітаючий фреймворк**, FastAPI. 🙇 + +Наприклад, ви можете спробувати: + +* Speakeasy +* Stainless +* liblab + +Деякі з цих рішень також можуть бути з відкритим кодом або мати безкоштовні тарифи, тож ви можете спробувати їх без фінансових зобов'язань. Інші комерційні генератори SDK також доступні й їх можна знайти онлайн. 🤓 + +## Створити TypeScript SDK { #create-a-typescript-sdk } + +Почнімо з простого застосунку FastAPI: + +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} + +Зверніть увагу, що *операції шляху* визначають моделі, які вони використовують для корисного навантаження запиту та корисного навантаження відповіді, використовуючи моделі `Item` і `ResponseMessage`. + +### Документація API { #api-docs } + +Якщо ви перейдете до `/docs`, ви побачите **схеми** даних, які надсилаються в запитах і приймаються у відповідях: + + + +Ви бачите ці схеми, оскільки їх було оголошено як моделі в застосунку. + +Ця інформація доступна у **схемі OpenAPI** застосунку, а потім показується в документації API. + +Та сама інформація з моделей, яку включено до OpenAPI, може бути використана для **генерації клієнтського коду**. + +### Hey API { #hey-api } + +Коли у нас є застосунок FastAPI з моделями, ми можемо використати Hey API для генерації клієнта TypeScript. Найшвидший спосіб зробити це — через npx. + +```sh +npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client +``` + +Це згенерує TypeScript SDK у `./src/client`. + +Ви можете дізнатися, як встановити `@hey-api/openapi-ts`, і почитати про згенерований результат на їхньому сайті. + +### Використання SDK { #using-the-sdk } + +Тепер ви можете імпортувати та використовувати клієнтський код. Це може виглядати так; зверніть увагу, що ви отримуєте «автодоповнення» для методів: + + + +Ви також отримаєте автодоповнення для корисного навантаження, яке надсилаєте: + + + +/// tip | Порада + +Зверніть увагу на автодоповнення для `name` і `price`, які були визначені в застосунку FastAPI, у моделі `Item`. + +/// + +Ви бачитимете вбудовані помилки для даних, які надсилаєте: + + + +Об'єкт відповіді також матиме автодоповнення: + + + +## Застосунок FastAPI з мітками { #fastapi-app-with-tags } + +У багатьох випадках ваш застосунок FastAPI буде більшим, і ви, ймовірно, використовуватимете мітки, щоб розділяти різні групи *операцій шляху*. + +Наприклад, у вас може бути секція для **items** і окрема секція для **users**, і їх можна розділити мітками: + +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} + +### Згенерувати TypeScript-клієнт із мітками { #generate-a-typescript-client-with-tags } + +Якщо ви згенеруєте клієнт для застосунку FastAPI, що використовує мітки, зазвичай клієнтський код також буде розділено за цими мітками. + +Таким чином, ви матимете правильно впорядковані та згруповані частини клієнтського коду: + + + +У цьому випадку у вас є: + +* `ItemsService` +* `UsersService` + +### Назви методів клієнта { #client-method-names } + +Зараз згенеровані назви методів на кшталт `createItemItemsPost` виглядають не дуже охайно: + +```TypeScript +ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) +``` + +...це тому, що генератор клієнта використовує внутрішній OpenAPI **operation ID** для кожної *операції шляху*. + +OpenAPI вимагає, щоб кожен operation ID був унікальним для всіх *операцій шляху*, тому FastAPI використовує **назву функції**, **шлях** і **HTTP-метод/операцію** для генерації цього operation ID, адже так воно може гарантувати унікальність operation ID. + +Але далі я покажу, як це покращити. 🤓 + +## Користувацькі operation ID та кращі назви методів { #custom-operation-ids-and-better-method-names } + +Ви можете **змінити** спосіб **генерації** цих operation ID, щоб зробити їх простішими та мати **простіші назви методів** у клієнтах. + +У цьому випадку вам потрібно буде іншим способом гарантувати, що кожен operation ID є **унікальним**. + +Наприклад, ви можете переконатися, що кожна *операція шляху* має мітку, а потім генерувати operation ID на основі **мітки** та **назви** *операції шляху* (назви функції). + +### Користувацька функція генерування унікального ID { #custom-generate-unique-id-function } + +FastAPI використовує **унікальний ID** для кожної *операції шляху*, який застосовується для **operation ID**, а також для назв будь-яких потрібних користувацьких моделей для запитів чи відповідей. + +Ви можете налаштувати цю функцію. Вона приймає `APIRoute` і повертає строку. + +Наприклад, тут використовується перша мітка (у вас, ймовірно, буде лише одна мітка) і назва *операції шляху* (назва функції). + +Потім ви можете передати цю користувацьку функцію до **FastAPI** як параметр `generate_unique_id_function`: + +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} + +### Згенерувати TypeScript-клієнт з користувацькими operation ID { #generate-a-typescript-client-with-custom-operation-ids } + +Тепер, якщо ви згенеруєте клієнт знову, ви побачите покращені назви методів: + + + +Як бачите, тепер у назвах методів є мітка, а потім назва функції; вони більше не містять інформації з URL-шляху та HTTP-операції. + +### Попередня обробка специфікації OpenAPI для генератора клієнта { #preprocess-the-openapi-specification-for-the-client-generator } + +У згенерованому коді все ще є певна **дубльована інформація**. + +Ми вже знаємо, що цей метод стосується **items**, адже це слово є в `ItemsService` (взято з мітки), але все ще маємо назву мітки як префікс у назві методу. 😕 + +Ми, ймовірно, все одно захочемо зберегти це загалом для OpenAPI, адже так гарантується унікальність operation ID. + +Але для згенерованого клієнта ми можемо **змінити** operation ID в OpenAPI безпосередньо перед генерацією клієнтів, просто щоб зробити назви методів приємнішими та **чистішими**. + +Ми можемо завантажити JSON OpenAPI у файл `openapi.json`, а потім **прибрати цей префікс із міткою** за допомогою такого скрипту: + +{* ../../docs_src/generate_clients/tutorial004_py310.py *} + +//// tab | Node.js + +```Javascript +{!> ../../docs_src/generate_clients/tutorial004.js!} +``` + +//// + +Після цього operation ID буде перейменовано з чогось на кшталт `items-get_items` просто на `get_items`, тож генератор клієнта зможе створити простіші назви методів. + +### Згенерувати TypeScript-клієнт із попередньо обробленим OpenAPI { #generate-a-typescript-client-with-the-preprocessed-openapi } + +Оскільки кінцевий результат тепер у файлі `openapi.json`, вам потрібно оновити шлях до вхідних даних: + +```sh +npx @hey-api/openapi-ts -i ./openapi.json -o src/client +``` + +Після генерації нового клієнта ви отримаєте **чисті назви методів**, із усім **автодоповненням**, **вбудованими помилками** тощо: + + + +## Переваги { #benefits } + +Використовуючи автоматично згенеровані клієнти, ви отримаєте **автодоповнення** для: + +* Методів. +* Корисного навантаження запиту в тілі, параметрах запиту тощо. +* Корисного навантаження відповіді. + +Також ви матимете **вбудовані помилки** для всього. + +І щоразу, коли ви оновлюєте код бекенда та **перегенеровуєте** фронтенд, у ньому з'являтимуться нові *операції шляху* як методи, старі буде видалено, а будь-які інші зміни відобразяться у згенерованому коді. 🤓 + +Це також означає, що якщо щось змінилося, це буде **відображено** в клієнтському коді автоматично. А якщо ви **зіберете** клієнт, буде повідомлено про помилку, якщо є будь-яка **невідповідність** у використаних даних. + +Таким чином, ви **виявлятимете багато помилок** дуже рано в циклі розробки, замість того, щоб чекати, поки помилки проявляться у ваших кінцевих користувачів у продакшені, і лише потім намагатися з'ясувати, у чому проблема. ✨ diff --git a/docs/uk/docs/advanced/index.md b/docs/uk/docs/advanced/index.md new file mode 100644 index 000000000..1cffe0cec --- /dev/null +++ b/docs/uk/docs/advanced/index.md @@ -0,0 +1,21 @@ +# Просунутий посібник користувача { #advanced-user-guide } + +## Додаткові можливості { #additional-features } + +Основний [Навчальний посібник - Посібник користувача](../tutorial/index.md){.internal-link target=_blank} має бути достатнім, щоб провести вас через усі основні можливості **FastAPI**. + +У наступних розділах ви побачите інші опції, конфігурації та додаткові можливості. + +/// tip | Порада + +Наступні розділи не обов'язково «просунуті». + +І можливо, що рішення для вашого випадку використання може бути в одному з них. + +/// + +## Спершу прочитайте навчальний посібник { #read-the-tutorial-first } + +Ви все ще можете використовувати більшість можливостей **FastAPI**, маючи знання з основного [Навчального посібника - Посібника користувача](../tutorial/index.md){.internal-link target=_blank}. + +А в наступних розділах передбачається, що ви вже його прочитали і знайомі з основними ідеями. diff --git a/docs/uk/docs/advanced/middleware.md b/docs/uk/docs/advanced/middleware.md new file mode 100644 index 000000000..207ca96e0 --- /dev/null +++ b/docs/uk/docs/advanced/middleware.md @@ -0,0 +1,97 @@ +# Просунуте проміжне програмне забезпечення { #advanced-middleware } + +У головному навчальному посібнику ви читали, як додати [Користувацьке проміжне ПЗ](../tutorial/middleware.md){.internal-link target=_blank} до вашого застосунку. + +Також ви читали, як обробляти [CORS за допомогою `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. + +У цьому розділі розглянемо, як використовувати інше проміжне ПЗ. + +## Додавання middleware ASGI { #adding-asgi-middlewares } + +Оскільки **FastAPI** базується на Starlette і реалізує специфікацію ASGI, ви можете використовувати будь-яке проміжне ПЗ ASGI. + +Middleware не обов'язково має бути створене саме для FastAPI або Starlette, головне - щоб воно відповідало специфікації ASGI. + +Загалом, middleware ASGI — це класи, які очікують отримати застосунок ASGI як перший аргумент. + +Тож у документації до сторонніх middleware ASGI вам, імовірно, порадять зробити приблизно так: + +```Python +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +Але FastAPI (точніше Starlette) надає простіший спосіб, який гарантує, що внутрішнє middleware обробляє помилки сервера, а користувацькі обробники винятків працюють коректно. + +Для цього використовуйте `app.add_middleware()` (як у прикладі для CORS). + +```Python +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +`app.add_middleware()` приймає клас middleware як перший аргумент і будь-які додаткові аргументи, що будуть передані цьому middleware. + +## Вбудоване middleware { #integrated-middlewares } + +**FastAPI** містить кілька middleware для поширених випадків використання, далі розглянемо, як їх використовувати. + +/// note | Технічні деталі + +У наступних прикладах ви також можете використовувати `from starlette.middleware.something import SomethingMiddleware`. + +**FastAPI** надає кілька middleware у `fastapi.middleware` виключно для зручності розробника. Але більшість доступних middleware походять безпосередньо зі Starlette. + +/// + +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } + +Примушує, щоб усі вхідні запити були або `https`, або `wss`. + +Будь-який вхідний запит до `http` або `ws` буде перенаправлено на захищену схему. + +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} + +## `TrustedHostMiddleware` { #trustedhostmiddleware } + +Примушує, щоб усі вхідні запити мали коректно встановлений заголовок `Host`, щоб захиститися від атак HTTP Host Header. + +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} + +Підтримуються такі аргументи: + +- `allowed_hosts` - Список доменних імен, які слід дозволити як імена хостів. Підтримуються домени з «дикою картою», такі як `*.example.com`, для зіставлення піддоменів. Щоб дозволити будь-яке ім'я хоста, або використовуйте `allowed_hosts=["*"]`, або не додавайте це middleware. +- `www_redirect` - Якщо встановлено True, запити до не-www версій дозволених хостів буде перенаправлено до їхніх www-варіантів. Типово `True`. + +Якщо вхідний запит не проходить перевірку, буде надіслано відповідь `400`. + +## `GZipMiddleware` { #gzipmiddleware } + +Обробляє відповіді GZip для будь-якого запиту, що містить `"gzip"` у заголовку `Accept-Encoding`. + +Middleware обробляє як стандартні, так і потокові відповіді. + +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} + +Підтримуються такі аргументи: + +- `minimum_size` - Не GZip-увати відповіді, менші за цей мінімальний розмір у байтах. Типово `500`. +- `compresslevel` - Використовується під час стиснення GZip. Це ціле число в діапазоні від 1 до 9. Типово `9`. Менше значення дає швидше стиснення, але більший розмір файлів; більше значення дає повільніше стиснення, але менший розмір файлів. + +## Інше middleware { #other-middlewares } + +Є багато іншого проміжного ПЗ ASGI. + +Наприклад: + +- `ProxyHeadersMiddleware` з Uvicorn +- MessagePack + +Щоб переглянути інші доступні middleware, ознайомтеся з документацією Starlette щодо middleware та списком ASGI Awesome. diff --git a/docs/uk/docs/advanced/openapi-callbacks.md b/docs/uk/docs/advanced/openapi-callbacks.md new file mode 100644 index 000000000..1f2adb1fc --- /dev/null +++ b/docs/uk/docs/advanced/openapi-callbacks.md @@ -0,0 +1,186 @@ +# Зворотні виклики OpenAPI { #openapi-callbacks } + +Ви можете створити API з операцією шляху, яка ініціюватиме запит до зовнішнього API, створеного кимось іншим (ймовірно тим самим розробником, який буде використовувати ваш API). + +Процес, що відбувається, коли ваш застосунок API викликає зовнішній API, називається «зворотний виклик». Тому що програмне забезпечення, написане зовнішнім розробником, надсилає запит до вашого API, а потім ваш API виконує зворотний виклик, надсилаючи запит до зовнішнього API (його, ймовірно, також створив той самий розробник). + +У такому випадку вам може знадобитися задокументувати, яким має бути той зовнішній API: які операції шляху він має мати, яке тіло очікувати, яку відповідь повертати тощо. + +## Застосунок зі зворотними викликами { #an-app-with-callbacks } + +Розгляньмо це на прикладі. + +Уявімо, що ви розробляєте застосунок для створення рахунків. + +Ці рахунки матимуть `id`, `title` (необов'язково), `customer` і `total`. + +Користувач вашого API (зовнішній розробник) створить рахунок у вашому API за допомогою POST-запиту. + +Потім ваш API буде (уявімо): + +- Надсилати рахунок деякому клієнту зовнішнього розробника. +- Отримувати оплату. +- Надсилати сповіщення назад користувачу API (зовнішньому розробнику). + - Це буде зроблено шляхом надсилання POST-запиту (з вашого API) до деякого зовнішнього API, наданого тим зовнішнім розробником (це і є «зворотний виклик»). + +## Звичайний застосунок FastAPI { #the-normal-fastapi-app } + +Спочатку подивімося, як виглядав би звичайний застосунок API до додавання зворотного виклику. + +Він матиме операцію шляху, яка отримуватиме тіло `Invoice`, і параметр запиту `callback_url`, що міститиме URL для зворотного виклику. + +Ця частина цілком звична, більшість коду вам, ймовірно, уже знайома: + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} + +/// tip | Порада + +Параметр запиту `callback_url` використовує тип Pydantic Url. + +/// + +Єдина нова річ - це `callbacks=invoices_callback_router.routes` як аргумент декоратора операції шляху. Далі розглянемо, що це таке. + +## Документування зворотного виклику { #documenting-the-callback } + +Фактичний код зворотного виклику сильно залежатиме від вашого застосунку API. + +І, ймовірно, сильно відрізнятиметься від застосунку до застосунку. + +Це можуть бути лише один-два рядки коду, наприклад: + +```Python +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +Але, можливо, найважливіша частина зворотного виклику - переконатися, що користувач вашого API (зовнішній розробник) правильно реалізує зовнішній API відповідно до даних, які ваш API надсилатиме в тілі запиту зворотного виклику тощо. + +Тому далі ми додамо код, щоб задокументувати, яким має бути цей зовнішній API, щоб приймати зворотний виклик від вашого API. + +Ця документація з'явиться в Swagger UI за адресою `/docs` у вашому API і дасть змогу зовнішнім розробникам зрозуміти, як створити зовнішній API. + +У цьому прикладі сам зворотний виклик не реалізовано (це може бути лише один рядок коду), лише частину з документацією. + +/// tip | Порада + +Фактичний зворотний виклик - це просто HTTP-запит. + +Реалізуючи зворотний виклик самостійно, ви можете скористатися, наприклад, HTTPX або Requests. + +/// + +## Напишіть код документації для зворотного виклику { #write-the-callback-documentation-code } + +Цей код не виконуватиметься у вашому застосунку, він потрібен лише, щоб задокументувати, яким має бути зовнішній API. + +Але ви вже знаєте, як легко створювати автоматичну документацію для API за допомогою FastAPI. + +Тож ми скористаємося цими знаннями, щоб задокументувати, яким має бути зовнішній API... створивши операції шляху, які має реалізувати зовнішній API (ті, які викликатиме ваш API). + +/// tip | Порада + +Пишучи код для документування зворотного виклику, корисно уявити, що ви - той *зовнішній розробник*. І що ви зараз реалізуєте *зовнішній API*, а не *ваш API*. + +Тимчасово прийнявши цю точку зору ( *зовнішнього розробника* ), вам буде очевидніше, куди помістити параметри, яку Pydantic-модель використати для тіла, для відповіді тощо для того *зовнішнього API*. + +/// + +### Створіть callback `APIRouter` { #create-a-callback-apirouter } + +Спочатку створіть новий `APIRouter`, який міститиме один або кілька зворотних викликів. + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *} + +### Створіть операцію шляху зворотного виклику { #create-the-callback-path-operation } + +Щоб створити операцію шляху зворотного виклику, використайте той самий `APIRouter`, який ви створили вище. + +Вона має виглядати як звичайна операція шляху FastAPI: + +- Ймовірно має містити оголошення тіла, яке вона приймає, напр. `body: InvoiceEvent`. +- І також може містити оголошення відповіді, яку вона повертає, напр. `response_model=InvoiceEventReceived`. + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} + +Є 2 основні відмінності від звичайної операції шляху: + +- Їй не потрібен реальний код, адже ваш застосунок ніколи не викликатиме цей код. Вона використовується лише для документування зовнішнього API. Тому функція може просто містити `pass`. +- Шлях може містити вираз OpenAPI 3 (див. нижче), де можна використовувати змінні з параметрами та частини оригінального запиту, надісланого до вашого API. + +### Вираз шляху зворотного виклику { #the-callback-path-expression } + +Шлях зворотного виклику може містити вираз OpenAPI 3, який включає частини оригінального запиту, надісланого до вашого API. + +У цьому випадку це строка: + +```Python +"{$callback_url}/invoices/{$request.body.id}" +``` + +Отже, якщо користувач вашого API (зовнішній розробник) надішле запит до *вашого API* на: + +``` +https://yourapi.com/invoices/?callback_url=https://www.external.org/events +``` + +з JSON-тілом: + +```JSON +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +тоді *ваш API* опрацює рахунок і згодом надішле запит зворотного виклику на `callback_url` ( *зовнішній API* ): + +``` +https://www.external.org/events/invoices/2expen51ve +``` + +з JSON-тілом на кшталт: + +```JSON +{ + "description": "Payment celebration", + "paid": true +} +``` + +і очікуватиме відповідь від того *зовнішнього API* з JSON-тілом на кшталт: + +```JSON +{ + "ok": true +} +``` + +/// tip | Порада + +Зверніть увагу, що використаний URL зворотного виклику містить URL, отриманий як параметр запиту в `callback_url` (`https://www.external.org/events`), а також `id` рахунку зсередини JSON-тіла (`2expen51ve`). + +/// + +### Додайте маршрутизатор зворотного виклику { #add-the-callback-router } + +На цьому етапі ви маєте потрібні операції шляху зворотного виклику (ті, які має реалізувати *зовнішній розробник* у *зовнішньому API*) у створеному вище маршрутизаторі зворотного виклику. + +Тепер використайте параметр `callbacks` у декораторі операції шляху вашого API, щоб передати атрибут `.routes` (це насправді просто `list` маршрутів/операцій шляху) з цього маршрутизатора зворотного виклику: + +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} + +/// tip | Порада + +Зверніть увагу, що ви передаєте не сам маршрутизатор (`invoices_callback_router`) у `callback=`, а атрибут `.routes`, тобто `invoices_callback_router.routes`. + +/// + +### Перевірте документацію { #check-the-docs } + +Тепер ви можете запустити застосунок і перейти за адресою http://127.0.0.1:8000/docs. + +Ви побачите вашу документацію з розділом «Callbacks» для вашої операції шляху, який показує, як має виглядати зовнішній API: + + diff --git a/docs/uk/docs/advanced/openapi-webhooks.md b/docs/uk/docs/advanced/openapi-webhooks.md new file mode 100644 index 000000000..0d8a7f4c5 --- /dev/null +++ b/docs/uk/docs/advanced/openapi-webhooks.md @@ -0,0 +1,55 @@ +# Вебхуки OpenAPI { #openapi-webhooks } + +Бувають випадки, коли ви хочете повідомити **користувачів** вашого API, що ваш застосунок може викликати *їхній* застосунок (надсилаючи запит) із деякими даними, зазвичай щоб **сповістити** про певний тип **події**. + +Це означає, що замість звичного процесу, коли ваші користувачі надсилають запити до вашого API, саме **ваш API** (або ваш застосунок) може **надсилати запити до їхньої системи** (до їх API, їх застосунку). + +Зазвичай це називають **вебхуком**. + +## Кроки вебхуків { #webhooks-steps } + +Зазвичай процес такий: ви визначаєте у своєму коді, яке повідомлення надсилатимете, тобто **тіло запиту**. + +Ви також якимось чином визначаєте моменти, коли ваш застосунок надсилатиме ці запити або події. + +А **ваші користувачі** якимось чином (наприклад, у веб-дашборді) визначають **URL**, куди ваш застосунок має надсилати ці запити. + +Уся **логіка** щодо реєстрації URL для вебхуків і код для фактичного надсилання цих запитів - на ваш розсуд. Ви пишете це у **власному коді** так, як вважаєте за потрібне. + +## Документування вебхуків у **FastAPI** та OpenAPI { #documenting-webhooks-with-fastapi-and-openapi } + +У **FastAPI**, використовуючи OpenAPI, ви можете визначити назви цих вебхуків, типи HTTP-операцій, які ваш застосунок може надсилати (наприклад, `POST`, `PUT` тощо), і **тіла** запитів, які ваш застосунок надсилатиме. + +Це значно спростить для ваших користувачів **реалізацію їхніх API** для отримання ваших запитів **вебхуків**; вони навіть зможуть згенерувати частину власного коду API автоматично. + +/// info | Інформація + +Вебхуки доступні в OpenAPI 3.1.0 і вище, підтримуються FastAPI `0.99.0` і вище. + +/// + +## Застосунок із вебхуками { #an-app-with-webhooks } + +Коли ви створюєте застосунок **FastAPI**, є атрибут `webhooks`, який можна використати для визначення *вебхуків* так само, як ви визначаєте *операції шляху*, наприклад за допомогою `@app.webhooks.post()`. + +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} + +Визначені вами вебхуки потраплять до **схеми OpenAPI** та автоматичного **інтерфейсу документації**. + +/// info | Інформація + +Об'єкт `app.webhooks` насправді є просто `APIRouter` - тим самим типом, який ви використовуєте, структуризуючи застосунок у кількох файлах. + +/// + +Зверніть увагу, що з вебхуками ви фактично не оголошуєте *шлях* (на кшталт `/items/`), текст, який ви передаєте там, - це лише **ідентифікатор** вебхука (назва події). Наприклад, у `@app.webhooks.post("new-subscription")` назва вебхука - `new-subscription`. + +Це тому, що очікується, що **ваші користувачі** іншим способом (наприклад, у веб-дашборді) визначать фактичний **URL-шлях**, де вони хочуть отримувати запит вебхука. + +### Перевірте документацію { #check-the-docs } + +Тепер ви можете запустити свій застосунок і перейти за адресою http://127.0.0.1:8000/docs. + +Ви побачите у своїй документації звичайні *операції шляху*, а також деякі **вебхуки**: + + diff --git a/docs/uk/docs/advanced/path-operation-advanced-configuration.md b/docs/uk/docs/advanced/path-operation-advanced-configuration.md new file mode 100644 index 000000000..202f9317e --- /dev/null +++ b/docs/uk/docs/advanced/path-operation-advanced-configuration.md @@ -0,0 +1,172 @@ +# Додаткова конфігурація операцій шляху { #path-operation-advanced-configuration } + +## OpenAPI operationId { #openapi-operationid } + +/// warning | Попередження + +Якщо ви не «експерт» з OpenAPI, імовірно, вам це не потрібно. + +/// + +Ви можете встановити OpenAPI `operationId`, який буде використано у вашій *операції шляху*, за допомогою параметра `operation_id`. + +Потрібно переконатися, що він унікальний для кожної операції. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} + +### Використання назви *функції операції шляху* як operationId { #using-the-path-operation-function-name-as-the-operationid } + +Якщо ви хочете використовувати назви функцій ваших API як `operationId`, ви можете пройтися по всіх них і переписати `operation_id` кожної *операції шляху*, використовуючи їхній `APIRoute.name`. + +Зробіть це після додавання всіх *операцій шляху*. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} + +/// tip | Порада + +Якщо ви вручну викликаєте `app.openapi()`, оновіть значення `operationId` до цього. + +/// + +/// warning | Попередження + +Якщо ви робите це, переконайтеся, що кожна з ваших *функцій операцій шляху* має унікальну назву. + +Навіть якщо вони в різних модулях (файлах Python). + +/// + +## Виключення з OpenAPI { #exclude-from-openapi } + +Щоб виключити *операцію шляху* зі згенерованої Схеми OpenAPI (а отже, і з автоматичних систем документації), використайте параметр `include_in_schema` і встановіть його в `False`: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} + +## Розширений опис із docstring { #advanced-description-from-docstring } + +Ви можете обмежити кількість рядків із docstring *функції операції шляху*, що використовуються для OpenAPI. + +Додавання `\f` (екранованого символу «form feed») змусить **FastAPI** обрізати вивід для OpenAPI в цій точці. + +Це не з’явиться в документації, але інші інструменти (такі як Sphinx) зможуть використати решту. + +{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} + +## Додаткові відповіді { #additional-responses } + +Ймовірно, ви вже бачили, як оголошувати `response_model` і `status_code` для *операції шляху*. + +Це визначає метадані про основну відповідь *операції шляху*. + +Також можна оголосити додаткові відповіді з їхніми моделями, кодами статусу тощо. + +У документації є цілий розділ про це, ви можете прочитати його тут: [Додаткові відповіді в OpenAPI](additional-responses.md){.internal-link target=_blank}. + +## Додатково в OpenAPI { #openapi-extra } + +Коли ви оголошуєте *операцію шляху* у своєму застосунку, **FastAPI** автоматично генерує відповідні метадані про цю *операцію шляху* для включення в Схему OpenAPI. + +/// note | Технічні деталі + +У специфікації OpenAPI це називається Об'єкт Operation. + +/// + +Він містить усю інформацію про *операцію шляху* і використовується для побудови автоматичної документації. + +Він включає `tags`, `parameters`, `requestBody`, `responses` тощо. + +Цю OpenAPI-схему, специфічну для *операції шляху*, зазвичай генерує **FastAPI** автоматично, але ви також можете її розширити. + +/// tip | Порада + +Це низькорівнева точка розширення. + +Якщо вам потрібно лише оголосити додаткові відповіді, зручніше зробити це через [Додаткові відповіді в OpenAPI](additional-responses.md){.internal-link target=_blank}. + +/// + +Ви можете розширити OpenAPI-схему для *операції шляху*, використовуючи параметр `openapi_extra`. + +### Розширення OpenAPI { #openapi-extensions } + +`openapi_extra` може бути корисним, наприклад, для оголошення [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): + +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py310.py hl[6] *} + +Якщо ви відкриєте автоматичну документацію API, ваше розширення з’явиться внизу конкретної *операції шляху*. + + + +І якщо ви відкриєте згенерований OpenAPI (за адресою `/openapi.json` у вашому API), ви також побачите своє розширення як частину конкретної *операції шляху*: + +```JSON hl_lines="22" +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "x-aperture-labs-portal": "blue" + } + } + } +} +``` + +### Власна схема OpenAPI для *операції шляху* { #custom-openapi-path-operation-schema } + +Словник у `openapi_extra` буде глибоко об’єднано з автоматично згенерованою OpenAPI-схемою для *операції шляху*. + +Тож ви можете додати додаткові дані до автоматично згенерованої схеми. + +Наприклад, ви можете вирішити читати та перевіряти запит власним кодом, не використовуючи автоматичні можливості FastAPI з Pydantic, але все ж захотіти визначити запит у Схемі OpenAPI. + +Ви можете зробити це за допомогою `openapi_extra`: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py310.py hl[19:36, 39:40] *} + +У цьому прикладі ми не оголошували жодної моделі Pydantic. Насправді тіло запиту навіть не розібрано як JSON, воно читається безпосередньо як `bytes`, а функція `magic_data_reader()` відповідатиме за його розбір певним чином. + +Водночас ми можемо оголосити очікувану схему для тіла запиту. + +### Власний тип вмісту OpenAPI { #custom-openapi-content-type } + +Використовуючи той самий прийом, ви можете застосувати модель Pydantic, щоб визначити Схему JSON, яка потім включається в користувацький розділ OpenAPI-схеми для *операції шляху*. + +І ви можете зробити це, навіть якщо тип даних у запиті - не JSON. + +Наприклад, у цьому застосунку ми не використовуємо вбудовану функціональність FastAPI для отримання Схеми JSON з моделей Pydantic і не використовуємо автоматичну валідацію для JSON. Насправді ми оголошуємо тип вмісту запиту як YAML, а не JSON: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[15:20, 22] *} + +Попри те, що ми не використовуємо типову вбудовану функціональність, ми все одно використовуємо модель Pydantic, щоб вручну згенерувати Схему JSON для даних, які хочемо отримати у форматі YAML. + +Потім ми працюємо із запитом безпосередньо і отримуємо тіло як `bytes`. Це означає, що FastAPI навіть не намагатиметься розібрати корисне навантаження запиту як JSON. + +Далі у нашому коді ми безпосередньо розбираємо цей YAML-вміст і знову використовуємо ту саму модель Pydantic, щоб перевірити YAML-вміст: + +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py310.py hl[24:31] *} + +/// tip | Порада + +Тут ми перевикористовуємо ту саму модель Pydantic. + +Але так само ми могли б перевіряти дані іншим способом. + +/// diff --git a/docs/uk/docs/advanced/response-change-status-code.md b/docs/uk/docs/advanced/response-change-status-code.md new file mode 100644 index 000000000..fdf9f81c5 --- /dev/null +++ b/docs/uk/docs/advanced/response-change-status-code.md @@ -0,0 +1,31 @@ +# Відповідь - зміна коду статусу { #response-change-status-code } + +Ймовірно, ви вже читали, що можна встановити типовий [код статусу відповіді](../tutorial/response-status-code.md){.internal-link target=_blank}. + +Але інколи потрібно повернути інший код статусу, ніж типовий. + +## Випадок використання { #use-case } + +Наприклад, уявімо, що ви хочете за замовчуванням повертати код статусу HTTP «OK» `200`. + +Але якщо даних не існувало, ви хочете створити їх і повернути код статусу HTTP «CREATED» `201`. + +Водночас ви все одно хочете мати змогу фільтрувати та перетворювати повернені дані за допомогою `response_model`. + +Для таких випадків ви можете використати параметр `Response`. + +## Використовуйте параметр `Response` { #use-a-response-parameter } + +Ви можете оголосити параметр типу `Response` у своїй функції операції шляху (так само, як для кукі та заголовків). + +Потім ви можете встановити `status_code` у цьому *тимчасовому* об'єкті відповіді. + +{* ../../docs_src/response_change_status_code/tutorial001_py310.py hl[1,9,12] *} + +Після цього ви можете повернути будь-який потрібний об'єкт, як зазвичай (`dict`, модель бази даних тощо). + +І якщо ви оголосили `response_model`, він усе одно буде використаний для фільтрації та перетворення поверненого об'єкта. + +**FastAPI** використає цей *тимчасовий* об'єкт відповіді, щоб отримати код статусу (а також кукі та заголовки), і помістить їх у фінальну відповідь, що містить повернуте вами значення, відфільтроване за допомогою `response_model`. + +Ви також можете оголосити параметр `Response` у залежностях і встановлювати там код статусу. Але майте на увазі: останнє встановлене значення матиме пріоритет. diff --git a/docs/uk/docs/advanced/response-cookies.md b/docs/uk/docs/advanced/response-cookies.md new file mode 100644 index 000000000..826602e70 --- /dev/null +++ b/docs/uk/docs/advanced/response-cookies.md @@ -0,0 +1,51 @@ +# Кукі у відповіді { #response-cookies } + +## Використовуйте параметр `Response` { #use-a-response-parameter } + +Ви можете оголосити параметр типу `Response` у вашій *функції операції шляху*. + +Потім ви можете встановити кукі в цьому *тимчасовому* об'єкті відповіді. + +{* ../../docs_src/response_cookies/tutorial002_py310.py hl[1, 8:9] *} + +Після цього ви можете повернути будь-який потрібний об'єкт, як зазвичай (наприклад, `dict`, модель бази даних тощо). + +І якщо ви оголосили `response_model`, він усе одно буде використаний для фільтрації та перетворення об'єкта, який ви повернули. + +**FastAPI** використає цю *тимчасову* відповідь, щоб витягнути кукі (а також заголовки та код статусу) і помістить їх у фінальну відповідь, що містить значення, яке ви повернули, відфільтроване будь-якою `response_model`. + +Ви також можете оголосити параметр `Response` у залежностях і встановлювати в них кукі (і заголовки). + +## Повертайте `Response` безпосередньо { #return-a-response-directly } + +Ви також можете створювати кукі, повертаючи `Response` безпосередньо у вашому коді. + +Для цього ви можете створити відповідь, як описано в [Повернути відповідь безпосередньо](response-directly.md){.internal-link target=_blank}. + +Потім встановіть у ньому кукі і поверніть його: + +{* ../../docs_src/response_cookies/tutorial001_py310.py hl[10:12] *} + +/// tip | Порада + +Майте на увазі, що якщо ви повертаєте відповідь безпосередньо замість використання параметра `Response`, FastAPI поверне її напряму. + +Тому вам потрібно переконатися, що ваші дані мають коректний тип. Наприклад, сумісні з JSON, якщо ви повертаєте `JSONResponse`. + +А також що ви не надсилаєте дані, які мали б бути відфільтровані за допомогою `response_model`. + +/// + +### Більше інформації { #more-info } + +/// note | Технічні деталі + +Ви також можете використати `from starlette.responses import Response` або `from starlette.responses import JSONResponse`. + +**FastAPI** надає ті самі `starlette.responses` як `fastapi.responses` лише для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette. + +І оскільки `Response` часто використовується для встановлення заголовків і кукі, **FastAPI** також надає його як `fastapi.Response`. + +/// + +Щоб побачити всі доступні параметри та опції, перегляньте документацію в Starlette. diff --git a/docs/uk/docs/advanced/response-directly.md b/docs/uk/docs/advanced/response-directly.md new file mode 100644 index 000000000..7396ab756 --- /dev/null +++ b/docs/uk/docs/advanced/response-directly.md @@ -0,0 +1,65 @@ +# Повернення Response безпосередньо { #return-a-response-directly } + +Коли ви створюєте операцію шляху FastAPI, зазвичай ви можете повертати з неї будь-які дані: `dict`, `list`, модель Pydantic, модель бази даних тощо. + +Типово FastAPI автоматично перетворить це значення повернення на JSON, використовуючи `jsonable_encoder`, описаний у [Сумісному з JSON кодері](../tutorial/encoder.md){.internal-link target=_blank}. + +Потім, за лаштунками, він помістить ці дані, сумісні з JSON (наприклад, `dict`), у `JSONResponse`, який буде використано для надсилання відповіді клієнту. + +Але ви можете повертати `JSONResponse` безпосередньо з ваших операцій шляху. + +Це може бути корисним, наприклад, щоб повертати власні заголовки або кукі. + +## Повернення `Response` { #return-a-response } + +Насправді ви можете повертати будь-який `Response` або будь-який його підклас. + +/// tip | Порада + +`JSONResponse` сам є підкласом `Response`. + +/// + +І коли ви повертаєте `Response`, FastAPI передасть його безпосередньо. + +Він не виконуватиме жодних перетворень даних за допомогою моделей Pydantic, не перетворюватиме вміст на будь-який тип тощо. + +Це дає вам багато гнучкості. Ви можете повертати будь-які типи даних, переписувати будь-які оголошення або перевірки даних тощо. + +## Використання `jsonable_encoder` у `Response` { #using-the-jsonable-encoder-in-a-response } + +Оскільки FastAPI не вносить змін у `Response`, який ви повертаєте, вам потрібно впевнитися, що його вміст готовий. + +Наприклад, ви не можете покласти модель Pydantic у `JSONResponse`, не перетворивши її спочатку на `dict` з усіма типами даних (як-от `datetime`, `UUID` тощо), перетвореними на типи, сумісні з JSON. + +Для таких випадків ви можете використати `jsonable_encoder`, щоб перетворити ваші дані перед тим, як передати їх у відповідь: + +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} + +/// note | Технічні деталі + +Ви також можете використати `from starlette.responses import JSONResponse`. + +FastAPI надає ті самі `starlette.responses` як `fastapi.responses` просто як зручність для вас, розробника. Але більшість доступних `Response` походять безпосередньо зі Starlette. + +/// + +## Повернення власного `Response` { #returning-a-custom-response } + +Наведений вище приклад показує всі необхідні частини, але він ще не дуже корисний, адже ви могли просто повернути `item` безпосередньо, і FastAPI помістив би його у `JSONResponse`, перетворивши на `dict` тощо. Усе це відбувається за замовчуванням. + +Тепер подивімося, як це використати, щоб повернути власну відповідь. + +Припустімо, ви хочете повернути відповідь XML. + +Ви можете помістити свій вміст XML у строку, помістити це в `Response` і повернути: + +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} + +## Примітки { #notes } + +Коли ви повертаєте `Response` безпосередньо, його дані не перевіряються, не перетворюються (серіалізуються) і не документуються автоматично. + +Але ви все ще можете задокументувати це, як описано в [Додаткових відповідях в OpenAPI](additional-responses.md){.internal-link target=_blank}. + +У подальших розділах ви побачите, як використовувати/оголошувати ці власні `Response`, водночас зберігаючи автоматичне перетворення даних, документацію тощо. diff --git a/docs/uk/docs/advanced/response-headers.md b/docs/uk/docs/advanced/response-headers.md new file mode 100644 index 000000000..1c9d4e677 --- /dev/null +++ b/docs/uk/docs/advanced/response-headers.md @@ -0,0 +1,41 @@ +# Заголовки відповіді { #response-headers } + +## Використовуйте параметр `Response` { #use-a-response-parameter } + +Ви можете оголосити параметр типу `Response` у вашій функції операції шляху (так само, як і для кукі). + +Потім ви можете встановлювати заголовки в цьому *тимчасовому* обʼєкті відповіді. + +{* ../../docs_src/response_headers/tutorial002_py310.py hl[1, 7:8] *} + +Далі ви можете повернути будь-який потрібний обʼєкт, як зазвичай (наприклад, `dict`, модель бази даних тощо). + +Якщо ви оголосили `response_model`, його все одно буде використано для фільтрації та перетворення поверненого обʼєкта. + +FastAPI використає цей *тимчасовий* обʼєкт відповіді, щоб витягти заголовки (а також кукі та код статусу) і помістить їх у кінцеву відповідь, яка міститиме повернуте вами значення, відфільтроване будь-яким `response_model`. + +Також ви можете оголосити параметр `Response` у залежностях і встановлювати в них заголовки (та кукі). + +## Поверніть `Response` безпосередньо { #return-a-response-directly } + +Ви також можете додавати заголовки, коли повертаєте `Response` безпосередньо. + +Створіть відповідь, як описано в [Повернення Response безпосередньо](response-directly.md){.internal-link target=_blank}, і передайте заголовки як додатковий параметр: + +{* ../../docs_src/response_headers/tutorial001_py310.py hl[10:12] *} + +/// note | Технічні деталі + +Ви також можете використати `from starlette.responses import Response` або `from starlette.responses import JSONResponse`. + +FastAPI надає ті самі `starlette.responses` як `fastapi.responses` просто для зручності для вас, розробника. Але більшість доступних типів відповідей походять безпосередньо зі Starlette. + +Оскільки `Response` часто використовують для встановлення заголовків і кукі, FastAPI також надає його як `fastapi.Response`. + +/// + +## Власні заголовки { #custom-headers } + +Майте на увазі, що власні закриті заголовки можна додавати за допомогою префікса `X-`. + +Але якщо у вас є власні заголовки, які клієнт у браузері має бачити, вам потрібно додати їх у вашу конфігурацію CORS (докладніше в [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), використовуючи параметр `expose_headers`, задокументований у документації Starlette щодо CORS. diff --git a/docs/uk/docs/advanced/security/http-basic-auth.md b/docs/uk/docs/advanced/security/http-basic-auth.md new file mode 100644 index 000000000..e0578772d --- /dev/null +++ b/docs/uk/docs/advanced/security/http-basic-auth.md @@ -0,0 +1,107 @@ +# HTTP Basic Auth { #http-basic-auth } + +Для найпростіших випадків ви можете використовувати HTTP Basic Auth. + +У HTTP Basic Auth застосунок очікує заголовок, що містить ім'я користувача та пароль. + +Якщо він його не отримує, повертається помилка HTTP 401 «Unauthorized». + +І повертається заголовок `WWW-Authenticate` зі значенням `Basic` та необов'язковим параметром `realm`. + +Це каже браузеру показати вбудовану підсказку для введення імені користувача та пароля. + +Потім, коли ви введете це ім'я користувача та пароль, браузер автоматично надішле їх у заголовку. + +## Простий HTTP Basic Auth { #simple-http-basic-auth } + +- Імпортуйте `HTTPBasic` і `HTTPBasicCredentials`. +- Створіть «`security` scheme» за допомогою `HTTPBasic`. +- Використайте цей `security` як залежність у вашій операції шляху. +- Він повертає об'єкт типу `HTTPBasicCredentials`: + - Він містить надіслані `username` і `password`. + +{* ../../docs_src/security/tutorial006_an_py310.py hl[4,8,12] *} + +Коли ви спробуєте відкрити URL вперше (або натиснете кнопку «Execute» у документації), браузер попросить вас ввести ім'я користувача та пароль: + + + +## Перевірте ім'я користувача { #check-the-username } + +Ось більш повний приклад. + +Використайте залежність, щоб перевірити, чи правильні ім'я користувача та пароль. + +Для цього використайте стандартний модуль Python `secrets`, щоб перевірити ім'я користувача та пароль. + +`secrets.compare_digest()` повинен отримувати `bytes` або `str`, що містить лише ASCII-символи (англійські), це означає, що він не працюватиме з символами на кшталт `á`, як у `Sebastián`. + +Щоб це обійти, ми спочатку перетворюємо `username` і `password` у `bytes`, кодувавши їх у UTF-8. + +Потім ми можемо використати `secrets.compare_digest()`, щоб упевнитися, що `credentials.username` дорівнює `"stanleyjobson"`, а `credentials.password` дорівнює `"swordfish"`. + +{* ../../docs_src/security/tutorial007_an_py310.py hl[1,12:24] *} + +Це було б подібно до: + +```Python +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # Return some error + ... +``` + +Але використовуючи `secrets.compare_digest()`, це буде захищено від типу атак, що називаються «атаки за часом» (timing attacks). + +### Атаки за часом { #timing-attacks } + +Що таке «атака за часом»? + +Уявімо, що зловмисники намагаються вгадати ім'я користувача та пароль. + +Вони надсилають запит з ім'ям користувача `johndoe` та паролем `love123`. + +Тоді Python-код у вашому застосунку буде еквівалентний чомусь на кшталт: + +```Python +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Але в той момент, коли Python порівнює першу `j` у `johndoe` з першою `s` у `stanleyjobson`, він поверне `False`, тому що вже знає, що ці дві строки не однакові, «немає сенсу витрачати обчислення на порівняння решти літер». І ваш застосунок скаже «Невірні ім'я користувача або пароль». + +А потім зловмисники спробують з ім'ям користувача `stanleyjobsox` і паролем `love123`. + +І ваш код застосунку зробить щось на кшталт: + +```Python +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +Python доведеться порівняти весь `stanleyjobso` у обох `stanleyjobsox` і `stanleyjobson`, перш ніж зрозуміти, що строки різні. Тому відповідь «Невірні ім'я користувача або пароль» займе на кілька мікросекунд довше. + +#### Час відповіді допомагає зловмисникам { #the-time-to-answer-helps-the-attackers } + +У цей момент, помітивши, що сервер витратив на кілька мікросекунд більше, щоб надіслати відповідь «Невірні ім'я користувача або пароль», зловмисники знатимуть, що вони щось вгадали, деякі початкові літери правильні. + +І тоді вони можуть спробувати знову, знаючи, що це, ймовірно, щось більш схоже на `stanleyjobsox`, ніж на `johndoe`. + +#### «Професійна» атака { #a-professional-attack } + +Звісно, зловмисники не робитимуть усе це вручну, вони напишуть програму, можливо з тисячами або мільйонами перевірок за секунду. І вони отримуватимуть лише по одній правильній літері за раз. + +Але так за кілька хвилин або годин зловмисники вгадають правильні ім'я користувача та пароль, «за допомогою» нашого застосунку, просто використовуючи час, потрібний для відповіді. + +#### Виправте за допомогою `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest } + +Але в нашому коді ми насправді використовуємо `secrets.compare_digest()`. + +Коротко, він витрачає однаковий час на порівняння `stanleyjobsox` зі `stanleyjobson`, як і на порівняння `johndoe` зі `stanleyjobson`. І так само для пароля. + +Таким чином, використовуючи `secrets.compare_digest()` у коді вашого застосунку, він буде захищений від усього цього класу атак безпеки. + +### Поверніть помилку { #return-the-error } + +Після виявлення, що облікові дані неправильні, поверніть `HTTPException` з кодом статусу 401 (такий самий повертається, коли облікові дані не надані) і додайте заголовок `WWW-Authenticate`, щоб браузер знову показав підсказку входу: + +{* ../../docs_src/security/tutorial007_an_py310.py hl[26:30] *} diff --git a/docs/uk/docs/advanced/security/index.md b/docs/uk/docs/advanced/security/index.md new file mode 100644 index 000000000..a3479794f --- /dev/null +++ b/docs/uk/docs/advanced/security/index.md @@ -0,0 +1,19 @@ +# Просунута безпека { #advanced-security } + +## Додаткові можливості { #additional-features } + +Є кілька додаткових можливостей для роботи з безпекою, окрім тих, що розглянуті в [Навчальний посібник - Посібник користувача: Безпека](../../tutorial/security/index.md){.internal-link target=_blank}. + +/// tip | Порада + +Наступні розділи не обов'язково «просунуті». + +І можливо, що для вашого випадку використання рішення є в одному з них. + +/// + +## Спершу прочитайте навчальний посібник { #read-the-tutorial-first } + +У наступних розділах передбачається, що ви вже прочитали основний [Навчальний посібник - Посібник користувача: Безпека](../../tutorial/security/index.md){.internal-link target=_blank}. + +Усі вони базуються на тих самих концепціях, але надають деякі додаткові можливості. diff --git a/docs/uk/docs/advanced/security/oauth2-scopes.md b/docs/uk/docs/advanced/security/oauth2-scopes.md new file mode 100644 index 000000000..34ef04a28 --- /dev/null +++ b/docs/uk/docs/advanced/security/oauth2-scopes.md @@ -0,0 +1,274 @@ +# OAuth2 scopes { #oauth2-scopes } + +Ви можете використовувати OAuth2 scopes безпосередньо з **FastAPI**, вони інтегровані для безшовної роботи. + +Це дозволить мати більш детальну систему дозволів, відповідно до стандарту OAuth2, інтегровану у ваш застосунок OpenAPI (і документацію API). + +OAuth2 зі scopes - це механізм, який використовують багато великих провайдерів автентифікації, як-от Facebook, Google, GitHub, Microsoft, X (Twitter) тощо. Вони застосовують його, щоб надавати конкретні дозволи користувачам і застосункам. + +Кожного разу, коли ви «log in with» Facebook, Google, GitHub, Microsoft, X (Twitter), цей застосунок використовує OAuth2 зі scopes. + +У цьому розділі ви побачите, як керувати автентифікацією та авторизацією за допомогою того ж OAuth2 зі scopes у вашому застосунку **FastAPI**. + +/// warning | Попередження + +Це більш-менш просунутий розділ. Якщо ви тільки починаєте, можете пропустити його. + +Вам не обов’язково потрібні OAuth2 scopes, ви можете керувати автентифікацією та авторизацією будь-яким зручним способом. + +Але OAuth2 зі scopes можна гарно інтегрувати у ваш API (з OpenAPI) і документацію API. + +Водночас ви все одно примушуєте виконувати ці scopes або будь-які інші вимоги безпеки/авторизації так, як потрібно, у своєму коді. + +У багатьох випадках OAuth2 зі scopes - це надмірність. + +Але якщо ви знаєте, що це потрібно, або просто цікаво, читайте далі. + +/// + +## OAuth2 scopes та OpenAPI { #oauth2-scopes-and-openapi } + +Специфікація OAuth2 визначає «scopes» як список строк, розділених пробілами. + +Вміст кожної з цих строк може мати будь-який формат, але не повинен містити пробілів. + +Ці scopes представляють «дозволи». + +В OpenAPI (наприклад, у документації API) ви можете визначати «схеми безпеки». + +Коли одна з цих схем безпеки використовує OAuth2, ви також можете оголошувати та використовувати scopes. + +Кожен «scope» - це просто строка (без пробілів). + +Зазвичай їх використовують для оголошення конкретних дозволів безпеки, наприклад: + +- `users:read` або `users:write` - поширені приклади. +- `instagram_basic` використовується Facebook / Instagram. +- `https://www.googleapis.com/auth/drive` використовується Google. + +/// info | Інформація + +В OAuth2 «scope» - це просто строка, що декларує конкретний потрібний дозвіл. + +Не має значення, чи містить вона інші символи на кшталт `:` або чи це URL. + +Ці деталі специфічні для реалізації. + +Для OAuth2 це просто строки. + +/// + +## Загальний огляд { #global-view } + +Спочатку швидко подивімося на частини, що відрізняються від прикладів у головному **Навчальному посібнику - Керівництві користувача** для [OAuth2 з паролем (і хешуванням), Bearer з JWT-токенами](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Тепер із використанням OAuth2 scopes: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} + +Тепер розгляньмо ці зміни крок за кроком. + +## Схема безпеки OAuth2 { #oauth2-security-scheme } + +Перша зміна - тепер ми оголошуємо схему безпеки OAuth2 з двома доступними scopes: `me` і `items`. + +Параметр `scopes` приймає `dict`, де кожен scope - це ключ, а опис - значення: + +{* ../../docs_src/security/tutorial005_an_py310.py hl[63:66] *} + +Оскільки тепер ми оголошуємо ці scopes, вони з’являться в документації API, коли ви увійдете/авторизуєтеся. + +І ви зможете обрати, які scopes надати доступ: `me` і `items`. + +Це той самий механізм, який використовується, коли ви надаєте дозволи під час входу через Facebook, Google, GitHub тощо: + + + +## JWT токен зі scopes { #jwt-token-with-scopes } + +Тепер змініть операцію шляху токена, щоб повертати запитані scopes. + +Ми все ще використовуємо той самий `OAuth2PasswordRequestForm`. Він містить властивість `scopes` зі `list` з `str`, по одному scope, отриманому в запиті. + +І ми повертаємо scopes як частину JWT токена. + +/// danger | Обережно + +Для простоти тут ми просто додаємо отримані scopes безпосередньо до токена. + +Але у вашому застосунку, з міркувань безпеки, переконайтеся, що ви додаєте лише ті scopes, які користувач дійсно може мати, або ті, що ви попередньо визначили. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} + +## Оголосіть scopes в операціях шляху та залежностях { #declare-scopes-in-path-operations-and-dependencies } + +Тепер ми оголошуємо, що операція шляху для `/users/me/items/` вимагає scope `items`. + +Для цього імпортуємо і використовуємо `Security` з `fastapi`. + +Ви можете використовувати `Security` для оголошення залежностей (так само як `Depends`), але `Security` також приймає параметр `scopes` зі списком scopes (строк). + +У цьому випадку ми передаємо функцію-залежність `get_current_active_user` до `Security` (так само, як зробили б із `Depends`). + +А також передаємо `list` scopes, у цьому випадку лише один scope: `items` (їх могло б бути більше). + +І функція-залежність `get_current_active_user` також може оголошувати підзалежності не лише з `Depends`, а й з `Security`. Оголошуючи свою підзалежність (`get_current_user`) і додаткові вимоги до scopes. + +У цьому випадку вона вимагає scope `me` (вона могла б вимагати більш ніж один scope). + +/// note | Примітка + +Вам не обов’язково додавати різні scopes у різних місцях. + +Ми робимо це тут, щоб показати, як **FastAPI** обробляє scopes, оголошені на різних рівнях. + +/// + +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} + +/// info | Технічні деталі + +`Security` насправді є підкласом `Depends`, і має лише один додатковий параметр, який ми побачимо пізніше. + +Але використовуючи `Security` замість `Depends`, **FastAPI** знатиме, що можна оголошувати scopes безпеки, використовувати їх внутрішньо та документувати API через OpenAPI. + +Коли ви імпортуєте `Query`, `Path`, `Depends`, `Security` та інші з `fastapi`, це насправді функції, що повертають спеціальні класи. + +/// + +## Використовуйте `SecurityScopes` { #use-securityscopes } + +Тепер оновіть залежність `get_current_user`. + +Вона використовується наведеними вище залежностями. + +Тут ми використовуємо ту саму схему OAuth2, створену раніше, оголошуючи її як залежність: `oauth2_scheme`. + +Оскільки ця функція-залежність не має власних вимог до scopes, ми можемо використовувати `Depends` з `oauth2_scheme`, немає потреби застосовувати `Security`, коли не потрібно вказувати scopes безпеки. + +Ми також оголошуємо спеціальний параметр типу `SecurityScopes`, імпортований з `fastapi.security`. + +Клас `SecurityScopes` подібний до `Request` (у `Request` ми напряму отримували об’єкт запиту). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} + +## Використовуйте scopes { #use-the-scopes } + +Параметр `security_scopes` матиме тип `SecurityScopes`. + +Він матиме властивість `scopes` зі списком, що містить усі scopes, потрібні самій функції та всім залежним, які використовують її як підзалежність. Тобто всім «залежним»... це може звучати заплутано, нижче пояснено ще раз. + +Об’єкт `security_scopes` (класу `SecurityScopes`) також надає атрибут `scope_str` з одним рядком, що містить ці scopes, розділені пробілами (ми його використаємо). + +Ми створюємо `HTTPException`, який зможемо повторно використати (`raise`) у кількох місцях. + +У цьому винятку ми включаємо потрібні scopes (якщо є) як строку, розділену пробілами (використовуючи `scope_str`). Ми поміщаємо цю строку зі scopes в заголовок `WWW-Authenticate` (це частина специфікації). + +{* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} + +## Перевірте `username` і структуру даних { #verify-the-username-and-data-shape } + +Ми перевіряємо, що отримали `username`, і видобуваємо scopes. + +Потім валідовуємо ці дані за допомогою Pydantic-моделі (перехоплюючи виняток `ValidationError`), і якщо виникає помилка читання JWT токена або валідації даних Pydantic, підіймаємо раніше створений `HTTPException`. + +Для цього ми оновлюємо Pydantic-модель `TokenData` новою властивістю `scopes`. + +Валідувавши дані через Pydantic, ми гарантуємо, що, наприклад, маємо саме `list` із `str` для scopes і `str` для `username`. + +Замість, наприклад, `dict` або чогось іншого, що може зламати застосунок далі, створивши ризик безпеки. + +Ми також перевіряємо, що існує користувач із цим username, інакше підіймаємо той самий виняток. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} + +## Перевірте `scopes` { #verify-the-scopes } + +Тепер перевіряємо, що всі потрібні scopes - для цієї залежності та всіх залежних (включно з операціями шляху) - містяться в scopes, наданих у отриманому токені, інакше підіймаємо `HTTPException`. + +Для цього використовуємо `security_scopes.scopes`, що містить `list` із усіма цими scopes як `str`. + +{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} + +## Дерево залежностей і scopes { #dependency-tree-and-scopes } + +Ще раз розгляньмо дерево залежностей і scopes. + +Оскільки залежність `get_current_active_user` має підзалежність `get_current_user`, scope «me», оголошений у `get_current_active_user`, буде включений до списку потрібних scopes у `security_scopes.scopes`, переданого до `get_current_user`. + +Сама операція шляху також оголошує scope «items», отже він також буде у списку `security_scopes.scopes`, переданому до `get_current_user`. + +Ось як виглядає ієрархія залежностей і scopes: + +- Операція шляху `read_own_items` має: + - Потрібні scopes `["items"]` із залежністю: + - `get_current_active_user`: + - Функція-залежність `get_current_active_user` має: + - Потрібні scopes `["me"]` із залежністю: + - `get_current_user`: + - Функція-залежність `get_current_user` має: + - Власних scopes не потребує. + - Залежність, що використовує `oauth2_scheme`. + - Параметр `security_scopes` типу `SecurityScopes`: + - Цей параметр `security_scopes` має властивість `scopes` із `list`, що містить усі наведені вище scopes, отже: + - `security_scopes.scopes` міститиме `["me", "items"]` для операції шляху `read_own_items`. + - `security_scopes.scopes` міститиме `["me"]` для операції шляху `read_users_me`, адже він оголошений у залежності `get_current_active_user`. + - `security_scopes.scopes` міститиме `[]` (нічого) для операції шляху `read_system_status`, бо вона не оголошує жодного `Security` зі `scopes`, і її залежність `get_current_user` також не оголошує жодних `scopes`. + +/// tip | Порада + +Важливе і «магічне» тут у тому, що `get_current_user` матиме різні списки `scopes` для перевірки для кожної операції шляху. + +Усе залежить від `scopes`, оголошених у кожній операції шляху та кожній залежності в дереві залежностей для конкретної операції шляху. + +/// + +## Більше деталей про `SecurityScopes` { #more-details-about-securityscopes } + +Ви можете використовувати `SecurityScopes` у будь-якому місці й у кількох місцях, він не обов’язково має бути в «кореневій» залежності. + +Він завжди міститиме scopes безпеки, оголошені в поточних залежностях `Security` і всіх залежних для **цієї конкретної** операції шляху і **цього конкретного** дерева залежностей. + +Оскільки `SecurityScopes` міститиме всі scopes, оголошені залежними, ви можете використовувати його, щоб перевірити, що токен має потрібні scopes, у центральній функції-залежності, а потім оголошувати різні вимоги до scopes у різних операціях шляху. + +Вони перевірятимуться незалежно для кожної операції шляху. + +## Перевірте { #check-it } + +Якщо ви відкриєте документацію API, ви зможете автентифікуватися і вказати, які scopes хочете авторизувати. + + + +Якщо ви не оберете жодного scope, ви будете «автентифіковані», але при спробі доступу до `/users/me/` або `/users/me/items/` отримаєте помилку про недостатні дозволи. Ви все ще матимете доступ до `/status/`. + +Якщо оберете scope `me`, але не scope `items`, ви зможете отримати доступ до `/users/me/`, але не до `/users/me/items/`. + +Так станеться зі стороннім застосунком, який спробує звернутися до однієї з цих операцій шляху з токеном, наданим користувачем, залежно від того, скільки дозволів користувач надав застосунку. + +## Про сторонні інтеграції { #about-third-party-integrations } + +У цьому прикладі ми використовуємо «потік паролю» OAuth2. + +Це доречно, коли ми входимо у власний застосунок, ймовірно, з власним фронтендом. + +Адже ми можемо довіряти йому отримання `username` і `password`, бо ми його контролюємо. + +Але якщо ви створюєте OAuth2-застосунок, до якого підключатимуться інші (тобто якщо ви створюєте провайдера автентифікації на кшталт Facebook, Google, GitHub тощо), слід використовувати один з інших потоків. + +Найпоширеніший - неявний потік (implicit flow). + +Найбезпечніший - потік коду (code flow), але його складніше реалізувати, оскільки він потребує більше кроків. Через складність багато провайдерів у підсумку радять неявний потік. + +/// note | Примітка + +Часто кожен провайдер автентифікації називає свої потоки по-різному, роблячи це частиною свого бренду. + +Але зрештою вони реалізують той самий стандарт OAuth2. + +/// + +**FastAPI** містить утиліти для всіх цих потоків автентифікації OAuth2 у `fastapi.security.oauth2`. + +## `Security` у параметрі декоратора `dependencies` { #security-in-decorator-dependencies } + +Так само як ви можете визначити `list` із `Depends` у параметрі `dependencies` декоратора (як пояснено в [Залежності в декораторах операцій шляху](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), ви також можете використовувати там `Security` зі `scopes`. diff --git a/docs/uk/docs/advanced/settings.md b/docs/uk/docs/advanced/settings.md new file mode 100644 index 000000000..dccb4b091 --- /dev/null +++ b/docs/uk/docs/advanced/settings.md @@ -0,0 +1,302 @@ +# Налаштування та змінні оточення { #settings-and-environment-variables } + +У багатьох випадках вашому застосунку можуть знадобитися зовнішні налаштування або конфігурації, наприклад секретні ключі, облікові дані бази даних, облікові дані для email-сервісів тощо. + +Більшість із цих налаштувань змінні (можуть змінюватися), як-от URL-адреси баз даних. І багато з них можуть бути чутливими, як-от секрети. + +З цієї причини поширено надавати їх у змінних оточення, які зчитуються застосунком. + +/// tip | Порада + +Щоб зрозуміти змінні оточення, ви можете прочитати [Змінні оточення](../environment-variables.md){.internal-link target=_blank}. + +/// + +## Типи та перевірка { #types-and-validation } + +Ці змінні оточення можуть містити лише текстові строки, оскільки вони зовнішні до Python і мають бути сумісні з іншими програмами та рештою системи (і навіть з різними операційними системами, як-от Linux, Windows, macOS). + +Це означає, що будь-яке значення, прочитане в Python зі змінної оточення, буде `str`, і будь-яке перетворення в інший тип або будь-яка перевірка мають виконуватися в коді. + +## Pydantic `Settings` { #pydantic-settings } + +На щастя, Pydantic надає чудовий інструмент для обробки цих налаштувань із змінних оточення - Pydantic: Settings management. + +### Встановіть `pydantic-settings` { #install-pydantic-settings } + +Спершу переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили пакет `pydantic-settings`: + +
+ +```console +$ pip install pydantic-settings +---> 100% +``` + +
+ +Він також входить у склад, якщо ви встановлюєте додаткові можливості «all» за допомогою: + +
+ +```console +$ pip install "fastapi[all]" +---> 100% +``` + +
+ +### Створіть об'єкт `Settings` { #create-the-settings-object } + +Імпортуйте `BaseSettings` із Pydantic і створіть підклас, дуже подібно до моделі Pydantic. + +Так само, як і з моделями Pydantic, ви оголошуєте атрибути класу з анотаціями типів і, за потреби, значеннями за замовчуванням. + +Ви можете використовувати всі ті самі можливості перевірки та інструменти, що й для моделей Pydantic, як-от різні типи даних і додаткові перевірки з `Field()`. + +{* ../../docs_src/settings/tutorial001_py310.py hl[2,5:8,11] *} + +/// tip | Порада + +Якщо вам потрібно щось швидко скопіювати й вставити, не використовуйте цей приклад, скористайтеся останнім нижче. + +/// + +Потім, коли ви створите екземпляр цього класу `Settings` (у цьому випадку в об'єкті `settings`), Pydantic зчитуватиме змінні оточення без урахування регістру, тож верхньорегістрова змінна `APP_NAME` все одно буде прочитана для атрибута `app_name`. + +Далі він перетворить і перевірить дані. Тож коли ви використовуватимете об'єкт `settings`, у вас будуть дані тих типів, які ви оголосили (наприклад, `items_per_user` буде `int`). + +### Використовуйте `settings` { #use-the-settings } + +Потім ви можете використати новий об'єкт `settings` у вашому застосунку: + +{* ../../docs_src/settings/tutorial001_py310.py hl[18:20] *} + +### Запустіть сервер { #run-the-server } + +Далі ви б запустили сервер, передаючи конфігурації як змінні оточення, наприклад, ви можете встановити `ADMIN_EMAIL` і `APP_NAME` так: + +
+ +```console +$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +/// tip | Порада + +Щоб встановити кілька змінних оточення для однієї команди, просто розділіть їх пробілами і розмістіть усі перед командою. + +/// + +Після цього налаштування `admin_email` буде встановлено в `"deadpool@example.com"`. + +`app_name` буде `"ChimichangApp"`. + +А `items_per_user` збереже своє значення за замовчуванням `50`. + +## Налаштування в іншому модулі { #settings-in-another-module } + +Ви можете розмістити ці налаштування в іншому модулі, як ви бачили в [Більші застосунки - кілька файлів](../tutorial/bigger-applications.md){.internal-link target=_blank}. + +Наприклад, у вас може бути файл `config.py` з: + +{* ../../docs_src/settings/app01_py310/config.py *} + +А потім використати його у файлі `main.py`: + +{* ../../docs_src/settings/app01_py310/main.py hl[3,11:13] *} + +/// tip | Порада + +Вам також знадобиться файл `__init__.py`, як ви бачили в [Більші застосунки - кілька файлів](../tutorial/bigger-applications.md){.internal-link target=_blank}. + +/// + +## Налаштування як залежність { #settings-in-a-dependency } + +Іноді може бути корисно надавати налаштування через залежність, замість того, щоб мати глобальний об'єкт `settings`, який використовується всюди. + +Це може бути особливо корисно під час тестування, оскільки дуже легко переписати залежність власними налаштуваннями. + +### Файл конфігурації { #the-config-file } + +Продовжуючи попередній приклад, ваш файл `config.py` може виглядати так: + +{* ../../docs_src/settings/app02_an_py310/config.py hl[10] *} + +Зверніть увагу, що тепер ми не створюємо екземпляр за замовчуванням `settings = Settings()`. + +### Основний файл застосунку { #the-main-app-file } + +Тепер ми створюємо залежність, яка повертає новий `config.Settings()`. + +{* ../../docs_src/settings/app02_an_py310/main.py hl[6,12:13] *} + +/// tip | Порада + +Ми обговоримо `@lru_cache` трохи згодом. + +Поки що можете вважати, що `get_settings()` - це звичайна функція. + +/// + +А далі ми можемо вимагати її у *функції операції шляху* як залежність і використовувати будь-де, де це потрібно. + +{* ../../docs_src/settings/app02_an_py310/main.py hl[17,19:21] *} + +### Налаштування і тестування { #settings-and-testing } + +Потім буде дуже просто надати інший об'єкт налаштувань під час тестування, створивши переписування залежності для `get_settings`: + +{* ../../docs_src/settings/app02_an_py310/test_main.py hl[9:10,13,21] *} + +У переписуванні залежності ми встановлюємо нове значення `admin_email` під час створення нового об'єкта `Settings`, а потім повертаємо цей новий об'єкт. + +Після цього ми можемо перевірити, що саме він використовується. + +## Читання файлу `.env` { #reading-a-env-file } + +Якщо у вас багато налаштувань, які можуть часто змінюватися, можливо в різних оточеннях, може бути корисно розмістити їх у файлі, а потім зчитувати їх із нього так, ніби це змінні оточення. + +Ця практика достатньо поширена, тож має назву - ці змінні оточення зазвичай розміщуються у файлі `.env`, а сам файл називається «dotenv». + +/// tip | Порада + +Файл, що починається з крапки (`.`), є прихованим у системах, подібних до Unix, як-от Linux і macOS. + +Але файл dotenv не обов'язково має мати саме таку назву. + +/// + +Pydantic має підтримку читання з таких типів файлів за допомогою зовнішньої бібліотеки. Ви можете дізнатися більше тут: Pydantic Settings: Dotenv (.env) support. + +/// tip | Порада + +Щоб це працювало, потрібно виконати `pip install python-dotenv`. + +/// + +### Файл `.env` { #the-env-file } + +У вас може бути файл `.env` із вмістом: + +```bash +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +### Зчитування налаштувань із `.env` { #read-settings-from-env } + +Потім оновіть ваш `config.py` так: + +{* ../../docs_src/settings/app03_an_py310/config.py hl[9] *} + +/// tip | Порада + +Атрибут `model_config` використовується лише для конфігурації Pydantic. Докладніше: Pydantic: Concepts: Configuration. + +/// + +Тут ми визначаємо конфіг `env_file` усередині вашого класу Pydantic `Settings` і задаємо значення - ім'я файла з dotenv, який ми хочемо використати. + +### Створення `Settings` лише один раз за допомогою `lru_cache` { #creating-the-settings-only-once-with-lru-cache } + +Читання файла з диска зазвичай є дорогою (повільною) операцією, тож, імовірно, ви захочете робити це лише один раз і потім перевикористовувати той самий об'єкт налаштувань замість зчитування для кожного запиту. + +Але щоразу, коли ми робимо: + +```Python +Settings() +``` + +буде створено новий об'єкт `Settings`, і під час створення він знову зчитуватиме файл `.env`. + +Якби функція залежності виглядала так: + +```Python +def get_settings(): + return Settings() +``` + +ми створювали б цей об'єкт для кожного запиту і читали б файл `.env` для кожного запиту. ⚠️ + +Але оскільки ми використовуємо декоратор `@lru_cache` зверху, об'єкт `Settings` буде створено лише один раз, під час першого виклику. ✔️ + +{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *} + +Потім для будь-якого подальшого виклику `get_settings()` у залежностях для наступних запитів, замість виконання внутрішнього коду `get_settings()` і створення нового об'єкта `Settings`, він повертатиме той самий об'єкт, що був повернутий під час першого виклику, знову і знову. + +#### Технічні деталі `lru_cache` { #lru-cache-technical-details } + +`@lru_cache` модифікує функцію, яку він декорує, так, щоб вона повертала те саме значення, що й уперше, замість повторного обчислення, виконуючи код функції щоразу. + +Тобто функція під ним буде виконана один раз для кожної комбінації аргументів. А потім значення, повернені кожною з цих комбінацій аргументів, використовуватимуться знову і знову щоразу, коли функцію викликають із точно такою ж комбінацією аргументів. + +Наприклад, якщо у вас є функція: + +```Python +@lru_cache +def say_hi(name: str, salutation: str = "Ms."): + return f"Hello {salutation} {name}" +``` + +ваша програма може виконуватись так: + +```mermaid +sequenceDiagram + +participant code as Code +participant function as say_hi() +participant execute as Execute function + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Camila") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick", salutation="Mr.") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: return stored result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end +``` + +У випадку з нашою залежністю `get_settings()` функція взагалі не приймає жодних аргументів, тож вона завжди повертає те саме значення. + +Таким чином, вона поводиться майже так само, якби це була просто глобальна змінна. Але оскільки використовується функція залежності, ми можемо легко переписати її для тестування. + +`@lru_cache` є частиною `functools`, що входить до стандартної бібліотеки Python, більше про це можна прочитати в документації Python для `@lru_cache`. + +## Підсумок { #recap } + +Ви можете використовувати Pydantic Settings для обробки налаштувань або конфігурацій вашого застосунку, з усією потужністю моделей Pydantic. + +- Використовуючи залежність, ви можете спростити тестування. +- Ви можете використовувати з ним файли `.env`. +- Використання `@lru_cache` дає змогу уникнути повторного читання файла dotenv для кожного запиту, водночас дозволяючи переписувати його під час тестування. diff --git a/docs/uk/docs/advanced/sub-applications.md b/docs/uk/docs/advanced/sub-applications.md new file mode 100644 index 000000000..5e611c6ff --- /dev/null +++ b/docs/uk/docs/advanced/sub-applications.md @@ -0,0 +1,67 @@ +# Підзастосунки - монтування { #sub-applications-mounts } + +Якщо вам потрібно мати два незалежні застосунки FastAPI з власними незалежними OpenAPI та власними інтерфейсами документації, ви можете мати головний застосунок і «змонтувати» один або кілька підзастосунків. + +## Монтування застосунку **FastAPI** { #mounting-a-fastapi-application } + +«Монтування» означає додавання повністю «незалежного» застосунку на конкретний шлях, який далі обробляє все під цим шляхом за допомогою _операцій шляху_, оголошених у цьому підзастосунку. + +### Застосунок верхнього рівня { #top-level-application } + +Спочатку створіть головний, верхньорівневий застосунок **FastAPI** та його _операції шляху_: + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *} + +### Підзастосунок { #sub-application } + +Потім створіть свій підзастосунок та його _операції шляху_. + +Цей підзастосунок - просто ще один стандартний застосунок FastAPI, але саме його буде «змонтовано»: + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *} + +### Змонтуйте підзастосунок { #mount-the-sub-application } + +У вашому застосунку верхнього рівня, `app`, змонтуйте підзастосунок `subapi`. + +У цьому випадку його буде змонтовано за шляхом `/subapi`: + +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *} + +### Перевірте автоматичну документацію API { #check-the-automatic-api-docs } + +Тепер запустіть команду `fastapi` з вашим файлом: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +І відкрийте документацію за адресою http://127.0.0.1:8000/docs. + +Ви побачите автоматичну документацію API для головного застосунку, що містить лише його власні _операції шляху_: + + + +А потім відкрийте документацію для підзастосунку за адресою http://127.0.0.1:8000/subapi/docs. + +Ви побачите автоматичну документацію API для підзастосунку, що містить лише його власні _операції шляху_, усі з правильним префіксом підшляху `/subapi`: + + + +Якщо ви спробуєте взаємодіяти з будь-яким із двох інтерфейсів користувача, вони працюватимуть коректно, адже браузер зможе спілкуватися з кожним конкретним застосунком або підзастосунком. + +### Технічні деталі: `root_path` { #technical-details-root-path } + +Коли ви монтуєте підзастосунок, як описано вище, FastAPI подбає про передачу шляху монтування для підзастосунку, використовуючи механізм зі специфікації ASGI під назвою `root_path`. + +Таким чином підзастосунок знатиме, що слід використовувати цей префікс шляху для інтерфейсу документації. + +Підзастосунок також може мати власні змонтовані підзастосунки, і все працюватиме коректно, оскільки FastAPI автоматично обробляє всі ці `root_path`. + +Ви дізнаєтеся більше про `root_path` і як використовувати його явно в розділі [За представником](behind-a-proxy.md){.internal-link target=_blank}. diff --git a/docs/uk/docs/advanced/templates.md b/docs/uk/docs/advanced/templates.md new file mode 100644 index 000000000..9e1ce3709 --- /dev/null +++ b/docs/uk/docs/advanced/templates.md @@ -0,0 +1,126 @@ +# Шаблони { #templates } + +Ви можете використовувати будь-який рушій шаблонів з **FastAPI**. + +Поширений вибір - Jinja2, той самий, що використовується у Flask та інших інструментах. + +Є утиліти для простої конфігурації, які ви можете використовувати безпосередньо у вашому застосунку **FastAPI** (надає Starlette). + +## Встановіть залежності { #install-dependencies } + +Переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md){.internal-link target=_blank}, активували його та встановили `jinja2`: + +
+ +```console +$ pip install jinja2 + +---> 100% +``` + +
+ +## Використання `Jinja2Templates` { #using-jinja2templates } + +- Імпортуйте `Jinja2Templates`. +- Створіть об'єкт `templates`, який ви зможете перевикористовувати. +- Оголосіть параметр `Request` в *операції шляху*, яка повертатиме шаблон. +- Використайте створені `templates`, щоб зрендерити та повернути `TemplateResponse`; передайте назву шаблону, об'єкт `request` і словник «контекст» з парами ключ-значення, які будуть використані всередині шаблону Jinja2. + +{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *} + +/// note | Примітка + +До FastAPI 0.108.0, Starlette 0.29.0, параметр `name` був першим. + +Також раніше, у попередніх версіях, об'єкт `request` передавався як частина пар ключ-значення в контексті для Jinja2. + +/// + +/// tip | Порада + +Якщо вказати `response_class=HTMLResponse`, інтерфейс документації знатиме, що відповідь буде HTML. + +/// + +/// note | Технічні деталі + +Можна також використати `from starlette.templating import Jinja2Templates`. + +**FastAPI** надає той самий `starlette.templating` як `fastapi.templating` просто для зручності для вас, розробника. Але більшість доступних відповідей надходять безпосередньо зі Starlette. Так само з `Request` і `StaticFiles`. + +/// + +## Створення шаблонів { #writing-templates } + +Потім ви можете написати шаблон у `templates/item.html`, наприклад: + +```jinja hl_lines="7" +{!../../docs_src/templates/templates/item.html!} +``` + +### Значення контексту шаблону { #template-context-values } + +У HTML, який містить: + +{% raw %} + +```jinja +Item ID: {{ id }} +``` + +{% endraw %} + +...буде показано `id`, взятий із «контексту» `dict`, який ви передали: + +```Python +{"id": id} +``` + +Наприклад, з ID `42` це буде відображено як: + +```html +Item ID: 42 +``` + +### Аргументи `url_for` у шаблоні { #template-url-for-arguments } + +Ви також можете використовувати `url_for()` у шаблоні - вона приймає ті самі аргументи, що й ваша *функція операції шляху*. + +Тож фрагмент: + +{% raw %} + +```jinja + +``` + +{% endraw %} + +...згенерує посилання на той самий URL, який оброблятиме *функція операції шляху* `read_item(id=id)`. + +Наприклад, з ID `42` це буде відображено як: + +```html + +``` + +## Шаблони і статичні файли { #templates-and-static-files } + +Ви також можете використовувати `url_for()` у шаблоні, наприклад з `StaticFiles`, які ви змонтували з `name="static"`. + +```jinja hl_lines="4" +{!../../docs_src/templates/templates/item.html!} +``` + +У цьому прикладі це посилатиметься на файл CSS у `static/styles.css` за допомогою: + +```CSS hl_lines="4" +{!../../docs_src/templates/static/styles.css!} +``` + +І оскільки ви використовуєте `StaticFiles`, цей файл CSS буде автоматично обслуговуватись вашим застосунком **FastAPI** за URL `/static/styles.css`. + +## Детальніше { #more-details } + +Докладніше, зокрема як тестувати шаблони, дивіться документацію Starlette щодо шаблонів. diff --git a/docs/uk/docs/advanced/testing-dependencies.md b/docs/uk/docs/advanced/testing-dependencies.md new file mode 100644 index 000000000..3061e745d --- /dev/null +++ b/docs/uk/docs/advanced/testing-dependencies.md @@ -0,0 +1,53 @@ +# Тестування залежностей з переписуваннями { #testing-dependencies-with-overrides } + +## Переписування залежностей під час тестування { #overriding-dependencies-during-testing } + +Є сценарії, коли ви можете захотіти переписати залежність під час тестування. + +Ви не хочете запускати оригінальну залежність (ані будь-які її підзалежності). + +Натомість ви хочете надати іншу залежність, яку буде використано лише під час тестів (можливо, тільки в окремих тестах), і яка повертатиме значення, що підставляється всюди, де раніше використовувалося значення оригінальної залежності. + +### Випадки використання: зовнішній сервіс { #use-cases-external-service } + +Наприклад, у вас є зовнішній постачальник автентифікації, який потрібно викликати. + +Ви надсилаєте йому токен, і він повертає автентифікованого користувача. + +Такий постачальник може стягувати плату за кожний запит, а виклик може займати більше часу, ніж використання фіксованого фальшивого користувача для тестів. + +Ймовірно, ви хочете протестувати зовнішнього постачальника один раз, але не обов'язково викликати його для кожного тесту. + +У такому разі ви можете переписати залежність, що викликає цього постачальника, і використати власну залежність, яка повертає фальшивого користувача, лише для ваших тестів. + +### Використовуйте атрибут `app.dependency_overrides` { #use-the-app-dependency-overrides-attribute } + +Для таких випадків ваш застосунок FastAPI має атрибут `app.dependency_overrides`, це простий `dict`. + +Щоб переписати залежність для тестування, вкажіть у ключі оригінальну залежність (функцію), а значенням - ваше переписування залежності (іншу функцію). + +Після цього FastAPI викликатиме це переписування замість оригінальної залежності. + +{* ../../docs_src/dependency_testing/tutorial001_an_py310.py hl[26:27,30] *} + +/// tip | Порада + +Ви можете налаштувати переписування для залежності, яка використовується де завгодно у вашому застосунку FastAPI. + +Оригінальна залежність може використовуватися у функції операції шляху, у декораторі операції шляху (коли ви не використовуєте повернуте значення), у виклику `.include_router()` тощо. + +FastAPI усе одно зможе її переписати. + +/// + +Потім ви можете скинути переписування (видалити їх), встановивши для `app.dependency_overrides` порожній `dict`: + +```Python +app.dependency_overrides = {} +``` + +/// tip | Порада + +Якщо ви хочете переписувати залежність лише під час окремих тестів, встановіть переписування на початку тесту (всередині тестової функції) і скиньте його наприкінці (в кінці тестової функції). + +/// diff --git a/docs/uk/docs/advanced/testing-events.md b/docs/uk/docs/advanced/testing-events.md new file mode 100644 index 000000000..040b4f756 --- /dev/null +++ b/docs/uk/docs/advanced/testing-events.md @@ -0,0 +1,11 @@ +# Тестування подій: тривалість життя та запуск - вимкнення { #testing-events-lifespan-and-startup-shutdown } + +Коли вам потрібно, щоб `lifespan` виконувався у ваших тестах, ви можете використати `TestClient` з оператором `with`: + +{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *} + +Ви можете прочитати більше у [«Запуск тривалості життя у тестах на офіційному сайті документації Starlette.»](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) + +Для застарілих подій `startup` і `shutdown` ви можете використовувати `TestClient` так: + +{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *} diff --git a/docs/uk/docs/advanced/testing-websockets.md b/docs/uk/docs/advanced/testing-websockets.md new file mode 100644 index 000000000..cec576fdd --- /dev/null +++ b/docs/uk/docs/advanced/testing-websockets.md @@ -0,0 +1,13 @@ +# Тестування WebSocket { #testing-websockets } + +Ви можете використовувати той самий `TestClient` для тестування WebSocket. + +Для цього використайте `TestClient` в інструкції `with`, підключаючись до WebSocket: + +{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *} + +/// note | Примітка + +Докладніше дивіться документацію Starlette з тестування WebSocket. + +/// diff --git a/docs/uk/docs/advanced/using-request-directly.md b/docs/uk/docs/advanced/using-request-directly.md new file mode 100644 index 000000000..81b90f19b --- /dev/null +++ b/docs/uk/docs/advanced/using-request-directly.md @@ -0,0 +1,55 @@ +# Використання Request безпосередньо { #using-the-request-directly } + +Дотепер ви оголошували потрібні вам частини запиту разом з їх типами. + +Отримуючи дані з: +- шляху як параметрів; +- заголовків; +- кукі; +- тощо. + +І таким чином **FastAPI** перевіряє ці дані, перетворює їх і автоматично генерує документацію для вашого API. + +Але бувають ситуації, коли може знадобитися доступ до об'єкта `Request` безпосередньо. + +## Деталі про об'єкт `Request` { #details-about-the-request-object } + +Оскільки під капотом **FastAPI** - це **Starlette** з шаром інструментів зверху, ви можете за потреби використовувати об'єкт `Request` Starlette безпосередньо. + +Це також означає, що якщо ви отримуєте дані безпосередньо з об'єкта `Request` (наприклад, читаєте тіло), FastAPI не буде їх перевіряти, перетворювати або документувати (через OpenAPI для автоматичного інтерфейсу користувача API). + +Водночас будь-який інший параметр, оголошений звичайним способом (наприклад, тіло з моделлю Pydantic), і надалі буде перевірений, перетворений, анотований тощо. + +Але є окремі випадки, коли корисно отримати об'єкт `Request`. + +## Використовуйте об'єкт `Request` безпосередньо { #use-the-request-object-directly } + +Припустімо, ви хочете отримати IP-адресу/хост клієнта всередині вашої функції операції шляху. + +Для цього потрібно звернутися до запиту безпосередньо. + +{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *} + +Якщо вказати у функції операції шляху параметр типу `Request`, **FastAPI** передасть у нього об'єкт `Request`. + +/// tip | Порада + +Зауважте, що в цьому прикладі ми оголошуємо параметр шляху поряд із параметром запиту. + +Тому параметр шляху буде витягнуто, перевірено, перетворено до вказаного типу та анотовано в OpenAPI. + +Так само ви можете оголошувати будь-які інші параметри звичайним чином і додатково отримувати `Request`. + +/// + +## Документація `Request` { #request-documentation } + +Докладніше про об'єкт `Request` на офіційному сайті документації Starlette. + +/// note | Технічні деталі + +Ви також можете використати `from starlette.requests import Request`. + +**FastAPI** надає його безпосередньо як зручність для вас, розробника. Але він походить безпосередньо із Starlette. + +/// diff --git a/docs/uk/docs/advanced/websockets.md b/docs/uk/docs/advanced/websockets.md new file mode 100644 index 000000000..21bb61d7e --- /dev/null +++ b/docs/uk/docs/advanced/websockets.md @@ -0,0 +1,186 @@ +# WebSockets { #websockets } + +Ви можете використовувати WebSockets з **FastAPI**. + +## Встановіть `websockets` { #install-websockets } + +Переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md){.internal-link target=_blank}, активували його та встановили `websockets` (бібліотеку Python, що полегшує використання протоколу «WebSocket»): + +
+ +```console +$ pip install websockets + +---> 100% +``` + +
+ +## Клієнт WebSockets { #websockets-client } + +### У продакшені { #in-production } + +У вашій продакшен-системі у вас, напевно, є фронтенд, створений за допомогою сучасного фреймворку на кшталт React, Vue.js або Angular. + +Для спілкування через WebSockets з бекендом ви, ймовірно, використовуватимете утиліти вашого фронтенду. + +Або у вас може бути нативний мобільний застосунок, що напряму спілкується з вашим WebSocket-бекендом нативним кодом. + +Або будь-який інший спосіб спілкування з кінцевою точкою WebSocket. + +--- + +Але для цього прикладу ми використаємо дуже простий HTML-документ з невеликим JavaScript, усе всередині довгого рядка. + +Звісно, це не оптимально і ви б не використовували це у продакшені. + +У продакшені ви б використали один з варіантів вище. + +Але це найпростіший спосіб зосередитися на серверній частині WebSockets і мати робочий приклад: + +{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} + +## Створіть `websocket` { #create-a-websocket } + +У вашому застосунку **FastAPI** створіть `websocket`: + +{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} + +/// note | Технічні деталі + +Ви також можете використати `from starlette.websockets import WebSocket`. + +**FastAPI** надає той самий `WebSocket` напряму як зручність для вас, розробника. Але він походить безпосередньо зі Starlette. + +/// + +## Очікуйте повідомлення та надсилайте повідомлення { #await-for-messages-and-send-messages } + +У вашому маршруті WebSocket ви можете `await` повідомлення і надсилати повідомлення. + +{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} + +Ви можете отримувати та надсилати бінарні, текстові та JSON-дані. + +## Спробуйте { #try-it } + +Якщо ваш файл називається `main.py`, запустіть ваш застосунок командою: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Відкрийте у браузері http://127.0.0.1:8000. + +Ви побачите просту сторінку на кшталт: + + + +Ви можете вводити повідомлення у поле вводу та надсилати їх: + + + +І ваш застосунок **FastAPI** з WebSockets відповість: + + + +Ви можете надсилати (і отримувати) багато повідомлень: + + + +І всі вони використовуватимуть те саме з'єднання WebSocket. + +## Використання `Depends` та іншого { #using-depends-and-others } + +У кінцевих точках WebSocket ви можете імпортувати з `fastapi` і використовувати: + +* `Depends` +* `Security` +* `Cookie` +* `Header` +* `Path` +* `Query` + +Вони працюють так само, як для інших ендпойнтів FastAPI/*операцій шляху*: + +{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} + +/// info + +Оскільки це WebSocket, не має сенсу піднімати `HTTPException`, натомість ми піднімаємо `WebSocketException`. + +Ви можете використати код закриття з чинних кодів, визначених у специфікації. + +/// + +### Спробуйте WebSockets із залежностями { #try-the-websockets-with-dependencies } + +Якщо ваш файл називається `main.py`, запустіть ваш застосунок командою: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Відкрийте у браузері http://127.0.0.1:8000. + +Там ви можете встановити: + +* «Item ID», який використовується у шляху. +* «Token», який використовується як параметр запиту. + +/// tip + +Зверніть увагу, що параметр запиту `token` буде оброблено залежністю. + +/// + +Після цього ви зможете під'єднати WebSocket, а далі надсилати й отримувати повідомлення: + + + +## Обробка відключень і кількох клієнтів { #handling-disconnections-and-multiple-clients } + +Коли з'єднання WebSocket закривається, `await websocket.receive_text()` підніме виняток `WebSocketDisconnect`, який ви можете перехопити й обробити, як у цьому прикладі. + +{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} + +Щоб спробувати: + +* Відкрийте застосунок у кількох вкладках браузера. +* Надсилайте з них повідомлення. +* Потім закрийте одну з вкладок. + +Це підніме виняток `WebSocketDisconnect`, і всі інші клієнти отримають повідомлення на кшталт: + +``` +Client #1596980209979 left the chat +``` + +/// tip + +Застосунок вище - це мінімальний і простий приклад, що демонструє, як обробляти та розсилати повідомлення кільком з'єднанням WebSocket. + +Але майте на увазі, що оскільки все обробляється в пам'яті, в одному списку, це працюватиме лише поки процес запущений, і лише з одним процесом. + +Якщо вам потрібне щось просте для інтеграції з FastAPI, але більш надійне, з підтримкою Redis, PostgreSQL чи інших, перегляньте encode/broadcaster. + +/// + +## Детальніше { #more-info } + +Щоб дізнатися більше про можливості, перегляньте документацію Starlette: + +* Клас `WebSocket`. +* Обробка WebSocket на основі класів. diff --git a/docs/uk/docs/advanced/wsgi.md b/docs/uk/docs/advanced/wsgi.md new file mode 100644 index 000000000..896924135 --- /dev/null +++ b/docs/uk/docs/advanced/wsgi.md @@ -0,0 +1,51 @@ +# Підключення WSGI - Flask, Django та інші { #including-wsgi-flask-django-others } + +Ви можете монтувати застосунки WSGI, як ви бачили в [Підзастосунки - монтування](sub-applications.md){.internal-link target=_blank}, [За представником](behind-a-proxy.md){.internal-link target=_blank}. + +Для цього ви можете використати `WSGIMiddleware` і обгорнути ним ваш застосунок WSGI, наприклад Flask, Django тощо. + +## Використання `WSGIMiddleware` { #using-wsgimiddleware } + +/// info | Інформація + +Для цього потрібно встановити `a2wsgi`, наприклад за допомогою `pip install a2wsgi`. + +/// + +Потрібно імпортувати `WSGIMiddleware` з `a2wsgi`. + +Потім обгорніть застосунок WSGI (напр., Flask) цим проміжним програмним забезпеченням. + +І змонтуйте його під певним шляхом. + +{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *} + +/// note | Примітка + +Раніше рекомендувалося використовувати `WSGIMiddleware` з `fastapi.middleware.wsgi`, але тепер його визнано застарілим. + +Замість цього радимо використовувати пакет `a2wsgi`. Використання залишається таким самим. + +Просто переконайтеся, що у вас встановлено пакет `a2wsgi`, і імпортуйте `WSGIMiddleware` коректно з `a2wsgi`. + +/// + +## Перевірте { #check-it } + +Тепер кожен запит за шляхом `/v1/` оброблятиметься застосунком Flask. + +А решта - **FastAPI**. + +Якщо ви запустите це й перейдете на http://localhost:8000/v1/, ви побачите відповідь від Flask: + +```txt +Hello, World from Flask! +``` + +А якщо ви перейдете на http://localhost:8000/v2, ви побачите відповідь від FastAPI: + +```JSON +{ + "message": "Hello World" +} +``` diff --git a/docs/uk/docs/async.md b/docs/uk/docs/async.md new file mode 100644 index 000000000..baf472054 --- /dev/null +++ b/docs/uk/docs/async.md @@ -0,0 +1,444 @@ +# Рівночасність і async / await { #concurrency-and-async-await } + +Деталі щодо синтаксису `async def` для функцій операції шляху і деякі відомості про асинхронний код, рівночасність і паралелізм. + +## Поспішаєте? { #in-a-hurry } + +TL;DR: + +Якщо ви використовуєте сторонні бібліотеки, які вимагають виклику з `await`, наприклад: + +```Python +results = await some_library() +``` + +Тоді оголошуйте ваші функції операції шляху з `async def`, наприклад: + +```Python hl_lines="2" +@app.get('/') +async def read_results(): + results = await some_library() + return results +``` + +/// note | Примітка + +Ви можете використовувати `await` лише всередині функцій, створених з `async def`. + +/// + +--- + +Якщо ви використовуєте сторонню бібліотеку, яка взаємодіє з чимось (база даних, API, файлова система тощо) і не підтримує використання `await` (наразі це стосується більшості бібліотек баз даних), тоді оголошуйте ваші функції операції шляху як зазвичай, просто з `def`, наприклад: + +```Python hl_lines="2" +@app.get('/') +def results(): + results = some_library() + return results +``` + +--- + +Якщо ваш застосунок (якимось чином) не має комунікувати з чимось іншим і чекати на відповідь, використовуйте `async def`, навіть якщо вам не потрібно використовувати `await` всередині. + +--- + +Якщо ви не певні, використовуйте звичайний `def`. + +--- + +Примітка: ви можете змішувати `def` і `async def` у ваших функціях операції шляху скільки завгодно і визначати кожну з них найкращим для вас способом. FastAPI зробить з ними все правильно. + +У будь-якому з наведених випадків FastAPI все одно працюватиме асинхронно і буде надзвичайно швидким. + +Але слідуючи крокам вище, він зможе зробити деякі оптимізації продуктивності. + +## Технічні деталі { #technical-details } + +Сучасні версії Python мають підтримку «асинхронного коду» за допомогою так званих «співпрограм» з синтаксисом **`async` і `await`**. + +Розгляньмо цю фразу по частинах у секціях нижче: + +- Асинхронний код +- `async` і `await` +- Співпрограми + +## Асинхронний код { #asynchronous-code } + +Асинхронний код означає, що мова 💬 має спосіб сказати комп’ютеру/програмі 🤖, що в певний момент у коді він 🤖 має почекати, поки «щось інше» завершиться десь ще. Скажімо, це «щось інше» називається «slow-file» 📝. + +Отже, в цей час комп’ютер може піти і зробити іншу роботу, доки «slow-file» 📝 завершується. + +Далі комп’ютер/програма 🤖 повертатиметься щоразу, коли матиме можливість, бо знову чекає, або коли він 🤖 завершив усю роботу, яка була в нього на той момент. І він 🤖 перевірить, чи якась із задач, на які він чекав, уже завершилася, виконавши все, що потрібно. + +Потім він 🤖 бере першу завершену задачу (скажімо, наш «slow-file» 📝) і продовжує робити те, що потрібно було зробити з нею. + +Це «чекати на щось інше» зазвичай стосується операцій I/O, які відносно «повільні» (порівняно зі швидкістю процесора та пам’яті з довільним доступом), наприклад, очікування: + +- даних від клієнта, що надсилаються мережею +- даних, надісланих вашим застосунком, які клієнт має отримати мережею +- вмісту файла на диску, який система має прочитати і передати вашому застосунку +- вмісту, який ваш застосунок передав системі, щоб він був записаний на диск +- віддаленої операції API +- завершення операції бази даних +- повернення результатів запиту до бази даних +- тощо + +Оскільки час виконання переважно витрачається на очікування операцій I/O, їх називають операціями «I/O bound». + +Це називається «асинхронним», тому що комп’ютеру/програмі не потрібно бути «синхронізованими» з повільною задачею, очікуючи точного моменту її завершення, нічого не роблячи, лише щоб отримати результат задачі та продовжити роботу. + +Натомість, у «асинхронній» системі щойно завершена задача може трохи зачекати в черзі (кілька мікросекунд), доки комп’ютер/програма завершить те, що пішов робити, а потім повернеться, щоб забрати результати і продовжити роботу з ними. + +Для «синхронного» (на противагу «асинхронному») часто також використовують термін «послідовний», бо комп’ютер/програма слідує всім крокам послідовно, перш ніж перемкнутися на іншу задачу, навіть якщо ці кроки включають очікування. + +### Рівночасність і бургери { #concurrency-and-burgers } + +Ідею **асинхронного** коду, описану вище, інколи також називають **«рівночасністю»**. Вона відрізняється від **«паралелізму»**. + +І рівночасність, і паралелізм стосуються «різних речей, що відбуваються більш-менш одночасно». + +Але деталі між рівночасністю і паралелізмом досить різні. + +Щоб побачити різницю, уявімо таку історію про бургери: + +### Рівночасні бургери { #concurrent-burgers } + +Ви йдете зі своєю симпатією по фастфуд, стаєте в чергу, доки касир приймає замовлення у людей перед вами. 😍 + + + +Потім ваша черга, ви замовляєте 2 дуже вишукані бургери для вашої симпатії і для себе. 🍔🍔 + + + +Касир каже щось кухарю на кухні, щоб той знав, що треба приготувати ваші бургери (хоча зараз він готує бургери для попередніх клієнтів). + + + +Ви платите. 💸 + +Касир дає вам номер вашої черги. + + + +Поки ви чекаєте, ви з вашою симпатією обираєте столик, сідаєте і довго розмовляєте (адже ваші бургери дуже вишукані і потребують часу на приготування). + +Сидячи за столиком із вашою симпатією, доки чекаєте бургери, ви можете витратити цей час, милуючись тим, яка ваша симпатія класна, мила і розумна ✨😍✨. + + + +Чекаючи і спілкуючись із вашою симпатією, час від часу ви перевіряєте номер на табло біля прилавка, щоб побачити, чи вже ваша черга. + +І от нарешті ваша черга. Ви підходите до прилавка, забираєте бургери і повертаєтеся до столика. + + + +Ви з вашою симпатією їсте бургери і гарно проводите час. ✨ + + + +/// info | Інформація + +Прекрасні ілюстрації від Ketrina Thompson. 🎨 + +/// + +--- + +Уявіть, що в цій історії ви - комп’ютер/програма 🤖. + +Поки ви в черзі, ви просто бездіяльні 😴, чекаєте своєї черги, нічого «продуктивного» не роблячи. Але черга рухається швидко, бо касир лише приймає замовлення (а не готує їх), тож це нормально. + +Коли ж ваша черга, ви виконуєте справді «продуктивну» роботу: переглядаєте меню, вирішуєте, що бажаєте, дізнаєтеся вибір вашої симпатії, платите, перевіряєте, що віддаєте правильну купюру чи картку, що з вас правильно списали кошти, що замовлення містить правильні позиції тощо. + +Але потім, хоча у вас ще немає бургерів, ваша взаємодія з касиром «на паузі» ⏸, бо вам доводиться чекати 🕙, поки бургери будуть готові. + +Втім, відійшовши від прилавка і сівши за столик із номерком, ви можете перемкнути 🔀 увагу на свою симпатію і «попрацювати» ⏯ 🤓 над цим. Тоді ви знову робите щось дуже «продуктивне» - фліртуєте зі своєю симпатією 😍. + +Потім касир 💁 каже «Я закінчив робити бургери», виводячи ваш номер на табло прилавка, але ви не підстрибуєте миттєво, щойно номер змінюється на ваш. Ви знаєте, що ніхто не вкраде ваші бургери, адже у вас є номер вашої черги, а в інших - свій. + +Тож ви чекаєте, поки ваша симпатія завершить історію (завершить поточну роботу ⏯/задачу 🤓), лагідно усміхаєтеся і кажете, що підете за бургерами ⏸. + +Потім ви йдете до прилавка 🔀, до початкової задачі, яку тепер завершено ⏯, забираєте бургери, дякуєте і несете їх до столу. Це завершує той крок/задачу взаємодії з прилавком ⏹. Натомість з’являється нова задача «їсти бургери» 🔀 ⏯, але попередня «отримати бургери» завершена ⏹. + +### Паралельні бургери { #parallel-burgers } + +А тепер уявімо, що це не «рівночасні бургери», а «паралельні бургери». + +Ви йдете зі своєю симпатією по паралельний фастфуд. + +Ви стаєте в чергу, поки кілька (скажімо, 8) касирів, які водночас є кухарями, приймають замовлення у людей перед вами. + +Кожен перед вами чекає, поки його бургери будуть готові, перш ніж відійти від прилавка, тому що кожен з 8 касирів одразу йде і готує бургер, перш ніж приймати наступне замовлення. + + + +Нарешті ваша черга, ви замовляєте 2 дуже вишукані бургери для вашої симпатії і для себе. + +Ви платите 💸. + + + +Касир іде на кухню. + +Ви чекаєте, стоячи перед прилавком 🕙, щоб ніхто інший не забрав ваші бургери раніше, ніж ви, адже номерків черги немає. + + + +Оскільки ви з вашою симпатією зайняті тим, щоб ніхто не став перед вами і не забрав ваші бургери, щойно вони з’являться, ви не можете приділяти увагу своїй симпатії. 😞 + +Це «синхронна» робота, ви «синхронізовані» з касиром/кухарем 👨‍🍳. Вам доводиться чекати 🕙 і бути тут у точний момент, коли касир/кухар 👨‍🍳 завершить бургери і віддасть їх вам, інакше хтось інший може їх забрати. + + + +Потім ваш касир/кухар 👨‍🍳 нарешті повертається з вашими бургерами після довгого очікування 🕙 перед прилавком. + + + +Ви берете бургери і йдете до столика зі своєю симпатією. + +Ви просто їх їсте - і все. ⏹ + + + +Багато розмов чи флірту не було, бо більшість часу пішла на очікування 🕙 перед прилавком. 😞 + +/// info | Інформація + +Прекрасні ілюстрації від Ketrina Thompson. 🎨 + +/// + +--- + +У цьому сценарії паралельних бургерів ви - комп’ютер/програма 🤖 з двома процесорами (ви і ваша симпатія), які обидва чекають 🕙 і приділяють увагу ⏯ «очікуванню біля прилавка» 🕙 тривалий час. + +У закладу фастфуду 8 процесорів (касира/кухаря). У той час як у закладі з рівночасними бургерами могло бути лише 2 (один касир і один кухар). + +Та все одно фінальний досвід не найкращий. 😞 + +--- + +Це була б паралельна історія про бургери. 🍔 + +Для більш «реального» прикладу уявіть банк. + +До недавнього часу більшість банків мали кілька касирів 👨‍💼👨‍💼👨‍💼👨‍💼 і велику чергу 🕙🕙🕙🕙🕙🕙🕙🕙. + +Усі касири робили всю роботу з одним клієнтом за іншим 👨‍💼⏯. + +І вам доводилося 🕙 довго стояти в черзі, інакше ви втратите свою чергу. + +Ви, напевно, не хотіли б брати свою симпатію 😍 із собою у справи до банку 🏦. + +### Висновок про бургери { #burger-conclusion } + +У цьому сценарії «фастфуд із вашою симпатією», оскільки є багато очікування 🕙, значно доцільніше мати рівночасну систему ⏸🔀⏯. + +Так є у більшості вебзастосунків. + +Багато-багато користувачів, але ваш сервер чекає 🕙 на їхнє не надто гарне з’єднання, щоб вони надіслали свої запити. + +А потім знову чекає 🕙 на повернення відповідей. + +Це «очікування» 🕙 вимірюється у мікросекундах, але все ж, у сумі - це багато очікування в підсумку. + +Ось чому дуже логічно використовувати асинхронний ⏸🔀⏯ код для веб API. + +Такий тип асинхронності зробив NodeJS популярним (хоча NodeJS не є паралельним), і це сила Go як мови програмування. + +І такий самий рівень продуктивності ви отримуєте з **FastAPI**. + +А оскільки можна мати паралелізм і асинхронність одночасно, ви отримуєте вищу продуктивність, ніж більшість протестованих фреймворків NodeJS, і на рівні з Go, який є компільованою мовою, ближчою до C (усе завдяки Starlette). + +### Чи краща рівночасність за паралелізм? { #is-concurrency-better-than-parallelism } + +Ні! Це не мораль історії. + +Рівночасність відрізняється від паралелізму. І вона краща у конкретних сценаріях, що містять багато очікування. Через це зазвичай вона значно краща за паралелізм для розробки вебзастосунків. Але не для всього. + +Щоб урівноважити це, уявімо коротку історію: + +> Ви маєте прибрати великий брудний будинок. + +*Так, це вся історія*. + +--- + +Тут немає очікування 🕙 - просто багато роботи, яку треба зробити, у багатьох місцях будинку. + +У вас могли б бути «черги» як у прикладі з бургерами: спочатку вітальня, потім кухня. Але оскільки ви ні на що не чекаєте 🕙, а просто прибираєте, «черги» нічого не змінять. + +Завершення займе той самий час із «чергами» чи без (рівночасність), і ви виконаєте той самий обсяг роботи. + +Але в цьому випадку, якби ви могли привести 8 колишніх касирів/кухарів/тепер прибиральників, і кожен з них (разом із вами) взяв би свою зону будинку для прибирання, ви могли б виконати всю роботу паралельно — з додатковою допомогою — і завершити значно швидше. + +У цьому сценарії кожен з прибиральників (включно з вами) був би процесором, що виконує свою частину роботи. + +І оскільки більшість часу виконання займає реальна робота (а не очікування), а роботу на комп’ютері виконує CPU, ці проблеми називають «CPU bound». + +--- + +Поширені приклади «CPU bound» операцій - це речі, що потребують складної математичної обробки. + +Наприклад: + +- **Обробка аудіо** або **зображень**. +- **Комп’ютерний зір**: зображення складається з мільйонів пікселів, кожен піксель має 3 значення/кольори, обробка зазвичай потребує обчислення чогось над цими пікселями, усіма одночасно. +- **Машинне навчання**: зазвичай потребує великої кількості множень «матриць» і «векторів». Уявіть величезну таблицю з числами і множення всіх їх разом одночасно. +- **Глибоке навчання**: це підгалузь машинного навчання, тож те саме застосовується. Просто тут не одна таблиця чисел для множення, а величезний їх набір, і в багатьох випадках ви використовуєте спеціальний процесор для побудови та/або використання цих моделей. + +### Рівночасність + паралелізм: веб + машинне навчання { #concurrency-parallelism-web-machine-learning } + +З **FastAPI** ви можете скористатися рівночасністю, що дуже поширена у веброзробці (та ж головна принада NodeJS). + +Але ви також можете використати переваги паралелізму і багатопроцесорності (наявність кількох процесів, що працюють паралельно) для навантажень «CPU bound», як у системах машинного навчання. + +Це, плюс простий факт, що Python є основною мовою для **Data Science**, машинного навчання і особливо глибокого навчання, робить FastAPI дуже вдалим вибором для веб API та застосунків Data Science / машинного навчання (серед багатьох інших). + +Щоб побачити, як досягти цього паралелізму у продакшні, див. розділ про [Розгортання](deployment/index.md){.internal-link target=_blank}. + +## `async` і `await` { #async-and-await } + +Сучасні версії Python мають дуже інтуїтивний спосіб визначення асинхронного коду. Це робить його схожим на звичайний «послідовний» код і виконує «очікування» за вас у відповідні моменти. + +Коли є операція, яка вимагатиме очікування перед поверненням результатів і має підтримку цих нових можливостей Python, ви можете написати її так: + +```Python +burgers = await get_burgers(2) +``` + +Ключ тут - `await`. Він каже Python, що потрібно почекати ⏸, поки `get_burgers(2)` завершить свою роботу 🕙, перш ніж зберегти результати в `burgers`. Завдяки цьому Python знатиме, що може піти і зробити щось інше 🔀 ⏯ тим часом (наприклад, прийняти інший запит). + +Щоб `await` працював, він має бути всередині функції, що підтримує цю асинхронність. Для цього просто оголосіть її як `async def`: + +```Python hl_lines="1" +async def get_burgers(number: int): + # Виконайте деякі асинхронні дії, щоб створити бургери + return burgers +``` + +...замість `def`: + +```Python hl_lines="2" +# Це не асинхронно +def get_sequential_burgers(number: int): + # Виконайте деякі послідовні дії, щоб створити бургери + return burgers +``` + +З `async def` Python знає, що всередині цієї функції він має відслідковувати вирази `await`, і що він може «ставити на паузу» ⏸ виконання цієї функції і йти робити щось інше 🔀, перш ніж повернутися. + +Коли ви хочете викликати функцію, визначену з `async def`, ви маєте «очікувати» її. Тож це не спрацює: + +```Python +# Це не спрацює, тому що get_burgers визначено як: async def +burgers = get_burgers(2) +``` + +--- + +Отже, якщо ви використовуєте бібліотеку, яку можна викликати з `await`, вам потрібно створити функцію операції шляху, що її використовує, з `async def`, як тут: + +```Python hl_lines="2-3" +@app.get('/burgers') +async def read_burgers(): + burgers = await get_burgers(2) + return burgers +``` + +### Більше технічних деталей { #more-technical-details } + +Ви могли помітити, що `await` можна використовувати лише всередині функцій, визначених з `async def`. + +А водночас функції, визначені з `async def`, потрібно «очікувати». Тож функції з `async def` також можна викликати лише всередині функцій, визначених з `async def`. + +Тож як же викликати першу `async`-функцію - курка чи яйце? + +Якщо ви працюєте з **FastAPI**, вам не потрібно про це турбуватися, адже цією «першою» функцією буде ваша функція операції шляху, і FastAPI знатиме, як учинити правильно. + +Але якщо ви хочете використовувати `async` / `await` без FastAPI, ви також можете це зробити. + +### Пишемо свій власний async-код { #write-your-own-async-code } + +Starlette (і **FastAPI**) базуються на AnyIO, що робить їх сумісними як зі стандартною бібліотекою Python asyncio, так і з Trio. + +Зокрема, ви можете безпосередньо використовувати AnyIO для ваших просунутих сценаріїв рівночасності, що потребують складніших патернів у вашому коді. + +І навіть якщо ви не використовували FastAPI, ви могли б писати свої власні async-застосунки з AnyIO, щоб мати високу сумісність і отримати його переваги (наприклад, *структурована рівночасність*). + +Я створив іншу бібліотеку поверх AnyIO, як тонкий шар, щоб дещо покращити анотації типів і отримати кращу **автодопомогу** (autocompletion), **вбудовані помилки** (inline errors) тощо. Вона також має дружній вступ і навчальний посібник, щоб допомогти вам **зрозуміти** і написати **власний async-код**: Asyncer. Вона буде особливо корисною, якщо вам потрібно **поєднувати async-код зі звичайним** (блокуючим/синхронним) кодом. + +### Інші форми асинхронного коду { #other-forms-of-asynchronous-code } + +Такий стиль використання `async` і `await` відносно новий у мові. + +Але він значно полегшує роботу з асинхронним кодом. + +Такий самий (або майже ідентичний) синтаксис нещодавно з’явився в сучасних версіях JavaScript (у Browser і NodeJS). + +До цього робота з асинхронним кодом була значно складнішою. + +У попередніх версіях Python ви могли використовувати потоки (threads) або Gevent. Але код набагато складніший для розуміння, налагодження і мислення про нього. + +У попередніх версіях NodeJS/Browser JavaScript ви б використовували «callbacks», що призводить до «callback hell». + +## Співпрограми { #coroutines } + +**Співпрограма** - це просто дуже вишукана назва для об’єкта, який повертає функція `async def`. Python знає, що це щось на кшталт функції, яку можна запустити і яка завершиться в певний момент, але яку також можна поставити на паузу ⏸ всередині, коли є `await`. + +Але всю цю функціональність використання асинхронного коду з `async` і `await` часто підсумовують як використання «співпрограм». Це порівняно з головною ключовою особливістю Go - «Goroutines». + +## Висновок { #conclusion } + +Погляньмо на ту саму фразу ще раз: + +> Сучасні версії Python мають підтримку «асинхронного коду» за допомогою так званих «співпрограм», з синтаксисом **`async` і `await`**. + +Тепер це має більше сенсу. ✨ + +Усе це приводить у дію FastAPI (через Starlette) і дає йому таку вражаючу продуктивність. + +## Дуже технічні деталі { #very-technical-details } + +/// warning | Попередження + +Ймовірно, ви можете пропустити це. + +Це дуже технічні деталі про те, як **FastAPI** працює «під капотом». + +Якщо у вас є чимало технічних знань (співпрограми, потоки, блокування тощо) і вам цікаво, як FastAPI обробляє `async def` проти звичайного `def`, - вперед. + +/// + +### Функції операції шляху { #path-operation-functions } + +Коли ви оголошуєте функцію операції шляху зі звичайним `def` замість `async def`, вона виконується у зовнішньому пулі потоків (threadpool), який потім «очікується», замість прямого виклику (оскільки прямий виклик блокував би сервер). + +Якщо ви прийшли з іншого async-фреймворку, який не працює так, як описано вище, і звикли визначати тривіальні, лише обчислювальні функції операції шляху зі звичайним `def` заради крихітного виграшу у продуктивності (близько 100 наносекунд), зверніть увагу, що у **FastAPI** ефект буде протилежним. У таких випадках краще використовувати `async def`, якщо тільки ваші функції операції шляху не використовують код, що виконує блокуюче I/O. + +Втім, у будь-якій ситуації є велика ймовірність, що **FastAPI** [все одно буде швидшим](index.md#performance){.internal-link target=_blank} (або принаймні порівнянним) за ваш попередній фреймворк. + +### Залежності { #dependencies } + +Те саме стосується і [залежностей](tutorial/dependencies/index.md){.internal-link target=_blank}. Якщо залежність є стандартною функцією `def` замість `async def`, вона виконується у зовнішньому пулі потоків. + +### Підзалежності { #sub-dependencies } + +Ви можете мати кілька залежностей і [підзалежностей](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}, які вимагають одна одну (як параметри визначень функцій). Деякі з них можуть бути створені з `async def`, а деякі - зі звичайним `def`. Все працюватиме, і ті, що створені зі звичайним `def`, будуть викликані у зовнішньому потоці (з пулу потоків), а не «очікувані». + +### Інші допоміжні функції { #other-utility-functions } + +Будь-яка інша допоміжна функція, яку ви викликаєте безпосередньо, може бути створена зі звичайним `def` або `async def`, і FastAPI не впливатиме на спосіб її виклику. + +Це відрізняється від функцій, які FastAPI викликає за вас: функції операції шляху і залежності. + +Якщо ваша допоміжна функція є звичайною функцією з `def`, її буде викликано безпосередньо (як ви написали у своєму коді), не в пулі потоків; якщо функція створена з `async def`, тоді вам слід використовувати `await` при її виклику у вашому коді. + +--- + +Знову ж таки, це дуже технічні деталі, які, ймовірно, стануть у пригоді, якщо ви спеціально їх шукали. + +Інакше вам вистачить настанов із розділу вище: Поспішаєте?. diff --git a/docs/uk/docs/benchmarks.md b/docs/uk/docs/benchmarks.md new file mode 100644 index 000000000..d53b7ee98 --- /dev/null +++ b/docs/uk/docs/benchmarks.md @@ -0,0 +1,34 @@ +# Бенчмарки { #benchmarks } + +Незалежні бенчмарки TechEmpower показують, що застосунки FastAPI, запущені під керуванням Uvicorn, є одним із найшвидших доступних фреймворків Python, поступаючись лише самим Starlette і Uvicorn (використовуються FastAPI внутрішньо). + +Але переглядаючи бенчмарки та порівняння, майте на увазі таке. + +## Бенчмарки та швидкість { #benchmarks-and-speed } + +Під час перегляду бенчмарків часто порівнюють кілька інструментів різних типів як рівноцінні. + +Зокрема, разом порівнюють Uvicorn, Starlette і FastAPI (серед багатьох інших інструментів). + +Чим простіше завдання, яке розв'язує інструмент, тим кращою буде продуктивність. І більшість бенчмарків не перевіряють додаткові можливості, що надає інструмент. + +Ієрархія приблизно така: + +* Uvicorn: сервер ASGI + * Starlette: (використовує Uvicorn) веб-мікрофреймворк + * FastAPI: (використовує Starlette) мікрофреймворк для API з низкою додаткових можливостей для створення API, з валідацією даних тощо. + +* Uvicorn: + * Матиме найвищу продуктивність, адже майже не містить додаткового коду окрім власне сервера. + * Ви не писатимете застосунок безпосередньо на Uvicorn. Це означало б, що ваш код мав би включати принаймні приблизно весь код, який надає Starlette (або FastAPI). І якщо зробити так, ваш кінцевий застосунок матиме ті самі накладні витрати, що й під час використання фреймворку, який мінімізує код застосунку та помилки. + * Якщо ви порівнюєте Uvicorn, порівнюйте його з Daphne, Hypercorn, uWSGI тощо. Сервери застосунків. +* Starlette: + * Матиме наступну за швидкістю продуктивність після Uvicorn. Насправді Starlette використовує Uvicorn для запуску. Тож вона може бути «повільнішою» за Uvicorn лише через необхідність виконувати більше коду. + * Але надає інструменти для створення простих веб-застосунків із маршрутизацією на основі шляхів тощо. + * Якщо ви порівнюєте Starlette, порівнюйте її з Sanic, Flask, Django тощо. Веб-фреймворки (або мікрофреймворки). +* FastAPI: + * Аналогічно до того, як Starlette використовує Uvicorn і не може бути швидшою за нього, FastAPI використовує Starlette, тож не може бути швидшою за неї. + * FastAPI надає більше можливостей поверх Starlette. Можливості, які майже завжди потрібні під час створення API, як-от валідація та серіалізація даних. І, використовуючи його, ви безкоштовно отримуєте автоматичну документацію (автоматична документація навіть не додає накладних витрат під час роботи застосунку - вона генерується під час запуску). + * Якби ви не використовували FastAPI і застосували Starlette безпосередньо (або інший інструмент, наприклад Sanic, Flask, Responder тощо), вам довелося б самостійно реалізувати всю валідацію та серіалізацію даних. Тож ваш кінцевий застосунок усе одно мав би ті самі накладні витрати, ніби він був створений із використанням FastAPI. І в багатьох випадках саме ця валідація та серіалізація даних становить найбільший обсяг коду в застосунках. + * Отже, використовуючи FastAPI, ви заощаджуєте час розробки, зменшуєте кількість помилок і рядків коду та, ймовірно, отримуєте таку саму (або кращу) продуктивність, як і без нього (адже інакше вам довелося б реалізувати все це у власному коді). + * Якщо ви порівнюєте FastAPI, порівнюйте його з веб-фреймворком (або набором інструментів), який надає валідацію даних, серіалізацію та документацію, наприклад Flask-apispec, NestJS, Molten тощо. Фреймворки з вбудованою автоматичною валідацією даних, серіалізацією та документацією. diff --git a/docs/uk/docs/deployment/cloud.md b/docs/uk/docs/deployment/cloud.md new file mode 100644 index 000000000..a17aaf259 --- /dev/null +++ b/docs/uk/docs/deployment/cloud.md @@ -0,0 +1,24 @@ +# Розгортання FastAPI у хмарних постачальників { #deploy-fastapi-on-cloud-providers } + +Ви можете використовувати практично **будь-якого хмарного постачальника**, щоб розгорнути свій застосунок FastAPI. + +У більшості випадків основні хмарні постачальники мають інструкції з розгортання FastAPI у них. + +## FastAPI Cloud { #fastapi-cloud } + +**FastAPI Cloud** створено тим самим автором і командою, що стоять за **FastAPI**. + +Воно спрощує процес **створення**, **розгортання** та **доступу** до API з мінімальними зусиллями. + +Воно переносить той самий **досвід розробника** зі створення застосунків із FastAPI на їх **розгортання** у хмарі. 🎉 + +FastAPI Cloud є основним спонсором і джерелом фінансування проєктів з відкритим кодом *FastAPI and friends*. ✨ + +## Хмарні постачальники - спонсори { #cloud-providers-sponsors } + +Деякі інші хмарні постачальники ✨ [**спонсорують FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ також. 🙇 + +Можливо, ви захочете розглянути їх, щоб дотримуватися їхніх інструкцій і спробувати їхні сервіси: + +* Render +* Railway diff --git a/docs/uk/docs/deployment/concepts.md b/docs/uk/docs/deployment/concepts.md new file mode 100644 index 000000000..07ad31440 --- /dev/null +++ b/docs/uk/docs/deployment/concepts.md @@ -0,0 +1,321 @@ +# Концепції розгортання { #deployments-concepts } + +Під час розгортання застосунку **FastAPI** (або будь-якого веб-API) є кілька концепцій, які, ймовірно, вас цікавлять, і, спираючись на них, ви зможете знайти **найвідповідніший** спосіб **розгорнути ваш застосунок**. + +Деякі важливі концепції: + +- Безпека - HTTPS +- Запуск під час старту +- Перезапуски +- Реплікація (кількість запущених процесів) +- Пам'ять +- Попередні кроки перед стартом + +Подивимось, як вони впливають на **розгортання**. + +Зрештою головна мета - **обслуговувати клієнтів вашого API** так, щоб це було **безпечним**, з **мінімумом перерв у роботі**, і щоб **обчислювальні ресурси** (наприклад, віддалені сервери/віртуальні машини) використовувалися якомога ефективніше. 🚀 + +Нижче я трохи більше розповім про ці **концепції**, і, сподіваюся, це дасть вам потрібну **інтуїцію**, щоб вирішувати, як розгортати ваш API в дуже різних середовищах, можливо, навіть у **майбутніх**, яких ще не існує. + +Враховуючи ці концепції, ви зможете **оцінювати та проєктувати** найкращий спосіб розгортання **ваших власних API**. + +У наступних розділах я наведу більш **конкретні рецепти** розгортання застосунків FastAPI. + +А поки перегляньмо ці важливі **концептуальні ідеї**. Вони також застосовні до будь-якого іншого типу веб-API. 💡 + +## Безпека - HTTPS { #security-https } + +У [попередньому розділі про HTTPS](https.md){.internal-link target=_blank} ми дізналися, як HTTPS забезпечує шифрування для вашого API. + +Ми також бачили, що HTTPS зазвичай надається компонентом, **зовнішнім** щодо вашого серверного застосунку, - **TLS Termination Proxy**. + +І має бути щось, що відповідає за **оновлення сертифікатів HTTPS** - це може бути той самий компонент або інший. + +### Приклади інструментів для HTTPS { #example-tools-for-https } + +Деякі інструменти, які можна використовувати як TLS Termination Proxy: + +- Traefik + - Автоматично обробляє оновлення сертифікатів ✨ +- Caddy + - Автоматично обробляє оновлення сертифікатів ✨ +- Nginx + - З зовнішнім компонентом на кшталт Certbot для оновлення сертифікатів +- HAProxy + - З зовнішнім компонентом на кшталт Certbot для оновлення сертифікатів +- Kubernetes з Ingress Controller, наприклад Nginx + - З зовнішнім компонентом на кшталт cert-manager для оновлення сертифікатів +- Обробляється внутрішньо хмарним провайдером як частина їхніх сервісів (див. нижче 👇) + +Ще один варіант - використати **хмарний сервіс**, який зробить більше роботи, зокрема налаштує HTTPS. Можуть бути обмеження або додаткова вартість тощо. Але у такому разі вам не потрібно самостійно налаштовувати TLS Termination Proxy. + +У наступних розділах я покажу кілька конкретних прикладів. + +--- + +Далі всі наступні концепції стосуються програми, яка запускає ваш фактичний API (наприклад, Uvicorn). + +## Програма і процес { #program-and-process } + +Ми багато говоритимемо про запущений «процес», тож корисно чітко розуміти, що це означає, і чим відрізняється від слова «програма». + +### Що таке програма { #what-is-a-program } + +Слово **програма** зазвичай вживають для опису багатьох речей: + +- **Код**, який ви пишете, **файли Python**. +- **Файл**, який може бути **виконаний** операційною системою, наприклад: `python`, `python.exe` або `uvicorn`. +- Конкретна програма під час **виконання** в операційній системі, що використовує CPU та зберігає дані в пам'яті. Це також називають **процесом**. + +### Що таке процес { #what-is-a-process } + +Слово **процес** зазвичай використовують у більш специфічному значенні, маючи на увазі саме те, що виконується в операційній системі (як у попередньому пункті): + +- Конкретна програма під час **виконання** в операційній системі. + - Це не про файл і не про код, це **конкретно** про те, що **виконується** та керується операційною системою. +- Будь-яка програма, будь-який код **може щось робити** лише під час **виконання**. Тобто коли **процес запущений**. +- Процес може бути **завершений** (або «kill») вами чи операційною системою. У цей момент він припиняє виконання і **більше нічого не може робити**. +- Кожен застосунок, який працює на вашому комп'ютері, має певний процес за собою: кожна запущена програма, кожне вікно тощо. Зазвичай на комп'ютері одночасно працює **багато процесів**. +- **Кілька процесів** **однієї й тієї самої програми** можуть працювати одночасно. + +Якщо ви відкриєте «диспетчер завдань» або «системний монітор» (чи подібні інструменти) в операційній системі, ви побачите багато таких процесів. + +Наприклад, ви, ймовірно, побачите кілька процесів того самого браузера (Firefox, Chrome, Edge тощо). Зазвичай він запускає один процес на вкладку плюс деякі додаткові процеси. + + + +--- + +Тепер, коли ми знаємо різницю між термінами **процес** і **програма**, продовжимо говорити про розгортання. + +## Запуск під час старту { #running-on-startup } + +У більшості випадків, коли ви створюєте веб-API, ви хочете, щоб він **працював постійно**, без перерв, щоб клієнти завжди мали до нього доступ. Звісно, якщо немає особливих причин запускати його лише в певних ситуаціях. Але зазвичай ви хочете, щоб він постійно працював і був **доступний**. + +### На віддаленому сервері { #in-a-remote-server } + +Коли ви налаштовуєте віддалений сервер (хмарний сервер, віртуальну машину тощо), найпростіше - використовувати `fastapi run` (який використовує Uvicorn) або щось схоже, вручну, так само, як під час локальної розробки. + +І це працюватиме та буде корисним **під час розробки**. + +Але якщо ви втратите з'єднання з сервером, **запущений процес**, найімовірніше, завершиться. + +І якщо сервер буде перезавантажено (наприклад, після оновлень або міграцій у хмарного провайдера), ви, ймовірно, **не помітите цього**. І через це ви навіть не знатимете, що треба вручну перезапустити процес. У результаті ваш API просто залишиться «мертвим». 😱 + +### Автоматичний запуск під час старту { #run-automatically-on-startup } + +Загалом ви, напевно, захочете, щоб серверна програма (наприклад, Uvicorn) запускалася автоматично під час старту сервера і без будь-якого **людського втручання**, щоб завжди був запущений процес із вашим API (наприклад, Uvicorn із вашим FastAPI-застосунком). + +### Окрема програма { #separate-program } + +Щоб цього досягти, зазвичай використовують **окрему програму**, яка гарантує запуск вашого застосунку під час старту. І в багатьох випадках вона також забезпечує запуск інших компонентів або застосунків, наприклад бази даних. + +### Приклади інструментів для запуску під час старту { #example-tools-to-run-at-startup } + +Приклади інструментів, які можуть це робити: + +- Docker +- Kubernetes +- Docker Compose +- Docker у режимі Swarm +- Systemd +- Supervisor +- Обробляється внутрішньо хмарним провайдером як частина їхніх сервісів +- Інші... + +У наступних розділах я наведу більш конкретні приклади. + +## Перезапуски { #restarts } + +Подібно до забезпечення запуску застосунку під час старту системи, ви, ймовірно, також захочете гарантувати його **перезапуск** після збоїв. + +### Ми помиляємося { #we-make-mistakes } + +Ми, люди, постійно робимо **помилки**. Майже завжди у програмному забезпеченні є приховані **помилки**. 🐛 + +І ми як розробники постійно покращуємо код, знаходячи ці помилки та додаючи нові можливості (можливо, теж додаючи нові помилки 😅). + +### Невеликі помилки обробляються автоматично { #small-errors-automatically-handled } + +Створюючи веб-API з FastAPI, якщо в нашому коді є помилка, FastAPI зазвичай обмежує її одним запитом, який цю помилку спровокував. 🛡 + +Клієнт отримає **500 Internal Server Error** для цього запиту, але застосунок продовжить працювати для наступних запитів замість повного краху. + +### Великі помилки - крахи { #bigger-errors-crashes } + +Втім, бувають випадки, коли ми пишемо код, який **падає весь застосунок**, спричиняючи крах Uvicorn і Python. 💥 + +І все ж ви, ймовірно, не захочете, щоб застосунок залишався «мертвим» через помилку в одному місці - ви, напевно, хочете, щоб він **продовжував працювати** принаймні для тих *операцій шляху*, що не зламані. + +### Перезапуск після краху { #restart-after-crash } + +Але в таких випадках із серйозними помилками, що призводять до краху запущеного **процесу**, потрібен зовнішній компонент, відповідальний за **перезапуск** процесу, принаймні кілька разів... + +/// tip | Порада + +...Хоча якщо весь застосунок просто **миттєво падає**, безглуздо перезапускати його безкінечно. Але в таких випадках ви, ймовірно, помітите це під час розробки або принаймні відразу після розгортання. + +Тож зосередьмося на основних випадках, коли у майбутньому він може повністю падати за певних обставин, і тоді все ще має сенс його перезапускати. + +/// + +Ймовірно, ви захочете мати відокремлений **зовнішній компонент**, який відповідає за перезапуск застосунку, адже до того моменту сам застосунок з Uvicorn і Python уже впав, і нічого в тому ж коді цієї ж програми зробити не зможе. + +### Приклади інструментів для автоматичного перезапуску { #example-tools-to-restart-automatically } + +У більшості випадків той самий інструмент, який використовується для **запуску програми під час старту**, також використовується для автоматичних **перезапусків**. + +Наприклад, це можуть забезпечувати: + +- Docker +- Kubernetes +- Docker Compose +- Docker у режимі Swarm +- Systemd +- Supervisor +- Обробляється внутрішньо хмарним провайдером як частина їхніх сервісів +- Інші... + +## Реплікація - процеси та пам'ять { #replication-processes-and-memory } + +У застосунку FastAPI, використовуючи серверну програму, як-от команду `fastapi`, що запускає Uvicorn, один запуск в **одному процесі** може обслуговувати кількох клієнтів рівночасно. + +Але часто ви захочете запускати кілька процесів-працівників одночасно. + +### Кілька процесів - працівники { #multiple-processes-workers } + +Якщо у вас більше клієнтів, ніж може обробити один процес (наприклад, якщо віртуальна машина не надто потужна) і на сервері є **кілька ядер** CPU, тоді ви можете запустити **кілька процесів** із тим самим застосунком паралельно і розподіляти запити між ними. + +Коли ви запускаєте **кілька процесів** того самого програмного забезпечення API, їх зазвичай називають **працівниками** (workers). + +### Процеси-працівники і порти { #worker-processes-and-ports } + +Пам'ятаєте з документації [Про HTTPS](https.md){.internal-link target=_blank}, що на сервері лише один процес може слухати певну комбінацію порту та IP-адреси? + +Це досі так. + +Отже, щоб мати **кілька процесів** одночасно, має бути **єдиний процес, який слухає порт**, і який далі якимось чином передає комунікацію кожному процесу-працівнику. + +### Пам'ять на процес { #memory-per-process } + +Коли програма завантажує щось у пам'ять, наприклад модель машинного навчання в змінну або вміст великого файлу в змінну, все це **споживає частину пам'яті (RAM)** сервера. + +І кілька процесів зазвичай **не діляться пам'яттю**. Це означає, що кожен запущений процес має власні речі, змінні та пам'ять. І якщо у вашому коді споживається багато пам'яті, **кожен процес** споживатиме еквівалентний обсяг пам'яті. + +### Пам'ять сервера { #server-memory } + +Наприклад, якщо ваш код завантажує модель машинного навчання розміром **1 GB**, то при запуску одного процесу з вашим API він споживатиме щонайменше 1 GB RAM. А якщо ви запустите **4 процеси** (4 працівники) - кожен споживатиме 1 GB RAM. Отже, загалом ваш API споживатиме **4 GB RAM**. + +І якщо ваш віддалений сервер або віртуальна машина має лише 3 GB RAM, спроба використати понад 4 GB призведе до проблем. 🚨 + +### Кілька процесів - приклад { #multiple-processes-an-example } + +У цьому прикладі є **керівний процес** (Manager Process), який запускає і контролює два **процеси-працівники**. + +Цей керівний процес, імовірно, саме і слухатиме **порт** на IP. І він передаватиме всю комунікацію процесам-працівникам. + +Ці процеси-працівники виконуватимуть ваш застосунок, здійснюватимуть основні обчислення, щоб отримати **запит** і повернути **відповідь**, і завантажуватимуть усе, що ви зберігаєте в змінних у RAM. + + + +Звісно, на тій самій машині, окрім вашого застосунку, зазвичай працюватимуть **інші процеси**. + +Цікавий момент: відсоток **використання CPU** кожним процесом може сильно **варіюватися** з часом, тоді як **пам'ять (RAM)** зазвичай залишається більш-менш **стабільною**. + +Якщо у вас API, що виконує порівняний обсяг обчислень щоразу і у вас багато клієнтів, тоді **використання CPU** також, ймовірно, буде *стабільним* (замість постійних швидких коливань). + +### Приклади інструментів і стратегій реплікації { #examples-of-replication-tools-and-strategies } + +Є кілька підходів для цього, і про конкретні стратегії я розповім у наступних розділах, наприклад, коли говоритимемо про Docker і контейнери. + +Головне обмеження: має бути **єдиний** компонент, що обробляє **порт** на **публічній IP-адресі**. А тоді він має мати спосіб **передавати** комунікацію реплікованим **процесам/працівникам**. + +Ось кілька можливих комбінацій і стратегій: + +- **Uvicorn** з `--workers` + - Один **менеджер процесів** Uvicorn слухатиме **IP** і **порт** та запускатиме **кілька процесів-працівників Uvicorn**. +- **Kubernetes** та інші розподілені **системи контейнерів** + - Щось на рівні **Kubernetes** слухатиме **IP** і **порт**. Реплікація відбуватиметься через **кілька контейнерів**, у кожному з яких запущено **один процес Uvicorn**. +- **Хмарні сервіси**, що роблять це за вас + - Хмарний сервіс, ймовірно, **забезпечить реплікацію за вас**. Він може дозволяти визначити **процес для запуску** або **образ контейнера** для використання; у будь-якому разі це, найімовірніше, буде **один процес Uvicorn**, а сервіс відповідатиме за його реплікацію. + +/// tip | Порада + +Не хвилюйтеся, якщо деякі пункти про **контейнери**, Docker чи Kubernetes поки що не дуже зрозумілі. + +Я розповім більше про образи контейнерів, Docker, Kubernetes тощо в майбутньому розділі: [FastAPI у контейнерах - Docker](docker.md){.internal-link target=_blank}. + +/// + +## Попередні кроки перед стартом { #previous-steps-before-starting } + +Є багато випадків, коли потрібно виконати деякі кроки **перед стартом** вашого застосунку. + +Наприклад, ви можете захотіти запустити **міграції бази даних**. + +Але найчастіше ці кроки потрібно виконувати лише **один раз**. + +Отже, ви захочете мати **єдиний процес**, який виконає ці **попередні кроки** перед запуском застосунку. + +І потрібно переконатися, що їх виконує саме один процес навіть якщо потім ви запускаєте **кілька процесів** (кілька працівників) для самого застосунку. Якщо ці кроки виконуватимуться **кількома процесами**, вони **подвоюватимуть** роботу, виконуючи її **паралельно**, і якщо кроки делікатні, як-от міграції бази даних, це може призвести до конфліктів. + +Звісно, бувають випадки, коли немає проблеми запускати попередні кроки кілька разів - тоді все набагато простіше. + +/// tip | Порада + +Також майте на увазі, що залежно від вашого налаштування інколи **попередні кроки взагалі не потрібні** перед запуском застосунку. + +У такому разі про це можна не турбуватися. 🤷 + +/// + +### Приклади стратегій попередніх кроків { #examples-of-previous-steps-strategies } + +Це **значною мірою залежить** від способу **розгортання вашої системи** і, ймовірно, буде пов'язано зі способом запуску програм, обробки перезапусків тощо. + +Ось кілька можливих ідей: + +- «Init Container» у Kubernetes, який виконується перед вашим контейнером застосунку +- bash-скрипт, який виконує попередні кроки, а потім запускає ваш застосунок + - Вам усе одно потрібен спосіб запускати/перезапускати цей bash-скрипт, виявляти помилки тощо. + +/// tip | Порада + +Я наведу більш конкретні приклади для цього з контейнерами у майбутньому розділі: [FastAPI у контейнерах - Docker](docker.md){.internal-link target=_blank}. + +/// + +## Використання ресурсів { #resource-utilization } + +Ваш сервер(и) - це **ресурс**, який ви можете споживати/**використовувати** вашими програмами: час обчислень на CPU та доступну RAM. + +Скільки системних ресурсів ви хочете споживати/використовувати? Легко подумати «небагато», але насправді ви, ймовірно, захочете споживати **настільки багато, наскільки можливо без краху**. + +Якщо ви платите за 3 сервери, але використовуєте лише трохи їх RAM і CPU, ви, ймовірно, **марнуєте гроші** 💸 і, можливо, **електроенергію серверів** 🌎 тощо. + +У такому разі може бути краще мати лише 2 сервери й використовувати більший відсоток їхніх ресурсів (CPU, пам'ять, диск, пропускну здатність мережі тощо). + +З іншого боку, якщо у вас 2 сервери і ви використовуєте **100% їхнього CPU та RAM**, у певний момент якийсь процес попросить більше пам'яті, і сервер муситиме використати диск як «пам'ять» (що може бути у тисячі разів повільнішим) або навіть **впасти**. Або процесу знадобляться обчислення, і він чекатиме, доки CPU знову звільниться. + +У цьому випадку краще додати **ще один сервер** і запустити частину процесів на ньому, щоб у всіх було **достатньо RAM та часу CPU**. + +Також можлива ситуація, коли з певної причини ви отримаєте **стрибок** використання вашого API. Можливо, він став вірусним або його почали використовувати інші сервіси чи боти. І ви можете захотіти мати додаткові ресурси на випадок таких ситуацій. + +Ви можете встановити **довільний цільовий** рівень, наприклад, **між 50% і 90%** використання ресурсів. Суть у тому, що це, ймовірно, головні речі, які ви захочете вимірювати й використовувати для налаштування розгортань. + +Ви можете використати прості інструменти, як-от `htop`, щоб побачити використання CPU і RAM на сервері або кількість, спожиту кожним процесом. Або складніші засоби моніторингу, розподілені між серверами тощо. + +## Підсумок { #recap } + +Тут ви прочитали про основні концепції, які, ймовірно, потрібно тримати в голові, вирішуючи, як розгортати ваш застосунок: + +- Безпека - HTTPS +- Запуск під час старту +- Перезапуски +- Реплікація (кількість запущених процесів) +- Пам'ять +- Попередні кроки перед стартом + +Розуміння цих ідей і того, як їх застосовувати, має дати вам інтуїцію, необхідну для прийняття рішень під час конфігурування і тонкого налаштування ваших розгортань. 🤓 + +У наступних розділах я наведу більше конкретних прикладів можливих стратегій, якими ви можете скористатися. 🚀 diff --git a/docs/uk/docs/deployment/docker.md b/docs/uk/docs/deployment/docker.md new file mode 100644 index 000000000..d6faacfe5 --- /dev/null +++ b/docs/uk/docs/deployment/docker.md @@ -0,0 +1,618 @@ +# FastAPI у контейнерах - Docker { #fastapi-in-containers-docker } + +Під час розгортання застосунків FastAPI поширений підхід - збирати образи контейнерів Linux. Зазвичай це робиться за допомогою Docker. Потім ви можете розгорнути цей образ контейнера кількома різними способами. + +Використання контейнерів Linux має кілька переваг, зокрема безпека, відтворюваність, простота та інші. + +/// tip | Порада + +Поспішаєте і вже все це знаєте? Перейдіть до [`Dockerfile` нижче 👇](#build-a-docker-image-for-fastapi). + +/// + +
+Попередній перегляд Dockerfile 👀 + +```Dockerfile +FROM python:3.14 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +CMD ["fastapi", "run", "app/main.py", "--port", "80"] + +# Якщо запускаєте за представником на кшталт Nginx або Traefik, додайте --proxy-headers +# CMD ["fastapi", "run", "app/main.py", "--port", "80", "--proxy-headers"] +``` + +
+ +## Що таке контейнер { #what-is-a-container } + +Контейнери (переважно контейнери Linux) - це дуже легкий спосіб упакувати застосунки з усіма їхніми залежностями та потрібними файлами, ізолювавши їх від інших контейнерів (інших застосунків або компонентів) у тій самій системі. + +Контейнери Linux працюють, використовуючи той самий ядро Linux, що й хост (машина, віртуальна машина, хмарний сервер тощо). Це означає, що вони дуже легкі (у порівнянні з повними віртуальними машинами, які емулюють цілу операційну систему). + +Таким чином контейнери споживають мало ресурсів, приблизно як безпосередньо запущені процеси (віртуальна машина споживала б значно більше). + +У контейнерів також є власні ізольовані процеси виконання (зазвичай лише один процес), файлові системи та мережі, що спрощує розгортання, безпеку, розробку тощо. + +## Що таке образ контейнера { #what-is-a-container-image } + +Контейнер запускається з образу контейнера. + +Образ контейнера - це статична версія всіх файлів, змінних оточення та типова команда/програма, яка має бути присутня в контейнері. Тут «статична» означає, що образ контейнера не запущений, він не виконується, це лише упаковані файли та метадані. + +На противагу «образу контейнера», що є збереженим статичним вмістом, «контейнер» зазвичай означає запущений екземпляр, те, що виконується. + +Коли контейнер запущено (запущений з образу контейнера), він може створювати або змінювати файли, змінні оточення тощо. Ці зміни існуватимуть лише в цьому контейнері, але не збережуться в базовому образі контейнера (не будуть записані на диск). + +Образ контейнера можна порівняти з файлом і вмістом програми, наприклад `python` і файлом `main.py`. + +А сам контейнер (на відміну від образу) - це фактично запущений екземпляр образу, порівнянний із процесом. Насправді контейнер працює лише тоді, коли в ньому працює процес (і зазвичай це один процес). Контейнер зупиняється, коли в ньому не працює жоден процес. + +## Образи контейнерів { #container-images } + +Docker був одним з основних інструментів для створення та керування образами контейнерів і контейнерами. + +Існує публічний Docker Hub з готовими офіційними образами для багатьох інструментів, середовищ, баз даних і застосунків. + +Наприклад, є офіційний образ Python. + +І є багато інших образів для різних речей, як-от бази даних, наприклад для: + +* PostgreSQL +* MySQL +* MongoDB +* Redis тощо. + +Використовуючи готовий образ контейнера, дуже легко поєднувати та використовувати різні інструменти. Наприклад, щоб випробувати нову базу даних. У більшості випадків ви можете використати офіційні образи та просто налаштувати їх змінними оточення. + +Таким чином, у багатьох випадках ви зможете навчитися працювати з контейнерами і Docker та повторно використати ці знання з багатьма різними інструментами і компонентами. + +Тобто ви запускатимете кілька контейнерів з різними речами, як-от базу даних, застосунок на Python, вебсервер із фронтендом на React, і з’єднаєте їх через внутрішню мережу. + +Усі системи керування контейнерами (як Docker чи Kubernetes) мають ці мережеві можливості вбудовано. + +## Контейнери і процеси { #containers-and-processes } + +Образ контейнера зазвичай містить у своїх метаданих типову програму або команду, яку слід виконати під час запуску контейнера, і параметри для цієї програми. Дуже схоже на те, що ви б виконали в командному рядку. + +Коли контейнер запускається, він виконає цю команду/програму (хоча ви можете перевизначити її і запустити іншу команду/програму). + +Контейнер працює доти, доки працює головний процес (команда або програма). + +Зазвичай контейнер має один процес, але також можливо запускати підпроцеси з головного процесу, і таким чином у вас може бути кілька процесів у тому самому контейнері. + +Але неможливо мати запущений контейнер без принаймні одного запущеного процесу. Якщо головний процес зупиняється, контейнер зупиняється. + +## Зібрати Docker-образ для FastAPI { #build-a-docker-image-for-fastapi } + +Гаразд, зберімо щось зараз! 🚀 + +Я покажу вам, як зібрати образ Docker для FastAPI з нуля на основі офіційного образу Python. + +Це те, що ви захочете робити у більшості випадків, наприклад: + +* Використання Kubernetes або подібних інструментів +* Під час запуску на Raspberry Pi +* Використання хмарного сервісу, який запустить для вас образ контейнера тощо + +### Вимоги до пакетів { #package-requirements } + +Зазвичай ви маєте вимоги до пакетів для вашого застосунку в окремому файлі. + +Це залежить переважно від інструменту, який ви використовуєте для встановлення цих вимог. + +Найпоширеніший спосіб - мати файл `requirements.txt` з назвами пакетів і їхніми версіями, по одному на рядок. + +Звісно, ви застосуєте ті самі ідеї з [Про версії FastAPI](versions.md){.internal-link target=_blank}, щоб задати діапазони версій. + +Наприклад, ваш `requirements.txt` може виглядати так: + +``` +fastapi[standard]>=0.113.0,<0.114.0 +pydantic>=2.7.0,<3.0.0 +``` + +І зазвичай ви встановлюватимете ці залежності пакетів через `pip`, наприклад: + +
+ +```console +$ pip install -r requirements.txt +---> 100% +Successfully installed fastapi pydantic +``` + +
+ +/// info | Інформація + +Існують інші формати та інструменти для визначення і встановлення залежностей пакетів. + +/// + +### Створіть код **FastAPI** { #create-the-fastapi-code } + +* Створіть директорію `app` і перейдіть у неї. +* Створіть порожній файл `__init__.py`. +* Створіть файл `main.py` з вмістом: + +```Python +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: str | None = None): + return {"item_id": item_id, "q": q} +``` + +### Dockerfile { #dockerfile } + +Тепер у тій самій директорії проєкту створіть файл `Dockerfile` з вмістом: + +```{ .dockerfile .annotate } +# (1)! +FROM python:3.14 + +# (2)! +WORKDIR /code + +# (3)! +COPY ./requirements.txt /code/requirements.txt + +# (4)! +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (5)! +COPY ./app /code/app + +# (6)! +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +1. Почніть з офіційного базового образу Python. + +2. Встановіть поточну робочу директорію в `/code`. + + Саме сюди ми помістимо файл `requirements.txt` і директорію `app`. + +3. Скопіюйте файл з вимогами в директорію `/code`. + + Спочатку скопіюйте лише файл з вимогами, а не решту коду. + + Оскільки цей файл змінюється нечасто, Docker виявить це і використає кеш для цього кроку, що також увімкне кеш і для наступного кроку. + +4. Встановіть залежності пакетів із файлу вимог. + + Опція `--no-cache-dir` каже `pip` не зберігати завантажені пакети локально, адже це потрібно лише тоді, якщо `pip` буде запущено знову для встановлення тих самих пакетів, але в роботі з контейнерами це не так. + + /// note | Примітка + + `--no-cache-dir` стосується лише `pip`, це не має відношення до Docker чи контейнерів. + + /// + + Опція `--upgrade` каже `pip` оновити пакети, якщо вони вже встановлені. + + Оскільки попередній крок копіювання файлу може бути виявлений кешем Docker, цей крок також використовуватиме кеш Docker, коли це можливо. + + Використання кешу на цьому кроці збереже вам багато часу під час повторних збірок образу в розробці, замість того щоб завжди завантажувати і встановлювати всі залежності. + +5. Скопіюйте директорію `./app` у директорію `/code`. + + Оскільки тут увесь код, який змінюється найчастіше, кеш Docker не буде легко використаний для цього або будь-яких наступних кроків. + + Тому важливо розмістити це ближче до кінця `Dockerfile`, щоб оптимізувати час збірки образу контейнера. + +6. Встановіть команду для використання `fastapi run`, яка всередині використовує Uvicorn. + + `CMD` приймає список строк, кожна з яких - це те, що ви б набирали в командному рядку, розділене пробілами. + + Ця команда буде виконана з поточної робочої директорії, тієї самої `/code`, яку ви вказали вище через `WORKDIR /code`. + +/// tip | Порада + +Перегляньте, що робить кожен рядок, клацнувши на кожну номерну позначку в коді. 👆 + +/// + +/// warning | Попередження + +Обов’язково завжди використовуйте exec form інструкції `CMD`, як пояснено нижче. + +/// + +#### Використовуйте `CMD` - exec form { #use-cmd-exec-form } + +Інструкцію Docker `CMD` можна записати у двох формах: + +✅ Exec form: + +```Dockerfile +# ✅ Робіть так +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +⛔️ Shell form: + +```Dockerfile +# ⛔️ Не робіть так +CMD fastapi run app/main.py --port 80 +``` + +Обов’язково завжди використовуйте exec form, щоб FastAPI міг коректно завершувати роботу та щоб були викликані [події тривалості життя](../advanced/events.md){.internal-link target=_blank}. + +Докладніше про це можна прочитати в документації Docker про shell та exec form. + +Це може бути особливо помітно при використанні `docker compose`. Див. розділ FAQ Docker Compose для технічних деталей: Чому мої сервіси потребують 10 секунд, щоб пересотворитися або зупинитися?. + +#### Структура директорій { #directory-structure } + +Зараз у вас має бути така структура директорій: + +``` +. +├── app +│   ├── __init__.py +│ └── main.py +├── Dockerfile +└── requirements.txt +``` + +#### За представником з термінацією TLS { #behind-a-tls-termination-proxy } + +Якщо ви запускаєте контейнер за представником з термінацією TLS (балансувальником навантаження), наприклад Nginx або Traefik, додайте опцію `--proxy-headers`. Це скаже Uvicorn (через CLI FastAPI) довіряти заголовкам, що надсилаються цим представником, які вказують, що застосунок працює за HTTPS тощо. + +```Dockerfile +CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] +``` + +#### Кеш Docker { #docker-cache } + +У цьому `Dockerfile` є важливий трюк: спочатку ми копіюємо лише файл із залежностями, а не решту коду. Ось чому. + +```Dockerfile +COPY ./requirements.txt /code/requirements.txt +``` + +Docker та інші інструменти збирають ці образи контейнерів інкрементально, додаючи один шар поверх іншого, починаючи з верхньої частини `Dockerfile` і додаючи будь-які файли, створені кожною інструкцією в `Dockerfile`. + +Docker та подібні інструменти також використовують внутрішній кеш під час збірки образу. Якщо файл не змінювався з моменту останньої збірки, тоді він повторно використає той самий шар, створений востаннє, замість копіювання файлу знову та створення нового шару з нуля. + +Просте уникнення копіювання файлів не обов’язково суттєво покращує ситуацію, але оскільки для цього кроку використано кеш, він може використати кеш і для наступного кроку. Наприклад, він може використати кеш для інструкції, яка встановлює залежності: + +```Dockerfile +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt +``` + +Файл із вимогами до пакетів змінюватиметься нечасто. Отже, копіюючи лише цей файл, Docker зможе використати кеш для цього кроку. + +А потім Docker зможе використати кеш і для наступного кроку, який завантажує та встановлює ці залежності. І саме тут ми заощаджуємо багато часу. ✨ ...і уникаємо нудного очікування. 😪😆 + +Завантаження і встановлення залежностей пакетів може займати хвилини, але використання кешу займе максимум секунди. + +І оскільки ви збиратимете образ контейнера знову і знову під час розробки, щоб перевіряти, що зміни у вашому коді працюють, це заощадить багато накопиченого часу. + +Потім, ближче до кінця `Dockerfile`, ми копіюємо весь код. Оскільки це те, що змінюється найчастіше, ми розміщуємо це ближче до кінця, адже майже завжди все після цього кроку не зможе використати кеш. + +```Dockerfile +COPY ./app /code/app +``` + +### Зберіть Docker-образ { #build-the-docker-image } + +Тепер, коли всі файли на місці, зберімо образ контейнера. + +* Перейдіть у директорію проєкту (де ваш `Dockerfile`, який містить директорію `app`). +* Зберіть ваш образ FastAPI: + +
+ +```console +$ docker build -t myimage . + +---> 100% +``` + +
+ +/// tip | Порада + +Зверніть увагу на `.` в кінці. Це еквівалент `./`. Воно каже Docker, яку директорію використовувати для збірки образу контейнера. + +У цьому випадку - це поточна директорія (`.`). + +/// + +### Запустіть Docker-контейнер { #start-the-docker-container } + +* Запустіть контейнер на основі вашого образу: + +
+ +```console +$ docker run -d --name mycontainer -p 80:80 myimage +``` + +
+ +## Перевірте { #check-it } + +Ви маєте змогу перевірити це за URL вашого Docker-контейнера, наприклад: http://192.168.99.100/items/5?q=somequery або http://127.0.0.1/items/5?q=somequery (або еквівалент, використовуючи ваш Docker-хост). + +Ви побачите щось таке: + +```JSON +{"item_id": 5, "q": "somequery"} +``` + +## Інтерактивна документація API { #interactive-api-docs } + +Тепер ви можете перейти на http://192.168.99.100/docs або http://127.0.0.1/docs (або еквівалент, використовуючи ваш Docker-хост). + +Ви побачите автоматичну інтерактивну документацію API (надається Swagger UI): + +![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) + +## Альтернативна документація API { #alternative-api-docs } + +Також ви можете перейти на http://192.168.99.100/redoc або http://127.0.0.1/redoc (або еквівалент, використовуючи ваш Docker-хост). + +Ви побачите альтернативну автоматичну документацію (надається ReDoc): + +![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) + +## Збірка Docker-образу з FastAPI в одному файлі { #build-a-docker-image-with-a-single-file-fastapi } + +Якщо ваш FastAPI - це один файл, наприклад `main.py` без директорії `./app`, структура файлів може виглядати так: + +``` +. +├── Dockerfile +├── main.py +└── requirements.txt +``` + +Тоді вам потрібно лише змінити відповідні шляхи для копіювання файлу всередині `Dockerfile`: + +```{ .dockerfile .annotate hl_lines="10 13" } +FROM python:3.14 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +# (1)! +COPY ./main.py /code/ + +# (2)! +CMD ["fastapi", "run", "main.py", "--port", "80"] +``` + +1. Скопіюйте файл `main.py` безпосередньо у директорію `/code` (без будь-якої директорії `./app`). + +2. Використовуйте `fastapi run`, щоб обслуговувати ваш застосунок з одного файлу `main.py`. + +Коли ви передаєте файл у `fastapi run`, воно автоматично визначить, що це один файл, а не частина пакета, і знатиме, як імпортувати його та запустити ваш застосунок FastAPI. 😎 + +## Концепції розгортання { #deployment-concepts } + +Поговорімо знову про деякі з тих самих [Концепцій розгортання](concepts.md){.internal-link target=_blank} у термінах контейнерів. + +Контейнери - це переважно інструмент для спрощення процесу збирання та розгортання застосунку, але вони не нав’язують конкретний підхід до обробки цих концепцій розгортання, і існує кілька можливих стратегій. + +Гарна новина полягає в тому, що для кожної стратегії є спосіб покрити всі концепції розгортання. 🎉 + +Розгляньмо ці концепції розгортання в контексті контейнерів: + +* HTTPS +* Автозапуск +* Перезапуски +* Реплікація (кількість запущених процесів) +* Пам’ять +* Попередні кроки перед запуском + +## HTTPS { #https } + +Якщо зосередитись лише на образі контейнера для застосунку FastAPI (а згодом на запущеному контейнері), HTTPS зазвичай обробляється зовнішнім іншим інструментом. + +Це може бути інший контейнер, наприклад з Traefik, що обробляє HTTPS і автоматичне отримання сертифікатів. + +/// tip | Порада + +Traefik має інтеграції з Docker, Kubernetes та іншими, тож налаштувати і сконфігурувати HTTPS для ваших контейнерів з ним дуже просто. + +/// + +Альтернативно, HTTPS може оброблятись хмарним провайдером як один з їхніх сервісів (при цьому застосунок усе ще працює в контейнері). + +## Автозапуск і перезапуски { #running-on-startup-and-restarts } + +Зазвичай інший інструмент відповідає за запуск і виконання вашого контейнера. + +Це може бути безпосередньо Docker, Docker Compose, Kubernetes, хмарний сервіс тощо. + +У більшості (або всіх) випадків є проста опція, щоб увімкнути запуск контейнера при старті системи та перезапуски у разі збоїв. Наприклад, у Docker це опція командного рядка `--restart`. + +Без використання контейнерів змусити застосунки запускатися при старті системи та з перезапусками може бути клопітно і складно. Але під час роботи з контейнерами у більшості випадків ця функціональність вбудована за замовчуванням. ✨ + +## Реплікація - кількість процесів { #replication-number-of-processes } + +Якщо у вас є кластер машин із Kubernetes, Docker Swarm Mode, Nomad або іншою подібною складною системою для керування розподіленими контейнерами на кількох машинах, тоді ви, ймовірно, захочете обробляти реплікацію на рівні кластера замість використання менеджера процесів (як-от Uvicorn з працівниками) у кожному контейнері. + +Одна з таких розподілених систем керування контейнерами, як-от Kubernetes, зазвичай має інтегровані способи обробляти реплікацію контейнерів, підтримуючи водночас балансування навантаження для вхідних запитів. Усе це - на рівні кластера. + +У таких випадках ви, ймовірно, захочете зібрати Docker-образ з нуля, як [пояснено вище](#dockerfile), встановивши ваші залежності і запустивши один процес Uvicorn замість використання кількох працівників Uvicorn. + +### Балансувальник навантаження { #load-balancer } + +При використанні контейнерів зазвичай є якийсь компонент, що слухає на головному порту. Це може бути інший контейнер, який також є представником з термінацією TLS для обробки HTTPS, або подібний інструмент. + +Оскільки цей компонент приймає навантаження запитів і розподіляє його між працівниками (сподіваємось) збалансовано, його також часто називають балансувальником навантаження. + +/// tip | Порада + +Той самий компонент представника з термінацією TLS, що використовується для HTTPS, швидше за все, також буде балансувальником навантаження. + +/// + +І під час роботи з контейнерами та сама система, яку ви використовуєте для їх запуску і керування ними, вже матиме внутрішні інструменти для передавання мережевої комунікації (наприклад, HTTP-запитів) від цього балансувальника навантаження (який також може бути представником з термінацією TLS) до контейнерів із вашим застосунком. + +### Один балансувальник навантаження - кілька контейнерів-працівників { #one-load-balancer-multiple-worker-containers } + +Під час роботи з Kubernetes або подібними розподіленими системами керування контейнерами використання їхніх внутрішніх мережевих механізмів дозволяє єдиному балансувальнику навантаження, що слухає на головному порту, передавати комунікацію (запити) до кількох контейнерів, у яких запущено ваш застосунок. + +Кожен з цих контейнерів із вашим застосунком зазвичай має лише один процес (наприклад, процес Uvicorn, що запускає ваш застосунок FastAPI). Усі вони будуть ідентичними контейнерами, які запускають те саме, але кожен зі своїм процесом, пам’яттю тощо. Таким чином ви використаєте переваги паралелізму на різних ядрах процесора або навіть на різних машинах. + +А розподілена система контейнерів із балансувальником навантаження розподілятиме запити між кожним із контейнерів із вашим застосунком по черзі. Тож кожен запит може оброблятися одним із кількох реплікованих контейнерів, що запускають ваш застосунок. + +І зазвичай цей балансувальник навантаження зможе обробляти запити, які йдуть до інших застосунків у вашому кластері (наприклад, до іншого домену або під іншим префіксом шляху URL), і передаватиме комунікацію до відповідних контейнерів для того іншого застосунку, що працює у вашому кластері. + +### Один процес на контейнер { #one-process-per-container } + +У такому сценарії ви, ймовірно, захочете мати один (Uvicorn) процес на контейнер, адже ви вже обробляєте реплікацію на рівні кластера. + +Тобто в цьому випадку ви не захочете мати кількох працівників у контейнері, наприклад через опцію командного рядка `--workers`. Ви захочете мати лише один процес Uvicorn на контейнер (але, ймовірно, кілька контейнерів). + +Наявність іншого менеджера процесів всередині контейнера (як це було б із кількома працівниками) лише додасть зайвої складності, яку, найімовірніше, ви вже вирішуєте на рівні кластера. + +### Контейнери з кількома процесами та особливі випадки { #containers-with-multiple-processes-and-special-cases } + +Звісно, є особливі випадки, коли ви можете захотіти мати контейнер із кількома процесами-працівниками Uvicorn всередині. + +У таких випадках ви можете використати опцію командного рядка `--workers`, щоб задати кількість працівників, яких потрібно запустити: + +```{ .dockerfile .annotate } +FROM python:3.14 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +# (1)! +CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"] +``` + +1. Тут ми використовуємо опцію командного рядка `--workers`, щоб встановити кількість працівників 4. + +Ось кілька прикладів, коли це може мати сенс: + +#### Простий застосунок { #a-simple-app } + +Ви можете захотіти менеджер процесів у контейнері, якщо ваш застосунок достатньо простий, щоб запускати його на одному сервері, а не на кластері. + +#### Docker Compose { #docker-compose } + +Ви можете розгортати на одному сервері (не в кластері) за допомогою Docker Compose, тож у вас не буде простого способу керувати реплікацією контейнерів (у Docker Compose), зберігаючи спільну мережу та балансування навантаження. + +Тоді ви можете захотіти мати один контейнер із менеджером процесів, що запускає кілька процесів-працівників всередині. + +--- + +Головна думка: це не правила, викарбувані в камені, яких потрібно сліпо дотримуватися. Ви можете використати ці ідеї, щоб оцінити власний кейс і вирішити, який підхід найкращий для вашої системи, розглядаючи, як керувати такими концепціями: + +* Безпека - HTTPS +* Автозапуск +* Перезапуски +* Реплікація (кількість запущених процесів) +* Пам’ять +* Попередні кроки перед запуском + +## Пам’ять { #memory } + +Якщо ви запускаєте один процес на контейнер, ви матимете більш-менш чітко визначений, стабільний і обмежений обсяг пам’яті, що споживається кожним із цих контейнерів (їх може бути більше одного, якщо вони репліковані). + +Потім ви можете встановити ті самі ліміти та вимоги до пам’яті у ваших конфігураціях для системи керування контейнерами (наприклад, у Kubernetes). Таким чином вона зможе реплікувати контейнери на доступних машинах, враховуючи обсяг пам’яті, потрібний їм, і обсяг доступної пам’яті на машинах у кластері. + +Якщо ваш застосунок простий, імовірно, це не буде проблемою, і вам може не знадобитися задавати жорсткі ліміти пам’яті. Але якщо ви використовуєте багато пам’яті (наприклад, із моделями машинного навчання), вам слід перевірити, скільки пам’яті ви споживаєте, і відкоригувати кількість контейнерів, що запускаються на кожній машині (і, можливо, додати більше машин у ваш кластер). + +Якщо ви запускаєте кілька процесів на контейнер, вам потрібно переконатися, що кількість запущених процесів не споживає більше пам’яті, ніж доступно. + +## Попередні кроки перед запуском і контейнери { #previous-steps-before-starting-and-containers } + +Якщо ви використовуєте контейнери (наприклад, Docker, Kubernetes), то є два основні підходи. + +### Кілька контейнерів { #multiple-containers } + +Якщо у вас кілька контейнерів, імовірно кожен запускає один процес (наприклад, у кластері Kubernetes), тоді ви, ймовірно, захочете мати окремий контейнер, який виконає попередні кроки в одному контейнері, запустивши один процес, перед запуском реплікованих контейнерів-працівників. + +/// info | Інформація + +Якщо ви використовуєте Kubernetes, це, ймовірно, буде Init Container. + +/// + +Якщо у вашому випадку немає проблеми запускати ці попередні кроки кілька разів паралельно (наприклад, якщо ви не виконуєте міграції бази даних, а лише перевіряєте, чи база вже готова), тоді ви також можете просто помістити їх у кожен контейнер безпосередньо перед запуском головного процесу. + +### Один контейнер { #single-container } + +Якщо у вас просте налаштування з одним контейнером, який потім запускає кілька процесів-працівників (або теж лише один процес), тоді ви можете виконати ці попередні кроки в тому ж контейнері безпосередньо перед запуском процесу із застосунком. + +### Базовий образ Docker { #base-docker-image } + +Колись існував офіційний образ Docker для FastAPI: tiangolo/uvicorn-gunicorn-fastapi. Але зараз він застарілий. ⛔️ + +Ймовірно, вам не слід використовувати цей базовий образ Docker (або будь-який інший подібний). + +Якщо ви використовуєте Kubernetes (або інші) і вже налаштовуєте реплікацію на рівні кластера з кількома контейнерами. У таких випадках краще зібрати образ з нуля, як описано вище: [Зібрати Docker-образ для FastAPI](#build-a-docker-image-for-fastapi). + +А якщо вам потрібно мати кілька працівників, ви можете просто використати опцію командного рядка `--workers`. + +/// note | Технічні деталі + +Цей образ Docker було створено тоді, коли Uvicorn не підтримував керування та перезапуск «мертвих» працівників, тому потрібно було використовувати Gunicorn з Uvicorn, що додавало чимало складності лише для того, щоб Gunicorn керував та перезапускав процеси-працівники Uvicorn. + +Але тепер, коли Uvicorn (і команда `fastapi`) підтримують використання `--workers`, немає причин використовувати базовий образ Docker замість того, щоб зібрати власний (це приблизно та сама кількість коду 😅). + +/// + +## Розгорнути образ контейнера { #deploy-the-container-image } + +Після отримання образу контейнера (Docker) є кілька способів його розгорнути. + +Наприклад: + +* З Docker Compose на одному сервері +* З кластером Kubernetes +* З кластером Docker Swarm Mode +* З іншим інструментом, як-от Nomad +* З хмарним сервісом, який бере ваш образ контейнера і розгортає його + +## Образ Docker з `uv` { #docker-image-with-uv } + +Якщо ви використовуєте uv для встановлення та керування вашим проєктом, ви можете скористатися їхнім посібником Docker для uv. + +## Підсумок { #recap } + +Використовуючи системи контейнерів (наприклад, з Docker і Kubernetes), досить просто обробляти всі концепції розгортання: + +* HTTPS +* Автозапуск +* Перезапуски +* Реплікація (кількість запущених процесів) +* Пам’ять +* Попередні кроки перед запуском + +У більшості випадків ви, ймовірно, не захочете використовувати будь-який базовий образ, а натомість зібрати образ контейнера з нуля на основі офіційного образу Python для Docker. + +Дотримуючись порядку інструкцій у `Dockerfile` і використовуючи кеш Docker, ви можете мінімізувати час збірки, щоб максимізувати свою продуктивність (і уникнути нудьги). 😎 diff --git a/docs/uk/docs/deployment/fastapicloud.md b/docs/uk/docs/deployment/fastapicloud.md new file mode 100644 index 000000000..4b4f3e59b --- /dev/null +++ b/docs/uk/docs/deployment/fastapicloud.md @@ -0,0 +1,65 @@ +# FastAPI Cloud { #fastapi-cloud } + +Ви можете розгорнути свій застосунок FastAPI на FastAPI Cloud однією командою, приєднуйтесь до списку очікування, якщо ще ні. 🚀 + +## Вхід { #login } + +Переконайтеся, що у вас вже є обліковий запис **FastAPI Cloud** (ми запросили вас зі списку очікування 😉). + +Потім увійдіть: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +## Розгортання { #deploy } + +Тепер розгорніть свій застосунок однією командою: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +Ось і все! Тепер ви можете отримати доступ до свого застосунку за цим URL. ✨ + +## Про FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** створено тим самим автором і командою, що стоїть за **FastAPI**. + +Він спрощує процес **створення**, **розгортання** та **доступу** до API з мінімальними зусиллями. + +Він переносить той самий **досвід розробника**, що і під час створення застосунків з FastAPI, на їх **розгортання** у хмарі. 🎉 + +Він також подбає про більшість речей, які вам потрібні під час розгортання застосунку, як-от: + +- HTTPS +- реплікація з автомасштабуванням на основі запитів +- тощо + +FastAPI Cloud - основний спонсор і джерело фінансування для відкритих проєктів *«FastAPI та друзі»*. ✨ + +## Розгортання в інших хмарних провайдерів { #deploy-to-other-cloud-providers } + +FastAPI є відкритим кодом і базується на стандартах. Ви можете розгортати застосунки FastAPI в будь-якого хмарного провайдера за вашим вибором. + +Дотримуйтесь інструкцій вашого хмарного провайдера, щоб розгортати застосунки FastAPI у них. 🤓 + +## Розгортання на вашому сервері { #deploy-your-own-server } + +Пізніше в цьому розділі **Розгортання** я також навчу вас усім деталям, щоб ви розуміли, що відбувається, що потрібно зробити і як розгортати застосунки FastAPI самостійно, зокрема на власних серверах. 🤓 diff --git a/docs/uk/docs/deployment/https.md b/docs/uk/docs/deployment/https.md new file mode 100644 index 000000000..29329c88f --- /dev/null +++ b/docs/uk/docs/deployment/https.md @@ -0,0 +1,231 @@ +# Про HTTPS { #about-https } + +Легко припустити, що HTTPS - це щось, що просто «увімкнено» або ні. + +Але все значно складніше. + +/// tip | Порада + +Якщо ви поспішаєте або це неважливо для вас, переходьте до наступних розділів для покрокових інструкцій із налаштування всього різними техніками. + +/// + +Щоб вивчити основи HTTPS з точки зору споживача, перегляньте https://howhttps.works/. + +Тепер, з точки зору розробника, ось кілька речей, які варто пам'ятати, розмірковуючи про HTTPS: + +* Для HTTPS сервер має мати «сертифікати», видані третьою стороною. + * Насправді ці сертифікати «отримуються» у третьої сторони, а не «генеруються». +* Сертифікати мають строк дії. + * Їхній строк дії спливає. + * І тоді їх потрібно поновити, знову отримавши у третьої сторони. +* Шифрування з'єднання відбувається на рівні TCP. + * Це один шар нижче від HTTP. + * Тож обробка сертифіката та шифрування виконується до HTTP. +* TCP не знає про «домени». Лише про IP-адреси. + * Інформація про конкретний домен, який запитується, міститься в даних HTTP. +* Сертифікати HTTPS «засвідчують» певний домен, але протокол і шифрування працюють на рівні TCP, до того як відомо, з яким доменом маємо справу. +* Типово це означало б, що на одну IP-адресу можна мати лише один сертифікат HTTPS. + * Неважливо, наскільки великий ваш сервер або наскільки малий кожен застосунок на ньому. + * Однак для цього є рішення. +* Є розширення протоколу TLS (який обробляє шифрування на рівні TCP, до HTTP), що називається SNI. + * Це розширення SNI дозволяє одному серверу (з однією IP-адресою) мати кілька сертифікатів HTTPS і обслуговувати кілька доменів/застосунків по HTTPS. + * Щоб це працювало, один-єдиний компонент (програма), що працює на сервері та слухає публічну IP-адресу, має мати всі сертифікати HTTPS на сервері. +* Після отримання захищеного з'єднання протокол обміну все ще HTTP. + * Вміст зашифровано, хоча він надсилається протоколом HTTP. + +Поширена практика мати одну програму/HTTP-сервер, що працює на сервері (машині, хості тощо) і керує всіма частинами HTTPS: приймає зашифровані HTTPS-запити, надсилає розшифровані HTTP-запити до фактичного HTTP-застосунку, що працює на тому ж сервері (у нашому випадку застосунок FastAPI), отримує HTTP-відповідь від застосунку, шифрує її за допомогою відповідного сертифіката HTTPS і надсилає її назад клієнту через HTTPS. Такий сервер часто називають TLS Termination Proxy. + +Деякі варіанти, які ви можете використати як TLS Termination Proxy: + +* Traefik (також може виконувати поновлення сертифікатів) +* Caddy (також може виконувати поновлення сертифікатів) +* Nginx +* HAProxy + +## Let's Encrypt { #lets-encrypt } + +До Let's Encrypt ці сертифікати HTTPS продавалися довіреними третіми сторонами. + +Процес отримання одного з таких сертифікатів був громіздким, вимагав чимало паперової роботи, а самі сертифікати були доволі дорогими. + +Але потім з'явився проєкт Let's Encrypt. + +Це проєкт Linux Foundation. Він надає сертифікати HTTPS безкоштовно, в автоматизований спосіб. Ці сертифікати використовують усі стандартні криптографічні механізми безпеки і є короткостроковими (близько 3 місяців), тож безпека насправді краща завдяки зменшеній тривалості життя. + +Домени безпечно перевіряються, а сертифікати генеруються автоматично. Це також дозволяє автоматизувати поновлення цих сертифікатів. + +Ідея полягає в автоматизації отримання та поновлення цих сертифікатів, щоб ви могли мати безпечний HTTPS безкоштовно та назавжди. + +## HTTPS для розробників { #https-for-developers } + +Ось приклад того, як може виглядати HTTPS API, крок за кроком, зосереджуючись переважно на ідеях, важливих для розробників. + +### Доменне ім'я { #domain-name } + +Ймовірно, все почнеться з того, що ви придбаєте якесь доменне ім'я. Потім ви налаштуєте його на сервері DNS (можливо, у вашого ж хмарного провайдера). + +Ви, скоріш за все, отримаєте хмарний сервер (віртуальну машину) або щось подібне, і він матиме фіксовану публічну IP-адресу. + +На сервері(ах) DNS ви налаштуєте запис («`A record`»), щоб спрямувати ваш домен на публічну IP-адресу вашого сервера. + +Ймовірно, ви зробите це лише один раз, уперше, коли все налаштовуватимете. + +/// tip | Порада + +Частина про доменне ім'я значно раніше за HTTPS, але оскільки все залежить від домену та IP-адреси, варто згадати це тут. + +/// + +### DNS { #dns } + +Тепер зосередьмося на всіх власне частинах HTTPS. + +Спочатку браузер звернеться до серверів DNS, щоб дізнатися, яка IP-адреса для домену, у цьому випадку `someapp.example.com`. + +Сервери DNS повідомлять браузеру використати конкретну IP-адресу. Це буде публічна IP-адреса, яку використовує ваш сервер і яку ви налаштували на серверах DNS. + + + +### Початок TLS рукостискання { #tls-handshake-start } + +Потім браузер зв'яжеться з цією IP-адресою на порту 443 (порт HTTPS). + +Перша частина комунікації - це просто встановлення з'єднання між клієнтом і сервером та узгодження криптографічних ключів тощо. + + + +Ця взаємодія між клієнтом і сервером для встановлення з'єднання TLS називається TLS рукостисканням. + +### TLS із розширенням SNI { #tls-with-sni-extension } + +Лише один процес на сервері може слухати конкретний порт на конкретній IP-адресі. Інші процеси можуть слухати інші порти на тій самій IP-адресі, але лише один для кожної комбінації IP-адреси та порту. + +TLS (HTTPS) за замовчуванням використовує конкретний порт `443`. Отже, це порт, який нам потрібен. + +Оскільки лише один процес може слухати цей порт, процесом, що робитиме це, буде TLS Termination Proxy. + +TLS Termination Proxy матиме доступ до одного або кількох сертифікатів TLS (сертифікатів HTTPS). + +Використовуючи обговорене вище розширення SNI, TLS Termination Proxy перевірить, який із наявних сертифікатів TLS (HTTPS) слід використати для цього з'єднання, обравши той, що відповідає домену, очікуваному клієнтом. + +У цьому випадку він використає сертифікат для `someapp.example.com`. + + + +Клієнт уже довіряє сутності, яка видала цей сертифікат TLS (у цьому випадку Let's Encrypt, але про це згодом), тож він може перевірити, що сертифікат дійсний. + +Потім, використовуючи сертифікат, клієнт і TLS Termination Proxy узгоджують, як шифрувати решту TCP-комунікації. На цьому частина TLS рукостискання завершується. + +Після цього клієнт і сервер мають зашифроване TCP-з'єднання - саме це надає TLS. І тоді вони можуть використати це з'єднання, щоб почати власне HTTP-комунікацію. + +І це і є HTTPS: це звичайний HTTP усередині захищеного TLS-з'єднання замість чистого (незашифрованого) TCP-з'єднання. + +/// tip | Порада + +Зверніть увагу, що шифрування комунікації відбувається на рівні TCP, а не на рівні HTTP. + +/// + +### HTTPS-запит { #https-request } + +Тепер, коли клієнт і сервер (конкретно браузер і TLS Termination Proxy) мають зашифроване TCP-з'єднання, вони можуть почати HTTP-комунікацію. + +Отже, клієнт надсилає HTTPS-запит. Це просто HTTP-запит через зашифроване TLS-з'єднання. + + + +### Розшифрування запиту { #decrypt-the-request } + +TLS Termination Proxy використає узгоджене шифрування, щоб розшифрувати запит, і передасть звичайний (розшифрований) HTTP-запит процесу, що запускає застосунок (наприклад, процесу з Uvicorn, який запускає застосунок FastAPI). + + + +### HTTP-відповідь { #http-response } + +Застосунок обробить запит і надішле звичайну (незашифровану) HTTP-відповідь TLS Termination Proxy. + + + +### HTTPS-відповідь { #https-response } + +Потім TLS Termination Proxy зашифрує відповідь, використовуючи попередньо узгоджену криптографію (що почалася із сертифіката для `someapp.example.com`), і надішле її назад у браузер. + +Далі браузер перевірить, що відповідь дійсна й зашифрована правильним криптографічним ключем тощо. Потім він розшифрує відповідь і обробить її. + + + +Клієнт (браузер) знатиме, що відповідь надходить від правильного сервера, тому що використовується узгоджена раніше криптографія з використанням сертифіката HTTPS. + +### Кілька застосунків { #multiple-applications } + +На тому самому сервері (або серверах) може бути кілька застосунків, наприклад інші програми API або база даних. + +Лише один процес може обробляти конкретну IP-адресу і порт (TLS Termination Proxy у нашому прикладі), але інші застосунки/процеси також можуть працювати на сервері(ах), доки вони не намагаються використати ту саму комбінацію публічної IP-адреси й порту. + + + +Таким чином, TLS Termination Proxy може обробляти HTTPS і сертифікати для кількох доменів, для кількох застосунків, а потім передавати запити до відповідного застосунку в кожному випадку. + +### Поновлення сертифіката { #certificate-renewal } + +У певний момент у майбутньому строк дії кожного сертифіката спливе (приблизно через 3 місяці після його отримання). + +Потім інша програма (в деяких випадках це інша програма, а в деяких - той самий TLS Termination Proxy) зв'яжеться з Let's Encrypt і поновить сертифікат(и). + + + +Сертифікати TLS пов'язані з доменним іменем, а не з IP-адресою. + +Тому, щоб поновити сертифікати, програма поновлення має довести авторитету (Let's Encrypt), що вона справді «володіє» і контролює цей домен. + +Щоб зробити це й задовольнити різні потреби застосунків, є кілька способів. Деякі популярні: + +* Змінити деякі записи DNS. + * Для цього програма поновлення має підтримувати API провайдера DNS, тож залежно від того, якого провайдера DNS ви використовуєте, це може бути або не бути варіантом. +* Запуститися як сервер (принаймні під час процесу отримання сертифіката) на публічній IP-адресі, пов'язаній із доменом. + * Як ми казали вище, лише один процес може слухати конкретну IP-адресу та порт. + * Це одна з причин, чому дуже зручно, коли той самий TLS Termination Proxy також займається процесом поновлення сертифікатів. + * Інакше вам, можливо, доведеться на мить зупинити TLS Termination Proxy, запустити програму поновлення, щоб отримати сертифікати, потім налаштувати їх у TLS Termination Proxy і перезапустити TLS Termination Proxy. Це неідеально, оскільки ваші застосунки будуть недоступні під час вимкнення TLS Termination Proxy. + +Увесь цей процес поновлення, паралельно з обслуговуванням застосунку, - одна з головних причин, чому ви можете захотіти мати окрему систему для обробки HTTPS за допомогою TLS Termination Proxy замість того, щоб просто використовувати сертифікати TLS безпосередньо з сервером застосунку (наприклад, Uvicorn). + +## Направлені заголовки проксі { #proxy-forwarded-headers } + +Коли ви використовуєте проксі для обробки HTTPS, ваш сервер застосунку (наприклад, Uvicorn через FastAPI CLI) нічого не знає про процес HTTPS, він спілкується звичайним HTTP із TLS Termination Proxy. + +Цей проксі зазвичай динамічно встановлює деякі HTTP-заголовки перед передачею запиту серверу застосунку, щоб дати йому знати, що запит направляється проксі. + +/// note | Технічні деталі + +Заголовки проксі: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +Втім, оскільки сервер застосунку не знає, що він стоїть за довіреним проксі, за замовчуванням він не довірятиме цим заголовкам. + +Але ви можете налаштувати сервер застосунку, щоб довіряти направленим заголовкам, надісланим проксі. Якщо ви використовуєте FastAPI CLI, ви можете скористатися курсивною *опцією CLI* `--forwarded-allow-ips`, щоб повідомити, з яких IP-адрес слід довіряти цим направленим заголовкам. + +Наприклад, якщо сервер застосунку отримує комунікацію лише від довіреного проксі, ви можете встановити `--forwarded-allow-ips="*"`, щоб довіряти всім вхідним IP-адресам, оскільки він отримуватиме запити лише з тієї IP-адреси, яку використовує проксі. + +Так застосунок зможе знати свою публічну URL-адресу, чи використовує він HTTPS, домен тощо. + +Це буде корисно, наприклад, для коректної обробки перенаправлень. + +/// tip | Порада + +Ви можете дізнатися більше про це в документації [За проксі - Увімкнути направлені заголовки проксі](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} + +/// + +## Підсумок { #recap } + +Наявність HTTPS дуже важлива і в більшості випадків критична. Більшість зусиль, які вам як розробнику доведеться докласти навколо HTTPS, полягають лише в розумінні цих концепцій і того, як вони працюють. + +Але як тільки ви знаєте базову інформацію про HTTPS для розробників, ви можете легко комбінувати й налаштовувати різні інструменти, щоб керувати всім просто. + +У деяких наступних розділах я покажу кілька конкретних прикладів налаштування HTTPS для застосунків FastAPI. 🔒 diff --git a/docs/uk/docs/deployment/index.md b/docs/uk/docs/deployment/index.md new file mode 100644 index 000000000..738668139 --- /dev/null +++ b/docs/uk/docs/deployment/index.md @@ -0,0 +1,23 @@ +# Розгортання { #deployment } + +Розгортання застосунку **FastAPI** відносно просте. + +## Що означає розгортання { #what-does-deployment-mean } + +Розгорнути застосунок - це виконати необхідні кроки, щоб зробити його доступним для користувачів. + +Для **веб API** це зазвичай означає розміщення його на **віддаленій машині** з **серверною програмою**, що забезпечує хорошу продуктивність, стабільність тощо, щоб ваші **користувачі** могли **отримувати доступ** до застосунку ефективно та без перерв чи проблем. + +Це відрізняється від етапів **розробки**, коли ви постійно змінюєте код, ламаєте й виправляєте його, зупиняєте та перезапускаєте сервер розробки тощо. + +## Стратегії розгортання { #deployment-strategies } + +Існує кілька способів зробити це залежно від вашого конкретного випадку використання та інструментів, які ви використовуєте. + +Ви можете розгорнути сервер самостійно, використовуючи комбінацію інструментів, можете скористатися **хмарним сервісом**, який виконує частину роботи за вас, або обрати інші варіанти. + +Наприклад, ми, команда, що стоїть за FastAPI, створили **FastAPI Cloud**, щоб зробити розгортання застосунків FastAPI у хмарі якомога простішим і з тим самим досвідом розробки, що й під час роботи з FastAPI. + +Я покажу вам кілька основних концепцій, про які, ймовірно, варто пам'ятати під час розгортання **FastAPI**-застосунку (хоча більшість із них стосується будь-яких інших типів веб-застосунків). + +У наступних розділах ви побачите більше деталей, на які варто зважати, та деякі техніки, як це зробити. ✨ diff --git a/docs/uk/docs/deployment/manually.md b/docs/uk/docs/deployment/manually.md new file mode 100644 index 000000000..d70ec5d5d --- /dev/null +++ b/docs/uk/docs/deployment/manually.md @@ -0,0 +1,157 @@ +# Запустіть сервер вручну { #run-a-server-manually } + +## Використовуйте команду `fastapi run` { #use-the-fastapi-run-command } + +Коротко: використовуйте `fastapi run`, щоб запустити ваш застосунок FastAPI: + +
+ +```console +$ fastapi run main.py + + FastAPI Starting production 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://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) +``` + +
+ +Це спрацює в більшості випадків. 😎 + +Цю команду можна використати, наприклад, щоб запустити ваш застосунок FastAPI у контейнері, на сервері тощо. + +## Сервери ASGI { #asgi-servers } + +Розгляньмо деталі. + +FastAPI використовує стандарт для побудови Python вебфреймворків і серверів під назвою ASGI. FastAPI - це ASGI вебфреймворк. + +Головне, що потрібно, щоб запустити застосунок **FastAPI** (або будь-який інший ASGI-застосунок) на віддаленій серверній машині, - це програма ASGI-сервера на кшталт **Uvicorn**; саме вона постачається за замовчуванням у команді `fastapi`. + +Є кілька альтернатив, зокрема: + +* Uvicorn: високопродуктивний ASGI-сервер. +* Hypercorn: ASGI-сервер, сумісний з HTTP/2 і Trio, серед інших можливостей. +* Daphne: ASGI-сервер, створений для Django Channels. +* Granian: Rust HTTP-сервер для Python-застосунків. +* NGINX Unit: NGINX Unit - легке й універсальне середовище виконання вебзастосунків. + +## Серверна машина і серверна програма { #server-machine-and-server-program } + +Є невелика деталь щодо назв, яку варто пам'ятати. 💡 + +Слово «**сервер**» зазвичай означає і віддалений/хмарний комп'ютер (фізична або віртуальна машина), і програму, що працює на цій машині (наприклад, Uvicorn). + +Майте на увазі, що коли ви бачите слово «сервер» загалом, воно може стосуватися будь-якого з цих двох значень. + +Коли йдеться про віддалену машину, її часто називають «сервер», а також «машина», «VM» (віртуальна машина), «вузол». Усе це означає різновиди віддаленої машини, зазвичай з Linux, на якій ви запускаєте програми. + +## Встановіть серверну програму { #install-the-server-program } + +Після встановлення FastAPI ви отримуєте продакшн-сервер Uvicorn і можете запускати його командою `fastapi run`. + +Але ви також можете встановити ASGI-сервер вручну. + +Переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md){.internal-link target=_blank}, активували його, після чого можете встановити серверну програму. + +Наприклад, щоб установити Uvicorn: + +
+ +```console +$ pip install "uvicorn[standard]" + +---> 100% +``` + +
+ +Подібний процес застосовується до будь-якої іншої ASGI-серверної програми. + +/// tip | Порада + +Додавши `standard`, Uvicorn встановить і використовуватиме деякі рекомендовані додаткові залежності. + +Зокрема `uvloop` - високопродуктивну заміну «без змін у коді» для `asyncio`, що суттєво підвищує рівночасність і продуктивність. + +Якщо ви встановлюєте FastAPI через `pip install "fastapi[standard]"`, ви вже отримаєте і `uvicorn[standard]`. + +/// + +## Запустіть серверну програму { #run-the-server-program } + +Якщо ви встановили ASGI-сервер вручну, зазвичай потрібно передати рядок імпорту в спеціальному форматі, щоб він імпортував ваш застосунок FastAPI: + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 80 + +INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) +``` + +
+ +/// note | Примітка + +Команда `uvicorn main:app` означає: + +* `main`: файл `main.py` (Python «модуль»). +* `app`: об'єкт, створений у `main.py` рядком `app = FastAPI()`. + +Це еквівалентно: + +```Python +from main import app +``` + +/// + +Кожна альтернативна ASGI-серверна програма матиме подібну команду; читайте більше в їхній документації. + +/// warning | Попередження + +Uvicorn та інші сервери підтримують опцію `--reload`, корисну під час розробки. + +Опція `--reload` споживає значно більше ресурсів, є менш стабільною тощо. + +Вона дуже допомагає під час розробки, але її не слід використовувати в продакшні. + +/// + +## Концепції розгортання { #deployment-concepts } + +Ці приклади запускають серверну програму (наприклад, Uvicorn), піднімаючи один процес, що слухає всі IP (`0.0.0.0`) на визначеному порту (наприклад, `80`). + +Це базова ідея. Але, ймовірно, вам знадобиться подбати ще про таке: + +* Безпека - HTTPS +* Автозапуск +* Перезапуски +* Реплікація (кількість запущених процесів) +* Пам'ять +* Попередні кроки перед стартом + +У наступних розділах я розповім більше про кожну з цих концепцій, як про них думати, і наведу конкретні приклади та стратегії для їх опрацювання. 🚀 diff --git a/docs/uk/docs/deployment/server-workers.md b/docs/uk/docs/deployment/server-workers.md new file mode 100644 index 000000000..81a8bd2a4 --- /dev/null +++ b/docs/uk/docs/deployment/server-workers.md @@ -0,0 +1,139 @@ +# Працівники сервера - Uvicorn з працівниками { #server-workers-uvicorn-with-workers } + +Повернімося до попередніх концепцій розгортання: + +- Безпека - HTTPS +- Запуск під час старту +- Перезапуски +- Реплікація (кількість процесів, що виконуються) +- Пам'ять +- Попередні кроки перед запуском + +До цього моменту, проходячи всі навчальні посібники в документації, ви, ймовірно, запускали серверну програму, наприклад, використовуючи команду `fastapi`, яка запускає Uvicorn у вигляді одного процесу. + +Під час розгортання застосунків ви, найімовірніше, захочете мати реплікацію процесів, щоб використовувати кілька ядер і обробляти більше запитів. + +Як ви бачили в попередньому розділі про [Концепції розгортання](concepts.md){.internal-link target=_blank}, існує кілька стратегій, які можна використовувати. + +Тут я покажу, як використовувати Uvicorn із процесами-працівниками за допомогою команди `fastapi` або безпосередньо команди `uvicorn`. + +/// info | Інформація + +Якщо ви використовуєте контейнери, наприклад з Docker або Kubernetes, я розповім про це більше в наступному розділі: [FastAPI у контейнерах - Docker](docker.md){.internal-link target=_blank}. + +Зокрема, під час запуску в Kubernetes вам, найімовірніше, не варто використовувати працівників, натомість запускати один процес Uvicorn на контейнер. Але про це я розповім пізніше в тому розділі. + +/// + +## Кілька працівників { #multiple-workers } + +Ви можете запустити кілька працівників за допомогою параметра командного рядка `--workers`: + +//// tab | `fastapi` + +Якщо ви використовуєте команду `fastapi`: + +
+ +```console +$ fastapi run --workers 4 main.py + + FastAPI Starting production 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://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. +``` + +
+ +//// + +//// tab | `uvicorn` + +Якщо ви віддаєте перевагу використовувати команду `uvicorn` безпосередньо: + +
+ +```console +$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 +INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit) +INFO: Started parent process [27365] +INFO: Started server process [27368] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27369] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27370] +INFO: Waiting for application startup. +INFO: Application startup complete. +INFO: Started server process [27367] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +
+ +//// + +Єдина нова опція тут — `--workers`, яка вказує Uvicorn запустити 4 процеси-працівники. + +Також ви можете побачити, що виводиться PID кожного процесу: `27365` для батьківського процесу (це менеджер процесів) і по одному для кожного процесу-працівника: `27368`, `27369`, `27370` і `27367`. + +## Концепції розгортання { #deployment-concepts } + +Тут ви побачили, як використовувати кілька працівників, щоб паралелізувати виконання застосунку, використати кілька ядер процесора та обслуговувати більше запитів. + +Із наведеного вище списку концепцій розгортання, використання працівників головним чином допоможе з частиною про реплікацію і трохи з перезапусками, але про інше все ще треба подбати: + +- Безпека - HTTPS +- Запуск під час старту +- ***Перезапуски*** +- Реплікація (кількість процесів, що виконуються) +- Пам'ять +- Попередні кроки перед запуском + +## Контейнери і Docker { #containers-and-docker } + +У наступному розділі про [FastAPI у контейнерах - Docker](docker.md){.internal-link target=_blank} я поясню кілька стратегій, які ви можете використати для інших концепцій розгортання. + +Я покажу, як побудувати власний образ з нуля для запуску одного процесу Uvicorn. Це простий процес і, ймовірно, саме те, що потрібно при використанні розподіленої системи керування контейнерами, такої як Kubernetes. + +## Підсумок { #recap } + +Ви можете використовувати кілька процесів-працівників за допомогою параметра CLI `--workers` у командах `fastapi` або `uvicorn`, щоб скористатися перевагами багатоядерних процесорів і запускати кілька процесів паралельно. + +Ви можете застосувати ці інструменти та ідеї, якщо налаштовуєте власну систему розгортання і самостійно дбаєте про інші концепції розгортання. + +Перегляньте наступний розділ, щоб дізнатися про FastAPI з контейнерами (наприклад Docker і Kubernetes). Ви побачите, що ці інструменти також мають прості способи вирішити інші концепції розгортання. ✨ diff --git a/docs/uk/docs/deployment/versions.md b/docs/uk/docs/deployment/versions.md new file mode 100644 index 000000000..4f6d1b01a --- /dev/null +++ b/docs/uk/docs/deployment/versions.md @@ -0,0 +1,93 @@ +# Про версії FastAPI { #about-fastapi-versions } + +**FastAPI** уже використовується у продакшні в багатьох застосунках і системах. Покриття тестами підтримується на рівні 100%. Але розробка все ще рухається швидко. + +Нові можливості додаються часто, помилки регулярно виправляються, а код постійно поліпшується. + +Тому поточні версії все ще `0.x.x`, це відображає те, що кожна версія потенційно може містити несумісні зміни. Це відповідає правилам Семантичного версіонування. + +Ви можете створювати продакшн-застосунки з **FastAPI** вже зараз (і, ймовірно, робите це вже певний час), просто переконайтеся, що ви використовуєте версію, яка коректно працює з рештою вашого коду. + +## Зафіксуйте версію `fastapi` { #pin-your-fastapi-version } + +Перше, що слід зробити, - «зафіксувати» версію **FastAPI**, яку ви використовуєте, до конкретної останньої версії, що, як ви знаєте, правильно працює для вашого застосунку. + +Наприклад, припустімо, що у вашому застосунку використовується версія `0.112.0`. + +Якщо ви використовуєте файл `requirements.txt`, ви можете вказати версію так: + +```txt +fastapi[standard]==0.112.0 +``` + +це означатиме, що ви використовуватимете саме версію `0.112.0`. + +Або ви також можете зафіксувати так: + +```txt +fastapi[standard]>=0.112.0,<0.113.0 +``` + +це означатиме, що ви використовуватимете версії `0.112.0` і вище, але менше `0.113.0`, наприклад, версія `0.112.2` все ще буде прийнятною. + +Якщо ви використовуєте інший інструмент для керування встановленнями, як-от `uv`, Poetry, Pipenv чи інші, у всіх них є спосіб визначати конкретні версії для ваших пакетів. + +## Доступні версії { #available-versions } + +Ви можете переглянути доступні версії (наприклад, щоб перевірити поточну останню) в [Примітках до випусків](../release-notes.md){.internal-link target=_blank}. + +## Про версії { #about-versions } + +Відповідно до правил Семантичного версіонування, будь-яка версія нижче `1.0.0` потенційно може містити несумісні зміни. + +FastAPI також дотримується правила, що будь-яка зміна «PATCH»-версії - це виправлення помилок і зміни без порушень сумісності. + +/// tip | Порада + +«PATCH» - це останнє число, наприклад, у `0.2.3` PATCH-версія - це `3`. + +/// + +Тож ви можете зафіксувати версію так: + +```txt +fastapi>=0.45.0,<0.46.0 +``` + +Несумісні зміни та нові можливості додаються у «MINOR»-версіях. + +/// tip | Порада + +«MINOR» - це число посередині, наприклад, у `0.2.3` MINOR-версія - це `2`. + +/// + +## Оновлення версій FastAPI { #upgrading-the-fastapi-versions } + +Ви повинні додати тести для вашого застосунку. + +З **FastAPI** це дуже легко (завдяки Starlette), перегляньте документацію: [Тестування](../tutorial/testing.md){.internal-link target=_blank} + +Після того як у вас є тести, ви можете оновити версію **FastAPI** до новішої і переконатися, що весь ваш код працює правильно, запустивши тести. + +Якщо все працює або після внесення необхідних змін і проходження всіх тестів, зафіксуйте вашу версію `fastapi` на цій новій версії. + +## Про Starlette { #about-starlette } + +Не варто фіксувати версію `starlette`. + +Різні версії **FastAPI** використовуватимуть конкретну новішу версію Starlette. + +Отже, просто дозвольте **FastAPI** використовувати відповідну версію Starlette. + +## Про Pydantic { #about-pydantic } + +Pydantic включає тести для **FastAPI** разом із власними тестами, тому нові версії Pydantic (вище `1.0.0`) завжди сумісні з FastAPI. + +Ви можете зафіксувати Pydantic на будь-якій версії вище `1.0.0`, яка вам підходить. + +Наприклад: + +```txt +pydantic>=2.7.0,<3.0.0 +``` diff --git a/docs/uk/docs/environment-variables.md b/docs/uk/docs/environment-variables.md new file mode 100644 index 000000000..b61fd011f --- /dev/null +++ b/docs/uk/docs/environment-variables.md @@ -0,0 +1,298 @@ +# Змінні оточення { #environment-variables } + +/// tip | Порада + +Якщо ви вже знаєте, що таке «змінні оточення» і як їх використовувати, можете пропустити цей розділ. + +/// + +Змінна оточення (також відома як «env var») - це змінна, що існує поза кодом Python, в операційній системі, і може бути прочитана вашим кодом Python (а також іншими програмами). + +Змінні оточення корисні для роботи з налаштуваннями застосунку, як частина встановлення Python тощо. + +## Створення і використання змінних оточення { #create-and-use-env-vars } + +Ви можете створювати і використовувати змінні оточення в оболонці (терміналі) без участі Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Ви можете створити змінну оточення MY_NAME командою +$ export MY_NAME="Wade Wilson" + +// Потім можна використати її з іншими програмами, наприклад +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Створіть змінну оточення MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Використайте її з іншими програмами, наприклад +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## Читання змінних оточення в Python { #read-env-vars-in-python } + +Ви також можете створити змінні оточення поза Python, у терміналі (або будь-яким іншим способом), а потім зчитати їх у Python. + +Наприклад, у вас може бути файл `main.py` з: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip | Порада + +Другий аргумент до `os.getenv()` - це значення за замовчуванням, яке буде повернено. + +Якщо його не вказано, за замовчуванням це `None`. Тут ми надаємо `"World"` як значення за замовчуванням. + +/// + +Потім ви можете запустити цю програму Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Тут ми ще не встановлюємо змінну оточення +$ python main.py + +// Оскільки ми не встановили змінну оточення, отримуємо значення за замовчуванням + +Hello World from Python + +// Але якщо спочатку створимо змінну оточення +$ export MY_NAME="Wade Wilson" + +// А потім знову викличемо програму +$ python main.py + +// Тепер вона може прочитати змінну оточення + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Тут ми ще не встановлюємо змінну оточення +$ python main.py + +// Оскільки ми не встановили змінну оточення, отримуємо значення за замовчуванням + +Hello World from Python + +// Але якщо спочатку створимо змінну оточення +$ $Env:MY_NAME = "Wade Wilson" + +// А потім знову викличемо програму +$ python main.py + +// Тепер вона може прочитати змінну оточення + +Hello Wade Wilson from Python +``` + +
+ +//// + +Оскільки змінні оточення можна встановлювати поза кодом, але читати в коді, і їх не потрібно зберігати (фіксувати у `git`) разом з іншими файлами, їх часто використовують для конфігурацій або налаштувань. + +Ви також можете створити змінну оточення лише для конкретного запуску програми, вона буде доступна тільки цій програмі і лише на час її виконання. + +Щоб зробити це, створіть її безпосередньо перед командою запуску програми, в тому самому рядку: + +
+ +```console +// Створіть змінну оточення MY_NAME безпосередньо в цьому виклику програми +$ MY_NAME="Wade Wilson" python main.py + +// Тепер вона може прочитати змінну оточення + +Hello Wade Wilson from Python + +// Після цього змінна оточення більше не існує +$ python main.py + +Hello World from Python +``` + +
+ +/// tip | Порада + +Докладніше про це можна прочитати у The Twelve-Factor App: Config. + +/// + +## Типи і перевірка { #types-and-validation } + +Ці змінні оточення можуть містити лише текстові строки, оскільки вони зовнішні щодо Python і мають бути сумісними з іншими програмами та рештою системи (і навіть з різними операційними системами, як-от Linux, Windows, macOS). + +Це означає, що будь-яке значення, прочитане в Python зі змінної оточення, буде `str`, а будь-яке перетворення до іншого типу або будь-яка перевірка має виконуватися в коді. + +Ви дізнаєтеся більше про використання змінних оточення для роботи з налаштуваннями застосунку в розділі [Просунутий посібник користувача - Налаштування і змінні оточення](./advanced/settings.md){.internal-link target=_blank}. + +## Змінна оточення `PATH` { #path-environment-variable } + +Є спеціальна змінна оточення `PATH`, яку використовують операційні системи (Linux, macOS, Windows) для пошуку програм для запуску. + +Значення змінної `PATH` - це довга строка, що складається з каталогів, розділених двокрапкою `:` у Linux і macOS та крапкою з комою `;` у Windows. + +Наприклад, змінна оточення `PATH` може виглядати так: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Це означає, що система має шукати програми в каталогах: + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +Це означає, що система має шукати програми в каталогах: + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +Коли ви вводите команду в терміналі, операційна система шукає програму в кожному з тих каталогів, перелічених у змінній оточення `PATH`. + +Наприклад, коли ви вводите `python` у терміналі, операційна система шукає програму з назвою `python` у першому каталозі цього списку. + +Якщо знайде, вона використає її. Інакше продовжить пошук в інших каталогах. + +### Встановлення Python і оновлення `PATH` { #installing-python-and-updating-the-path } + +Під час встановлення Python вас можуть запитати, чи хочете ви оновити змінну оточення `PATH`. + +//// tab | Linux, macOS + +Припустімо, ви встановлюєте Python і він опиняється в каталозі `/opt/custompython/bin`. + +Якщо ви погодитеся оновити змінну оточення `PATH`, інсталятор додасть `/opt/custompython/bin` до змінної `PATH`. + +Це може виглядати так: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +Тепер, коли ви введете `python` у терміналі, система знайде програму Python у `/opt/custompython/bin` (останній каталог) і використає саме її. + +//// + +//// tab | Windows + +Припустімо, ви встановлюєте Python і він опиняється в каталозі `C:\opt\custompython\bin`. + +Якщо ви погодитеся оновити змінну оточення `PATH`, інсталятор додасть `C:\opt\custompython\bin` до змінної `PATH`. + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +Тепер, коли ви введете `python` у терміналі, система знайде програму Python у `C:\opt\custompython\bin` (останній каталог) і використає саме її. + +//// + +Отже, якщо ви введете: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +Система знайде програму `python` у `/opt/custompython/bin` і запустить її. + +Це приблизно еквівалентно введенню: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +Система знайде програму `python` у `C:\opt\custompython\bin\python` і запустить її. + +Це приблизно еквівалентно введенню: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +Ця інформація стане у пригоді під час вивчення [Віртуальних середовищ](virtual-environments.md){.internal-link target=_blank}. + +## Висновок { #conclusion } + +Тепер ви маєте базове розуміння того, що таке змінні оточення і як їх використовувати в Python. + +Також можна прочитати більше у Вікіпедії про змінну оточення. + +У багатьох випадках не одразу очевидно, як змінні оточення будуть корисними та застосовними. Але вони постійно з’являються в різних сценаріях під час розробки, тож варто про них знати. + +Наприклад, вам знадобиться ця інформація в наступному розділі про [Віртуальні середовища](virtual-environments.md). diff --git a/docs/uk/docs/help-fastapi.md b/docs/uk/docs/help-fastapi.md new file mode 100644 index 000000000..a98e56c26 --- /dev/null +++ b/docs/uk/docs/help-fastapi.md @@ -0,0 +1,256 @@ +# Допоможіть FastAPI - отримайте допомогу { #help-fastapi-get-help } + +Вам подобається **FastAPI**? + +Хочете допомогти FastAPI, іншим користувачам та автору? + +Або ви хочете отримати допомогу щодо **FastAPI**? + +Є дуже прості способи допомогти (деякі потребують лише одного-двох кліків). + +І є кілька способів отримати допомогу. + +## Підпишіться на розсилку { #subscribe-to-the-newsletter } + +Ви можете підписатися на (нечасту) розсилку [**FastAPI and friends**](newsletter.md){.internal-link target=_blank}, щоб бути в курсі: + +* Новин про FastAPI та друзів 🚀 +* Посібників 📝 +* Можливостей ✨ +* Несумісних змін 🚨 +* Порад і трюків ✅ + +## Стежте за FastAPI в X (Twitter) { #follow-fastapi-on-x-twitter } + +Стежте за @fastapi в **X (Twitter)**, щоб отримувати найсвіжіші новини про **FastAPI**. 🐦 + +## Додайте зірочку **FastAPI** на GitHub { #star-fastapi-in-github } + +Ви можете «поставити зірочку» FastAPI на GitHub (натиснувши кнопку зірочки у верхньому правому куті): https://github.com/fastapi/fastapi. ⭐️ + +Додавши зірочку, іншим користувачам буде легше його знайти і побачити, що він уже був корисним для інших. + +## Стежте за випусками в репозиторії GitHub { #watch-the-github-repository-for-releases } + +Ви можете «спостерігати» за FastAPI на GitHub (натиснувши кнопку «watch» у верхньому правому куті): https://github.com/fastapi/fastapi. 👀 + +Там ви можете вибрати «Releases only». + +Зробивши це, ви отримуватимете сповіщення (на вашу електронну пошту) щоразу, коли вийде новий реліз (нова версія) **FastAPI** з виправленнями помилок і новими можливостями. + +## Зв'яжіться з автором { #connect-with-the-author } + +Ви можете зв'язатися зі мною (Sebastián Ramírez / `tiangolo`), автором. + +Ви можете: + +* Стежити за мною на **GitHub**. + * Подивитися інші Open Source-проєкти, які я створив і які можуть вам допомогти. + * Стежити, щоб бачити, коли я створюю новий Open Source-проєкт. +* Стежити за мною в **X (Twitter)** або Mastodon. + * Розкажіть мені, як ви використовуєте FastAPI (мені дуже приємно це чути). + * Дізнаватися, коли я роблю оголошення або випускаю нові інструменти. + * Також ви можете стежити за @fastapi в X (Twitter) (окремий акаунт). +* Стежити за мною в **LinkedIn**. + * Дізнаватися, коли я роблю оголошення або випускаю нові інструменти (хоча X (Twitter) я використовую частіше 🤷‍♂). +* Читати, що я пишу (або стежити за мною) на **Dev.to** або **Medium**. + * Читати інші ідеї, статті та про інструменти, які я створив. + * Стежити, щоб читати нове, коли я щось публікую. + +## Твітніть про **FastAPI** { #tweet-about-fastapi } + +Твітніть про **FastAPI** і дайте мені та іншим знати, чому він вам подобається. 🎉 + +Мені дуже подобається дізнаватися, як використовують **FastAPI**, що вам у ньому сподобалося, у якому проєкті/компанії ви його застосовуєте тощо. + +## Голосуйте за FastAPI { #vote-for-fastapi } + +* Проголосуйте за **FastAPI** на Slant. +* Проголосуйте за **FastAPI** на AlternativeTo. +* Повідомте, що ви використовуєте **FastAPI**, на StackShare. + +## Допомагайте іншим з питаннями на GitHub { #help-others-with-questions-in-github } + +Ви можете спробувати допомагати іншим з їхніми питаннями у: + +* GitHub Discussions +* GitHub Issues + +У багатьох випадках ви вже можете знати відповідь на ці питання. 🤓 + +Якщо ви багато допомагаєте людям із їхніми питаннями, ви станете офіційним [Експертом FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. 🎉 + +Пам'ятайте, найважливіше: намагайтеся бути добрими. Люди приходять зі своєю фрустрацією і часто питають не найкращим чином, але постарайтеся бути якомога доброзичливішими. 🤗 + +Ідея в тому, щоб спільнота **FastAPI** була доброзичливою та гостинною. Водночас не терпіть булінг чи неповажну поведінку щодо інших. Ми маємо піклуватися одне про одного. + +--- + +Ось як допомагати іншим із питаннями (у discussions або issues): + +### Зрозумійте питання { #understand-the-question } + +* Перевірте, чи розумієте ви **мету** та варіант використання людини, яка питає. + +* Потім перевірте, чи **чітке** саме питання (переважна більшість — це питання). + +* Часто запит стосується уявного рішення користувача, але може бути **краще**. Якщо ви краще зрозумієте проблему та варіант використання, зможете запропонувати кращу **альтернативу**. + +* Якщо ви не розумієте питання, попросіть більше **подробиць**. + +### Відтворіть проблему { #reproduce-the-problem } + +У більшості випадків і запитань мова йде про **оригінальний код** людини. + +Часто вони наводять лише фрагмент коду, але цього недостатньо, щоб **відтворити проблему**. + +* Ви можете попросити надати мінімальний, відтворюваний приклад, який ви зможете **скопіювати-вставити** і запустити локально, щоб побачити ту саму помилку або поведінку, яку бачать вони, або краще зрозуміти їхній варіант використання. + +* Якщо ви дуже щедрі, можете спробувати **створити такий приклад** самостійно, лише на основі опису проблеми. Просто майте на увазі, що це може зайняти багато часу, і краще спочатку попросити їх уточнити проблему. + +### Запропонуйте рішення { #suggest-solutions } + +* Після того як ви змогли зрозуміти питання, дайте можливу **відповідь**. + +* Часто краще зрозуміти їхню **первинну проблему або варіант використання**, адже може бути кращий спосіб її вирішити, ніж те, що вони намагаються зробити. + +### Попросіть закрити { #ask-to-close } + +Якщо вони відповіли, дуже ймовірно, що ви розв'язали їхню проблему. Вітаю, **ви герой**! 🦸 + +* Тепер, якщо це вирішило їхню проблему, ви можете попросити: + + * У GitHub Discussions: позначити коментар як **відповідь**. + * У GitHub Issues: **закрити** issue. + +## Стежте за репозиторієм GitHub { #watch-the-github-repository } + +Ви можете «спостерігати» за FastAPI на GitHub (натиснувши кнопку «watch» у верхньому правому куті): https://github.com/fastapi/fastapi. 👀 + +Якщо вибрати «Watching» замість «Releases only», ви отримуватимете сповіщення, коли хтось створює нове issue або питання. Ви також можете вказати, що хочете отримувати сповіщення лише про нові issues, або discussions, або PR тощо. + +Тоді ви зможете спробувати допомогти їм вирішити ці питання. + +## Ставте питання { #ask-questions } + +Ви можете створити нове питання у репозиторії GitHub, наприклад, щоб: + +* Поставити **питання** або запитати про **проблему**. +* Запропонувати нову **можливість**. + +**Примітка**: якщо ви це зробите, я попрошу вас також допомагати іншим. 😉 + +## Переглядайте запити на витяг { #review-pull-requests } + +Ви можете допомогти мені переглядати запити на витяг інших. + +І знову, будь ласка, намагайтеся бути якомога доброзичливішими. 🤗 + +--- + +Ось що слід пам'ятати і як переглядати запит на витяг: + +### Зрозумійте проблему { #understand-the-problem } + +* Спочатку переконайтеся, що ви **розумієте проблему**, яку намагається вирішити запит на витяг. Може бути довша дискусія у GitHub Discussion або issue. + +* Також є велика ймовірність, що запит на витяг насправді не потрібен, бо проблему можна розв'язати **іншим способом**. Тоді ви можете про це запропонувати або запитати. + +### Не переймайтеся стилем { #dont-worry-about-style } + +* Не надто хвилюйтеся про стиль повідомлень фіксацій, я зроблю squash and merge, налаштувавши фіксацію вручну. + +* Також не переймайтеся правилами стилю, вже є автоматизовані інструменти, що це перевіряють. + +А якщо будуть інші вимоги щодо стилю чи узгодженості, я попрошу про це безпосередньо або додам зверху фіксації з потрібними змінами. + +### Перевірте код { #check-the-code } + +* Перегляньте та прочитайте код, оцініть, чи він має сенс, **запустіть його локально** і перевірте, чи справді він розв'язує проблему. + +* Потім залиште **коментар**, що ви це зробили, так я знатиму, що ви справді перевірили. + +/// info | Інформація + +На жаль, я не можу просто довіряти PR, які мають кілька схвалень. + +Бувало не раз, що PR мали 3, 5 або більше схвалень, мабуть тому, що опис привабливий, але коли я перевіряв PR, вони виявлялися зламаними, мали помилку або не розв'язували заявлену проблему. 😅 + +Тож дуже важливо, щоб ви справді прочитали і запустили код та повідомили в коментарях, що ви це зробили. 🤓 + +/// + +* Якщо PR можна якось спростити, ви можете про це попросити, але немає потреби бути надто прискіпливими, адже може бути багато суб'єктивних точок зору (і в мене також є своя 🙈), тож краще зосередитися на фундаментальних речах. + +### Тести { #tests } + +* Допоможіть перевірити, що PR має **тести**. + +* Переконайтеся, що тести **падають** до PR. 🚨 + +* Потім перевірте, що тести **проходять** після PR. ✅ + +* Багато PRів не мають тестів, ви можете **нагадати** додати тести або навіть **запропонувати** деякі тести самі. Це одна з речей, яка найбільше забирає часу, і ви дуже допоможете. + +* Потім також прокоментуйте, що саме ви спробували, так я знатиму, що ви це перевірили. 🤓 + +## Створіть запит на витяг { #create-a-pull-request } + +Ви можете [зробити внесок](contributing.md){.internal-link target=_blank} у вихідний код із запитами на витяг, наприклад: + +* Щоб виправити описку, знайдену в документації. +* Щоб поділитися статтею, відео або подкастом про FastAPI, який ви створили або знайшли, відредагувавши цей файл. + * Обов'язково додайте ваше посилання на початок відповідного розділу. +* Щоб допомогти [перекласти документацію](contributing.md#translations){.internal-link target=_blank} вашою мовою. + * Ви також можете допомогти з переглядом перекладів, створених іншими. +* Щоб запропонувати нові розділи документації. +* Щоб виправити наявну проблему/помилку. + * Обов'язково додайте тести. +* Щоб додати нову можливість. + * Обов'язково додайте тести. + * Обов'язково додайте документацію, якщо це доречно. + +## Допоможіть підтримувати FastAPI { #help-maintain-fastapi } + +Допоможіть мені підтримувати **FastAPI**! 🤓 + +Роботи багато, і більшу її частину можете зробити **ВИ**. + +Основні завдання, які ви можете виконувати вже зараз: + +* [Допомагайте іншим з питаннями на GitHub](#help-others-with-questions-in-github){.internal-link target=_blank} (див. розділ вище). +* [Переглядайте запити на витяг](#review-pull-requests){.internal-link target=_blank} (див. розділ вище). + +Ці два завдання **найбільше споживають час**. Це основна робота з підтримки FastAPI. + +Якщо ви можете допомогти мені з цим, **ви допомагаєте підтримувати FastAPI** і гарантуєте, що він **рухатиметься швидше й краще**. 🚀 + +## Долучайтеся до чату { #join-the-chat } + +Долучайтеся до 👥 серверу чату Discord 👥 і спілкуйтеся з іншими в спільноті FastAPI. + +/// tip | Порада + +Для запитань ставте їх у GitHub Discussions, там значно вища ймовірність, що вам допоможуть [Експерти FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}. + +Використовуйте чат лише для інших загальних розмов. + +/// + +### Не використовуйте чат для запитань { #dont-use-the-chat-for-questions } + +Майте на увазі, що оскільки чати дозволяють більше «вільної розмови», легко ставити надто загальні питання, на які складніше відповісти, тож ви можете не отримати відповідей. + +У GitHub шаблон підкаже вам, як написати правильне питання, щоб ви легше отримали хорошу відповідь, або навіть вирішили проблему самостійно ще до запиту. І в GitHub я можу гарантувати, що завжди на все відповім, навіть якщо це займе трохи часу. Особисто я не можу робити це в чатах. 😅 + +Розмови в чатах також не так просто шукати, як у GitHub, тож питання та відповіді можуть загубитися. І лише ті, що в GitHub, зараховуються, щоб стати [Експертом FastAPI](fastapi-people.md#fastapi-experts){.internal-link target=_blank}, тож швидше за все ви отримаєте більше уваги саме в GitHub. + +З іншого боку, у чатах є тисячі користувачів, тож дуже ймовірно, що ви майже завжди знайдете там співрозмовника. 😄 + +## Спонсоруйте автора { #sponsor-the-author } + +Якщо ваш **продукт/компанія** залежить від **FastAPI** або пов'язана з ним і ви хочете охопити його користувачів, ви можете спонсорувати автора (мене) через GitHub sponsors. Залежно від рівня ви можете отримати додаткові переваги, наприклад значок у документації. 🎁 + +--- + +Дякую! 🚀 diff --git a/docs/uk/docs/history-design-future.md b/docs/uk/docs/history-design-future.md new file mode 100644 index 000000000..1897807c8 --- /dev/null +++ b/docs/uk/docs/history-design-future.md @@ -0,0 +1,79 @@ +# Історія, проєктування і майбутнє { #history-design-and-future } + +Деякий час тому користувач **FastAPI** запитав: + +> Яка історія цього проєкту? Здається, він нізвідки за кілька тижнів став чудовим [...] + +Ось трохи цієї історії. + +## Альтернативи { #alternatives } + +Я створював API зі складними вимогами протягом кількох років (машинне навчання, розподілені системи, асинхронні завдання, бази даних NoSQL тощо), очолюючи кілька команд розробників. + +У межах цього мені довелося досліджувати, тестувати й використовувати багато альтернатив. + +Історія **FastAPI** значною мірою - це історія його попередників. + +Як сказано в розділі [Альтернативи](alternatives.md){.internal-link target=_blank}: + +
+ +**FastAPI** не існував би без попередньої роботи інших. + +Було створено багато інструментів, які надихнули на його створення. + +Я роками уникав створення нового фреймворку. Спочатку я намагався вирішити всі можливості, які покриває **FastAPI**, використовуючи різні фреймворки, плагіни та інструменти. + +Але в певний момент не залишилося іншого варіанту, окрім створити щось, що надає всі ці можливості, узявши найкращі ідеї з попередніх інструментів і поєднавши їх якнайкраще, використовуючи можливості самої мови, яких раніше взагалі не було (підказки типів Python 3.6+). + +
+ +## Дослідження { #investigation } + +Використовуючи всі попередні альтернативи, я мав змогу повчитися в кожної, узяти ідеї й поєднати їх якнайкраще для себе та команд розробників, з якими працював. + +Наприклад, було очевидно, що в ідеалі все має ґрунтуватися на стандартних підказках типів Python. + +Також найкращим підходом було використовувати вже наявні стандарти. + +Тож, ще до початку написання коду **FastAPI**, я провів кілька місяців, вивчаючи специфікації OpenAPI, Схеми JSON, OAuth2 тощо. Розуміючи їхні взаємозв'язки, перетини та відмінності. + +## Проєктування { #design } + +Потім я приділив час проєктуванню «API» для розробника, яке я хотів мати як користувач (як розробник, що використовує FastAPI). + +Я протестував кілька ідей у найпопулярніших Python-редакторах: PyCharm, VS Code, редакторах на основі Jedi. + +За даними Python Developer Survey, це охоплює близько 80% користувачів. + +Це означає, що **FastAPI** спеціально тестувався з редакторами, якими користуються 80% розробників Python. І оскільки більшість інших редакторів працюють подібно, усі ці переваги мають працювати практично у всіх редакторах. + +Так я зміг знайти найкращі способи максимально зменшити дублювання коду, забезпечити автодоповнення всюди, перевірки типів і помилок тощо. + +І все це так, щоб надати найкращий досвід розробки для всіх розробників. + +## Вимоги { #requirements } + +Після перевірки кількох альтернатив я вирішив використовувати **Pydantic** через його переваги. + +Потім я зробив внески до нього, щоб зробити його повністю сумісним із Схемою JSON, додати підтримку різних способів оголошення обмежень і поліпшити підтримку редакторів (перевірки типів, автодоповнення) на основі тестів у кількох редакторах. + +Під час розробки я також зробив внески до **Starlette**, іншої ключової залежності. + +## Розробка { #development } + +Коли я взявся безпосередньо за створення **FastAPI**, більшість складових уже були на місцях: дизайн визначено, вимоги та інструменти підготовлено, знання про стандарти й специфікації - чіткі та свіжі. + +## Майбутнє { #future } + +На цей момент уже зрозуміло, що **FastAPI** зі своїми ідеями корисний для багатьох. + +Його обирають замість попередніх альтернатив, бо він краще відповідає багатьом сценаріям використання. + +Багато розробників і команд уже залежать від **FastAPI** у своїх проєктах (включно зі мною та моєю командою). + +Але попереду ще багато покращень і можливостей. + +**FastAPI** має велике майбутнє. + +І [ваша допомога](help-fastapi.md){.internal-link target=_blank} дуже цінується. diff --git a/docs/uk/docs/how-to/authentication-error-status-code.md b/docs/uk/docs/how-to/authentication-error-status-code.md new file mode 100644 index 000000000..58016f261 --- /dev/null +++ b/docs/uk/docs/how-to/authentication-error-status-code.md @@ -0,0 +1,17 @@ +# Використовуйте старі коди статусу помилки автентифікації 403 { #use-old-403-authentication-error-status-codes } + +До версії FastAPI `0.122.0`, коли інтегровані засоби безпеки повертали клієнту помилку після невдалої автентифікації, вони використовували HTTP код статусу `403 Forbidden`. + +Починаючи з версії FastAPI `0.122.0`, вони використовують більш доречний HTTP код статусу `401 Unauthorized` і повертають змістовний заголовок `WWW-Authenticate` у відповіді, відповідно до специфікацій HTTP, RFC 7235, RFC 9110. + +Але якщо з якоїсь причини ваші клієнти залежать від старої поведінки, ви можете повернутися до неї, переписавши метод `make_not_authenticated_error` у ваших класах безпеки. + +Наприклад, ви можете створити підклас `HTTPBearer`, який повертатиме помилку `403 Forbidden` замість типового `401 Unauthorized`: + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} + +/// tip | Порада + +Зверніть увагу, що функція повертає екземпляр винятку, вона не породжує його. Породження відбувається в іншій частині внутрішнього коду. + +/// diff --git a/docs/uk/docs/how-to/conditional-openapi.md b/docs/uk/docs/how-to/conditional-openapi.md new file mode 100644 index 000000000..f8bbaa649 --- /dev/null +++ b/docs/uk/docs/how-to/conditional-openapi.md @@ -0,0 +1,56 @@ +# Умовний OpenAPI { #conditional-openapi } + +Якщо потрібно, ви можете використовувати налаштування і змінні оточення, щоб умовно налаштовувати OpenAPI залежно від середовища, а також повністю вимикати його. + +## Про безпеку, API та документацію { #about-security-apis-and-docs } + +Приховування інтерфейсів документації у продукційному середовищі *не має* бути способом захисту вашого API. + +Це не додає жодної додаткової безпеки вашому API, *операції шляху* й надалі будуть доступні там, де вони є. + +Якщо у вашому коді є вразливість, вона залишиться. + +Приховування документації лише ускладнює розуміння того, як взаємодіяти з вашим API, і може ускладнити для вас його налагодження у продакшні. Це можна вважати просто формою Безпека через неясність. + +Якщо ви хочете захистити ваш API, є кілька кращих дій, які ви можете зробити, наприклад: + +- Переконайтеся, що у вас добре визначені моделі Pydantic для тіл запитів і відповідей. +- Налаштуйте потрібні дозволи та ролі за допомогою залежностей. +- Ніколи не зберігайте паролі у відкритому вигляді, лише хеші паролів. +- Реалізуйте та використовуйте відомі криптографічні інструменти, як-от pwdlib і токени JWT. +- Додайте більш детальний контроль дозволів із областями OAuth2 там, де це потрібно. +- ...тощо. + +Втім, у вас може бути дуже специфічний випадок використання, коли справді потрібно вимкнути документацію API для певного середовища (наприклад, для продакшну) або залежно від конфігурацій зі змінних оточення. + +## Умовний OpenAPI з налаштувань і змінних оточення { #conditional-openapi-from-settings-and-env-vars } + +Ви можете легко використати ті самі налаштування Pydantic, щоб налаштувати згенерований OpenAPI та інтерфейси документації. + +Наприклад: + +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} + +Тут ми оголошуємо налаштування `openapi_url` з тим самим значенням за замовчуванням `"/openapi.json"`. + +Потім ми використовуємо його під час створення застосунку `FastAPI`. + +Далі ви можете вимкнути OpenAPI (включно з інтерфейсами документації), встановивши змінну оточення `OPENAPI_URL` у пусту строку, наприклад: + +
+ +```console +$ OPENAPI_URL= uvicorn main:app + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Потім, якщо ви перейдете за URL `/openapi.json`, `/docs` або `/redoc`, ви просто отримаєте помилку `404 Not Found` на кшталт: + +```JSON +{ + "detail": "Not Found" +} +``` diff --git a/docs/uk/docs/how-to/configure-swagger-ui.md b/docs/uk/docs/how-to/configure-swagger-ui.md new file mode 100644 index 000000000..f8c4470df --- /dev/null +++ b/docs/uk/docs/how-to/configure-swagger-ui.md @@ -0,0 +1,70 @@ +# Налаштуйте Swagger UI { #configure-swagger-ui } + +Ви можете налаштувати додаткові параметри Swagger UI. + +Щоб їх налаштувати, передайте аргумент `swagger_ui_parameters` під час створення об’єкта додатка `FastAPI()` або до функції `get_swagger_ui_html()`. + +`swagger_ui_parameters` отримує словник із налаштуваннями, що передаються безпосередньо до Swagger UI. + +FastAPI перетворює ці налаштування на **JSON**, щоб зробити їх сумісними з JavaScript, оскільки саме це потрібно Swagger UI. + +## Вимкніть підсвітку синтаксису { #disable-syntax-highlighting } + +Наприклад, ви можете вимкнути підсвітку синтаксису в Swagger UI. + +Без змін у налаштуваннях підсвітка синтаксису увімкнена за замовчуванням: + + + +Але ви можете вимкнути її, встановивши `syntaxHighlight` у `False`: + +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} + +...після цього Swagger UI більше не показуватиме підсвітку синтаксису: + + + +## Змініть тему { #change-the-theme } + +Так само ви можете задати тему підсвітки синтаксису ключем `"syntaxHighlight.theme"` (зверніть увагу, що посередині є крапка): + +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} + +Це налаштування змінить колірну тему підсвітки синтаксису: + + + +## Змініть параметри Swagger UI за замовчуванням { #change-default-swagger-ui-parameters } + +FastAPI містить деякі параметри конфігурації за замовчуванням, що підходять для більшості випадків. + +Вони включають такі типові налаштування: + +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} + +Ви можете переписати будь-яке з них, задавши інше значення в аргументі `swagger_ui_parameters`. + +Наприклад, щоб вимкнути `deepLinking`, ви можете передати такі налаштування до `swagger_ui_parameters`: + +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} + +## Інші параметри Swagger UI { #other-swagger-ui-parameters } + +Щоб побачити всі можливі налаштування, які ви можете використовувати, прочитайте офіційну документацію щодо параметрів Swagger UI. + +## Налаштування лише для JavaScript { #javascript-only-settings } + +Swagger UI також дозволяє інші налаштування як об’єкти, що є тільки для **JavaScript** (наприклад, функції JavaScript). + +FastAPI також включає такі налаштування `presets`, що є лише для JavaScript: + +```JavaScript +presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIBundle.SwaggerUIStandalonePreset +] +``` + +Це об’єкти **JavaScript**, а не строки, тому ви не можете передати їх безпосередньо з коду Python. + +Якщо вам потрібно використати такі налаштування лише для JavaScript, скористайтеся одним із методів вище. Повністю перепишіть операцію шляху Swagger UI та вручну напишіть потрібний JavaScript. diff --git a/docs/uk/docs/how-to/custom-docs-ui-assets.md b/docs/uk/docs/how-to/custom-docs-ui-assets.md new file mode 100644 index 000000000..faea3ccc4 --- /dev/null +++ b/docs/uk/docs/how-to/custom-docs-ui-assets.md @@ -0,0 +1,185 @@ +# Користувацькі статичні ресурси інтерфейсу документації (самохостинг) { #custom-docs-ui-static-assets-self-hosting } + +Документація API використовує **Swagger UI** і **ReDoc**, і кожному з них потрібні файли JavaScript та CSS. + +Типово ці файли віддаються з CDN. + +Але це можна налаштувати: ви можете вказати конкретний CDN або віддавати файли самостійно. + +## Власний CDN для JavaScript і CSS { #custom-cdn-for-javascript-and-css } + +Припустімо, що ви хочете використовувати інший CDN, наприклад `https://unpkg.com/`. + +Це може бути корисно, якщо, наприклад, ви живете в країні, що обмежує деякі URL-адреси. + +### Вимкніть автоматичну документацію { #disable-the-automatic-docs } + +Перший крок - вимкнути автоматичну документацію, адже типово вона використовує стандартний CDN. + +Щоб їх вимкнути, встановіть їхні URL у `None` під час створення вашого застосунку `FastAPI`: + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} + +### Додайте користувацьку документацію { #include-the-custom-docs } + +Тепер ви можете створити *операції шляху* для користувацької документації. + +Ви можете перевикористати внутрішні функції FastAPI для створення HTML-сторінок документації і передати їм потрібні аргументи: + +- `openapi_url`: URL, за яким HTML-сторінка документації зможе отримати схему OpenAPI для вашого API. Тут можна використати атрибут `app.openapi_url`. +- `title`: заголовок вашого API. +- `oauth2_redirect_url`: тут можна використати `app.swagger_ui_oauth2_redirect_url`, щоб узяти значення за замовчуванням. +- `swagger_js_url`: URL, за яким HTML для Swagger UI зможе отримати файл **JavaScript**. Це URL вашого користувацького CDN. +- `swagger_css_url`: URL, за яким HTML для Swagger UI зможе отримати файл **CSS**. Це URL вашого користувацького CDN. + +Аналогічно для ReDoc... + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} + +/// tip | Порада + +*Операція шляху* для `swagger_ui_redirect` - це допоміжний маршрут, коли ви використовуєте OAuth2. + +Якщо ви інтегруєте ваш API з провайдером OAuth2, ви зможете автентифікуватися і повернутися до документації API з отриманими обліковими даними. І взаємодіяти з ним, використовуючи реальну автентифікацію OAuth2. + +Swagger UI впорається з цим «за лаштунками», але йому потрібен цей «redirect» хелпер. + +/// + +### Створіть операцію шляху для перевірки { #create-a-path-operation-to-test-it } + +Тепер, щоб перевірити, що все працює, створіть *операцію шляху*: + +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} + +### Перевірте { #test-it } + +Тепер ви маєте змогу відкрити документацію за http://127.0.0.1:8000/docs і перезавантажити сторінку, вона завантажить ці ресурси з нового CDN. + +## Самохостинг JavaScript і CSS для документації { #self-hosting-javascript-and-css-for-docs } + +Самохостинг JavaScript і CSS може бути корисним, якщо, наприклад, ваш застосунок має працювати офлайн, без доступу до Інтернету, або в локальній мережі. + +Тут ви побачите, як віддавати ці файли самостійно, у тому самому застосунку FastAPI, і налаштувати документацію на їх використання. + +### Структура файлів проєкту { #project-file-structure } + +Припустімо, що структура файлів вашого проєкту виглядає так: + +``` +. +├── app +│ ├── __init__.py +│ ├── main.py +``` + +Тепер створіть каталог для збереження цих статичних файлів. + +Нова структура файлів може виглядати так: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static/ +``` + +### Завантажте файли { #download-the-files } + +Завантажте статичні файли, потрібні для документації, і помістіть їх у каталог `static/`. + +Ви, ймовірно, можете натиснути правою кнопкою на кожному посиланні і вибрати опцію на кшталт «Зберегти посилання як...». + +**Swagger UI** використовує файли: + +- `swagger-ui-bundle.js` +- `swagger-ui.css` + +А **ReDoc** використовує файл: + +- `redoc.standalone.js` + +Після цього ваша структура файлів може виглядати так: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +└── static + ├── redoc.standalone.js + ├── swagger-ui-bundle.js + └── swagger-ui.css +``` + +### Обслуговуйте статичні файли { #serve-the-static-files } + +- Імпортуйте `StaticFiles`. +- Змонтуйте екземпляр `StaticFiles()` у певному шляху. + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} + +### Перевірте статичні файли { #test-the-static-files } + +Запустіть ваш застосунок і перейдіть до http://127.0.0.1:8000/static/redoc.standalone.js. + +Ви маєте побачити дуже довгий файл JavaScript для **ReDoc**. + +Він може починатися приблизно так: + +```JavaScript +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): +... +``` + +Це підтверджує, що ви можете віддавати статичні файли з вашого застосунку і що ви розмістили статичні файли для документації у правильному місці. + +Тепер ми можемо налаштувати застосунок на використання цих статичних файлів для документації. + +### Вимкніть автоматичну документацію для статичних файлів { #disable-the-automatic-docs-for-static-files } + +Як і при використанні користувацького CDN, першим кроком є вимкнення автоматичної документації, оскільки типово вона використовує CDN. + +Щоб їх вимкнути, встановіть їхні URL у `None` під час створення вашого застосунку `FastAPI`: + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} + +### Додайте користувацьку документацію для статичних файлів { #include-the-custom-docs-for-static-files } + +Аналогічно користувацькому CDN, тепер ви можете створити *операції шляху* для користувацької документації. + +Знову ж, ви можете перевикористати внутрішні функції FastAPI для створення HTML-сторінок документації і передати їм потрібні аргументи: + +- `openapi_url`: URL, за яким HTML-сторінка документації зможе отримати схему OpenAPI для вашого API. Тут можна використати атрибут `app.openapi_url`. +- `title`: заголовок вашого API. +- `oauth2_redirect_url`: тут можна використати `app.swagger_ui_oauth2_redirect_url`, щоб узяти значення за замовчуванням. +- `swagger_js_url`: URL, за яким HTML для Swagger UI зможе отримати файл **JavaScript**. **Це той файл, який тепер віддає ваш власний застосунок**. +- `swagger_css_url`: URL, за яким HTML для Swagger UI зможе отримати файл **CSS**. **Це той файл, який тепер віддає ваш власний застосунок**. + +Аналогічно для ReDoc... + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} + +/// tip | Порада + +*Операція шляху* для `swagger_ui_redirect` - це допоміжний маршрут, коли ви використовуєте OAuth2. + +Якщо ви інтегруєте ваш API з провайдером OAuth2, ви зможете автентифікуватися і повернутися до документації API з отриманими обліковими даними. І взаємодіяти з ним, використовуючи реальну автентифікацію OAuth2. + +Swagger UI впорається з цим «за лаштунками», але йому потрібен цей «redirect» хелпер. + +/// + +### Створіть операцію шляху для перевірки статичних файлів { #create-a-path-operation-to-test-static-files } + +Тепер, щоб перевірити, що все працює, створіть *операцію шляху*: + +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} + +### Перевірте UI зі статичними файлами { #test-static-files-ui } + +Тепер ви маєте змогу вимкнути WiFi, відкрити документацію за http://127.0.0.1:8000/docs і перезавантажити сторінку. + +І навіть без Інтернету ви зможете побачити документацію для вашого API і взаємодіяти з ним. diff --git a/docs/uk/docs/how-to/custom-request-and-route.md b/docs/uk/docs/how-to/custom-request-and-route.md new file mode 100644 index 000000000..9f21da7a8 --- /dev/null +++ b/docs/uk/docs/how-to/custom-request-and-route.md @@ -0,0 +1,109 @@ +# Користувацькі класи Request та APIRoute { #custom-request-and-apiroute-class } + +У деяких випадках ви можете захотіти перевизначити логіку, яку використовують класи `Request` та `APIRoute`. + +Зокрема, це може бути доброю альтернативою логіці в проміжному програмному забезпеченні. + +Наприклад, якщо потрібно прочитати або змінити тіло запиту до того, як його обробить ваш застосунок. + +/// danger | Обережно + +Це «просунута» можливість. + +Якщо ви тільки починаєте працювати з **FastAPI**, можливо, варто пропустити цей розділ. + +/// + +## Випадки використання { #use-cases } + +Деякі варіанти використання: + +- Перетворення не-JSON тіл запитів на JSON (наприклад, `msgpack`). +- Розпакування тіл запитів, стиснених gzip. +- Автоматичне логування всіх тіл запитів. + +## Обробка користувацьких кодувань тіла запиту { #handling-custom-request-body-encodings } + +Розгляньмо, як використати користувацький підклас `Request` для розпакування gzip-запитів. + +А також підклас `APIRoute`, щоб застосувати цей користувацький клас запиту. + +### Створіть користувацький клас `GzipRequest` { #create-a-custom-gziprequest-class } + +/// tip | Порада + +Це навчальний приклад, щоб продемонструвати принцип роботи. Якщо вам потрібна підтримка Gzip, скористайтеся вбудованим [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}. + +/// + +Спочатку створимо клас `GzipRequest`, який перевизначить метод `Request.body()`, щоб розпаковувати тіло за наявності відповідного заголовка. + +Якщо в заголовку немає `gzip`, він не намагатиметься розпаковувати тіло. + +Таким чином один і той самий клас маршруту зможе обробляти як стиснені gzip, так і нестиснені запити. + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} + +### Створіть користувацький клас `GzipRoute` { #create-a-custom-gziproute-class } + +Далі створимо користувацький підклас `fastapi.routing.APIRoute`, який використовуватиме `GzipRequest`. + +Цього разу він перевизначить метод `APIRoute.get_route_handler()`. + +Цей метод повертає функцію. І саме ця функція прийме запит і поверне відповідь. + +Тут ми використовуємо її, щоб створити `GzipRequest` з початкового запиту. + +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} + +/// note | Технічні деталі + +У `Request` є атрибут `request.scope` - це просто Python `dict`, що містить метадані, пов'язані із запитом. + +Також `Request` має `request.receive` - це функція для «отримання» тіла запиту. + +`scope` `dict` і функція `receive` є частиною специфікації ASGI. + +І саме ці дві сутності - `scope` та `receive` - потрібні для створення нового екземпляра `Request`. + +Щоб дізнатися більше про `Request`, перегляньте документацію Starlette про запити. + +/// + +Єдине, що робить інакше функція, повернена `GzipRequest.get_route_handler`, - перетворює `Request` на `GzipRequest`. + +Завдяки цьому наш `GzipRequest` подбає про розпакування даних (за потреби) перед передаванням їх у наші *операції шляху*. + +Після цього вся логіка обробки залишається тією самою. + +А завдяки змінам у `GzipRequest.body` тіло запиту за потреби буде автоматично розпаковане під час завантаження **FastAPI**. + +## Доступ до тіла запиту в обробнику виключень { #accessing-the-request-body-in-an-exception-handler } + +/// tip | Порада + +Щоб розв’язати це саме завдання, скоріш за все, простіше використати `body` у користувацькому обробнику `RequestValidationError` ([Обробка помилок](../tutorial/handling-errors.md#use-the-requestvalidationerror-body){.internal-link target=_blank}). + +Але цей приклад усе ще корисний і показує, як взаємодіяти з внутрішніми компонентами. + +/// + +Ми також можемо скористатися цим підходом, щоб отримати доступ до тіла запиту в обробнику виключень. + +Усе, що потрібно, - обробити запит усередині блоку `try`/`except`: + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} + +Якщо станеться виключення, екземпляр `Request` усе ще буде у видимості, тож ми зможемо прочитати й використати тіло запиту під час обробки помилки: + +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} + +## Користувацький клас `APIRoute` у маршрутизаторі { #custom-apiroute-class-in-a-router } + +Можна також встановити параметр `route_class` у `APIRouter`: + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} + +У цьому прикладі *операції шляху* в `router` використовуватимуть користувацький клас `TimedRoute` і матимуть додатковий заголовок відповіді `X-Response-Time` із часом, витраченим на формування відповіді: + +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} diff --git a/docs/uk/docs/how-to/extending-openapi.md b/docs/uk/docs/how-to/extending-openapi.md new file mode 100644 index 000000000..1597cbc76 --- /dev/null +++ b/docs/uk/docs/how-to/extending-openapi.md @@ -0,0 +1,80 @@ +# Розширення OpenAPI { #extending-openapi } + +У деяких випадках вам може знадобитися змінити згенеровану схему OpenAPI. + +У цьому розділі ви побачите як це зробити. + +## Звичайний процес { #the-normal-process } + +Звичайний (типовий) процес такий. + +Застосунок `FastAPI` (екземпляр) має метод `.openapi()`, який має повертати схему OpenAPI. + +Під час створення об'єкта застосунку реєструється *операція шляху* для `/openapi.json` (або для того значення, яке ви встановили в `openapi_url`). + +Вона просто повертає відповідь JSON з результатом методу `.openapi()` застосунку. + +Типово метод `.openapi()` перевіряє властивість `.openapi_schema`, і якщо там вже є дані, повертає їх. + +Якщо ні, він генерує їх за допомогою утилітарної функції `fastapi.openapi.utils.get_openapi`. + +Функція `get_openapi()` приймає такі параметри: + +- `title`: Заголовок OpenAPI, показується в документації. +- `version`: Версія вашого API, напр. `2.5.0`. +- `openapi_version`: Версія специфікації OpenAPI, що використовується. Типово остання: `3.1.0`. +- `summary`: Короткий підсумок API. +- `description`: Опис вашого API; може містити markdown і буде показаний у документації. +- `routes`: Список маршрутів, це кожна з зареєстрованих *операцій шляху*. Їх беруть з `app.routes`. + +/// info | Інформація + +Параметр `summary` доступний в OpenAPI 3.1.0 і вище, підтримується FastAPI 0.99.0 і вище. + +/// + +## Переписування типових значень { #overriding-the-defaults } + +Використовуючи наведене вище, ви можете скористатися тією ж утилітарною функцією для генерації схеми OpenAPI і переписати потрібні частини. + +Наприклад, додаймо розширення OpenAPI для ReDoc для додавання власного логотипа. + +### Звичайний **FastAPI** { #normal-fastapi } + +Спочатку напишіть ваш застосунок **FastAPI** як зазвичай: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *} + +### Згенерувати схему OpenAPI { #generate-the-openapi-schema } + +Далі використайте ту ж утилітарну функцію для генерації схеми OpenAPI всередині функції `custom_openapi()`: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *} + +### Змінити схему OpenAPI { #modify-the-openapi-schema } + +Тепер можна додати розширення ReDoc, додавши власний `x-logo` до «об'єкта» `info` у схемі OpenAPI: + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *} + +### Кешувати схему OpenAPI { #cache-the-openapi-schema } + +Ви можете використовувати властивість `.openapi_schema` як «кеш» для збереження згенерованої схеми. + +Так вашому застосунку не доведеться щоразу генерувати схему, коли користувач відкриває документацію вашого API. + +Вона буде згенерована лише один раз, а потім та сама закешована схема використовуватиметься для наступних запитів. + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *} + +### Переписати метод { #override-the-method } + +Тепер ви можете замінити метод `.openapi()` вашою новою функцією. + +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *} + +### Перевірте { #check-it } + +Коли ви перейдете за адресою http://127.0.0.1:8000/redoc, побачите, що використовується ваш власний логотип (у цьому прикладі логотип **FastAPI**): + + diff --git a/docs/uk/docs/how-to/general.md b/docs/uk/docs/how-to/general.md new file mode 100644 index 000000000..f33ae195c --- /dev/null +++ b/docs/uk/docs/how-to/general.md @@ -0,0 +1,39 @@ +# Загальне - Як зробити - Рецепти { #general-how-to-recipes } + +Нижче наведено кілька вказівок до інших розділів документації для загальних або частих питань. + +## Фільтрування даних - Безпека { #filter-data-security } + +Щоб гарантувати, що ви не повертаєте більше даних, ніж слід, прочитайте документацію [Навчальний посібник - Модель відповіді - Тип повернення](../tutorial/response-model.md){.internal-link target=_blank}. + +## Мітки документації - OpenAPI { #documentation-tags-openapi } + +Щоб додати мітки до ваших *операцій шляху* та згрупувати їх в інтерфейсі документації, прочитайте документацію [Навчальний посібник - Налаштування операції шляху - Мітки](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. + +## Короткий опис і опис - OpenAPI { #documentation-summary-and-description-openapi } + +Щоб додати короткий опис і опис до ваших *операцій шляху* і показати їх в інтерфейсі документації, прочитайте документацію [Навчальний посібник - Налаштування операції шляху - Короткий опис і опис](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. + +## Опис відповіді в документації - OpenAPI { #documentation-response-description-openapi } + +Щоб визначити опис відповіді, що відображається в інтерфейсі документації, прочитайте документацію [Навчальний посібник - Налаштування операції шляху - Опис відповіді](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. + +## Позначити застарілою *операцію шляху* - OpenAPI { #documentation-deprecate-a-path-operation-openapi } + +Щоб позначити *операцію шляху* як застарілу і показати це в інтерфейсі документації, прочитайте документацію [Навчальний посібник - Налаштування операції шляху - Позначення як застаріле](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. + +## Перетворити будь-які дані на сумісні з JSON { #convert-any-data-to-json-compatible } + +Щоб перетворити будь-які дані на сумісні з JSON, прочитайте документацію [Навчальний посібник - Кодувальник, сумісний з JSON](../tutorial/encoder.md){.internal-link target=_blank}. + +## Метадані OpenAPI - Документація { #openapi-metadata-docs } + +Щоб додати метадані до вашої схеми OpenAPI, зокрема ліцензію, версію, контактні дані тощо, прочитайте документацію [Навчальний посібник - Метадані та URL документації](../tutorial/metadata.md){.internal-link target=_blank}. + +## Власний URL OpenAPI { #openapi-custom-url } + +Щоб налаштувати URL OpenAPI (або прибрати його), прочитайте документацію [Навчальний посібник - Метадані та URL документації](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. + +## URL документації OpenAPI { #openapi-docs-urls } + +Щоб оновити URL, які використовуються для автоматично згенерованих інтерфейсів користувача документації, прочитайте документацію [Навчальний посібник - Метадані та URL документації](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. diff --git a/docs/uk/docs/how-to/graphql.md b/docs/uk/docs/how-to/graphql.md new file mode 100644 index 000000000..2d0e355ea --- /dev/null +++ b/docs/uk/docs/how-to/graphql.md @@ -0,0 +1,60 @@ +# GraphQL { #graphql } + +Оскільки FastAPI базується на стандарті ASGI, дуже просто інтегрувати будь-яку бібліотеку GraphQL, сумісну з ASGI. + +Ви можете поєднувати звичайні *операції шляху* FastAPI з GraphQL в одному застосунку. + +/// tip | Порада + +GraphQL розв’язує деякі дуже специфічні сценарії використання. + +Порівняно зі звичайними веб-API він має переваги та недоліки. + +Переконайтеся, що переваги для вашого випадку використання переважають недоліки. 🤓 + +/// + +## Бібліотеки GraphQL { #graphql-libraries } + +Ось деякі бібліотеки GraphQL з підтримкою ASGI. Ви можете використовувати їх із FastAPI: + +* Strawberry 🍓 + * З документацією для FastAPI +* Ariadne + * З документацією для FastAPI +* Tartiflette + * З Tartiflette ASGI для інтеграції з ASGI +* Graphene + * З starlette-graphene3 + +## GraphQL зі Strawberry { #graphql-with-strawberry } + +Якщо вам потрібен або ви хочете використовувати GraphQL, Strawberry - рекомендована бібліотека, адже її дизайн найближчий до дизайну FastAPI; усе базується на анотаціях типів. + +Залежно від вашого сценарію використання ви можете надати перевагу іншій бібліотеці, але якби ви запитали мене, я, ймовірно, порадив би спробувати Strawberry. + +Ось невеликий приклад того, як інтегрувати Strawberry з FastAPI: + +{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *} + +Більше про Strawberry ви можете дізнатися в документації Strawberry. + +І також документацію про Strawberry з FastAPI. + +## Застарілий `GraphQLApp` зі Starlette { #older-graphqlapp-from-starlette } + +Попередні версії Starlette містили клас `GraphQLApp` для інтеграції з Graphene. + +Його вилучено з Starlette як застарілий, але якщо у вас є код, що його використовував, ви можете легко мігрувати на starlette-graphene3, який покриває той самий сценарій використання та має майже ідентичний інтерфейс. + +/// tip | Порада + +Якщо вам потрібен GraphQL, я все ж рекомендую звернути увагу на Strawberry, адже він базується на анотаціях типів, а не на власних класах і типах. + +/// + +## Дізнайтеся більше { #learn-more } + +Ви можете дізнатися більше про GraphQL в офіційній документації GraphQL. + +Також ви можете почитати більше про кожну з цих бібліотек за наведеними посиланнями. diff --git a/docs/uk/docs/how-to/index.md b/docs/uk/docs/how-to/index.md new file mode 100644 index 000000000..ac2dd16eb --- /dev/null +++ b/docs/uk/docs/how-to/index.md @@ -0,0 +1,13 @@ +# Як зробити - Рецепти { #how-to-recipes } + +Тут ви побачите різні рецепти або керівництва «як зробити» з **різних тем**. + +Більшість із цих ідей є більш-менш **незалежними**, і в більшості випадків вам слід вивчати їх лише тоді, коли вони безпосередньо стосуються **вашого проєкту**. + +Якщо щось здається цікавим і корисним для вашого проєкту, сміливо перевірте це, інакше, ймовірно, просто пропустіть. + +/// tip | Порада + +Якщо ви хочете **вивчити FastAPI** у структурований спосіб (рекомендується), натомість прочитайте [Навчальний посібник - Посібник користувача](../tutorial/index.md){.internal-link target=_blank} розділ за розділом. + +/// diff --git a/docs/uk/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/uk/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md new file mode 100644 index 000000000..0f5d1c924 --- /dev/null +++ b/docs/uk/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -0,0 +1,135 @@ +# Перехід з Pydantic v1 на Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 } + +Якщо у вас стара програма FastAPI, можливо, ви використовуєте Pydantic версії 1. + +FastAPI версії 0.100.0 підтримував як Pydantic v1, так і v2. Використовувалася та версія, яку ви встановили. + +FastAPI версії 0.119.0 запровадив часткову підтримку Pydantic v1 всередині Pydantic v2 (як `pydantic.v1`), щоб спростити перехід на v2. + +FastAPI 0.126.0 припинив підтримку Pydantic v1, водночас ще певний час підтримував `pydantic.v1`. + +/// warning | Попередження + +Команда Pydantic припинила підтримку Pydantic v1 для останніх версій Python, починаючи з Python 3.14. + +Це стосується і `pydantic.v1`, який більше не підтримується в Python 3.14 і новіших. + +Якщо ви хочете використовувати найновіші можливості Python, вам потрібно переконатися, що ви використовуєте Pydantic v2. + +/// + +Якщо у вас стара програма FastAPI з Pydantic v1, нижче я покажу, як мігрувати на Pydantic v2, а також можливості FastAPI 0.119.0, які допоможуть з поступовою міграцією. + +## Офіційний посібник { #official-guide } + +У Pydantic є офіційний Посібник з міграції з v1 на v2. + +Там описано, що змінилося, як перевірки тепер стали коректнішими та суворішими, можливі застереження тощо. + +Прочитайте його, щоб краще зрозуміти зміни. + +## Тести { #tests } + +Переконайтеся, що у вашій програмі є [тести](../tutorial/testing.md){.internal-link target=_blank} і що ви запускаєте їх у системі безперервної інтеграції (CI). + +Так ви зможете виконати оновлення і впевнитися, що все працює як очікується. + +## `bump-pydantic` { #bump-pydantic } + +У багатьох випадках, якщо ви використовуєте звичайні моделі Pydantic без налаштувань, більшу частину процесу міграції з Pydantic v1 на Pydantic v2 можна автоматизувати. + +Ви можете скористатися `bump-pydantic` від тієї ж команди Pydantic. + +Цей інструмент допоможе автоматично змінити більшість коду, який потрібно змінити. + +Після цього запустіть тести й перевірте, чи все працює. Якщо так - ви все завершили. 😎 + +## Pydantic v1 у v2 { #pydantic-v1-in-v2 } + +Pydantic v2 містить усе з Pydantic v1 як підмодуль `pydantic.v1`. Але це більше не підтримується у версіях Python вище 3.13. + +Це означає, що ви можете встановити останню версію Pydantic v2 та імпортувати і використовувати старі компоненти Pydantic v1 з цього підмодуля, ніби у вас встановлено старий Pydantic v1. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} + +### Підтримка FastAPI для Pydantic v1 у v2 { #fastapi-support-for-pydantic-v1-in-v2 } + +Починаючи з FastAPI 0.119.0, також є часткова підтримка Pydantic v1 всередині Pydantic v2, щоб спростити перехід на v2. + +Тож ви можете оновити Pydantic до останньої версії 2 і змінити імпорти на використання підмодуля `pydantic.v1`, і в багатьох випадках усе просто запрацює. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} + +/// warning | Попередження + +Майте на увазі, що оскільки команда Pydantic більше не підтримує Pydantic v1 у нових версіях Python, починаючи з Python 3.14, використання `pydantic.v1` також не підтримується в Python 3.14 і новіших. + +/// + +### Pydantic v1 і v2 в одній програмі { #pydantic-v1-and-v2-on-the-same-app } + +Pydantic не підтримує ситуацію, коли модель Pydantic v2 має власні поля, визначені як моделі Pydantic v1, або навпаки. + +```mermaid +graph TB + subgraph "❌ Not Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V1Field["Pydantic v1 Model"] + end + subgraph V1["Pydantic v1 Model"] + V2Field["Pydantic v2 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +...але в одній програмі ви можете мати окремі моделі на Pydantic v1 і v2. + +```mermaid +graph TB + subgraph "✅ Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V2Field["Pydantic v2 Model"] + end + subgraph V1["Pydantic v1 Model"] + V1Field["Pydantic v1 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +У деяких випадках можна навіть використовувати моделі і Pydantic v1, і v2 в одній операції шляху у вашій програмі FastAPI: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} + +У наведеному вище прикладі вхідна модель - це модель Pydantic v1, а вихідна модель (визначена як `response_model=ItemV2`) - модель Pydantic v2. + +### Параметри Pydantic v1 { #pydantic-v1-parameters } + +Якщо вам потрібно використовувати деякі специфічні для FastAPI інструменти для параметрів, як-от `Body`, `Query`, `Form` тощо, з моделями Pydantic v1, ви можете імпортувати їх з `fastapi.temp_pydantic_v1_params`, поки завершуєте міграцію на Pydantic v2: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} + +### Покрокова міграція { #migrate-in-steps } + +/// tip | Порада + +Спершу спробуйте `bump-pydantic`: якщо ваші тести проходять і все працює - ви впоралися однією командою. ✨ + +/// + +Якщо `bump-pydantic` не підходить для вашого випадку, скористайтеся підтримкою одночасно Pydantic v1 і v2 в одній програмі, щоб виконати поступову міграцію на Pydantic v2. + +Спочатку ви можете оновити Pydantic до останньої версії 2 і змінити імпорти на `pydantic.v1` для всіх ваших моделей. + +Потім починайте переносити моделі з Pydantic v1 на v2 групами, поступовими кроками. 🚶 diff --git a/docs/uk/docs/how-to/separate-openapi-schemas.md b/docs/uk/docs/how-to/separate-openapi-schemas.md new file mode 100644 index 000000000..7e6fcbf5f --- /dev/null +++ b/docs/uk/docs/how-to/separate-openapi-schemas.md @@ -0,0 +1,101 @@ +# Окремі схеми OpenAPI для введення та виведення, чи ні { #separate-openapi-schemas-for-input-and-output-or-not } + +Відколи вийшов **Pydantic v2**, згенерований OpenAPI став трохи точнішим і більш коректним, ніж раніше. 😎 + +Насправді подекуди буде навіть **дві схеми JSON** в OpenAPI для тієї самої моделі Pydantic: для введення та для виведення - залежно від наявності значень за замовчуванням. + +Розгляньмо, як це працює, і як це змінити за потреби. + +## Моделі Pydantic для введення та виведення { #pydantic-models-for-input-and-output } + +Припустімо, у вас є модель Pydantic зі значеннями за замовчуванням, наприклад: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} + +### Модель для введення { #model-for-input } + +Якщо ви використовуєте цю модель як введення, як тут: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:15] hl[14] *} + +…тоді поле `description` не буде обов'язковим, адже воно має значення за замовчуванням `None`. + +### Модель для введення в документації { #input-model-in-docs } + +У документації ви побачите, що поле `description` не має **червоної зірочки** - воно не позначене як обов'язкове: + +
+ +
+ +### Модель для виведення { #model-for-output } + +Але якщо ви використовуєте цю саму модель для виведення, як тут: + +{* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py hl[19] *} + +…тоді, оскільки `description` має значення за замовчуванням, якщо ви нічого не повернете для цього поля, воно все одно матиме це **значення за замовчуванням**. + +### Модель для даних відповіді при виведенні { #model-for-output-response-data } + +Якщо ви скористаєтеся документацією та перевірите відповідь, навіть попри те, що код нічого не додав в одне з полів `description`, JSON-відповідь містить значення за замовчуванням (`null`): + +
+ +
+ +Це означає, що воно **завжди матиме значення** - просто іноді це значення може бути `None` (або `null` у JSON). + +Отже, клієнтам, які використовують ваш API, не потрібно перевіряти, чи існує значення - вони можуть **припустити, що поле завжди буде присутнє**, але в окремих випадках воно матиме значення за замовчуванням `None`. + +У OpenAPI це описується тим, що поле позначається як **обов'язкове**, адже воно завжди присутнє. + +Тому Схема JSON для моделі може відрізнятися залежно від того, чи використовується вона для **введення або виведення**: +- для **введення** `description` не буде обов'язковим +- для **виведення** воно буде **обов'язковим** (і можливо `None`, або в термінах JSON - `null`) + +### Модель для виведення в документації { #model-for-output-in-docs } + +У документації ви також можете перевірити модель для виведення: **і** `name`, і `description` позначені як **обов'язкові** червоною зірочкою: + +
+ +
+ +### Модель для введення та виведення в документації { #model-for-input-and-output-in-docs } + +Якщо відкрити всі наявні Схеми (схеми JSON) в OpenAPI, то ви побачите дві: `Item-Input` і `Item-Output`. + +Для `Item-Input` поле `description` **не є обов'язковим** - червоної зірочки немає. + +А для `Item-Output` `description` **є обов'язковим** - є червона зірочка. + +
+ +
+ +Завдяки цій можливості у **Pydantic v2** ваша документація API стає більш **точною**, а якщо у вас є згенеровані клієнти та SDK, вони також будуть точнішими - з кращим **досвідом розробника** та узгодженістю. 🎉 + +## Не розділяти схеми { #do-not-separate-schemas } + +Втім іноді може знадобитися мати **одну й ту саму схему для введення та виведення**. + +Основний випадок - коли у вас вже є згенерований клієнтський код/SDK, і ви поки не бажаєте оновлювати весь такий згенерований код/SDK. Ймовірно, ви зробите це пізніше, але не зараз. + +У такому разі ви можете вимкнути цю можливість у **FastAPI** параметром `separate_input_output_schemas=False`. + +/// info | Інформація + +Підтримку `separate_input_output_schemas` додано у FastAPI `0.102.0`. 🤓 + +/// + +{* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} + +### Одна схема для моделей введення та виведення в документації { #same-schema-for-input-and-output-models-in-docs } + +Тепер для цієї моделі буде лише одна спільна схема для введення та виведення - тільки `Item`, і в ній `description` буде **не обов'язковим**: + +
+ +
diff --git a/docs/uk/docs/how-to/testing-database.md b/docs/uk/docs/how-to/testing-database.md new file mode 100644 index 000000000..2e6b21ced --- /dev/null +++ b/docs/uk/docs/how-to/testing-database.md @@ -0,0 +1,7 @@ +# Тестування бази даних { #testing-a-database } + +Ви можете ознайомитися з базами даних, SQL і SQLModel у документації SQLModel. 🤓 + +Є невеликий навчальний посібник про використання SQLModel з FastAPI. ✨ + +Цей навчальний посібник містить розділ про тестування баз даних SQL. 😎 diff --git a/docs/uk/docs/project-generation.md b/docs/uk/docs/project-generation.md new file mode 100644 index 000000000..4899090d4 --- /dev/null +++ b/docs/uk/docs/project-generation.md @@ -0,0 +1,28 @@ +# Шаблон Full Stack FastAPI { #full-stack-fastapi-template } + +Шаблони, хоча зазвичай постачаються з певним налаштуванням, спроєктовані бути гнучкими та налаштовуваними. Це дає змогу змінювати їх і адаптувати до вимог вашого проєкту, що робить їх чудовою відправною точкою. 🏁 + +Ви можете використати цей шаблон для старту, адже в ньому вже виконано значну частину початкового налаштування, безпеки, роботи з базою даних і деяких кінцевих точок API. + +Репозиторій GitHub: Шаблон Full Stack FastAPI + +## Шаблон Full Stack FastAPI - стек технологій і можливості { #full-stack-fastapi-template-technology-stack-and-features } + +- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/uk) для бекенд API на Python. + - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) для взаємодії з SQL-базою даних у Python (ORM). + - 🔍 [Pydantic](https://docs.pydantic.dev), який використовується FastAPI, для перевірки даних і керування налаштуваннями. + - 💾 [PostgreSQL](https://www.postgresql.org) як SQL-база даних. +- 🚀 [React](https://react.dev) для фронтенду. + - 💃 Використання TypeScript, хуків, Vite та інших частин сучасного фронтенд-стеку. + - 🎨 [Tailwind CSS](https://tailwindcss.com) і [shadcn/ui](https://ui.shadcn.com) для фронтенд-компонентів. + - 🤖 Автоматично згенерований фронтенд-клієнт. + - 🧪 [Playwright](https://playwright.dev) для End-to-End тестування. + - 🦇 Підтримка темного режиму. +- 🐋 [Docker Compose](https://www.docker.com) для розробки та продакшену. +- 🔒 Безпечне хешування паролів за замовчуванням. +- 🔑 Автентифікація JWT (JSON Web Token). +- 📫 Відновлення пароля на основі електронної пошти. +- ✅ Тести з [Pytest](https://pytest.org). +- 📞 [Traefik](https://traefik.io) як зворотний представник / балансувальник навантаження. +- 🚢 Інструкції з розгортання з Docker Compose, включно з налаштуванням фронтенд-представника Traefik для автоматичних HTTPS-сертифікатів. +- 🏭 CI (безперервна інтеграція) і CD (безперервне розгортання) на базі GitHub Actions. diff --git a/docs/uk/docs/resources/index.md b/docs/uk/docs/resources/index.md new file mode 100644 index 000000000..353992fad --- /dev/null +++ b/docs/uk/docs/resources/index.md @@ -0,0 +1,3 @@ +# Ресурси { #resources } + +Додаткові ресурси, зовнішні посилання та інше. ✈️ diff --git a/docs/uk/docs/translation-banner.md b/docs/uk/docs/translation-banner.md new file mode 100644 index 000000000..864080399 --- /dev/null +++ b/docs/uk/docs/translation-banner.md @@ -0,0 +1,11 @@ +/// details | 🌐 Переклад ШІ та людьми + +Цей переклад виконано ШІ під керівництвом людей. 🤝 + +Можливі помилки через неправильне розуміння початкового змісту або неприродні формулювання тощо. 🤖 + +Ви можете покращити цей переклад, [допомігши нам краще спрямовувати AI LLM](https://fastapi.tiangolo.com/uk/contributing/#translations). + +[Англійська версія](ENGLISH_VERSION_URL) + +/// diff --git a/docs/uk/docs/tutorial/bigger-applications.md b/docs/uk/docs/tutorial/bigger-applications.md new file mode 100644 index 000000000..a75da2ac6 --- /dev/null +++ b/docs/uk/docs/tutorial/bigger-applications.md @@ -0,0 +1,504 @@ +# Більші застосунки - кілька файлів { #bigger-applications-multiple-files } + +Якщо ви створюєте застосунок або веб-API, рідко вдається вмістити все в один файл. + +**FastAPI** надає зручний інструмент для структурування вашого застосунку, зберігаючи всю гнучкість. + +/// info | Інформація + +Якщо ви прийшли з Flask, це еквівалент «Blueprints» у Flask. + +/// + +## Приклад структури файлів { #an-example-file-structure } + +Припустімо, у вас така структура файлів: + +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   ├── dependencies.py +│   └── routers +│   │ ├── __init__.py +│   │ ├── items.py +│   │ └── users.py +│   └── internal +│   ├── __init__.py +│   └── admin.py +``` + +/// tip | Порада + +Тут кілька файлів `__init__.py`: по одному в кожному каталозі та підкаталозі. + +Саме це дозволяє імпортувати код з одного файлу в інший. + +Наприклад, у `app/main.py` ви можете мати рядок: + +``` +from app.routers import items +``` + +/// + +* Каталог `app` містить усе. І він має порожній файл `app/__init__.py`, тож це «пакет Python» (збірка «модулів Python»): `app`. +* Він містить файл `app/main.py`. Оскільки він усередині пакета Python (каталог з файлом `__init__.py`), це «модуль» цього пакета: `app.main`. +* Є також файл `app/dependencies.py`, так само як `app/main.py`, це «модуль»: `app.dependencies`. +* Є підкаталог `app/routers/` з іншим файлом `__init__.py`, отже це «підпакет Python»: `app.routers`. +* Файл `app/routers/items.py` знаходиться в пакеті `app/routers/`, отже це підмодуль: `app.routers.items`. +* Так само і `app/routers/users.py`, це інший підмодуль: `app.routers.users`. +* Є також підкаталог `app/internal/` з іншим файлом `__init__.py`, отже це інший «підпакет Python»: `app.internal`. +* І файл `app/internal/admin.py` - ще один підмодуль: `app.internal.admin`. + + + +Та сама структура файлів з коментарями: + +```bash +. +├── app # «app» - це пакет Python +│   ├── __init__.py # цей файл робить «app» «пакетом Python» +│   ├── main.py # модуль «main», напр. import app.main +│   ├── dependencies.py # модуль «dependencies», напр. import app.dependencies +│   └── routers # «routers» - це «підпакет Python» +│   │ ├── __init__.py # робить «routers» «підпакетом Python» +│   │ ├── items.py # підмодуль «items», напр. import app.routers.items +│   │ └── users.py # підмодуль «users», напр. import app.routers.users +│   └── internal # «internal» - це «підпакет Python» +│   ├── __init__.py # робить «internal» «підпакетом Python» +│   └── admin.py # підмодуль «admin», напр. import app.internal.admin +``` + +## `APIRouter` { #apirouter } + +Припустімо, файл, присвячений обробці лише користувачів, - це підмодуль у `/app/routers/users.py`. + +Ви хочете мати *операції шляху*, пов'язані з користувачами, відокремлено від решти коду, щоб зберегти порядок. + +Але це все одно частина того самого застосунку/веб-API **FastAPI** (це частина того самого «пакета Python»). + +Ви можете створювати *операції шляху* для цього модуля, використовуючи `APIRouter`. + +### Імпортуйте `APIRouter` { #import-apirouter } + +Імпортуйте його та створіть «екземпляр» так само, як ви б робили з класом `FastAPI`: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} + +### *Операції шляху* з `APIRouter` { #path-operations-with-apirouter } + +Потім використовуйте його для оголошення *операцій шляху*. + +Використовуйте його так само, як і клас `FastAPI`: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} + +Можете думати про `APIRouter` як про «міні `FastAPI`». + +Підтримуються ті самі опції. + +Ті самі `parameters`, `responses`, `dependencies`, `tags` тощо. + +/// tip | Порада + +У цьому прикладі змінна називається `router`, але ви можете назвати її як завгодно. + +/// + +Ми включимо цей `APIRouter` у основний застосунок `FastAPI`, але спочатку розгляньмо залежності та інший `APIRouter`. + +## Залежності { #dependencies } + +Бачимо, що нам знадобляться кілька залежностей, які використовуються в різних місцях застосунку. + +Тож помістимо їх у власний модуль `dependencies` (`app/dependencies.py`). + +Тепер використаємо просту залежність для читання користувацького заголовка `X-Token`: + +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} + +/// tip | Порада + +Ми використовуємо вигаданий заголовок, щоб спростити приклад. + +Але в реальних випадках ви отримаєте кращі результати, використовуючи інтегровані [засоби безпеки](security/index.md){.internal-link target=_blank}. + +/// + +## Інший модуль з `APIRouter` { #another-module-with-apirouter } + +Припустімо, у вас також є кінцеві точки для обробки «items» у модулі `app/routers/items.py`. + +У вас є *операції шляху* для: + +* `/items/` +* `/items/{item_id}` + +Структура така сама, як у `app/routers/users.py`. + +Але ми хочемо бути розумнішими й трохи спростити код. + +Ми знаємо, що всі *операції шляху* в цьому модулі мають однакові: + +* Префікс шляху `prefix`: `/items`. +* `tags`: (лише одна мітка: `items`). +* Додаткові `responses`. +* `dependencies`: усім потрібна залежність `X-Token`, яку ми створили. + +Тож замість додавання цього до кожної *операції шляху*, ми можемо додати це до `APIRouter`. + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} + +Оскільки шлях кожної *операції шляху* має починатися з `/`, як у: + +```Python hl_lines="1" +@router.get("/{item_id}") +async def read_item(item_id: str): + ... +``` + +...префікс не має містити кінцевий `/`. + +Отже, у цьому випадку префікс - це `/items`. + +Ми також можемо додати список `tags` та додаткові `responses`, які застосовуватимуться до всіх *операцій шляху*, включених у цей router. + +І ми можемо додати список `dependencies`, які буде додано до всіх *операцій шляху* у router і які виконуватимуться/вирішуватимуться для кожного запиту до них. + +/// tip | Порада + +Зверніть увагу, що так само як і для [залежностей у декораторах *операцій шляху*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, жодне значення не буде передано вашій *функції операції шляху*. + +/// + +У підсумку шляхи предметів тепер: + +* `/items/` +* `/items/{item_id}` + +...як ми і планували. + +* Вони будуть позначені списком міток, що містить один рядок `"items"`. + * Ці «мітки» особливо корисні для автоматичної інтерактивної документації (за допомогою OpenAPI). +* Усі вони включатимуть наперед визначені `responses`. +* Для всіх цих *операцій шляху* список `dependencies` буде оцінений/виконаний перед ними. + * Якщо ви також оголосите залежності в конкретній *операції шляху*, **вони також будуть виконані**. + * Спочатку виконуються залежності router'а, потім [`dependencies` у декораторі](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, а потім звичайні параметричні залежності. + * Ви також можете додати [`Security` залежності з `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}. + +/// tip | Порада + +Наявність `dependencies` у `APIRouter` можна використати, наприклад, щоб вимагати автентифікацію для всієї групи *операцій шляху*. Навіть якщо залежності не додані до кожної з них окремо. + +/// + +/// check | Перевірте + +Параметри `prefix`, `tags`, `responses` і `dependencies` - це (як і в багатьох інших випадках) просто можливість **FastAPI**, яка допомагає уникати дублювання коду. + +/// + +### Імпортуйте залежності { #import-the-dependencies } + +Цей код живе в модулі `app.routers.items`, у файлі `app/routers/items.py`. + +І нам потрібно отримати функцію залежності з модуля `app.dependencies`, файлу `app/dependencies.py`. + +Тож ми використовуємо відносний імпорт з `..` для залежностей: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} + +#### Як працюють відносні імпорти { #how-relative-imports-work } + +/// tip | Порада + +Якщо ви досконало знаєте, як працюють імпорти, перейдіть до наступного розділу нижче. + +/// + +Одна крапка `.`, як у: + +```Python +from .dependencies import get_token_header +``` + +означає: + +* Починаючи в тому самому пакеті, де знаходиться цей модуль (файл `app/routers/items.py`) (каталог `app/routers/`)... +* знайти модуль `dependencies` (уявний файл `app/routers/dependencies.py`)... +* і з нього імпортувати функцію `get_token_header`. + +Але такого файлу не існує, наші залежності у файлі `app/dependencies.py`. + +Згадайте, як виглядає структура нашого застосунку/файлів: + + + +--- + +Дві крапки `..`, як у: + +```Python +from ..dependencies import get_token_header +``` + +означають: + +* Починаючи в тому самому пакеті, де знаходиться цей модуль (файл `app/routers/items.py`) (каталог `app/routers/`)... +* перейти до батьківського пакета (каталог `app/`)... +* і там знайти модуль `dependencies` (файл `app/dependencies.py`)... +* і з нього імпортувати функцію `get_token_header`. + +Це працює правильно! 🎉 + +--- + +Так само, якби ми використали три крапки `...`, як у: + +```Python +from ...dependencies import get_token_header +``` + +це б означало: + +* Починаючи в тому самому пакеті, де знаходиться цей модуль (файл `app/routers/items.py`) (каталог `app/routers/`)... +* перейти до батьківського пакета (каталог `app/`)... +* потім перейти до батьківського пакета від того (немає батьківського пакета, `app` - верхній рівень 😱)... +* і там знайти модуль `dependencies` (файл `app/dependencies.py`)... +* і з нього імпортувати функцію `get_token_header`. + +Це б посилалося на якийсь пакет вище за `app/` з власним файлом `__init__.py` тощо. Але в нас такого немає. Тож у нашому прикладі це спричинить помилку. 🚨 + +Але тепер ви знаєте, як це працює, тож можете використовувати відносні імпорти у власних застосунках, незалежно від їхньої складності. 🤓 + +### Додайте користувацькі `tags`, `responses` і `dependencies` { #add-some-custom-tags-responses-and-dependencies } + +Ми не додаємо префікс `/items` ані `tags=["items"]` до кожної *операції шляху*, бо додали їх до `APIRouter`. + +Але ми все ще можемо додати _ще_ `tags`, які будуть застосовані до конкретної *операції шляху*, а також додаткові `responses`, специфічні для цієї *операції шляху*: + +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} + +/// tip | Порада + +Остання операція шляху матиме комбінацію міток: `["items", "custom"]`. + +І вона також матиме в документації обидві відповіді: одну для `404` і одну для `403`. + +/// + +## Основний `FastAPI` { #the-main-fastapi } + +Тепер розгляньмо модуль `app/main.py`. + +Тут ви імпортуєте і використовуєте клас `FastAPI`. + +Це буде головний файл вашого застосунку, який усе поєднує. + +І оскільки більшість вашої логіки тепер житиме у власних модулях, головний файл буде досить простим. + +### Імпортуйте `FastAPI` { #import-fastapi } + +Імпортуйте та створіть клас `FastAPI`, як зазвичай. + +І ми навіть можемо оголосити [глобальні залежності](dependencies/global-dependencies.md){.internal-link target=_blank}, які будуть поєднані із залежностями кожного `APIRouter`: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} + +### Імпортуйте `APIRouter` { #import-the-apirouter } + +Тепер імпортуємо інші підмодулі, що мають `APIRouter`: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} + +Оскільки файли `app/routers/users.py` та `app/routers/items.py` - це підмодулі, що є частиною того самого пакета Python `app`, ми можемо використати одну крапку `.` для «відносних імпортів». + +### Як працює імпорт { #how-the-importing-works } + +Розділ: + +```Python +from .routers import items, users +``` + +означає: + +* Починаючи в тому самому пакеті, де знаходиться цей модуль (файл `app/main.py`) (каталог `app/`)... +* знайти підпакет `routers` (каталог `app/routers/`)... +* і з нього імпортувати підмодулі `items` (файл `app/routers/items.py`) і `users` (файл `app/routers/users.py`)... + +Модуль `items` матиме змінну `router` (`items.router`). Це та сама, що ми створили у файлі `app/routers/items.py`, це об’єкт `APIRouter`. + +Потім ми робимо те саме для модуля `users`. + +Ми також могли б імпортувати їх так: + +```Python +from app.routers import items, users +``` + +/// info | Інформація + +Перша версія - «відносний імпорт»: + +```Python +from .routers import items, users +``` + +Друга версія - «абсолютний імпорт»: + +```Python +from app.routers import items, users +``` + +Щоб дізнатися більше про пакети й модулі Python, прочитайте офіційну документацію Python про модулі. + +/// + +### Уникайте колізій імен { #avoid-name-collisions } + +Ми імпортуємо підмодуль `items` напряму, замість імпорту лише його змінної `router`. + +Це тому, що в підмодулі `users` також є змінна з назвою `router`. + +Якби ми імпортували один за одним, як: + +```Python +from .routers.items import router +from .routers.users import router +``` + +`router` з `users` перезаписав би той, що з `items`, і ми не змогли б використовувати їх одночасно. + +Щоб мати змогу використовувати обидва в одному файлі, ми імпортуємо підмодулі напряму: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} + +### Додайте `APIRouter` для `users` і `items` { #include-the-apirouters-for-users-and-items } + +Тепер додаймо `router` з підмодулів `users` і `items`: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} + +/// info | Інформація + +`users.router` містить `APIRouter` у файлі `app/routers/users.py`. + +А `items.router` містить `APIRouter` у файлі `app/routers/items.py`. + +/// + +За допомогою `app.include_router()` ми можемо додати кожен `APIRouter` до основного застосунку `FastAPI`. + +Це включить усі маршрути з цього router'а як частину застосунку. + +/// note | Технічні деталі + +Фактично, всередині для кожної *операції шляху*, оголошеної в `APIRouter`, буде створена окрема *операція шляху*. + +Тобто за лаштунками все працюватиме так, ніби це один і той самий застосунок. + +/// + +/// check | Перевірте + +Вам не потрібно перейматися продуктивністю під час включення router'ів. + +Це займе мікросекунди і відбуватиметься лише під час запуску. + +Тож це не вплине на продуктивність. ⚡ + +/// + +### Додайте `APIRouter` з власними `prefix`, `tags`, `responses` і `dependencies` { #include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies } + +Уявімо, що ваша організація надала вам файл `app/internal/admin.py`. + +Він містить `APIRouter` з кількома адміністративними *операціями шляху*, які організація спільно використовує між кількома проєктами. + +Для цього прикладу він буде дуже простим. Але припустімо, що оскільки його спільно використовують з іншими проєктами організації, ми не можемо модифікувати його та додавати `prefix`, `dependencies`, `tags` тощо прямо до `APIRouter`: + +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} + +Але ми все одно хочемо встановити користувацький `prefix` під час включення `APIRouter`, щоб усі його *операції шляху* починалися з `/admin`, хочемо захистити його за допомогою `dependencies`, які вже є в цьому проєкті, і хочемо додати `tags` та `responses`. + +Ми можемо оголосити все це, не змінюючи оригінальний `APIRouter`, передавши ці параметри до `app.include_router()`: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} + +Таким чином, вихідний `APIRouter` залишиться без змін, тож ми все ще зможемо спільно використовувати той самий файл `app/internal/admin.py` з іншими проєктами в організації. + +У результаті в нашому застосунку кожна з *операцій шляху* з модуля `admin` матиме: + +* Префікс `/admin`. +* Мітку `admin`. +* Залежність `get_token_header`. +* Відповідь `418`. 🍵 + +Але це вплине лише на цей `APIRouter` у нашому застосунку, а не на будь-який інший код, який його використовує. + +Наприклад, інші проєкти можуть використовувати той самий `APIRouter` з іншим методом автентифікації. + +### Додайте *операцію шляху* { #include-a-path-operation } + +Ми також можемо додавати *операції шляху* безпосередньо до застосунку `FastAPI`. + +Тут ми це робимо... просто щоб показати, що так можна 🤷: + +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} + +і це працюватиме коректно разом з усіма іншими *операціями шляху*, доданими через `app.include_router()`. + +/// info | Дуже технічні деталі + +Примітка: це дуже технічна деталь, яку ви, ймовірно, можете просто пропустити. + +--- + +`APIRouter` не «монтуються», вони не ізольовані від решти застосунку. + +Це тому, що ми хочемо включати їхні *операції шляху* в схему OpenAPI та інтерфейси користувача. + +Оскільки ми не можемо просто ізолювати їх і «змонтувати» незалежно від решти, *операції шляху* «клонуються» (створюються заново), а не включаються безпосередньо. + +/// + +## Перевірте автоматичну документацію API { #check-the-automatic-api-docs } + +Тепер запустіть ваш застосунок: + +
+ +```console +$ fastapi dev app/main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +І відкрийте документацію за адресою http://127.0.0.1:8000/docs. + +Ви побачите автоматичну документацію API, що включає шляхи з усіх підмодулів, з правильними шляхами (і префіксами) та правильними мітками: + + + +## Включайте той самий router кілька разів з різними `prefix` { #include-the-same-router-multiple-times-with-different-prefix } + +Ви також можете використовувати `.include_router()` кілька разів з одним і тим самим router'ом, але з різними префіксами. + +Це може бути корисно, наприклад, щоб публікувати той самий API під різними префіксами, наприклад `/api/v1` і `/api/latest`. + +Це просунуте використання, яке вам може й не знадобитися, але воно є на випадок, якщо потрібно. + +## Включіть один `APIRouter` до іншого { #include-an-apirouter-in-another } + +Так само як ви можете включити `APIRouter` у застосунок `FastAPI`, ви можете включити `APIRouter` в інший `APIRouter`, використовуючи: + +```Python +router.include_router(other_router) +``` + +Переконайтеся, що ви робите це до включення `router` в застосунок `FastAPI`, щоб *операції шляху* з `other_router` також були включені. diff --git a/docs/uk/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/uk/docs/tutorial/dependencies/classes-as-dependencies.md new file mode 100644 index 000000000..e64f90ae2 --- /dev/null +++ b/docs/uk/docs/tutorial/dependencies/classes-as-dependencies.md @@ -0,0 +1,288 @@ +# Класи як залежності { #classes-as-dependencies } + +Перш ніж заглибитися у систему **впровадження залежностей**, оновімо попередній приклад. + +## `dict` з попереднього прикладу { #a-dict-from-the-previous-example } + +У попередньому прикладі ми повертали `dict` із нашого «залежного»: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[9] *} + +Але тоді ми отримуємо `dict` у параметрі `commons` функції операції шляху. + +І ми знаємо, що редактори не можуть надати багато підтримки (наприклад, автодоповнення) для `dict`, адже вони не знають їхніх ключів і типів значень. + +Можна зробити краще… + +## Що робить об’єкт залежністю { #what-makes-a-dependency } + +Дотепер ви бачили залежності, оголошені як функції. + +Але це не єдиний спосіб оголошувати залежності (хоча, ймовірно, найпоширеніший). + +Ключовий момент у тому, що залежність має бути «викликаємим». + +«Викликаємий» у Python - це будь-що, що Python може «викликати», як функцію. + +Отже, якщо у вас є об’єкт `something` (який може й не бути функцією) і ви можете «викликати» його (виконати) так: + +```Python +something() +``` + +або + +```Python +something(some_argument, some_keyword_argument="foo") +``` + +тоді це «викликаємий». + +## Класи як залежності { #classes-as-dependencies_1 } + +Ви могли помітити, що для створення екземпляра класу Python ви використовуєте той самий синтаксис. + +Наприклад: + +```Python +class Cat: + def __init__(self, name: str): + self.name = name + + +fluffy = Cat(name="Mr Fluffy") +``` + +У цьому випадку `fluffy` - екземпляр класу `Cat`. + +А для створення `fluffy` ви «викликаєте» `Cat`. + +Отже, клас Python також є **викликаємим**. + +Тож у **FastAPI** ви можете використовувати клас Python як залежність. + +Насправді **FastAPI** перевіряє, що це «викликаємий» об’єкт (функція, клас чи щось інше) і які параметри в нього оголошені. + +Якщо ви передаєте «викликаємий» як залежність у **FastAPI**, він проаналізує параметри цього об’єкта і обробить їх так само, як параметри для функції операції шляху. Включно з підзалежностями. + +Це також стосується викликаємих без жодних параметрів. Так само, як і для функцій операцій шляху без параметрів. + +Тоді ми можемо змінити залежність `common_parameters` вище на клас `CommonQueryParams`: + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[11:15] *} + +Зверніть увагу на метод `__init__`, який використовують для створення екземпляра класу: + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[12] *} + +…він має ті самі параметри, що й наш попередній `common_parameters`: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8] *} + +Саме ці параметри **FastAPI** використає, щоб «розв’язати» залежність. + +В обох випадках буде: + +- Необов’язковий параметр запиту `q`, який є `str`. +- Параметр запиту `skip`, який є `int`, зі значенням за замовчуванням `0`. +- Параметр запиту `limit`, який є `int`, зі значенням за замовчуванням `100`. + +В обох випадках дані будуть перетворені, перевірені й задокументовані в схемі OpenAPI тощо. + +## Використання { #use-it } + +Тепер ви можете оголосити залежність, використовуючи цей клас. + +{* ../../docs_src/dependencies/tutorial002_an_py310.py hl[19] *} + +**FastAPI** викликає клас `CommonQueryParams`. Це створює «екземпляр» цього класу, і цей екземпляр буде передано як параметр `commons` у вашу функцію. + +## Анотація типів проти `Depends` { #type-annotation-vs-depends } + +Зверніть увагу, що вище ми двічі пишемо `CommonQueryParams`: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ без Annotated + +/// tip | Порада + +Надавайте перевагу варіанту з `Annotated`, якщо це можливо. + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +Останній `CommonQueryParams` у: + +```Python +... Depends(CommonQueryParams) +``` + + - це те, що **FastAPI** фактично використає, щоб дізнатися, яка залежність. + +Саме з нього **FastAPI** витягне оголошені параметри і саме його **FastAPI** буде викликати. + +--- + +У цьому випадку перший `CommonQueryParams` у: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, ... +``` + +//// + +//// tab | Python 3.10+ без Annotated + +/// tip | Порада + +Надавайте перевагу варіанту з `Annotated`, якщо це можливо. + +/// + +```Python +commons: CommonQueryParams ... +``` + +//// + +…не має жодного особливого значення для **FastAPI**. FastAPI не використовуватиме його для перетворення даних, перевірки тощо (адже для цього використовується `Depends(CommonQueryParams)`). + +Насправді ви могли б написати просто: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[Any, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ без Annotated + +/// tip | Порада + +Надавайте перевагу варіанту з `Annotated`, якщо це можливо. + +/// + +```Python +commons = Depends(CommonQueryParams) +``` + +//// + +…як у: + +{* ../../docs_src/dependencies/tutorial003_an_py310.py hl[19] *} + +Але оголошувати тип рекомендується - так ваш редактор знатиме, що буде передано в параметр `commons`, і зможе допомагати з автодоповненням, перевірками типів тощо: + + + +## Скорочення { #shortcut } + +Але ви бачите, що тут маємо деяке дублювання коду - `CommonQueryParams` пишемо двічі: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ без Annotated + +/// tip | Порада + +Надавайте перевагу варіанту з `Annotated`, якщо це можливо. + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +**FastAPI** надає скорочення для таких випадків, коли залежність - це саме клас, який **FastAPI** «викличе», щоб створити екземпляр цього класу. + +Для таких випадків ви можете зробити так: + +Замість того щоб писати: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +//// + +//// tab | Python 3.10+ без Annotated + +/// tip | Порада + +Надавайте перевагу варіанту з `Annotated`, якщо це можливо. + +/// + +```Python +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +//// + +…напишіть: + +//// tab | Python 3.10+ + +```Python +commons: Annotated[CommonQueryParams, Depends()] +``` + +//// + +//// tab | Python 3.10+ без Annotated + +/// tip | Порада + +Надавайте перевагу варіанту з `Annotated`, якщо це можливо. + +/// + +```Python +commons: CommonQueryParams = Depends() +``` + +//// + +Ви оголошуєте залежність як тип параметра і використовуєте `Depends()` без параметрів, замість того щоб вдруге писати повний клас усередині `Depends(CommonQueryParams)`. + +Той самий приклад виглядатиме так: + +{* ../../docs_src/dependencies/tutorial004_an_py310.py hl[19] *} + +…і **FastAPI** знатиме, що робити. + +/// tip | Порада + +Якщо це здається заплутанішим, ніж корисним, просто не використовуйте це - воно не є обов’язковим. + +Це лише скорочення. Адже **FastAPI** дбає про мінімізацію дублювання коду. + +/// diff --git a/docs/uk/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/uk/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md new file mode 100644 index 000000000..4614c626c --- /dev/null +++ b/docs/uk/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -0,0 +1,69 @@ +# Залежності в декораторах операцій шляху { #dependencies-in-path-operation-decorators } + +Іноді вам насправді не потрібне значення, яке повертає залежність, у вашій *функції операції шляху*. + +Або залежність узагалі не повертає значення. + +Але її все одно потрібно виконати/опрацювати. + +Для таких випадків, замість оголошення параметра *функції операції шляху* з `Depends`, ви можете додати `list` `dependencies` до *декоратора операції шляху*. + +## Додайте `dependencies` до *декоратора операції шляху* { #add-dependencies-to-the-path-operation-decorator } + +*Декоратор операції шляху* приймає необов'язковий аргумент `dependencies`. + +Це має бути `list` з `Depends()`: + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} + +Ці залежності будуть виконані/оброблені так само, як звичайні залежності. Але їхні значення (якщо щось повертають) не передаватимуться у вашу *функцію операції шляху*. + +/// tip | Порада + +Деякі редактори перевіряють невикористані параметри функцій і показують їх як помилки. + +Використовуючи ці `dependencies` у *декораторі операції шляху*, ви можете гарантувати їх виконання та водночас уникнути помилок редактора/інструментів. + +Це також може допомогти уникнути плутанини для нових розробників, які бачать невикористаний параметр у вашому коді й можуть вирішити, що він зайвий. + +/// + +/// info | Інформація + +У цьому прикладі ми використовуємо вигадані власні заголовки `X-Key` і `X-Token`. + +Але в реальних випадках, під час впровадження безпеки, ви отримаєте більше користі, використовуючи вбудовані [Інструменти безпеки (наступний розділ)](../security/index.md){.internal-link target=_blank}. + +/// + +## Помилки залежностей і значення, що повертаються { #dependencies-errors-and-return-values } + +Ви можете використовувати ті самі *функції* залежностей, що й зазвичай. + +### Вимоги залежностей { #dependency-requirements } + +Вони можуть оголошувати вимоги до запиту (наприклад, заголовки) або інші підзалежності: + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} + +### Підіймати винятки { #raise-exceptions } + +Ці залежності можуть `raise` винятки, так само як і звичайні залежності: + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} + +### Значення, що повертаються { #return-values } + +Вони можуть повертати значення або ні - ці значення не використовуватимуться. + +Отже, ви можете перевикористати звичайну залежність (яка повертає значення), яку вже застосовуєте деінде, і навіть якщо значення не буде використано, залежність буде виконана: + +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} + +## Залежності для групи операцій шляху { #dependencies-for-a-group-of-path-operations } + +Далі, читаючи про структурування великих застосунків ([Більші застосунки - декілька файлів](../../tutorial/bigger-applications.md){.internal-link target=_blank}), можливо з кількома файлами, ви дізнаєтеся, як оголосити один параметр `dependencies` для групи *операцій шляху*. + +## Глобальні залежності { #global-dependencies } + +Далі ми побачимо, як додати залежності до всього застосунку `FastAPI`, щоб вони застосовувалися до кожної *операції шляху*. diff --git a/docs/uk/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/uk/docs/tutorial/dependencies/dependencies-with-yield.md new file mode 100644 index 000000000..70d4210a1 --- /dev/null +++ b/docs/uk/docs/tutorial/dependencies/dependencies-with-yield.md @@ -0,0 +1,289 @@ +# Залежності з yield { #dependencies-with-yield } + +FastAPI підтримує залежності, які виконують деякі додаткові кроки після завершення. + +Щоб це зробити, використовуйте `yield` замість `return` і напишіть додаткові кроки (код) після нього. + +/// tip | Порада + +Переконайтесь, що ви використовуєте `yield` лише один раз на залежність. + +/// + +/// note | Технічні деталі + +Будь-яка функція, яку можна використовувати з: + +* `@contextlib.contextmanager` або +* `@contextlib.asynccontextmanager` + +буде придатною як залежність у **FastAPI**. + +Насправді FastAPI використовує ці два декоратори внутрішньо. + +/// + +## Залежність бази даних з `yield` { #a-database-dependency-with-yield } + +Наприклад, ви можете використати це, щоб створити сесію бази даних і закрити її після завершення. + +Перед створенням відповіді виконується лише код до і включно з оператором `yield`: + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[2:4] *} + +Значення, передане `yield`, впроваджується в *операції шляху* та інші залежності: + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[4] *} + +Код після оператора `yield` виконується після відповіді: + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[5:6] *} + +/// tip | Порада + +Можете використовувати як `async`, так і звичайні функції. + +**FastAPI** зробить усе правильно з кожною з них, так само як і зі звичайними залежностями. + +/// + +## Залежність з `yield` та `try` { #a-dependency-with-yield-and-try } + +Якщо ви використовуєте блок `try` в залежності з `yield`, ви отримаєте будь-який виняток, який був згенерований під час використання залежності. + +Наприклад, якщо якийсь код десь посередині, в іншій залежності або в *операції шляху*, зробив «rollback» транзакції бази даних або створив будь-який інший виняток, ви отримаєте цей виняток у своїй залежності. + +Тож ви можете обробити цей конкретний виняток усередині залежності за допомогою `except SomeException`. + +Так само ви можете використовувати `finally`, щоб гарантувати виконання завершальних кроків незалежно від того, був виняток чи ні. + +{* ../../docs_src/dependencies/tutorial007_py310.py hl[3,5] *} + +## Підзалежності з `yield` { #sub-dependencies-with-yield } + +Ви можете мати підзалежності та «дерева» підзалежностей будь-якого розміру і форми, і будь-яка або всі з них можуть використовувати `yield`. + +**FastAPI** гарантує, що «exit code» у кожній залежності з `yield` буде виконано в правильному порядку. + +Наприклад, `dependency_c` може залежати від `dependency_b`, а `dependency_b` - від `dependency_a`: + +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[6,14,22] *} + +І всі вони можуть використовувати `yield`. + +У цьому випадку `dependency_c`, щоб виконати свій завершальний код, потребує, щоб значення з `dependency_b` (тут `dep_b`) все ще було доступним. + +І, у свою чергу, `dependency_b` потребує, щоб значення з `dependency_a` (тут `dep_a`) було доступним для свого завершального коду. + +{* ../../docs_src/dependencies/tutorial008_an_py310.py hl[18:19,26:27] *} + +Так само ви можете мати деякі залежності з `yield`, а інші - з `return`, і частина з них може залежати від інших. + +І ви можете мати одну залежність, яка вимагає кілька інших залежностей з `yield` тощо. + +Ви можете мати будь-які комбінації залежностей, які вам потрібні. + +**FastAPI** подбає, щоб усе виконувалося в правильному порядку. + +/// note | Технічні деталі + +Це працює завдяки Менеджерам контексту Python. + +**FastAPI** використовує їх внутрішньо, щоб досягти цього. + +/// + +## Залежності з `yield` та `HTTPException` { #dependencies-with-yield-and-httpexception } + +Ви бачили, що можна використовувати залежності з `yield` і мати блоки `try`, які намагаються виконати деякий код, а потім запускають завершальний код після `finally`. + +Також можна використовувати `except`, щоб перехопити згенерований виняток і щось із ним зробити. + +Наприклад, ви можете підняти інший виняток, як-от `HTTPException`. + +/// tip | Порада + +Це доволі просунута техніка, і в більшості випадків вона вам не знадобиться, адже ви можете піднімати винятки (включно з `HTTPException`) всередині іншого коду вашого застосунку, наприклад, у *функції операції шляху*. + +Але вона є, якщо вам це потрібно. 🤓 + +/// + +{* ../../docs_src/dependencies/tutorial008b_an_py310.py hl[18:22,31] *} + +Якщо ви хочете перехоплювати винятки та створювати на їх основі користувацьку відповідь, створіть [Користувацький обробник винятків](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank}. + +## Залежності з `yield` та `except` { #dependencies-with-yield-and-except } + +Якщо ви перехоплюєте виняток за допомогою `except` у залежності з `yield` і не піднімаєте його знову (або не піднімаєте новий виняток), FastAPI не зможе помітити, що стався виняток, так само як це було б у звичайному Python: + +{* ../../docs_src/dependencies/tutorial008c_an_py310.py hl[15:16] *} + +У цьому випадку клієнт побачить відповідь *HTTP 500 Internal Server Error*, як і має бути, з огляду на те, що ми не піднімаємо `HTTPException` або подібний виняток, але на сервері **не буде жодних логів** чи інших ознак того, що це була за помилка. 😱 + +### Завжди використовуйте `raise` у залежностях з `yield` та `except` { #always-raise-in-dependencies-with-yield-and-except } + +Якщо ви перехоплюєте виняток у залежності з `yield`, якщо тільки ви не піднімаєте інший `HTTPException` або подібний, **вам слід повторно підняти початковий виняток**. + +Ви можете повторно підняти той самий виняток, використовуючи `raise`: + +{* ../../docs_src/dependencies/tutorial008d_an_py310.py hl[17] *} + +Тепер клієнт отримає ту саму відповідь *HTTP 500 Internal Server Error*, але сервер матиме наш користувацький `InternalError` у логах. 😎 + +## Виконання залежностей з `yield` { #execution-of-dependencies-with-yield } + +Послідовність виконання приблизно така, як на цій діаграмі. Час тече зверху вниз. І кожна колонка - це одна з частин, що взаємодіють або виконують код. + +```mermaid +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,operation: Can raise exceptions, including HTTPException + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise Exception + dep -->> handler: Raise Exception + handler -->> client: HTTP error response + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise Exception (e.g. HTTPException) + opt handle + dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception + end + handler -->> client: HTTP error response + end + + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> tasks: Handle exceptions in the background task code + end +``` + +/// info | Інформація + +Лише **одна відповідь** буде надіслана клієнту. Це може бути одна з помилкових відповідей або відповідь від *операції шляху*. + +Після відправлення однієї з цих відповідей іншу відправити не можна. + +/// + +/// tip | Порада + +Якщо ви піднімаєте будь-який виняток у коді з *функції операції шляху*, він буде переданий у залежності з `yield`, включно з `HTTPException`. У більшості випадків ви захочете повторно підняти той самий виняток або новий із залежності з `yield`, щоб переконатися, що його коректно оброблено. + +/// + +## Ранній вихід і `scope` { #early-exit-and-scope } + +Зазвичай завершальний код залежностей з `yield` виконується **після того**, як відповідь надіслано клієнту. + +Але якщо ви знаєте, що вам не потрібно використовувати залежність після повернення з *функції операції шляху*, ви можете використати `Depends(scope="function")`, щоб сказати FastAPI, що слід закрити залежність після повернення з *функції операції шляху*, але **до** надсилання **відповіді**. + +{* ../../docs_src/dependencies/tutorial008e_an_py310.py hl[12,16] *} + +`Depends()` приймає параметр `scope`, який може бути: + +* `"function"`: запустити залежність перед *функцією операції шляху*, що обробляє запит, завершити залежність після завершення *функції операції шляху*, але **до** того, як відповідь буде відправлена клієнту. Тобто функція залежності буде виконуватися **навколо** *функції операції **шляху***. +* `"request"`: запустити залежність перед *функцією операції шляху*, що обробляє запит (подібно до `"function"`), але завершити **після** того, як відповідь буде відправлена клієнту. Тобто функція залежності буде виконуватися **навколо** циклу **запиту** та відповіді. + +Якщо не вказано, і залежність має `yield`, за замовчуванням `scope` дорівнює `"request"`. + +### `scope` для підзалежностей { #scope-for-sub-dependencies } + +Коли ви оголошуєте залежність із `scope="request"` (за замовчуванням), будь-яка підзалежність також має мати `scope` рівний `"request"`. + +Але залежність з `scope` рівним `"function"` може мати залежності з `scope` `"function"` і `scope` `"request"`. + +Це тому, що будь-яка залежність має бути здатною виконати свій завершальний код раніше за підзалежності, оскільки вона може все ще потребувати їх під час свого завершального коду. + +```mermaid +sequenceDiagram + +participant client as Client +participant dep_req as Dep scope="request" +participant dep_func as Dep scope="function" +participant operation as Path Operation + + client ->> dep_req: Start request + Note over dep_req: Run code up to yield + dep_req ->> dep_func: Pass dependency + Note over dep_func: Run code up to yield + dep_func ->> operation: Run path operation with dependency + operation ->> dep_func: Return from path operation + Note over dep_func: Run code after yield + Note over dep_func: ✅ Dependency closed + dep_func ->> client: Send response to client + Note over client: Response sent + Note over dep_req: Run code after yield + Note over dep_req: ✅ Dependency closed +``` + +## Залежності з `yield`, `HTTPException`, `except` і фоновими задачами { #dependencies-with-yield-httpexception-except-and-background-tasks } + +Залежності з `yield` еволюціонували з часом, щоб покрити різні сценарії та виправити деякі проблеми. + +Якщо ви хочете дізнатися, що змінювалося в різних версіях FastAPI, прочитайте про це в просунутому посібнику користувача: [Розширені залежності - Залежності з `yield`, `HTTPException`, `except` і фоновими задачами](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}. +## Менеджери контексту { #context-managers } + +### Що таке «Менеджери контексту» { #what-are-context-managers } + +«Менеджери контексту» - це будь-які Python-об'єкти, які можна використовувати в операторі `with`. + +Наприклад, можна використати `with`, щоб прочитати файл: + +```Python +with open("./somefile.txt") as f: + contents = f.read() + print(contents) +``` + +Під капотом `open("./somefile.txt")` створює об'єкт, який називається «Менеджер контексту». + +Коли блок `with` завершується, він гарантує закриття файлу, навіть якщо були винятки. + +Коли ви створюєте залежність з `yield`, **FastAPI** внутрішньо створить для неї менеджер контексту й поєднає його з іншими пов'язаними інструментами. + +### Використання менеджерів контексту в залежностях з `yield` { #using-context-managers-in-dependencies-with-yield } + +/// warning | Попередження + +Це, загалом, «просунута» ідея. + +Якщо ви тільки починаєте з **FastAPI**, можливо, варто наразі пропустити це. + +/// + +У Python ви можете створювати Менеджери контексту, створивши клас із двома методами: `__enter__()` і `__exit__()`. + +Ви також можете використовувати їх усередині залежностей **FastAPI** з `yield`, використовуючи +`with` або `async with` у середині функції залежності: + +{* ../../docs_src/dependencies/tutorial010_py310.py hl[1:9,13] *} + +/// tip | Порада + +Інший спосіб створити менеджер контексту: + +* `@contextlib.contextmanager` або +* `@contextlib.asynccontextmanager` + +використовуючи їх для декорування функції з одним `yield`. + +Саме це **FastAPI** використовує внутрішньо для залежностей з `yield`. + +Але вам не потрібно використовувати ці декоратори для залежностей FastAPI (і не варто). + +FastAPI зробить це за вас внутрішньо. + +/// diff --git a/docs/uk/docs/tutorial/dependencies/global-dependencies.md b/docs/uk/docs/tutorial/dependencies/global-dependencies.md new file mode 100644 index 000000000..3cffa1752 --- /dev/null +++ b/docs/uk/docs/tutorial/dependencies/global-dependencies.md @@ -0,0 +1,15 @@ +# Глобальні залежності { #global-dependencies } + +Для деяких типів застосунків ви можете захотіти додати залежності до всього застосунку. + +Подібно до того, як ви можете [додавати `dependencies` до *декораторів операцій шляху*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, ви можете додати їх до застосунку `FastAPI`. + +У такому разі вони будуть застосовані до всіх *операцій шляху* в застосунку: + +{* ../../docs_src/dependencies/tutorial012_an_py310.py hl[17] *} + +Усі ідеї з розділу про [додавання `dependencies` до *декораторів операцій шляху*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} так само застосовні, але в цьому випадку - до всіх *операцій шляху* в застосунку. + +## Залежності для груп *операцій шляху* { #dependencies-for-groups-of-path-operations } + +Пізніше, читаючи про структуру більших застосунків ([Більші застосунки - кілька файлів](../../tutorial/bigger-applications.md){.internal-link target=_blank}), можливо з кількома файлами, ви дізнаєтеся, як оголосити єдиний параметр `dependencies` для групи *операцій шляху*. diff --git a/docs/uk/docs/tutorial/dependencies/index.md b/docs/uk/docs/tutorial/dependencies/index.md new file mode 100644 index 000000000..cbcf69307 --- /dev/null +++ b/docs/uk/docs/tutorial/dependencies/index.md @@ -0,0 +1,250 @@ +# Залежності { #dependencies } + +**FastAPI** має дуже потужну, але інтуїтивну систему **Впровадження залежностей**. + +Вона створена так, щоб бути дуже простою у використанні та щоб полегшити будь-якому розробнику інтеграцію інших компонентів з **FastAPI**. + +## Що таке «Впровадження залежностей» { #what-is-dependency-injection } + +У програмуванні **«Впровадження залежностей»** означає, що існує спосіб для вашого коду (у цьому випадку ваших *функцій операцій шляху*) задекларувати речі, які йому потрібні для роботи: «залежності». + +А потім ця система (у цьому випадку **FastAPI**) подбає про все необхідне, щоб надати вашому коду ці потрібні залежності («інжектувати» залежності). + +Це дуже корисно, коли вам потрібно: + +* Мати спільну логіку (одна й та сама логіка знову і знову). +* Ділитися з’єднаннями з базою даних. +* Примусово застосовувати безпеку, автентифікацію, вимоги до ролей тощо. +* І багато іншого... + +Все це з мінімізацією дублювання коду. + +## Перші кроки { #first-steps } + +Розгляньмо дуже простий приклад. Він буде таким простим, що поки що не дуже корисним. + +Але так ми зможемо зосередитися на тому, як працює система **Впровадження залежностей**. + +### Створіть залежність або «залежний» { #create-a-dependency-or-dependable } + +Спочатку зосередьмося на залежності. + +Це просто функція, яка може приймати ті самі параметри, що й *функція операції шляху*: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[8:9] *} + +Ось і все. + +**2 рядки**. + +І вона має ту саму форму та структуру, що й усі ваші *функції операцій шляху*. + +Можете думати про неї як про *функцію операції шляху* без «декоратора» (без `@app.get("/some-path")`). + +І вона може повертати будь-що. + +У цьому випадку ця залежність очікує: + +* Необов’язковий параметр запиту `q` типу `str`. +* Необов’язковий параметр запиту `skip` типу `int`, за замовчуванням `0`. +* Необов’язковий параметр запиту `limit` типу `int`, за замовчуванням `100`. + +Потім вона просто повертає `dict`, що містить ці значення. + +/// info | Інформація + +FastAPI додав підтримку `Annotated` (і почав її рекомендувати) у версії 0.95.0. + +Якщо у вас старіша версія, ви отримаєте помилки при спробі використати `Annotated`. + +Переконайтеся, що ви [Оновіть версію FastAPI](../../deployment/versions.md#upgrading-the-fastapi-versions){.internal-link target=_blank} щонайменше до 0.95.1 перед використанням `Annotated`. + +/// + +### Імпортуйте `Depends` { #import-depends } + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *} + +### Оголосіть залежність у «залежному» { #declare-the-dependency-in-the-dependant } + +Так само, як ви використовуєте `Body`, `Query` тощо з параметрами вашої *функції операції шляху*, використовуйте `Depends` з новим параметром: + +{* ../../docs_src/dependencies/tutorial001_an_py310.py hl[13,18] *} + +Хоча ви використовуєте `Depends` у параметрах вашої функції так само, як `Body`, `Query` тощо, `Depends` працює трохи інакше. + +Ви передаєте в `Depends` лише один параметр. + +Цей параметр має бути чимось на кшталт функції. + +Ви її не викликаєте безпосередньо (не додавайте дужки в кінці), ви просто передаєте її як параметр у `Depends()`. + +І ця функція приймає параметри так само, як і *функції операцій шляху*. + +/// tip | Порада + +У наступному розділі ви побачите, які ще «речі», окрім функцій, можна використовувати як залежності. + +/// + +Щоразу, коли надходить новий запит, **FastAPI** подбає про: + +* Виклик вашої функції-залежності («залежного») з правильними параметрами. +* Отримання результату з вашої функції. +* Присвоєння цього результату параметру у вашій *функції операції шляху*. + +```mermaid +graph TB + +common_parameters(["common_parameters"]) +read_items["/items/"] +read_users["/users/"] + +common_parameters --> read_items +common_parameters --> read_users +``` + +Таким чином ви пишете спільний код один раз, а **FastAPI** подбає про його виклик для ваших *операцій шляху*. + +/// check | Перевірте + +Зверніть увагу, що вам не потрібно створювати спеціальний клас і передавати його кудись у **FastAPI**, щоб «зареєструвати» його чи щось подібне. + +Ви просто передаєте його в `Depends`, і **FastAPI** знає, що робити далі. + +/// + +## Спільне використання залежностей `Annotated` { #share-annotated-dependencies } + +У наведених вище прикладах видно невелике **дублювання коду**. + +Коли вам потрібно використати залежність `common_parameters()`, доводиться писати весь параметр з анотацією типу та `Depends()`: + +```Python +commons: Annotated[dict, Depends(common_parameters)] +``` + +Але оскільки ми використовуємо `Annotated`, ми можемо зберегти це значення `Annotated` у змінній і використовувати його в кількох місцях: + +{* ../../docs_src/dependencies/tutorial001_02_an_py310.py hl[12,16,21] *} + +/// tip | Порада + +Це просто стандартний Python, це називається «псевдонім типу» і насправді не є специфічним для **FastAPI**. + +Але оскільки **FastAPI** базується на стандартах Python, включно з `Annotated`, ви можете використати цей трюк у своєму коді. 😎 + +/// + +Залежності продовжать працювати як очікується, і **найкраще** те, що **інформація про типи буде збережена**, а це означає, що ваш редактор зможе й надалі надавати **автозаповнення**, **помилки в рядку** тощо. Те саме і для інших інструментів, як-от `mypy`. + +Це буде особливо корисно у **великій кодовій базі**, де ви використовуєте **одні й ті самі залежності** знову і знову в **багатьох *операціях шляху***. + +## Бути `async` чи не бути `async` { #to-async-or-not-to-async } + +Оскільки залежності також викликатимуться **FastAPI** (так само, як і ваші *функції операцій шляху*), під час визначення ваших функцій діють ті самі правила. + +Ви можете використовувати `async def` або звичайний `def`. + +І ви можете оголошувати залежності з `async def` всередині звичайних *функцій операцій шляху* з `def`, або залежності з `def` всередині *функцій операцій шляху* з `async def` тощо. + +Це не має значення. **FastAPI** знатиме, що робити. + +/// note | Примітка + +Якщо ви не впевнені, перегляньте розділ [Async: *"In a hurry?"*](../../async.md#in-a-hurry){.internal-link target=_blank} про `async` і `await` у документації. + +/// + +## Інтегровано з OpenAPI { #integrated-with-openapi } + +Усі декларації запитів, перевірки та вимоги ваших залежностей (і субзалежностей) будуть інтегровані в ту саму схему OpenAPI. + +Тож інтерактивна документація також міститиме всю інформацію з цих залежностей: + + + +## Просте використання { #simple-usage } + +Якщо придивитися, *функції операцій шляху* оголошуються для використання щоразу, коли збігаються *шлях* та *операція*, а потім **FastAPI** подбає про виклик функції з правильними параметрами, витягуючи дані із запиту. + +Насправді всі (або більшість) вебфреймворків працюють так само. + +Ви ніколи не викликаєте ці функції безпосередньо. Їх викликає ваш фреймворк (у цьому випадку **FastAPI**). + +За допомогою системи впровадження залежностей ви також можете вказати **FastAPI**, що ваша *функція операції шляху* також «залежить» від чогось, що має бути виконано до вашої *функції операції шляху*, і **FastAPI** подбає про виконання цього та «інжектування» результатів. + +Інші поширені терміни для цієї самої ідеї «впровадження залежностей»: + +* ресурси +* провайдери +* сервіси +* інжектовані об’єкти +* компоненти + +## Плагіни **FastAPI** { #fastapi-plug-ins } + +Інтеграції та «плагіни» можна будувати за допомогою системи **Впровадження залежностей**. Але насправді **немає потреби створювати «плагіни»**, оскільки, використовуючи залежності, можна оголосити безмежну кількість інтеграцій та взаємодій, які стають доступними для ваших *функцій операцій шляху*. + +І залежності можна створювати дуже просто та інтуїтивно, що дозволяє вам просто імпортувати потрібні пакунки Python і інтегрувати їх з вашими функціями API за кілька рядків коду, буквально. + +Ви побачите приклади цього в наступних розділах, про реляційні та NoSQL бази даних, безпеку тощо. + +## Сумісність **FastAPI** { #fastapi-compatibility } + +Простота системи впровадження залежностей робить **FastAPI** сумісним з: + +* усіма реляційними базами даних +* NoSQL базами даних +* зовнішніми пакунками +* зовнішніми API +* системами автентифікації та авторизації +* системами моніторингу використання API +* системами інжекції даних у відповідь +* тощо. + +## Просто і потужно { #simple-and-powerful } + +Хоча ієрархічна система впровадження залежностей дуже проста у визначенні та використанні, вона все ще дуже потужна. + +Ви можете визначати залежності, які своєю чергою можуть визначати власні залежності. + +Зрештою будується ієрархічне дерево залежностей, і система **Впровадження залежностей** подбає про розв’язання всіх цих залежностей (і їхніх субзалежностей) та надання (інжектування) результатів на кожному кроці. + +Наприклад, припустімо, у вас є 4 кінцеві точки API (*операції шляху*): + +* `/items/public/` +* `/items/private/` +* `/users/{user_id}/activate` +* `/items/pro/` + +тоді ви могли б додати різні вимоги до дозволів для кожної з них лише за допомогою залежностей і субзалежностей: + +```mermaid +graph TB + +current_user(["current_user"]) +active_user(["active_user"]) +admin_user(["admin_user"]) +paying_user(["paying_user"]) + +public["/items/public/"] +private["/items/private/"] +activate_user["/users/{user_id}/activate"] +pro_items["/items/pro/"] + +current_user --> active_user +active_user --> admin_user +active_user --> paying_user + +current_user --> public +active_user --> private +admin_user --> activate_user +paying_user --> pro_items +``` + +## Інтегровано з **OpenAPI** { #integrated-with-openapi_1 } + +Усі ці залежності, декларуючи свої вимоги, також додають параметри, перевірки тощо до ваших *операцій шляху*. + +**FastAPI** подбає про додавання всього цього до схеми OpenAPI, щоб це відображалося в інтерактивних системах документації. diff --git a/docs/uk/docs/tutorial/dependencies/sub-dependencies.md b/docs/uk/docs/tutorial/dependencies/sub-dependencies.md new file mode 100644 index 000000000..4e7488086 --- /dev/null +++ b/docs/uk/docs/tutorial/dependencies/sub-dependencies.md @@ -0,0 +1,105 @@ +# Підзалежності { #sub-dependencies } + +Ви можете створювати залежності, які мають підзалежності. + +Вони можуть бути настільки глибокими, наскільки потрібно. + +FastAPI подбає про їх розв'язання. + +## Перша залежність «dependable» { #first-dependency-dependable } + +Можна створити першу залежність («dependable») так: + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[8:9] *} + +Вона оголошує необов'язковий параметр запиту `q` типу `str`, а потім просто повертає його. + +Це досить просто (не дуже корисно), але допоможе зосередитися на тому, як працюють підзалежності. + +## Друга залежність, «dependable» і «dependant» { #second-dependency-dependable-and-dependant } + +Далі ви можете створити іншу функцію залежності («dependable»), яка водночас оголошує власну залежність (тож вона також є «dependant»): + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[13] *} + +Зосередьмося на оголошених параметрах: + +- Хоча ця функція сама є залежністю («dependable»), вона також оголошує іншу залежність (вона «залежить» від чогось). + - Вона залежить від `query_extractor` і присвоює значення, яке він повертає, параметру `q`. +- Вона також оголошує необов'язкове кукі `last_query` типу `str`. + - Якщо користувач не надав параметр запиту `q`, ми використовуємо останній запит, який зберегли раніше в кукі. + +## Використання залежності { #use-the-dependency } + +Потім ми можемо використати залежність так: + +{* ../../docs_src/dependencies/tutorial005_an_py310.py hl[23] *} + +/// info | Інформація + +Зверніть увагу, що ми оголошуємо лише одну залежність у функції операції шляху — `query_or_cookie_extractor`. + +Але FastAPI знатиме, що спочатку треба розв'язати `query_extractor`, щоб передати його результат у `query_or_cookie_extractor` під час виклику. + +/// + +```mermaid +graph TB + +query_extractor(["query_extractor"]) +query_or_cookie_extractor(["query_or_cookie_extractor"]) + +read_query["/items/"] + +query_extractor --> query_or_cookie_extractor --> read_query +``` + +## Використання тієї ж залежності кілька разів { #using-the-same-dependency-multiple-times } + +Якщо одна з ваших залежностей оголошена кілька разів для однієї операції шляху, наприклад, кілька залежностей мають спільну підзалежність, FastAPI знатиме, що цю підзалежність потрібно викликати лише один раз на запит. + +І він збереже повернуте значення у «кеш» і передасть його всім «dependants», яким воно потрібне в цьому конкретному запиті, замість того щоб викликати залежність кілька разів для одного й того ж запиту. + +У просунутому сценарії, коли ви знаєте, що залежність має викликатися на кожному кроці (можливо, кілька разів) у межах того самого запиту замість використання «кешованого» значення, ви можете встановити параметр `use_cache=False` при використанні `Depends`: + +//// tab | Python 3.10+ + +```Python hl_lines="1" +async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): + return {"fresh_value": fresh_value} +``` + +//// + +//// tab | Python 3.10+ без Annotated + +/// tip | Порада + +Надавайте перевагу версії з `Annotated`, якщо це можливо. + +/// + +```Python hl_lines="1" +async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)): + return {"fresh_value": fresh_value} +``` + +//// + +## Підсумок { #recap } + +Попри всі модні терміни, система впровадження залежностей досить проста. + +Це просто функції, які виглядають так само, як функції операцій шляху. + +Втім вона дуже потужна і дозволяє оголошувати довільно глибоко вкладені «графи» залежностей (дерева). + +/// tip | Порада + +Усе це може здаватися не надто корисним на простих прикладах. + +Але ви побачите, наскільки це корисно, у розділах про **безпеку**. + +І також побачите, скільки коду це вам заощадить. + +/// diff --git a/docs/uk/docs/tutorial/extra-models.md b/docs/uk/docs/tutorial/extra-models.md new file mode 100644 index 000000000..25930b8c0 --- /dev/null +++ b/docs/uk/docs/tutorial/extra-models.md @@ -0,0 +1,211 @@ +# Додаткові моделі { #extra-models } + +Продовжуючи попередній приклад, часто потрібно мати більше ніж одну пов’язану модель. + +Особливо це стосується моделей користувача, тому що: + +* **вхідна модель** повинна мати пароль. +* **вихідна модель** не повинна містити пароль. +* **модель бази даних**, ймовірно, повинна містити хеш пароля. + +/// danger | Обережно + +Ніколи не зберігайте паролі користувачів у відкритому вигляді. Завжди зберігайте «безпечний хеш», який потім можна перевірити. + +Якщо ви ще не знаєте, що таке «хеш пароля», ви дізнаєтесь у [розділах про безпеку](security/simple-oauth2.md#password-hashing){.internal-link target=_blank}. + +/// + +## Кілька моделей { #multiple-models } + +Ось загальна ідея того, як можуть виглядати моделі з їхніми полями пароля та місцями використання: + +{* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} + +### Про `**user_in.model_dump()` { #about-user-in-model-dump } + +#### `.model_dump()` у Pydantic { #pydantics-model-dump } + +`user_in` - це модель Pydantic класу `UserIn`. + +Моделі Pydantic мають метод `.model_dump()`, який повертає `dict` з даними моделі. + +Отже, якщо ми створимо об’єкт Pydantic `user_in` так: + +```Python +user_in = UserIn(username="john", password="secret", email="john.doe@example.com") +``` + +і викличемо: + +```Python +user_dict = user_in.model_dump() +``` + +тепер ми маємо `dict` з даними у змінній `user_dict` (це `dict`, а не об’єкт моделі Pydantic). + +А якщо викликати: + +```Python +print(user_dict) +``` + +ми отримаємо Python `dict` з: + +```Python +{ + 'username': 'john', + 'password': 'secret', + 'email': 'john.doe@example.com', + 'full_name': None, +} +``` + +#### Розпакування `dict` { #unpacking-a-dict } + +Якщо взяти `dict`, наприклад `user_dict`, і передати його у функцію (або клас) як `**user_dict`, Python «розпакує» його. Ключі та значення `user_dict` будуть передані безпосередньо як іменовані аргументи. + +Отже, продовжуючи з `user_dict` вище, запис: + +```Python +UserInDB(**user_dict) +``` + +дасть еквівалентний результат: + +```Python +UserInDB( + username="john", + password="secret", + email="john.doe@example.com", + full_name=None, +) +``` + +А точніше, використовуючи безпосередньо `user_dict`, з будь-яким його вмістом у майбутньому: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], +) +``` + +#### Модель Pydantic зі вмісту іншої { #a-pydantic-model-from-the-contents-of-another } + +Як у прикладі вище ми отримали `user_dict` з `user_in.model_dump()`, цей код: + +```Python +user_dict = user_in.model_dump() +UserInDB(**user_dict) +``` + +буде еквівалентним: + +```Python +UserInDB(**user_in.model_dump()) +``` + +...тому що `user_in.model_dump()` повертає `dict`, а ми змушуємо Python «розпакувати» його, передаючи в `UserInDB` з префіксом `**`. + +Тож ми отримуємо модель Pydantic з даних іншої моделі Pydantic. + +#### Розпакування `dict` і додаткові ключові аргументи { #unpacking-a-dict-and-extra-keywords } + +Додаючи додатковий іменований аргумент `hashed_password=hashed_password`, як тут: + +```Python +UserInDB(**user_in.model_dump(), hashed_password=hashed_password) +``` + +...у підсумку це дорівнює: + +```Python +UserInDB( + username = user_dict["username"], + password = user_dict["password"], + email = user_dict["email"], + full_name = user_dict["full_name"], + hashed_password = hashed_password, +) +``` + +/// warning | Попередження + +Додаткові допоміжні функції `fake_password_hasher` і `fake_save_user` лише демонструють можливий потік даних і, звісно, не забезпечують реальної безпеки. + +/// + +## Зменшення дублювання { #reduce-duplication } + +Зменшення дублювання коду - одна з ключових ідей у **FastAPI**. + +Адже дублювання коду підвищує ймовірність помилок, проблем безпеки, розсинхронізації коду (коли ви оновлюєте в одному місці, але не в інших) тощо. + +І ці моделі спільно використовують багато даних та дублюють назви і типи атрибутів. + +Можна зробити краще. + +Можна оголосити модель `UserBase`, яка буде базовою для інших моделей. Потім створити підкласи цієї моделі, що наслідуватимуть її атрибути (оголошення типів, валідацію тощо). + +Уся конвертація даних, валідація, документація тощо працюватимуть як зазвичай. + +Таким чином, ми оголошуємо лише відмінності між моделями (з відкритим `password`, з `hashed_password` і без пароля): + +{* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *} + +## `Union` або `anyOf` { #union-or-anyof } + +Ви можете оголосити відповідь як `Union` двох або більше типів - це означає, що відповідь може бути будь-якого з них. + +В OpenAPI це буде визначено як `anyOf`. + +Для цього використайте стандартну підказку типу Python `typing.Union`: + +/// note | Примітка + +Під час визначення `Union` спочатку вказуйте найконкретніший тип, а потім менш конкретний. У прикладі нижче більш конкретний `PlaneItem` стоїть перед `CarItem` у `Union[PlaneItem, CarItem]`. + +/// + +{* ../../docs_src/extra_models/tutorial003_py310.py hl[1,14:15,18:20,33] *} + +### `Union` у Python 3.10 { #union-in-python-3-10 } + +У цьому прикладі ми передаємо `Union[PlaneItem, CarItem]` як значення аргументу `response_model`. + +Оскільки ми передаємо його як значення аргументу, а не в анотації типу, потрібно використовувати `Union` навіть у Python 3.10. + +Якби це була анотація типу, можна було б використати вертикальну риску, наприклад: + +```Python +some_variable: PlaneItem | CarItem +``` + +Але якщо записати це як присвоєння `response_model=PlaneItem | CarItem`, отримаємо помилку, тому що Python спробує виконати невалідну операцію між `PlaneItem` і `CarItem`, замість того щоб трактувати це як анотацію типу. + +## Список моделей { #list-of-models } + +Аналогічно можна оголошувати відповіді як списки об’єктів. + +Для цього використайте стандартний Python `list`: + +{* ../../docs_src/extra_models/tutorial004_py310.py hl[18] *} + +## Відповідь з довільним `dict` { #response-with-arbitrary-dict } + +Також можна оголосити відповідь, використовуючи звичайний довільний `dict`, вказавши лише типи ключів і значень, без моделі Pydantic. + +Це корисно, якщо ви заздалегідь не знаєте допустимі назви полів/атрибутів (які були б потрібні для моделі Pydantic). + +У такому разі можна використати `dict`: + +{* ../../docs_src/extra_models/tutorial005_py310.py hl[6] *} + +## Підсумок { #recap } + +Використовуйте кілька моделей Pydantic і вільно наслідуйте для кожного випадку. + +Не обов’язково мати одну модель даних на сутність, якщо ця сутність може мати різні «стани». Як у випадку сутності користувача зі станами: з `password`, з `password_hash` і без пароля. diff --git a/docs/uk/docs/tutorial/path-operation-configuration.md b/docs/uk/docs/tutorial/path-operation-configuration.md new file mode 100644 index 000000000..91b58b24e --- /dev/null +++ b/docs/uk/docs/tutorial/path-operation-configuration.md @@ -0,0 +1,107 @@ +# Налаштування операції шляху { #path-operation-configuration } + +Є кілька параметрів, які ви можете передати вашому «декоратору операції шляху» для налаштування. + +/// warning | Попередження + +Зверніть увагу, що ці параметри передаються безпосередньо «декоратору операції шляху», а не вашій «функції операції шляху». + +/// + +## Код статусу відповіді { #response-status-code } + +Ви можете визначити (HTTP) `status_code`, який буде використано у відповіді вашої «операції шляху». + +Можна передати безпосередньо цілий код, наприклад `404`. + +Якщо ви не пам'ятаєте призначення числових кодів, скористайтеся скороченими константами в `status`: + +{* ../../docs_src/path_operation_configuration/tutorial001_py310.py hl[1,15] *} + +Цей код статусу буде використано у відповіді та додано до схеми OpenAPI. + +/// note | Технічні деталі + +Ви також можете використати `from starlette import status`. + +FastAPI надає той самий `starlette.status` як `fastapi.status` для вашої зручності як розробника. Але він походить безпосередньо зі Starlette. + +/// + +## Мітки { #tags } + +Ви можете додати мітки до вашої «операції шляху», передайте параметр `tags` зі `list` із `str` (зазвичай лише один `str`): + +{* ../../docs_src/path_operation_configuration/tutorial002_py310.py hl[15,20,25] *} + +Вони будуть додані до схеми OpenAPI та використані інтерфейсами автоматичної документації: + + + +### Мітки з переліками { #tags-with-enums } + +У великому застосунку ви можете накопичити багато міток і захочете переконатися, що завжди використовуєте ту саму мітку для пов'язаних «операцій шляху». + +У таких випадках має сенс зберігати мітки в `Enum`. + +FastAPI підтримує це так само, як і зі звичайними строками: + +{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *} + +## Короткий опис і опис { #summary-and-description } + +Ви можете додати `summary` і `description`: + +{* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[17:18] *} + +## Опис зі строки документації { #description-from-docstring } + +Оскільки описи зазвичай довгі та займають кілька рядків, ви можете оголосити опис «операції шляху» у строці документації функції, і FastAPI прочитає його звідти. + +Ви можете писати Markdown у строці документації, його буде інтерпретовано та показано коректно (з урахуванням відступів у строці документації). + +{* ../../docs_src/path_operation_configuration/tutorial004_py310.py hl[17:25] *} + +Його буде використано в інтерактивній документації: + + + +## Опис відповіді { #response-description } + +Ви можете вказати опис відповіді параметром `response_description`: + +{* ../../docs_src/path_operation_configuration/tutorial005_py310.py hl[18] *} + +/// info | Інформація + +Зверніть увагу, що `response_description` стосується саме відповіді, а `description` стосується «операції шляху» загалом. + +/// + +/// check | Перевірте + +OpenAPI визначає, що кожна «операція шляху» потребує опису відповіді. + +Тому, якщо ви його не надасте, FastAPI автоматично згенерує «Successful response». + +/// + + + +## Позначити операцію шляху як застарілу { #deprecate-a-path-operation } + +Якщо потрібно позначити «операцію шляху» як застарілу, але не видаляючи її, передайте параметр `deprecated`: + +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} + +У інтерактивній документації вона буде чітко позначена як застаріла: + + + +Подивіться, як виглядають застарілі та незастарілі «операції шляху»: + + + +## Підсумок { #recap } + +Ви можете легко налаштовувати та додавати метадані до ваших «операцій шляху», передаючи параметри «декораторам операцій шляху». diff --git a/docs/uk/docs/tutorial/security/first-steps.md b/docs/uk/docs/tutorial/security/first-steps.md new file mode 100644 index 000000000..491328d86 --- /dev/null +++ b/docs/uk/docs/tutorial/security/first-steps.md @@ -0,0 +1,203 @@ +# Безпека - перші кроки { #security-first-steps } + +Уявімо, що ваш **backend** API працює на певному домені. + +А **frontend** - на іншому домені або в іншому шляху того ж домену (або у мобільному застосунку). + +І ви хочете, щоб frontend міг автентифікуватися в backend, використовуючи ім'я користувача та пароль. + +Ми можемо використати **OAuth2**, щоб збудувати це з **FastAPI**. + +Але заощадимо вам час на читання всієї довгої специфікації, щоб знайти лише потрібні дрібниці. + +Скористаймося інструментами, які надає **FastAPI**, щоб обробляти безпеку. + +## Як це виглядає { #how-it-looks } + +Спочатку просто запустімо код і подивімося, як він працює, а потім повернемося, щоб розібратися, що відбувається. + +## Створіть `main.py` { #create-main-py } + +Скопіюйте приклад у файл `main.py`: + +{* ../../docs_src/security/tutorial001_an_py310.py *} + +## Запустіть { #run-it } + +/// info | Інформація + +Пакет `python-multipart` автоматично встановлюється з **FastAPI**, коли ви виконуєте команду `pip install "fastapi[standard]"`. + +Однак, якщо ви використовуєте команду `pip install fastapi`, пакет `python-multipart` за замовчуванням не включено. + +Щоб встановити його вручну, переконайтеся, що ви створили [віртуальне оточення](../../virtual-environments.md){.internal-link target=_blank}, активували його, а потім встановили: + +```console +$ pip install python-multipart +``` + +Це тому, що **OAuth2** використовує «form data» для надсилання `username` та `password`. + +/// + +Запустіть приклад: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +## Перевірте { #check-it } + +Перейдіть до інтерактивної документації: http://127.0.0.1:8000/docs. + +Ви побачите щось подібне: + + + +/// check | Кнопка Authorize! + +У вас уже є нова блискуча кнопка «Authorize». + +А ваша *операція шляху* має маленький замок у правому верхньому куті, на який можна натиснути. + +/// + +Якщо натиснути її, з’явиться маленька форма авторизації, щоб ввести `username` і `password` (та інші необов’язкові поля): + + + +/// note | Примітка + +Неважливо, що ви введете у форму, поки що це не спрацює. Але ми скоро це доробимо. + +/// + +Звісно, це не frontend для кінцевих користувачів, але це чудовий інструмент для інтерактивної документації всього вашого API. + +Ним може користуватися команда frontend (якою можете бути і ви самі). + +Ним можуть користуватися сторонні застосунки та системи. + +І ним також можете користуватися ви самі, щоб налагоджувати, перевіряти та тестувати той самий застосунок. + +## Потік паролю { #the-password-flow } + +Тепер повернімося трохи назад і розберімося, що це все таке. + +`password` «flow» - це один зі способів («flows»), визначених в OAuth2, для обробки безпеки та автентифікації. + +OAuth2 був спроєктований так, щоб backend або API могли бути незалежними від сервера, який автентифікує користувача. + +Але в нашому випадку той самий застосунок **FastAPI** оброблятиме і API, і автентифікацію. + +Тож розгляньмо це у спрощеному вигляді: + +- Користувач вводить `username` і `password` у frontend і натискає `Enter`. +- Frontend (у браузері користувача) надсилає ці `username` і `password` на специфічну URL-адресу нашого API (оголошену як `tokenUrl="token"`). +- API перевіряє ці `username` і `password` та повертає «токен» (ми ще нічого з цього не реалізували). + - «Токен» - це просто строка з деяким вмістом, який ми можемо пізніше використати, щоб перевірити цього користувача. + - Зазвичай токен налаштований на завершення строку дії через певний час. + - Тож користувачу доведеться знову увійти пізніше. + - І якщо токен викрадуть, ризик менший. Це не як постійний ключ, який працюватиме завжди (у більшості випадків). +- Frontend тимчасово зберігає цей токен десь. +- Користувач клікає у frontend, щоб перейти до іншого розділу вебзастосунку. +- Frontend має отримати ще дані з API. + - Але для того конкретного кінцевого пункту потрібна автентифікація. + - Тож, щоб автентифікуватися в нашому API, він надсилає заголовок `Authorization` зі значенням `Bearer ` плюс токен. + - Якщо токен містить `foobar`, вміст заголовка `Authorization` буде: `Bearer foobar`. + +## `OAuth2PasswordBearer` у **FastAPI** { #fastapis-oauth2passwordbearer } + +**FastAPI** надає кілька інструментів на різних рівнях абстракції, щоб реалізувати ці функції безпеки. + +У цьому прикладі ми використаємо **OAuth2** з потоком **Password**, використовуючи токен **Bearer**. Це робиться за допомогою класу `OAuth2PasswordBearer`. + +/// info | Інформація + +«Bearer»-токен - не єдиний варіант. + +Але це найкращий для нашого сценарію. + +І, можливо, найкращий для більшості сценаріїв, якщо ви не експерт з OAuth2 і точно не знаєте, чому інша опція краще відповідає вашим потребам. + +У такому разі **FastAPI** теж надає інструменти, щоб це збудувати. + +/// + +Коли ми створюємо екземпляр класу `OAuth2PasswordBearer`, ми передаємо параметр `tokenUrl`. Цей параметр містить URL, який клієнт (frontend у браузері користувача) використовуватиме, щоб надіслати `username` і `password` для отримання токена. + +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} + +/// tip | Порада + +Тут `tokenUrl="token"` відноситься до відносної URL-адреси `token`, яку ми ще не створили. Оскільки це відносна URL-адреса, вона еквівалентна `./token`. + +Тому, якщо ваш API розміщений на `https://example.com/`, це буде `https://example.com/token`. А якщо на `https://example.com/api/v1/`, тоді це буде `https://example.com/api/v1/token`. + +Використання відносної URL-адреси важливе, щоб ваша програма продовжувала працювати навіть у просунутому сценарії, як-от [Behind a Proxy](../../advanced/behind-a-proxy.md){.internal-link target=_blank}. + +/// + +Цей параметр не створює той endpoint / *операцію шляху*, а декларує, що URL `/token` буде тим, який клієнт має використовувати, щоб отримати токен. Ця інформація використовується в OpenAPI, а потім - у системах інтерактивної документації API. + +Незабаром ми також створимо фактичну операцію шляху. + +/// info | Інформація + +Якщо ви дуже строгий «Pythonista», вам може не подобатися стиль імені параметра `tokenUrl` замість `token_url`. + +Це тому, що використано ту саму назву, що і в специфікації OpenAPI. Тож якщо вам потрібно буде дослідити ці схеми безпеки глибше, ви зможете просто скопіювати та вставити її, щоб знайти більше інформації. + +/// + +Змінна `oauth2_scheme` - це екземпляр `OAuth2PasswordBearer`, але це також і «викликаємий» об’єкт. + +Його можна викликати так: + +```Python +oauth2_scheme(some, parameters) +``` + +Тож його можна використовувати з `Depends`. + +### Використання { #use-it } + +Тепер ви можете передати `oauth2_scheme` як залежність через `Depends`. + +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} + +Ця залежність надасть `str`, який буде присвоєний параметру `token` *функції операції шляху*. + +**FastAPI** знатиме, що може використати цю залежність, щоб визначити «схему безпеки» в схемі OpenAPI (і в автоматичній документації API). + +/// info | Технічні деталі + +**FastAPI** знатиме, що може використати клас `OAuth2PasswordBearer` (оголошений у залежності), щоб визначити схему безпеки в OpenAPI, тому що він наслідує `fastapi.security.oauth2.OAuth2`, який своєю чергою наслідує `fastapi.security.base.SecurityBase`. + +Усі утиліти безпеки, які інтегруються з OpenAPI (і автоматичною документацією API), наслідують `SecurityBase`. Так **FastAPI** розуміє, як інтегрувати їх в OpenAPI. + +/// + +## Що відбувається { #what-it-does } + +Вона шукатиме в запиті заголовок `Authorization`, перевірить, чи його значення - це `Bearer ` плюс деякий токен, і поверне токен як `str`. + +Якщо заголовка `Authorization` немає або значення не містить токена `Bearer `, вона одразу відповість помилкою зі статус-кодом 401 (`UNAUTHORIZED`). + +Вам навіть не потрібно перевіряти, чи існує токен, щоб повернути помилку. Ви можете бути певні: якщо ваша функція виконується, у параметрі токена буде `str`. + +Ви вже можете спробувати це в інтерактивній документації: + + + +Ми ще не перевіряємо дійсність токена, але це вже початок. + +## Підсумок { #recap } + +Отже, лише 3-4 додатковими рядками ви вже маєте деяку примітивну форму безпеки. diff --git a/docs/uk/docs/tutorial/security/get-current-user.md b/docs/uk/docs/tutorial/security/get-current-user.md new file mode 100644 index 000000000..2371ad9fc --- /dev/null +++ b/docs/uk/docs/tutorial/security/get-current-user.md @@ -0,0 +1,105 @@ +# Отримати поточного користувача { #get-current-user } + +У попередньому розділі система безпеки (яка базується на системі впровадження залежностей) передавала функції операції шляху `token` як `str`: + +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} + +Але це ще не дуже корисно. + +Зробімо так, щоб вона повертала поточного користувача. + +## Створити модель користувача { #create-a-user-model } + +Спочатку створімо модель користувача в Pydantic. + +Так само, як ми використовуємо Pydantic для оголошення тіл, ми можемо використовувати його будь-де: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[5,12:6] *} + +## Створити залежність `get_current_user` { #create-a-get-current-user-dependency } + +Створімо залежність `get_current_user`. + +Пам'ятаєте, що залежності можуть мати підзалежності? + +`get_current_user` матиме залежність із тим самим `oauth2_scheme`, який ми створили раніше. + +Так само, як ми робили раніше безпосередньо в операції шляху, наша нова залежність `get_current_user` отримає `token` як `str` від підзалежності `oauth2_scheme`: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[25] *} + +## Отримати користувача { #get-the-user } + +`get_current_user` використає (фальшиву) утилітну функцію, яку ми створили, що приймає `token` як `str` і повертає нашу Pydantic-модель `User`: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[19:22,26:27] *} + +## Впровадити поточного користувача { #inject-the-current-user } + +Тепер ми можемо використати той самий `Depends` з нашим `get_current_user` в операції шляху: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[31] *} + +Зверніть увагу, що ми оголошуємо тип `current_user` як Pydantic-модель `User`. + +Це допоможе всередині функції з автодоповненням і перевірками типів. + +/// tip | Порада + +Можливо, ви пам'ятаєте, що тіла запиту також оголошуються моделями Pydantic. + +Тут **FastAPI** не заплутається, тому що ви використовуєте `Depends`. + +/// + +/// check | Перевірте + +Те, як спроєктована ця система залежностей, дозволяє мати різні залежності (різні «залежні»), які всі повертають модель `User`. + +Ми не обмежені наявністю лише однієї залежності, що може повертати такі дані. + +/// + +## Інші моделі { #other-models } + +Тепер ви можете отримувати поточного користувача безпосередньо у функціях операцій шляху та працювати з механізмами безпеки на рівні **впровадження залежностей**, використовуючи `Depends`. + +І ви можете використовувати будь-яку модель або дані для вимог безпеки (у цьому випадку Pydantic-модель `User`). + +Але ви не обмежені використанням якоїсь конкретної модели даних, класу чи типу. + +Хочете мати id та email і не мати жодного username у вашій моделі? Без проблем. Ви можете використовувати ті самі інструменти. + +Хочете мати просто `str`? Або лише `dict`? Або безпосередньо екземпляр класу моделі бази даних? Усе працює так само. + +У вашій програмі насправді входять не користувачі, а роботи, боти чи інші системи, що мають лише токен доступу? Знову ж, усе працює так само. + +Просто використовуйте будь-який тип моделі, будь-який клас, будь-яку базу даних, які потрібні вашій програмі. **FastAPI** подбає про це завдяки системі впровадження залежностей. + +## Розмір коду { #code-size } + +Цей приклад може здаватися багатослівним. Майте на увазі, що ми змішуємо безпеку, моделі даних, утилітні функції та операції шляху в одному файлі. + +Але ось ключовий момент. + +Речі, пов'язані з безпекою та впровадженням залежностей, пишуться один раз. + +І ви можете зробити це настільки складним, наскільки потрібно. І все одно мати це написаним лише один раз, в одному місці. З усією гнучкістю. + +Зате ви можете мати тисячі кінцевих точок (операцій шляху), що використовують одну й ту саму систему безпеки. + +І всі вони (або будь-яка їхня частина, яку ви захочете) можуть скористатися повторним використанням цих залежностей або будь-яких інших, які ви створите. + +І всі ці тисячі операцій шляху можуть бути всього у 3 рядки: + +{* ../../docs_src/security/tutorial002_an_py310.py hl[30:32] *} + +## Підсумок { #recap } + +Тепер ви можете отримувати поточного користувача безпосередньо у вашій функції операції шляху. + +Ми вже на півдорозі. + +Потрібно лише додати операцію шляху, щоб користувач/клієнт міг фактично надіслати `username` і `password`. + +Далі саме це. diff --git a/docs/uk/docs/tutorial/security/oauth2-jwt.md b/docs/uk/docs/tutorial/security/oauth2-jwt.md new file mode 100644 index 000000000..f94abb897 --- /dev/null +++ b/docs/uk/docs/tutorial/security/oauth2-jwt.md @@ -0,0 +1,277 @@ +# OAuth2 з паролем (і хешуванням), Bearer з токенами JWT { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens } + +Тепер, коли ми маємо весь потік безпеки, зробімо застосунок справді захищеним, використовуючи токени JWT і безпечне хешування паролів. + +Цей код ви можете реально використовувати у своєму застосунку, зберігати хеші паролів у своїй базі даних тощо. + +Ми почнемо з того місця, де зупинилися в попередньому розділі, і розширимо його. + +## Про JWT { #about-jwt } + +JWT означає «JSON Web Tokens». + +Це стандарт кодування об'єкта JSON у довгий щільний рядок без пробілів. Він виглядає так: + +``` +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c +``` + +Він не зашифрований, тому кожен може відновити інформацію з вмісту. + +Але він підписаний. Тож коли ви отримуєте токен, який ви видали, ви можете перевірити, що справді його видали ви. + +Таким чином, ви можете створити токен із терміном дії, наприклад, 1 тиждень. І коли користувач повернеться наступного дня з токеном, ви знатимете, що користувач досі увійшов у вашу систему. + +Через тиждень токен завершить термін дії, і користувач не буде авторизований та має знову увійти, щоб отримати новий токен. І якщо користувач (або третя сторона) намагатиметься змінити токен, щоб змінити термін дії, ви це виявите, бо підписи не співпадатимуть. + +Якщо хочете «погратися» з токенами JWT і побачити, як вони працюють, перегляньте https://jwt.io. + +## Встановіть `PyJWT` { #install-pyjwt } + +Нам потрібно встановити `PyJWT`, щоб створювати та перевіряти токени JWT у Python. + +Переконайтеся, що ви створили [віртуальне оточення](../../virtual-environments.md){.internal-link target=_blank}, активували його і тоді встановіть `pyjwt`: + +
+ +```console +$ pip install pyjwt + +---> 100% +``` + +
+ +/// info | Інформація + +Якщо ви плануєте використовувати алгоритми цифрового підпису на кшталт RSA або ECDSA, слід встановити залежність криптобібліотеки `pyjwt[crypto]`. + +Докладніше про це можна прочитати у документації з встановлення PyJWT. + +/// + +## Хешування паролів { #password-hashing } + +«Хешування» означає перетворення деякого вмісту (у цьому випадку пароля) на послідовність байтів (просто строку), що виглядає як нісенітниця. + +Кожного разу, коли ви передаєте точно той самий вміст (точно той самий пароль), ви отримуєте точно ту саму «нісенітницю». + +Але не можна перетворити цю «нісенітницю» назад у пароль. + +### Навіщо використовувати хешування паролів { #why-use-password-hashing } + +Якщо вашу базу даних вкрадуть, зловмисник не матиме відкритих паролів ваших користувачів, а лише хеші. + +Тож зловмисник не зможе спробувати використати цей пароль в іншій системі (оскільки багато користувачів використовують той самий пароль всюди, це було б небезпечно). + +## Встановіть `pwdlib` { #install-pwdlib } + +pwdlib - це чудовий пакет Python для роботи з хешами паролів. + +Він підтримує багато безпечних алгоритмів хешування та утиліт для роботи з ними. + +Рекомендований алгоритм - «Argon2». + +Переконайтеся, що ви створили [віртуальне оточення](../../virtual-environments.md){.internal-link target=_blank}, активували його і тоді встановіть pwdlib з Argon2: + +
+ +```console +$ pip install "pwdlib[argon2]" + +---> 100% +``` + +
+ +/// tip | Порада + +З `pwdlib` ви навіть можете налаштувати його так, щоб він умів читати паролі, створені **Django**, плагіном безпеки **Flask** або багатьма іншими. + +Тож ви зможете, наприклад, спільно використовувати ті самі дані з застосунку Django в базі даних із застосунком FastAPI. Або поступово мігрувати застосунок Django, використовуючи ту саму базу даних. + +І ваші користувачі зможуть входити як із вашого застосунку Django, так і з вашого застосунку **FastAPI** одночасно. + +/// + +## Хешування і перевірка паролів { #hash-and-verify-the-passwords } + +Імпортуйте потрібні інструменти з `pwdlib`. + +Створіть екземпляр PasswordHash з рекомендованими налаштуваннями - він буде використаний для хешування та перевірки паролів. + +/// tip | Порада + +pwdlib також підтримує алгоритм хешування bcrypt, але не включає застарілі алгоритми - для роботи із застарілими хешами рекомендується використовувати бібліотеку passlib. + +Наприклад, ви можете використати її для читання і перевірки паролів, згенерованих іншою системою (наприклад, Django), але хешувати будь-які нові паролі іншим алгоритмом, таким як Argon2 або Bcrypt. + +І бути сумісними з усіма ними одночасно. + +/// + +Створіть утилітарну функцію для хешування пароля, що надходить від користувача. + +І ще одну утиліту для перевірки, чи отриманий пароль відповідає збереженому хешу. + +І ще одну - для автентифікації та повернення користувача. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} + +Коли `authenticate_user` викликається з ім'ям користувача, якого немає в базі даних, ми все одно запускаємо `verify_password` для «підставного» хешу. + +Це забезпечує приблизно однаковий час відповіді кінцевої точки незалежно від того, чи є ім'я користувача дійсним, запобігаючи **атакам за часом**, які могли б бути використані для перелічення наявних імен користувачів. + +/// note | Примітка + +Якщо ви перевірите нову (фальшиву) базу даних `fake_users_db`, побачите, як зараз виглядає хеш пароля: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`. + +/// + +## Опрацювання токенів JWT { #handle-jwt-tokens } + +Імпортуйте встановлені модулі. + +Створіть випадковий секретний ключ, який буде використано для підписання токенів JWT. + +Щоб згенерувати безпечний випадковий секретний ключ, використайте команду: + +
+ +```console +$ openssl rand -hex 32 + +09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 +``` + +
+ +І скопіюйте вивід у змінну `SECRET_KEY` (не використовуйте той, що в прикладі). + +Створіть змінну `ALGORITHM` з алгоритмом для підписання токена JWT і встановіть її в `"HS256"`. + +Створіть змінну для терміну дії токена. + +Визначте модель Pydantic, яка буде використана в кінцевій точці токена для відповіді. + +Створіть утилітарну функцію для генерації нового токена доступу. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} + +## Оновіть залежності { #update-the-dependencies } + +Оновіть `get_current_user`, щоб отримувати той самий токен, що й раніше, але цього разу - токен JWT. + +Декодуйте отриманий токен, перевірте його та поверніть поточного користувача. + +Якщо токен недійсний, одразу поверніть помилку HTTP. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} + +## Оновіть операцію шляху `/token` { #update-the-token-path-operation } + +Створіть `timedelta` з часом життя токена. + +Створіть справжній токен доступу JWT і поверніть його. + +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} + +### Технічні деталі про «subject» `sub` у JWT { #technical-details-about-the-jwt-subject-sub } + +Специфікація JWT каже, що існує ключ `sub` із суб'єктом токена. + +Використовувати його не обов'язково, але саме туди зазвичай поміщають ідентифікатор користувача, тож ми використовуємо його тут. + +JWT може використовуватися й для інших речей, окрім ідентифікації користувача та надання йому можливості безпосередньо виконувати операції з вашою API. + +Наприклад, ви можете ідентифікувати «автомобіль» або «допис у блозі». + +Тоді ви можете додати дозволи щодо цієї сутності, як-от «керувати» (для автомобіля) або «редагувати» (для допису). + +І потім ви можете видати цей токен JWT користувачу (або боту), і він зможе виконувати ці дії (керувати автомобілем або редагувати допис), навіть не маючи облікового запису - лише з токеном JWT, який ваша API для цього згенерувала. + +Використовуючи ці ідеї, JWT можна застосовувати у значно складніших сценаріях. + +У таких випадках кілька сутностей можуть мати однакові ідентифікатори, скажімо `foo` (користувач `foo`, автомобіль `foo` і допис `foo`). + +Щоб уникнути колізій ідентифікаторів, під час створення токена JWT для користувача ви можете додати префікс до значення ключа `sub`, наприклад `username:`. Отже, у цьому прикладі значення `sub` могло б бути: `username:johndoe`. + +Важливо пам'ятати, що ключ `sub` має містити унікальний ідентифікатор у межах усього застосунку і має бути строкою. + +## Перевірте { #check-it } + +Запустіть сервер і перейдіть до документації: http://127.0.0.1:8000/docs. + +Ви побачите такий інтерфейс користувача: + + + +Авторизуйте застосунок так само, як раніше. + +Використайте облікові дані: + +Username: `johndoe` +Password: `secret` + +/// check | Перевірте + +Зверніть увагу, що ніде в коді немає відкритого пароля "`secret`", ми маємо лише хешовану версію. + +/// + + + +Викличте кінцеву точку `/users/me/`, ви отримаєте відповідь: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + + + +Якщо відкриєте інструменти розробника, ви побачите, що у відправлених даних є лише токен, пароль надсилається тільки в першому запиті для автентифікації користувача та отримання токена доступу, але не надсилається далі: + + + +/// note | Примітка + +Зверніть увагу на заголовок `Authorization` зі значенням, що починається з `Bearer `. + +/// + +## Просунуте використання зі `scopes` { #advanced-usage-with-scopes } + +OAuth2 має поняття «scopes». + +Ви можете використовувати їх, щоб додати конкретний набір дозволів до токена JWT. + +Потім ви можете видати цей токен користувачу напряму або третій стороні для взаємодії з вашою API із набором обмежень. + +Ви можете дізнатися, як їх використовувати і як вони інтегровані з **FastAPI** пізніше у **просунутому посібнику користувача**. + +## Підсумок { #recap } + +Маючи все, що ви бачили досі, ви можете налаштувати захищений застосунок **FastAPI**, використовуючи стандарти на кшталт OAuth2 і JWT. + +Майже в будь-якому фреймворку опрацювання безпеки дуже швидко стає досить складною темою. + +Багато пакетів, що сильно це спрощують, змушені йти на численні компроміси з моделлю даних, базою даних і доступними можливостями. Дехто з цих пакетів, які надто все спрощують, насправді мають приховані вади безпеки. + +--- + +**FastAPI** не йде на жодні компроміси з будь-якою базою даних, моделлю даних чи інструментом. + +Він дає вам усю гнучкість, щоб обрати ті, які найкраще підходять вашому проєкту. + +І ви можете напряму використовувати добре підтримувані та широко застосовувані пакети на кшталт `pwdlib` і `PyJWT`, адже **FastAPI** не вимагає жодних складних механізмів для інтеграції зовнішніх пакетів. + +Водночас він надає інструменти, щоб максимально спростити процес без компромісів у гнучкості, надійності чи безпеці. + +І ви можете використовувати та впроваджувати безпечні стандартні протоколи, як-от OAuth2, відносно простим способом. + +У **просунутому посібнику користувача** ви можете дізнатися більше про те, як використовувати «scopes» в OAuth2 для більш детальної системи дозволів, дотримуючись тих самих стандартів. OAuth2 зі scopes - це механізм, який використовують багато великих провайдерів автентифікації, як-от Facebook, Google, GitHub, Microsoft, X (Twitter) тощо, щоб авторизувати сторонні застосунки на взаємодію з їхніми API від імені користувачів. diff --git a/docs/uk/docs/tutorial/security/simple-oauth2.md b/docs/uk/docs/tutorial/security/simple-oauth2.md new file mode 100644 index 000000000..05f949738 --- /dev/null +++ b/docs/uk/docs/tutorial/security/simple-oauth2.md @@ -0,0 +1,289 @@ +# Простий OAuth2 з паролем і Bearer { #simple-oauth2-with-password-and-bearer } + +Тепер продовжимо з попереднього розділу і додамо відсутні частини, щоб отримати повний потік безпеки. + +## Отримайте `username` і `password` { #get-the-username-and-password } + +Ми використаємо утиліти безпеки **FastAPI**, щоб отримати `username` і `password`. + +OAuth2 визначає, що під час використання «потоку паролю» (який ми використовуємо) клієнт/користувач має надіслати поля `username` і `password` як дані форми. + +І специфікація каже, що поля мають називатися саме так. Тому `user-name` або `email` не підійдуть. + +Але не хвилюйтеся, у фронтенді ви можете відображати це так, як забажаєте, для кінцевих користувачів. + +І ваші моделі бази даних можуть використовувати будь-які інші назви. + +Але для *операції шляху* входу ми маємо використовувати саме ці назви, щоб бути сумісними зі специфікацією (і мати змогу, наприклад, користуватися вбудованою системою документації API). + +Специфікація також вказує, що `username` і `password` мають надсилатися як дані форми (тобто без JSON). + +### `scope` { #scope } + +Специфікація також каже, що клієнт може надіслати інше поле форми «`scope`». + +Назва поля форми — `scope` (в однині), але насправді це довга строка зі «scopes», розділеними пробілами. + +Кожен «scope» — це просто строка (без пробілів). + +Їх зазвичай використовують для оголошення конкретних прав доступу, наприклад: + +- `users:read` або `users:write` — поширені приклади. +- `instagram_basic` використовується Facebook / Instagram. +- `https://www.googleapis.com/auth/drive` використовується Google. + +/// info | Інформація + +У OAuth2 «scope» — це просто строка, що оголошує конкретний потрібний дозвіл. + +Не має значення, чи містить вона інші символи на кшталт `:` або чи це URL. + +Ці деталі залежать від реалізації. + +Для OAuth2 це просто строки. + +/// + +## Код для отримання `username` і `password` { #code-to-get-the-username-and-password } + +Тепер скористаймося утилітами, наданими **FastAPI**, щоб обробити це. + +### `OAuth2PasswordRequestForm` { #oauth2passwordrequestform } + +Спочатку імпортуйте `OAuth2PasswordRequestForm` і використайте його як залежність із `Depends` в *операції шляху* для `/token`: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[4,78] *} + +`OAuth2PasswordRequestForm` — це клас залежності, що оголошує тіло форми з: + +- `username`. +- `password`. +- Необов'язковим полем `scope` як великою строкою, складеною зі строк, розділених пробілами. +- Необов'язковим `grant_type`. + +/// tip | Порада + +Специфікація OAuth2 насправді вимагає поле `grant_type` із фіксованим значенням `password`, але `OAuth2PasswordRequestForm` цього не примушує. + +Якщо вам потрібно це примусити, використовуйте `OAuth2PasswordRequestFormStrict` замість `OAuth2PasswordRequestForm`. + +/// + +- Необов'язковим `client_id` (для нашого прикладу не потрібно). +- Необов'язковим `client_secret` (для нашого прикладу не потрібно). + +/// info | Інформація + +`OAuth2PasswordRequestForm` — не спеціальний клас для **FastAPI**, як `OAuth2PasswordBearer`. + +`OAuth2PasswordBearer` робить так, що **FastAPI** знає, що це схема безпеки. Тому її додають таким чином до OpenAPI. + +Але `OAuth2PasswordRequestForm` — це просто клас залежності, який ви могли б написати самі, або ви могли б напряму оголосити параметри `Form`. + +Оскільки це типова задача, **FastAPI** надає його безпосередньо, щоб спростити роботу. + +/// + +### Використовуйте дані форми { #use-the-form-data } + +/// tip | Порада + +Екземпляр класу залежності `OAuth2PasswordRequestForm` не матиме атрибута `scope` з довгою строкою, розділеною пробілами, натомість він матиме атрибут `scopes` зі справжнім списком строк для кожного надісланого scope. + +У цьому прикладі ми не використовуємо `scopes`, але ця можливість є, якщо знадобиться. + +/// + +Тепер отримайте дані користувача з (фальшивої) бази даних, використовуючи `username` з поля форми. + +Якщо такого користувача немає, повертаємо помилку «Incorrect username or password». + +Для помилки використовуємо виняток `HTTPException`: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[3,79:81] *} + +### Перевірте пароль { #check-the-password } + +На цьому етапі ми маємо дані користувача з нашої бази даних, але ще не перевірили пароль. + +Спершу розмістимо ці дані в Pydantic-моделі `UserInDB`. + +Ви ніколи не повинні зберігати паролі у відкритому вигляді, тож ми використаємо (фальшиву) систему хешування паролів. + +Якщо паролі не збігаються, повертаємо ту саму помилку. + +#### Хешування паролів { #password-hashing } + +«Хешування» означає: перетворення деякого вмісту (у цьому випадку пароля) у послідовність байтів (просто строку), яка виглядає як нісенітниця. + +Кожного разу, коли ви передаєте точно той самий вміст (точно той самий пароль), ви отримуєте точно ту саму «нісенітницю». + +Але ви не можете перетворити цю «нісенітницю» назад у пароль. + +##### Навіщо використовувати хешування паролів { #why-use-password-hashing } + +Якщо вашу базу даних вкрадуть, зловмисник не матиме паролів користувачів у відкритому вигляді, лише хеші. + +Тож зловмисник не зможе спробувати використати ті самі паролі в іншій системі (оскільки багато користувачів використовують той самий пароль всюди, це було б небезпечно). + +{* ../../docs_src/security/tutorial003_an_py310.py hl[82:85] *} + +#### Про `**user_dict` { #about-user-dict } + +`UserInDB(**user_dict)` означає: + +Передати ключі та значення з `user_dict` безпосередньо як аргументи ключ-значення, еквівалентно до: + +```Python +UserInDB( + username = user_dict["username"], + email = user_dict["email"], + full_name = user_dict["full_name"], + disabled = user_dict["disabled"], + hashed_password = user_dict["hashed_password"], +) +``` + +/// info | Інформація + +Для повнішого пояснення `**user_dict` перегляньте [документацію для **Додаткових моделей**](../extra-models.md#about-user-in-dict){.internal-link target=_blank}. + +/// + +## Поверніть токен { #return-the-token } + +Відповідь точки входу `token` має бути об'єктом JSON. + +Вона повинна містити `token_type`. У нашому випадку, оскільки ми використовуємо токени «Bearer», тип токена має бути «`bearer`». + +Також має бути `access_token` зі строкою, що містить наш токен доступу. + +Для цього простого прикладу ми зробимо повністю небезпечно і повернемо той самий `username` як токен. + +/// tip | Порада + +У наступному розділі ви побачите справді безпечну реалізацію з хешуванням паролів і токенами JWT. + +А поки зосередьмося на конкретних деталях, які нам потрібні. + +/// + +{* ../../docs_src/security/tutorial003_an_py310.py hl[87] *} + +/// tip | Порада + +Згідно зі специфікацією, ви маєте повертати JSON з `access_token` і `token_type`, як у цьому прикладі. + +Це те, що ви маєте зробити самостійно у своєму коді, і переконатися, що використовуєте саме ці ключі JSON. + +Це майже єдине, що ви маєте зробити правильно самі, щоб відповідати специфікації. + +Решту **FastAPI** зробить за вас. + +/// + +## Оновіть залежності { #update-the-dependencies } + +Тепер оновимо наші залежності. + +Ми хочемо отримати `current_user` лише якщо цей користувач активний. + +Тому створимо додаткову залежність `get_current_active_user`, яка своєю чергою використовує як залежність `get_current_user`. + +Обидві ці залежності просто повернуть HTTP-помилку, якщо користувача не існує або він неактивний. + +Отже, у нашій кінцевій точці ми отримаємо користувача лише якщо він існує, був правильно автентифікований і є активним: + +{* ../../docs_src/security/tutorial003_an_py310.py hl[58:66,69:74,94] *} + +/// info | Інформація + +Додатковий заголовок `WWW-Authenticate` зі значенням `Bearer`, який ми тут повертаємо, також є частиною специфікації. + +Будь-який HTTP (помилка) зі статус-кодом 401 «UNAUTHORIZED» також має повертати заголовок `WWW-Authenticate`. + +У випадку токенів носія (наш випадок) значенням цього заголовка має бути `Bearer`. + +Фактично ви можете пропустити цей додатковий заголовок, і все одно працюватиме. + +Але він наданий тут, щоб відповідати специфікаціям. + +Також можуть існувати інструменти, які очікують і використовують його (зараз або в майбутньому), і це може бути корисно вам або вашим користувачам, зараз або в майбутньому. + +У цьому користь стандартів... + +/// + +## Подивіться в дії { #see-it-in-action } + +Відкрийте інтерактивну документацію: http://127.0.0.1:8000/docs. + +### Автентифікація { #authenticate } + +Натисніть кнопку «Authorize». + +Використайте облікові дані: + +Користувач: `johndoe` + +Пароль: `secret` + + + +Після автентифікації в системі ви побачите таке: + + + +### Отримайте власні дані користувача { #get-your-own-user-data } + +Тепер використайте операцію `GET` зі шляхом `/users/me`. + +Ви отримаєте дані свого користувача, наприклад: + +```JSON +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" +} +``` + + + +Якщо натиснути значок замка й вийти з системи, а потім знову спробувати ту саму операцію, ви отримаєте помилку HTTP 401 такого вигляду: + +```JSON +{ + "detail": "Not authenticated" +} +``` + +### Неактивний користувач { #inactive-user } + +Тепер спробуйте з неактивним користувачем, автентифікуйтеся з: + +Користувач: `alice` + +Пароль: `secret2` + +І спробуйте використати операцію `GET` зі шляхом `/users/me`. + +Ви отримаєте помилку «Inactive user», наприклад: + +```JSON +{ + "detail": "Inactive user" +} +``` + +## Підсумок { #recap } + +Тепер у вас є інструменти для реалізації повної системи безпеки на основі `username` і `password` для вашого API. + +Використовуючи ці інструменти, ви можете зробити систему безпеки сумісною з будь-якою базою даних і з будь-якою моделлю користувача чи даних. + +Єдина відсутня деталь у тому, що наразі це ще не «безпечна» реалізація. + +У наступному розділі ви побачите, як використовувати безпечну бібліотеку для хешування паролів і токени JWT. diff --git a/docs/uk/docs/tutorial/sql-databases.md b/docs/uk/docs/tutorial/sql-databases.md new file mode 100644 index 000000000..991d1e33a --- /dev/null +++ b/docs/uk/docs/tutorial/sql-databases.md @@ -0,0 +1,357 @@ +# SQL (реляційні) бази даних { #sql-relational-databases } + +**FastAPI** не вимагає від вас використовувати SQL (реляційну) базу даних. Але ви можете скористатися будь-якою базою даних, яку забажаєте. + +Тут ми розглянемо приклад з SQLModel. + +**SQLModel** побудовано поверх SQLAlchemy та Pydantic. Її створив той самий автор, що і **FastAPI**, як ідеальну пару для застосунків FastAPI, яким потрібні **SQL бази даних**. + +/// tip | Порада + +Ви можете використовувати будь-яку іншу бібліотеку для SQL або NoSQL баз (інколи їх називають "ORMs"), FastAPI нічого не нав’язує. 😎 + +/// + +Оскільки SQLModel базується на SQLAlchemy, ви можете легко використовувати будь-яку базу даних, яку підтримує SQLAlchemy (а отже й SQLModel), наприклад: + +* PostgreSQL +* MySQL +* SQLite +* Oracle +* Microsoft SQL Server тощо + +У цьому прикладі ми використаємо **SQLite**, оскільки вона зберігається в одному файлі, а Python має вбудовану підтримку. Тож ви можете скопіювати цей приклад і запустити «як є». + +Пізніше, для вашого продакшн-застосунку, ви можете перейти на сервер бази даних, наприклад **PostgreSQL**. + +/// tip | Порада + +Існує офіційний генератор проєкту з **FastAPI** та **PostgreSQL**, включно з фронтендом та іншими інструментами: https://github.com/fastapi/full-stack-fastapi-template + +/// + +Це дуже простий і короткий навчальний посібник. Якщо ви хочете вивчити бази даних загалом, SQL або більш просунуті можливості, зверніться до документації SQLModel. + +## Встановіть `SQLModel` { #install-sqlmodel } + +Спочатку переконайтеся, що ви створили [віртуальне оточення](../virtual-environments.md){.internal-link target=_blank}, активували його та встановили `sqlmodel`: + +
+ +```console +$ pip install sqlmodel +---> 100% +``` + +
+ +## Створіть застосунок з однією моделлю { #create-the-app-with-a-single-model } + +Спершу створимо найпростішу версію застосунку з однією моделлю **SQLModel**. + +Потім нижче покращимо безпеку і гнучкість за допомогою кількох моделей. 🤓 + +### Створіть моделі { #create-models } + +Імпортуйте `SQLModel` і створіть модель бази даних: + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[1:11] hl[7:11] *} + +Клас `Hero` дуже схожий на модель Pydantic (фактично, під капотом це і є модель Pydantic). + +Є кілька відмінностей: + +* `table=True` каже SQLModel, що це «таблична модель» - вона має представляти **таблицю** в SQL базі даних, а не просто «модель даних» (як будь-який інший звичайний клас Pydantic). + +* `Field(primary_key=True)` каже SQLModel, що `id` - це **первинний ключ** у SQL базі даних (більше про первинні ключі в SQL див. у документації SQLModel). + + Примітка: Ми використовуємо `int | None` для поля первинного ключа, щоб у Python-коді можна було створити об’єкт без `id` (`id=None`), припускаючи, що база даних згенерує його під час збереження. SQLModel розуміє, що `id` надасть база даних, і визначає стовпець як ненульовий `INTEGER` у схемі бази даних. Докладніше див. документацію SQLModel про первинні ключі. + +* `Field(index=True)` каже SQLModel створити **SQL-індекс** для цього стовпця, що дозволить швидше виконувати пошук у базі даних під час читання даних, відфільтрованих за цим стовпцем. + + SQLModel знатиме, що оголошене як `str` стане SQL-стовпцем типу `TEXT` (або `VARCHAR`, залежно від бази). + +### Створіть рушій { #create-an-engine } + +`engine` SQLModel (під капотом це `engine` SQLAlchemy) - це те, що **утримує з’єднання** з базою даних. + +У вас має бути **єдиний об’єкт `engine`** для всього коду, що під’єднується до тієї самої бази. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[14:18] hl[14:15,17:18] *} + +Використання `check_same_thread=False` дозволяє FastAPI використовувати ту саму базу SQLite у різних потоках. Це необхідно, адже **один запит** може використати **понад один потік** (наприклад, у залежностях). + +Не переймайтеся, завдяки структурі коду ми далі забезпечимо **одну сесію SQLModel на запит**, власне цього і прагне `check_same_thread`. + +### Створіть таблиці { #create-the-tables } + +Додамо функцію, яка використовує `SQLModel.metadata.create_all(engine)`, щоб **створити таблиці** для всіх «табличних моделей». + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[21:22] hl[21:22] *} + +### Створіть залежність сесії { #create-a-session-dependency } + +**`Session`** зберігає **об’єкти в пам’яті** та відстежує зміни у даних, а потім **використовує `engine`** для взаємодії з базою даних. + +Ми створимо **залежність** FastAPI з `yield`, яка надаватиме нову `Session` для кожного запиту. Це й гарантує, що ми використовуємо одну сесію на запит. 🤓 + +Далі створимо залежність `Annotated` - `SessionDep`, щоб спростити решту коду, який її використовуватиме. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[25:30] hl[25:27,30] *} + +### Створюйте таблиці під час запуску { #create-database-tables-on-startup } + +Ми створимо таблиці бази під час запуску застосунку. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[32:37] hl[35:37] *} + +Тут ми створюємо таблиці на події запуску застосунку. + +У продакшні ви, ймовірно, використаєте міграційний скрипт, який виконується перед стартом застосунку. 🤓 + +/// tip | Порада + +SQLModel матиме утиліти міграцій-обгортки над Alembic, але поки що ви можете використовувати Alembic напряму. + +/// + +### Створіть героя { #create-a-hero } + +Оскільки кожна модель SQLModel також є моделлю Pydantic, ви можете використовувати її в тих самих **анотаціях типів**, що і моделі Pydantic. + +Наприклад, якщо ви оголосите параметр типу `Hero`, він буде прочитаний з **JSON-тіла**. + +Так само ви можете оголосити її як **тип, що повертається** функції, і форма даних з’явиться в автоматичному UI документації API. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[40:45] hl[40:45] *} + +Тут ми використовуємо залежність `SessionDep` (`Session`), щоб додати нового `Hero` до екземпляра `Session`, закомітити зміни до бази, оновити дані в `hero`, а потім повернути його. + +### Читання героїв { #read-heroes } + +Ми можемо **читати** `Hero` з бази даних, використовуючи `select()`. Ми можемо додати `limit` і `offset` для пагінації результатів. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[48:55] hl[51:52,54] *} + +### Читання одного героя { #read-one-hero } + +Ми можемо **прочитати** одного `Hero`. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[58:63] hl[60] *} + +### Видалення героя { #delete-a-hero } + +Ми також можемо **видалити** `Hero`. + +{* ../../docs_src/sql_databases/tutorial001_an_py310.py ln[66:73] hl[71] *} + +### Запуск застосунку { #run-the-app } + +Ви можете запустити застосунок: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Потім перейдіть до інтерфейсу `/docs`, ви побачите, що **FastAPI** використовує ці **моделі** для **документування** API, а також для **серіалізації** та **валідації** даних. + +
+ +
+ +## Оновіть застосунок кількома моделями { #update-the-app-with-multiple-models } + +Тепер трохи **відрефакторимо** застосунок, щоб підвищити **безпеку** та **гнучкість**. + +Якщо подивитися на попередній варіант, у UI видно, що наразі клієнт може сам обирати `id` для створюваного `Hero`. 😱 + +Цього допускати не можна - вони можуть перезаписати `id`, який уже призначений у БД. Визначення `id` має виконувати **бекенд** або **база даних**, **а не клієнт**. + +Крім того, ми створюємо для героя `secret_name`, але наразі повертаємо його всюди - це не дуже «секретно»... 😅 + +Виправимо це, додавши кілька **додаткових моделей**. Ось де SQLModel засяє. ✨ + +### Створіть кілька моделей { #create-multiple-models } + +У **SQLModel** будь-який клас моделі з `table=True` - це **таблична модель**. + +А будь-який клас моделі без `table=True` - це **модель даних**; це, по суті, просто моделі Pydantic (з парою невеликих доповнень). 🤓 + +З SQLModel ми можемо використовувати **успадкування**, щоб **уникати дублювання** полів у всіх випадках. + +#### `HeroBase` - базовий клас { #herobase-the-base-class } + +Почнімо з моделі `HeroBase`, яка містить усі **спільні поля** для всіх моделей: + +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:9] hl[7:9] *} + +#### `Hero` - «таблична модель» { #hero-the-table-model } + +Потім створімо `Hero`, фактичну «табличну модель», з **додатковими полями**, яких немає в інших моделях завжди: + +* `id` +* `secret_name` + +Оскільки `Hero` успадковується від `HeroBase`, вона **також** містить **поля**, оголошені в `HeroBase`, тож усі поля `Hero` такі: + +* `id` +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:14] hl[12:14] *} + +#### `HeroPublic` - публічна «модель даних» { #heropublic-the-public-data-model } + +Далі створимо модель `HeroPublic` - це та, що буде **повертатися** клієнтам API. + +Вона має ті самі поля, що й `HeroBase`, отже не включатиме `secret_name`. + +Нарешті, особистості наших героїв захищено! 🥷 + +Вона також перевизначає `id: int`. Роблячи це, ми укладаємо **контракт** з клієнтами API, що `id` завжди буде присутній і буде типу `int` (ніколи не `None`). + +/// tip | Порада + +Наявність у моделі відповіді гарантії, що значення завжди доступне і завжди `int` (не `None`), дуже корисна для клієнтів API - вони можуть писати значно простіший код, маючи цю визначеність. + +Також **автоматично згенеровані клієнти** матимуть простіші інтерфейси, тож розробникам, які взаємодіють з вашим API, буде значно зручніше. 😎 + +/// + +Усі поля в `HeroPublic` такі самі, як у `HeroBase`, з `id`, оголошеним як `int` (не `None`): + +* `id` +* `name` +* `age` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:18] hl[17:18] *} + +#### `HeroCreate` - «модель даних» для створення героя { #herocreate-the-data-model-to-create-a-hero } + +Тепер створимо модель `HeroCreate` - вона **валідуватиме** дані, отримані від клієнтів. + +Вона має ті ж поля, що й `HeroBase`, а також `secret_name`. + +Тепер, коли клієнти **створюють нового героя**, вони надсилають `secret_name`, він зберігається в базі даних, але ці секретні імена не повертаються клієнтам у API. + +/// tip | Порада + +Так слід поводитися з **паролями**. Приймайте їх, але не повертайте в API. + +Також потрібно **хешувати** значення паролів перед збереженням, **ніколи не зберігайте їх у відкритому вигляді**. + +/// + +Поля `HeroCreate`: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:22] hl[21:22] *} + +#### `HeroUpdate` - «модель даних» для оновлення героя { #heroupdate-the-data-model-to-update-a-hero } + +У попередній версії у нас не було способу **оновлювати героя**, але тепер, маючи **кілька моделей**, ми можемо це зробити. 🎉 + +«Модель даних» `HeroUpdate` дещо особлива: вона має **всі ті самі поля**, що й для створення нового героя, але всі поля **необов’язкові** (усі мають значення за замовчуванням). Таким чином, під час оновлення героя ви можете надіслати лише ті поля, які хочете змінити. + +Оскільки **поля фактично змінюються** (тип тепер включає `None` і значення за замовчуванням - `None`), нам потрібно **оголосити їх заново**. + +Насправді немає потреби успадковуватися від `HeroBase`, бо ми перевизначаємо всі поля. Я залишу успадкування для послідовності, але це не обов’язково. Це радше питання смаку. 🤷 + +Поля `HeroUpdate`: + +* `name` +* `age` +* `secret_name` + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[7:28] hl[25:28] *} + +### Створення з `HeroCreate` і повернення `HeroPublic` { #create-with-herocreate-and-return-a-heropublic } + +Тепер, коли в нас **кілька моделей**, ми можемо оновити частини застосунку, що їх використовують. + +У запиті ми отримуємо «модель даних» `HeroCreate`, і з неї створюємо «табличну модель» `Hero`. + +Ця нова «таблична модель» `Hero` міститиме поля, надіслані клієнтом, а також матиме `id`, згенерований базою даних. + +Далі ми повертаємо з функції цю ж «табличну модель» `Hero`. Але оскільки ми оголошуємо `response_model` як «модель даних» `HeroPublic`, **FastAPI** використає `HeroPublic` для валідації та серіалізації даних. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[56:62] hl[56:58] *} + +/// tip | Порада + +Тепер ми використовуємо `response_model=HeroPublic` замість **анотації типу, що повертається** `-> HeroPublic`, тому що значення, яке ми повертаємо, насправді - не `HeroPublic`. + +Якби ми оголосили `-> HeroPublic`, ваш редактор і лінтер справедливо зауважили б, що ви повертаєте `Hero`, а не `HeroPublic`. + +Оголошуючи це в `response_model`, ми кажемо **FastAPI** зробити свою справу, не втручаючись в анотації типів та підказки від вашого редактора й інших інструментів. + +/// + +### Читання героїв з `HeroPublic` { #read-heroes-with-heropublic } + +Можемо зробити те саме, що й раніше, щоб **читати** `Hero`, знову використовуємо `response_model=list[HeroPublic]`, щоб гарантувати коректну валідацію та серіалізацію даних. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[65:72] hl[65] *} + +### Читання одного героя з `HeroPublic` { #read-one-hero-with-heropublic } + +Ми можемо **прочитати** одного героя: + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[75:80] hl[77] *} + +### Оновлення героя з `HeroUpdate` { #update-a-hero-with-heroupdate } + +Ми можемо **оновити героя**. Для цього використовуємо HTTP-операцію `PATCH`. + +У коді ми отримуємо `dict` з усіма даними, які надіслав клієнт, - лише з тими даними, які надіслав клієнт, виключаючи будь-які значення, присутні лише як значення за замовчуванням. Для цього використовуємо `exclude_unset=True`. Це головний трюк. 🪄 + +Потім використовуємо `hero_db.sqlmodel_update(hero_data)`, щоб оновити `hero_db` даними з `hero_data`. + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[83:93] hl[83:84,88:89] *} + +### Знову видалення героя { #delete-a-hero-again } + +**Видалення** героя майже не змінилося. + +Ми не задовольнимо бажання відрефакторити все в цьому місці. 😅 + +{* ../../docs_src/sql_databases/tutorial002_an_py310.py ln[96:103] hl[101] *} + +### Знову запустіть застосунок { #run-the-app-again } + +Ви можете знову запустити застосунок: + +
+ +```console +$ fastapi dev main.py + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +Якщо ви перейдете до UI `/docs`, побачите, що він оновився і більше не очікуватиме отримати `id` від клієнта під час створення героя тощо. + +
+ +
+ +## Підсумок { #recap } + +Ви можете використовувати **SQLModel** для взаємодії з SQL базою даних і спростити код за допомогою «моделей даних» та «табличних моделей». + +Багато чого ще можна дізнатися в документації **SQLModel**, там є розширений міні-навчальний посібник з використання SQLModel з **FastAPI**. 🚀 diff --git a/docs/uk/docs/virtual-environments.md b/docs/uk/docs/virtual-environments.md new file mode 100644 index 000000000..78e7ab32f --- /dev/null +++ b/docs/uk/docs/virtual-environments.md @@ -0,0 +1,862 @@ +# Віртуальні середовища { #virtual-environments } + +Коли ви працюєте над проєктами Python, вам, імовірно, слід використовувати віртуальне середовище (або схожий механізм), щоб ізолювати пакети, які ви встановлюєте для кожного проєкту. + +/// info | Інформація + +Якщо ви вже знаєте про віртуальні середовища, як їх створювати та використовувати, можете пропустити цей розділ. 🤓 + +/// + +/// tip | Порада + +Віртуальне середовище відрізняється від змінної оточення. + +Змінна оточення - це змінна в системі, яку можуть використовувати програми. + +Віртуальне середовище - це каталог із файлами в ньому. + +/// + +/// info | Інформація + +На цій сторінці ви дізнаєтеся, як використовувати віртуальні середовища і як вони працюють. + +Якщо ви готові прийняти інструмент, що керує всім за вас (включно з установленням Python), спробуйте uv. + +/// + +## Створіть проєкт { #create-a-project } + +Спочатку створіть каталог для вашого проєкту. + +Зазвичай я створюю каталог з назвою `code` у моєму домашньому каталозі користувача. + +І всередині нього я створюю окремий каталог на кожен проєкт. + +
+ +```console +// Перейдіть до домашнього каталогу +$ cd +// Створіть каталог для всіх ваших проєктів з кодом +$ mkdir code +// Перейдіть у цей каталог code +$ cd code +// Створіть каталог для цього проєкту +$ mkdir awesome-project +// Перейдіть до каталогу цього проєкту +$ cd awesome-project +``` + +
+ +## Створіть віртуальне середовище { #create-a-virtual-environment } + +Коли ви починаєте працювати над проєктом Python уперше, створіть віртуальне середовище у вашому проєкті **у вашому проєкті**. + +/// tip | Порада + +Це потрібно робити лише один раз на проєкт, не щоразу, коли ви працюєте. + +/// + +//// tab | `venv` + +Щоб створити віртуальне середовище, ви можете використати модуль `venv`, який постачається разом із Python. + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | Що означає ця команда + +* `python`: використати програму з назвою `python` +* `-m`: викликати модуль як скрипт, далі ми вкажемо, який модуль +* `venv`: використати модуль з назвою `venv`, який зазвичай уже встановлено з Python +* `.venv`: створити віртуальне середовище в новому каталозі `.venv` + +/// + +//// + +//// tab | `uv` + +Якщо у вас встановлено `uv`, ви можете використати його для створення віртуального середовища. + +
+ +```console +$ uv venv +``` + +
+ +/// tip | Порада + +Типово `uv` створить віртуальне середовище в каталозі з назвою `.venv`. + +Але ви можете налаштувати це, передавши додатковий аргумент з назвою каталогу. + +/// + +//// + +Ця команда створює нове віртуальне середовище в каталозі з назвою `.venv`. + +/// details | `.venv` або інша назва + +Ви можете створити віртуальне середовище в іншому каталозі, але існує усталена домовленість називати його `.venv`. + +/// + +## Активуйте віртуальне середовище { #activate-the-virtual-environment } + +Активуйте нове віртуальне середовище, щоб будь-яка команда Python, яку ви запускаєте, або пакет, який ви встановлюєте, використовували його. + +/// tip | Порада + +Робіть це щоразу, коли ви починаєте нову сесію термінала для роботи над проєктом. + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Або якщо ви використовуєте Bash для Windows (напр., Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip | Порада + +Кожного разу, коли ви встановлюєте новий пакет у це середовище, активуйте середовище знову. + +Це гарантує, що якщо ви використовуєте програму термінала (CLI), встановлену цим пакетом, ви використовуєте саме ту з вашого віртуального середовища, а не будь-яку іншу, яка може бути встановлена глобально, імовірно з іншою версією, ніж вам потрібно. + +/// + +## Перевірте активність віртуального середовища { #check-the-virtual-environment-is-active } + +Перевірте, що віртуальне середовище активне (попередня команда спрацювала). + +/// tip | Порада + +Це необов'язково, але це гарний спосіб перевірити, що все працює як очікується і ви використовуєте саме те віртуальне середовище, яке планували. + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +Якщо показано бінарний файл `python` за шляхом `.venv/bin/python` усередині вашого проєкту (у цьому випадку `awesome-project`), тоді все спрацювало. 🎉 + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +Якщо показано бінарний файл `python` за шляхом `.venv\Scripts\python` усередині вашого проєкту (у цьому випадку `awesome-project`), тоді все спрацювало. 🎉 + +//// + +## Оновіть `pip` { #upgrade-pip } + +/// tip | Порада + +Якщо ви використовуєте `uv`, ви використовуватимете його для встановлення замість `pip`, тож вам не потрібно оновлювати `pip`. 😎 + +/// + +Якщо ви використовуєте `pip` для встановлення пакетів (він іде за замовчуванням із Python), вам слід оновити його до найновішої версії. + +Багато дивних помилок під час встановлення пакета вирішуються тим, що спочатку оновлюють `pip`. + +/// tip | Порада + +Зазвичай це роблять один раз, відразу після створення віртуального середовища. + +/// + +Переконайтеся, що віртуальне середовище активне (командою вище), а потім виконайте: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +/// tip | Порада + +Іноді ви можете отримати помилку **`No module named pip`** при спробі оновити pip. + +Якщо це сталося, встановіть і оновіть pip за допомогою команди нижче: + +
+ +```console +$ python -m ensurepip --upgrade + +---> 100% +``` + +
+ +Ця команда встановить pip, якщо він ще не встановлений, і також гарантує, що встановлена версія pip принаймні така ж нова, як доступна в `ensurepip`. + +/// + +## Додайте `.gitignore` { #add-gitignore } + +Якщо ви використовуєте Git (варто це робити), додайте файл `.gitignore`, щоб виключити з Git усе у вашому `.venv`. + +/// tip | Порада + +Якщо ви використали `uv` для створення віртуального середовища, він уже зробив це за вас, можете пропустити цей крок. 😎 + +/// + +/// tip | Порада + +Зробіть це один раз, відразу після створення віртуального середовища. + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | Що означає ця команда + +* `echo "*"`: «виведе» текст `*` у термінал (наступна частина трохи це змінює) +* `>`: усе, що команда ліворуч від `>` «виводить» у термінал, не слід друкувати, натомість записати у файл, вказаний праворуч від `>` +* `.gitignore`: назва файлу, куди слід записати текст + +А `*` для Git означає «все». Тож він ігноруватиме все в каталозі `.venv`. + +Ця команда створить файл `.gitignore` із вмістом: + +```gitignore +* +``` + +/// + +## Встановіть пакети { #install-packages } + +Після активації середовища ви можете встановлювати в нього пакети. + +/// tip | Порада + +Робіть це один раз під час встановлення або оновлення пакетів, потрібних вашому проєкту. + +Якщо вам потрібно оновити версію або додати новий пакет, ви зробите це знову. + +/// + +### Встановіть пакети безпосередньо { #install-packages-directly } + +Якщо ви поспішаєте та не хочете використовувати файл для оголошення вимог вашого проєкту до пакетів, ви можете встановити їх безпосередньо. + +/// tip | Порада + +Дуже добра ідея - записати пакети та версії, потрібні вашій програмі, у файл (наприклад, `requirements.txt` або `pyproject.toml`). + +/// + +//// tab | `pip` + +
+ +```console +$ pip install "fastapi[standard]" + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Якщо у вас є `uv`: + +
+ +```console +$ uv pip install "fastapi[standard]" +---> 100% +``` + +
+ +//// + +### Встановіть з `requirements.txt` { #install-from-requirements-txt } + +Якщо у вас є `requirements.txt`, ви можете використати його для встановлення перелічених там пакетів. + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +Якщо у вас є `uv`: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | `requirements.txt` + +`requirements.txt` із деякими пакетами може виглядати так: + +```requirements.txt +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +/// + +## Запустіть вашу програму { #run-your-program } + +Після активації віртуального середовища ви можете запустити вашу програму, і вона використовуватиме Python із вашого віртуального середовища з пакетами, які ви там встановили. + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## Налаштуйте ваш редактор { #configure-your-editor } + +Ймовірно, ви використовуєте редактор коду, переконайтеся, що ви налаштували його на використання того самого віртуального середовища, яке ви створили (швидше за все, він визначить його автоматично), щоб отримувати автодоповнення та підсвічування помилок. + +Наприклад: + +* VS Code +* PyCharm + +/// tip | Порада + +Зазвичай це потрібно робити лише один раз, коли ви створюєте віртуальне середовище. + +/// + +## Деактивуйте віртуальне середовище { #deactivate-the-virtual-environment } + +Коли ви завершили роботу над проєктом, ви можете деактивувати віртуальне середовище. + +
+ +```console +$ deactivate +``` + +
+ +Таким чином, коли ви запустите `python`, він не намагатиметься запускатися з того віртуального середовища з установленими там пакетами. + +## Готові до роботи { #ready-to-work } + +Тепер ви готові почати працювати над вашим проєктом. + +/// tip | Порада + +Хочете зрозуміти, що це все було вище? + +Продовжуйте читати. 👇🤓 + +/// + +## Навіщо віртуальні середовища { #why-virtual-environments } + +Щоб працювати з FastAPI, вам потрібно встановити Python. + +Після цього вам потрібно буде встановити FastAPI та інші пакети, які ви хочете використовувати. + +Для встановлення пакетів зазвичай використовують команду `pip`, що постачається з Python (або схожі альтернативи). + +Однак, якщо ви просто користуватиметеся `pip` напряму, пакети встановлюватимуться у ваше глобальне середовище Python (глобальну інсталяцію Python). + +### Проблема { #the-problem } + +То в чому ж проблема встановлення пакетів у глобальне середовище Python? + +З часом ви, вірогідно, писатимете багато різних програм, які залежать від різних пакетів. І деякі з цих ваших проєктів залежатимуть від різних версій одного й того ж пакета. 😱 + +Наприклад, ви можете створити проєкт із назвою `philosophers-stone`, ця програма залежить від іншого пакета з назвою `harry`, використовуючи версію `1`. Тож вам потрібно встановити `harry`. + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` + +Потім, трохи згодом, ви створюєте інший проєкт із назвою `prisoner-of-azkaban`, і цей проєкт також залежить від `harry`, але йому потрібна версія `harry` `3`. + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] +``` + +Але тепер проблема в тому, що якщо ви встановлюєте пакети глобально (у глобальне середовище), а не у локальне віртуальне середовище, вам доведеться вибирати, яку версію `harry` встановити. + +Якщо ви хочете запустити `philosophers-stone`, вам спочатку потрібно встановити `harry` версії `1`, наприклад, так: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +У підсумку у вас буде встановлено `harry` версії `1` у глобальному середовищі Python. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -->|requires| harry-1 + end +``` + +Але якщо ви захочете запустити `prisoner-of-azkaban`, вам доведеться видалити `harry` версії `1` і встановити `harry` версії `3` (або просто встановлення версії `3` автоматично видалить версію `1`). + +
+ +```console +$ pip install "harry==3" +``` + +
+ +У підсумку у вас буде встановлено `harry` версії `3` у глобальному середовищі Python. + +А якщо ви знову спробуєте запустити `philosophers-stone`, є шанс, що він не працюватиме, тому що йому потрібен `harry` версії `1`. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -.-x|⛔️| harry-1 + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --> |requires| harry-3 + end +``` + +/// tip | Порада + +У пакетах Python дуже поширена практика намагатися якнайкраще уникати несумісних змін у нових версіях, але краще підстрахуватися та встановлювати новіші версії свідомо і тоді, коли ви можете запустити тести, щоб перевірити, що все працює коректно. + +/// + +Тепер уявіть те саме з багатьма іншими пакетами, від яких залежать усі ваші проєкти. Це дуже складно керувати. І ви, імовірно, запускатимете деякі проєкти з деякими несумісними версіями пакетів і не розумітимете, чому щось не працює. + +Також, залежно від вашої операційної системи (напр., Linux, Windows, macOS), у ній може бути вже встановлений Python. І в такому разі, імовірно, уже будуть попередньо встановлені деякі пакети з певними версіями, потрібними вашій системі. Якщо ви встановлюєте пакети в глобальне середовище Python, ви можете зламати деякі програми, що постачаються з вашою операційною системою. + +## Де встановлюються пакети { #where-are-packages-installed } + +Коли ви встановлюєте Python, він створює на вашому комп'ютері кілька каталогів із деякими файлами. + +Деякі з цих каталогів відповідають за зберігання всіх пакетів, які ви встановлюєте. + +Коли ви запускаєте: + +
+ +```console +// Не запускайте це зараз, це лише приклад 🤓 +$ pip install "fastapi[standard]" +---> 100% +``` + +
+ +Це завантажить стиснений файл з кодом FastAPI, зазвичай із PyPI. + +Також будуть завантажені файли для інших пакетів, від яких залежить FastAPI. + +Потім усе це буде розпаковано та покладено в каталог на вашому комп'ютері. + +Типово ці завантажені та розпаковані файли будуть покладені в каталог, що постачається з вашою інсталяцією Python, це глобальне середовище. + +## Що таке віртуальні середовища { #what-are-virtual-environments } + +Рішенням проблеми з наявністю всіх пакетів у глобальному середовищі є використання віртуального середовища для кожного проєкту, над яким ви працюєте. + +Віртуальне середовище - це каталог, дуже схожий на глобальний, у якому ви можете встановлювати пакети для конкретного проєкту. + +Таким чином кожен проєкт матиме власне віртуальне середовище (каталог `.venv`) із власними пакетами. + +```mermaid +flowchart TB + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) --->|requires| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --->|requires| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## Що означає активація віртуального середовища { #what-does-activating-a-virtual-environment-mean } + +Коли ви активуєте віртуальне середовище, наприклад так: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Або якщо ви використовуєте Bash для Windows (напр., Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +Ця команда створить або змінить деякі [Змінні оточення](environment-variables.md){.internal-link target=_blank}, які будуть доступні для наступних команд. + +Однією з цих змінних є змінна `PATH`. + +/// tip | Порада + +Ви можете дізнатися більше про змінну оточення `PATH` у розділі [Змінні оточення](environment-variables.md#path-environment-variable){.internal-link target=_blank}. + +/// + +Активація віртуального середовища додає його шлях `.venv/bin` (на Linux і macOS) або `.venv\Scripts` (на Windows) до змінної оточення `PATH`. + +Скажімо, до активації середовища змінна `PATH` виглядала так: + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +Це означає, що система шукатиме програми в: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +Це означає, що система шукатиме програми в: + +* `C:\Windows\System32` + +//// + +Після активації віртуального середовища змінна `PATH` виглядатиме приблизно так: + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +Це означає, що система тепер спочатку шукатиме програми в: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +перед тим, як шукати в інших каталогах. + +Тож коли ви введете `python` у терміналі, система знайде програму Python у + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +і використає саме її. + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +Це означає, що система тепер спочатку шукатиме програми в: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +перед тим, як шукати в інших каталогах. + +Тож коли ви введете `python` у терміналі, система знайде програму Python у + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +і використає саме її. + +//// + +Важлива деталь: шлях до віртуального середовища буде додано на початок змінної `PATH`. Система знайде його раніше за будь-який інший доступний Python. Таким чином, коли ви запускаєте `python`, використовується саме Python із віртуального середовища, а не будь-який інший `python` (наприклад, з глобального середовища). + +Активація віртуального середовища також змінює ще кілька речей, але це одна з найважливіших. + +## Перевірка віртуального середовища { #checking-a-virtual-environment } + +Коли ви перевіряєте, чи активне віртуальне середовище, наприклад так: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +Це означає, що програма `python`, яка буде використана, знаходиться у віртуальному середовищі. + +На Linux і macOS використовують `which`, а в Windows PowerShell - `Get-Command`. + +Принцип роботи цієї команди в тому, що вона перевіряє змінну оточення `PATH`, проходячи по кожному шляху по порядку, шукаючи програму з назвою `python`. Щойно вона її знайде, вона покаже вам шлях до цієї програми. + +Найважливіше, що коли ви викликаєте `python`, це рівно той «`python`», який буде виконаний. + +Отже, ви можете підтвердити, чи перебуваєте в правильному віртуальному середовищі. + +/// tip | Порада + +Легко активувати одне віртуальне середовище, отримати один Python, а потім перейти до іншого проєкту. + +І другий проєкт не працюватиме, бо ви використовуєте некоректний Python з віртуального середовища іншого проєкту. + +Корисно вміти перевіряти, який саме `python` використовується. 🤓 + +/// + +## Навіщо деактивувати віртуальне середовище { #why-deactivate-a-virtual-environment } + +Наприклад, ви працюєте над проєктом `philosophers-stone`, активували його віртуальне середовище, встановили пакети та працюєте з цим середовищем. + +А потім ви хочете працювати над іншим проєктом `prisoner-of-azkaban`. + +Ви переходите до цього проєкту: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +Якщо ви не деактивуєте віртуальне середовище для `philosophers-stone`, коли ви запустите `python` у терміналі, він спробує використовувати Python із `philosophers-stone`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// Помилка імпорту sirius, його не встановлено 😱 +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +Але якщо ви деактивуєте віртуальне середовище і активуєте нове для `prisoner-of-askaban`, тоді при запуску `python` він використовуватиме Python із віртуального середовища в `prisoner-of-azkaban`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// Вам не потрібно бути в старому каталозі, щоб деактивувати, це можна зробити будь-де, навіть після переходу до іншого проєкту 😎 +$ deactivate + +// Активуйте віртуальне середовище в prisoner-of-azkaban/.venv 🚀 +$ source .venv/bin/activate + +// Тепер, коли ви запускаєте python, він знайде пакет sirius, встановлений у цьому віртуальному середовищі ✨ +$ python main.py + +I solemnly swear 🐺 +``` + +
+ +## Альтернативи { #alternatives } + +Це простий посібник, щоб ви швидко стартували та зрозуміли, як усе працює «під капотом». + +Існує багато альтернатив керування віртуальними середовищами, залежностями пакетів (вимогами), проєктами. + +Коли будете готові й захочете використовувати інструмент для керування всім проєктом, залежностями пакетів, віртуальними середовищами тощо, я раджу спробувати uv. + +`uv` уміє багато чого, зокрема: + +* Встановлювати Python для вас, включно з різними версіями +* Керувати віртуальним середовищем ваших проєктів +* Встановлювати пакети +* Керувати залежностями пакетів і версіями у вашому проєкті +* Гарантувати, що у вас є точний набір пакетів і версій для встановлення, включно з їхніми залежностями, щоб ви були певні, що зможете запустити ваш проєкт у продакшені точно так само, як і на вашому комп'ютері під час розробки - це називається блокуванням +* І багато іншого + +## Висновок { #conclusion } + +Якщо ви все це прочитали й зрозуміли, тепер ви знаєте значно більше про віртуальні середовища, ніж багато розробників. 🤓 + +Знання цих деталей, найімовірніше, стане в пригоді в майбутньому, коли ви налагоджуватимете щось, що виглядає складним, але ви знатимете, як усе працює «під капотом». 😎 From 82d90c51b4ca557c6f942b6800fba2ff3cc900c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 14 Feb 2026 08:43:37 +0000 Subject: [PATCH 137/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5e1eaf937..61366bb84 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for uk (add-missing). PR [#14922](https://github.com/fastapi/fastapi/pull/14922) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for zh-hant (update-all and add-missing). PR [#14921](https://github.com/fastapi/fastapi/pull/14921) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for fr (update-all and add-missing). PR [#14920](https://github.com/fastapi/fastapi/pull/14920) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for de (update-all) . PR [#14910](https://github.com/fastapi/fastapi/pull/14910) by [@YuriiMotov](https://github.com/YuriiMotov). From 01e2e1088cc2bd80a707208234af6f9570e06963 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Sat, 14 Feb 2026 09:57:01 +0100 Subject: [PATCH 138/195] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20ko=20(update-all=20and=20add-missing)=20(#14923)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update all and add missing * 🎨 Auto format --------- Co-authored-by: github-actions[bot] --- docs/ko/docs/_llm-test.md | 12 +- docs/ko/docs/advanced/additional-responses.md | 4 +- .../docs/advanced/additional-status-codes.md | 8 +- .../ko/docs/advanced/advanced-dependencies.md | 8 +- .../ko/docs/advanced/advanced-python-types.md | 61 ++++++ docs/ko/docs/advanced/async-tests.md | 8 +- docs/ko/docs/advanced/behind-a-proxy.md | 12 +- docs/ko/docs/advanced/custom-response.md | 32 +-- docs/ko/docs/advanced/dataclasses.md | 2 +- docs/ko/docs/advanced/events.md | 12 +- docs/ko/docs/advanced/generate-clients.md | 8 +- docs/ko/docs/advanced/index.md | 2 +- docs/ko/docs/advanced/middleware.md | 8 +- docs/ko/docs/advanced/openapi-callbacks.md | 34 ++-- docs/ko/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 16 +- .../advanced/response-change-status-code.md | 2 +- docs/ko/docs/advanced/response-cookies.md | 4 +- docs/ko/docs/advanced/response-directly.md | 2 +- docs/ko/docs/advanced/response-headers.md | 8 +- .../docs/advanced/security/http-basic-auth.md | 8 +- docs/ko/docs/advanced/settings.md | 24 +-- docs/ko/docs/advanced/sub-applications.md | 6 +- docs/ko/docs/advanced/templates.md | 2 +- docs/ko/docs/advanced/testing-events.md | 4 +- docs/ko/docs/advanced/testing-websockets.md | 4 +- .../docs/advanced/using-request-directly.md | 2 +- docs/ko/docs/advanced/websockets.md | 18 +- docs/ko/docs/advanced/wsgi.md | 2 +- docs/ko/docs/alternatives.md | 8 +- docs/ko/docs/deployment/docker.md | 10 +- docs/ko/docs/deployment/https.md | 4 +- docs/ko/docs/deployment/manually.md | 2 +- docs/ko/docs/deployment/server-workers.md | 12 +- docs/ko/docs/deployment/versions.md | 4 +- docs/ko/docs/environment-variables.md | 34 ++-- docs/ko/docs/features.md | 10 +- .../authentication-error-status-code.md | 4 +- docs/ko/docs/how-to/conditional-openapi.md | 4 +- docs/ko/docs/how-to/configure-swagger-ui.md | 8 +- docs/ko/docs/how-to/custom-docs-ui-assets.md | 16 +- docs/ko/docs/how-to/extending-openapi.md | 10 +- docs/ko/docs/how-to/graphql.md | 2 +- ...migrate-from-pydantic-v1-to-pydantic-v2.md | 4 +- .../docs/how-to/separate-openapi-schemas.md | 2 +- docs/ko/docs/index.md | 10 +- docs/ko/docs/project-generation.md | 2 +- docs/ko/docs/python-types.md | 186 ++++-------------- docs/ko/docs/resources/index.md | 2 +- docs/ko/docs/translation-banner.md | 11 ++ docs/ko/docs/tutorial/background-tasks.md | 6 +- docs/ko/docs/tutorial/bigger-applications.md | 52 ++--- docs/ko/docs/tutorial/body-fields.md | 4 +- docs/ko/docs/tutorial/body-multiple-params.md | 7 - docs/ko/docs/tutorial/body-nested-models.md | 4 +- docs/ko/docs/tutorial/body.md | 8 +- docs/ko/docs/tutorial/cookie-param-models.md | 8 +- docs/ko/docs/tutorial/cookie-params.md | 4 +- docs/ko/docs/tutorial/cors.md | 4 +- docs/ko/docs/tutorial/debugging.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +-- ...pendencies-in-path-operation-decorators.md | 30 +-- .../dependencies/dependencies-with-yield.md | 24 +-- .../dependencies/global-dependencies.md | 5 +- docs/ko/docs/tutorial/dependencies/index.md | 6 +- .../tutorial/dependencies/sub-dependencies.md | 6 +- docs/ko/docs/tutorial/encoder.md | 2 +- docs/ko/docs/tutorial/extra-models.md | 16 +- docs/ko/docs/tutorial/first-steps.md | 22 +-- docs/ko/docs/tutorial/handling-errors.md | 16 +- docs/ko/docs/tutorial/header-param-models.md | 4 +- docs/ko/docs/tutorial/metadata.md | 12 +- docs/ko/docs/tutorial/middleware.md | 8 +- .../tutorial/path-operation-configuration.md | 8 +- .../path-params-numeric-validations.md | 14 +- docs/ko/docs/tutorial/path-params.md | 32 +-- .../tutorial/query-params-str-validations.md | 48 ++--- docs/ko/docs/tutorial/query-params.md | 6 +- docs/ko/docs/tutorial/request-files.md | 14 +- docs/ko/docs/tutorial/request-form-models.md | 4 +- .../docs/tutorial/request-forms-and-files.md | 4 +- docs/ko/docs/tutorial/request-forms.md | 8 +- docs/ko/docs/tutorial/response-model.md | 6 +- docs/ko/docs/tutorial/response-status-code.md | 10 +- docs/ko/docs/tutorial/schema-extra-example.md | 2 +- docs/ko/docs/tutorial/security/first-steps.md | 6 +- .../tutorial/security/get-current-user.md | 2 +- docs/ko/docs/tutorial/security/oauth2-jwt.md | 26 +-- .../docs/tutorial/security/simple-oauth2.md | 4 +- docs/ko/docs/tutorial/sql-databases.md | 2 +- docs/ko/docs/tutorial/static-files.md | 2 +- docs/ko/docs/tutorial/testing.md | 12 +- docs/ko/docs/virtual-environments.md | 24 +-- 93 files changed, 548 insertions(+), 620 deletions(-) create mode 100644 docs/ko/docs/advanced/advanced-python-types.md create mode 100644 docs/ko/docs/translation-banner.md diff --git a/docs/ko/docs/_llm-test.md b/docs/ko/docs/_llm-test.md index 272af763a..92bfb83f6 100644 --- a/docs/ko/docs/_llm-test.md +++ b/docs/ko/docs/_llm-test.md @@ -35,7 +35,7 @@ //// tab | 테스트 -어제 제 친구가 이렇게 썼습니다: "If you spell incorrectly correctly, you have spelled it incorrectly". 이에 저는 이렇게 답했습니다: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"". +어제 제 친구가 이렇게 썼습니다: "If you spell incorrectly correctly, you have spelled it incorrectly". 이에 저는 이렇게 답했습니다: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'". /// note | 참고 @@ -256,15 +256,15 @@ works(foo="bar") # 이건 동작합니다 🎉 //// tab | 테스트 -* 당신 -* 당신의 +* 여러분 +* 여러분의 * 예: (e.g.) * 등 (etc.) -* `int`로서의 `foo` -* `str`로서의 `bar` -* `list`로서의 `baz` +* `foo`로서의 `int` +* `bar`로서의 `str` +* `baz`로서의 `list` * 튜토리얼 - 사용자 가이드 * 고급 사용자 가이드 diff --git a/docs/ko/docs/advanced/additional-responses.md b/docs/ko/docs/advanced/additional-responses.md index a6f51f5b9..2ed6bc3fc 100644 --- a/docs/ko/docs/advanced/additional-responses.md +++ b/docs/ko/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ 예를 들어, 상태 코드 `404`와 Pydantic 모델 `Message`를 사용하는 다른 응답을 선언하려면 다음과 같이 작성할 수 있습니다: -{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py310.py hl[18,22] *} /// note | 참고 @@ -203,7 +203,7 @@ 또한 `response_model`을 사용하는 상태 코드 `200` 응답을 선언하되, 커스텀 `example`을 포함할 수도 있습니다: -{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py310.py hl[20:31] *} 이 모든 내용은 OpenAPI에 결합되어 포함되고, API 문서에 표시됩니다: diff --git a/docs/ko/docs/advanced/additional-status-codes.md b/docs/ko/docs/advanced/additional-status-codes.md index 64a7eabd5..e3c898044 100644 --- a/docs/ko/docs/advanced/additional-status-codes.md +++ b/docs/ko/docs/advanced/additional-status-codes.md @@ -1,14 +1,14 @@ # 추가 상태 코드 { #additional-status-codes } -기본적으로 **FastAPI**는 응답을 `JSONResponse`를 사용하여 반환하며, *경로 작업(path operation)*에서 반환한 내용을 해당 `JSONResponse` 안에 넣어 반환합니다. +기본적으로 **FastAPI**는 응답을 `JSONResponse`를 사용하여 반환하며, *경로 처리*에서 반환한 내용을 해당 `JSONResponse` 안에 넣어 반환합니다. -기본 상태 코드 또는 *경로 작업*에서 설정한 상태 코드를 사용합니다. +기본 상태 코드 또는 *경로 처리*에서 설정한 상태 코드를 사용합니다. ## 추가 상태 코드 { #additional-status-codes_1 } 기본 상태 코드와 별도로 추가 상태 코드를 반환하려면 `JSONResponse`와 같이 `Response`를 직접 반환하고 추가 상태 코드를 직접 설정할 수 있습니다. -예를 들어 항목을 업데이트할 수 있는 *경로 작업*이 있고 성공 시 200 “OK”의 HTTP 상태 코드를 반환한다고 가정해 보겠습니다. +예를 들어 항목을 업데이트할 수 있는 *경로 처리*가 있고 성공 시 200 “OK”의 HTTP 상태 코드를 반환한다고 가정해 보겠습니다. 하지만 새로운 항목을 허용하기를 원할 것입니다. 그리고 항목이 이전에 존재하지 않았다면 이를 생성하고 HTTP 상태 코드 201 "Created"를 반환합니다. @@ -16,7 +16,7 @@ {* ../../docs_src/additional_status_codes/tutorial001_an_py310.py hl[4,25] *} -/// warning | 경고 +/// warning 위의 예제처럼 `Response`를 직접 반환하면 바로 반환됩니다. diff --git a/docs/ko/docs/advanced/advanced-dependencies.md b/docs/ko/docs/advanced/advanced-dependencies.md index fe1606258..eb5d35d95 100644 --- a/docs/ko/docs/advanced/advanced-dependencies.md +++ b/docs/ko/docs/advanced/advanced-dependencies.md @@ -18,7 +18,7 @@ Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법 이를 위해 `__call__` 메서드를 선언합니다: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[12] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[12] *} 이 경우, **FastAPI**는 추가 매개변수와 하위 의존성을 확인하기 위해 `__call__`을 사용하게 되며, 나중에 *경로 처리 함수*에서 매개변수에 값을 전달할 때 이를 호출하게 됩니다. @@ -27,7 +27,7 @@ Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법 이제 `__init__`을 사용하여 의존성을 "매개변수화"할 수 있는 인스턴스의 매개변수를 선언할 수 있습니다: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[9] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[9] *} 이 경우, **FastAPI**는 `__init__`에 전혀 관여하지 않으며, 우리는 이 메서드를 코드에서 직접 사용하게 됩니다. @@ -35,7 +35,7 @@ Python에는 클래스의 인스턴스를 "호출 가능"하게 만드는 방법 다음과 같이 이 클래스의 인스턴스를 생성할 수 있습니다: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[18] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[18] *} 이렇게 하면 `checker.fixed_content` 속성에 `"bar"`라는 값을 담아 의존성을 "매개변수화"할 수 있습니다. @@ -51,7 +51,7 @@ checker(q="somequery") ...그리고 이때 반환되는 값을 *경로 처리 함수*의 의존성 값으로, `fixed_content_included` 매개변수에 전달합니다: -{* ../../docs_src/dependencies/tutorial011_an_py39.py hl[22] *} +{* ../../docs_src/dependencies/tutorial011_an_py310.py hl[22] *} /// tip | 팁 diff --git a/docs/ko/docs/advanced/advanced-python-types.md b/docs/ko/docs/advanced/advanced-python-types.md new file mode 100644 index 000000000..0bd8aa543 --- /dev/null +++ b/docs/ko/docs/advanced/advanced-python-types.md @@ -0,0 +1,61 @@ +# 고급 Python 타입 { #advanced-python-types } + +Python 타입을 다룰 때 유용할 수 있는 몇 가지 추가 아이디어를 소개합니다. + +## `Union` 또는 `Optional` 사용 { #using-union-or-optional } + +어떤 이유로 코드에서 `|`를 사용할 수 없다면, 예를 들어 타입 어노테이션이 아니라 `response_model=` 같은 곳이라면, 파이프 문자(`|`) 대신 `typing`의 `Union`을 사용할 수 있습니다. + +예를 들어, 어떤 값이 `str` 또는 `None`이 될 수 있다고 선언할 수 있습니다: + +```python +from typing import Union + + +def say_hi(name: Union[str, None]): + print(f"Hi {name}!") +``` + +`typing`에는 `None`이 될 수 있음을 선언하는 축약형으로 `Optional`도 있습니다. + +아주 개인적인 관점에서의 팁입니다: + +- 🚨 `Optional[SomeType]` 사용은 피하세요 +- 대신 ✨ **`Union[SomeType, None]`를 사용하세요** ✨. + +둘은 동등하며 내부적으로도 같습니다. 하지만 단어 "optional"은 값이 선택 사항이라는 인상을 주는 반면, 실제 의미는 "값이 `None`이 될 수 있다"는 뜻입니다. 값이 선택 사항이 아니라 여전히 필수인 경우에도 그렇습니다. + +`Union[SomeType, None]`가 의미를 더 명확하게 드러낸다고 생각합니다. + +이는 단지 단어와 명칭의 문제입니다. 하지만 이런 단어가 여러분과 팀원이 코드를 어떻게 생각하는지에 영향을 줄 수 있습니다. + +예를 들어, 다음 함수를 보세요: + +```python +from typing import Optional + + +def say_hi(name: Optional[str]): + print(f"Hey {name}!") +``` + +매개변수 `name`은 `Optional[str]`로 정의되어 있지만, 사실 선택적이지 않습니다. 이 매개변수 없이 함수를 호출할 수 없습니다: + +```Python +say_hi() # 이런, 에러가 발생합니다! 😱 +``` + +`name` 매개변수는 기본값이 없기 때문에 여전히 필수입니다(선택적이 아님). 대신, `name`에는 `None`을 전달할 수 있습니다: + +```Python +say_hi(name=None) # 작동합니다. None은 유효합니다 🎉 +``` + +좋은 소식은, 대부분의 경우 타입의 합집합을 정의할 때 그냥 `|`를 사용할 수 있다는 점입니다: + +```python +def say_hi(name: str | None): + print(f"Hey {name}!") +``` + +그래서 보통은 `Optional`과 `Union` 같은 이름에 대해 걱정하지 않으셔도 됩니다. 😎 diff --git a/docs/ko/docs/advanced/async-tests.md b/docs/ko/docs/advanced/async-tests.md index 6c8593681..0479ac41d 100644 --- a/docs/ko/docs/advanced/async-tests.md +++ b/docs/ko/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ `main.py` 파일은 다음과 같습니다: -{* ../../docs_src/async_tests/app_a_py39/main.py *} +{* ../../docs_src/async_tests/app_a_py310/main.py *} `test_main.py` 파일에는 `main.py`에 대한 테스트가 있으며, 이제 다음과 같이 보일 수 있습니다: -{* ../../docs_src/async_tests/app_a_py39/test_main.py *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py *} ## 실행하기 { #run-it } @@ -56,7 +56,7 @@ $ pytest `@pytest.mark.anyio` 마커는 pytest에게 이 테스트 함수가 비동기로 호출되어야 한다고 알려줍니다: -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[7] *} /// tip | 팁 @@ -66,7 +66,7 @@ $ pytest 그 다음 앱으로 `AsyncClient`를 만들고, `await`를 사용해 비동기 요청을 보낼 수 있습니다. -{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py310/test_main.py hl[9:12] *} 이는 다음과 동등합니다: diff --git a/docs/ko/docs/advanced/behind-a-proxy.md b/docs/ko/docs/advanced/behind-a-proxy.md index 92bddac51..825592c06 100644 --- a/docs/ko/docs/advanced/behind-a-proxy.md +++ b/docs/ko/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" 예를 들어, *경로 처리* `/items/`를 정의했다고 해봅시다: -{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py310.py hl[6] *} 클라이언트가 `/items`로 접근하면, 기본적으로 `/items/`로 리디렉션됩니다. @@ -115,7 +115,7 @@ sequenceDiagram 코드는 모두 `/app`만 있다고 가정하고 작성되어 있는데도 말입니다. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[6] *} 그리고 프록시는 요청을 앱 서버(아마 FastAPI CLI를 통해 실행되는 Uvicorn)로 전달하기 전에, 동적으로 **경로 접두사**를 **"제거"**합니다. 그래서 애플리케이션은 여전히 `/app`에서 서비스된다고 믿게 되고, 코드 전체를 `/api/v1` 접두사를 포함하도록 수정할 필요가 없어집니다. @@ -193,7 +193,7 @@ ASGI 사양은 이 사용 사례를 위해 `root_path`를 정의합니다. 여기서는 데모 목적을 위해 메시지에 포함하고 있습니다. -{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py310.py hl[8] *} 그 다음 Uvicorn을 다음과 같이 시작하면: @@ -220,7 +220,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 또는 `--root-path` 같은 커맨드 라인 옵션(또는 동등한 방법)을 제공할 수 없는 경우, FastAPI 앱을 생성할 때 `root_path` 파라미터를 설정할 수 있습니다: -{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} +{* ../../docs_src/behind_a_proxy/tutorial002_py310.py hl[3] *} `FastAPI`에 `root_path`를 전달하는 것은 Uvicorn이나 Hypercorn에 커맨드 라인 옵션 `--root-path`를 전달하는 것과 동일합니다. @@ -400,7 +400,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 예: -{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} +{* ../../docs_src/behind_a_proxy/tutorial003_py310.py hl[4:7] *} 다음과 같은 OpenAPI 스키마를 생성합니다: @@ -455,7 +455,7 @@ OpenAPI 사양에서 `servers` 속성은 선택 사항입니다. **FastAPI**가 `root_path`를 사용한 자동 server를 포함하지 않게 하려면, `root_path_in_servers=False` 파라미터를 사용할 수 있습니다: -{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} +{* ../../docs_src/behind_a_proxy/tutorial004_py310.py hl[9] *} 그러면 OpenAPI 스키마에 포함되지 않습니다. diff --git a/docs/ko/docs/advanced/custom-response.md b/docs/ko/docs/advanced/custom-response.md index 55dc2a4be..6d54eaf2b 100644 --- a/docs/ko/docs/advanced/custom-response.md +++ b/docs/ko/docs/advanced/custom-response.md @@ -30,7 +30,7 @@ 하지만 반환하는 내용이 **JSON으로 직렬화 가능**하다고 확신하는 경우, 해당 내용을 응답 클래스에 직접 전달할 수 있으며, FastAPI가 반환 내용을 `jsonable_encoder`를 통해 처리한 뒤 응답 클래스에 전달하는 오버헤드를 피할 수 있습니다. -{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} /// info | 정보 @@ -55,7 +55,7 @@ * `HTMLResponse`를 임포트 합니다. * *경로 처리 데코레이터*의 `response_class` 매개변수로 `HTMLResponse`를 전달합니다. -{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *} /// info | 정보 @@ -73,7 +73,7 @@ 위의 예제와 동일하게 `HTMLResponse`를 반환하는 코드는 다음과 같을 수 있습니다: -{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *} /// warning | 경고 @@ -97,7 +97,7 @@ 예를 들어, 다음과 같이 작성할 수 있습니다: -{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *} 이 예제에서, `generate_html_response()` 함수는 HTML을 `str`로 반환하는 대신 이미 `Response`를 생성하고 반환합니다. @@ -136,7 +136,7 @@ FastAPI (실제로는 Starlette)가 자동으로 Content-Length 헤더를 포함시킵니다. 또한 `media_type`에 기반하여 Content-Type 헤더를 포함하며, 텍스트 타입의 경우 문자 집합을 추가 합니다. -{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} ### `HTMLResponse` { #htmlresponse } @@ -146,7 +146,7 @@ FastAPI (실제로는 Starlette)가 자동으로 Content-Length 헤더를 포함 텍스트 또는 바이트를 받아 일반 텍스트 응답을 반환합니다. -{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *} ### `JSONResponse` { #jsonresponse } @@ -180,7 +180,7 @@ FastAPI (실제로는 Starlette)가 자동으로 Content-Length 헤더를 포함 /// -{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} /// tip | 팁 @@ -194,14 +194,14 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30 `RedirectResponse`를 직접 반환할 수 있습니다. -{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *} --- 또는 `response_class` 매개변수에서 사용할 수도 있습니다: -{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *} 이 경우, *경로 처리* 함수에서 URL을 직접 반환할 수 있습니다. @@ -211,13 +211,13 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30 `status_code` 매개변수를 `response_class` 매개변수와 함께 사용할 수도 있습니다: -{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *} ### `StreamingResponse` { #streamingresponse } 비동기 제너레이터 또는 일반 제너레이터/이터레이터를 받아 응답 본문을 스트리밍 합니다. -{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py310.py hl[2,14] *} #### 파일과 같은 객체를 사용한 `StreamingResponse` { #using-streamingresponse-with-file-like-objects } @@ -227,7 +227,7 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30 이 방식은 클라우드 스토리지, 비디오 처리 등의 다양한 라이브러리와 함께 사용할 수 있습니다. -{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} 1. 이것이 제너레이터 함수입니다. `yield` 문을 포함하고 있으므로 "제너레이터 함수"입니다. 2. `with` 블록을 사용함으로써, 제너레이터 함수가 완료된 후 파일과 같은 객체가 닫히도록 합니다. 즉, 응답 전송이 끝난 후 닫힙니다. @@ -256,11 +256,11 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30 파일 응답에는 적절한 `Content-Length`, `Last-Modified`, 및 `ETag` 헤더가 포함됩니다. -{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *} 또한 `response_class` 매개변수를 사용할 수도 있습니다: -{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *} 이 경우, 경로 처리 함수에서 파일 경로를 직접 반환할 수 있습니다. @@ -274,7 +274,7 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30 `CustomORJSONResponse`를 생성할 수 있습니다. 여기서 핵심은 `Response.render(content)` 메서드를 생성하여 내용을 `bytes`로 반환하는 것입니다: -{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *} 이제 다음 대신: @@ -300,7 +300,7 @@ HTTP 리디렉션 응답을 반환합니다. 기본적으로 상태 코드는 30 아래 예제에서 **FastAPI**는 모든 *경로 처리*에서 기본적으로 `JSONResponse` 대신 `ORJSONResponse`를 사용합니다. -{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} /// tip | 팁 diff --git a/docs/ko/docs/advanced/dataclasses.md b/docs/ko/docs/advanced/dataclasses.md index 92ad5545b..0210812b6 100644 --- a/docs/ko/docs/advanced/dataclasses.md +++ b/docs/ko/docs/advanced/dataclasses.md @@ -64,7 +64,7 @@ dataclass는 자동으로 Pydantic dataclass로 변환됩니다. 6. 여기서는 dataclasses 리스트인 `items`를 포함하는 딕셔너리를 반환합니다. - FastAPI는 여전히 데이터를 JSON으로 serializing할 수 있습니다. + FastAPI는 여전히 데이터를 JSON으로 직렬화할 수 있습니다. 7. 여기서 `response_model`은 `Author` dataclasses 리스트에 대한 타입 애너테이션을 사용합니다. diff --git a/docs/ko/docs/advanced/events.md b/docs/ko/docs/advanced/events.md index 35223eaf3..f1dd8397a 100644 --- a/docs/ko/docs/advanced/events.md +++ b/docs/ko/docs/advanced/events.md @@ -30,7 +30,7 @@ `yield`를 사용해 비동기 함수 `lifespan()`을 다음과 같이 생성합니다: -{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[16,19] *} 여기서는 `yield` 이전에 (가짜) 모델 함수를 머신러닝 모델이 들어 있는 딕셔너리에 넣어 모델을 로드하는 비용이 큰 *시작* 작업을 시뮬레이션합니다. 이 코드는 애플리케이션이 **요청을 받기 시작하기 전**, *시작* 동안에 실행됩니다. @@ -48,7 +48,7 @@ 먼저 주목할 점은 `yield`를 사용하여 비동기 함수를 정의하고 있다는 것입니다. 이는 `yield`를 사용하는 의존성과 매우 유사합니다. -{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py310.py hl[14:19] *} 함수의 첫 번째 부분, 즉 `yield` 이전의 코드는 애플리케이션이 시작되기 **전에** 실행됩니다. @@ -60,7 +60,7 @@ 이는 함수를 "**비동기 컨텍스트 매니저**"라고 불리는 것으로 변환합니다. -{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py310.py hl[1,13] *} 파이썬에서 **컨텍스트 매니저**는 `with` 문에서 사용할 수 있는 것입니다. 예를 들어, `open()`은 컨텍스트 매니저로 사용할 수 있습니다: @@ -82,7 +82,7 @@ async with lifespan(app): `FastAPI` 앱의 `lifespan` 매개변수는 **비동기 컨텍스트 매니저**를 받으므로, 새 `lifespan` 비동기 컨텍스트 매니저를 전달할 수 있습니다. -{* ../../docs_src/events/tutorial003_py39.py hl[22] *} +{* ../../docs_src/events/tutorial003_py310.py hl[22] *} ## 대체 이벤트(사용 중단) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ async with lifespan(app): 애플리케이션이 시작되기 전에 실행되어야 하는 함수를 추가하려면, `"startup"` 이벤트로 선언합니다: -{* ../../docs_src/events/tutorial001_py39.py hl[8] *} +{* ../../docs_src/events/tutorial001_py310.py hl[8] *} 이 경우, `startup` 이벤트 핸들러 함수는 "database"(그냥 `dict`) 항목을 일부 값으로 초기화합니다. @@ -116,7 +116,7 @@ async with lifespan(app): 애플리케이션이 종료될 때 실행되어야 하는 함수를 추가하려면, `"shutdown"` 이벤트로 선언합니다: -{* ../../docs_src/events/tutorial002_py39.py hl[6] *} +{* ../../docs_src/events/tutorial002_py310.py hl[6] *} 여기서 `shutdown` 이벤트 핸들러 함수는 텍스트 한 줄 `"Application shutdown"`을 `log.txt` 파일에 기록합니다. diff --git a/docs/ko/docs/advanced/generate-clients.md b/docs/ko/docs/advanced/generate-clients.md index 1def3efe1..3cfbb7578 100644 --- a/docs/ko/docs/advanced/generate-clients.md +++ b/docs/ko/docs/advanced/generate-clients.md @@ -40,7 +40,7 @@ FastAPI는 **OpenAPI 3.1** 사양을 자동으로 생성하므로, 사용하는 간단한 FastAPI 애플리케이션으로 시작해 보겠습니다: -{* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} +{* ../../docs_src/generate_clients/tutorial001_py310.py hl[7:9,12:13,16:17,21] *} *path operation*에서 요청 페이로드와 응답 페이로드에 사용하는 모델을 `Item`, `ResponseMessage` 모델로 정의하고 있다는 점에 주목하세요. @@ -98,7 +98,7 @@ npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client 예를 들어 **items** 섹션과 **users** 섹션이 있고, 이를 태그로 분리할 수 있습니다: -{* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} +{* ../../docs_src/generate_clients/tutorial002_py310.py hl[21,26,34] *} ### 태그로 TypeScript 클라이언트 생성하기 { #generate-a-typescript-client-with-tags } @@ -145,7 +145,7 @@ FastAPI는 각 *path operation*에 대해 **유일 ID**를 사용하며, 이는 그 다음 이 커스텀 함수를 `generate_unique_id_function` 매개변수로 **FastAPI**에 전달할 수 있습니다: -{* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} +{* ../../docs_src/generate_clients/tutorial003_py310.py hl[6:7,10] *} ### 커스텀 Operation ID로 TypeScript 클라이언트 생성하기 { #generate-a-typescript-client-with-custom-operation-ids } @@ -167,7 +167,7 @@ OpenAPI 전반에서는 operation ID가 **유일**하다는 것을 보장하기 OpenAPI JSON을 `openapi.json` 파일로 다운로드한 뒤, 아래와 같은 스크립트로 **접두사 태그를 제거**할 수 있습니다: -{* ../../docs_src/generate_clients/tutorial004_py39.py *} +{* ../../docs_src/generate_clients/tutorial004_py310.py *} //// tab | Node.js diff --git a/docs/ko/docs/advanced/index.md b/docs/ko/docs/advanced/index.md index 78ef5ffec..6212a7279 100644 --- a/docs/ko/docs/advanced/index.md +++ b/docs/ko/docs/advanced/index.md @@ -1,4 +1,4 @@ -# 심화 사용자 안내서 - 도입부 { #advanced-user-guide } +# 심화 사용자 안내서 { #advanced-user-guide } ## 추가 기능 { #additional-features } diff --git a/docs/ko/docs/advanced/middleware.md b/docs/ko/docs/advanced/middleware.md index be2c972a6..f6f7bbc96 100644 --- a/docs/ko/docs/advanced/middleware.md +++ b/docs/ko/docs/advanced/middleware.md @@ -8,7 +8,7 @@ ## ASGI middleware 추가하기 { #adding-asgi-middlewares } -**FastAPI**는 Starlette를 기반으로 하고 ASGI 사양을 구현하므로, 어떤 ASGI middleware든 사용할 수 있습니다. +**FastAPI**는 Starlette를 기반으로 하고 ASGI 사양을 구현하므로, 어떤 ASGI middleware든 사용할 수 있습니다. ASGI 사양을 따르기만 하면, FastAPI나 Starlette를 위해 만들어진 middleware가 아니어도 동작합니다. @@ -57,13 +57,13 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") `http` 또는 `ws`로 들어오는 모든 요청은 대신 보안 스킴으로 리디렉션됩니다. -{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial001_py310.py hl[2,6] *} ## `TrustedHostMiddleware` { #trustedhostmiddleware } HTTP Host Header 공격을 방어하기 위해, 들어오는 모든 요청에 올바르게 설정된 `Host` 헤더가 있어야 하도록 강제합니다. -{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} +{* ../../docs_src/advanced_middleware/tutorial002_py310.py hl[2,6:8] *} 다음 인자들을 지원합니다: @@ -78,7 +78,7 @@ HTTP Host Header 공격을 방어하기 위해, 들어오는 모든 요청에 이 middleware는 일반 응답과 스트리밍 응답을 모두 처리합니다. -{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial003_py310.py hl[2,6] *} 다음 인자들을 지원합니다: diff --git a/docs/ko/docs/advanced/openapi-callbacks.md b/docs/ko/docs/advanced/openapi-callbacks.md index e4bdea9d6..95f90d73c 100644 --- a/docs/ko/docs/advanced/openapi-callbacks.md +++ b/docs/ko/docs/advanced/openapi-callbacks.md @@ -1,8 +1,8 @@ # OpenAPI 콜백 { #openapi-callbacks } -다른 사람이 만든 *external API*(아마도 당신의 API를 *사용*할 동일한 개발자)가 요청을 트리거하도록 만드는 *경로 처리*를 가진 API를 만들 수 있습니다. +다른 사람이 만든 *external API*(아마도 여러분의 API를 *사용*할 동일한 개발자)가 요청을 트리거하도록 만드는 *경로 처리*를 가진 API를 만들 수 있습니다. -당신의 API 앱이 *external API*를 호출할 때 일어나는 과정을 "callback"이라고 합니다. 외부 개발자가 작성한 소프트웨어가 당신의 API로 요청을 보낸 다음, 당신의 API가 다시 *external API*로 요청을 보내 *되돌려 호출*하기 때문입니다(아마도 같은 개발자가 만든 API일 것입니다). +여러분의 API 앱이 *external API*를 호출할 때 일어나는 과정을 "callback"이라고 합니다. 외부 개발자가 작성한 소프트웨어가 여러분의 API로 요청을 보낸 다음, 여러분의 API가 다시 *external API*로 요청을 보내 *되돌려 호출*하기 때문입니다(아마도 같은 개발자가 만든 API일 것입니다). 이 경우, 그 *external API*가 어떤 형태여야 하는지 문서화하고 싶을 수 있습니다. 어떤 *경로 처리*를 가져야 하는지, 어떤 body를 기대하는지, 어떤 응답을 반환해야 하는지 등입니다. @@ -14,14 +14,14 @@ 이 청구서는 `id`, `title`(선택 사항), `customer`, `total`을 갖습니다. -당신의 API 사용자(외부 개발자)는 POST 요청으로 당신의 API에서 청구서를 생성합니다. +여러분의 API 사용자(외부 개발자)는 POST 요청으로 여러분의 API에서 청구서를 생성합니다. -그 다음 당신의 API는(가정해 보면): +그 다음 여러분의 API는(가정해 보면): * 청구서를 외부 개발자의 고객에게 전송합니다. * 돈을 수금합니다. * API 사용자(외부 개발자)의 API로 다시 알림을 보냅니다. - * 이는 (당신의 API에서) 그 외부 개발자가 제공하는 어떤 *external API*로 POST 요청을 보내는 방식으로 수행됩니다(이것이 "callback"입니다). + * 이는 (여러분의 API에서) 그 외부 개발자가 제공하는 어떤 *external API*로 POST 요청을 보내는 방식으로 수행됩니다(이것이 "callback"입니다). ## 일반적인 **FastAPI** 앱 { #the-normal-fastapi-app } @@ -43,7 +43,7 @@ ## 콜백 문서화하기 { #documenting-the-callback } -실제 콜백 코드는 당신의 API 앱에 크게 의존합니다. +실제 콜백 코드는 여러분의 API 앱에 크게 의존합니다. 그리고 앱마다 많이 달라질 수 있습니다. @@ -54,11 +54,11 @@ callback_url = "https://example.com/api/v1/invoices/events/" httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) ``` -하지만 콜백에서 가장 중요한 부분은, 당신의 API 사용자(외부 개발자)가 콜백 요청 body로 *당신의 API*가 보낼 데이터 등에 맞춰 *external API*를 올바르게 구현하도록 보장하는 것입니다. +하지만 콜백에서 가장 중요한 부분은, 여러분의 API 사용자(외부 개발자)가 콜백 요청 body로 *여러분의 API*가 보낼 데이터 등에 맞춰 *external API*를 올바르게 구현하도록 보장하는 것입니다. -그래서 다음으로 할 일은, *당신의 API*에서 보내는 콜백을 받기 위해 그 *external API*가 어떤 형태여야 하는지 문서화하는 코드를 추가하는 것입니다. +그래서 다음으로 할 일은, *여러분의 API*에서 보내는 콜백을 받기 위해 그 *external API*가 어떤 형태여야 하는지 문서화하는 코드를 추가하는 것입니다. -그 문서는 당신의 API에서 `/docs`의 Swagger UI에 표시되며, 외부 개발자들이 *external API*를 어떻게 만들어야 하는지 알 수 있게 해줍니다. +그 문서는 여러분의 API에서 `/docs`의 Swagger UI에 표시되며, 외부 개발자들이 *external API*를 어떻게 만들어야 하는지 알 수 있게 해줍니다. 이 예시는 콜백 자체(한 줄 코드로도 될 수 있음)를 구현하지 않고, 문서화 부분만 구현합니다. @@ -76,11 +76,11 @@ httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) 하지만 **FastAPI**로 API의 자동 문서를 쉽게 생성하는 방법은 이미 알고 있습니다. -따라서 그와 같은 지식을 사용해 *external API*가 어떻게 생겨야 하는지 문서화할 것입니다... 즉 외부 API가 구현해야 하는 *경로 처리(들)*(당신의 API가 호출할 것들)을 만들어서 말입니다. +따라서 그와 같은 지식을 사용해 *external API*가 어떻게 생겨야 하는지 문서화할 것입니다... 즉 외부 API가 구현해야 하는 *경로 처리(들)*(여러분의 API가 호출할 것들)을 만들어서 말입니다. /// tip | 팁 -콜백을 문서화하는 코드를 작성할 때는, 자신이 그 *외부 개발자*라고 상상하는 것이 유용할 수 있습니다. 그리고 지금은 *당신의 API*가 아니라 *external API*를 구현하고 있다고 생각해 보세요. +콜백을 문서화하는 코드를 작성할 때는, 자신이 그 *외부 개발자*라고 상상하는 것이 유용할 수 있습니다. 그리고 지금은 *여러분의 API*가 아니라 *external API*를 구현하고 있다고 생각해 보세요. 이 관점(외부 개발자의 관점)을 잠시 채택하면, 그 *external API*를 위해 파라미터, body용 Pydantic 모델, 응답 등을 어디에 두어야 하는지가 더 명확하게 느껴질 수 있습니다. @@ -105,12 +105,12 @@ httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) 일반적인 *경로 처리*와의 주요 차이점은 2가지입니다: -* 실제 코드를 가질 필요가 없습니다. 당신의 앱은 이 코드를 절대 호출하지 않기 때문입니다. 이는 *external API*를 문서화하는 데만 사용됩니다. 따라서 함수는 그냥 `pass`만 있어도 됩니다. -* *path*에는 OpenAPI 3 expression(자세한 내용은 아래 참고)이 포함될 수 있으며, 이를 통해 *당신의 API*로 보내진 원래 요청의 파라미터와 일부 값을 변수로 사용할 수 있습니다. +* 실제 코드를 가질 필요가 없습니다. 여러분의 앱은 이 코드를 절대 호출하지 않기 때문입니다. 이는 *external API*를 문서화하는 데만 사용됩니다. 따라서 함수는 그냥 `pass`만 있어도 됩니다. +* *path*에는 OpenAPI 3 expression(자세한 내용은 아래 참고)이 포함될 수 있으며, 이를 통해 *여러분의 API*로 보내진 원래 요청의 파라미터와 일부 값을 변수로 사용할 수 있습니다. ### 콜백 경로 표현식 { #the-callback-path-expression } -콜백 *path*는 *당신의 API*로 보내진 원래 요청의 일부를 포함할 수 있는 OpenAPI 3 expression을 가질 수 있습니다. +콜백 *path*는 *여러분의 API*로 보내진 원래 요청의 일부를 포함할 수 있는 OpenAPI 3 expression을 가질 수 있습니다. 이 경우, 다음 `str`입니다: @@ -118,7 +118,7 @@ httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) "{$callback_url}/invoices/{$request.body.id}" ``` -따라서 당신의 API 사용자(외부 개발자)가 *당신의 API*로 다음 요청을 보내고: +따라서 여러분의 API 사용자(외부 개발자)가 *여러분의 API*로 다음 요청을 보내고: ``` https://yourapi.com/invoices/?callback_url=https://www.external.org/events @@ -134,7 +134,7 @@ JSON body가 다음과 같다면: } ``` -그러면 *당신의 API*는 청구서를 처리하고, 나중에 어느 시점에서 `callback_url`(즉 *external API*)로 콜백 요청을 보냅니다: +그러면 *여러분의 API*는 청구서를 처리하고, 나중에 어느 시점에서 `callback_url`(즉 *external API*)로 콜백 요청을 보냅니다: ``` https://www.external.org/events/invoices/2expen51ve @@ -167,7 +167,7 @@ https://www.external.org/events/invoices/2expen51ve 이 시점에서, 위에서 만든 콜백 라우터 안에 *콜백 경로 처리(들)*(즉 *external developer*가 *external API*에 구현해야 하는 것들)을 준비했습니다. -이제 *당신의 API 경로 처리 데코레이터*에서 `callbacks` 파라미터를 사용해, 그 콜백 라우터의 `.routes` 속성(실제로는 routes/*경로 처리*의 `list`)을 전달합니다: +이제 *여러분의 API 경로 처리 데코레이터*에서 `callbacks` 파라미터를 사용해, 그 콜백 라우터의 `.routes` 속성(실제로는 routes/*경로 처리*의 `list`)을 전달합니다: {* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} diff --git a/docs/ko/docs/advanced/openapi-webhooks.md b/docs/ko/docs/advanced/openapi-webhooks.md index 89cacf7b7..8651c4176 100644 --- a/docs/ko/docs/advanced/openapi-webhooks.md +++ b/docs/ko/docs/advanced/openapi-webhooks.md @@ -32,7 +32,7 @@ Webhooks는 OpenAPI 3.1.0 이상에서 사용할 수 있으며, FastAPI `0.99.0` **FastAPI** 애플리케이션을 만들면, *경로 처리*를 정의하는 것과 같은 방식으로(예: `@app.webhooks.post()`), *webhooks*를 정의하는 데 사용할 수 있는 `webhooks` 속성이 있습니다. -{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py310.py hl[9:12,15:20] *} 여러분이 정의한 webhook은 **OpenAPI** 스키마와 자동 **docs UI**에 포함됩니다. diff --git a/docs/ko/docs/advanced/path-operation-advanced-configuration.md b/docs/ko/docs/advanced/path-operation-advanced-configuration.md index f20fa6d26..da8e43bd3 100644 --- a/docs/ko/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/ko/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ OpenAPI “전문가”가 아니라면, 아마 이 내용은 필요하지 않 각 작업마다 고유하도록 보장해야 합니다. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py310.py hl[6] *} ### *경로 처리 함수* 이름을 operationId로 사용하기 { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ API의 함수 이름을 `operationId`로 사용하고 싶다면, 모든 API를 모든 *경로 처리*를 추가한 뒤에 수행해야 합니다. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py310.py hl[2, 12:21, 24] *} /// tip | 팁 @@ -40,7 +40,7 @@ API의 함수 이름을 `operationId`로 사용하고 싶다면, 모든 API를 생성된 OpenAPI 스키마(따라서 자동 문서화 시스템)에서 특정 *경로 처리*를 제외하려면, `include_in_schema` 매개변수를 `False`로 설정하세요: -{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py310.py hl[6] *} ## docstring에서 고급 설명 가져오기 { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ OpenAPI 명세에서는 이를 parsed
되지도 않고, `bytes`로 직접 읽습니다. 그리고 함수 `magic_data_reader()`가 어떤 방식으로든 이를 파싱하는 역할을 담당합니다. +이 예시에서는 어떤 Pydantic 모델도 선언하지 않았습니다. 사실 요청 바디는 JSON으로 파싱되지도 않고, `bytes`로 직접 읽습니다. 그리고 함수 `magic_data_reader()`가 어떤 방식으로든 이를 파싱하는 역할을 담당합니다. 그럼에도 불구하고, 요청 바디에 대해 기대하는 스키마를 선언할 수 있습니다. @@ -153,7 +153,7 @@ OpenAPI 명세에서는 이를 Pydantic: Concepts: Configuration을 참고하세요. +`model_config` 속성은 Pydantic 설정을 위한 것입니다. 자세한 내용은 Pydantic: Concepts: Configuration를 참고하세요. /// @@ -226,11 +226,11 @@ def get_settings(): 하지만 위에 `@lru_cache` 데코레이터를 사용하고 있으므로, `Settings` 객체는 최초 호출 시 딱 한 번만 생성됩니다. ✔️ -{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} +{* ../../docs_src/settings/app03_an_py310/main.py hl[1,11] *} 그 다음 요청들에서 의존성으로 `get_settings()`가 다시 호출될 때마다, `get_settings()`의 내부 코드를 실행해서 새 `Settings` 객체를 만드는 대신, 첫 호출에서 반환된 동일한 객체를 계속 반환합니다. -#### `lru_cache` Technical Details { #lru-cache-technical-details } +#### `lru_cache` 기술 세부사항 { #lru-cache-technical-details } `@lru_cache`는 데코레이션한 함수가 매번 다시 계산하는 대신, 첫 번째에 반환된 동일한 값을 반환하도록 함수를 수정합니다(즉, 매번 함수 코드를 실행하지 않습니다). diff --git a/docs/ko/docs/advanced/sub-applications.md b/docs/ko/docs/advanced/sub-applications.md index e1554ca5d..8da217aab 100644 --- a/docs/ko/docs/advanced/sub-applications.md +++ b/docs/ko/docs/advanced/sub-applications.md @@ -10,7 +10,7 @@ 먼저, 메인 최상위 **FastAPI** 애플리케이션과 그 *경로 처리*를 생성합니다: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[3, 6:8] *} ### 하위 응용프로그램 { #sub-application } @@ -18,7 +18,7 @@ 이 하위 응용프로그램은 또 다른 표준 FastAPI 애플리케이션이지만, "마운트"될 애플리케이션입니다: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 14:16] *} ### 하위 응용프로그램 마운트 { #mount-the-sub-application } @@ -26,7 +26,7 @@ 이 경우 `/subapi` 경로에 마운트됩니다: -{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *} +{* ../../docs_src/sub_applications/tutorial001_py310.py hl[11, 19] *} ### 자동 API 문서 확인 { #check-the-automatic-api-docs } diff --git a/docs/ko/docs/advanced/templates.md b/docs/ko/docs/advanced/templates.md index fffffa6a5..3ae718f15 100644 --- a/docs/ko/docs/advanced/templates.md +++ b/docs/ko/docs/advanced/templates.md @@ -27,7 +27,7 @@ $ pip install jinja2 * 템플릿을 반환할 *경로 처리*에 `Request` 매개변수를 선언합니다. * 생성한 `templates`를 사용하여 `TemplateResponse`를 렌더링하고 반환합니다. 템플릿의 이름, 요청 객체 및 Jinja2 템플릿 내에서 사용될 키-값 쌍이 포함된 "컨텍스트" 딕셔너리도 전달합니다. -{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *} +{* ../../docs_src/templates/tutorial001_py310.py hl[4,11,15:18] *} /// note | 참고 diff --git a/docs/ko/docs/advanced/testing-events.md b/docs/ko/docs/advanced/testing-events.md index 8dbd4f6e6..a1a7daff3 100644 --- a/docs/ko/docs/advanced/testing-events.md +++ b/docs/ko/docs/advanced/testing-events.md @@ -2,11 +2,11 @@ 테스트에서 `lifespan`을 실행해야 하는 경우, `with` 문과 함께 `TestClient`를 사용할 수 있습니다: -{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *} +{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *} ["공식 Starlette 문서 사이트에서 테스트에서 라이프스팬 실행하기."](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)에 대한 자세한 내용을 더 읽을 수 있습니다. 더 이상 권장되지 않는 `startup` 및 `shutdown` 이벤트의 경우, 다음과 같이 `TestClient`를 사용할 수 있습니다: -{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *} +{* ../../docs_src/app_testing/tutorial003_py310.py hl[9:12,20:24] *} diff --git a/docs/ko/docs/advanced/testing-websockets.md b/docs/ko/docs/advanced/testing-websockets.md index 1cb3cad67..23ff34cd3 100644 --- a/docs/ko/docs/advanced/testing-websockets.md +++ b/docs/ko/docs/advanced/testing-websockets.md @@ -4,10 +4,10 @@ 이를 위해 `with` 문에서 `TestClient`를 사용하여 WebSocket에 연결합니다: -{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *} +{* ../../docs_src/app_testing/tutorial002_py310.py hl[27:31] *} /// note | 참고 -자세한 내용은 Starlette의 testing WebSockets 문서를 확인하세요. +자세한 내용은 Starlette의 WebSocket 테스트 문서를 확인하세요. /// diff --git a/docs/ko/docs/advanced/using-request-directly.md b/docs/ko/docs/advanced/using-request-directly.md index e0a5e99f8..0c45e2e2f 100644 --- a/docs/ko/docs/advanced/using-request-directly.md +++ b/docs/ko/docs/advanced/using-request-directly.md @@ -29,7 +29,7 @@ 이를 위해서는 요청에 직접 접근해야 합니다. -{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *} +{* ../../docs_src/using_request_directly/tutorial001_py310.py hl[1,7:8] *} *경로 처리 함수* 매개변수를 `Request` 타입으로 선언하면 **FastAPI**가 해당 매개변수에 `Request`를 전달하는 것을 알게 됩니다. diff --git a/docs/ko/docs/advanced/websockets.md b/docs/ko/docs/advanced/websockets.md index b6817870b..384f93952 100644 --- a/docs/ko/docs/advanced/websockets.md +++ b/docs/ko/docs/advanced/websockets.md @@ -24,7 +24,7 @@ $ pip install websockets 그리고 백엔드와 WebSockets을 사용해 통신하려면 아마도 프런트엔드의 유틸리티를 사용할 것입니다. -또는 네이티브 코드로 WebSocket 백엔드와 직접 통신하는 네이티브 모바일 응용 프로그램을 가질 수도 있습니다. +또는 네이티브 코드로 WebSocket 백엔드와 직접 통신하는 네이티브 모바일 애플리케이션을 가질 수도 있습니다. 혹은 WebSocket 엔드포인트와 통신할 수 있는 다른 방법이 있을 수도 있습니다. @@ -38,13 +38,13 @@ $ pip install websockets 그러나 이는 WebSockets의 서버 측에 집중하고 동작하는 예제를 제공하는 가장 간단한 방법입니다: -{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[2,6:38,41:43] *} ## `websocket` 생성하기 { #create-a-websocket } -**FastAPI** 응용 프로그램에서 `websocket`을 생성합니다: +**FastAPI** 애플리케이션에서 `websocket`을 생성합니다: -{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} /// note | 기술 세부사항 @@ -58,13 +58,13 @@ $ pip install websockets WebSocket 경로에서 메시지를 대기(`await`)하고 전송할 수 있습니다. -{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *} +{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} 여러분은 이진 데이터, 텍스트, JSON 데이터를 받을 수 있고 전송할 수 있습니다. ## 시도해보기 { #try-it } -파일 이름이 `main.py`라고 가정하고 다음으로 응용 프로그램을 실행합니다: +파일 이름이 `main.py`라고 가정하고 다음으로 애플리케이션을 실행합니다:
@@ -86,7 +86,7 @@ $ fastapi dev main.py -그리고 WebSockets가 포함된 **FastAPI** 응용 프로그램이 응답을 돌려줄 것입니다: +그리고 WebSockets가 포함된 **FastAPI** 애플리케이션이 응답을 돌려줄 것입니다: @@ -121,7 +121,7 @@ WebSocket이기 때문에 `HTTPException`을 발생시키는 것은 적절하지 ### 종속성을 가진 WebSockets 시도해보기 { #try-the-websockets-with-dependencies } -파일 이름이 `main.py`라고 가정하고 다음으로 응용 프로그램을 실행합니다: +파일 이름이 `main.py`라고 가정하고 다음으로 애플리케이션을 실행합니다:
@@ -154,7 +154,7 @@ $ fastapi dev main.py WebSocket 연결이 닫히면, `await websocket.receive_text()`가 `WebSocketDisconnect` 예외를 발생시킵니다. 그러면 이 예제처럼 이를 잡아 처리할 수 있습니다. -{* ../../docs_src/websockets/tutorial003_py39.py hl[79:81] *} +{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} 테스트해보기: diff --git a/docs/ko/docs/advanced/wsgi.md b/docs/ko/docs/advanced/wsgi.md index 5e0e87c5e..24b074443 100644 --- a/docs/ko/docs/advanced/wsgi.md +++ b/docs/ko/docs/advanced/wsgi.md @@ -18,7 +18,7 @@ 그리고 해당 경로에 마운트합니다. -{* ../../docs_src/wsgi/tutorial001_py39.py hl[1,3,23] *} +{* ../../docs_src/wsgi/tutorial001_py310.py hl[1,3,23] *} /// note | 참고 diff --git a/docs/ko/docs/alternatives.md b/docs/ko/docs/alternatives.md index d8c2df2d8..f26fbe39d 100644 --- a/docs/ko/docs/alternatives.md +++ b/docs/ko/docs/alternatives.md @@ -137,7 +137,7 @@ Flask REST framework는 여러 개가 있지만, 시간을 들여 조사해 본 ### Marshmallow { #marshmallow } -API 시스템에 필요한 주요 기능 중 하나는 데이터 "serialization"입니다. 이는 코드(Python)에서 데이터를 가져와 네트워크로 전송할 수 있는 형태로 변환하는 것을 의미합니다. 예를 들어 데이터베이스의 데이터를 담은 객체를 JSON 객체로 변환하거나, `datetime` 객체를 문자열로 변환하는 등의 작업입니다. +API 시스템에 필요한 주요 기능 중 하나는 데이터 "직렬화"입니다. 이는 코드(Python)에서 데이터를 가져와 네트워크로 전송할 수 있는 형태로 변환하는 것을 의미합니다. 예를 들어 데이터베이스의 데이터를 담은 객체를 JSON 객체로 변환하거나, `datetime` 객체를 문자열로 변환하는 등의 작업입니다. API에 또 하나 크게 필요한 기능은 데이터 검증입니다. 특정 파라미터를 기준으로 데이터가 유효한지 확인하는 것입니다. 예를 들어 어떤 필드가 `int`인지, 임의의 문자열이 아닌지 확인하는 식입니다. 이는 특히 들어오는 데이터에 유용합니다. @@ -145,7 +145,7 @@ API에 또 하나 크게 필요한 기능은 데이터 검증입니다. 특정 이런 기능들을 제공하기 위해 Marshmallow가 만들어졌습니다. 훌륭한 라이브러리이며, 저도 이전에 많이 사용했습니다. -하지만 Python type hints가 존재하기 전에 만들어졌습니다. 그래서 각 schema를 정의하려면 Marshmallow가 제공하는 특정 유틸리티와 클래스를 사용해야 합니다. +하지만 Python type hints가 존재하기 전에 만들어졌습니다. 그래서 각 스키마를 정의하려면 Marshmallow가 제공하는 특정 유틸리티와 클래스를 사용해야 합니다. /// check | **FastAPI**에 영감을 준 것 @@ -155,7 +155,7 @@ API에 또 하나 크게 필요한 기능은 데이터 검증입니다. 특정 ### Webargs { #webargs } -API에 필요한 또 다른 큰 기능은 들어오는 요청에서 데이터를 parsing하는 것입니다. +API에 필요한 또 다른 큰 기능은 들어오는 요청에서 데이터를 파싱하는 것입니다. Webargs는 Flask를 포함한 여러 framework 위에서 이를 제공하기 위해 만들어진 도구입니다. @@ -419,7 +419,7 @@ Marshmallow와 비교할 수 있습니다. 다만 benchmark에서 Marshmallow보 ### Starlette { #starlette } -Starlette는 경량 ASGI framework/toolkit으로, 고성능 asyncio 서비스를 만들기에 이상적입니다. +Starlette는 경량 ASGI framework/toolkit으로, 고성능 asyncio 서비스를 만들기에 이상적입니다. 매우 단순하고 직관적입니다. 쉽게 확장할 수 있도록 설계되었고, 모듈식 component를 갖습니다. diff --git a/docs/ko/docs/deployment/docker.md b/docs/ko/docs/deployment/docker.md index 20e341c26..ca0136d83 100644 --- a/docs/ko/docs/deployment/docker.md +++ b/docs/ko/docs/deployment/docker.md @@ -14,7 +14,7 @@ FastAPI 애플리케이션을 배포할 때 일반적인 접근 방법은 **리 Dockerfile Preview 👀 ```Dockerfile -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -166,7 +166,7 @@ def read_item(item_id: int, q: str | None = None): ```{ .dockerfile .annotate } # (1)! -FROM python:3.9 +FROM python:3.14 # (2)! WORKDIR /code @@ -390,7 +390,7 @@ FastAPI가 단일 파일(예: `./app` 디렉터리 없이 `main.py`만 있는 그런 다음 `Dockerfile`에서 해당 파일을 복사하도록 경로만 맞게 변경하면 됩니다: ```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 +FROM python:3.14 WORKDIR /code @@ -454,7 +454,7 @@ Traefik은 Docker, Kubernetes 등과 통합되어 있어, 이를 사용해 컨 ## 복제 - 프로세스 개수 { #replication-number-of-processes } -**Kubernetes**, Docker Swarm Mode, Nomad 등의 복잡한 시스템으로 여러 머신에 분산된 컨테이너를 관리하는 클러스터를 사용한다면, 각 컨테이너에서(**워커를 사용하는 Uvicorn** 같은) **프로세스 매니저**를 쓰는 대신, **클러스터 레벨**에서 **복제를 처리**하고 싶을 가능성이 큽니다. +**Kubernetes**, Docker Swarm Mode, Nomad 등의 복잡한 시스템으로 여러 머신에 분산된 컨테이너를 관리하는 클러스터를 사용한다면, 각 컨테이너에서(**워커를 사용하는 Uvicorn** 같은) **프로세스 매니저**를 쓰는 대신, **클러스터 레벨**에서 **복제를 처리**하고 싶을 가능성이 큽니다. Kubernetes 같은 분산 컨테이너 관리 시스템은 보통 들어오는 요청에 대한 **로드 밸런싱**을 지원하면서도, **컨테이너 복제**를 처리하는 통합된 방법을 가지고 있습니다. 모두 **클러스터 레벨**에서요. @@ -499,7 +499,7 @@ HTTPS에 사용되는 동일한 **TLS 종료 프록시** 컴포넌트가 **로 그런 경우에는 `--workers` 커맨드 라인 옵션을 사용해 실행할 워커 수를 설정할 수 있습니다: ```{ .dockerfile .annotate } -FROM python:3.9 +FROM python:3.14 WORKDIR /code diff --git a/docs/ko/docs/deployment/https.md b/docs/ko/docs/deployment/https.md index 888ec6159..bda942af6 100644 --- a/docs/ko/docs/deployment/https.md +++ b/docs/ko/docs/deployment/https.md @@ -15,7 +15,7 @@ HTTPS는 그냥 “켜져 있거나” 아니면 “꺼져 있는” 것이라 이제 **개발자 관점**에서 HTTPS를 생각할 때 염두에 두어야 할 여러 가지가 있습니다: * HTTPS를 사용하려면, **서버**가 **제3자**가 발급한 **"인증서(certificates)"**를 **보유**해야 합니다. - * 이 인증서는 실제로 제3자가 “생성”해 주는 것이고, 서버가 만드는 것이 아니라 제3자로부터 **발급/획득**하는 것입니다. + * 이 인증서는 실제로 '생성'되는 것이 아니라 제3자로부터 **발급/획득**하는 것입니다. * 인증서에는 **유효 기간**이 있습니다. * 즉, **만료**됩니다. * 그리고 나면 제3자로부터 다시 **갱신**해서 **재발급/재획득**해야 합니다. @@ -65,7 +65,7 @@ Let's Encrypt 이전에는 이러한 **HTTPS 인증서**가 신뢰할 수 있는 아마도 시작은 **도메인 이름**을 **획득**하는 것일 겁니다. 그 다음 DNS 서버(아마 같은 클라우드 제공업체)에서 이를 설정합니다. -대개 클라우드 서버(가상 머신) 같은 것을 사용하게 되고, 거기에는 fixed **공개 IP 주소**가 있습니다. +대개 클라우드 서버(가상 머신) 같은 것을 사용하게 되고, 거기에는 고정 **공개 IP 주소**가 있습니다. DNS 서버(들)에서 **도메인**이 서버의 **공개 IP 주소**를 가리키도록 레코드(“`A record`”)를 설정합니다. diff --git a/docs/ko/docs/deployment/manually.md b/docs/ko/docs/deployment/manually.md index e85dd02a3..93b1e7611 100644 --- a/docs/ko/docs/deployment/manually.md +++ b/docs/ko/docs/deployment/manually.md @@ -46,7 +46,7 @@ $ fastapi run ASGI라고 불리는, Python 웹 프레임워크와 서버를 만들기 위한 표준을 사용합니다. FastAPI는 ASGI 웹 프레임워크입니다. +FastAPI는 ASGI라고 불리는, Python 웹 프레임워크와 서버를 만들기 위한 표준을 사용합니다. FastAPI는 ASGI 웹 프레임워크입니다. 원격 서버 머신에서 **FastAPI** 애플리케이션(또는 다른 ASGI 애플리케이션)을 실행하기 위해 필요한 핵심 요소는 **Uvicorn** 같은 ASGI 서버 프로그램입니다. `fastapi` 명령에는 기본으로 이것이 포함되어 있습니다. diff --git a/docs/ko/docs/deployment/server-workers.md b/docs/ko/docs/deployment/server-workers.md index e98cfd114..1be102925 100644 --- a/docs/ko/docs/deployment/server-workers.md +++ b/docs/ko/docs/deployment/server-workers.md @@ -9,13 +9,13 @@ * 메모리 * 시작하기 전의 이전 단계 -지금까지 문서의 모든 튜토리얼을 참고하면서, `fastapi` 명령처럼 Uvicorn을 실행하는 **서버 프로그램**을 사용해 **단일 프로세스**로 실행해 왔을 가능성이 큽니다. +지금까지 문서의 모든 튜토리얼을 참고하면서, `fastapi` 명령어처럼 Uvicorn을 실행하는 **서버 프로그램**을 사용해 **단일 프로세스**로 실행해 왔을 가능성이 큽니다. 애플리케이션을 배포할 때는 **다중 코어**를 활용하고 더 많은 요청을 처리할 수 있도록 **프로세스 복제**를 하고 싶을 가능성이 큽니다. 이전 장의 [배포 개념들](concepts.md){.internal-link target=_blank}에서 본 것처럼, 사용할 수 있는 전략이 여러 가지 있습니다. -여기서는 `fastapi` 명령을 사용하거나 `uvicorn` 명령을 직접 사용해서, **워커 프로세스**와 함께 **Uvicorn**을 사용하는 방법을 보여드리겠습니다. +여기서는 `fastapi` 명령어를 사용하거나 `uvicorn` 명령어를 직접 사용해서, **워커 프로세스**와 함께 **Uvicorn**을 사용하는 방법을 보여드리겠습니다. /// info | 정보 @@ -27,11 +27,11 @@ Docker나 Kubernetes 같은 컨테이너를 사용하고 있다면, 다음 장 ## 여러 워커 { #multiple-workers } -`--workers` 커맨드라인 옵션으로 여러 워커를 시작할 수 있습니다: +`--workers` 명령어 옵션으로 여러 워커를 시작할 수 있습니다: //// tab | `fastapi` -`fastapi` 명령을 사용한다면: +`fastapi` 명령어를 사용한다면:
@@ -81,7 +81,7 @@ $ fastapi run --workers 4 @@ -132,7 +132,7 @@ $ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 ## 요약 { #recap } -`fastapi` 또는 `uvicorn` 명령에서 `--workers` CLI 옵션을 사용해 여러 워커 프로세스를 실행하면, **멀티 코어 CPU**를 활용해 **여러 프로세스를 병렬로 실행**할 수 있습니다. +`fastapi` 또는 `uvicorn` 명령어에서 `--workers` CLI 옵션을 사용해 여러 워커 프로세스를 실행하면, **멀티 코어 CPU**를 활용해 **여러 프로세스를 병렬로 실행**할 수 있습니다. 다른 배포 개념들을 직접 처리하면서 **자체 배포 시스템**을 구축하는 경우, 이러한 도구와 아이디어를 활용할 수 있습니다. diff --git a/docs/ko/docs/deployment/versions.md b/docs/ko/docs/deployment/versions.md index 173ba925c..b94832aa8 100644 --- a/docs/ko/docs/deployment/versions.md +++ b/docs/ko/docs/deployment/versions.md @@ -34,7 +34,7 @@ fastapi[standard]>=0.112.0,<0.113.0 ## 이용 가능한 버전들 { #available-versions } -사용 가능한 버전(예: 현재 최신 버전이 무엇인지 확인하기 위해)은 [Release Notes](../release-notes.md){.internal-link target=_blank}에서 확인할 수 있습니다. +사용 가능한 버전(예: 현재 최신 버전이 무엇인지 확인하기 위해)은 [릴리스 노트](../release-notes.md){.internal-link target=_blank}에서 확인할 수 있습니다. ## 버전들에 대해 { #about-versions } @@ -66,7 +66,7 @@ fastapi>=0.45.0,<0.46.0 앱에 테스트를 추가해야 합니다. -**FastAPI**에서는 매우 쉽습니다(Starlette 덕분에). 문서를 확인해 보세요: [Testing](../tutorial/testing.md){.internal-link target=_blank} +**FastAPI**에서는 매우 쉽습니다(Starlette 덕분에). 문서를 확인해 보세요: [테스트](../tutorial/testing.md){.internal-link target=_blank} 테스트를 갖춘 뒤에는 **FastAPI** 버전을 더 최신 버전으로 업그레이드하고, 테스트를 실행하여 모든 코드가 올바르게 동작하는지 확인하세요. diff --git a/docs/ko/docs/environment-variables.md b/docs/ko/docs/environment-variables.md index dc231acb6..e8809573f 100644 --- a/docs/ko/docs/environment-variables.md +++ b/docs/ko/docs/environment-variables.md @@ -19,10 +19,10 @@
```console -// You could create an env var MY_NAME with +// 환경 변수 MY_NAME을 다음과 같이 생성할 수 있습니다 $ export MY_NAME="Wade Wilson" -// Then you could use it with other programs, like +// 그런 다음 다른 프로그램과 함께 사용할 수 있습니다. 예: $ echo "Hello $MY_NAME" Hello Wade Wilson @@ -37,10 +37,10 @@ Hello Wade Wilson
```console -// Create an env var MY_NAME +// 환경 변수 MY_NAME 생성 $ $Env:MY_NAME = "Wade Wilson" -// Use it with other programs, like +// 다른 프로그램과 함께 사용하기. 예: $ echo "Hello $Env:MY_NAME" Hello Wade Wilson @@ -78,20 +78,20 @@ print(f"Hello {name} from Python")
```console -// Here we don't set the env var yet +// 여기서는 아직 환경 변수를 설정하지 않습니다 $ python main.py -// As we didn't set the env var, we get the default value +// 환경 변수를 설정하지 않았으므로 기본값이 사용됩니다 Hello World from Python -// But if we create an environment variable first +// 하지만 먼저 환경 변수를 생성하면 $ export MY_NAME="Wade Wilson" -// And then call the program again +// 그리고 프로그램을 다시 실행하면 $ python main.py -// Now it can read the environment variable +// 이제 환경 변수를 읽을 수 있습니다 Hello Wade Wilson from Python ``` @@ -105,20 +105,20 @@ Hello Wade Wilson from Python
```console -// Here we don't set the env var yet +// 여기서는 아직 환경 변수를 설정하지 않습니다 $ python main.py -// As we didn't set the env var, we get the default value +// 환경 변수를 설정하지 않았으므로 기본값이 사용됩니다 Hello World from Python -// But if we create an environment variable first +// 하지만 먼저 환경 변수를 생성하면 $ $Env:MY_NAME = "Wade Wilson" -// And then call the program again +// 그리고 프로그램을 다시 실행하면 $ python main.py -// Now it can read the environment variable +// 이제 환경 변수를 읽을 수 있습니다 Hello Wade Wilson from Python ``` @@ -136,14 +136,14 @@ Hello Wade Wilson from Python
```console -// Create an env var MY_NAME in line for this program call +// 이 프로그램 호출을 위해 같은 줄에서 환경 변수 MY_NAME 생성 $ MY_NAME="Wade Wilson" python main.py -// Now it can read the environment variable +// 이제 환경 변수를 읽을 수 있습니다 Hello Wade Wilson from Python -// The env var no longer exists afterwards +// 이후에는 해당 환경 변수가 존재하지 않습니다 $ python main.py Hello World from Python diff --git a/docs/ko/docs/features.md b/docs/ko/docs/features.md index 17cc9289f..b511ae470 100644 --- a/docs/ko/docs/features.md +++ b/docs/ko/docs/features.md @@ -6,7 +6,7 @@ ### 개방형 표준을 기반으로 { #based-on-open-standards } -* OpenAPI: path operations, 매개변수, 요청 본문, 보안 등의 선언을 포함하여 API를 생성합니다. +* OpenAPI: 경로 처리, 매개변수, 요청 본문, 보안 등의 선언을 포함하여 API를 생성합니다. * JSON Schema를 사용한 자동 데이터 모델 문서화(OpenAPI 자체가 JSON Schema를 기반으로 하기 때문입니다). * 단순히 떠올려서 덧붙인 레이어가 아니라, 세심한 검토를 거친 뒤 이러한 표준을 중심으로 설계되었습니다. * 이는 또한 다양한 언어로 자동 **클라이언트 코드 생성**을 사용할 수 있게 해줍니다. @@ -136,7 +136,7 @@ Python 개발자 설문조사에서 Dependency Injection 시스템을 포함하고 있습니다. +FastAPI는 사용하기 매우 쉽지만, 매우 강력한 의존성 주입 시스템을 포함하고 있습니다. * 의존성도 의존성을 가질 수 있어, 의존성의 계층 또는 **의존성의 "그래프"**를 생성합니다. * 모든 것이 프레임워크에 의해 **자동으로 처리됩니다**. @@ -153,8 +153,8 @@ FastAPI는 사용하기 매우 쉽지만, 매우 강력한 IDE/linter/뇌**와 잘 어울립니다: +* 여러분의 **IDE/린터/뇌**와 잘 어울립니다: * pydantic 데이터 구조는 여러분이 정의한 클래스 인스턴스일 뿐이므로, 자동 완성, 린팅, mypy, 그리고 직관까지도 검증된 데이터와 함께 제대로 작동합니다. * **복잡한 구조**를 검증합니다: * 계층적인 Pydantic 모델, Python `typing`의 `List`와 `Dict` 등을 사용합니다. diff --git a/docs/ko/docs/how-to/authentication-error-status-code.md b/docs/ko/docs/how-to/authentication-error-status-code.md index 47120cae6..e039c073e 100644 --- a/docs/ko/docs/how-to/authentication-error-status-code.md +++ b/docs/ko/docs/how-to/authentication-error-status-code.md @@ -8,10 +8,10 @@ FastAPI 버전 `0.122.0`부터는 더 적절한 HTTP 상태 코드 `401 Unauthor 예를 들어, 기본값인 `401 Unauthorized` 오류 대신 `403 Forbidden` 오류를 반환하는 `HTTPBearer`의 서브클래스를 만들 수 있습니다: -{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py310.py hl[9:13] *} /// tip | 팁 -함수는 예외를 `raise`하는 것이 아니라 예외 인스턴스를 `return`한다는 점에 유의하세요. 예외를 발생시키는(`raise`) 작업은 내부 코드의 나머지 부분에서 수행됩니다. +함수는 예외를 `return`하는 것이지 `raise`하지 않는다는 점에 유의하세요. 예외를 발생시키는(`raise`) 작업은 내부 코드의 나머지 부분에서 수행됩니다. /// diff --git a/docs/ko/docs/how-to/conditional-openapi.md b/docs/ko/docs/how-to/conditional-openapi.md index 16e683366..3a87bf010 100644 --- a/docs/ko/docs/how-to/conditional-openapi.md +++ b/docs/ko/docs/how-to/conditional-openapi.md @@ -4,7 +4,7 @@ ## 보안, API 및 docs에 대해서 { #about-security-apis-and-docs } -프로덕션에서, 문서화된 사용자 인터페이스(UI)를 숨기는 것이 API를 보호하는 방법이 *되어서는 안 됩니다*. +프로덕션에서, 문서화된 사용자 인터페이스(UI)를 숨기는 것이 API를 보호하는 방법이 되어서는 안 됩니다. 이는 API에 추가적인 보안을 제공하지 않으며, *경로 처리*는 여전히 동일한 위치에서 사용 할 수 있습니다. @@ -29,7 +29,7 @@ API를 보호하고 싶다면, 예를 들어 다음과 같은 더 나은 방법 예를 들어: -{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py310.py hl[6,11] *} 여기서 `openapi_url` 설정을 기본값인 `"/openapi.json"`으로 선언합니다. diff --git a/docs/ko/docs/how-to/configure-swagger-ui.md b/docs/ko/docs/how-to/configure-swagger-ui.md index 174f976f6..b518fd8f7 100644 --- a/docs/ko/docs/how-to/configure-swagger-ui.md +++ b/docs/ko/docs/how-to/configure-swagger-ui.md @@ -18,7 +18,7 @@ FastAPI는 이 구성을 **JSON** 형식으로 변환하여 JavaScript와 호환 그러나 `syntaxHighlight`를 `False`로 설정하여 구문 강조 기능을 비활성화할 수 있습니다: -{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py310.py hl[3] *} ...그럼 Swagger UI에서 더 이상 구문 강조 기능이 표시되지 않습니다: @@ -28,7 +28,7 @@ FastAPI는 이 구성을 **JSON** 형식으로 변환하여 JavaScript와 호환 동일한 방식으로 `"syntaxHighlight.theme"` 키를 사용하여 구문 강조 테마를 설정할 수 있습니다 (중간에 점이 포함된 것을 참고하십시오). -{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py310.py hl[3] *} 이 설정은 구문 강조 색상 테마를 변경합니다: @@ -42,11 +42,11 @@ FastAPI는 대부분의 사용 사례에 적합한 몇 가지 기본 구성 매 {* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} -`swagger_ui_parameters` 인수에 다른 값을 설정하여 이러한 기본값 중 일부를 재정의할 수 있습니다. +`swagger_ui_parameters` 인수에 다른 값을 설정하여 이러한 기본값 중 어느 것이든 재정의할 수 있습니다. 예를 들어, `deepLinking`을 비활성화하려면 `swagger_ui_parameters`에 다음 설정을 전달할 수 있습니다: -{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial003_py310.py hl[3] *} ## 기타 Swagger UI 매개변수 { #other-swagger-ui-parameters } diff --git a/docs/ko/docs/how-to/custom-docs-ui-assets.md b/docs/ko/docs/how-to/custom-docs-ui-assets.md index d6383c29c..7eb4b3cfd 100644 --- a/docs/ko/docs/how-to/custom-docs-ui-assets.md +++ b/docs/ko/docs/how-to/custom-docs-ui-assets.md @@ -8,7 +8,7 @@ API 문서는 **Swagger UI**와 **ReDoc**을 사용하며, 각각 JavaScript와 ## JavaScript와 CSS용 커스텀 CDN { #custom-cdn-for-javascript-and-css } -예를 들어 다른 CDN을 사용하고 싶다고 해봅시다. 예를 들면 `https://unpkg.com/`을 사용하려는 경우입니다. +예를 들어 다른 CDN을 사용하고 싶다고 해봅시다. 예를 들면 `https://unpkg.com/`을 사용하려는 경우입니다. 이는 예를 들어 특정 국가에서 일부 URL을 제한하는 경우에 유용할 수 있습니다. @@ -18,7 +18,7 @@ API 문서는 **Swagger UI**와 **ReDoc**을 사용하며, 각각 JavaScript와 비활성화하려면 `FastAPI` 앱을 생성할 때 해당 URL을 `None`으로 설정하세요: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[8] *} ### 커스텀 문서 포함하기 { #include-the-custom-docs } @@ -34,7 +34,7 @@ FastAPI 내부 함수를 재사용해 문서용 HTML 페이지를 생성하고, ReDoc도 마찬가지입니다... -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[2:6,11:19,22:24,27:33] *} /// tip | 팁 @@ -50,7 +50,7 @@ Swagger UI가 이 과정을 백그라운드에서 처리해 주지만, 이를 이제 모든 것이 제대로 동작하는지 테스트할 수 있도록 *경로 처리*를 하나 만드세요: -{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py310.py hl[36:38] *} ### 테스트하기 { #test-it } @@ -118,7 +118,7 @@ JavaScript와 CSS를 자체 호스팅하는 것은 예를 들어, 오프라인 * `StaticFiles`를 import합니다. * 특정 경로에 `StaticFiles()` 인스턴스를 "마운트(mount)"합니다. -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[7,11] *} ### 정적 파일 테스트하기 { #test-the-static-files } @@ -144,7 +144,7 @@ JavaScript와 CSS를 자체 호스팅하는 것은 예를 들어, 오프라인 비활성화하려면 `FastAPI` 앱을 생성할 때 해당 URL을 `None`으로 설정하세요: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[9] *} ### 정적 파일을 위한 커스텀 문서 포함하기 { #include-the-custom-docs-for-static-files } @@ -160,7 +160,7 @@ JavaScript와 CSS를 자체 호스팅하는 것은 예를 들어, 오프라인 ReDoc도 마찬가지입니다... -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[2:6,14:22,25:27,30:36] *} /// tip | 팁 @@ -176,7 +176,7 @@ Swagger UI가 이 과정을 백그라운드에서 처리해 주지만, 이를 이제 모든 것이 제대로 동작하는지 테스트할 수 있도록 *경로 처리*를 하나 만드세요: -{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py310.py hl[39:41] *} ### 정적 파일 UI 테스트하기 { #test-static-files-ui } diff --git a/docs/ko/docs/how-to/extending-openapi.md b/docs/ko/docs/how-to/extending-openapi.md index d04d6c23e..0589e479b 100644 --- a/docs/ko/docs/how-to/extending-openapi.md +++ b/docs/ko/docs/how-to/extending-openapi.md @@ -43,19 +43,19 @@ 먼저, 평소처럼 **FastAPI** 애플리케이션을 모두 작성합니다: -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[1,4,7:9] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[1,4,7:9] *} ### OpenAPI 스키마 생성하기 { #generate-the-openapi-schema } 그다음 `custom_openapi()` 함수 안에서, 동일한 유틸리티 함수를 사용해 OpenAPI 스키마를 생성합니다: -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[2,15:21] *} ### OpenAPI 스키마 수정하기 { #modify-the-openapi-schema } 이제 OpenAPI 스키마의 `info` "object"에 커스텀 `x-logo`를 추가하여 ReDoc 확장을 더할 수 있습니다: -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[22:24] *} ### OpenAPI 스키마 캐시하기 { #cache-the-openapi-schema } @@ -65,13 +65,13 @@ 스키마는 한 번만 생성되고, 이후 요청에서는 같은 캐시된 스키마가 사용됩니다. -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[13:14,25:26] *} ### 메서드 오버라이드하기 { #override-the-method } 이제 `.openapi()` 메서드를 새 함수로 교체할 수 있습니다. -{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *} +{* ../../docs_src/extending_openapi/tutorial001_py310.py hl[29] *} ### 확인하기 { #check-it } diff --git a/docs/ko/docs/how-to/graphql.md b/docs/ko/docs/how-to/graphql.md index 3cc467eb7..a960e6d8b 100644 --- a/docs/ko/docs/how-to/graphql.md +++ b/docs/ko/docs/how-to/graphql.md @@ -35,7 +35,7 @@ 다음은 Strawberry를 FastAPI와 통합하는 방법에 대한 간단한 미리보기입니다: -{* ../../docs_src/graphql_/tutorial001_py39.py hl[3,22,25] *} +{* ../../docs_src/graphql_/tutorial001_py310.py hl[3,22,25] *} Strawberry 문서에서 Strawberry에 대해 더 알아볼 수 있습니다. diff --git a/docs/ko/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/ko/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md index 6e528ecaf..5dd40cbdc 100644 --- a/docs/ko/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md +++ b/docs/ko/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -22,7 +22,7 @@ Pydantic v1을 사용하는 오래된 FastAPI 앱이 있다면, 여기서는 이 ## 공식 가이드 { #official-guide } -Pydantic에는 v1에서 v2로의 공식 Migration Guide가 있습니다. +Pydantic에는 v1에서 v2로의 공식 마이그레이션 가이드가 있습니다. 여기에는 무엇이 바뀌었는지, 검증이 이제 어떻게 더 정확하고 엄격해졌는지, 가능한 주의사항 등도 포함되어 있습니다. @@ -30,7 +30,7 @@ Pydantic에는 v1에서 v2로의 공식 - +
**Pydantic v2**의 이 기능 덕분에 API 문서는 더 **정밀**해지고, 자동 생성된 클라이언트와 SDK가 있다면 그것들도 더 정밀해져서 더 나은 **developer experience**와 일관성을 제공할 수 있습니다. 🎉 diff --git a/docs/ko/docs/index.md b/docs/ko/docs/index.md index 776b8c47c..d2e08be16 100644 --- a/docs/ko/docs/index.md +++ b/docs/ko/docs/index.md @@ -40,7 +40,7 @@ FastAPI는 현대적이고, 빠르며(고성능), 파이썬 표준 타입 힌트 * **빠름**: (Starlette과 Pydantic 덕분에) **NodeJS** 및 **Go**와 대등할 정도로 매우 높은 성능. [사용 가능한 가장 빠른 파이썬 프레임워크 중 하나](#performance). * **빠른 코드 작성**: 약 200%에서 300%까지 기능 개발 속도 증가. * * **적은 버그**: 사람(개발자)에 의한 에러 약 40% 감소. * -* **직관적**: 훌륭한 편집기 지원. 모든 곳에서 자동완성. 적은 디버깅 시간. +* **직관적**: 훌륭한 편집기 지원. 자동완성이 모든 곳에서 동작. 적은 디버깅 시간. * **쉬움**: 쉽게 사용하고 배우도록 설계. 적은 문서 읽기 시간. * **짧음**: 코드 중복 최소화. 각 매개변수 선언의 여러 기능. 적은 버그. * **견고함**: 준비된 프로덕션 용 코드를 얻으십시오. 자동 대화형 문서와 함께. @@ -368,7 +368,7 @@ item: Item * 데이터 검증: * 데이터가 유효하지 않을 때 자동으로 생성하는 명확한 에러. * 깊이 중첩된 JSON 객체에 대한 유효성 검사. -* 입력 데이터 변환: 네트워크에서 파이썬 데이터 및 타입으로 전송. 읽을 수 있는 것들: +* 입력 데이터 변환: 네트워크에서 파이썬 데이터 및 타입으로 전송. 읽을 수 있는 것들: * JSON. * 경로 매개변수. * 쿼리 매개변수. @@ -376,7 +376,7 @@ item: Item * 헤더. * 폼(Forms). * 파일. -* 출력 데이터 변환: 파이썬 데이터 및 타입을 네트워크 데이터로 전환(JSON 형식으로): +* 출력 데이터 변환: 파이썬 데이터 및 타입을 네트워크 데이터로 전환(JSON 형식으로): * 파이썬 타입 변환 (`str`, `int`, `float`, `bool`, `list`, 등). * `datetime` 객체. * `UUID` 객체. @@ -439,7 +439,7 @@ item: Item * 서로 다른 장소에서 **매개변수** 선언: **헤더**, **쿠키**, **폼 필드** 그리고 **파일**. * `maximum_length` 또는 `regex`처럼 **유효성 제약**하는 방법. -* 강력하고 사용하기 쉬운 **의존성 주입** 시스템. +* 강력하고 사용하기 쉬운 **의존성 주입** 시스템. * **OAuth2** 지원을 포함한 **JWT tokens** 및 **HTTP Basic**을 갖는 보안과 인증. * (Pydantic 덕분에) **깊은 중첩 JSON 모델**을 선언하는데 더 진보한 (하지만 마찬가지로 쉬운) 기술. * Strawberry 및 기타 라이브러리와의 **GraphQL** 통합. @@ -524,7 +524,7 @@ Starlette이 사용하는: * httpx - `TestClient`를 사용하려면 필요. * jinja2 - 기본 템플릿 설정을 사용하려면 필요. -* python-multipart - `request.form()`과 함께 form "parsing" 지원을 원하면 필요. +* python-multipart - `request.form()`과 함께 form "파싱" 지원을 원하면 필요. FastAPI가 사용하는: diff --git a/docs/ko/docs/project-generation.md b/docs/ko/docs/project-generation.md index 73ea67d3e..e3120e6f8 100644 --- a/docs/ko/docs/project-generation.md +++ b/docs/ko/docs/project-generation.md @@ -2,7 +2,7 @@ 템플릿은 일반적으로 특정 설정과 함께 제공되지만, 유연하고 커스터마이징이 가능하게 디자인 되었습니다. 이 특성들은 여러분이 프로젝트의 요구사항에 맞춰 수정, 적용을 할 수 있게 해주고, 템플릿이 완벽한 시작점이 되게 해줍니다. 🏁 -많은 초기 설정, 보안, 데이터베이스 및 일부 API 엔드포인트가 이미 준비되어 있으므로, 여러분은 이 템플릿을 (프로젝트를) 시작하는 데 사용할 수 있습니다. +많은 초기 설정, 보안, 데이터베이스 및 일부 API 엔드포인트가 이미 준비되어 있으므로, 여러분은 이 템플릿을 시작하는 데 사용할 수 있습니다. GitHub 저장소: Full Stack FastAPI 템플릿 diff --git a/docs/ko/docs/python-types.md b/docs/ko/docs/python-types.md index dc264df80..d7d9021ed 100644 --- a/docs/ko/docs/python-types.md +++ b/docs/ko/docs/python-types.md @@ -2,7 +2,7 @@ 파이썬은 선택적으로 "타입 힌트(type hints)"(“type annotations”라고도 함)를 지원합니다. -이러한 **"타입 힌트"** 또는 애너테이션은 변수의 타입을 선언할 수 있게 해주는 특수한 구문입니다. +이러한 **"타입 힌트"** 또는 애너테이션은 변수의 타입을 선언할 수 있게 해주는 특수한 구문입니다. 변수의 타입을 선언하면 에디터와 도구가 더 나은 지원을 제공할 수 있습니다. @@ -22,7 +22,7 @@ 간단한 예제로 시작해봅시다: -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} 이 프로그램을 호출하면 다음이 출력됩니다: @@ -34,9 +34,9 @@ John Doe * `first_name`과 `last_name`를 받습니다. * `title()`로 각각의 첫 글자를 대문자로 변환합니다. -* 가운데에 공백을 두고 연결합니다. +* 가운데에 공백을 두고 연결합니다. -{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py310.py hl[2] *} ### 수정하기 { #edit-it } @@ -80,7 +80,7 @@ John Doe 이것들이 "타입 힌트"입니다: -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} 이것은 다음처럼 기본값을 선언하는 것과는 다릅니다: @@ -108,7 +108,7 @@ John Doe 이 함수를 확인해보세요. 이미 타입 힌트가 있습니다: -{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py310.py hl[1] *} 에디터가 변수의 타입을 알고 있기 때문에, 자동완성만 되는 게 아니라 오류 검사도 할 수 있습니다: @@ -116,7 +116,7 @@ John Doe 이제 고쳐야 한다는 것을 알고, `age`를 `str(age)`로 문자열로 바꿉니다: -{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py310.py hl[2] *} ## 타입 선언 { #declaring-types } @@ -135,29 +135,32 @@ John Doe * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py310.py hl[1] *} -### 타입 매개변수가 있는 Generic(제네릭) 타입 { #generic-types-with-type-parameters } +### `typing` 모듈 { #typing-module } -`dict`, `list`, `set`, `tuple`처럼 다른 값을 담을 수 있는 데이터 구조가 있습니다. 그리고 내부 값에도 각자의 타입이 있을 수 있습니다. +몇 가지 추가적인 사용 사례에서는 표준 라이브러리의 `typing` 모듈에서 무언가를 import해야 할 수 있습니다. 예를 들어 어떤 값이 "아무 타입"일 수 있다고 선언하려면, `typing`의 `Any`를 사용할 수 있습니다: -이렇게 내부 타입을 가지는 타입을 "**generic**" 타입이라고 부릅니다. 그리고 내부 타입까지 포함해 선언할 수도 있습니다. +```python +from typing import Any -이런 타입과 내부 타입을 선언하려면 표준 파이썬 모듈 `typing`을 사용할 수 있습니다. 이 모듈은 이러한 타입 힌트를 지원하기 위해 존재합니다. -#### 더 최신 버전의 Python { #newer-versions-of-python } +def some_function(data: Any): + print(data) +``` -`typing`을 사용하는 문법은 Python 3.6부터 최신 버전까지, Python 3.9, Python 3.10 등을 포함한 모든 버전과 **호환**됩니다. +### Generic(제네릭) 타입 { #generic-types } -파이썬이 발전함에 따라 **더 최신 버전**에서는 이러한 타입 애너테이션 지원이 개선되며, 많은 경우 타입 애너테이션을 선언하기 위해 `typing` 모듈을 import해서 사용할 필요조차 없게 됩니다. +일부 타입은 대괄호 안에 "타입 매개변수"를 받아 내부 타입을 정의할 수 있습니다. 예를 들어 "문자열의 리스트"는 `list[str]`로 선언합니다. -프로젝트에서 더 최신 버전의 파이썬을 선택할 수 있다면, 그 추가적인 단순함을 활용할 수 있습니다. +이렇게 타입 매개변수를 받을 수 있는 타입을 **Generic types** 또는 **Generics**라고 부릅니다. -이 문서 전체에는 각 파이썬 버전과 호환되는 예제가 있습니다(차이가 있을 때). +대괄호와 내부 타입을 사용해 동일한 내장 타입들을 제네릭으로 사용할 수 있습니다: -예를 들어 "**Python 3.6+**"는 Python 3.6 이상(3.7, 3.8, 3.9, 3.10 등 포함)과 호환된다는 뜻입니다. 그리고 "**Python 3.9+**"는 Python 3.9 이상(3.10 등 포함)과 호환된다는 뜻입니다. - -**최신 버전의 Python**을 사용할 수 있다면, 최신 버전용 예제를 사용하세요. 예를 들어 "**Python 3.10+**"처럼, 가장 **좋고 가장 단순한 문법**을 갖게 됩니다. +* `list` +* `tuple` +* `set` +* `dict` #### List { #list } @@ -169,7 +172,7 @@ John Doe `list`는 내부 타입을 포함하는 타입이므로, 그 타입들을 대괄호 안에 넣습니다: -{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[1] *} /// info | 정보 @@ -195,7 +198,7 @@ John Doe `tuple`과 `set`도 동일하게 선언할 수 있습니다: -{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial007_py310.py hl[1] *} 이는 다음을 의미합니다: @@ -210,7 +213,7 @@ John Doe 두 번째 타입 매개변수는 `dict`의 값을 위한 것입니다: -{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial008_py310.py hl[1] *} 이는 다음을 의미합니다: @@ -222,44 +225,20 @@ John Doe 변수가 **여러 타입 중 어떤 것이든** 될 수 있다고 선언할 수 있습니다. 예를 들어 `int` 또는 `str`입니다. -Python 3.6 이상(3.10 포함)에서는 `typing`의 `Union` 타입을 사용하고, 대괄호 안에 허용할 수 있는 타입들을 넣을 수 있습니다. +이를 정의하려면 두 타입을 세로 막대(`|`)로 구분해 사용합니다. -Python 3.10에는 가능한 타입들을 세로 막대(`|`)로 구분해 넣을 수 있는 **새 문법**도 있습니다. - -//// tab | Python 3.10+ +이는 두 타입 집합의 합집합(union) 안의 어느 것이든 될 수 있다는 뜻이므로 "유니온"이라고 부릅니다. ```Python hl_lines="1" {!> ../../docs_src/python_types/tutorial008b_py310.py!} ``` -//// - -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b_py39.py!} -``` - -//// - -두 경우 모두 이는 `item`이 `int` 또는 `str`일 수 있다는 뜻입니다. +이는 `item`이 `int` 또는 `str`일 수 있다는 뜻입니다. #### `None`일 수도 있음 { #possibly-none } 값이 `str` 같은 타입일 수도 있지만, `None`일 수도 있다고 선언할 수 있습니다. -Python 3.6 이상(3.10 포함)에서는 `typing` 모듈에서 `Optional`을 import해서 사용하여 선언할 수 있습니다. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009_py39.py!} -``` - -그냥 `str` 대신 `Optional[str]`을 사용하면, 값이 항상 `str`이라고 가정하고 있지만 실제로는 `None`일 수도 있는 상황에서 에디터가 오류를 감지하도록 도와줍니다. - -`Optional[Something]`은 사실 `Union[Something, None]`의 축약이며, 서로 동등합니다. - -또한 이는 Python 3.10에서 `Something | None`을 사용할 수 있다는 의미이기도 합니다: - //// tab | Python 3.10+ ```Python hl_lines="1" @@ -268,96 +247,7 @@ Python 3.6 이상(3.10 포함)에서는 `typing` 모듈에서 `Optional`을 impo //// -//// tab | Python 3.9+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009_py39.py!} -``` - -//// - -//// tab | Python 3.9+ alternative - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b_py39.py!} -``` - -//// - -#### `Union` 또는 `Optional` 사용하기 { #using-union-or-optional } - -Python 3.10 미만 버전을 사용한다면, 아주 **주관적인** 관점에서의 팁입니다: - -* 🚨 `Optional[SomeType]` 사용을 피하세요 -* 대신 ✨ **`Union[SomeType, None]`을 사용하세요** ✨. - -둘은 동등하고 내부적으로는 같은 것이지만, `Optional`이라는 단어가 값이 선택 사항인 것처럼 보일 수 있기 때문에 `Optional` 대신 `Union`을 권장합니다. 실제 의미는 값이 선택 사항이라는 뜻이 아니라, "값이 `None`일 수 있다"는 뜻이기 때문입니다. 선택 사항이 아니고 여전히 필수인 경우에도요. - -`Union[SomeType, None]`이 의미를 더 명확하게 드러낸다고 생각합니다. - -이건 단지 단어와 이름의 문제입니다. 하지만 그런 단어들이 여러분과 팀원이 코드에 대해 생각하는 방식에 영향을 줄 수 있습니다. - -예로, 이 함수를 봅시다: - -{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} - -매개변수 `name`은 `Optional[str]`로 정의되어 있지만, **선택 사항이 아닙니다**. 매개변수 없이 함수를 호출할 수 없습니다: - -```Python -say_hi() # Oh, no, this throws an error! 😱 -``` - -기본값이 없기 때문에 `name` 매개변수는 **여전히 필수입니다**(*optional*이 아님). 그럼에도 `name`은 값으로 `None`을 허용합니다: - -```Python -say_hi(name=None) # This works, None is valid 🎉 -``` - -좋은 소식은 Python 3.10을 사용하면, 타입의 유니온을 정의하기 위해 간단히 `|`를 사용할 수 있어서 이런 걱정을 할 필요가 없다는 점입니다: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -그러면 `Optional`이나 `Union` 같은 이름에 대해 걱정할 필요도 없습니다. 😎 - -#### Generic(제네릭) 타입 { #generic-types } - -대괄호 안에 타입 매개변수를 받는 이러한 타입들은 **Generic types** 또는 **Generics**라고 부릅니다. 예를 들면: - -//// tab | Python 3.10+ - -대괄호와 내부 타입을 사용해, 동일한 내장 타입들을 제네릭으로 사용할 수 있습니다: - -* `list` -* `tuple` -* `set` -* `dict` - -그리고 이전 파이썬 버전과 마찬가지로 `typing` 모듈의 다음도 사용할 수 있습니다: - -* `Union` -* `Optional` -* ...그 밖의 것들. - -Python 3.10에서는 제네릭 `Union`과 `Optional`을 사용하는 대안으로, 타입 유니온을 선언하기 위해 세로 막대(`|`)를 사용할 수 있는데, 훨씬 더 좋고 단순합니다. - -//// - -//// tab | Python 3.9+ - -대괄호와 내부 타입을 사용해, 동일한 내장 타입들을 제네릭으로 사용할 수 있습니다: - -* `list` -* `tuple` -* `set` -* `dict` - -그리고 `typing` 모듈의 제네릭들: - -* `Union` -* `Optional` -* ...그 밖의 것들. - -//// +그냥 `str` 대신 `str | None`을 사용하면, 값이 항상 `str`이라고 가정하고 있지만 실제로는 `None`일 수도 있는 상황에서 에디터가 오류를 감지하도록 도와줍니다. ### 타입으로서의 클래스 { #classes-as-types } @@ -365,11 +255,11 @@ Python 3.10에서는 제네릭 `Union`과 `Optional`을 사용하는 대안으 이름을 가진 `Person` 클래스가 있다고 해봅시다: -{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[1:3] *} 그러면 `Person` 타입의 변수를 선언할 수 있습니다: -{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py310.py hl[6] *} 그리고 다시, 에디터의 모든 지원을 받을 수 있습니다: @@ -405,19 +295,13 @@ Pydantic 공식 문서의 예시: 이 모든 것은 [자습서 - 사용자 안내서](tutorial/index.md){.internal-link target=_blank}에서 실제로 많이 보게 될 것입니다. -/// tip | 팁 - -Pydantic은 기본값 없이 `Optional` 또는 `Union[Something, None]`을 사용할 때 특별한 동작이 있습니다. 이에 대해서는 Pydantic 문서의 Required Optional fields에서 더 읽을 수 있습니다. - -/// - ## 메타데이터 애너테이션이 있는 타입 힌트 { #type-hints-with-metadata-annotations } -파이썬에는 `Annotated`를 사용해 이러한 타입 힌트에 **추가 메타데이터**를 넣을 수 있는 기능도 있습니다. +파이썬에는 `Annotated`를 사용해 이러한 타입 힌트에 **추가 메타데이터**를 넣을 수 있는 기능도 있습니다. -Python 3.9부터 `Annotated`는 표준 라이브러리의 일부이므로, `typing`에서 import할 수 있습니다. +`Annotated`는 `typing`에서 import할 수 있습니다. -{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial013_py310.py hl[1,4] *} 파이썬 자체는 이 `Annotated`로 아무것도 하지 않습니다. 그리고 에디터와 다른 도구들에게는 타입이 여전히 `str`입니다. diff --git a/docs/ko/docs/resources/index.md b/docs/ko/docs/resources/index.md index 477b93a58..f8ec8dddd 100644 --- a/docs/ko/docs/resources/index.md +++ b/docs/ko/docs/resources/index.md @@ -1,3 +1,3 @@ # 리소스 { #resources } -추가 리소스, 외부 링크 등. ✈️ +추가 리소스, 외부 링크, 그리고 더 많은 자료. ✈️ diff --git a/docs/ko/docs/translation-banner.md b/docs/ko/docs/translation-banner.md new file mode 100644 index 000000000..7bcd907d5 --- /dev/null +++ b/docs/ko/docs/translation-banner.md @@ -0,0 +1,11 @@ +/// details | 🌐 AI와 사람이 함께한 번역 + +이 번역은 사람의 안내를 받아 AI가 만들었습니다. 🤝 + +원문의 의미를 오해하거나 부자연스러워 보이는 등 오류가 있을 수 있습니다. 🤖 + +[AI LLM을 더 잘 안내하는 데 도움을 주세요](https://fastapi.tiangolo.com/ko/contributing/#translations). + +[영문 버전](ENGLISH_VERSION_URL) + +/// diff --git a/docs/ko/docs/tutorial/background-tasks.md b/docs/ko/docs/tutorial/background-tasks.md index 9e868f2fa..f23902e11 100644 --- a/docs/ko/docs/tutorial/background-tasks.md +++ b/docs/ko/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ FastAPI에서는 응답을 반환한 *후에* 실행할 백그라운드 작업 먼저 `BackgroundTasks`를 임포트하고, `BackgroundTasks` 타입 선언으로 *경로 처리 함수*에 매개변수를 정의합니다: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[1,13] *} **FastAPI**가 `BackgroundTasks` 타입의 객체를 생성하고 해당 매개변수로 전달합니다. @@ -31,13 +31,13 @@ FastAPI에서는 응답을 반환한 *후에* 실행할 백그라운드 작업 그리고 쓰기 작업은 `async`와 `await`를 사용하지 않으므로, 일반 `def`로 함수를 정의합니다: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[6:9] *} ## 백그라운드 작업 추가 { #add-the-background-task } *경로 처리 함수* 내부에서 `.add_task()` 메서드로 작업 함수를 *백그라운드 작업* 객체에 전달합니다: -{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py310.py hl[14] *} `.add_task()`는 다음 인자를 받습니다: diff --git a/docs/ko/docs/tutorial/bigger-applications.md b/docs/ko/docs/tutorial/bigger-applications.md index cfc3900d4..1b9d17e49 100644 --- a/docs/ko/docs/tutorial/bigger-applications.md +++ b/docs/ko/docs/tutorial/bigger-applications.md @@ -58,17 +58,17 @@ from app.routers import items ```bash . -├── app # "app" is a Python package -│   ├── __init__.py # this file makes "app" a "Python package" -│   ├── main.py # "main" module, e.g. import app.main -│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies -│   └── routers # "routers" is a "Python subpackage" -│   │ ├── __init__.py # makes "routers" a "Python subpackage" -│   │ ├── items.py # "items" submodule, e.g. import app.routers.items -│   │ └── users.py # "users" submodule, e.g. import app.routers.users -│   └── internal # "internal" is a "Python subpackage" -│   ├── __init__.py # makes "internal" a "Python subpackage" -│   └── admin.py # "admin" submodule, e.g. import app.internal.admin +├── app # 'app'은 Python 패키지입니다 +│   ├── __init__.py # 이 파일로 'app'이 'Python 패키지'가 됩니다 +│   ├── main.py # 'main' 모듈, 예: import app.main +│   ├── dependencies.py # 'dependencies' 모듈, 예: import app.dependencies +│   └── routers # 'routers'는 'Python 하위 패키지'입니다 +│   │ ├── __init__.py # 이 파일로 'routers'가 'Python 하위 패키지'가 됩니다 +│   │ ├── items.py # 'items' 서브모듈, 예: import app.routers.items +│   │ └── users.py # 'users' 서브모듈, 예: import app.routers.users +│   └── internal # 'internal'은 'Python 하위 패키지'입니다 +│   ├── __init__.py # 이 파일로 'internal'이 'Python 하위 패키지'가 됩니다 +│   └── admin.py # 'admin' 서브모듈, 예: import app.internal.admin ``` ## `APIRouter` { #apirouter } @@ -85,7 +85,7 @@ from app.routers import items `FastAPI` 클래스와 동일한 방식으로 import하고 "instance"를 생성합니다: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} ### `APIRouter`로 *path operations* 만들기 { #path-operations-with-apirouter } @@ -93,7 +93,7 @@ from app.routers import items `FastAPI` 클래스를 사용할 때와 동일한 방식으로 사용합니다: -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} `APIRouter`는 "미니 `FastAPI`" 클래스라고 생각할 수 있습니다. @@ -117,7 +117,7 @@ from app.routers import items 이제 간단한 dependency를 사용해 커스텀 `X-Token` 헤더를 읽어 보겠습니다: -{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | 팁 @@ -149,7 +149,7 @@ from app.routers import items 따라서 각 *path operation*마다 매번 모두 추가하는 대신, `APIRouter`에 한 번에 추가할 수 있습니다. -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} 각 *path operation*의 경로는 다음처럼 `/`로 시작해야 하므로: @@ -208,7 +208,7 @@ async def read_item(item_id: str): 그래서 dependencies에 대해 `..`를 사용하는 상대 import를 사용합니다: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[3] title["app/routers/items.py"] *} #### 상대 import가 동작하는 방식 { #how-relative-imports-work } @@ -279,7 +279,7 @@ from ...dependencies import get_token_header 하지만 특정 *path operation*에만 적용될 _추가_ `tags`를 더할 수도 있고, 그 *path operation* 전용의 추가 `responses`도 넣을 수 있습니다: -{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | 팁 @@ -305,13 +305,13 @@ from ...dependencies import get_token_header 또한 각 `APIRouter`의 dependencies와 결합될 [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank}도 선언할 수 있습니다: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[1,3,7] title["app/main.py"] *} ### `APIRouter` import하기 { #import-the-apirouter } 이제 `APIRouter`가 있는 다른 submodule들을 import합니다: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[4:5] title["app/main.py"] *} `app/routers/users.py`와 `app/routers/items.py` 파일은 같은 Python package `app`에 속한 submodule들이므로, 점 하나 `.`를 사용해 "상대 import"로 가져올 수 있습니다. @@ -374,13 +374,13 @@ from .routers.users import router 따라서 같은 파일에서 둘 다 사용할 수 있도록 submodule들을 직접 import합니다: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[5] title["app/main.py"] *} ### `users`와 `items`용 `APIRouter` 포함하기 { #include-the-apirouters-for-users-and-items } 이제 submodule `users`와 `items`의 `router`를 포함해 봅시다: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[10:11] title["app/main.py"] *} /// info | 정보 @@ -394,7 +394,7 @@ from .routers.users import router 그 router의 모든 route가 애플리케이션의 일부로 포함됩니다. -/// note Technical Details | 기술 세부사항 +/// note | 기술 세부사항 내부적으로는 `APIRouter`에 선언된 각 *path operation*마다 *path operation*을 실제로 생성합니다. @@ -420,13 +420,13 @@ router를 포함(include)할 때 성능을 걱정할 필요는 없습니다. 이 예시에서는 매우 단순하게 만들겠습니다. 하지만 조직 내 다른 프로젝트와 공유되기 때문에, 이를 수정할 수 없어 `prefix`, `dependencies`, `tags` 등을 `APIRouter`에 직접 추가할 수 없다고 해봅시다: -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} 하지만 `APIRouter`를 포함할 때 커스텀 `prefix`를 지정해 모든 *path operations*가 `/admin`으로 시작하게 하고, 이 프로젝트에서 이미 가진 `dependencies`로 보호하고, `tags`와 `responses`도 포함하고 싶습니다. 원래 `APIRouter`를 수정하지 않고도 `app.include_router()`에 파라미터를 전달해서 이를 선언할 수 있습니다: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[14:17] title["app/main.py"] *} 이렇게 하면 원래 `APIRouter`는 수정되지 않으므로, 조직 내 다른 프로젝트에서도 동일한 `app/internal/admin.py` 파일을 계속 공유할 수 있습니다. @@ -447,11 +447,11 @@ router를 포함(include)할 때 성능을 걱정할 필요는 없습니다. 여기서는 가능하다는 것을 보여주기 위해... 그냥 해봅니다 🤷: -{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/main.py hl[21:23] title["app/main.py"] *} 그리고 `app.include_router()`로 추가한 다른 모든 *path operations*와 함께 올바르게 동작합니다. -/// info | 정보 +/// info | 매우 기술적인 세부사항 **참고**: 이는 매우 기술적인 세부사항이라 아마 **그냥 건너뛰어도 됩니다**. diff --git a/docs/ko/docs/tutorial/body-fields.md b/docs/ko/docs/tutorial/body-fields.md index c98734ab3..0774dc441 100644 --- a/docs/ko/docs/tutorial/body-fields.md +++ b/docs/ko/docs/tutorial/body-fields.md @@ -48,8 +48,8 @@ /// warning | 경고 -별도 키가 전달된 `Field` 또한 여러분의 어플리케이션의 OpenAPI 스키마에 나타날 것입니다. -이런 키가 OpenAPI 명세서, [the OpenAPI validator](https://validator.swagger.io/)같은 몇몇 OpenAPI 도구들에 포함되지 못할 수 있으며, 여러분이 생성한 스키마와 호환되지 않을 수 있습니다. +별도 키가 전달된 `Field` 또한 여러분의 애플리케이션의 OpenAPI 스키마에 나타날 것입니다. +이런 키가 OpenAPI 명세서의 일부가 아닐 수도 있으므로, [OpenAPI validator](https://validator.swagger.io/) 같은 몇몇 OpenAPI 도구들은 여러분이 생성한 스키마와 호환되지 않을 수 있습니다. /// diff --git a/docs/ko/docs/tutorial/body-multiple-params.md b/docs/ko/docs/tutorial/body-multiple-params.md index bebdffed8..3db614d72 100644 --- a/docs/ko/docs/tutorial/body-multiple-params.md +++ b/docs/ko/docs/tutorial/body-multiple-params.md @@ -106,13 +106,6 @@ q: str | None = None ``` -또는 Python 3.9에서는: - -```Python -q: Union[str, None] = None -``` - - 예를 들어: {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} diff --git a/docs/ko/docs/tutorial/body-nested-models.md b/docs/ko/docs/tutorial/body-nested-models.md index 4a8c1afc1..33f0f71e9 100644 --- a/docs/ko/docs/tutorial/body-nested-models.md +++ b/docs/ko/docs/tutorial/body-nested-models.md @@ -164,7 +164,7 @@ images: list[Image] 이를 아래처럼: -{* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} +{* ../../docs_src/body_nested_models/tutorial008_py310.py hl[13] *} ## 어디서나 편집기 지원 { #editor-support-everywhere } @@ -194,7 +194,7 @@ Pydantic 모델 대신 `dict`로 직접 작업한다면 이런 종류의 편집 이 경우, `int` 키와 `float` 값을 가진 한 어떤 `dict`든 받아들입니다: -{* ../../docs_src/body_nested_models/tutorial009_py39.py hl[7] *} +{* ../../docs_src/body_nested_models/tutorial009_py310.py hl[7] *} /// tip | 팁 diff --git a/docs/ko/docs/tutorial/body.md b/docs/ko/docs/tutorial/body.md index 1e66c60c2..b282d1cc9 100644 --- a/docs/ko/docs/tutorial/body.md +++ b/docs/ko/docs/tutorial/body.md @@ -74,7 +74,7 @@ * 매개변수 `item`에 포함된 수신 데이터를 제공합니다. * 함수 내에서 매개변수를 `Item` 타입으로 선언했기 때문에, 모든 어트리뷰트와 그에 대한 타입에 대한 편집기 지원(완성 등)을 또한 받을 수 있습니다. * 여러분의 모델을 위한 JSON Schema 정의를 생성합니다. 여러분의 프로젝트에 적합하다면 여러분이 사용하고 싶은 곳 어디에서나 사용할 수 있습니다. -* 이러한 스키마는, 생성된 OpenAPI 스키마 일부가 될 것이며, 자동 문서화 UIs에 사용됩니다. +* 이러한 스키마는, 생성된 OpenAPI 스키마 일부가 될 것이며, 자동 문서화 UIs에 사용됩니다. ## 자동 문서화 { #automatic-docs } @@ -141,7 +141,7 @@ **본문**, **경로** 그리고 **쿼리** 매개변수 모두 동시에 선언할 수도 있습니다. -**FastAPI**는 각각을 인지하고 데이터를 옳바른 위치에 가져올 것입니다. +**FastAPI**는 각각을 인지하고 데이터를 올바른 위치에 가져올 것입니다. {* ../../docs_src/body/tutorial004_py310.py hl[16] *} @@ -153,9 +153,9 @@ /// note | 참고 -FastAPI는 `q`의 값이 필요없음을 알게 될 것입니다. 기본 값이 `= None`이기 때문입니다. +FastAPI는 `q`의 값이 필요없음을 기본 값 `= None` 때문에 알게 됩니다. -Python 3.10+의 `str | None` 또는 Python 3.9+의 `Union[str, None]`에 있는 `Union`은 FastAPI가 `q` 값이 필수가 아님을 판단하기 위해 사용하지 않습니다. 기본 값이 `= None`이기 때문에 필수가 아님을 알게 됩니다. +`str | None`은 FastAPI가 값이 필수인지 아닌지를 판단하기 위해 사용하지 않습니다. 기본 값이 `= None`이므로 필수가 아님을 알게 됩니다. 하지만 타입 어노테이션을 추가하면 편집기가 더 나은 지원을 제공하고 오류를 감지할 수 있습니다. diff --git a/docs/ko/docs/tutorial/cookie-param-models.md b/docs/ko/docs/tutorial/cookie-param-models.md index 00238d1b7..70b76e09c 100644 --- a/docs/ko/docs/tutorial/cookie-param-models.md +++ b/docs/ko/docs/tutorial/cookie-param-models.md @@ -46,7 +46,7 @@ 일부 특별한 사용 사례(흔하지는 않겠지만)에서는 수신하려는 쿠키를 **제한**할 수 있습니다. -이제 API는 자신의 cookie consent를 제어할 수 있는 권한을 갖게 되었습니다. 🤪🍪 +이제 API는 자신의 쿠키 동의를 제어할 수 있는 권한을 갖게 되었습니다. 🤪🍪 Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forbid`)할 수 있습니다: @@ -54,9 +54,9 @@ Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forb 클라이언트가 **추가 쿠키**를 보내려고 시도하면, **오류** 응답을 받게 됩니다. -동의를 얻기 위해 애쓰는 불쌍한 쿠키 배너(팝업)들, API가 거부하는데도. 🍪 +동의를 얻기 위해 애쓰는 불쌍한 쿠키 배너(팝업)들, API가 거부하는데도. 🍪 -예를 들어, 클라이언트가 `good-list-please` 값으로 `santa_tracker` 쿠키를 보내려고 하면 클라이언트는 `santa_tracker` 쿠키가 허용되지 않는다는 **오류** 응답을 받게 됩니다: +예를 들어, 클라이언트가 `good-list-please` 값으로 `santa_tracker` 쿠키를 보내려고 하면 클라이언트는 `santa_tracker` 쿠키가 허용되지 않는다는 **오류** 응답을 받게 됩니다: ```json { @@ -73,4 +73,4 @@ Pydantic의 모델 구성을 사용하여 추가(`extra`) 필드를 금지(`forb ## 요약 { #summary } -**Pydantic 모델**을 사용하여 **FastAPI**에서 **쿠키**를 선언할 수 있습니다. 😎 +**Pydantic 모델**을 사용하여 **FastAPI**에서 **쿠키**를 선언할 수 있습니다. 😎 diff --git a/docs/ko/docs/tutorial/cookie-params.md b/docs/ko/docs/tutorial/cookie-params.md index 0591a5e96..6ea09101c 100644 --- a/docs/ko/docs/tutorial/cookie-params.md +++ b/docs/ko/docs/tutorial/cookie-params.md @@ -24,13 +24,13 @@ /// -/// info | 정보 +/// info 쿠키를 선언하기 위해서는 `Cookie`를 사용해야 합니다. 그렇지 않으면 해당 매개변수를 쿼리 매개변수로 해석하기 때문입니다. /// -/// info | 정보 +/// info **브라우저는 쿠키를** 내부적으로 특별한 방식으로 처리하기 때문에, **JavaScript**가 쉽게 쿠키를 다루도록 허용하지 않는다는 점을 염두에 두세요. diff --git a/docs/ko/docs/tutorial/cors.md b/docs/ko/docs/tutorial/cors.md index 0f3948a3d..f78e1c070 100644 --- a/docs/ko/docs/tutorial/cors.md +++ b/docs/ko/docs/tutorial/cors.md @@ -46,7 +46,7 @@ * 특정 HTTP 메서드(`POST`, `PUT`) 또는 와일드카드 `"*"`를 사용한 모든 메서드. * 특정 HTTP 헤더 또는 와일드카드 `"*"`를 사용한 모든 헤더. -{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py310.py hl[2,6:11,13:19] *} `CORSMiddleware` 구현에서 사용하는 기본 매개변수는 기본적으로 제한적이므로, 브라우저가 Cross-Domain 컨텍스트에서 특정 출처, 메서드 또는 헤더를 사용할 수 있도록 하려면 이를 명시적으로 활성화해야 합니다. @@ -78,7 +78,7 @@ ## 더 많은 정보 { #more-info } -CORS에 대한 더 많은 정보는 Mozilla CORS 문서를 참고하세요. +CORS에 대한 더 많은 정보는 Mozilla CORS 문서를 참고하세요. /// note | 기술 세부사항 diff --git a/docs/ko/docs/tutorial/debugging.md b/docs/ko/docs/tutorial/debugging.md index ca20acff6..c27b68bc1 100644 --- a/docs/ko/docs/tutorial/debugging.md +++ b/docs/ko/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ FastAPI 애플리케이션에서 `uvicorn`을 직접 임포트하여 실행합니다: -{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py310.py hl[1,15] *} ### `__name__ == "__main__"` 에 대하여 { #about-name-main } diff --git a/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md index 68bba669a..83749f7b0 100644 --- a/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/ko/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ FastAPI가 실제로 확인하는 것은 그것이 "호출 가능(callable)"(함 위 코드에서 `CommonQueryParams`를 두 번 작성하는 방식에 주목하세요: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ Annotated 미사용 /// tip | 팁 @@ -137,7 +137,7 @@ FastAPI는 여기에서 선언된 매개변수들을 추출하고, 실제로 이 이 경우 첫 번째 `CommonQueryParams`는 다음에서: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ Annotated 미사용 /// tip | 팁 @@ -163,7 +163,7 @@ commons: CommonQueryParams ... 실제로는 이렇게만 작성해도 됩니다: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ Annotated 미사용 /// tip | 팁 @@ -197,7 +197,7 @@ commons = Depends(CommonQueryParams) 하지만 `CommonQueryParams`를 두 번 작성하는 코드 반복이 있다는 것을 볼 수 있습니다: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ Annotated 미사용 /// tip | 팁 @@ -225,7 +225,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) 다음처럼 작성하는 대신: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ Annotated 미사용 /// tip | 팁 @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...이렇게 작성합니다: -//// tab | Python 3.9+ +//// tab | Python 3.10+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.9+ non-Annotated +//// tab | Python 3.10+ Annotated 미사용 /// tip | 팁 diff --git a/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index 39c78c078..d269e7e77 100644 --- a/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/ko/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -1,28 +1,28 @@ -# 경로 작동 데코레이터에서의 의존성 { #dependencies-in-path-operation-decorators } +# 경로 처리 데코레이터에서의 의존성 { #dependencies-in-path-operation-decorators } -몇몇 경우에는, *경로 작동 함수* 안에서 의존성의 반환 값이 필요하지 않습니다. +몇몇 경우에는, *경로 처리 함수* 안에서 의존성의 반환 값이 필요하지 않습니다. 또는 의존성이 값을 반환하지 않습니다. 그러나 여전히 실행/해결될 필요가 있습니다. -그런 경우에, `Depends`를 사용하여 *경로 작동 함수*의 매개변수로 선언하는 것보다 *경로 작동 데코레이터*에 `dependencies`의 `list`를 추가할 수 있습니다. +그런 경우에, `Depends`를 사용하여 *경로 처리 함수*의 매개변수로 선언하는 대신 *경로 처리 데코레이터*에 `dependencies`의 `list`를 추가할 수 있습니다. -## *경로 작동 데코레이터*에 `dependencies` 추가하기 { #add-dependencies-to-the-path-operation-decorator } +## *경로 처리 데코레이터*에 `dependencies` 추가하기 { #add-dependencies-to-the-path-operation-decorator } -*경로 작동 데코레이터*는 `dependencies`라는 선택적인 인자를 받습니다. +*경로 처리 데코레이터*는 선택적인 인자 `dependencies`를 받습니다. -`Depends()`로 된 `list`이어야합니다: +`Depends()`로 된 `list`이어야 합니다: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[19] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *} -이러한 의존성들은 기존 의존성들과 같은 방식으로 실행/해결됩니다. 그러나 값은 (무엇이든 반환한다면) *경로 작동 함수*에 제공되지 않습니다. +이러한 의존성들은 기존 의존성들과 같은 방식으로 실행/해결됩니다. 그러나 값은 (무엇이든 반환한다면) *경로 처리 함수*에 제공되지 않습니다. /// tip | 팁 일부 편집기에서는 사용되지 않는 함수 매개변수를 검사하고 오류로 표시합니다. -*경로 작동 데코레이터*에서 이러한 `dependencies`를 사용하면 편집기/도구 오류를 피하면서도 실행되도록 할 수 있습니다. +*경로 처리 데코레이터*에서 이러한 `dependencies`를 사용하면 편집기/도구 오류를 피하면서도 실행되도록 할 수 있습니다. 또한 코드에서 사용되지 않는 매개변수를 보고 불필요하다고 생각할 수 있는 새로운 개발자의 혼란을 방지하는데 도움이 될 수 있습니다. @@ -44,13 +44,13 @@ (헤더같은) 요청 요구사항이나 하위-의존성을 선언할 수 있습니다: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[8,13] *} ### 오류 발생시키기 { #raise-exceptions } 다음 의존성은 기존 의존성과 동일하게 예외를 `raise`할 수 있습니다: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[10,15] *} ### 값 반환하기 { #return-values } @@ -58,12 +58,12 @@ 그래서 이미 다른 곳에서 사용된 (값을 반환하는) 일반적인 의존성을 재사용할 수 있고, 비록 값은 사용되지 않지만 의존성은 실행될 것입니다: -{* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} +{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[11,16] *} -## *경로 작동* 모음에 대한 의존성 { #dependencies-for-a-group-of-path-operations } +## *경로 처리* 모음에 대한 의존성 { #dependencies-for-a-group-of-path-operations } -나중에 여러 파일을 가지고 있을 수 있는 더 큰 애플리케이션을 구조화하는 법([더 큰 애플리케이션 - 여러 파일들](../../tutorial/bigger-applications.md){.internal-link target=_blank})을 읽을 때, *경로 작동* 모음에 대한 단일 `dependencies` 매개변수를 선언하는 법에 대해서 배우게 될 것입니다. +나중에 여러 파일을 가지고 있을 수 있는 더 큰 애플리케이션을 구조화하는 법([더 큰 애플리케이션 - 여러 파일들](../../tutorial/bigger-applications.md){.internal-link target=_blank})을 읽을 때, *경로 처리* 모음에 대한 단일 `dependencies` 매개변수를 선언하는 법에 대해서 배우게 될 것입니다. ## 전역 의존성 { #global-dependencies } -다음으로 각 *경로 작동*에 적용되도록 `FastAPI` 애플리케이션 전체에 의존성을 추가하는 법을 볼 것입니다. +다음으로 각 *경로 처리*에 적용되도록 `FastAPI` 애플리케이션 전체에 의존성을 추가하는 법을 볼 것입니다. diff --git a/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md index 9bf6c0693..7b50fd438 100644 --- a/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/ko/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,6 +1,6 @@ # `yield`를 사용하는 의존성 { #dependencies-with-yield } -FastAPI는 작업 완료 후 추가 단계를 수행하는 의존성을 지원합니다. +FastAPI는 작업 완료 후 추가 단계를 수행하는 의존성을 지원합니다. 이를 구현하려면 `return` 대신 `yield`를 사용하고, 추가로 실행할 단계 (코드)를 그 뒤에 작성하세요. @@ -29,15 +29,15 @@ FastAPI는 CORS를 처리하는 방법을 보게 될 것입니다. +다음 섹션에서 미들웨어로 CORS를 처리하는 방법을 보게 될 것입니다. diff --git a/docs/ko/docs/tutorial/path-operation-configuration.md b/docs/ko/docs/tutorial/path-operation-configuration.md index baef3fb40..0d6a0d422 100644 --- a/docs/ko/docs/tutorial/path-operation-configuration.md +++ b/docs/ko/docs/tutorial/path-operation-configuration.md @@ -46,7 +46,7 @@ **FastAPI**는 일반 문자열과 동일한 방식으로 이를 지원합니다: -{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *} +{* ../../docs_src/path_operation_configuration/tutorial002b_py310.py hl[1,8:10,13,18] *} ## 요약과 설명 { #summary-and-description } @@ -56,7 +56,7 @@ ## 독스트링으로 만든 설명 { #description-from-docstring } -설명은 보통 길어지고 여러 줄에 걸쳐있기 때문에, *경로 처리* 설명을 함수 docstring에 선언할 수 있으며, **FastAPI**는 그곳에서 이를 읽습니다. +설명은 보통 길어지고 여러 줄에 걸쳐있기 때문에, *경로 처리* 설명을 함수 독스트링에 선언할 수 있으며, **FastAPI**는 그곳에서 이를 읽습니다. 독스트링에는 Markdown을 작성할 수 있으며, (독스트링의 들여쓰기를 고려하여) 올바르게 해석되고 표시됩니다. @@ -90,9 +90,9 @@ OpenAPI는 각 *경로 처리*가 응답에 관한 설명을 요구할 것을 ## *경로 처리* 지원중단하기 { #deprecate-a-path-operation } -*경로 처리*를 제거하지 않고 deprecated로 표시해야 한다면, `deprecated` 매개변수를 전달하면 됩니다: +*경로 처리*를 제거하지 않고 지원중단으로 표시해야 한다면, `deprecated` 매개변수를 전달하면 됩니다: -{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py310.py hl[16] *} 대화형 문서에서 지원중단으로 명확하게 표시됩니다: diff --git a/docs/ko/docs/tutorial/path-params-numeric-validations.md b/docs/ko/docs/tutorial/path-params-numeric-validations.md index f2c52d4aa..51f9fe2c1 100644 --- a/docs/ko/docs/tutorial/path-params-numeric-validations.md +++ b/docs/ko/docs/tutorial/path-params-numeric-validations.md @@ -54,11 +54,11 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이 따라서 함수를 다음과 같이 선언할 수 있습니다: -{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py310.py hl[7] *} 하지만 `Annotated`를 사용하면 이 문제가 없다는 점을 기억하세요. `Query()`나 `Path()`에 함수 매개변수 기본값을 사용하지 않기 때문에, 순서는 중요하지 않습니다. -{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py39.py *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_an_py310.py *} ## 필요한 대로 매개변수 정렬하기, 트릭 { #order-the-parameters-as-you-need-tricks } @@ -83,13 +83,13 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이 파이썬은 `*`으로 아무것도 하지 않지만, 뒤따르는 모든 매개변수는 키워드 인자(키-값 쌍)로 호출되어야 함을 알게 됩니다. 이는 kwargs로도 알려져 있습니다. 기본값이 없더라도 마찬가지입니다. -{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py310.py hl[7] *} ### `Annotated`를 쓰면 더 좋습니다 { #better-with-annotated } `Annotated`를 사용하면 함수 매개변수 기본값을 사용하지 않기 때문에 이 문제가 발생하지 않으며, 아마 `*`도 사용할 필요가 없다는 점을 기억하세요. -{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_an_py310.py hl[10] *} ## 숫자 검증: 크거나 같음 { #number-validations-greater-than-or-equal } @@ -97,7 +97,7 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이 여기서 `ge=1`인 경우, `item_id`는 `1`보다 "`g`reater than or `e`qual"(크거나 같은) 정수형 숫자여야 합니다. -{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial004_an_py310.py hl[10] *} ## 숫자 검증: 크거나 및 작거나 같음 { #number-validations-greater-than-and-less-than-or-equal } @@ -106,7 +106,7 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이 * `gt`: `g`reater `t`han * `le`: `l`ess than or `e`qual -{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py39.py hl[10] *} +{* ../../docs_src/path_params_numeric_validations/tutorial005_an_py310.py hl[10] *} ## 숫자 검증: 부동소수, 크거나 및 작거나 { #number-validations-floats-greater-than-and-less-than } @@ -118,7 +118,7 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이 lt 역시 마찬가지입니다. -{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} +{* ../../docs_src/path_params_numeric_validations/tutorial006_an_py310.py hl[13] *} ## 요약 { #recap } diff --git a/docs/ko/docs/tutorial/path-params.md b/docs/ko/docs/tutorial/path-params.md index ea5170ecc..c6e973709 100644 --- a/docs/ko/docs/tutorial/path-params.md +++ b/docs/ko/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ 파이썬의 포맷 문자열 리터럴에서 사용되는 문법을 이용하여 경로 "매개변수" 또는 "변수"를 선언할 수 있습니다: -{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py310.py hl[6:7] *} 경로 매개변수 `item_id`의 값은 함수의 `item_id` 인자로 전달됩니다. @@ -16,7 +16,7 @@ 파이썬 표준 타입 어노테이션을 사용하여 함수에 있는 경로 매개변수의 타입을 선언할 수 있습니다: -{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *} +{* ../../docs_src/path_params/tutorial002_py310.py hl[7] *} 위의 예시에서, `item_id`는 `int`로 선언되었습니다. @@ -26,7 +26,7 @@ /// -## 데이터 변환 { #data-conversion } +## 데이터 변환 { #data-conversion } 이 예제를 실행하고 http://127.0.0.1:8000/items/3을 열면, 다음 응답을 볼 수 있습니다: @@ -38,7 +38,7 @@ 함수가 받은(반환도 하는) 값은 문자열 `"3"`이 아니라 파이썬 `int` 형인 `3`입니다. -즉, 타입 선언을 하면 **FastAPI**는 자동으로 요청을 "파싱"합니다. +즉, 타입 선언을 하면 **FastAPI**는 자동으로 요청을 "파싱"합니다. /// @@ -116,21 +116,21 @@ 사용자 ID를 이용해 특정 사용자의 정보를 가져오는 경로 `/users/{user_id}`도 있습니다. -*경로 처리*는 순차적으로 평가되기 때문에 `/users/{user_id}` 이전에 `/users/me`에 대한 경로가 먼저 선언되었는지 확인해야 합니다: +*경로 처리*는 순차적으로 평가되기 때문에 `/users/me`에 대한 경로가 `/users/{user_id}` 이전에 먼저 선언되었는지 확인해야 합니다: -{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py310.py hl[6,11] *} 그렇지 않으면 `/users/{user_id}`에 대한 경로가 `/users/me`에도 매칭되어, 매개변수 `user_id`에 `"me"` 값이 들어왔다고 "생각하게" 됩니다. 마찬가지로, 경로 처리를 재정의할 수는 없습니다: -{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003b_py310.py hl[6,11] *} 경로가 먼저 매칭되기 때문에 첫 번째 것이 항상 사용됩니다. ## 사전정의 값 { #predefined-values } -만약 *경로 매개변수*를 받는 *경로 처리*가 있지만, 가능한 유효한 *경로 매개변수* 값들을 미리 정의하고 싶다면 파이썬 표준 `Enum`을 사용할 수 있습니다. +만약 *경로 매개변수*를 받는 *경로 처리*가 있지만, 가능한 유효한 *경로 매개변수* 값들을 미리 정의하고 싶다면 파이썬 표준 `Enum`을 사용할 수 있습니다. ### `Enum` 클래스 생성 { #create-an-enum-class } @@ -140,11 +140,11 @@ 가능한 값들에 해당하는 고정된 값의 클래스 어트리뷰트들을 만듭니다: -{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[1,6:9] *} /// tip | 팁 -혹시 궁금하다면, "AlexNet", "ResNet", 그리고 "LeNet"은 그저 머신 러닝 모델들의 이름입니다. +혹시 궁금하다면, "AlexNet", "ResNet", 그리고 "LeNet"은 그저 머신 러닝 모델들의 이름입니다. /// @@ -152,7 +152,7 @@ 생성한 열거형 클래스(`ModelName`)를 사용하는 타입 어노테이션으로 *경로 매개변수*를 만듭니다: -{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[16] *} ### 문서 확인 { #check-the-docs } @@ -168,13 +168,13 @@ 생성한 열거형 `ModelName`의 *열거형 멤버*와 비교할 수 있습니다: -{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[17] *} #### *열거형 값* 가져오기 { #get-the-enumeration-value } `model_name.value` 또는 일반적으로 `your_enum_member.value`를 이용하여 실제 값(위 예시의 경우 `str`)을 가져올 수 있습니다: -{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[20] *} /// tip | 팁 @@ -188,7 +188,7 @@ 클라이언트에 반환하기 전에 해당 값(이 경우 문자열)으로 변환됩니다: -{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py310.py hl[18,21,23] *} 클라이언트는 아래와 같은 JSON 응답을 얻게 됩니다: @@ -227,7 +227,7 @@ Starlette의 옵션을 직접 이용하여 다음과 같은 URL을 사용함으 따라서 다음과 같이 사용할 수 있습니다: -{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py310.py hl[6] *} /// tip | 팁 @@ -242,7 +242,7 @@ Starlette의 옵션을 직접 이용하여 다음과 같은 URL을 사용함으 **FastAPI**를 이용하면 짧고 직관적인 표준 파이썬 타입 선언을 사용하여 다음을 얻을 수 있습니다: * 편집기 지원: 오류 검사, 자동완성 등 -* 데이터 "parsing" +* 데이터 "파싱" * 데이터 검증 * API 주석(Annotation)과 자동 문서 diff --git a/docs/ko/docs/tutorial/query-params-str-validations.md b/docs/ko/docs/tutorial/query-params-str-validations.md index 68824932e..2b608fd1d 100644 --- a/docs/ko/docs/tutorial/query-params-str-validations.md +++ b/docs/ko/docs/tutorial/query-params-str-validations.md @@ -2,7 +2,7 @@ **FastAPI**를 사용하면 매개변수에 대한 추가 정보 및 검증을 선언할 수 있습니다. -이 응용 프로그램을 예로 들어보겠습니다: +이 애플리케이션을 예로 들어보겠습니다: {* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} @@ -41,46 +41,22 @@ FastAPI는 0.95.0 버전에서 `Annotated` 지원을 추가했고(그리고 이 ## `q` 매개변수의 타입에 `Annotated` 사용하기 { #use-annotated-in-the-type-for-the-q-parameter } -이전에 [Python Types Intro](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}에서 `Annotated`를 사용해 매개변수에 메타데이터를 추가할 수 있다고 말씀드린 것을 기억하시나요? +이전에 [Python 타입 소개](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}에서 `Annotated`를 사용해 매개변수에 메타데이터를 추가할 수 있다고 말씀드린 것을 기억하시나요? 이제 FastAPI에서 사용할 차례입니다. 🚀 다음과 같은 타입 어노테이션이 있었습니다: -//// tab | Python 3.10+ - ```Python q: str | None = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Union[str, None] = None -``` - -//// - 여기서 `Annotated`로 감싸서 다음과 같이 만듭니다: -//// tab | Python 3.10+ - ```Python q: Annotated[str | None] = None ``` -//// - -//// tab | Python 3.9+ - -```Python -q: Annotated[Union[str, None]] = None -``` - -//// - 두 버전 모두 같은 의미로, `q`는 `str` 또는 `None`이 될 수 있는 매개변수이며 기본값은 `None`입니다. 이제 재미있는 부분으로 넘어가 봅시다. 🎉 @@ -109,7 +85,7 @@ q: Annotated[Union[str, None]] = None ## 대안(이전 방식): 기본값으로 `Query` 사용 { #alternative-old-query-as-the-default-value } -이전 FastAPI 버전(0.95.0 이전)에서는 `Annotated`에 넣는 대신, 매개변수의 기본값으로 `Query`를 사용해야 했습니다. 주변에서 이 방식을 사용하는 코드를 볼 가능성이 높기 때문에 설명해 드리겠습니다. +이전 FastAPI 버전(0.95.0 이전)에서는 `Annotated`에 넣는 대신, 매개변수의 기본값으로 `Query`를 사용해야 했습니다. 주변에서 이 방식을 사용하는 코드를 볼 가능성이 높기 때문에 설명해 드리겠습니다. /// tip | 팁 @@ -192,7 +168,7 @@ FastAPI 없이도 **다른 곳에서** 같은 함수를 **호출**할 수 있고 ## 정규식 추가 { #add-regular-expressions } -매개변수와 일치해야 하는 정규 표현식 `pattern`을 정의할 수 있습니다: +매개변수와 일치해야 하는 정규 표현식 `pattern`을 정의할 수 있습니다: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} @@ -212,7 +188,7 @@ FastAPI 없이도 **다른 곳에서** 같은 함수를 **호출**할 수 있고 `q` 쿼리 매개변수에 `min_length`를 `3`으로 설정하고, 기본값을 `"fixedquery"`로 선언하고 싶다고 해봅시다: -{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial005_an_py310.py hl[9] *} /// note | 참고 @@ -242,7 +218,7 @@ q: Annotated[str | None, Query(min_length=3)] = None 따라서 `Query`를 사용하면서 값을 필수로 선언해야 할 때는, 기본값을 선언하지 않으면 됩니다: -{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial006_an_py310.py hl[9] *} ### 필수지만 `None` 가능 { #required-can-be-none } @@ -293,7 +269,7 @@ http://localhost:8000/items/?q=foo&q=bar 제공된 값이 없으면 기본 `list` 값을 정의할 수도 있습니다: -{* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial012_an_py310.py hl[9] *} 다음으로 이동하면: @@ -316,7 +292,7 @@ http://localhost:8000/items/ `list[str]` 대신 `list`를 직접 사용할 수도 있습니다: -{* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} +{* ../../docs_src/query_params_str_validations/tutorial013_an_py310.py hl[9] *} /// note | 참고 @@ -372,7 +348,7 @@ http://127.0.0.1:8000/items/?item-query=foobaritems 이제는 더 이상 이 매개변수를 마음에 들어하지 않는다고 가정해 봅시다. -이 매개변수를 사용하는 클라이언트가 있기 때문에 한동안은 남겨둬야 하지만, 문서에서 deprecated로 명확하게 보여주고 싶습니다. +이 매개변수를 사용하는 클라이언트가 있기 때문에 한동안은 남겨둬야 하지만, 문서에서 사용 중단됨으로 명확하게 보여주고 싶습니다. 그렇다면 `deprecated=True` 매개변수를 `Query`로 전달합니다: @@ -402,7 +378,7 @@ Pydantic에는 ISBN 도서 번호의 경우 아이템 ID가 `isbn-`으로 시작하고, IMDB 영화 URL ID의 경우 `imdb-`로 시작하는지 확인합니다: +예를 들어, 이 커스텀 validator는 ISBN 도서 번호의 경우 아이템 ID가 `isbn-`으로 시작하고, IMDB 영화 URL ID의 경우 `imdb-`로 시작하는지 확인합니다: {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} @@ -436,9 +412,9 @@ Pydantic에는 iterable object를 얻습니다. +`data.items()`를 사용하면 각 딕셔너리 항목의 키와 값을 담은 튜플로 구성된 이터러블 객체를 얻습니다. -이 iterable object를 `list(data.items())`로 적절한 `list`로 변환합니다. +이 이터러블 객체를 `list(data.items())`로 적절한 `list`로 변환합니다. 그 다음 `random.choice()`로 리스트에서 **무작위 값**을 얻어 `(id, name)` 형태의 튜플을 얻습니다. 예를 들어 `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")` 같은 값이 될 것입니다. diff --git a/docs/ko/docs/tutorial/query-params.md b/docs/ko/docs/tutorial/query-params.md index 5124f73bf..0a6b1f922 100644 --- a/docs/ko/docs/tutorial/query-params.md +++ b/docs/ko/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ 경로 매개변수의 일부가 아닌 다른 함수 매개변수를 선언하면 "쿼리" 매개변수로 자동 해석합니다. -{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py310.py hl[9] *} 쿼리는 URL에서 `?` 후에 나오고 `&`으로 구분되는 키-값 쌍의 집합입니다. @@ -24,7 +24,7 @@ URL의 일부이므로 "자연스럽게" 문자열입니다. 경로 매개변수에 적용된 동일한 프로세스가 쿼리 매개변수에도 적용됩니다: * (당연히) 편집기 지원 -* 데이터 "파싱" +* 데이터 "파싱" * 데이터 검증 * 자동 문서화 @@ -128,7 +128,7 @@ http://127.0.0.1:8000/items/foo?short=yes 그러나 쿼리 매개변수를 필수로 만들려면 단순히 기본값을 선언하지 않으면 됩니다: -{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py310.py hl[6:7] *} 여기 쿼리 매개변수 `needy`는 `str`형인 필수 쿼리 매개변수입니다. diff --git a/docs/ko/docs/tutorial/request-files.md b/docs/ko/docs/tutorial/request-files.md index cc0000921..3ee0fa74c 100644 --- a/docs/ko/docs/tutorial/request-files.md +++ b/docs/ko/docs/tutorial/request-files.md @@ -20,13 +20,13 @@ $ pip install python-multipart `fastapi` 에서 `File` 과 `UploadFile` 을 임포트 합니다: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[3] *} ## `File` 매개변수 정의 { #define-file-parameters } `Body` 및 `Form` 과 동일한 방식으로 파일의 매개변수를 생성합니다: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[9] *} /// info | 정보 @@ -54,7 +54,7 @@ File의 본문을 선언할 때, 매개변수가 쿼리 매개변수 또는 본 `File` 매개변수를 `UploadFile` 타입으로 정의합니다: -{* ../../docs_src/request_files/tutorial001_an_py39.py hl[14] *} +{* ../../docs_src/request_files/tutorial001_an_py310.py hl[14] *} `UploadFile` 을 사용하는 것은 `bytes` 과 비교해 다음과 같은 장점이 있습니다: @@ -121,7 +121,7 @@ HTML의 폼들(`
`)이 서버에 데이터를 전송하는 방식은 하지만 파일이 포함된 경우, `multipart/form-data`로 인코딩됩니다. `File`을 사용하였다면, **FastAPI**는 본문의 적합한 부분에서 파일을 가져와야 한다는 것을 인지합니다. -인코딩과 폼 필드에 대해 더 알고싶다면,
MDN web docs for POST를 참고하기 바랍니다. +인코딩과 폼 필드에 대해 더 알고싶다면, MDN web docs for POST를 참고하기 바랍니다. /// @@ -143,7 +143,7 @@ HTML의 폼들(`
`)이 서버에 데이터를 전송하는 방식은 추가 메타데이터를 설정하기 위해 예를 들어 `UploadFile`과 함께 `File()`을 사용할 수도 있습니다: -{* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} +{* ../../docs_src/request_files/tutorial001_03_an_py310.py hl[9,15] *} ## 다중 파일 업로드 { #multiple-file-uploads } @@ -153,7 +153,7 @@ HTML의 폼들(`
`)이 서버에 데이터를 전송하는 방식은 이 기능을 사용하기 위해 , `bytes` 의 `List` 또는 `UploadFile` 를 선언하기 바랍니다: -{* ../../docs_src/request_files/tutorial002_an_py39.py hl[10,15] *} +{* ../../docs_src/request_files/tutorial002_an_py310.py hl[10,15] *} 선언한대로, `bytes` 의 `list` 또는 `UploadFile` 들을 전송받을 것입니다. @@ -169,7 +169,7 @@ HTML의 폼들(`
`)이 서버에 데이터를 전송하는 방식은 이전과 같은 방식으로 `UploadFile`에 대해서도 `File()`을 사용해 추가 매개변수를 설정할 수 있습니다: -{* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} +{* ../../docs_src/request_files/tutorial003_an_py310.py hl[11,18:20] *} ## 요약 { #recap } diff --git a/docs/ko/docs/tutorial/request-form-models.md b/docs/ko/docs/tutorial/request-form-models.md index b37186dfb..20a2e9597 100644 --- a/docs/ko/docs/tutorial/request-form-models.md +++ b/docs/ko/docs/tutorial/request-form-models.md @@ -24,7 +24,7 @@ $ pip install python-multipart **폼 필드**로 받고 싶은 필드를 **Pydantic 모델**로 선언한 다음, 매개변수를 `Form`으로 선언하면 됩니다: -{* ../../docs_src/request_form_models/tutorial001_an_py39.py hl[9:11,15] *} +{* ../../docs_src/request_form_models/tutorial001_an_py310.py hl[9:11,15] *} **FastAPI**는 요청에서 받은 **폼 데이터**에서 **각 필드**에 대한 데이터를 **추출**하고 정의한 Pydantic 모델을 줍니다. @@ -48,7 +48,7 @@ $ pip install python-multipart Pydantic의 모델 구성을 사용하여 `extra` 필드를 `forbid`할 수 있습니다: -{* ../../docs_src/request_form_models/tutorial002_an_py39.py hl[12] *} +{* ../../docs_src/request_form_models/tutorial002_an_py310.py hl[12] *} 클라이언트가 추가 데이터를 보내려고 하면 **오류** 응답을 받게 됩니다. diff --git a/docs/ko/docs/tutorial/request-forms-and-files.md b/docs/ko/docs/tutorial/request-forms-and-files.md index a5309b5c0..4c2d12bc0 100644 --- a/docs/ko/docs/tutorial/request-forms-and-files.md +++ b/docs/ko/docs/tutorial/request-forms-and-files.md @@ -16,13 +16,13 @@ $ pip install python-multipart ## `File` 및 `Form` 임포트 { #import-file-and-form } -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[3] *} ## `File` 및 `Form` 매개변수 정의 { #define-file-and-form-parameters } `Body` 및 `Query`와 동일한 방식으로 파일과 폼의 매개변수를 생성합니다: -{* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[10:12] *} +{* ../../docs_src/request_forms_and_files/tutorial001_an_py310.py hl[10:12] *} 파일과 폼 필드는 폼 데이터로 업로드되며, 파일과 폼 필드를 받게 됩니다. diff --git a/docs/ko/docs/tutorial/request-forms.md b/docs/ko/docs/tutorial/request-forms.md index 584cbba35..a830bc5f8 100644 --- a/docs/ko/docs/tutorial/request-forms.md +++ b/docs/ko/docs/tutorial/request-forms.md @@ -18,17 +18,17 @@ $ pip install python-multipart `fastapi`에서 `Form`을 임포트합니다: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[3] *} ## `Form` 매개변수 정의하기 { #define-form-parameters } `Body` 또는 `Query`와 동일한 방식으로 폼 매개변수를 만듭니다: -{* ../../docs_src/request_forms/tutorial001_an_py39.py hl[9] *} +{* ../../docs_src/request_forms/tutorial001_an_py310.py hl[9] *} 예를 들어, OAuth2 사양을 사용할 수 있는 방법 중 하나("패스워드 플로우"라고 함)로 `username`과 `password`를 폼 필드로 보내야 합니다. -spec에서는 필드 이름이 `username` 및 `password`로 정확하게 명명되어야 하고, JSON이 아닌 폼 필드로 전송해야 합니다. +사양에서는 필드 이름이 `username` 및 `password`로 정확하게 명명되어야 하고, JSON이 아닌 폼 필드로 전송해야 합니다. `Form`을 사용하면 유효성 검사, 예제, 별칭(예: `username` 대신 `user-name`) 등을 포함하여 `Body`(및 `Query`, `Path`, `Cookie`)와 동일한 구성을 선언할 수 있습니다. @@ -56,7 +56,7 @@ HTML 폼(`
`)이 데이터를 서버로 보내는 방식은 일반 그러나 폼에 파일이 포함된 경우, `multipart/form-data`로 인코딩합니다. 다음 장에서 파일 처리에 대해 읽을 겁니다. -이러한 인코딩 및 폼 필드에 대해 더 읽고 싶다면, POST에 대한 MDN 웹 문서를 참조하세요. +이러한 인코딩 및 폼 필드에 대해 더 읽고 싶다면, MDN 웹 문서를 참조하세요 POST에 대한. /// diff --git a/docs/ko/docs/tutorial/response-model.md b/docs/ko/docs/tutorial/response-model.md index 6246ed9ad..942901e7c 100644 --- a/docs/ko/docs/tutorial/response-model.md +++ b/docs/ko/docs/tutorial/response-model.md @@ -183,7 +183,7 @@ FastAPI는 Pydantic을 내부적으로 여러 방식으로 사용하여, 클래 가장 흔한 경우는 [고급 문서에서 나중에 설명하는 대로 Response를 직접 반환하는 것](../advanced/response-directly.md){.internal-link target=_blank}입니다. -{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py310.py hl[8,10:11] *} 이 간단한 경우는 반환 타입 어노테이션이 `Response` 클래스(또는 그 서브클래스)이기 때문에 FastAPI에서 자동으로 처리됩니다. @@ -193,7 +193,7 @@ FastAPI는 Pydantic을 내부적으로 여러 방식으로 사용하여, 클래 타입 어노테이션에 `Response`의 서브클래스를 사용할 수도 있습니다: -{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py310.py hl[8:9] *} 이는 `RedirectResponse`가 `Response`의 서브클래스이기 때문에 동작하며, FastAPI가 이 간단한 경우를 자동으로 처리합니다. @@ -201,7 +201,7 @@ FastAPI는 Pydantic을 내부적으로 여러 방식으로 사용하여, 클래 하지만 유효한 Pydantic 타입이 아닌 다른 임의의 객체(예: 데이터베이스 객체)를 반환하고, 함수에서 그렇게 어노테이션하면, FastAPI는 그 타입 어노테이션으로부터 Pydantic 응답 모델을 만들려고 시도하다가 실패합니다. -또한, 유효한 Pydantic 타입이 아닌 타입이 하나 이상 포함된 여러 타입 간의 union이 있는 경우에도 동일합니다. 예를 들어, 아래는 실패합니다 💥: +또한, 유효한 Pydantic 타입이 아닌 타입이 하나 이상 포함된 여러 타입 간의 union이 있는 경우에도 동일합니다. 예를 들어, 아래는 실패합니다 💥: {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} diff --git a/docs/ko/docs/tutorial/response-status-code.md b/docs/ko/docs/tutorial/response-status-code.md index 257f6a8d6..c81132dfb 100644 --- a/docs/ko/docs/tutorial/response-status-code.md +++ b/docs/ko/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ * `@app.delete()` * 등 -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} /// note | 참고 @@ -43,7 +43,7 @@ FastAPI는 이를 알고 있으며, 응답 본문이 없다고 명시하는 Open /// note | 참고 -만약 HTTP 상태 코드가 무엇인지 이미 알고 있다면, 다음 섹션으로 넘어가세요. +만약 HTTP 상태 코드가 무엇인지 이미 알고 있다면, 다음 섡션으로 넘어가세요. /// @@ -66,7 +66,7 @@ HTTP에서는 응답의 일부로 3자리 숫자 상태 코드를 보냅니다. /// tip | 팁 -각 상태 코드와 어떤 코드가 어떤 용도인지 더 알고 싶다면 MDN의 HTTP 상태 코드에 관한 문서를 확인하세요. +각 상태 코드와 어떤 코드가 어떤 용도인지 더 알고 싶다면 MDN의 HTTP 상태 코드에 관한 문서를 확인하세요. /// @@ -74,7 +74,7 @@ HTTP에서는 응답의 일부로 3자리 숫자 상태 코드를 보냅니다. 이전 예시를 다시 확인해보겠습니다: -{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py310.py hl[6] *} `201` 은 "생성됨"을 위한 상태 코드입니다. @@ -82,7 +82,7 @@ HTTP에서는 응답의 일부로 3자리 숫자 상태 코드를 보냅니다. `fastapi.status` 의 편의 변수를 사용할 수 있습니다. -{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py310.py hl[1,6] *} 이것들은 단지 편의를 위한 것으로, 동일한 숫자를 갖고 있지만, 이를 통해 편집기의 자동완성 기능으로 찾을 수 있습니다: diff --git a/docs/ko/docs/tutorial/schema-extra-example.md b/docs/ko/docs/tutorial/schema-extra-example.md index b2b54836a..a1d0c8468 100644 --- a/docs/ko/docs/tutorial/schema-extra-example.md +++ b/docs/ko/docs/tutorial/schema-extra-example.md @@ -74,7 +74,7 @@ Pydantic 모델과 같이 `Field()`를 사용할 때 추가적인 `examples`를 이와 같이 하면 예제들은 그 본문 데이터의 내부 **JSON 스키마**의 일부가 될 것입니다. -그럼에도 불구하고, 지금 이 문서를 작성하는 시간에, 문서 UI를 보여주는 역할을 맡은 Swagger UI는 **JSON 스키마** 속 데이터를 위한 여러 예제의 표현을 지원하지 않습니다. 하지만 해결 방안을 밑에서 읽어보세요. +그럼에도 불구하고, 지금 이 문서를 작성하는 시간에, 문서 UI를 보여주는 역할을 맡은 Swagger UI는 **JSON 스키마** 속 데이터를 위한 여러 예제의 표현을 지원하지 않습니다. 하지만 해결 방안을 밑에서 읽어보세요. ### OpenAPI-특화 `examples` { #openapi-specific-examples } diff --git a/docs/ko/docs/tutorial/security/first-steps.md b/docs/ko/docs/tutorial/security/first-steps.md index 4c9181b31..57b336d52 100644 --- a/docs/ko/docs/tutorial/security/first-steps.md +++ b/docs/ko/docs/tutorial/security/first-steps.md @@ -20,7 +20,7 @@ 예제를 파일 `main.py`에 복사하세요: -{* ../../docs_src/security/tutorial001_an_py39.py *} +{* ../../docs_src/security/tutorial001_an_py310.py *} ## 실행하기 { #run-it } @@ -132,7 +132,7 @@ OAuth2는 backend 또는 API가 사용자를 인증하는 서버와 독립적일 `OAuth2PasswordBearer` 클래스의 인스턴스를 만들 때 `tokenUrl` 파라미터를 전달합니다. 이 파라미터에는 클라이언트(사용자의 브라우저에서 실행되는 frontend)가 token을 받기 위해 `username`과 `password`를 보낼 URL이 들어 있습니다. -{* ../../docs_src/security/tutorial001_an_py39.py hl[8] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[8] *} /// tip | 팁 @@ -170,7 +170,7 @@ oauth2_scheme(some, parameters) 이제 `Depends`로 `oauth2_scheme`를 의존성에 전달할 수 있습니다. -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} 이 의존성은 `str`을 제공하고, 그 값은 *경로 처리 함수*의 파라미터 `token`에 할당됩니다. diff --git a/docs/ko/docs/tutorial/security/get-current-user.md b/docs/ko/docs/tutorial/security/get-current-user.md index f21a22b7a..eab599e27 100644 --- a/docs/ko/docs/tutorial/security/get-current-user.md +++ b/docs/ko/docs/tutorial/security/get-current-user.md @@ -2,7 +2,7 @@ 이전 장에서 (의존성 주입 시스템을 기반으로 한) 보안 시스템은 *경로 처리 함수*에 `str`로 `token`을 제공했습니다: -{* ../../docs_src/security/tutorial001_an_py39.py hl[12] *} +{* ../../docs_src/security/tutorial001_an_py310.py hl[12] *} 하지만 이는 여전히 그다지 유용하지 않습니다. diff --git a/docs/ko/docs/tutorial/security/oauth2-jwt.md b/docs/ko/docs/tutorial/security/oauth2-jwt.md index 907795ca4..f9c4cc2ff 100644 --- a/docs/ko/docs/tutorial/security/oauth2-jwt.md +++ b/docs/ko/docs/tutorial/security/oauth2-jwt.md @@ -1,6 +1,6 @@ # 패스워드(해싱 포함)를 사용하는 OAuth2, JWT 토큰을 사용하는 Bearer { #oauth2-with-password-and-hashing-bearer-with-jwt-tokens } -모든 보안 흐름을 구성했으므로, 이제 JWT 토큰과 안전한 패스워드 해싱을 사용해 애플리케이션을 실제로 안전하게 만들겠습니다. +모든 보안 흐름을 구성했으므로, 이제 JWT 토큰과 안전한 패스워드 해싱을 사용해 애플리케이션을 실제로 안전하게 만들겠습니다. 이 코드는 실제로 애플리케이션에서 사용할 수 있으며, 패스워드 해시를 데이터베이스에 저장하는 등의 작업에 활용할 수 있습니다. @@ -42,7 +42,7 @@ $ pip install pyjwt
-/// info +/// info | 정보 RSA나 ECDSA 같은 전자 서명 알고리즘을 사용할 계획이라면, cryptography 라이브러리 의존성인 `pyjwt[crypto]`를 설치해야 합니다. @@ -84,7 +84,7 @@ $ pip install "pwdlib[argon2]"
-/// tip +/// tip | 팁 `pwdlib`를 사용하면 **Django**, **Flask** 보안 플러그인 또는 다른 여러 도구로 생성한 패스워드를 읽을 수 있도록 설정할 수도 있습니다. @@ -100,7 +100,7 @@ $ pip install "pwdlib[argon2]" 권장 설정으로 PasswordHash 인스턴스를 생성합니다. 이는 패스워드를 해싱하고 검증하는 데 사용됩니다. -/// tip +/// tip | 팁 pwdlib는 bcrypt 해싱 알고리즘도 지원하지만 레거시 알고리즘은 포함하지 않습니다. 오래된 해시로 작업해야 한다면 passlib 라이브러리를 사용하는 것을 권장합니다. @@ -116,9 +116,13 @@ pwdlib는 bcrypt 해싱 알고리즘도 지원하지만 레거시 알고리즘 그리고 사용자를 인증하고 반환하는 또 다른 함수도 생성합니다. -{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,51,58:59,62:63,72:79] *} -/// note +`authenticate_user`가 데이터베이스에 존재하지 않는 사용자이름으로 호출되더라도, 여전히 `verify_password`를 더미 해시에 대해 실행합니다. + +이렇게 하면 사용자이름이 유효하든 아니든 엔드포인트가 응답하는 데 걸리는 시간이 대략 동일해져, 기존 사용자이름을 열거하는 데 악용될 수 있는 **타이밍 공격**을 방지합니다. + +/// note | 참고 새로운 (가짜) 데이터베이스 `fake_users_db`를 확인하면, 이제 해시 처리된 패스워드가 어떻게 생겼는지 볼 수 있습니다: `"$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc"`. @@ -152,7 +156,7 @@ JWT 토큰을 서명하는 데 사용될 알고리즘을 위한 변수 `ALGORITH 새 액세스 토큰을 생성하기 위한 유틸리티 함수를 생성합니다. -{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,82:90] *} ## 의존성 업데이트 { #update-the-dependencies } @@ -162,7 +166,7 @@ JWT 토큰을 서명하는 데 사용될 알고리즘을 위한 변수 `ALGORITH 토큰이 유효하지 않다면 즉시 HTTP 오류를 반환합니다. -{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[93:110] *} ## `/token` *경로 처리* 업데이트 { #update-the-token-path-operation } @@ -170,7 +174,7 @@ JWT 토큰을 서명하는 데 사용될 알고리즘을 위한 변수 `ALGORITH 실제 JWT 액세스 토큰을 생성하여 반환합니다. -{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} +{* ../../docs_src/security/tutorial004_an_py310.py hl[121:136] *} ### JWT "주체(subject)" `sub`에 대한 기술 세부사항 { #technical-details-about-the-jwt-subject-sub } @@ -209,7 +213,7 @@ JWT는 사용자를 식별하고 사용자가 API에서 직접 작업을 수행 Username: `johndoe` Password: `secret` -/// check +/// check | 확인 코드 어디에도 평문 패스워드 "`secret`"은 없고, 해시된 버전만 있다는 점에 유의하십시오. @@ -234,7 +238,7 @@ Password: `secret` -/// note +/// note | 참고 `Bearer `로 시작하는 값을 가진 `Authorization` 헤더에 주목하십시오. diff --git a/docs/ko/docs/tutorial/security/simple-oauth2.md b/docs/ko/docs/tutorial/security/simple-oauth2.md index 189dd89f2..918c94b25 100644 --- a/docs/ko/docs/tutorial/security/simple-oauth2.md +++ b/docs/ko/docs/tutorial/security/simple-oauth2.md @@ -162,7 +162,7 @@ UserInDB( /// tip | 팁 -다음 장에서는 패스워드 해싱 및 JWT 토큰을 사용하여 실제 보안 구현을 볼 수 있습니다. +다음 장에서는 패스워드 해싱 및 JWT 토큰을 사용하여 실제 보안 구현을 볼 수 있습니다. 하지만 지금은 필요한 세부 정보에 집중하겠습니다. @@ -286,4 +286,4 @@ UserInDB( 유일한 오점은 아직 실제로 "안전"하지 않다는 것입니다. -다음 장에서는 안전한 패스워드 해싱 라이브러리와 JWT 토큰을 사용하는 방법을 살펴보겠습니다. +다음 장에서는 안전한 패스워드 해싱 라이브러리와 JWT 토큰을 사용하는 방법을 살펴보겠습니다. diff --git a/docs/ko/docs/tutorial/sql-databases.md b/docs/ko/docs/tutorial/sql-databases.md index 3d64cf627..20c136716 100644 --- a/docs/ko/docs/tutorial/sql-databases.md +++ b/docs/ko/docs/tutorial/sql-databases.md @@ -8,7 +8,7 @@ /// tip | 팁 -다른 SQL 또는 NoSQL 데이터베이스 라이브러리를 사용할 수도 있습니다 (일부는 "ORMs"이라고도 불립니다), FastAPI는 특정 라이브러리의 사용을 강요하지 않습니다. 😎 +다른 SQL 또는 NoSQL 데이터베이스 라이브러리를 사용할 수도 있습니다 (일부는 "ORMs"이라고도 불립니다), FastAPI는 특정 라이브러리의 사용을 강요하지 않습니다. 😎 /// diff --git a/docs/ko/docs/tutorial/static-files.md b/docs/ko/docs/tutorial/static-files.md index aa4c57179..0235d83c7 100644 --- a/docs/ko/docs/tutorial/static-files.md +++ b/docs/ko/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ * `StaticFiles`를 임포트합니다. * 특정 경로에 `StaticFiles()` 인스턴스를 "마운트"합니다. -{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py310.py hl[2,6] *} /// note | 기술 세부사항 diff --git a/docs/ko/docs/tutorial/testing.md b/docs/ko/docs/tutorial/testing.md index db7fb17ea..57ab81151 100644 --- a/docs/ko/docs/tutorial/testing.md +++ b/docs/ko/docs/tutorial/testing.md @@ -2,7 +2,7 @@ Starlette 덕분에 **FastAPI** 애플리케이션을 테스트하는 일은 쉽고 즐거운 일이 되었습니다. -Starlette는 HTTPX를 기반으로 하며, 이는 Requests를 기반으로 설계되었기 때문에 매우 친숙하고 직관적입니다. +이는 HTTPX를 기반으로 하며, 이는 Requests를 기반으로 설계되었기 때문에 매우 친숙하고 직관적입니다. 이를 사용하면 **FastAPI**에서 pytest를 직접 사용할 수 있습니다. @@ -30,7 +30,7 @@ $ pip install httpx 표준적인 파이썬 표현식으로 확인이 필요한 곳에 간단한 `assert` 문장을 작성하세요(역시 표준적인 `pytest` 관례입니다). -{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py310.py hl[2,12,15:18] *} /// tip | 팁 @@ -42,7 +42,7 @@ $ pip install httpx /// -/// note Technical Details | 기술 세부사항 +/// note | 기술 세부사항 `from starlette.testclient import TestClient` 역시 사용할 수 있습니다. @@ -76,7 +76,7 @@ FastAPI 애플리케이션에 요청을 보내는 것 외에도 테스트에서 `main.py` 파일 안에 **FastAPI** app 을 만들었습니다: -{* ../../docs_src/app_testing/app_a_py39/main.py *} +{* ../../docs_src/app_testing/app_a_py310/main.py *} ### 테스트 파일 { #testing-file } @@ -92,7 +92,7 @@ FastAPI 애플리케이션에 요청을 보내는 것 외에도 테스트에서 파일들이 동일한 패키지에 위치해 있으므로, 상대 임포트를 사용하여 `main` 모듈(`main.py`)에서 `app` 객체를 임포트 해올 수 있습니다. -{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py310/test_main.py hl[3] *} ...그리고 이전에 작성했던 것과 같은 테스트 코드를 작성할 수 있습니다. @@ -115,7 +115,7 @@ FastAPI 애플리케이션에 요청을 보내는 것 외에도 테스트에서 이제 **FastAPI** 앱이 있는 `main.py` 파일에 몇 가지 다른 **경로 처리**가 추가된 경우를 생각해봅시다. -단일 오류를 반환할 수 있는 `GET` 작업이 있습니다. +오류를 반환할 수 있는 `GET` 작업이 있습니다. 여러 다른 오류를 반환할 수 있는 `POST` 작업이 있습니다. diff --git a/docs/ko/docs/virtual-environments.md b/docs/ko/docs/virtual-environments.md index b639f8a3e..e6baef73b 100644 --- a/docs/ko/docs/virtual-environments.md +++ b/docs/ko/docs/virtual-environments.md @@ -37,15 +37,15 @@ Python 설치까지 포함해 **모든 것을 관리해주는 도구**를 도입
```console -// Go to the home directory +// 홈 디렉터리로 이동 $ cd -// Create a directory for all your code projects +// 모든 코드 프로젝트를 위한 디렉터리 생성 $ mkdir code -// Enter into that code directory +// 그 code 디렉터리로 이동 $ cd code -// Create a directory for this project +// 이 프로젝트를 위한 디렉터리 생성 $ mkdir awesome-project -// Enter into that project directory +// 그 프로젝트 디렉터리로 이동 $ cd awesome-project ``` @@ -53,7 +53,7 @@ $ cd awesome-project ## 가상 환경 생성 { #create-a-virtual-environment } -Python 프로젝트를 **처음 시작할 때**, **프로젝트 내부**에 가상 환경을 생성하세요. +Python 프로젝트를 **처음 시작할 때**, 가상 환경을 **프로젝트 내부**에 생성하세요. /// tip @@ -166,7 +166,7 @@ $ source .venv/Scripts/activate 해당 환경에 **새 패키지**를 설치할 때마다, 환경을 다시 **활성화**하세요. -이렇게 하면 해당 패키지가 설치한 **터미널(CLI) 프로그램**을 사용할 때, 전역으로 설치되어 있을 수도 있는(아마 필요한 버전과는 다른 버전인) 다른 프로그램이 아니라 가상 환경에 있는 것을 사용하게 됩니다. +이렇게 하면 해당 패키지가 설치한 **터미널(CLI) 프로그램**을 사용할 때, 전역으로 설치되어 있을 수도 있는(아마 필요한 버전과는 다른 버전인) 다른 프로그램이 아니라 가상 환경에 있는 것을 사용하게 됩니다. /// @@ -557,7 +557,7 @@ Python을 설치하면 컴퓨터에 몇몇 파일이 들어 있는 디렉터리
```console -// Don't run this now, it's just an example 🤓 +// 지금은 실행하지 마세요, 예시일 뿐입니다 🤓 $ pip install "fastapi[standard]" ---> 100% ``` @@ -811,7 +811,7 @@ $ cd ~/code/prisoner-of-azkaban $ python main.py -// Error importing sirius, it's not installed 😱 +// sirius 임포트 오류, 설치되어 있지 않습니다 😱 Traceback (most recent call last): File "main.py", line 1, in import sirius @@ -826,13 +826,13 @@ Traceback (most recent call last): ```console $ cd ~/code/prisoner-of-azkaban -// You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project 😎 +// 비활성화를 위해 이전 디렉터리에 있을 필요는 없습니다. 어디서든, 다른 프로젝트로 이동한 뒤에도 할 수 있습니다 😎 $ deactivate -// Activate the virtual environment in prisoner-of-azkaban/.venv 🚀 +// prisoner-of-azkaban/.venv의 가상 환경을 활성화하세요 🚀 $ source .venv/bin/activate -// Now when you run python, it will find the package sirius installed in this virtual environment ✨ +// 이제 python을 실행하면, 이 가상 환경에 설치된 sirius 패키지를 찾습니다 ✨ $ python main.py I solemnly swear 🐺 From ed12105cced91614d5833857f51f7ccff0eeb03d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 14 Feb 2026 08:57:26 +0000 Subject: [PATCH 139/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 61366bb84..5e1435065 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Translations +* 🌐 Update translations for ko (update-all and add-missing). PR [#14923](https://github.com/fastapi/fastapi/pull/14923) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for uk (add-missing). PR [#14922](https://github.com/fastapi/fastapi/pull/14922) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for zh-hant (update-all and add-missing). PR [#14921](https://github.com/fastapi/fastapi/pull/14921) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for fr (update-all and add-missing). PR [#14920](https://github.com/fastapi/fastapi/pull/14920) by [@YuriiMotov](https://github.com/YuriiMotov). From bbb96d4a0abda689664582958eeb28e10179e6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 17 Feb 2026 00:08:04 -0800 Subject: [PATCH 140/195] =?UTF-8?q?=F0=9F=94=A5=20Remove=20Python=203.9=20?= =?UTF-8?q?specific=20files,=20no=20longer=20needed=20after=20updating=20t?= =?UTF-8?q?ranslations=20(#14931)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../additional_responses/tutorial001_py39.py | 22 ----- .../additional_responses/tutorial003_py39.py | 37 --------- .../advanced_middleware/tutorial001_py39.py | 11 --- .../advanced_middleware/tutorial002_py39.py | 13 --- .../advanced_middleware/tutorial003_py39.py | 11 --- docs_src/app_testing/app_a_py39/__init__.py | 0 docs_src/app_testing/app_a_py39/main.py | 8 -- docs_src/app_testing/app_a_py39/test_main.py | 11 --- docs_src/app_testing/tutorial001_py39.py | 18 ----- docs_src/app_testing/tutorial002_py39.py | 31 ------- docs_src/app_testing/tutorial003_py39.py | 24 ------ docs_src/app_testing/tutorial004_py39.py | 43 ---------- docs_src/async_tests/app_a_py39/__init__.py | 0 docs_src/async_tests/app_a_py39/main.py | 8 -- docs_src/async_tests/app_a_py39/test_main.py | 14 ---- .../tutorial001_an_py39.py | 21 ----- docs_src/background_tasks/tutorial001_py39.py | 15 ---- .../behind_a_proxy/tutorial001_01_py39.py | 8 -- docs_src/behind_a_proxy/tutorial001_py39.py | 8 -- docs_src/behind_a_proxy/tutorial002_py39.py | 8 -- docs_src/behind_a_proxy/tutorial003_py39.py | 14 ---- docs_src/behind_a_proxy/tutorial004_py39.py | 15 ---- .../app_an_py39/__init__.py | 0 .../app_an_py39/dependencies.py | 13 --- .../app_an_py39/internal/__init__.py | 0 .../app_an_py39/internal/admin.py | 8 -- .../bigger_applications/app_an_py39/main.py | 23 ------ .../app_an_py39/routers/__init__.py | 0 .../app_an_py39/routers/items.py | 38 --------- .../app_an_py39/routers/users.py | 18 ----- .../bigger_applications/app_py39/__init__.py | 0 .../app_py39/dependencies.py | 11 --- docs_src/bigger_applications/app_py39/main.py | 23 ------ .../body_nested_models/tutorial008_py39.py | 14 ---- .../body_nested_models/tutorial009_py39.py | 8 -- .../conditional_openapi/tutorial001_py39.py | 16 ---- .../configure_swagger_ui/tutorial001_py39.py | 8 -- .../configure_swagger_ui/tutorial002_py39.py | 8 -- .../configure_swagger_ui/tutorial003_py39.py | 8 -- docs_src/cors/tutorial001_py39.py | 24 ------ docs_src/custom_docs_ui/tutorial001_py39.py | 38 --------- docs_src/custom_docs_ui/tutorial002_py39.py | 41 ---------- docs_src/custom_response/tutorial001_py39.py | 9 --- docs_src/custom_response/tutorial001b_py39.py | 9 --- docs_src/custom_response/tutorial002_py39.py | 18 ----- docs_src/custom_response/tutorial003_py39.py | 19 ----- docs_src/custom_response/tutorial004_py39.py | 23 ------ docs_src/custom_response/tutorial005_py39.py | 9 --- docs_src/custom_response/tutorial006_py39.py | 9 --- docs_src/custom_response/tutorial006b_py39.py | 9 --- docs_src/custom_response/tutorial006c_py39.py | 9 --- docs_src/custom_response/tutorial007_py39.py | 14 ---- docs_src/custom_response/tutorial008_py39.py | 14 ---- docs_src/custom_response/tutorial009_py39.py | 10 --- docs_src/custom_response/tutorial009b_py39.py | 10 --- docs_src/custom_response/tutorial009c_py39.py | 19 ----- docs_src/custom_response/tutorial010_py39.py | 9 --- docs_src/debugging/tutorial001_py39.py | 15 ---- docs_src/dependencies/tutorial006_an_py39.py | 21 ----- docs_src/dependencies/tutorial006_py39.py | 19 ----- docs_src/dependencies/tutorial007_py39.py | 6 -- docs_src/dependencies/tutorial008_an_py39.py | 27 ------- docs_src/dependencies/tutorial008_py39.py | 25 ------ docs_src/dependencies/tutorial008b_an_py39.py | 32 -------- docs_src/dependencies/tutorial008b_py39.py | 30 ------- docs_src/dependencies/tutorial008c_an_py39.py | 29 ------- docs_src/dependencies/tutorial008c_py39.py | 27 ------- docs_src/dependencies/tutorial008d_an_py39.py | 30 ------- docs_src/dependencies/tutorial008d_py39.py | 28 ------- docs_src/dependencies/tutorial008e_an_py39.py | 17 ---- docs_src/dependencies/tutorial008e_py39.py | 15 ---- docs_src/dependencies/tutorial010_py39.py | 14 ---- docs_src/dependencies/tutorial011_an_py39.py | 23 ------ docs_src/dependencies/tutorial011_py39.py | 21 ----- docs_src/dependencies/tutorial012_an_py39.py | 27 ------- docs_src/dependencies/tutorial012_py39.py | 25 ------ docs_src/events/tutorial001_py39.py | 16 ---- docs_src/events/tutorial002_py39.py | 14 ---- docs_src/events/tutorial003_py39.py | 28 ------- .../extending_openapi/tutorial001_py39.py | 29 ------- docs_src/extra_models/tutorial004_py39.py | 20 ----- docs_src/extra_models/tutorial005_py39.py | 8 -- docs_src/first_steps/tutorial001_py39.py | 8 -- docs_src/first_steps/tutorial003_py39.py | 8 -- docs_src/generate_clients/tutorial001_py39.py | 26 ------ docs_src/generate_clients/tutorial002_py39.py | 36 --------- docs_src/generate_clients/tutorial003_py39.py | 42 ---------- docs_src/generate_clients/tutorial004_py39.py | 15 ---- docs_src/graphql_/tutorial001_py39.py | 25 ------ docs_src/handling_errors/tutorial001_py39.py | 12 --- docs_src/handling_errors/tutorial002_py39.py | 16 ---- docs_src/handling_errors/tutorial003_py39.py | 25 ------ docs_src/handling_errors/tutorial004_py39.py | 26 ------ docs_src/handling_errors/tutorial005_py39.py | 25 ------ docs_src/handling_errors/tutorial006_py39.py | 28 ------- docs_src/metadata/tutorial001_1_py39.py | 38 --------- docs_src/metadata/tutorial001_py39.py | 38 --------- docs_src/metadata/tutorial002_py39.py | 8 -- docs_src/metadata/tutorial003_py39.py | 8 -- docs_src/metadata/tutorial004_py39.py | 28 ------- docs_src/middleware/tutorial001_py39.py | 14 ---- docs_src/openapi_webhooks/tutorial001_py39.py | 25 ------ .../tutorial001_py39.py | 8 -- .../tutorial002_py39.py | 24 ------ .../tutorial003_py39.py | 8 -- .../tutorial005_py39.py | 8 -- .../tutorial006_py39.py | 41 ---------- .../tutorial007_py39.py | 32 -------- .../tutorial002b_py39.py | 20 ----- .../tutorial006_py39.py | 18 ----- docs_src/path_params/tutorial001_py39.py | 8 -- docs_src/path_params/tutorial002_py39.py | 8 -- docs_src/path_params/tutorial003_py39.py | 13 --- docs_src/path_params/tutorial003b_py39.py | 13 --- docs_src/path_params/tutorial004_py39.py | 8 -- docs_src/path_params/tutorial005_py39.py | 23 ------ .../tutorial002_an_py39.py | 15 ---- .../tutorial002_py39.py | 11 --- .../tutorial003_an_py39.py | 15 ---- .../tutorial003_py39.py | 11 --- .../tutorial004_an_py39.py | 15 ---- .../tutorial004_py39.py | 13 --- .../tutorial005_an_py39.py | 16 ---- .../tutorial005_py39.py | 15 ---- .../tutorial006_an_py39.py | 20 ----- .../tutorial006_py39.py | 18 ----- docs_src/python_types/tutorial001_py39.py | 6 -- docs_src/python_types/tutorial002_py39.py | 6 -- docs_src/python_types/tutorial003_py39.py | 3 - docs_src/python_types/tutorial004_py39.py | 3 - docs_src/python_types/tutorial006_py39.py | 3 - docs_src/python_types/tutorial007_py39.py | 2 - docs_src/python_types/tutorial008_py39.py | 4 - docs_src/python_types/tutorial008b_py39.py | 5 -- docs_src/python_types/tutorial009_py39.py | 8 -- docs_src/python_types/tutorial009b_py39.py | 8 -- docs_src/python_types/tutorial009c_py310.py | 2 - docs_src/python_types/tutorial009c_py39.py | 5 -- docs_src/python_types/tutorial010_py39.py | 7 -- docs_src/python_types/tutorial013_py39.py | 5 -- docs_src/query_params/tutorial001_py39.py | 10 --- docs_src/query_params/tutorial005_py39.py | 9 --- .../tutorial005_an_py39.py | 13 --- .../tutorial005_py39.py | 11 --- .../tutorial006_an_py39.py | 13 --- .../tutorial006_py39.py | 11 --- .../tutorial012_an_py39.py | 11 --- .../tutorial012_py39.py | 9 --- .../tutorial013_an_py39.py | 11 --- .../tutorial013_py39.py | 9 --- .../request_files/tutorial001_03_an_py39.py | 17 ---- docs_src/request_files/tutorial001_03_py39.py | 15 ---- docs_src/request_files/tutorial001_an_py39.py | 15 ---- docs_src/request_files/tutorial001_py39.py | 13 --- docs_src/request_files/tutorial002_an_py39.py | 33 -------- docs_src/request_files/tutorial002_py39.py | 31 ------- docs_src/request_files/tutorial003_an_py39.py | 39 --------- docs_src/request_files/tutorial003_py39.py | 35 -------- .../tutorial001_an_py39.py | 16 ---- .../request_form_models/tutorial001_py39.py | 14 ---- .../tutorial002_an_py39.py | 17 ---- .../request_form_models/tutorial002_py39.py | 15 ---- docs_src/request_forms/tutorial001_an_py39.py | 10 --- docs_src/request_forms/tutorial001_py39.py | 8 -- .../tutorial001_an_py39.py | 18 ----- .../tutorial001_py39.py | 14 ---- .../tutorial001_py39.py | 13 --- docs_src/response_cookies/tutorial001_py39.py | 12 --- docs_src/response_cookies/tutorial002_py39.py | 9 --- .../response_directly/tutorial002_py39.py | 18 ----- docs_src/response_headers/tutorial001_py39.py | 11 --- docs_src/response_headers/tutorial002_py39.py | 9 --- .../response_model/tutorial003_02_py39.py | 11 --- .../response_model/tutorial003_03_py39.py | 9 --- .../response_status_code/tutorial001_py39.py | 8 -- .../response_status_code/tutorial002_py39.py | 8 -- docs_src/security/tutorial001_an_py39.py | 13 --- docs_src/security/tutorial001_py39.py | 11 --- docs_src/security/tutorial006_an_py39.py | 13 --- docs_src/security/tutorial006_py39.py | 11 --- docs_src/security/tutorial007_an_py39.py | 36 --------- docs_src/security/tutorial007_py39.py | 33 -------- docs_src/settings/app01_py39/__init__.py | 0 docs_src/settings/app01_py39/config.py | 10 --- docs_src/settings/app01_py39/main.py | 14 ---- docs_src/settings/app02_an_py39/__init__.py | 0 docs_src/settings/app02_an_py39/config.py | 7 -- docs_src/settings/app02_an_py39/main.py | 22 ----- docs_src/settings/app02_an_py39/test_main.py | 23 ------ docs_src/settings/app02_py39/__init__.py | 0 docs_src/settings/app02_py39/config.py | 7 -- docs_src/settings/app02_py39/main.py | 21 ----- docs_src/settings/app02_py39/test_main.py | 23 ------ docs_src/settings/app03_an_py39/__init__.py | 0 docs_src/settings/app03_an_py39/config.py | 9 --- docs_src/settings/app03_an_py39/main.py | 22 ----- docs_src/settings/app03_py39/__init__.py | 0 docs_src/settings/app03_py39/config.py | 9 --- docs_src/settings/app03_py39/main.py | 21 ----- docs_src/settings/tutorial001_py39.py | 21 ----- docs_src/static_files/tutorial001_py39.py | 6 -- docs_src/sub_applications/tutorial001_py39.py | 19 ----- docs_src/templates/tutorial001_py39.py | 18 ----- .../tutorial001_py39.py | 9 --- docs_src/websockets/tutorial001_py39.py | 51 ------------ docs_src/websockets/tutorial003_py39.py | 81 ------------------- docs_src/wsgi/tutorial001_py39.py | 23 ------ .../test_code_includes/data/en_doc.md | 2 +- .../data/translated_doc_number_gt.md | 4 +- .../data/translated_doc_number_lt.md | 2 +- .../test_complex_doc/data/en_doc.md | 16 ++-- .../test_complex_doc/data/translated_doc.md | 16 ++-- .../data/translated_doc_expected.md | 16 ++-- .../test_python_types/test_tutorial009c.py | 33 -------- 214 files changed, 28 insertions(+), 3393 deletions(-) delete mode 100644 docs_src/additional_responses/tutorial001_py39.py delete mode 100644 docs_src/additional_responses/tutorial003_py39.py delete mode 100644 docs_src/advanced_middleware/tutorial001_py39.py delete mode 100644 docs_src/advanced_middleware/tutorial002_py39.py delete mode 100644 docs_src/advanced_middleware/tutorial003_py39.py delete mode 100644 docs_src/app_testing/app_a_py39/__init__.py delete mode 100644 docs_src/app_testing/app_a_py39/main.py delete mode 100644 docs_src/app_testing/app_a_py39/test_main.py delete mode 100644 docs_src/app_testing/tutorial001_py39.py delete mode 100644 docs_src/app_testing/tutorial002_py39.py delete mode 100644 docs_src/app_testing/tutorial003_py39.py delete mode 100644 docs_src/app_testing/tutorial004_py39.py delete mode 100644 docs_src/async_tests/app_a_py39/__init__.py delete mode 100644 docs_src/async_tests/app_a_py39/main.py delete mode 100644 docs_src/async_tests/app_a_py39/test_main.py delete mode 100644 docs_src/authentication_error_status_code/tutorial001_an_py39.py delete mode 100644 docs_src/background_tasks/tutorial001_py39.py delete mode 100644 docs_src/behind_a_proxy/tutorial001_01_py39.py delete mode 100644 docs_src/behind_a_proxy/tutorial001_py39.py delete mode 100644 docs_src/behind_a_proxy/tutorial002_py39.py delete mode 100644 docs_src/behind_a_proxy/tutorial003_py39.py delete mode 100644 docs_src/behind_a_proxy/tutorial004_py39.py delete mode 100644 docs_src/bigger_applications/app_an_py39/__init__.py delete mode 100644 docs_src/bigger_applications/app_an_py39/dependencies.py delete mode 100644 docs_src/bigger_applications/app_an_py39/internal/__init__.py delete mode 100644 docs_src/bigger_applications/app_an_py39/internal/admin.py delete mode 100644 docs_src/bigger_applications/app_an_py39/main.py delete mode 100644 docs_src/bigger_applications/app_an_py39/routers/__init__.py delete mode 100644 docs_src/bigger_applications/app_an_py39/routers/items.py delete mode 100644 docs_src/bigger_applications/app_an_py39/routers/users.py delete mode 100644 docs_src/bigger_applications/app_py39/__init__.py delete mode 100644 docs_src/bigger_applications/app_py39/dependencies.py delete mode 100644 docs_src/bigger_applications/app_py39/main.py delete mode 100644 docs_src/body_nested_models/tutorial008_py39.py delete mode 100644 docs_src/body_nested_models/tutorial009_py39.py delete mode 100644 docs_src/conditional_openapi/tutorial001_py39.py delete mode 100644 docs_src/configure_swagger_ui/tutorial001_py39.py delete mode 100644 docs_src/configure_swagger_ui/tutorial002_py39.py delete mode 100644 docs_src/configure_swagger_ui/tutorial003_py39.py delete mode 100644 docs_src/cors/tutorial001_py39.py delete mode 100644 docs_src/custom_docs_ui/tutorial001_py39.py delete mode 100644 docs_src/custom_docs_ui/tutorial002_py39.py delete mode 100644 docs_src/custom_response/tutorial001_py39.py delete mode 100644 docs_src/custom_response/tutorial001b_py39.py delete mode 100644 docs_src/custom_response/tutorial002_py39.py delete mode 100644 docs_src/custom_response/tutorial003_py39.py delete mode 100644 docs_src/custom_response/tutorial004_py39.py delete mode 100644 docs_src/custom_response/tutorial005_py39.py delete mode 100644 docs_src/custom_response/tutorial006_py39.py delete mode 100644 docs_src/custom_response/tutorial006b_py39.py delete mode 100644 docs_src/custom_response/tutorial006c_py39.py delete mode 100644 docs_src/custom_response/tutorial007_py39.py delete mode 100644 docs_src/custom_response/tutorial008_py39.py delete mode 100644 docs_src/custom_response/tutorial009_py39.py delete mode 100644 docs_src/custom_response/tutorial009b_py39.py delete mode 100644 docs_src/custom_response/tutorial009c_py39.py delete mode 100644 docs_src/custom_response/tutorial010_py39.py delete mode 100644 docs_src/debugging/tutorial001_py39.py delete mode 100644 docs_src/dependencies/tutorial006_an_py39.py delete mode 100644 docs_src/dependencies/tutorial006_py39.py delete mode 100644 docs_src/dependencies/tutorial007_py39.py delete mode 100644 docs_src/dependencies/tutorial008_an_py39.py delete mode 100644 docs_src/dependencies/tutorial008_py39.py delete mode 100644 docs_src/dependencies/tutorial008b_an_py39.py delete mode 100644 docs_src/dependencies/tutorial008b_py39.py delete mode 100644 docs_src/dependencies/tutorial008c_an_py39.py delete mode 100644 docs_src/dependencies/tutorial008c_py39.py delete mode 100644 docs_src/dependencies/tutorial008d_an_py39.py delete mode 100644 docs_src/dependencies/tutorial008d_py39.py delete mode 100644 docs_src/dependencies/tutorial008e_an_py39.py delete mode 100644 docs_src/dependencies/tutorial008e_py39.py delete mode 100644 docs_src/dependencies/tutorial010_py39.py delete mode 100644 docs_src/dependencies/tutorial011_an_py39.py delete mode 100644 docs_src/dependencies/tutorial011_py39.py delete mode 100644 docs_src/dependencies/tutorial012_an_py39.py delete mode 100644 docs_src/dependencies/tutorial012_py39.py delete mode 100644 docs_src/events/tutorial001_py39.py delete mode 100644 docs_src/events/tutorial002_py39.py delete mode 100644 docs_src/events/tutorial003_py39.py delete mode 100644 docs_src/extending_openapi/tutorial001_py39.py delete mode 100644 docs_src/extra_models/tutorial004_py39.py delete mode 100644 docs_src/extra_models/tutorial005_py39.py delete mode 100644 docs_src/first_steps/tutorial001_py39.py delete mode 100644 docs_src/first_steps/tutorial003_py39.py delete mode 100644 docs_src/generate_clients/tutorial001_py39.py delete mode 100644 docs_src/generate_clients/tutorial002_py39.py delete mode 100644 docs_src/generate_clients/tutorial003_py39.py delete mode 100644 docs_src/generate_clients/tutorial004_py39.py delete mode 100644 docs_src/graphql_/tutorial001_py39.py delete mode 100644 docs_src/handling_errors/tutorial001_py39.py delete mode 100644 docs_src/handling_errors/tutorial002_py39.py delete mode 100644 docs_src/handling_errors/tutorial003_py39.py delete mode 100644 docs_src/handling_errors/tutorial004_py39.py delete mode 100644 docs_src/handling_errors/tutorial005_py39.py delete mode 100644 docs_src/handling_errors/tutorial006_py39.py delete mode 100644 docs_src/metadata/tutorial001_1_py39.py delete mode 100644 docs_src/metadata/tutorial001_py39.py delete mode 100644 docs_src/metadata/tutorial002_py39.py delete mode 100644 docs_src/metadata/tutorial003_py39.py delete mode 100644 docs_src/metadata/tutorial004_py39.py delete mode 100644 docs_src/middleware/tutorial001_py39.py delete mode 100644 docs_src/openapi_webhooks/tutorial001_py39.py delete mode 100644 docs_src/path_operation_advanced_configuration/tutorial001_py39.py delete mode 100644 docs_src/path_operation_advanced_configuration/tutorial002_py39.py delete mode 100644 docs_src/path_operation_advanced_configuration/tutorial003_py39.py delete mode 100644 docs_src/path_operation_advanced_configuration/tutorial005_py39.py delete mode 100644 docs_src/path_operation_advanced_configuration/tutorial006_py39.py delete mode 100644 docs_src/path_operation_advanced_configuration/tutorial007_py39.py delete mode 100644 docs_src/path_operation_configuration/tutorial002b_py39.py delete mode 100644 docs_src/path_operation_configuration/tutorial006_py39.py delete mode 100644 docs_src/path_params/tutorial001_py39.py delete mode 100644 docs_src/path_params/tutorial002_py39.py delete mode 100644 docs_src/path_params/tutorial003_py39.py delete mode 100644 docs_src/path_params/tutorial003b_py39.py delete mode 100644 docs_src/path_params/tutorial004_py39.py delete mode 100644 docs_src/path_params/tutorial005_py39.py delete mode 100644 docs_src/path_params_numeric_validations/tutorial002_an_py39.py delete mode 100644 docs_src/path_params_numeric_validations/tutorial002_py39.py delete mode 100644 docs_src/path_params_numeric_validations/tutorial003_an_py39.py delete mode 100644 docs_src/path_params_numeric_validations/tutorial003_py39.py delete mode 100644 docs_src/path_params_numeric_validations/tutorial004_an_py39.py delete mode 100644 docs_src/path_params_numeric_validations/tutorial004_py39.py delete mode 100644 docs_src/path_params_numeric_validations/tutorial005_an_py39.py delete mode 100644 docs_src/path_params_numeric_validations/tutorial005_py39.py delete mode 100644 docs_src/path_params_numeric_validations/tutorial006_an_py39.py delete mode 100644 docs_src/path_params_numeric_validations/tutorial006_py39.py delete mode 100644 docs_src/python_types/tutorial001_py39.py delete mode 100644 docs_src/python_types/tutorial002_py39.py delete mode 100644 docs_src/python_types/tutorial003_py39.py delete mode 100644 docs_src/python_types/tutorial004_py39.py delete mode 100644 docs_src/python_types/tutorial006_py39.py delete mode 100644 docs_src/python_types/tutorial007_py39.py delete mode 100644 docs_src/python_types/tutorial008_py39.py delete mode 100644 docs_src/python_types/tutorial008b_py39.py delete mode 100644 docs_src/python_types/tutorial009_py39.py delete mode 100644 docs_src/python_types/tutorial009b_py39.py delete mode 100644 docs_src/python_types/tutorial009c_py310.py delete mode 100644 docs_src/python_types/tutorial009c_py39.py delete mode 100644 docs_src/python_types/tutorial010_py39.py delete mode 100644 docs_src/python_types/tutorial013_py39.py delete mode 100644 docs_src/query_params/tutorial001_py39.py delete mode 100644 docs_src/query_params/tutorial005_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial005_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial005_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial006_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial006_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial012_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial012_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial013_an_py39.py delete mode 100644 docs_src/query_params_str_validations/tutorial013_py39.py delete mode 100644 docs_src/request_files/tutorial001_03_an_py39.py delete mode 100644 docs_src/request_files/tutorial001_03_py39.py delete mode 100644 docs_src/request_files/tutorial001_an_py39.py delete mode 100644 docs_src/request_files/tutorial001_py39.py delete mode 100644 docs_src/request_files/tutorial002_an_py39.py delete mode 100644 docs_src/request_files/tutorial002_py39.py delete mode 100644 docs_src/request_files/tutorial003_an_py39.py delete mode 100644 docs_src/request_files/tutorial003_py39.py delete mode 100644 docs_src/request_form_models/tutorial001_an_py39.py delete mode 100644 docs_src/request_form_models/tutorial001_py39.py delete mode 100644 docs_src/request_form_models/tutorial002_an_py39.py delete mode 100644 docs_src/request_form_models/tutorial002_py39.py delete mode 100644 docs_src/request_forms/tutorial001_an_py39.py delete mode 100644 docs_src/request_forms/tutorial001_py39.py delete mode 100644 docs_src/request_forms_and_files/tutorial001_an_py39.py delete mode 100644 docs_src/request_forms_and_files/tutorial001_py39.py delete mode 100644 docs_src/response_change_status_code/tutorial001_py39.py delete mode 100644 docs_src/response_cookies/tutorial001_py39.py delete mode 100644 docs_src/response_cookies/tutorial002_py39.py delete mode 100644 docs_src/response_directly/tutorial002_py39.py delete mode 100644 docs_src/response_headers/tutorial001_py39.py delete mode 100644 docs_src/response_headers/tutorial002_py39.py delete mode 100644 docs_src/response_model/tutorial003_02_py39.py delete mode 100644 docs_src/response_model/tutorial003_03_py39.py delete mode 100644 docs_src/response_status_code/tutorial001_py39.py delete mode 100644 docs_src/response_status_code/tutorial002_py39.py delete mode 100644 docs_src/security/tutorial001_an_py39.py delete mode 100644 docs_src/security/tutorial001_py39.py delete mode 100644 docs_src/security/tutorial006_an_py39.py delete mode 100644 docs_src/security/tutorial006_py39.py delete mode 100644 docs_src/security/tutorial007_an_py39.py delete mode 100644 docs_src/security/tutorial007_py39.py delete mode 100644 docs_src/settings/app01_py39/__init__.py delete mode 100644 docs_src/settings/app01_py39/config.py delete mode 100644 docs_src/settings/app01_py39/main.py delete mode 100644 docs_src/settings/app02_an_py39/__init__.py delete mode 100644 docs_src/settings/app02_an_py39/config.py delete mode 100644 docs_src/settings/app02_an_py39/main.py delete mode 100644 docs_src/settings/app02_an_py39/test_main.py delete mode 100644 docs_src/settings/app02_py39/__init__.py delete mode 100644 docs_src/settings/app02_py39/config.py delete mode 100644 docs_src/settings/app02_py39/main.py delete mode 100644 docs_src/settings/app02_py39/test_main.py delete mode 100644 docs_src/settings/app03_an_py39/__init__.py delete mode 100644 docs_src/settings/app03_an_py39/config.py delete mode 100644 docs_src/settings/app03_an_py39/main.py delete mode 100644 docs_src/settings/app03_py39/__init__.py delete mode 100644 docs_src/settings/app03_py39/config.py delete mode 100644 docs_src/settings/app03_py39/main.py delete mode 100644 docs_src/settings/tutorial001_py39.py delete mode 100644 docs_src/static_files/tutorial001_py39.py delete mode 100644 docs_src/sub_applications/tutorial001_py39.py delete mode 100644 docs_src/templates/tutorial001_py39.py delete mode 100644 docs_src/using_request_directly/tutorial001_py39.py delete mode 100644 docs_src/websockets/tutorial001_py39.py delete mode 100644 docs_src/websockets/tutorial003_py39.py delete mode 100644 docs_src/wsgi/tutorial001_py39.py delete mode 100644 tests/test_tutorial/test_python_types/test_tutorial009c.py diff --git a/docs_src/additional_responses/tutorial001_py39.py b/docs_src/additional_responses/tutorial001_py39.py deleted file mode 100644 index ffa821b91..000000000 --- a/docs_src/additional_responses/tutorial001_py39.py +++ /dev/null @@ -1,22 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import JSONResponse -from pydantic import BaseModel - - -class Item(BaseModel): - id: str - value: str - - -class Message(BaseModel): - message: str - - -app = FastAPI() - - -@app.get("/items/{item_id}", response_model=Item, responses={404: {"model": Message}}) -async def read_item(item_id: str): - if item_id == "foo": - return {"id": "foo", "value": "there goes my hero"} - return JSONResponse(status_code=404, content={"message": "Item not found"}) diff --git a/docs_src/additional_responses/tutorial003_py39.py b/docs_src/additional_responses/tutorial003_py39.py deleted file mode 100644 index f3e41e8d2..000000000 --- a/docs_src/additional_responses/tutorial003_py39.py +++ /dev/null @@ -1,37 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import JSONResponse -from pydantic import BaseModel - - -class Item(BaseModel): - id: str - value: str - - -class Message(BaseModel): - message: str - - -app = FastAPI() - - -@app.get( - "/items/{item_id}", - response_model=Item, - responses={ - 404: {"model": Message, "description": "The item was not found"}, - 200: { - "description": "Item requested by ID", - "content": { - "application/json": { - "example": {"id": "bar", "value": "The bar tenders"} - } - }, - }, - }, -) -async def read_item(item_id: str): - if item_id == "foo": - return {"id": "foo", "value": "there goes my hero"} - else: - return JSONResponse(status_code=404, content={"message": "Item not found"}) diff --git a/docs_src/advanced_middleware/tutorial001_py39.py b/docs_src/advanced_middleware/tutorial001_py39.py deleted file mode 100644 index 35dbd3046..000000000 --- a/docs_src/advanced_middleware/tutorial001_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI -from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware - -app = FastAPI() - -app.add_middleware(HTTPSRedirectMiddleware) - - -@app.get("/") -async def main(): - return {"message": "Hello World"} diff --git a/docs_src/advanced_middleware/tutorial002_py39.py b/docs_src/advanced_middleware/tutorial002_py39.py deleted file mode 100644 index 405235ab9..000000000 --- a/docs_src/advanced_middleware/tutorial002_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import FastAPI -from fastapi.middleware.trustedhost import TrustedHostMiddleware - -app = FastAPI() - -app.add_middleware( - TrustedHostMiddleware, allowed_hosts=["example.com", "*.example.com"] -) - - -@app.get("/") -async def main(): - return {"message": "Hello World"} diff --git a/docs_src/advanced_middleware/tutorial003_py39.py b/docs_src/advanced_middleware/tutorial003_py39.py deleted file mode 100644 index e2c87e67d..000000000 --- a/docs_src/advanced_middleware/tutorial003_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI -from fastapi.middleware.gzip import GZipMiddleware - -app = FastAPI() - -app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=5) - - -@app.get("/") -async def main(): - return "somebigcontent" diff --git a/docs_src/app_testing/app_a_py39/__init__.py b/docs_src/app_testing/app_a_py39/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/app_testing/app_a_py39/main.py b/docs_src/app_testing/app_a_py39/main.py deleted file mode 100644 index 4679aec9c..000000000 --- a/docs_src/app_testing/app_a_py39/main.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -async def read_main(): - return {"msg": "Hello World"} diff --git a/docs_src/app_testing/app_a_py39/test_main.py b/docs_src/app_testing/app_a_py39/test_main.py deleted file mode 100644 index ddc013f40..000000000 --- a/docs_src/app_testing/app_a_py39/test_main.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi.testclient import TestClient - -from .main import app - -client = TestClient(app) - - -def test_read_main(): - response = client.get("/") - assert response.status_code == 200 - assert response.json() == {"msg": "Hello World"} diff --git a/docs_src/app_testing/tutorial001_py39.py b/docs_src/app_testing/tutorial001_py39.py deleted file mode 100644 index 79a853b48..000000000 --- a/docs_src/app_testing/tutorial001_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() - - -@app.get("/") -async def read_main(): - return {"msg": "Hello World"} - - -client = TestClient(app) - - -def test_read_main(): - response = client.get("/") - assert response.status_code == 200 - assert response.json() == {"msg": "Hello World"} diff --git a/docs_src/app_testing/tutorial002_py39.py b/docs_src/app_testing/tutorial002_py39.py deleted file mode 100644 index 71c898b3c..000000000 --- a/docs_src/app_testing/tutorial002_py39.py +++ /dev/null @@ -1,31 +0,0 @@ -from fastapi import FastAPI -from fastapi.testclient import TestClient -from fastapi.websockets import WebSocket - -app = FastAPI() - - -@app.get("/") -async def read_main(): - return {"msg": "Hello World"} - - -@app.websocket("/ws") -async def websocket(websocket: WebSocket): - await websocket.accept() - await websocket.send_json({"msg": "Hello WebSocket"}) - await websocket.close() - - -def test_read_main(): - client = TestClient(app) - response = client.get("/") - assert response.status_code == 200 - assert response.json() == {"msg": "Hello World"} - - -def test_websocket(): - client = TestClient(app) - with client.websocket_connect("/ws") as websocket: - data = websocket.receive_json() - assert data == {"msg": "Hello WebSocket"} diff --git a/docs_src/app_testing/tutorial003_py39.py b/docs_src/app_testing/tutorial003_py39.py deleted file mode 100644 index ca6b45ce0..000000000 --- a/docs_src/app_testing/tutorial003_py39.py +++ /dev/null @@ -1,24 +0,0 @@ -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() - -items = {} - - -@app.on_event("startup") -async def startup_event(): - items["foo"] = {"name": "Fighters"} - items["bar"] = {"name": "Tenders"} - - -@app.get("/items/{item_id}") -async def read_items(item_id: str): - return items[item_id] - - -def test_read_items(): - with TestClient(app) as client: - response = client.get("/items/foo") - assert response.status_code == 200 - assert response.json() == {"name": "Fighters"} diff --git a/docs_src/app_testing/tutorial004_py39.py b/docs_src/app_testing/tutorial004_py39.py deleted file mode 100644 index f83ac9ae9..000000000 --- a/docs_src/app_testing/tutorial004_py39.py +++ /dev/null @@ -1,43 +0,0 @@ -from contextlib import asynccontextmanager - -from fastapi import FastAPI -from fastapi.testclient import TestClient - -items = {} - - -@asynccontextmanager -async def lifespan(app: FastAPI): - items["foo"] = {"name": "Fighters"} - items["bar"] = {"name": "Tenders"} - yield - # clean up items - items.clear() - - -app = FastAPI(lifespan=lifespan) - - -@app.get("/items/{item_id}") -async def read_items(item_id: str): - return items[item_id] - - -def test_read_items(): - # Before the lifespan starts, "items" is still empty - assert items == {} - - with TestClient(app) as client: - # Inside the "with TestClient" block, the lifespan starts and items added - assert items == {"foo": {"name": "Fighters"}, "bar": {"name": "Tenders"}} - - response = client.get("/items/foo") - assert response.status_code == 200 - assert response.json() == {"name": "Fighters"} - - # After the requests is done, the items are still there - assert items == {"foo": {"name": "Fighters"}, "bar": {"name": "Tenders"}} - - # The end of the "with TestClient" block simulates terminating the app, so - # the lifespan ends and items are cleaned up - assert items == {} diff --git a/docs_src/async_tests/app_a_py39/__init__.py b/docs_src/async_tests/app_a_py39/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/async_tests/app_a_py39/main.py b/docs_src/async_tests/app_a_py39/main.py deleted file mode 100644 index 9594f859c..000000000 --- a/docs_src/async_tests/app_a_py39/main.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -async def root(): - return {"message": "Tomato"} diff --git a/docs_src/async_tests/app_a_py39/test_main.py b/docs_src/async_tests/app_a_py39/test_main.py deleted file mode 100644 index a57a31f7d..000000000 --- a/docs_src/async_tests/app_a_py39/test_main.py +++ /dev/null @@ -1,14 +0,0 @@ -import pytest -from httpx import ASGITransport, AsyncClient - -from .main import app - - -@pytest.mark.anyio -async def test_root(): - async with AsyncClient( - transport=ASGITransport(app=app), base_url="http://test" - ) as ac: - response = await ac.get("/") - assert response.status_code == 200 - assert response.json() == {"message": "Tomato"} diff --git a/docs_src/authentication_error_status_code/tutorial001_an_py39.py b/docs_src/authentication_error_status_code/tutorial001_an_py39.py deleted file mode 100644 index 7bbc2f717..000000000 --- a/docs_src/authentication_error_status_code/tutorial001_an_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Annotated - -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer - -app = FastAPI() - - -class HTTPBearer403(HTTPBearer): - def make_not_authenticated_error(self) -> HTTPException: - return HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="Not authenticated" - ) - - -CredentialsDep = Annotated[HTTPAuthorizationCredentials, Depends(HTTPBearer403())] - - -@app.get("/me") -def read_me(credentials: CredentialsDep): - return {"message": "You are authenticated", "token": credentials.credentials} diff --git a/docs_src/background_tasks/tutorial001_py39.py b/docs_src/background_tasks/tutorial001_py39.py deleted file mode 100644 index 1720a7433..000000000 --- a/docs_src/background_tasks/tutorial001_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from fastapi import BackgroundTasks, FastAPI - -app = FastAPI() - - -def write_notification(email: str, message=""): - with open("log.txt", mode="w") as email_file: - content = f"notification for {email}: {message}" - email_file.write(content) - - -@app.post("/send-notification/{email}") -async def send_notification(email: str, background_tasks: BackgroundTasks): - background_tasks.add_task(write_notification, email, message="some notification") - return {"message": "Notification sent in the background"} diff --git a/docs_src/behind_a_proxy/tutorial001_01_py39.py b/docs_src/behind_a_proxy/tutorial001_01_py39.py deleted file mode 100644 index 52b114395..000000000 --- a/docs_src/behind_a_proxy/tutorial001_01_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/") -def read_items(): - return ["plumbus", "portal gun"] diff --git a/docs_src/behind_a_proxy/tutorial001_py39.py b/docs_src/behind_a_proxy/tutorial001_py39.py deleted file mode 100644 index ede59ada1..000000000 --- a/docs_src/behind_a_proxy/tutorial001_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI, Request - -app = FastAPI() - - -@app.get("/app") -def read_main(request: Request): - return {"message": "Hello World", "root_path": request.scope.get("root_path")} diff --git a/docs_src/behind_a_proxy/tutorial002_py39.py b/docs_src/behind_a_proxy/tutorial002_py39.py deleted file mode 100644 index c1600cde9..000000000 --- a/docs_src/behind_a_proxy/tutorial002_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI, Request - -app = FastAPI(root_path="/api/v1") - - -@app.get("/app") -def read_main(request: Request): - return {"message": "Hello World", "root_path": request.scope.get("root_path")} diff --git a/docs_src/behind_a_proxy/tutorial003_py39.py b/docs_src/behind_a_proxy/tutorial003_py39.py deleted file mode 100644 index 3b7d8be01..000000000 --- a/docs_src/behind_a_proxy/tutorial003_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI, Request - -app = FastAPI( - servers=[ - {"url": "https://stag.example.com", "description": "Staging environment"}, - {"url": "https://prod.example.com", "description": "Production environment"}, - ], - root_path="/api/v1", -) - - -@app.get("/app") -def read_main(request: Request): - return {"message": "Hello World", "root_path": request.scope.get("root_path")} diff --git a/docs_src/behind_a_proxy/tutorial004_py39.py b/docs_src/behind_a_proxy/tutorial004_py39.py deleted file mode 100644 index 51bd5babc..000000000 --- a/docs_src/behind_a_proxy/tutorial004_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from fastapi import FastAPI, Request - -app = FastAPI( - servers=[ - {"url": "https://stag.example.com", "description": "Staging environment"}, - {"url": "https://prod.example.com", "description": "Production environment"}, - ], - root_path="/api/v1", - root_path_in_servers=False, -) - - -@app.get("/app") -def read_main(request: Request): - return {"message": "Hello World", "root_path": request.scope.get("root_path")} diff --git a/docs_src/bigger_applications/app_an_py39/__init__.py b/docs_src/bigger_applications/app_an_py39/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/bigger_applications/app_an_py39/dependencies.py b/docs_src/bigger_applications/app_an_py39/dependencies.py deleted file mode 100644 index 5c7612aa0..000000000 --- a/docs_src/bigger_applications/app_an_py39/dependencies.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated - -from fastapi import Header, HTTPException - - -async def get_token_header(x_token: Annotated[str, Header()]): - if x_token != "fake-super-secret-token": - raise HTTPException(status_code=400, detail="X-Token header invalid") - - -async def get_query_token(token: str): - if token != "jessica": - raise HTTPException(status_code=400, detail="No Jessica token provided") diff --git a/docs_src/bigger_applications/app_an_py39/internal/__init__.py b/docs_src/bigger_applications/app_an_py39/internal/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/bigger_applications/app_an_py39/internal/admin.py b/docs_src/bigger_applications/app_an_py39/internal/admin.py deleted file mode 100644 index 99d3da86b..000000000 --- a/docs_src/bigger_applications/app_an_py39/internal/admin.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter() - - -@router.post("/") -async def update_admin(): - return {"message": "Admin getting schwifty"} diff --git a/docs_src/bigger_applications/app_an_py39/main.py b/docs_src/bigger_applications/app_an_py39/main.py deleted file mode 100644 index ae544a3aa..000000000 --- a/docs_src/bigger_applications/app_an_py39/main.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi import Depends, FastAPI - -from .dependencies import get_query_token, get_token_header -from .internal import admin -from .routers import items, users - -app = FastAPI(dependencies=[Depends(get_query_token)]) - - -app.include_router(users.router) -app.include_router(items.router) -app.include_router( - admin.router, - prefix="/admin", - tags=["admin"], - dependencies=[Depends(get_token_header)], - responses={418: {"description": "I'm a teapot"}}, -) - - -@app.get("/") -async def root(): - return {"message": "Hello Bigger Applications!"} diff --git a/docs_src/bigger_applications/app_an_py39/routers/__init__.py b/docs_src/bigger_applications/app_an_py39/routers/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/bigger_applications/app_an_py39/routers/items.py b/docs_src/bigger_applications/app_an_py39/routers/items.py deleted file mode 100644 index bde9ff4d5..000000000 --- a/docs_src/bigger_applications/app_an_py39/routers/items.py +++ /dev/null @@ -1,38 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException - -from ..dependencies import get_token_header - -router = APIRouter( - prefix="/items", - tags=["items"], - dependencies=[Depends(get_token_header)], - responses={404: {"description": "Not found"}}, -) - - -fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}} - - -@router.get("/") -async def read_items(): - return fake_items_db - - -@router.get("/{item_id}") -async def read_item(item_id: str): - if item_id not in fake_items_db: - raise HTTPException(status_code=404, detail="Item not found") - return {"name": fake_items_db[item_id]["name"], "item_id": item_id} - - -@router.put( - "/{item_id}", - tags=["custom"], - responses={403: {"description": "Operation forbidden"}}, -) -async def update_item(item_id: str): - if item_id != "plumbus": - raise HTTPException( - status_code=403, detail="You can only update the item: plumbus" - ) - return {"item_id": item_id, "name": "The great Plumbus"} diff --git a/docs_src/bigger_applications/app_an_py39/routers/users.py b/docs_src/bigger_applications/app_an_py39/routers/users.py deleted file mode 100644 index 39b3d7e7c..000000000 --- a/docs_src/bigger_applications/app_an_py39/routers/users.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter() - - -@router.get("/users/", tags=["users"]) -async def read_users(): - return [{"username": "Rick"}, {"username": "Morty"}] - - -@router.get("/users/me", tags=["users"]) -async def read_user_me(): - return {"username": "fakecurrentuser"} - - -@router.get("/users/{username}", tags=["users"]) -async def read_user(username: str): - return {"username": username} diff --git a/docs_src/bigger_applications/app_py39/__init__.py b/docs_src/bigger_applications/app_py39/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/bigger_applications/app_py39/dependencies.py b/docs_src/bigger_applications/app_py39/dependencies.py deleted file mode 100644 index 8e45f004b..000000000 --- a/docs_src/bigger_applications/app_py39/dependencies.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import Header, HTTPException - - -async def get_token_header(x_token: str = Header()): - if x_token != "fake-super-secret-token": - raise HTTPException(status_code=400, detail="X-Token header invalid") - - -async def get_query_token(token: str): - if token != "jessica": - raise HTTPException(status_code=400, detail="No Jessica token provided") diff --git a/docs_src/bigger_applications/app_py39/main.py b/docs_src/bigger_applications/app_py39/main.py deleted file mode 100644 index ae544a3aa..000000000 --- a/docs_src/bigger_applications/app_py39/main.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi import Depends, FastAPI - -from .dependencies import get_query_token, get_token_header -from .internal import admin -from .routers import items, users - -app = FastAPI(dependencies=[Depends(get_query_token)]) - - -app.include_router(users.router) -app.include_router(items.router) -app.include_router( - admin.router, - prefix="/admin", - tags=["admin"], - dependencies=[Depends(get_token_header)], - responses={418: {"description": "I'm a teapot"}}, -) - - -@app.get("/") -async def root(): - return {"message": "Hello Bigger Applications!"} diff --git a/docs_src/body_nested_models/tutorial008_py39.py b/docs_src/body_nested_models/tutorial008_py39.py deleted file mode 100644 index 854a7a5a4..000000000 --- a/docs_src/body_nested_models/tutorial008_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI -from pydantic import BaseModel, HttpUrl - -app = FastAPI() - - -class Image(BaseModel): - url: HttpUrl - name: str - - -@app.post("/images/multiple/") -async def create_multiple_images(images: list[Image]): - return images diff --git a/docs_src/body_nested_models/tutorial009_py39.py b/docs_src/body_nested_models/tutorial009_py39.py deleted file mode 100644 index 59c1e5082..000000000 --- a/docs_src/body_nested_models/tutorial009_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.post("/index-weights/") -async def create_index_weights(weights: dict[int, float]): - return weights diff --git a/docs_src/conditional_openapi/tutorial001_py39.py b/docs_src/conditional_openapi/tutorial001_py39.py deleted file mode 100644 index eedb0d274..000000000 --- a/docs_src/conditional_openapi/tutorial001_py39.py +++ /dev/null @@ -1,16 +0,0 @@ -from fastapi import FastAPI -from pydantic_settings import BaseSettings - - -class Settings(BaseSettings): - openapi_url: str = "/openapi.json" - - -settings = Settings() - -app = FastAPI(openapi_url=settings.openapi_url) - - -@app.get("/") -def root(): - return {"message": "Hello World"} diff --git a/docs_src/configure_swagger_ui/tutorial001_py39.py b/docs_src/configure_swagger_ui/tutorial001_py39.py deleted file mode 100644 index 6c24ce758..000000000 --- a/docs_src/configure_swagger_ui/tutorial001_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI(swagger_ui_parameters={"syntaxHighlight": False}) - - -@app.get("/users/{username}") -async def read_user(username: str): - return {"message": f"Hello {username}"} diff --git a/docs_src/configure_swagger_ui/tutorial002_py39.py b/docs_src/configure_swagger_ui/tutorial002_py39.py deleted file mode 100644 index cc75c2196..000000000 --- a/docs_src/configure_swagger_ui/tutorial002_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI(swagger_ui_parameters={"syntaxHighlight": {"theme": "obsidian"}}) - - -@app.get("/users/{username}") -async def read_user(username: str): - return {"message": f"Hello {username}"} diff --git a/docs_src/configure_swagger_ui/tutorial003_py39.py b/docs_src/configure_swagger_ui/tutorial003_py39.py deleted file mode 100644 index b4449f5c6..000000000 --- a/docs_src/configure_swagger_ui/tutorial003_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI(swagger_ui_parameters={"deepLinking": False}) - - -@app.get("/users/{username}") -async def read_user(username: str): - return {"message": f"Hello {username}"} diff --git a/docs_src/cors/tutorial001_py39.py b/docs_src/cors/tutorial001_py39.py deleted file mode 100644 index d59ab27ac..000000000 --- a/docs_src/cors/tutorial001_py39.py +++ /dev/null @@ -1,24 +0,0 @@ -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware - -app = FastAPI() - -origins = [ - "http://localhost.tiangolo.com", - "https://localhost.tiangolo.com", - "http://localhost", - "http://localhost:8080", -] - -app.add_middleware( - CORSMiddleware, - allow_origins=origins, - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - - -@app.get("/") -async def main(): - return {"message": "Hello World"} diff --git a/docs_src/custom_docs_ui/tutorial001_py39.py b/docs_src/custom_docs_ui/tutorial001_py39.py deleted file mode 100644 index 1cfcce19a..000000000 --- a/docs_src/custom_docs_ui/tutorial001_py39.py +++ /dev/null @@ -1,38 +0,0 @@ -from fastapi import FastAPI -from fastapi.openapi.docs import ( - get_redoc_html, - get_swagger_ui_html, - get_swagger_ui_oauth2_redirect_html, -) - -app = FastAPI(docs_url=None, redoc_url=None) - - -@app.get("/docs", include_in_schema=False) -async def custom_swagger_ui_html(): - return get_swagger_ui_html( - openapi_url=app.openapi_url, - title=app.title + " - Swagger UI", - oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, - swagger_js_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js", - swagger_css_url="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css", - ) - - -@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False) -async def swagger_ui_redirect(): - return get_swagger_ui_oauth2_redirect_html() - - -@app.get("/redoc", include_in_schema=False) -async def redoc_html(): - return get_redoc_html( - openapi_url=app.openapi_url, - title=app.title + " - ReDoc", - redoc_js_url="https://unpkg.com/redoc@2/bundles/redoc.standalone.js", - ) - - -@app.get("/users/{username}") -async def read_user(username: str): - return {"message": f"Hello {username}"} diff --git a/docs_src/custom_docs_ui/tutorial002_py39.py b/docs_src/custom_docs_ui/tutorial002_py39.py deleted file mode 100644 index 23ea368f8..000000000 --- a/docs_src/custom_docs_ui/tutorial002_py39.py +++ /dev/null @@ -1,41 +0,0 @@ -from fastapi import FastAPI -from fastapi.openapi.docs import ( - get_redoc_html, - get_swagger_ui_html, - get_swagger_ui_oauth2_redirect_html, -) -from fastapi.staticfiles import StaticFiles - -app = FastAPI(docs_url=None, redoc_url=None) - -app.mount("/static", StaticFiles(directory="static"), name="static") - - -@app.get("/docs", include_in_schema=False) -async def custom_swagger_ui_html(): - return get_swagger_ui_html( - openapi_url=app.openapi_url, - title=app.title + " - Swagger UI", - oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, - swagger_js_url="/static/swagger-ui-bundle.js", - swagger_css_url="/static/swagger-ui.css", - ) - - -@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False) -async def swagger_ui_redirect(): - return get_swagger_ui_oauth2_redirect_html() - - -@app.get("/redoc", include_in_schema=False) -async def redoc_html(): - return get_redoc_html( - openapi_url=app.openapi_url, - title=app.title + " - ReDoc", - redoc_js_url="/static/redoc.standalone.js", - ) - - -@app.get("/users/{username}") -async def read_user(username: str): - return {"message": f"Hello {username}"} diff --git a/docs_src/custom_response/tutorial001_py39.py b/docs_src/custom_response/tutorial001_py39.py deleted file mode 100644 index 0f09bdf77..000000000 --- a/docs_src/custom_response/tutorial001_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import UJSONResponse - -app = FastAPI() - - -@app.get("/items/", response_class=UJSONResponse) -async def read_items(): - return [{"item_id": "Foo"}] diff --git a/docs_src/custom_response/tutorial001b_py39.py b/docs_src/custom_response/tutorial001b_py39.py deleted file mode 100644 index 95e6ca763..000000000 --- a/docs_src/custom_response/tutorial001b_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import ORJSONResponse - -app = FastAPI() - - -@app.get("/items/", response_class=ORJSONResponse) -async def read_items(): - return ORJSONResponse([{"item_id": "Foo"}]) diff --git a/docs_src/custom_response/tutorial002_py39.py b/docs_src/custom_response/tutorial002_py39.py deleted file mode 100644 index 23c495867..000000000 --- a/docs_src/custom_response/tutorial002_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import HTMLResponse - -app = FastAPI() - - -@app.get("/items/", response_class=HTMLResponse) -async def read_items(): - return """ - - - Some HTML in here - - -

Look ma! HTML!

- - - """ diff --git a/docs_src/custom_response/tutorial003_py39.py b/docs_src/custom_response/tutorial003_py39.py deleted file mode 100644 index 51ad3c146..000000000 --- a/docs_src/custom_response/tutorial003_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import HTMLResponse - -app = FastAPI() - - -@app.get("/items/") -async def read_items(): - html_content = """ - - - Some HTML in here - - -

Look ma! HTML!

- - - """ - return HTMLResponse(content=html_content, status_code=200) diff --git a/docs_src/custom_response/tutorial004_py39.py b/docs_src/custom_response/tutorial004_py39.py deleted file mode 100644 index 0e90f2012..000000000 --- a/docs_src/custom_response/tutorial004_py39.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import HTMLResponse - -app = FastAPI() - - -def generate_html_response(): - html_content = """ - - - Some HTML in here - - -

Look ma! HTML!

- - - """ - return HTMLResponse(content=html_content, status_code=200) - - -@app.get("/items/", response_class=HTMLResponse) -async def read_items(): - return generate_html_response() diff --git a/docs_src/custom_response/tutorial005_py39.py b/docs_src/custom_response/tutorial005_py39.py deleted file mode 100644 index 3d58f57fb..000000000 --- a/docs_src/custom_response/tutorial005_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import PlainTextResponse - -app = FastAPI() - - -@app.get("/", response_class=PlainTextResponse) -async def main(): - return "Hello World" diff --git a/docs_src/custom_response/tutorial006_py39.py b/docs_src/custom_response/tutorial006_py39.py deleted file mode 100644 index 332f8f87f..000000000 --- a/docs_src/custom_response/tutorial006_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import RedirectResponse - -app = FastAPI() - - -@app.get("/typer") -async def redirect_typer(): - return RedirectResponse("https://typer.tiangolo.com") diff --git a/docs_src/custom_response/tutorial006b_py39.py b/docs_src/custom_response/tutorial006b_py39.py deleted file mode 100644 index 03a7be399..000000000 --- a/docs_src/custom_response/tutorial006b_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import RedirectResponse - -app = FastAPI() - - -@app.get("/fastapi", response_class=RedirectResponse) -async def redirect_fastapi(): - return "https://fastapi.tiangolo.com" diff --git a/docs_src/custom_response/tutorial006c_py39.py b/docs_src/custom_response/tutorial006c_py39.py deleted file mode 100644 index 87c720364..000000000 --- a/docs_src/custom_response/tutorial006c_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import RedirectResponse - -app = FastAPI() - - -@app.get("/pydantic", response_class=RedirectResponse, status_code=302) -async def redirect_pydantic(): - return "https://docs.pydantic.dev/" diff --git a/docs_src/custom_response/tutorial007_py39.py b/docs_src/custom_response/tutorial007_py39.py deleted file mode 100644 index e2a53a211..000000000 --- a/docs_src/custom_response/tutorial007_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import StreamingResponse - -app = FastAPI() - - -async def fake_video_streamer(): - for i in range(10): - yield b"some fake video bytes" - - -@app.get("/") -async def main(): - return StreamingResponse(fake_video_streamer()) diff --git a/docs_src/custom_response/tutorial008_py39.py b/docs_src/custom_response/tutorial008_py39.py deleted file mode 100644 index fc071cbee..000000000 --- a/docs_src/custom_response/tutorial008_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import StreamingResponse - -some_file_path = "large-video-file.mp4" -app = FastAPI() - - -@app.get("/") -def main(): - def iterfile(): # (1) - with open(some_file_path, mode="rb") as file_like: # (2) - yield from file_like # (3) - - return StreamingResponse(iterfile(), media_type="video/mp4") diff --git a/docs_src/custom_response/tutorial009_py39.py b/docs_src/custom_response/tutorial009_py39.py deleted file mode 100644 index 71cf50cc1..000000000 --- a/docs_src/custom_response/tutorial009_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import FileResponse - -some_file_path = "large-video-file.mp4" -app = FastAPI() - - -@app.get("/") -async def main(): - return FileResponse(some_file_path) diff --git a/docs_src/custom_response/tutorial009b_py39.py b/docs_src/custom_response/tutorial009b_py39.py deleted file mode 100644 index 27200ee4b..000000000 --- a/docs_src/custom_response/tutorial009b_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import FileResponse - -some_file_path = "large-video-file.mp4" -app = FastAPI() - - -@app.get("/", response_class=FileResponse) -async def main(): - return some_file_path diff --git a/docs_src/custom_response/tutorial009c_py39.py b/docs_src/custom_response/tutorial009c_py39.py deleted file mode 100644 index de6b6688e..000000000 --- a/docs_src/custom_response/tutorial009c_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Any - -import orjson -from fastapi import FastAPI, Response - -app = FastAPI() - - -class CustomORJSONResponse(Response): - media_type = "application/json" - - def render(self, content: Any) -> bytes: - assert orjson is not None, "orjson must be installed" - return orjson.dumps(content, option=orjson.OPT_INDENT_2) - - -@app.get("/", response_class=CustomORJSONResponse) -async def main(): - return {"message": "Hello World"} diff --git a/docs_src/custom_response/tutorial010_py39.py b/docs_src/custom_response/tutorial010_py39.py deleted file mode 100644 index 57cb06260..000000000 --- a/docs_src/custom_response/tutorial010_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import ORJSONResponse - -app = FastAPI(default_response_class=ORJSONResponse) - - -@app.get("/items/") -async def read_items(): - return [{"item_id": "Foo"}] diff --git a/docs_src/debugging/tutorial001_py39.py b/docs_src/debugging/tutorial001_py39.py deleted file mode 100644 index 3de21d2a8..000000000 --- a/docs_src/debugging/tutorial001_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -import uvicorn -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def root(): - a = "a" - b = "b" + a - return {"hello world": b} - - -if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/docs_src/dependencies/tutorial006_an_py39.py b/docs_src/dependencies/tutorial006_an_py39.py deleted file mode 100644 index 11976ed6a..000000000 --- a/docs_src/dependencies/tutorial006_an_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Annotated - -from fastapi import Depends, FastAPI, Header, HTTPException - -app = FastAPI() - - -async def verify_token(x_token: Annotated[str, Header()]): - if x_token != "fake-super-secret-token": - raise HTTPException(status_code=400, detail="X-Token header invalid") - - -async def verify_key(x_key: Annotated[str, Header()]): - if x_key != "fake-super-secret-key": - raise HTTPException(status_code=400, detail="X-Key header invalid") - return x_key - - -@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)]) -async def read_items(): - return [{"item": "Foo"}, {"item": "Bar"}] diff --git a/docs_src/dependencies/tutorial006_py39.py b/docs_src/dependencies/tutorial006_py39.py deleted file mode 100644 index 9aff4154f..000000000 --- a/docs_src/dependencies/tutorial006_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from fastapi import Depends, FastAPI, Header, HTTPException - -app = FastAPI() - - -async def verify_token(x_token: str = Header()): - if x_token != "fake-super-secret-token": - raise HTTPException(status_code=400, detail="X-Token header invalid") - - -async def verify_key(x_key: str = Header()): - if x_key != "fake-super-secret-key": - raise HTTPException(status_code=400, detail="X-Key header invalid") - return x_key - - -@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)]) -async def read_items(): - return [{"item": "Foo"}, {"item": "Bar"}] diff --git a/docs_src/dependencies/tutorial007_py39.py b/docs_src/dependencies/tutorial007_py39.py deleted file mode 100644 index 2e4ab4777..000000000 --- a/docs_src/dependencies/tutorial007_py39.py +++ /dev/null @@ -1,6 +0,0 @@ -async def get_db(): - db = DBSession() - try: - yield db - finally: - db.close() diff --git a/docs_src/dependencies/tutorial008_an_py39.py b/docs_src/dependencies/tutorial008_an_py39.py deleted file mode 100644 index acc804c32..000000000 --- a/docs_src/dependencies/tutorial008_an_py39.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Annotated - -from fastapi import Depends - - -async def dependency_a(): - dep_a = generate_dep_a() - try: - yield dep_a - finally: - dep_a.close() - - -async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]): - dep_b = generate_dep_b() - try: - yield dep_b - finally: - dep_b.close(dep_a) - - -async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]): - dep_c = generate_dep_c() - try: - yield dep_c - finally: - dep_c.close(dep_b) diff --git a/docs_src/dependencies/tutorial008_py39.py b/docs_src/dependencies/tutorial008_py39.py deleted file mode 100644 index 8472f642d..000000000 --- a/docs_src/dependencies/tutorial008_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from fastapi import Depends - - -async def dependency_a(): - dep_a = generate_dep_a() - try: - yield dep_a - finally: - dep_a.close() - - -async def dependency_b(dep_a=Depends(dependency_a)): - dep_b = generate_dep_b() - try: - yield dep_b - finally: - dep_b.close(dep_a) - - -async def dependency_c(dep_b=Depends(dependency_b)): - dep_c = generate_dep_c() - try: - yield dep_c - finally: - dep_c.close(dep_b) diff --git a/docs_src/dependencies/tutorial008b_an_py39.py b/docs_src/dependencies/tutorial008b_an_py39.py deleted file mode 100644 index 3b8434c81..000000000 --- a/docs_src/dependencies/tutorial008b_an_py39.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Annotated - -from fastapi import Depends, FastAPI, HTTPException - -app = FastAPI() - - -data = { - "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"}, - "portal-gun": {"description": "Gun to create portals", "owner": "Rick"}, -} - - -class OwnerError(Exception): - pass - - -def get_username(): - try: - yield "Rick" - except OwnerError as e: - raise HTTPException(status_code=400, detail=f"Owner error: {e}") - - -@app.get("/items/{item_id}") -def get_item(item_id: str, username: Annotated[str, Depends(get_username)]): - if item_id not in data: - raise HTTPException(status_code=404, detail="Item not found") - item = data[item_id] - if item["owner"] != username: - raise OwnerError(username) - return item diff --git a/docs_src/dependencies/tutorial008b_py39.py b/docs_src/dependencies/tutorial008b_py39.py deleted file mode 100644 index 163e96600..000000000 --- a/docs_src/dependencies/tutorial008b_py39.py +++ /dev/null @@ -1,30 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException - -app = FastAPI() - - -data = { - "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"}, - "portal-gun": {"description": "Gun to create portals", "owner": "Rick"}, -} - - -class OwnerError(Exception): - pass - - -def get_username(): - try: - yield "Rick" - except OwnerError as e: - raise HTTPException(status_code=400, detail=f"Owner error: {e}") - - -@app.get("/items/{item_id}") -def get_item(item_id: str, username: str = Depends(get_username)): - if item_id not in data: - raise HTTPException(status_code=404, detail="Item not found") - item = data[item_id] - if item["owner"] != username: - raise OwnerError(username) - return item diff --git a/docs_src/dependencies/tutorial008c_an_py39.py b/docs_src/dependencies/tutorial008c_an_py39.py deleted file mode 100644 index da92efa9c..000000000 --- a/docs_src/dependencies/tutorial008c_an_py39.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Annotated - -from fastapi import Depends, FastAPI, HTTPException - -app = FastAPI() - - -class InternalError(Exception): - pass - - -def get_username(): - try: - yield "Rick" - except InternalError: - print("Oops, we didn't raise again, Britney 😱") - - -@app.get("/items/{item_id}") -def get_item(item_id: str, username: Annotated[str, Depends(get_username)]): - if item_id == "portal-gun": - raise InternalError( - f"The portal gun is too dangerous to be owned by {username}" - ) - if item_id != "plumbus": - raise HTTPException( - status_code=404, detail="Item not found, there's only a plumbus here" - ) - return item_id diff --git a/docs_src/dependencies/tutorial008c_py39.py b/docs_src/dependencies/tutorial008c_py39.py deleted file mode 100644 index 4b99a5a31..000000000 --- a/docs_src/dependencies/tutorial008c_py39.py +++ /dev/null @@ -1,27 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException - -app = FastAPI() - - -class InternalError(Exception): - pass - - -def get_username(): - try: - yield "Rick" - except InternalError: - print("Oops, we didn't raise again, Britney 😱") - - -@app.get("/items/{item_id}") -def get_item(item_id: str, username: str = Depends(get_username)): - if item_id == "portal-gun": - raise InternalError( - f"The portal gun is too dangerous to be owned by {username}" - ) - if item_id != "plumbus": - raise HTTPException( - status_code=404, detail="Item not found, there's only a plumbus here" - ) - return item_id diff --git a/docs_src/dependencies/tutorial008d_an_py39.py b/docs_src/dependencies/tutorial008d_an_py39.py deleted file mode 100644 index 99bd5cb91..000000000 --- a/docs_src/dependencies/tutorial008d_an_py39.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Annotated - -from fastapi import Depends, FastAPI, HTTPException - -app = FastAPI() - - -class InternalError(Exception): - pass - - -def get_username(): - try: - yield "Rick" - except InternalError: - print("We don't swallow the internal error here, we raise again 😎") - raise - - -@app.get("/items/{item_id}") -def get_item(item_id: str, username: Annotated[str, Depends(get_username)]): - if item_id == "portal-gun": - raise InternalError( - f"The portal gun is too dangerous to be owned by {username}" - ) - if item_id != "plumbus": - raise HTTPException( - status_code=404, detail="Item not found, there's only a plumbus here" - ) - return item_id diff --git a/docs_src/dependencies/tutorial008d_py39.py b/docs_src/dependencies/tutorial008d_py39.py deleted file mode 100644 index 93039343d..000000000 --- a/docs_src/dependencies/tutorial008d_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException - -app = FastAPI() - - -class InternalError(Exception): - pass - - -def get_username(): - try: - yield "Rick" - except InternalError: - print("We don't swallow the internal error here, we raise again 😎") - raise - - -@app.get("/items/{item_id}") -def get_item(item_id: str, username: str = Depends(get_username)): - if item_id == "portal-gun": - raise InternalError( - f"The portal gun is too dangerous to be owned by {username}" - ) - if item_id != "plumbus": - raise HTTPException( - status_code=404, detail="Item not found, there's only a plumbus here" - ) - return item_id diff --git a/docs_src/dependencies/tutorial008e_an_py39.py b/docs_src/dependencies/tutorial008e_an_py39.py deleted file mode 100644 index 80a44c7e2..000000000 --- a/docs_src/dependencies/tutorial008e_an_py39.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Annotated - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -def get_username(): - try: - yield "Rick" - finally: - print("Cleanup up before response is sent") - - -@app.get("/users/me") -def get_user_me(username: Annotated[str, Depends(get_username, scope="function")]): - return username diff --git a/docs_src/dependencies/tutorial008e_py39.py b/docs_src/dependencies/tutorial008e_py39.py deleted file mode 100644 index 1ed056e91..000000000 --- a/docs_src/dependencies/tutorial008e_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from fastapi import Depends, FastAPI - -app = FastAPI() - - -def get_username(): - try: - yield "Rick" - finally: - print("Cleanup up before response is sent") - - -@app.get("/users/me") -def get_user_me(username: str = Depends(get_username, scope="function")): - return username diff --git a/docs_src/dependencies/tutorial010_py39.py b/docs_src/dependencies/tutorial010_py39.py deleted file mode 100644 index c27f1b170..000000000 --- a/docs_src/dependencies/tutorial010_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -class MySuperContextManager: - def __init__(self): - self.db = DBSession() - - def __enter__(self): - return self.db - - def __exit__(self, exc_type, exc_value, traceback): - self.db.close() - - -async def get_db(): - with MySuperContextManager() as db: - yield db diff --git a/docs_src/dependencies/tutorial011_an_py39.py b/docs_src/dependencies/tutorial011_an_py39.py deleted file mode 100644 index 68b7434ec..000000000 --- a/docs_src/dependencies/tutorial011_an_py39.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Annotated - -from fastapi import Depends, FastAPI - -app = FastAPI() - - -class FixedContentQueryChecker: - def __init__(self, fixed_content: str): - self.fixed_content = fixed_content - - def __call__(self, q: str = ""): - if q: - return self.fixed_content in q - return False - - -checker = FixedContentQueryChecker("bar") - - -@app.get("/query-checker/") -async def read_query_check(fixed_content_included: Annotated[bool, Depends(checker)]): - return {"fixed_content_in_query": fixed_content_included} diff --git a/docs_src/dependencies/tutorial011_py39.py b/docs_src/dependencies/tutorial011_py39.py deleted file mode 100644 index 5d22f6823..000000000 --- a/docs_src/dependencies/tutorial011_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from fastapi import Depends, FastAPI - -app = FastAPI() - - -class FixedContentQueryChecker: - def __init__(self, fixed_content: str): - self.fixed_content = fixed_content - - def __call__(self, q: str = ""): - if q: - return self.fixed_content in q - return False - - -checker = FixedContentQueryChecker("bar") - - -@app.get("/query-checker/") -async def read_query_check(fixed_content_included: bool = Depends(checker)): - return {"fixed_content_in_query": fixed_content_included} diff --git a/docs_src/dependencies/tutorial012_an_py39.py b/docs_src/dependencies/tutorial012_an_py39.py deleted file mode 100644 index 6503591fc..000000000 --- a/docs_src/dependencies/tutorial012_an_py39.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Annotated - -from fastapi import Depends, FastAPI, Header, HTTPException - - -async def verify_token(x_token: Annotated[str, Header()]): - if x_token != "fake-super-secret-token": - raise HTTPException(status_code=400, detail="X-Token header invalid") - - -async def verify_key(x_key: Annotated[str, Header()]): - if x_key != "fake-super-secret-key": - raise HTTPException(status_code=400, detail="X-Key header invalid") - return x_key - - -app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)]) - - -@app.get("/items/") -async def read_items(): - return [{"item": "Portal Gun"}, {"item": "Plumbus"}] - - -@app.get("/users/") -async def read_users(): - return [{"username": "Rick"}, {"username": "Morty"}] diff --git a/docs_src/dependencies/tutorial012_py39.py b/docs_src/dependencies/tutorial012_py39.py deleted file mode 100644 index 36ce6c711..000000000 --- a/docs_src/dependencies/tutorial012_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from fastapi import Depends, FastAPI, Header, HTTPException - - -async def verify_token(x_token: str = Header()): - if x_token != "fake-super-secret-token": - raise HTTPException(status_code=400, detail="X-Token header invalid") - - -async def verify_key(x_key: str = Header()): - if x_key != "fake-super-secret-key": - raise HTTPException(status_code=400, detail="X-Key header invalid") - return x_key - - -app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)]) - - -@app.get("/items/") -async def read_items(): - return [{"item": "Portal Gun"}, {"item": "Plumbus"}] - - -@app.get("/users/") -async def read_users(): - return [{"username": "Rick"}, {"username": "Morty"}] diff --git a/docs_src/events/tutorial001_py39.py b/docs_src/events/tutorial001_py39.py deleted file mode 100644 index 128004c9f..000000000 --- a/docs_src/events/tutorial001_py39.py +++ /dev/null @@ -1,16 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - -items = {} - - -@app.on_event("startup") -async def startup_event(): - items["foo"] = {"name": "Fighters"} - items["bar"] = {"name": "Tenders"} - - -@app.get("/items/{item_id}") -async def read_items(item_id: str): - return items[item_id] diff --git a/docs_src/events/tutorial002_py39.py b/docs_src/events/tutorial002_py39.py deleted file mode 100644 index a71fea802..000000000 --- a/docs_src/events/tutorial002_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.on_event("shutdown") -def shutdown_event(): - with open("log.txt", mode="a") as log: - log.write("Application shutdown") - - -@app.get("/items/") -async def read_items(): - return [{"name": "Foo"}] diff --git a/docs_src/events/tutorial003_py39.py b/docs_src/events/tutorial003_py39.py deleted file mode 100644 index 2b650590b..000000000 --- a/docs_src/events/tutorial003_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from contextlib import asynccontextmanager - -from fastapi import FastAPI - - -def fake_answer_to_everything_ml_model(x: float): - return x * 42 - - -ml_models = {} - - -@asynccontextmanager -async def lifespan(app: FastAPI): - # Load the ML model - ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model - yield - # Clean up the ML models and release the resources - ml_models.clear() - - -app = FastAPI(lifespan=lifespan) - - -@app.get("/predict") -async def predict(x: float): - result = ml_models["answer_to_everything"](x) - return {"result": result} diff --git a/docs_src/extending_openapi/tutorial001_py39.py b/docs_src/extending_openapi/tutorial001_py39.py deleted file mode 100644 index 35e31c0e0..000000000 --- a/docs_src/extending_openapi/tutorial001_py39.py +++ /dev/null @@ -1,29 +0,0 @@ -from fastapi import FastAPI -from fastapi.openapi.utils import get_openapi - -app = FastAPI() - - -@app.get("/items/") -async def read_items(): - return [{"name": "Foo"}] - - -def custom_openapi(): - if app.openapi_schema: - return app.openapi_schema - openapi_schema = get_openapi( - title="Custom title", - version="2.5.0", - summary="This is a very custom OpenAPI schema", - description="Here's a longer description of the custom **OpenAPI** schema", - routes=app.routes, - ) - openapi_schema["info"]["x-logo"] = { - "url": "https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" - } - app.openapi_schema = openapi_schema - return app.openapi_schema - - -app.openapi = custom_openapi diff --git a/docs_src/extra_models/tutorial004_py39.py b/docs_src/extra_models/tutorial004_py39.py deleted file mode 100644 index 28cacde4d..000000000 --- a/docs_src/extra_models/tutorial004_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: str - - -items = [ - {"name": "Foo", "description": "There comes my hero"}, - {"name": "Red", "description": "It's my aeroplane"}, -] - - -@app.get("/items/", response_model=list[Item]) -async def read_items(): - return items diff --git a/docs_src/extra_models/tutorial005_py39.py b/docs_src/extra_models/tutorial005_py39.py deleted file mode 100644 index 9da2a0a0f..000000000 --- a/docs_src/extra_models/tutorial005_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/keyword-weights/", response_model=dict[str, float]) -async def read_keyword_weights(): - return {"foo": 2.3, "bar": 3.4} diff --git a/docs_src/first_steps/tutorial001_py39.py b/docs_src/first_steps/tutorial001_py39.py deleted file mode 100644 index ee60be1f9..000000000 --- a/docs_src/first_steps/tutorial001_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -async def root(): - return {"message": "Hello World"} diff --git a/docs_src/first_steps/tutorial003_py39.py b/docs_src/first_steps/tutorial003_py39.py deleted file mode 100644 index e30b827ea..000000000 --- a/docs_src/first_steps/tutorial003_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def root(): - return {"message": "Hello World"} diff --git a/docs_src/generate_clients/tutorial001_py39.py b/docs_src/generate_clients/tutorial001_py39.py deleted file mode 100644 index 6a5ae2320..000000000 --- a/docs_src/generate_clients/tutorial001_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - - -class ResponseMessage(BaseModel): - message: str - - -@app.post("/items/", response_model=ResponseMessage) -async def create_item(item: Item): - return {"message": "item received"} - - -@app.get("/items/", response_model=list[Item]) -async def get_items(): - return [ - {"name": "Plumbus", "price": 3}, - {"name": "Portal Gun", "price": 9001}, - ] diff --git a/docs_src/generate_clients/tutorial002_py39.py b/docs_src/generate_clients/tutorial002_py39.py deleted file mode 100644 index 83309760b..000000000 --- a/docs_src/generate_clients/tutorial002_py39.py +++ /dev/null @@ -1,36 +0,0 @@ -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - - -class ResponseMessage(BaseModel): - message: str - - -class User(BaseModel): - username: str - email: str - - -@app.post("/items/", response_model=ResponseMessage, tags=["items"]) -async def create_item(item: Item): - return {"message": "Item received"} - - -@app.get("/items/", response_model=list[Item], tags=["items"]) -async def get_items(): - return [ - {"name": "Plumbus", "price": 3}, - {"name": "Portal Gun", "price": 9001}, - ] - - -@app.post("/users/", response_model=ResponseMessage, tags=["users"]) -async def create_user(user: User): - return {"message": "User received"} diff --git a/docs_src/generate_clients/tutorial003_py39.py b/docs_src/generate_clients/tutorial003_py39.py deleted file mode 100644 index 40722cf10..000000000 --- a/docs_src/generate_clients/tutorial003_py39.py +++ /dev/null @@ -1,42 +0,0 @@ -from fastapi import FastAPI -from fastapi.routing import APIRoute -from pydantic import BaseModel - - -def custom_generate_unique_id(route: APIRoute): - return f"{route.tags[0]}-{route.name}" - - -app = FastAPI(generate_unique_id_function=custom_generate_unique_id) - - -class Item(BaseModel): - name: str - price: float - - -class ResponseMessage(BaseModel): - message: str - - -class User(BaseModel): - username: str - email: str - - -@app.post("/items/", response_model=ResponseMessage, tags=["items"]) -async def create_item(item: Item): - return {"message": "Item received"} - - -@app.get("/items/", response_model=list[Item], tags=["items"]) -async def get_items(): - return [ - {"name": "Plumbus", "price": 3}, - {"name": "Portal Gun", "price": 9001}, - ] - - -@app.post("/users/", response_model=ResponseMessage, tags=["users"]) -async def create_user(user: User): - return {"message": "User received"} diff --git a/docs_src/generate_clients/tutorial004_py39.py b/docs_src/generate_clients/tutorial004_py39.py deleted file mode 100644 index 894dc7f8d..000000000 --- a/docs_src/generate_clients/tutorial004_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from pathlib import Path - -file_path = Path("./openapi.json") -openapi_content = json.loads(file_path.read_text()) - -for path_data in openapi_content["paths"].values(): - for operation in path_data.values(): - tag = operation["tags"][0] - operation_id = operation["operationId"] - to_remove = f"{tag}-" - new_operation_id = operation_id[len(to_remove) :] - operation["operationId"] = new_operation_id - -file_path.write_text(json.dumps(openapi_content)) diff --git a/docs_src/graphql_/tutorial001_py39.py b/docs_src/graphql_/tutorial001_py39.py deleted file mode 100644 index e92b2d71c..000000000 --- a/docs_src/graphql_/tutorial001_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -import strawberry -from fastapi import FastAPI -from strawberry.fastapi import GraphQLRouter - - -@strawberry.type -class User: - name: str - age: int - - -@strawberry.type -class Query: - @strawberry.field - def user(self) -> User: - return User(name="Patrick", age=100) - - -schema = strawberry.Schema(query=Query) - - -graphql_app = GraphQLRouter(schema) - -app = FastAPI() -app.include_router(graphql_app, prefix="/graphql") diff --git a/docs_src/handling_errors/tutorial001_py39.py b/docs_src/handling_errors/tutorial001_py39.py deleted file mode 100644 index e5f32aac2..000000000 --- a/docs_src/handling_errors/tutorial001_py39.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import FastAPI, HTTPException - -app = FastAPI() - -items = {"foo": "The Foo Wrestlers"} - - -@app.get("/items/{item_id}") -async def read_item(item_id: str): - if item_id not in items: - raise HTTPException(status_code=404, detail="Item not found") - return {"item": items[item_id]} diff --git a/docs_src/handling_errors/tutorial002_py39.py b/docs_src/handling_errors/tutorial002_py39.py deleted file mode 100644 index e48c295c9..000000000 --- a/docs_src/handling_errors/tutorial002_py39.py +++ /dev/null @@ -1,16 +0,0 @@ -from fastapi import FastAPI, HTTPException - -app = FastAPI() - -items = {"foo": "The Foo Wrestlers"} - - -@app.get("/items-header/{item_id}") -async def read_item_header(item_id: str): - if item_id not in items: - raise HTTPException( - status_code=404, - detail="Item not found", - headers={"X-Error": "There goes my error"}, - ) - return {"item": items[item_id]} diff --git a/docs_src/handling_errors/tutorial003_py39.py b/docs_src/handling_errors/tutorial003_py39.py deleted file mode 100644 index 791cd6838..000000000 --- a/docs_src/handling_errors/tutorial003_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from fastapi import FastAPI, Request -from fastapi.responses import JSONResponse - - -class UnicornException(Exception): - def __init__(self, name: str): - self.name = name - - -app = FastAPI() - - -@app.exception_handler(UnicornException) -async def unicorn_exception_handler(request: Request, exc: UnicornException): - return JSONResponse( - status_code=418, - content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."}, - ) - - -@app.get("/unicorns/{name}") -async def read_unicorn(name: str): - if name == "yolo": - raise UnicornException(name=name) - return {"unicorn_name": name} diff --git a/docs_src/handling_errors/tutorial004_py39.py b/docs_src/handling_errors/tutorial004_py39.py deleted file mode 100644 index ae50807e9..000000000 --- a/docs_src/handling_errors/tutorial004_py39.py +++ /dev/null @@ -1,26 +0,0 @@ -from fastapi import FastAPI, HTTPException -from fastapi.exceptions import RequestValidationError -from fastapi.responses import PlainTextResponse -from starlette.exceptions import HTTPException as StarletteHTTPException - -app = FastAPI() - - -@app.exception_handler(StarletteHTTPException) -async def http_exception_handler(request, exc): - return PlainTextResponse(str(exc.detail), status_code=exc.status_code) - - -@app.exception_handler(RequestValidationError) -async def validation_exception_handler(request, exc: RequestValidationError): - message = "Validation errors:" - for error in exc.errors(): - message += f"\nField: {error['loc']}, Error: {error['msg']}" - return PlainTextResponse(message, status_code=400) - - -@app.get("/items/{item_id}") -async def read_item(item_id: int): - if item_id == 3: - raise HTTPException(status_code=418, detail="Nope! I don't like 3.") - return {"item_id": item_id} diff --git a/docs_src/handling_errors/tutorial005_py39.py b/docs_src/handling_errors/tutorial005_py39.py deleted file mode 100644 index 0e04fa086..000000000 --- a/docs_src/handling_errors/tutorial005_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from fastapi import FastAPI, Request -from fastapi.encoders import jsonable_encoder -from fastapi.exceptions import RequestValidationError -from fastapi.responses import JSONResponse -from pydantic import BaseModel - -app = FastAPI() - - -@app.exception_handler(RequestValidationError) -async def validation_exception_handler(request: Request, exc: RequestValidationError): - return JSONResponse( - status_code=422, - content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}), - ) - - -class Item(BaseModel): - title: str - size: int - - -@app.post("/items/") -async def create_item(item: Item): - return item diff --git a/docs_src/handling_errors/tutorial006_py39.py b/docs_src/handling_errors/tutorial006_py39.py deleted file mode 100644 index e05160d7e..000000000 --- a/docs_src/handling_errors/tutorial006_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from fastapi import FastAPI, HTTPException -from fastapi.exception_handlers import ( - http_exception_handler, - request_validation_exception_handler, -) -from fastapi.exceptions import RequestValidationError -from starlette.exceptions import HTTPException as StarletteHTTPException - -app = FastAPI() - - -@app.exception_handler(StarletteHTTPException) -async def custom_http_exception_handler(request, exc): - print(f"OMG! An HTTP error!: {repr(exc)}") - return await http_exception_handler(request, exc) - - -@app.exception_handler(RequestValidationError) -async def validation_exception_handler(request, exc): - print(f"OMG! The client sent invalid data!: {exc}") - return await request_validation_exception_handler(request, exc) - - -@app.get("/items/{item_id}") -async def read_item(item_id: int): - if item_id == 3: - raise HTTPException(status_code=418, detail="Nope! I don't like 3.") - return {"item_id": item_id} diff --git a/docs_src/metadata/tutorial001_1_py39.py b/docs_src/metadata/tutorial001_1_py39.py deleted file mode 100644 index 419232d86..000000000 --- a/docs_src/metadata/tutorial001_1_py39.py +++ /dev/null @@ -1,38 +0,0 @@ -from fastapi import FastAPI - -description = """ -ChimichangApp API helps you do awesome stuff. 🚀 - -## Items - -You can **read items**. - -## Users - -You will be able to: - -* **Create users** (_not implemented_). -* **Read users** (_not implemented_). -""" - -app = FastAPI( - title="ChimichangApp", - description=description, - summary="Deadpool's favorite app. Nuff said.", - version="0.0.1", - terms_of_service="http://example.com/terms/", - contact={ - "name": "Deadpoolio the Amazing", - "url": "http://x-force.example.com/contact/", - "email": "dp@x-force.example.com", - }, - license_info={ - "name": "Apache 2.0", - "identifier": "Apache-2.0", - }, -) - - -@app.get("/items/") -async def read_items(): - return [{"name": "Katana"}] diff --git a/docs_src/metadata/tutorial001_py39.py b/docs_src/metadata/tutorial001_py39.py deleted file mode 100644 index 76656e81b..000000000 --- a/docs_src/metadata/tutorial001_py39.py +++ /dev/null @@ -1,38 +0,0 @@ -from fastapi import FastAPI - -description = """ -ChimichangApp API helps you do awesome stuff. 🚀 - -## Items - -You can **read items**. - -## Users - -You will be able to: - -* **Create users** (_not implemented_). -* **Read users** (_not implemented_). -""" - -app = FastAPI( - title="ChimichangApp", - description=description, - summary="Deadpool's favorite app. Nuff said.", - version="0.0.1", - terms_of_service="http://example.com/terms/", - contact={ - "name": "Deadpoolio the Amazing", - "url": "http://x-force.example.com/contact/", - "email": "dp@x-force.example.com", - }, - license_info={ - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html", - }, -) - - -@app.get("/items/") -async def read_items(): - return [{"name": "Katana"}] diff --git a/docs_src/metadata/tutorial002_py39.py b/docs_src/metadata/tutorial002_py39.py deleted file mode 100644 index cf9ed7087..000000000 --- a/docs_src/metadata/tutorial002_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI(openapi_url="/api/v1/openapi.json") - - -@app.get("/items/") -async def read_items(): - return [{"name": "Foo"}] diff --git a/docs_src/metadata/tutorial003_py39.py b/docs_src/metadata/tutorial003_py39.py deleted file mode 100644 index ee09c7f37..000000000 --- a/docs_src/metadata/tutorial003_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI(docs_url="/documentation", redoc_url=None) - - -@app.get("/items/") -async def read_items(): - return [{"name": "Foo"}] diff --git a/docs_src/metadata/tutorial004_py39.py b/docs_src/metadata/tutorial004_py39.py deleted file mode 100644 index 465bd659d..000000000 --- a/docs_src/metadata/tutorial004_py39.py +++ /dev/null @@ -1,28 +0,0 @@ -from fastapi import FastAPI - -tags_metadata = [ - { - "name": "users", - "description": "Operations with users. The **login** logic is also here.", - }, - { - "name": "items", - "description": "Manage items. So _fancy_ they have their own docs.", - "externalDocs": { - "description": "Items external docs", - "url": "https://fastapi.tiangolo.com/", - }, - }, -] - -app = FastAPI(openapi_tags=tags_metadata) - - -@app.get("/users/", tags=["users"]) -async def get_users(): - return [{"name": "Harry"}, {"name": "Ron"}] - - -@app.get("/items/", tags=["items"]) -async def get_items(): - return [{"name": "wand"}, {"name": "flying broom"}] diff --git a/docs_src/middleware/tutorial001_py39.py b/docs_src/middleware/tutorial001_py39.py deleted file mode 100644 index e65a7dade..000000000 --- a/docs_src/middleware/tutorial001_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -import time - -from fastapi import FastAPI, Request - -app = FastAPI() - - -@app.middleware("http") -async def add_process_time_header(request: Request, call_next): - start_time = time.perf_counter() - response = await call_next(request) - process_time = time.perf_counter() - start_time - response.headers["X-Process-Time"] = str(process_time) - return response diff --git a/docs_src/openapi_webhooks/tutorial001_py39.py b/docs_src/openapi_webhooks/tutorial001_py39.py deleted file mode 100644 index 55822bb48..000000000 --- a/docs_src/openapi_webhooks/tutorial001_py39.py +++ /dev/null @@ -1,25 +0,0 @@ -from datetime import datetime - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Subscription(BaseModel): - username: str - monthly_fee: float - start_date: datetime - - -@app.webhooks.post("new-subscription") -def new_subscription(body: Subscription): - """ - When a new user subscribes to your service we'll send you a POST request with this - data to the URL that you register for the event `new-subscription` in the dashboard. - """ - - -@app.get("/users/") -def read_users(): - return ["Rick", "Morty"] diff --git a/docs_src/path_operation_advanced_configuration/tutorial001_py39.py b/docs_src/path_operation_advanced_configuration/tutorial001_py39.py deleted file mode 100644 index fafa8ffb8..000000000 --- a/docs_src/path_operation_advanced_configuration/tutorial001_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/", operation_id="some_specific_id_you_define") -async def read_items(): - return [{"item_id": "Foo"}] diff --git a/docs_src/path_operation_advanced_configuration/tutorial002_py39.py b/docs_src/path_operation_advanced_configuration/tutorial002_py39.py deleted file mode 100644 index 3aaae9b37..000000000 --- a/docs_src/path_operation_advanced_configuration/tutorial002_py39.py +++ /dev/null @@ -1,24 +0,0 @@ -from fastapi import FastAPI -from fastapi.routing import APIRoute - -app = FastAPI() - - -@app.get("/items/") -async def read_items(): - return [{"item_id": "Foo"}] - - -def use_route_names_as_operation_ids(app: FastAPI) -> None: - """ - Simplify operation IDs so that generated API clients have simpler function - names. - - Should be called only after all routes have been added. - """ - for route in app.routes: - if isinstance(route, APIRoute): - route.operation_id = route.name # in this case, 'read_items' - - -use_route_names_as_operation_ids(app) diff --git a/docs_src/path_operation_advanced_configuration/tutorial003_py39.py b/docs_src/path_operation_advanced_configuration/tutorial003_py39.py deleted file mode 100644 index dcc358e32..000000000 --- a/docs_src/path_operation_advanced_configuration/tutorial003_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/", include_in_schema=False) -async def read_items(): - return [{"item_id": "Foo"}] diff --git a/docs_src/path_operation_advanced_configuration/tutorial005_py39.py b/docs_src/path_operation_advanced_configuration/tutorial005_py39.py deleted file mode 100644 index 5837ad835..000000000 --- a/docs_src/path_operation_advanced_configuration/tutorial005_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/", openapi_extra={"x-aperture-labs-portal": "blue"}) -async def read_items(): - return [{"item_id": "portal-gun"}] diff --git a/docs_src/path_operation_advanced_configuration/tutorial006_py39.py b/docs_src/path_operation_advanced_configuration/tutorial006_py39.py deleted file mode 100644 index 403c3ee3f..000000000 --- a/docs_src/path_operation_advanced_configuration/tutorial006_py39.py +++ /dev/null @@ -1,41 +0,0 @@ -from fastapi import FastAPI, Request - -app = FastAPI() - - -def magic_data_reader(raw_body: bytes): - return { - "size": len(raw_body), - "content": { - "name": "Maaaagic", - "price": 42, - "description": "Just kiddin', no magic here. ✨", - }, - } - - -@app.post( - "/items/", - openapi_extra={ - "requestBody": { - "content": { - "application/json": { - "schema": { - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"type": "string"}, - "price": {"type": "number"}, - "description": {"type": "string"}, - }, - } - } - }, - "required": True, - }, - }, -) -async def create_item(request: Request): - raw_body = await request.body() - data = magic_data_reader(raw_body) - return data diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_py39.py b/docs_src/path_operation_advanced_configuration/tutorial007_py39.py deleted file mode 100644 index ff64ef792..000000000 --- a/docs_src/path_operation_advanced_configuration/tutorial007_py39.py +++ /dev/null @@ -1,32 +0,0 @@ -import yaml -from fastapi import FastAPI, HTTPException, Request -from pydantic import BaseModel, ValidationError - -app = FastAPI() - - -class Item(BaseModel): - name: str - tags: list[str] - - -@app.post( - "/items/", - openapi_extra={ - "requestBody": { - "content": {"application/x-yaml": {"schema": Item.model_json_schema()}}, - "required": True, - }, - }, -) -async def create_item(request: Request): - raw_body = await request.body() - try: - data = yaml.safe_load(raw_body) - except yaml.YAMLError: - raise HTTPException(status_code=422, detail="Invalid YAML") - try: - item = Item.model_validate(data) - except ValidationError as e: - raise HTTPException(status_code=422, detail=e.errors(include_url=False)) - return item diff --git a/docs_src/path_operation_configuration/tutorial002b_py39.py b/docs_src/path_operation_configuration/tutorial002b_py39.py deleted file mode 100644 index d53b4d817..000000000 --- a/docs_src/path_operation_configuration/tutorial002b_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from enum import Enum - -from fastapi import FastAPI - -app = FastAPI() - - -class Tags(Enum): - items = "items" - users = "users" - - -@app.get("/items/", tags=[Tags.items]) -async def get_items(): - return ["Portal gun", "Plumbus"] - - -@app.get("/users/", tags=[Tags.users]) -async def read_users(): - return ["Rick", "Morty"] diff --git a/docs_src/path_operation_configuration/tutorial006_py39.py b/docs_src/path_operation_configuration/tutorial006_py39.py deleted file mode 100644 index 7c1aa9b20..000000000 --- a/docs_src/path_operation_configuration/tutorial006_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/", tags=["items"]) -async def read_items(): - return [{"name": "Foo", "price": 42}] - - -@app.get("/users/", tags=["users"]) -async def read_users(): - return [{"username": "johndoe"}] - - -@app.get("/elements/", tags=["items"], deprecated=True) -async def read_elements(): - return [{"item_id": "Foo"}] diff --git a/docs_src/path_params/tutorial001_py39.py b/docs_src/path_params/tutorial001_py39.py deleted file mode 100644 index 7bbf70e6c..000000000 --- a/docs_src/path_params/tutorial001_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_item(item_id): - return {"item_id": item_id} diff --git a/docs_src/path_params/tutorial002_py39.py b/docs_src/path_params/tutorial002_py39.py deleted file mode 100644 index 8272ad70d..000000000 --- a/docs_src/path_params/tutorial002_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_item(item_id: int): - return {"item_id": item_id} diff --git a/docs_src/path_params/tutorial003_py39.py b/docs_src/path_params/tutorial003_py39.py deleted file mode 100644 index 5f0aa0923..000000000 --- a/docs_src/path_params/tutorial003_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/users/me") -async def read_user_me(): - return {"user_id": "the current user"} - - -@app.get("/users/{user_id}") -async def read_user(user_id: str): - return {"user_id": user_id} diff --git a/docs_src/path_params/tutorial003b_py39.py b/docs_src/path_params/tutorial003b_py39.py deleted file mode 100644 index 822d37369..000000000 --- a/docs_src/path_params/tutorial003b_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/users") -async def read_users(): - return ["Rick", "Morty"] - - -@app.get("/users") -async def read_users2(): - return ["Bean", "Elfo"] diff --git a/docs_src/path_params/tutorial004_py39.py b/docs_src/path_params/tutorial004_py39.py deleted file mode 100644 index 2961e6178..000000000 --- a/docs_src/path_params/tutorial004_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/files/{file_path:path}") -async def read_file(file_path: str): - return {"file_path": file_path} diff --git a/docs_src/path_params/tutorial005_py39.py b/docs_src/path_params/tutorial005_py39.py deleted file mode 100644 index 9a24a4963..000000000 --- a/docs_src/path_params/tutorial005_py39.py +++ /dev/null @@ -1,23 +0,0 @@ -from enum import Enum - -from fastapi import FastAPI - - -class ModelName(str, Enum): - alexnet = "alexnet" - resnet = "resnet" - lenet = "lenet" - - -app = FastAPI() - - -@app.get("/models/{model_name}") -async def get_model(model_name: ModelName): - if model_name is ModelName.alexnet: - return {"model_name": model_name, "message": "Deep Learning FTW!"} - - if model_name.value == "lenet": - return {"model_name": model_name, "message": "LeCNN all the images"} - - return {"model_name": model_name, "message": "Have some residuals"} diff --git a/docs_src/path_params_numeric_validations/tutorial002_an_py39.py b/docs_src/path_params_numeric_validations/tutorial002_an_py39.py deleted file mode 100644 index cd882abb2..000000000 --- a/docs_src/path_params_numeric_validations/tutorial002_an_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Path - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - q: str, item_id: Annotated[int, Path(title="The ID of the item to get")] -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial002_py39.py b/docs_src/path_params_numeric_validations/tutorial002_py39.py deleted file mode 100644 index 63ac691a8..000000000 --- a/docs_src/path_params_numeric_validations/tutorial002_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Path - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items(q: str, item_id: int = Path(title="The ID of the item to get")): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial003_an_py39.py b/docs_src/path_params_numeric_validations/tutorial003_an_py39.py deleted file mode 100644 index 1588556e7..000000000 --- a/docs_src/path_params_numeric_validations/tutorial003_an_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Path - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - item_id: Annotated[int, Path(title="The ID of the item to get")], q: str -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial003_py39.py b/docs_src/path_params_numeric_validations/tutorial003_py39.py deleted file mode 100644 index 8df0ffc62..000000000 --- a/docs_src/path_params_numeric_validations/tutorial003_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Path - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial004_an_py39.py b/docs_src/path_params_numeric_validations/tutorial004_an_py39.py deleted file mode 100644 index f67f6450e..000000000 --- a/docs_src/path_params_numeric_validations/tutorial004_an_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Path - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)], q: str -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial004_py39.py b/docs_src/path_params_numeric_validations/tutorial004_py39.py deleted file mode 100644 index 86651d47c..000000000 --- a/docs_src/path_params_numeric_validations/tutorial004_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import FastAPI, Path - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - *, item_id: int = Path(title="The ID of the item to get", ge=1), q: str -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial005_an_py39.py b/docs_src/path_params_numeric_validations/tutorial005_an_py39.py deleted file mode 100644 index 571dd583c..000000000 --- a/docs_src/path_params_numeric_validations/tutorial005_an_py39.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Path - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - item_id: Annotated[int, Path(title="The ID of the item to get", gt=0, le=1000)], - q: str, -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial005_py39.py b/docs_src/path_params_numeric_validations/tutorial005_py39.py deleted file mode 100644 index 8f12f2da0..000000000 --- a/docs_src/path_params_numeric_validations/tutorial005_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from fastapi import FastAPI, Path - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - *, - item_id: int = Path(title="The ID of the item to get", gt=0, le=1000), - q: str, -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial006_an_py39.py b/docs_src/path_params_numeric_validations/tutorial006_an_py39.py deleted file mode 100644 index 426ec3776..000000000 --- a/docs_src/path_params_numeric_validations/tutorial006_an_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Path, Query - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - *, - item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)], - q: str, - size: Annotated[float, Query(gt=0, lt=10.5)], -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - if size: - results.update({"size": size}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial006_py39.py b/docs_src/path_params_numeric_validations/tutorial006_py39.py deleted file mode 100644 index f07629aa0..000000000 --- a/docs_src/path_params_numeric_validations/tutorial006_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import FastAPI, Path, Query - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - *, - item_id: int = Path(title="The ID of the item to get", ge=0, le=1000), - q: str, - size: float = Query(gt=0, lt=10.5), -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - if size: - results.update({"size": size}) - return results diff --git a/docs_src/python_types/tutorial001_py39.py b/docs_src/python_types/tutorial001_py39.py deleted file mode 100644 index 09039435f..000000000 --- a/docs_src/python_types/tutorial001_py39.py +++ /dev/null @@ -1,6 +0,0 @@ -def get_full_name(first_name, last_name): - full_name = first_name.title() + " " + last_name.title() - return full_name - - -print(get_full_name("john", "doe")) diff --git a/docs_src/python_types/tutorial002_py39.py b/docs_src/python_types/tutorial002_py39.py deleted file mode 100644 index c0857a116..000000000 --- a/docs_src/python_types/tutorial002_py39.py +++ /dev/null @@ -1,6 +0,0 @@ -def get_full_name(first_name: str, last_name: str): - full_name = first_name.title() + " " + last_name.title() - return full_name - - -print(get_full_name("john", "doe")) diff --git a/docs_src/python_types/tutorial003_py39.py b/docs_src/python_types/tutorial003_py39.py deleted file mode 100644 index d021d8211..000000000 --- a/docs_src/python_types/tutorial003_py39.py +++ /dev/null @@ -1,3 +0,0 @@ -def get_name_with_age(name: str, age: int): - name_with_age = name + " is this old: " + age - return name_with_age diff --git a/docs_src/python_types/tutorial004_py39.py b/docs_src/python_types/tutorial004_py39.py deleted file mode 100644 index 9400269e2..000000000 --- a/docs_src/python_types/tutorial004_py39.py +++ /dev/null @@ -1,3 +0,0 @@ -def get_name_with_age(name: str, age: int): - name_with_age = name + " is this old: " + str(age) - return name_with_age diff --git a/docs_src/python_types/tutorial006_py39.py b/docs_src/python_types/tutorial006_py39.py deleted file mode 100644 index 486b67caf..000000000 --- a/docs_src/python_types/tutorial006_py39.py +++ /dev/null @@ -1,3 +0,0 @@ -def process_items(items: list[str]): - for item in items: - print(item) diff --git a/docs_src/python_types/tutorial007_py39.py b/docs_src/python_types/tutorial007_py39.py deleted file mode 100644 index ea96c7964..000000000 --- a/docs_src/python_types/tutorial007_py39.py +++ /dev/null @@ -1,2 +0,0 @@ -def process_items(items_t: tuple[int, int, str], items_s: set[bytes]): - return items_t, items_s diff --git a/docs_src/python_types/tutorial008_py39.py b/docs_src/python_types/tutorial008_py39.py deleted file mode 100644 index a393385b0..000000000 --- a/docs_src/python_types/tutorial008_py39.py +++ /dev/null @@ -1,4 +0,0 @@ -def process_items(prices: dict[str, float]): - for item_name, item_price in prices.items(): - print(item_name) - print(item_price) diff --git a/docs_src/python_types/tutorial008b_py39.py b/docs_src/python_types/tutorial008b_py39.py deleted file mode 100644 index e52539ead..000000000 --- a/docs_src/python_types/tutorial008b_py39.py +++ /dev/null @@ -1,5 +0,0 @@ -from typing import Union - - -def process_item(item: Union[int, str]): - print(item) diff --git a/docs_src/python_types/tutorial009_py39.py b/docs_src/python_types/tutorial009_py39.py deleted file mode 100644 index 6328a1495..000000000 --- a/docs_src/python_types/tutorial009_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from typing import Optional - - -def say_hi(name: Optional[str] = None): - if name is not None: - print(f"Hey {name}!") - else: - print("Hello World") diff --git a/docs_src/python_types/tutorial009b_py39.py b/docs_src/python_types/tutorial009b_py39.py deleted file mode 100644 index 9f1a05bc0..000000000 --- a/docs_src/python_types/tutorial009b_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from typing import Union - - -def say_hi(name: Union[str, None] = None): - if name is not None: - print(f"Hey {name}!") - else: - print("Hello World") diff --git a/docs_src/python_types/tutorial009c_py310.py b/docs_src/python_types/tutorial009c_py310.py deleted file mode 100644 index 96b1220fc..000000000 --- a/docs_src/python_types/tutorial009c_py310.py +++ /dev/null @@ -1,2 +0,0 @@ -def say_hi(name: str | None): - print(f"Hey {name}!") diff --git a/docs_src/python_types/tutorial009c_py39.py b/docs_src/python_types/tutorial009c_py39.py deleted file mode 100644 index 2f539a34b..000000000 --- a/docs_src/python_types/tutorial009c_py39.py +++ /dev/null @@ -1,5 +0,0 @@ -from typing import Optional - - -def say_hi(name: Optional[str]): - print(f"Hey {name}!") diff --git a/docs_src/python_types/tutorial010_py39.py b/docs_src/python_types/tutorial010_py39.py deleted file mode 100644 index 468cffc2d..000000000 --- a/docs_src/python_types/tutorial010_py39.py +++ /dev/null @@ -1,7 +0,0 @@ -class Person: - def __init__(self, name: str): - self.name = name - - -def get_person_name(one_person: Person): - return one_person.name diff --git a/docs_src/python_types/tutorial013_py39.py b/docs_src/python_types/tutorial013_py39.py deleted file mode 100644 index 65a0eaa93..000000000 --- a/docs_src/python_types/tutorial013_py39.py +++ /dev/null @@ -1,5 +0,0 @@ -from typing import Annotated - - -def say_hello(name: Annotated[str, "this is just metadata"]) -> str: - return f"Hello {name}" diff --git a/docs_src/query_params/tutorial001_py39.py b/docs_src/query_params/tutorial001_py39.py deleted file mode 100644 index 74e1a1760..000000000 --- a/docs_src/query_params/tutorial001_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - -fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] - - -@app.get("/items/") -async def read_item(skip: int = 0, limit: int = 10): - return fake_items_db[skip : skip + limit] diff --git a/docs_src/query_params/tutorial005_py39.py b/docs_src/query_params/tutorial005_py39.py deleted file mode 100644 index e16a40574..000000000 --- a/docs_src/query_params/tutorial005_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_user_item(item_id: str, needy: str): - item = {"item_id": item_id, "needy": needy} - return item diff --git a/docs_src/query_params_str_validations/tutorial005_an_py39.py b/docs_src/query_params_str_validations/tutorial005_an_py39.py deleted file mode 100644 index b1f6046b5..000000000 --- a/docs_src/query_params_str_validations/tutorial005_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial005_py39.py b/docs_src/query_params_str_validations/tutorial005_py39.py deleted file mode 100644 index 8ab42869e..000000000 --- a/docs_src/query_params_str_validations/tutorial005_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: str = Query(default="fixedquery", min_length=3)): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006_an_py39.py b/docs_src/query_params_str_validations/tutorial006_an_py39.py deleted file mode 100644 index 3b4a676d2..000000000 --- a/docs_src/query_params_str_validations/tutorial006_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)]): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006_py39.py b/docs_src/query_params_str_validations/tutorial006_py39.py deleted file mode 100644 index 9a90eb64e..000000000 --- a/docs_src/query_params_str_validations/tutorial006_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: str = Query(min_length=3)): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial012_an_py39.py b/docs_src/query_params_str_validations/tutorial012_an_py39.py deleted file mode 100644 index 9b5a9c2fb..000000000 --- a/docs_src/query_params_str_validations/tutorial012_an_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]): - query_items = {"q": q} - return query_items diff --git a/docs_src/query_params_str_validations/tutorial012_py39.py b/docs_src/query_params_str_validations/tutorial012_py39.py deleted file mode 100644 index 070d0b04b..000000000 --- a/docs_src/query_params_str_validations/tutorial012_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: list[str] = Query(default=["foo", "bar"])): - query_items = {"q": q} - return query_items diff --git a/docs_src/query_params_str_validations/tutorial013_an_py39.py b/docs_src/query_params_str_validations/tutorial013_an_py39.py deleted file mode 100644 index 602734145..000000000 --- a/docs_src/query_params_str_validations/tutorial013_an_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[list, Query()] = []): - query_items = {"q": q} - return query_items diff --git a/docs_src/query_params_str_validations/tutorial013_py39.py b/docs_src/query_params_str_validations/tutorial013_py39.py deleted file mode 100644 index 0b0f44869..000000000 --- a/docs_src/query_params_str_validations/tutorial013_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: list = Query(default=[])): - query_items = {"q": q} - return query_items diff --git a/docs_src/request_files/tutorial001_03_an_py39.py b/docs_src/request_files/tutorial001_03_an_py39.py deleted file mode 100644 index 93098a677..000000000 --- a/docs_src/request_files/tutorial001_03_an_py39.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, File, UploadFile - -app = FastAPI() - - -@app.post("/files/") -async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]): - return {"file_size": len(file)} - - -@app.post("/uploadfile/") -async def create_upload_file( - file: Annotated[UploadFile, File(description="A file read as UploadFile")], -): - return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001_03_py39.py b/docs_src/request_files/tutorial001_03_py39.py deleted file mode 100644 index d8005cc7d..000000000 --- a/docs_src/request_files/tutorial001_03_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from fastapi import FastAPI, File, UploadFile - -app = FastAPI() - - -@app.post("/files/") -async def create_file(file: bytes = File(description="A file read as bytes")): - return {"file_size": len(file)} - - -@app.post("/uploadfile/") -async def create_upload_file( - file: UploadFile = File(description="A file read as UploadFile"), -): - return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001_an_py39.py b/docs_src/request_files/tutorial001_an_py39.py deleted file mode 100644 index 26a767221..000000000 --- a/docs_src/request_files/tutorial001_an_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, File, UploadFile - -app = FastAPI() - - -@app.post("/files/") -async def create_file(file: Annotated[bytes, File()]): - return {"file_size": len(file)} - - -@app.post("/uploadfile/") -async def create_upload_file(file: UploadFile): - return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001_py39.py b/docs_src/request_files/tutorial001_py39.py deleted file mode 100644 index 2e0ea6391..000000000 --- a/docs_src/request_files/tutorial001_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import FastAPI, File, UploadFile - -app = FastAPI() - - -@app.post("/files/") -async def create_file(file: bytes = File()): - return {"file_size": len(file)} - - -@app.post("/uploadfile/") -async def create_upload_file(file: UploadFile): - return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial002_an_py39.py b/docs_src/request_files/tutorial002_an_py39.py deleted file mode 100644 index db524ceab..000000000 --- a/docs_src/request_files/tutorial002_an_py39.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, File, UploadFile -from fastapi.responses import HTMLResponse - -app = FastAPI() - - -@app.post("/files/") -async def create_files(files: Annotated[list[bytes], File()]): - return {"file_sizes": [len(file) for file in files]} - - -@app.post("/uploadfiles/") -async def create_upload_files(files: list[UploadFile]): - return {"filenames": [file.filename for file in files]} - - -@app.get("/") -async def main(): - content = """ - -
- - -
-
- - -
- - """ - return HTMLResponse(content=content) diff --git a/docs_src/request_files/tutorial002_py39.py b/docs_src/request_files/tutorial002_py39.py deleted file mode 100644 index b64cf5598..000000000 --- a/docs_src/request_files/tutorial002_py39.py +++ /dev/null @@ -1,31 +0,0 @@ -from fastapi import FastAPI, File, UploadFile -from fastapi.responses import HTMLResponse - -app = FastAPI() - - -@app.post("/files/") -async def create_files(files: list[bytes] = File()): - return {"file_sizes": [len(file) for file in files]} - - -@app.post("/uploadfiles/") -async def create_upload_files(files: list[UploadFile]): - return {"filenames": [file.filename for file in files]} - - -@app.get("/") -async def main(): - content = """ - -
- - -
-
- - -
- - """ - return HTMLResponse(content=content) diff --git a/docs_src/request_files/tutorial003_an_py39.py b/docs_src/request_files/tutorial003_an_py39.py deleted file mode 100644 index 5a8c5dab5..000000000 --- a/docs_src/request_files/tutorial003_an_py39.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, File, UploadFile -from fastapi.responses import HTMLResponse - -app = FastAPI() - - -@app.post("/files/") -async def create_files( - files: Annotated[list[bytes], File(description="Multiple files as bytes")], -): - return {"file_sizes": [len(file) for file in files]} - - -@app.post("/uploadfiles/") -async def create_upload_files( - files: Annotated[ - list[UploadFile], File(description="Multiple files as UploadFile") - ], -): - return {"filenames": [file.filename for file in files]} - - -@app.get("/") -async def main(): - content = """ - -
- - -
-
- - -
- - """ - return HTMLResponse(content=content) diff --git a/docs_src/request_files/tutorial003_py39.py b/docs_src/request_files/tutorial003_py39.py deleted file mode 100644 index 96f5e8742..000000000 --- a/docs_src/request_files/tutorial003_py39.py +++ /dev/null @@ -1,35 +0,0 @@ -from fastapi import FastAPI, File, UploadFile -from fastapi.responses import HTMLResponse - -app = FastAPI() - - -@app.post("/files/") -async def create_files( - files: list[bytes] = File(description="Multiple files as bytes"), -): - return {"file_sizes": [len(file) for file in files]} - - -@app.post("/uploadfiles/") -async def create_upload_files( - files: list[UploadFile] = File(description="Multiple files as UploadFile"), -): - return {"filenames": [file.filename for file in files]} - - -@app.get("/") -async def main(): - content = """ - -
- - -
-
- - -
- - """ - return HTMLResponse(content=content) diff --git a/docs_src/request_form_models/tutorial001_an_py39.py b/docs_src/request_form_models/tutorial001_an_py39.py deleted file mode 100644 index 7cc81aae9..000000000 --- a/docs_src/request_form_models/tutorial001_an_py39.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Form -from pydantic import BaseModel - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - - -@app.post("/login/") -async def login(data: Annotated[FormData, Form()]): - return data diff --git a/docs_src/request_form_models/tutorial001_py39.py b/docs_src/request_form_models/tutorial001_py39.py deleted file mode 100644 index 98feff0b9..000000000 --- a/docs_src/request_form_models/tutorial001_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI, Form -from pydantic import BaseModel - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - - -@app.post("/login/") -async def login(data: FormData = Form()): - return data diff --git a/docs_src/request_form_models/tutorial002_an_py39.py b/docs_src/request_form_models/tutorial002_an_py39.py deleted file mode 100644 index 3004e0852..000000000 --- a/docs_src/request_form_models/tutorial002_an_py39.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Form -from pydantic import BaseModel - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - model_config = {"extra": "forbid"} - - -@app.post("/login/") -async def login(data: Annotated[FormData, Form()]): - return data diff --git a/docs_src/request_form_models/tutorial002_py39.py b/docs_src/request_form_models/tutorial002_py39.py deleted file mode 100644 index 59b329e8d..000000000 --- a/docs_src/request_form_models/tutorial002_py39.py +++ /dev/null @@ -1,15 +0,0 @@ -from fastapi import FastAPI, Form -from pydantic import BaseModel - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - model_config = {"extra": "forbid"} - - -@app.post("/login/") -async def login(data: FormData = Form()): - return data diff --git a/docs_src/request_forms/tutorial001_an_py39.py b/docs_src/request_forms/tutorial001_an_py39.py deleted file mode 100644 index 8e9d2ea53..000000000 --- a/docs_src/request_forms/tutorial001_an_py39.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Form - -app = FastAPI() - - -@app.post("/login/") -async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]): - return {"username": username} diff --git a/docs_src/request_forms/tutorial001_py39.py b/docs_src/request_forms/tutorial001_py39.py deleted file mode 100644 index a53770001..000000000 --- a/docs_src/request_forms/tutorial001_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI, Form - -app = FastAPI() - - -@app.post("/login/") -async def login(username: str = Form(), password: str = Form()): - return {"username": username} diff --git a/docs_src/request_forms_and_files/tutorial001_an_py39.py b/docs_src/request_forms_and_files/tutorial001_an_py39.py deleted file mode 100644 index 12cc43e50..000000000 --- a/docs_src/request_forms_and_files/tutorial001_an_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, File, Form, UploadFile - -app = FastAPI() - - -@app.post("/files/") -async def create_file( - file: Annotated[bytes, File()], - fileb: Annotated[UploadFile, File()], - token: Annotated[str, Form()], -): - return { - "file_size": len(file), - "token": token, - "fileb_content_type": fileb.content_type, - } diff --git a/docs_src/request_forms_and_files/tutorial001_py39.py b/docs_src/request_forms_and_files/tutorial001_py39.py deleted file mode 100644 index 7b5224ce5..000000000 --- a/docs_src/request_forms_and_files/tutorial001_py39.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI, File, Form, UploadFile - -app = FastAPI() - - -@app.post("/files/") -async def create_file( - file: bytes = File(), fileb: UploadFile = File(), token: str = Form() -): - return { - "file_size": len(file), - "token": token, - "fileb_content_type": fileb.content_type, - } diff --git a/docs_src/response_change_status_code/tutorial001_py39.py b/docs_src/response_change_status_code/tutorial001_py39.py deleted file mode 100644 index 197decbfb..000000000 --- a/docs_src/response_change_status_code/tutorial001_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import FastAPI, Response, status - -app = FastAPI() - -tasks = {"foo": "Listen to the Bar Fighters"} - - -@app.put("/get-or-create-task/{task_id}", status_code=200) -def get_or_create_task(task_id: str, response: Response): - if task_id not in tasks: - tasks[task_id] = "This didn't exist before" - response.status_code = status.HTTP_201_CREATED - return tasks[task_id] diff --git a/docs_src/response_cookies/tutorial001_py39.py b/docs_src/response_cookies/tutorial001_py39.py deleted file mode 100644 index 33f8e8f6e..000000000 --- a/docs_src/response_cookies/tutorial001_py39.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import JSONResponse - -app = FastAPI() - - -@app.post("/cookie/") -def create_cookie(): - content = {"message": "Come to the dark side, we have cookies"} - response = JSONResponse(content=content) - response.set_cookie(key="fakesession", value="fake-cookie-session-value") - return response diff --git a/docs_src/response_cookies/tutorial002_py39.py b/docs_src/response_cookies/tutorial002_py39.py deleted file mode 100644 index 76c06fdb9..000000000 --- a/docs_src/response_cookies/tutorial002_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI, Response - -app = FastAPI() - - -@app.post("/cookie-and-object/") -def create_cookie(response: Response): - response.set_cookie(key="fakesession", value="fake-cookie-session-value") - return {"message": "Come to the dark side, we have cookies"} diff --git a/docs_src/response_directly/tutorial002_py39.py b/docs_src/response_directly/tutorial002_py39.py deleted file mode 100644 index 6643da6e6..000000000 --- a/docs_src/response_directly/tutorial002_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import FastAPI, Response - -app = FastAPI() - - -@app.get("/legacy/") -def get_legacy_data(): - data = """ - -
- Apply shampoo here. -
- - You'll have to use soap here. - -
- """ - return Response(content=data, media_type="application/xml") diff --git a/docs_src/response_headers/tutorial001_py39.py b/docs_src/response_headers/tutorial001_py39.py deleted file mode 100644 index 2da02a470..000000000 --- a/docs_src/response_headers/tutorial001_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import JSONResponse - -app = FastAPI() - - -@app.get("/headers/") -def get_headers(): - content = {"message": "Hello World"} - headers = {"X-Cat-Dog": "alone in the world", "Content-Language": "en-US"} - return JSONResponse(content=content, headers=headers) diff --git a/docs_src/response_headers/tutorial002_py39.py b/docs_src/response_headers/tutorial002_py39.py deleted file mode 100644 index d2c498305..000000000 --- a/docs_src/response_headers/tutorial002_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI, Response - -app = FastAPI() - - -@app.get("/headers-and-object/") -def get_headers(response: Response): - response.headers["X-Cat-Dog"] = "alone in the world" - return {"message": "Hello World"} diff --git a/docs_src/response_model/tutorial003_02_py39.py b/docs_src/response_model/tutorial003_02_py39.py deleted file mode 100644 index df6a09646..000000000 --- a/docs_src/response_model/tutorial003_02_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import FastAPI, Response -from fastapi.responses import JSONResponse, RedirectResponse - -app = FastAPI() - - -@app.get("/portal") -async def get_portal(teleport: bool = False) -> Response: - if teleport: - return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") - return JSONResponse(content={"message": "Here's your interdimensional portal."}) diff --git a/docs_src/response_model/tutorial003_03_py39.py b/docs_src/response_model/tutorial003_03_py39.py deleted file mode 100644 index 0d4bd8de5..000000000 --- a/docs_src/response_model/tutorial003_03_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI -from fastapi.responses import RedirectResponse - -app = FastAPI() - - -@app.get("/teleport") -async def get_teleport() -> RedirectResponse: - return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ") diff --git a/docs_src/response_status_code/tutorial001_py39.py b/docs_src/response_status_code/tutorial001_py39.py deleted file mode 100644 index 14b6d6e67..000000000 --- a/docs_src/response_status_code/tutorial001_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.post("/items/", status_code=201) -async def create_item(name: str): - return {"name": name} diff --git a/docs_src/response_status_code/tutorial002_py39.py b/docs_src/response_status_code/tutorial002_py39.py deleted file mode 100644 index 4fcc9829d..000000000 --- a/docs_src/response_status_code/tutorial002_py39.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI, status - -app = FastAPI() - - -@app.post("/items/", status_code=status.HTTP_201_CREATED) -async def create_item(name: str): - return {"name": name} diff --git a/docs_src/security/tutorial001_an_py39.py b/docs_src/security/tutorial001_an_py39.py deleted file mode 100644 index de110402e..000000000 --- a/docs_src/security/tutorial001_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated - -from fastapi import Depends, FastAPI -from fastapi.security import OAuth2PasswordBearer - -app = FastAPI() - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -@app.get("/items/") -async def read_items(token: Annotated[str, Depends(oauth2_scheme)]): - return {"token": token} diff --git a/docs_src/security/tutorial001_py39.py b/docs_src/security/tutorial001_py39.py deleted file mode 100644 index 224e59602..000000000 --- a/docs_src/security/tutorial001_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import Depends, FastAPI -from fastapi.security import OAuth2PasswordBearer - -app = FastAPI() - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -@app.get("/items/") -async def read_items(token: str = Depends(oauth2_scheme)): - return {"token": token} diff --git a/docs_src/security/tutorial006_an_py39.py b/docs_src/security/tutorial006_an_py39.py deleted file mode 100644 index 03c696a4b..000000000 --- a/docs_src/security/tutorial006_an_py39.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Annotated - -from fastapi import Depends, FastAPI -from fastapi.security import HTTPBasic, HTTPBasicCredentials - -app = FastAPI() - -security = HTTPBasic() - - -@app.get("/users/me") -def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]): - return {"username": credentials.username, "password": credentials.password} diff --git a/docs_src/security/tutorial006_py39.py b/docs_src/security/tutorial006_py39.py deleted file mode 100644 index 29121ffd6..000000000 --- a/docs_src/security/tutorial006_py39.py +++ /dev/null @@ -1,11 +0,0 @@ -from fastapi import Depends, FastAPI -from fastapi.security import HTTPBasic, HTTPBasicCredentials - -app = FastAPI() - -security = HTTPBasic() - - -@app.get("/users/me") -def read_current_user(credentials: HTTPBasicCredentials = Depends(security)): - return {"username": credentials.username, "password": credentials.password} diff --git a/docs_src/security/tutorial007_an_py39.py b/docs_src/security/tutorial007_an_py39.py deleted file mode 100644 index 87ef98657..000000000 --- a/docs_src/security/tutorial007_an_py39.py +++ /dev/null @@ -1,36 +0,0 @@ -import secrets -from typing import Annotated - -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import HTTPBasic, HTTPBasicCredentials - -app = FastAPI() - -security = HTTPBasic() - - -def get_current_username( - credentials: Annotated[HTTPBasicCredentials, Depends(security)], -): - current_username_bytes = credentials.username.encode("utf8") - correct_username_bytes = b"stanleyjobson" - is_correct_username = secrets.compare_digest( - current_username_bytes, correct_username_bytes - ) - current_password_bytes = credentials.password.encode("utf8") - correct_password_bytes = b"swordfish" - is_correct_password = secrets.compare_digest( - current_password_bytes, correct_password_bytes - ) - if not (is_correct_username and is_correct_password): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Basic"}, - ) - return credentials.username - - -@app.get("/users/me") -def read_current_user(username: Annotated[str, Depends(get_current_username)]): - return {"username": username} diff --git a/docs_src/security/tutorial007_py39.py b/docs_src/security/tutorial007_py39.py deleted file mode 100644 index ac816eb0c..000000000 --- a/docs_src/security/tutorial007_py39.py +++ /dev/null @@ -1,33 +0,0 @@ -import secrets - -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import HTTPBasic, HTTPBasicCredentials - -app = FastAPI() - -security = HTTPBasic() - - -def get_current_username(credentials: HTTPBasicCredentials = Depends(security)): - current_username_bytes = credentials.username.encode("utf8") - correct_username_bytes = b"stanleyjobson" - is_correct_username = secrets.compare_digest( - current_username_bytes, correct_username_bytes - ) - current_password_bytes = credentials.password.encode("utf8") - correct_password_bytes = b"swordfish" - is_correct_password = secrets.compare_digest( - current_password_bytes, correct_password_bytes - ) - if not (is_correct_username and is_correct_password): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Basic"}, - ) - return credentials.username - - -@app.get("/users/me") -def read_current_user(username: str = Depends(get_current_username)): - return {"username": username} diff --git a/docs_src/settings/app01_py39/__init__.py b/docs_src/settings/app01_py39/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/settings/app01_py39/config.py b/docs_src/settings/app01_py39/config.py deleted file mode 100644 index b31b8811d..000000000 --- a/docs_src/settings/app01_py39/config.py +++ /dev/null @@ -1,10 +0,0 @@ -from pydantic_settings import BaseSettings - - -class Settings(BaseSettings): - app_name: str = "Awesome API" - admin_email: str - items_per_user: int = 50 - - -settings = Settings() diff --git a/docs_src/settings/app01_py39/main.py b/docs_src/settings/app01_py39/main.py deleted file mode 100644 index 4a3a86ce2..000000000 --- a/docs_src/settings/app01_py39/main.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI - -from .config import settings - -app = FastAPI() - - -@app.get("/info") -async def info(): - return { - "app_name": settings.app_name, - "admin_email": settings.admin_email, - "items_per_user": settings.items_per_user, - } diff --git a/docs_src/settings/app02_an_py39/__init__.py b/docs_src/settings/app02_an_py39/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/settings/app02_an_py39/config.py b/docs_src/settings/app02_an_py39/config.py deleted file mode 100644 index e17b5035d..000000000 --- a/docs_src/settings/app02_an_py39/config.py +++ /dev/null @@ -1,7 +0,0 @@ -from pydantic_settings import BaseSettings - - -class Settings(BaseSettings): - app_name: str = "Awesome API" - admin_email: str - items_per_user: int = 50 diff --git a/docs_src/settings/app02_an_py39/main.py b/docs_src/settings/app02_an_py39/main.py deleted file mode 100644 index 6d5db12a8..000000000 --- a/docs_src/settings/app02_an_py39/main.py +++ /dev/null @@ -1,22 +0,0 @@ -from functools import lru_cache -from typing import Annotated - -from fastapi import Depends, FastAPI - -from .config import Settings - -app = FastAPI() - - -@lru_cache -def get_settings(): - return Settings() - - -@app.get("/info") -async def info(settings: Annotated[Settings, Depends(get_settings)]): - return { - "app_name": settings.app_name, - "admin_email": settings.admin_email, - "items_per_user": settings.items_per_user, - } diff --git a/docs_src/settings/app02_an_py39/test_main.py b/docs_src/settings/app02_an_py39/test_main.py deleted file mode 100644 index 7a04d7e8e..000000000 --- a/docs_src/settings/app02_an_py39/test_main.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi.testclient import TestClient - -from .config import Settings -from .main import app, get_settings - -client = TestClient(app) - - -def get_settings_override(): - return Settings(admin_email="testing_admin@example.com") - - -app.dependency_overrides[get_settings] = get_settings_override - - -def test_app(): - response = client.get("/info") - data = response.json() - assert data == { - "app_name": "Awesome API", - "admin_email": "testing_admin@example.com", - "items_per_user": 50, - } diff --git a/docs_src/settings/app02_py39/__init__.py b/docs_src/settings/app02_py39/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/settings/app02_py39/config.py b/docs_src/settings/app02_py39/config.py deleted file mode 100644 index e17b5035d..000000000 --- a/docs_src/settings/app02_py39/config.py +++ /dev/null @@ -1,7 +0,0 @@ -from pydantic_settings import BaseSettings - - -class Settings(BaseSettings): - app_name: str = "Awesome API" - admin_email: str - items_per_user: int = 50 diff --git a/docs_src/settings/app02_py39/main.py b/docs_src/settings/app02_py39/main.py deleted file mode 100644 index 941f82e6b..000000000 --- a/docs_src/settings/app02_py39/main.py +++ /dev/null @@ -1,21 +0,0 @@ -from functools import lru_cache - -from fastapi import Depends, FastAPI - -from .config import Settings - -app = FastAPI() - - -@lru_cache -def get_settings(): - return Settings() - - -@app.get("/info") -async def info(settings: Settings = Depends(get_settings)): - return { - "app_name": settings.app_name, - "admin_email": settings.admin_email, - "items_per_user": settings.items_per_user, - } diff --git a/docs_src/settings/app02_py39/test_main.py b/docs_src/settings/app02_py39/test_main.py deleted file mode 100644 index 7a04d7e8e..000000000 --- a/docs_src/settings/app02_py39/test_main.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi.testclient import TestClient - -from .config import Settings -from .main import app, get_settings - -client = TestClient(app) - - -def get_settings_override(): - return Settings(admin_email="testing_admin@example.com") - - -app.dependency_overrides[get_settings] = get_settings_override - - -def test_app(): - response = client.get("/info") - data = response.json() - assert data == { - "app_name": "Awesome API", - "admin_email": "testing_admin@example.com", - "items_per_user": 50, - } diff --git a/docs_src/settings/app03_an_py39/__init__.py b/docs_src/settings/app03_an_py39/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/settings/app03_an_py39/config.py b/docs_src/settings/app03_an_py39/config.py deleted file mode 100644 index 08f8f88c2..000000000 --- a/docs_src/settings/app03_an_py39/config.py +++ /dev/null @@ -1,9 +0,0 @@ -from pydantic_settings import BaseSettings, SettingsConfigDict - - -class Settings(BaseSettings): - app_name: str = "Awesome API" - admin_email: str - items_per_user: int = 50 - - model_config = SettingsConfigDict(env_file=".env") diff --git a/docs_src/settings/app03_an_py39/main.py b/docs_src/settings/app03_an_py39/main.py deleted file mode 100644 index 2f64b9cd1..000000000 --- a/docs_src/settings/app03_an_py39/main.py +++ /dev/null @@ -1,22 +0,0 @@ -from functools import lru_cache -from typing import Annotated - -from fastapi import Depends, FastAPI - -from . import config - -app = FastAPI() - - -@lru_cache -def get_settings(): - return config.Settings() - - -@app.get("/info") -async def info(settings: Annotated[config.Settings, Depends(get_settings)]): - return { - "app_name": settings.app_name, - "admin_email": settings.admin_email, - "items_per_user": settings.items_per_user, - } diff --git a/docs_src/settings/app03_py39/__init__.py b/docs_src/settings/app03_py39/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs_src/settings/app03_py39/config.py b/docs_src/settings/app03_py39/config.py deleted file mode 100644 index 08f8f88c2..000000000 --- a/docs_src/settings/app03_py39/config.py +++ /dev/null @@ -1,9 +0,0 @@ -from pydantic_settings import BaseSettings, SettingsConfigDict - - -class Settings(BaseSettings): - app_name: str = "Awesome API" - admin_email: str - items_per_user: int = 50 - - model_config = SettingsConfigDict(env_file=".env") diff --git a/docs_src/settings/app03_py39/main.py b/docs_src/settings/app03_py39/main.py deleted file mode 100644 index ea64a5709..000000000 --- a/docs_src/settings/app03_py39/main.py +++ /dev/null @@ -1,21 +0,0 @@ -from functools import lru_cache - -from fastapi import Depends, FastAPI - -from . import config - -app = FastAPI() - - -@lru_cache -def get_settings(): - return config.Settings() - - -@app.get("/info") -async def info(settings: config.Settings = Depends(get_settings)): - return { - "app_name": settings.app_name, - "admin_email": settings.admin_email, - "items_per_user": settings.items_per_user, - } diff --git a/docs_src/settings/tutorial001_py39.py b/docs_src/settings/tutorial001_py39.py deleted file mode 100644 index d48c4c060..000000000 --- a/docs_src/settings/tutorial001_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from fastapi import FastAPI -from pydantic_settings import BaseSettings - - -class Settings(BaseSettings): - app_name: str = "Awesome API" - admin_email: str - items_per_user: int = 50 - - -settings = Settings() -app = FastAPI() - - -@app.get("/info") -async def info(): - return { - "app_name": settings.app_name, - "admin_email": settings.admin_email, - "items_per_user": settings.items_per_user, - } diff --git a/docs_src/static_files/tutorial001_py39.py b/docs_src/static_files/tutorial001_py39.py deleted file mode 100644 index 460352c7e..000000000 --- a/docs_src/static_files/tutorial001_py39.py +++ /dev/null @@ -1,6 +0,0 @@ -from fastapi import FastAPI -from fastapi.staticfiles import StaticFiles - -app = FastAPI() - -app.mount("/static", StaticFiles(directory="static"), name="static") diff --git a/docs_src/sub_applications/tutorial001_py39.py b/docs_src/sub_applications/tutorial001_py39.py deleted file mode 100644 index 57e627e80..000000000 --- a/docs_src/sub_applications/tutorial001_py39.py +++ /dev/null @@ -1,19 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/app") -def read_main(): - return {"message": "Hello World from main app"} - - -subapi = FastAPI() - - -@subapi.get("/sub") -def read_sub(): - return {"message": "Hello World from sub API"} - - -app.mount("/subapi", subapi) diff --git a/docs_src/templates/tutorial001_py39.py b/docs_src/templates/tutorial001_py39.py deleted file mode 100644 index 81ccc8d4d..000000000 --- a/docs_src/templates/tutorial001_py39.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import FastAPI, Request -from fastapi.responses import HTMLResponse -from fastapi.staticfiles import StaticFiles -from fastapi.templating import Jinja2Templates - -app = FastAPI() - -app.mount("/static", StaticFiles(directory="static"), name="static") - - -templates = Jinja2Templates(directory="templates") - - -@app.get("/items/{id}", response_class=HTMLResponse) -async def read_item(request: Request, id: str): - return templates.TemplateResponse( - request=request, name="item.html", context={"id": id} - ) diff --git a/docs_src/using_request_directly/tutorial001_py39.py b/docs_src/using_request_directly/tutorial001_py39.py deleted file mode 100644 index 2d7288b54..000000000 --- a/docs_src/using_request_directly/tutorial001_py39.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI, Request - -app = FastAPI() - - -@app.get("/items/{item_id}") -def read_root(item_id: str, request: Request): - client_host = request.client.host - return {"client_host": client_host, "item_id": item_id} diff --git a/docs_src/websockets/tutorial001_py39.py b/docs_src/websockets/tutorial001_py39.py deleted file mode 100644 index a43a2be17..000000000 --- a/docs_src/websockets/tutorial001_py39.py +++ /dev/null @@ -1,51 +0,0 @@ -from fastapi import FastAPI, WebSocket -from fastapi.responses import HTMLResponse - -app = FastAPI() - -html = """ - - - - Chat - - -

WebSocket Chat

-
- - -
-
    -
- - - -""" - - -@app.get("/") -async def get(): - return HTMLResponse(html) - - -@app.websocket("/ws") -async def websocket_endpoint(websocket: WebSocket): - await websocket.accept() - while True: - data = await websocket.receive_text() - await websocket.send_text(f"Message text was: {data}") diff --git a/docs_src/websockets/tutorial003_py39.py b/docs_src/websockets/tutorial003_py39.py deleted file mode 100644 index 316218088..000000000 --- a/docs_src/websockets/tutorial003_py39.py +++ /dev/null @@ -1,81 +0,0 @@ -from fastapi import FastAPI, WebSocket, WebSocketDisconnect -from fastapi.responses import HTMLResponse - -app = FastAPI() - -html = """ - - - - Chat - - -

WebSocket Chat

-

Your ID:

-
- - -
-
    -
- - - -""" - - -class ConnectionManager: - def __init__(self): - self.active_connections: list[WebSocket] = [] - - async def connect(self, websocket: WebSocket): - await websocket.accept() - self.active_connections.append(websocket) - - def disconnect(self, websocket: WebSocket): - self.active_connections.remove(websocket) - - async def send_personal_message(self, message: str, websocket: WebSocket): - await websocket.send_text(message) - - async def broadcast(self, message: str): - for connection in self.active_connections: - await connection.send_text(message) - - -manager = ConnectionManager() - - -@app.get("/") -async def get(): - return HTMLResponse(html) - - -@app.websocket("/ws/{client_id}") -async def websocket_endpoint(websocket: WebSocket, client_id: int): - await manager.connect(websocket) - try: - while True: - data = await websocket.receive_text() - await manager.send_personal_message(f"You wrote: {data}", websocket) - await manager.broadcast(f"Client #{client_id} says: {data}") - except WebSocketDisconnect: - manager.disconnect(websocket) - await manager.broadcast(f"Client #{client_id} left the chat") diff --git a/docs_src/wsgi/tutorial001_py39.py b/docs_src/wsgi/tutorial001_py39.py deleted file mode 100644 index 8eeceb829..000000000 --- a/docs_src/wsgi/tutorial001_py39.py +++ /dev/null @@ -1,23 +0,0 @@ -from a2wsgi import WSGIMiddleware -from fastapi import FastAPI -from flask import Flask, request -from markupsafe import escape - -flask_app = Flask(__name__) - - -@flask_app.route("/") -def flask_main(): - name = request.args.get("name", "World") - return f"Hello, {escape(name)} from Flask!" - - -app = FastAPI() - - -@app.get("/v2") -def read_main(): - return {"message": "Hello World"} - - -app.mount("/v1", WSGIMiddleware(flask_app)) diff --git a/scripts/tests/test_translation_fixer/test_code_includes/data/en_doc.md b/scripts/tests/test_translation_fixer/test_code_includes/data/en_doc.md index 593da0b32..0e021dade 100644 --- a/scripts/tests/test_translation_fixer/test_code_includes/data/en_doc.md +++ b/scripts/tests/test_translation_fixer/test_code_includes/data/en_doc.md @@ -4,7 +4,7 @@ Some text -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} Some more text diff --git a/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_gt.md b/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_gt.md index c1ad94d27..aca1464ff 100644 --- a/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_gt.md +++ b/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_gt.md @@ -4,7 +4,7 @@ Some text -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} Some more text @@ -12,4 +12,4 @@ Some more text And even more text -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} diff --git a/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_lt.md b/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_lt.md index 07eaf2c23..12573aa19 100644 --- a/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_lt.md +++ b/scripts/tests/test_translation_fixer/test_code_includes/data/translated_doc_number_lt.md @@ -4,7 +4,7 @@ Some text -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} Some more text diff --git a/scripts/tests/test_translation_fixer/test_complex_doc/data/en_doc.md b/scripts/tests/test_translation_fixer/test_complex_doc/data/en_doc.md index 69cd3f3fd..d6c1e3d2f 100644 --- a/scripts/tests/test_translation_fixer/test_complex_doc/data/en_doc.md +++ b/scripts/tests/test_translation_fixer/test_complex_doc/data/en_doc.md @@ -141,16 +141,16 @@ def hello_world(): ## Simple code includes { #simple-code-includes } -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} -{* ../../docs_src/python_types/tutorial002_py39.py *} +{* ../../docs_src/python_types/tutorial002_py310.py *} ## Code includes with highlighting { #code-includes-with-highlighting } -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} -{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[10] *} ## Code includes with line ranges { #code-includes-with-line-ranges } @@ -169,19 +169,19 @@ def hello_world(): ## Code includes qith title { #code-includes-with-title } -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} ## Code includes with unknown attributes { #code-includes-with-unknown-attributes } -{* ../../docs_src/python_types/tutorial001_py39.py unknown[123] *} +{* ../../docs_src/python_types/tutorial001_py310.py unknown[123] *} ## Some more code includes to test fixing { #some-more-code-includes-to-test-fixing } {* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} {* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} diff --git a/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc.md b/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc.md index c922d7b13..b27eef202 100644 --- a/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc.md +++ b/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc.md @@ -139,16 +139,16 @@ def hello_world(): ## Простые включения кода { #simple-code-includes } -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} -{* ../../docs_src/python_types/tutorial002_py39.py *} +{* ../../docs_src/python_types/tutorial002_py310.py *} ## Включения кода с подсветкой { #code-includes-with-highlighting } -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} -{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[10] *} ## Включения кода с диапазонами строк { #code-includes-with-line-ranges } @@ -167,19 +167,19 @@ def hello_world(): ## Включения кода с заголовком { #code-includes-with-title } -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} ## Включения кода с неизвестными атрибутами { #code-includes-with-unknown-attributes } -{* ../../docs_src/python_types/tutorial001_py39.py unknown[123] *} +{* ../../docs_src/python_types/tutorial001_py310.py unknown[123] *} ## Ещё включения кода для тестирования исправления { #some-more-code-includes-to-test-fixing } {* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19 : 21] *} -{* ../../docs_src/bigger_applications/app_an_py39/wrong.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/wrong.py hl[3] title["app/internal/admin.py"] *} {* ../../docs_src/dependencies/tutorial013_an_py310.py ln[1:30] hl[1:10] *} diff --git a/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc_expected.md b/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc_expected.md index b33f36e77..8e2394f14 100644 --- a/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc_expected.md +++ b/scripts/tests/test_translation_fixer/test_complex_doc/data/translated_doc_expected.md @@ -139,16 +139,16 @@ def hello_world(): ## Простые включения кода { #simple-code-includes } -{* ../../docs_src/python_types/tutorial001_py39.py *} +{* ../../docs_src/python_types/tutorial001_py310.py *} -{* ../../docs_src/python_types/tutorial002_py39.py *} +{* ../../docs_src/python_types/tutorial002_py310.py *} ## Включения кода с подсветкой { #code-includes-with-highlighting } -{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py310.py hl[1] *} -{* ../../docs_src/python_types/tutorial006_py39.py hl[10] *} +{* ../../docs_src/python_types/tutorial006_py310.py hl[10] *} ## Включения кода с диапазонами строк { #code-includes-with-line-ranges } @@ -167,19 +167,19 @@ def hello_world(): ## Включения кода с заголовком { #code-includes-with-title } -{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/routers/users.py hl[1,3] title["app/routers/users.py"] *} -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} ## Включения кода с неизвестными атрибутами { #code-includes-with-unknown-attributes } -{* ../../docs_src/python_types/tutorial001_py39.py unknown[123] *} +{* ../../docs_src/python_types/tutorial001_py310.py unknown[123] *} ## Ещё включения кода для тестирования исправления { #some-more-code-includes-to-test-fixing } {* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} -{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} +{* ../../docs_src/bigger_applications/app_an_py310/internal/admin.py hl[3] title["app/internal/admin.py"] *} {* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} diff --git a/tests/test_tutorial/test_python_types/test_tutorial009c.py b/tests/test_tutorial/test_python_types/test_tutorial009c.py deleted file mode 100644 index 17c4b9e0c..000000000 --- a/tests/test_tutorial/test_python_types/test_tutorial009c.py +++ /dev/null @@ -1,33 +0,0 @@ -import importlib -import re -from types import ModuleType -from unittest.mock import patch - -import pytest - -from ...utils import needs_py310 - - -@pytest.fixture( - name="module", - params=[ - pytest.param("tutorial009c_py310"), - pytest.param("tutorial009c_py310", marks=needs_py310), - ], -) -def get_module(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.python_types.{request.param}") - return mod - - -def test_say_hi(module: ModuleType): - with patch("builtins.print") as mock_print: - module.say_hi("FastAPI") - - mock_print.assert_called_once_with("Hey FastAPI!") - - with pytest.raises( - TypeError, - match=re.escape("say_hi() missing 1 required positional argument: 'name'"), - ): - module.say_hi() From 6e5680c7ea3edbb092518832f16b5203c618c557 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Feb 2026 08:08:30 +0000 Subject: [PATCH 141/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5e1435065..1f72f883a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* 🔥 Remove Python 3.9 specific files, no longer needed after updating translations. PR [#14931](https://github.com/fastapi/fastapi/pull/14931) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs for JWT to prevent timing attacks. PR [#14908](https://github.com/fastapi/fastapi/pull/14908) by [@tiangolo](https://github.com/tiangolo). ### Translations From faee822574d3c202123f48eb09b9bd207385335b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 17 Feb 2026 01:59:14 -0800 Subject: [PATCH 142/195] =?UTF-8?q?=F0=9F=8E=A8=20Upgrade=20typing=20synta?= =?UTF-8?q?x=20for=20Python=203.10=20(#14932)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: tiangolo <1326112+tiangolo@users.noreply.github.com> Co-authored-by: github-actions[bot] --- docs_src/extra_models/tutorial003_py310.py | 4 +--- scripts/doc_parsing_utils.py | 22 ++++++++--------- scripts/mkdocs_hooks.py | 8 +++---- scripts/notify_translations.py | 24 +++++++++---------- scripts/people.py | 12 +++++----- tests/main.py | 5 ++-- tests/test_additional_properties_bool.py | 4 +--- ...itional_responses_union_duplicate_anyof.py | 4 +--- tests/test_callable_endpoint.py | 3 +-- tests/test_compat.py | 24 +++++++++---------- tests/test_custom_middleware_exception.py | 3 +-- tests/test_custom_schema_fields.py | 8 +++---- tests/test_dependency_contextvars.py | 6 ++--- tests/test_dependency_overrides.py | 4 +--- tests/test_dependency_paramless.py | 4 ++-- tests/test_enforce_once_required_parameter.py | 6 ++--- tests/test_extra_routes.py | 4 +--- tests/test_filter_pydantic_sub_model_pv2.py | 4 +--- tests/test_form_default.py | 8 +++---- tests/test_forms_single_model.py | 4 ++-- tests/test_infer_param_optionality.py | 6 ++--- tests/test_invalid_sequence_param.py | 4 +--- tests/test_jsonable_encoder.py | 4 ++-- tests/test_openapi_examples.py | 8 +++---- .../test_openapi_query_parameter_extension.py | 4 +--- tests/test_openapi_schema_type.py | 4 +--- ...t_openapi_separate_input_output_schemas.py | 8 +++---- tests/test_optional_file_list.py | 4 +--- tests/test_param_class.py | 4 +--- tests/test_param_include_in_schema.py | 8 +++---- tests/test_pydantic_v1_error.py | 3 +-- ...ataclasses_uuid_stringified_annotations.py | 5 ++-- .../test_body/test_list.py | 8 +++---- .../test_body/test_optional_list.py | 20 +++++++--------- .../test_body/test_optional_str.py | 20 +++++++--------- .../test_body/test_required_str.py | 12 ++++------ .../test_cookie/test_optional_str.py | 18 +++++++------- .../test_file/test_optional.py | 18 +++++++------- .../test_file/test_optional_list.py | 20 +++++++--------- .../test_form/test_optional_list.py | 20 +++++++--------- .../test_form/test_optional_str.py | 18 +++++++------- .../test_header/test_optional_list.py | 20 +++++++--------- .../test_header/test_optional_str.py | 18 +++++++------- .../test_query/test_optional_list.py | 20 +++++++--------- .../test_query/test_optional_str.py | 18 +++++++------- tests/test_required_noneable.py | 8 +++---- ...est_response_model_as_return_annotation.py | 12 ++++------ tests/test_router_events.py | 5 ++-- tests/test_schema_extra_examples.py | 20 +++++++--------- .../test_security_api_key_cookie_optional.py | 4 +--- .../test_security_api_key_header_optional.py | 6 ++--- tests/test_security_api_key_query_optional.py | 6 ++--- tests/test_security_http_base_optional.py | 4 +--- tests/test_security_http_basic_optional.py | 3 +-- tests/test_security_http_bearer_optional.py | 4 +--- tests/test_security_http_digest_optional.py | 4 +--- ...curity_oauth2_authorization_code_bearer.py | 4 +--- ...2_authorization_code_bearer_description.py | 4 +--- ...uthorization_code_bearer_scopes_openapi.py | 6 ++--- tests/test_security_oauth2_optional.py | 6 ++--- ...st_security_oauth2_optional_description.py | 6 ++--- ...ecurity_oauth2_password_bearer_optional.py | 4 +--- ...h2_password_bearer_optional_description.py | 4 +--- .../test_security_openid_connect_optional.py | 6 ++--- tests/test_serialize_response.py | 6 ++--- tests/test_serialize_response_dataclass.py | 5 ++-- tests/test_serialize_response_model.py | 6 ++--- tests/test_skip_defaults.py | 12 ++++------ tests/test_sub_callbacks.py | 6 ++--- tests/test_union_body.py | 6 ++--- tests/test_union_body_discriminator.py | 5 ++-- ...test_union_body_discriminator_annotated.py | 4 ++-- tests/test_union_forms.py | 4 ++-- tests/test_union_inherited_body.py | 6 ++--- tests/test_validate_response.py | 8 +++---- tests/test_validate_response_dataclass.py | 6 ++--- tests/utils.py | 1 - 77 files changed, 269 insertions(+), 375 deletions(-) diff --git a/docs_src/extra_models/tutorial003_py310.py b/docs_src/extra_models/tutorial003_py310.py index 06675cbc0..8fe6f7136 100644 --- a/docs_src/extra_models/tutorial003_py310.py +++ b/docs_src/extra_models/tutorial003_py310.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import FastAPI from pydantic import BaseModel @@ -30,6 +28,6 @@ items = { } -@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem]) +@app.get("/items/{item_id}", response_model=PlaneItem | CarItem) async def read_item(item_id: str): return items[item_id] diff --git a/scripts/doc_parsing_utils.py b/scripts/doc_parsing_utils.py index 79f2e9ec0..1cd2299e6 100644 --- a/scripts/doc_parsing_utils.py +++ b/scripts/doc_parsing_utils.py @@ -1,5 +1,5 @@ import re -from typing import TypedDict, Union +from typing import TypedDict CODE_INCLUDE_RE = re.compile(r"^\{\*\s*(\S+)\s*(.*)\*\}$") CODE_INCLUDE_PLACEHOLDER = "" @@ -50,8 +50,8 @@ class MarkdownLinkInfo(TypedDict): line_no: int url: str text: str - title: Union[str, None] - attributes: Union[str, None] + title: str | None + attributes: str | None full_match: str @@ -287,8 +287,8 @@ def _add_lang_code_to_url(url: str, lang_code: str) -> str: def _construct_markdown_link( url: str, text: str, - title: Union[str, None], - attributes: Union[str, None], + title: str | None, + attributes: str | None, lang_code: str, ) -> str: """ @@ -549,7 +549,7 @@ def extract_multiline_code_blocks(text: list[str]) -> list[MultilineCodeBlockInf return blocks -def _split_hash_comment(line: str) -> tuple[str, Union[str, None]]: +def _split_hash_comment(line: str) -> tuple[str, str | None]: match = HASH_COMMENT_RE.match(line) if match: code = match.group("code").rstrip() @@ -558,7 +558,7 @@ def _split_hash_comment(line: str) -> tuple[str, Union[str, None]]: return line.rstrip(), None -def _split_slashes_comment(line: str) -> tuple[str, Union[str, None]]: +def _split_slashes_comment(line: str) -> tuple[str, str | None]: match = SLASHES_COMMENT_RE.match(line) if match: code = match.group("code").rstrip() @@ -603,9 +603,9 @@ def replace_multiline_code_block( return block_a["content"].copy() # We don't handle mermaid code blocks for now code_block: list[str] = [] - for line_a, line_b in zip(block_a["content"], block_b["content"]): - line_a_comment: Union[str, None] = None - line_b_comment: Union[str, None] = None + for line_a, line_b in zip(block_a["content"], block_b["content"], strict=False): + line_a_comment: str | None = None + line_b_comment: str | None = None # Handle comments based on language if block_language in { @@ -659,7 +659,7 @@ def replace_multiline_code_blocks_in_text( ) modified_text = text.copy() - for block, original_block in zip(code_blocks, original_code_blocks): + for block, original_block in zip(code_blocks, original_code_blocks, strict=True): updated_content = replace_multiline_code_block(block, original_block) start_line_index = block["start_line_no"] - 1 diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py index 567c0111d..97006953d 100644 --- a/scripts/mkdocs_hooks.py +++ b/scripts/mkdocs_hooks.py @@ -1,6 +1,6 @@ from functools import lru_cache from pathlib import Path -from typing import Any, Union +from typing import Any import material from mkdocs.config.defaults import MkDocsConfig @@ -105,9 +105,9 @@ def on_files(files: Files, *, config: MkDocsConfig) -> Files: def generate_renamed_section_items( - items: list[Union[Page, Section, Link]], *, config: MkDocsConfig -) -> list[Union[Page, Section, Link]]: - new_items: list[Union[Page, Section, Link]] = [] + items: list[Page | Section | Link], *, config: MkDocsConfig +) -> list[Page | Section | Link]: + new_items: list[Page | Section | Link] = [] for item in items: if isinstance(item, Section): new_title = item.title diff --git a/scripts/notify_translations.py b/scripts/notify_translations.py index 74cdf0dff..3484b69c7 100644 --- a/scripts/notify_translations.py +++ b/scripts/notify_translations.py @@ -3,7 +3,7 @@ import random import sys import time from pathlib import Path -from typing import Any, Union, cast +from typing import Any, cast import httpx from github import Github @@ -181,9 +181,9 @@ class Settings(BaseSettings): github_repository: str github_token: SecretStr github_event_path: Path - github_event_name: Union[str, None] = None + github_event_name: str | None = None httpx_timeout: int = 30 - debug: Union[bool, None] = False + debug: bool | None = False number: int | None = None @@ -199,12 +199,12 @@ def get_graphql_response( *, settings: Settings, query: str, - after: Union[str, None] = None, - category_id: Union[str, None] = None, - discussion_number: Union[int, None] = None, - discussion_id: Union[str, None] = None, - comment_id: Union[str, None] = None, - body: Union[str, None] = None, + after: str | None = None, + category_id: str | None = None, + discussion_number: int | None = None, + discussion_id: str | None = None, + comment_id: str | None = None, + body: str | None = None, ) -> dict[str, Any]: headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"} variables = { @@ -249,7 +249,7 @@ def get_graphql_translation_discussions( def get_graphql_translation_discussion_comments_edges( - *, settings: Settings, discussion_number: int, after: Union[str, None] = None + *, settings: Settings, discussion_number: int, after: str | None = None ) -> list[CommentsEdge]: data = get_graphql_response( settings=settings, @@ -372,8 +372,8 @@ def main() -> None: f"Found a translation discussion for language: {lang} in discussion: #{discussion.number}" ) - already_notified_comment: Union[Comment, None] = None - already_done_comment: Union[Comment, None] = None + already_notified_comment: Comment | None = None + already_done_comment: Comment | None = None logging.info( f"Checking current comments in discussion: #{discussion.number} to see if already notified about this PR: #{pr.number}" diff --git a/scripts/people.py b/scripts/people.py index 207ab4649..f3254ab60 100644 --- a/scripts/people.py +++ b/scripts/people.py @@ -6,7 +6,7 @@ from collections import Counter from collections.abc import Container from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Any, Union +from typing import Any import httpx import yaml @@ -70,7 +70,7 @@ class Author(BaseModel): class CommentsNode(BaseModel): createdAt: datetime - author: Union[Author, None] = None + author: Author | None = None class Replies(BaseModel): @@ -89,7 +89,7 @@ class DiscussionsComments(BaseModel): class DiscussionsNode(BaseModel): number: int - author: Union[Author, None] = None + author: Author | None = None title: str | None = None createdAt: datetime comments: DiscussionsComments @@ -127,8 +127,8 @@ def get_graphql_response( *, settings: Settings, query: str, - after: Union[str, None] = None, - category_id: Union[str, None] = None, + after: str | None = None, + category_id: str | None = None, ) -> dict[str, Any]: headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"} variables = {"after": after, "category_id": category_id} @@ -156,7 +156,7 @@ def get_graphql_response( def get_graphql_question_discussion_edges( *, settings: Settings, - after: Union[str, None] = None, + after: str | None = None, ) -> list[DiscussionsEdge]: data = get_graphql_response( settings=settings, diff --git a/tests/main.py b/tests/main.py index 7edb16c61..d2fbbe615 100644 --- a/tests/main.py +++ b/tests/main.py @@ -1,5 +1,4 @@ import http -from typing import Optional from fastapi import FastAPI, Path, Query @@ -54,7 +53,7 @@ def get_bool_id(item_id: bool): @app.get("/path/param/{item_id}") -def get_path_param_id(item_id: Optional[str] = Path()): +def get_path_param_id(item_id: str | None = Path()): return item_id @@ -161,7 +160,7 @@ def get_query_type(query: int): @app.get("/query/int/optional") -def get_query_type_optional(query: Optional[int] = None): +def get_query_type_optional(query: int | None = None): if query is None: return "foo bar" return f"foo bar {query}" diff --git a/tests/test_additional_properties_bool.py b/tests/test_additional_properties_bool.py index c02841cde..9a1e139ea 100644 --- a/tests/test_additional_properties_bool.py +++ b/tests/test_additional_properties_bool.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -19,7 +17,7 @@ app = FastAPI() @app.post("/") async def post( - foo: Union[Foo, None] = None, + foo: Foo | None = None, ): return foo diff --git a/tests/test_additional_responses_union_duplicate_anyof.py b/tests/test_additional_responses_union_duplicate_anyof.py index 5d833fce4..401bc0a74 100644 --- a/tests/test_additional_responses_union_duplicate_anyof.py +++ b/tests/test_additional_responses_union_duplicate_anyof.py @@ -4,8 +4,6 @@ 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 inline_snapshot import snapshot @@ -23,7 +21,7 @@ class ModelB(BaseModel): app = FastAPI( responses={ 500: { - "model": Union[ModelA, ModelB], + "model": ModelA | ModelB, "content": {"application/json": {"examples": {"Case A": {"value": "a"}}}}, } } diff --git a/tests/test_callable_endpoint.py b/tests/test_callable_endpoint.py index 1882e9053..28999d383 100644 --- a/tests/test_callable_endpoint.py +++ b/tests/test_callable_endpoint.py @@ -1,11 +1,10 @@ from functools import partial -from typing import Optional from fastapi import FastAPI from fastapi.testclient import TestClient -def main(some_arg, q: Optional[str] = None): +def main(some_arg, q: str | None = None): return {"some_arg": some_arg, "q": q} diff --git a/tests/test_compat.py b/tests/test_compat.py index 0b5600f8f..772bd305e 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import FastAPI, UploadFile from fastapi._compat import ( Undefined, @@ -10,8 +8,6 @@ from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict from pydantic.fields import FieldInfo -from .utils import needs_py310 - def test_model_field_default_required(): from fastapi._compat import v2 @@ -26,7 +22,7 @@ def test_complex(): app = FastAPI() @app.post("/") - def foo(foo: Union[str, list[int]]): + def foo(foo: str | list[int]): return foo client = TestClient(app) @@ -49,17 +45,17 @@ def test_propagates_pydantic2_model_config(): class EmbeddedModel(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) - value: Union[str, Missing] = Missing() + value: str | Missing = Missing() class Model(BaseModel): model_config = ConfigDict( arbitrary_types_allowed=True, ) - value: Union[str, Missing] = Missing() + value: str | Missing = Missing() embedded_model: EmbeddedModel = EmbeddedModel() @app.post("/") - def foo(req: Model) -> dict[str, Union[str, None]]: + def foo(req: Model) -> dict[str, str | None]: return { "value": req.value or None, "embedded_value": req.embedded_model.value or None, @@ -89,7 +85,7 @@ def test_is_bytes_sequence_annotation_union(): # TODO: in theory this would allow declaring types that could be lists of bytes # to be read from files and other types, but I'm not even sure it's a good idea # to support it as a first class "feature" - assert is_bytes_sequence_annotation(Union[list[str], list[bytes]]) + assert is_bytes_sequence_annotation(list[str] | list[bytes]) def test_is_uploadfile_sequence_annotation(): @@ -97,21 +93,20 @@ def test_is_uploadfile_sequence_annotation(): # TODO: in theory this would allow declaring types that could be lists of UploadFile # and other types, but I'm not even sure it's a good idea to support it as a first # class "feature" - assert is_uploadfile_sequence_annotation(Union[list[str], list[UploadFile]]) + assert is_uploadfile_sequence_annotation(list[str] | list[UploadFile]) def test_serialize_sequence_value_with_optional_list(): """Test that serialize_sequence_value handles optional lists correctly.""" from fastapi._compat import v2 - field_info = FieldInfo(annotation=Union[list[str], None]) + field_info = FieldInfo(annotation=list[str] | None) field = v2.ModelField(name="items", field_info=field_info) result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"]) assert result == ["a", "b", "c"] assert isinstance(result, list) -@needs_py310 def test_serialize_sequence_value_with_optional_list_pipe_union(): """Test that serialize_sequence_value handles optional lists correctly (with new syntax).""" from fastapi._compat import v2 @@ -125,9 +120,12 @@ def test_serialize_sequence_value_with_optional_list_pipe_union(): def test_serialize_sequence_value_with_none_first_in_union(): """Test that serialize_sequence_value handles Union[None, List[...]] correctly.""" + from typing import Union + from fastapi._compat import v2 - field_info = FieldInfo(annotation=Union[None, list[str]]) + # Use Union[None, list[str]] to ensure None comes first in the union args + field_info = FieldInfo(annotation=Union[None, list[str]]) # noqa: UP007 field = v2.ModelField(name="items", field_info=field_info) result = v2.serialize_sequence_value(field=field, value=["x", "y"]) assert result == ["x", "y"] diff --git a/tests/test_custom_middleware_exception.py b/tests/test_custom_middleware_exception.py index d9b81e7c2..cf548f4ae 100644 --- a/tests/test_custom_middleware_exception.py +++ b/tests/test_custom_middleware_exception.py @@ -1,5 +1,4 @@ from pathlib import Path -from typing import Optional from fastapi import APIRouter, FastAPI, File, UploadFile from fastapi.exceptions import HTTPException @@ -17,7 +16,7 @@ class ContentSizeLimitMiddleware: max_content_size (optional): the maximum content size allowed in bytes, None for no limit """ - def __init__(self, app: APIRouter, max_content_size: Optional[int] = None): + def __init__(self, app: APIRouter, max_content_size: int | None = None): self.app = app self.max_content_size = max_content_size diff --git a/tests/test_custom_schema_fields.py b/tests/test_custom_schema_fields.py index 60b795e9b..c907c5424 100644 --- a/tests/test_custom_schema_fields.py +++ b/tests/test_custom_schema_fields.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from fastapi import FastAPI from fastapi.testclient import TestClient @@ -10,9 +10,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Annotated[ - Optional[str], WithJsonSchema({"type": ["string", "null"]}) - ] = None + description: Annotated[str | None, WithJsonSchema({"type": ["string", "null"]})] = ( + None + ) model_config = { "json_schema_extra": { diff --git a/tests/test_dependency_contextvars.py b/tests/test_dependency_contextvars.py index 0c2e5594b..eba135785 100644 --- a/tests/test_dependency_contextvars.py +++ b/tests/test_dependency_contextvars.py @@ -1,11 +1,11 @@ -from collections.abc import Awaitable +from collections.abc import Awaitable, Callable from contextvars import ContextVar -from typing import Any, Callable, Optional +from typing import Any from fastapi import Depends, FastAPI, Request, Response from fastapi.testclient import TestClient -legacy_request_state_context_var: ContextVar[Optional[dict[str, Any]]] = ContextVar( +legacy_request_state_context_var: ContextVar[dict[str, Any] | None] = ContextVar( "legacy_request_state_context_var", default=None ) diff --git a/tests/test_dependency_overrides.py b/tests/test_dependency_overrides.py index e25db624d..7c99d9d9d 100644 --- a/tests/test_dependency_overrides.py +++ b/tests/test_dependency_overrides.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import APIRouter, Depends, FastAPI from fastapi.testclient import TestClient @@ -38,7 +36,7 @@ app.include_router(router) client = TestClient(app) -async def overrider_dependency_simple(q: Optional[str] = None): +async def overrider_dependency_simple(q: str | None = None): return {"q": q, "skip": 5, "limit": 10} diff --git a/tests/test_dependency_paramless.py b/tests/test_dependency_paramless.py index 1774196fe..62c977b82 100644 --- a/tests/test_dependency_paramless.py +++ b/tests/test_dependency_paramless.py @@ -1,4 +1,4 @@ -from typing import Annotated, Union +from typing import Annotated from fastapi import FastAPI, HTTPException, Security from fastapi.security import ( @@ -13,7 +13,7 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") def process_auth( - credentials: Annotated[Union[str, None], Security(oauth2_scheme)], + credentials: Annotated[str | None, Security(oauth2_scheme)], security_scopes: SecurityScopes, ): # This is an incorrect way of using it, this is not checking if the scopes are diff --git a/tests/test_enforce_once_required_parameter.py b/tests/test_enforce_once_required_parameter.py index 0dee15c25..9f8a96454 100644 --- a/tests/test_enforce_once_required_parameter.py +++ b/tests/test_enforce_once_required_parameter.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, FastAPI, Query from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -11,7 +9,7 @@ def _get_client_key(client_id: str = Query(...)) -> str: return f"{client_id}_key" -def _get_client_tag(client_id: Optional[str] = Query(None)) -> Optional[str]: +def _get_client_tag(client_id: str | None = Query(None)) -> str | None: if client_id is None: return None return f"{client_id}_tag" @@ -20,7 +18,7 @@ def _get_client_tag(client_id: Optional[str] = Query(None)) -> Optional[str]: @app.get("/foo") def foo_handler( client_key: str = Depends(_get_client_key), - client_tag: Optional[str] = Depends(_get_client_tag), + client_tag: str | None = Depends(_get_client_tag), ): return {"client_id": client_key, "client_tag": client_tag} diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py index 96f85b446..985adb943 100644 --- a/tests/test_extra_routes.py +++ b/tests/test_extra_routes.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.testclient import TestClient @@ -11,7 +9,7 @@ app = FastAPI() class Item(BaseModel): name: str - price: Optional[float] = None + price: float | None = None @app.api_route("/items/{item_id}", methods=["GET"]) diff --git a/tests/test_filter_pydantic_sub_model_pv2.py b/tests/test_filter_pydantic_sub_model_pv2.py index 1de2b50f7..1f39581c2 100644 --- a/tests/test_filter_pydantic_sub_model_pv2.py +++ b/tests/test_filter_pydantic_sub_model_pv2.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from dirty_equals import HasRepr from fastapi import Depends, FastAPI @@ -22,7 +20,7 @@ def get_client(): class ModelA(BaseModel): name: str - description: Optional[str] = None + description: str | None = None foo: ModelB tags: dict[str, str] = {} diff --git a/tests/test_form_default.py b/tests/test_form_default.py index 0b3eb8f2e..c4d33e3fb 100644 --- a/tests/test_form_default.py +++ b/tests/test_form_default.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from fastapi import FastAPI, File, Form from starlette.testclient import TestClient @@ -7,14 +7,14 @@ app = FastAPI() @app.post("/urlencoded") -async def post_url_encoded(age: Annotated[Optional[int], Form()] = None): +async def post_url_encoded(age: Annotated[int | None, Form()] = None): return age @app.post("/multipart") async def post_multi_part( - age: Annotated[Optional[int], Form()] = None, - file: Annotated[Optional[bytes], File()] = None, + age: Annotated[int | None, Form()] = None, + file: Annotated[bytes | None, File()] = None, ): return {"file": file, "age": age} diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py index 7d03d2957..4575e3335 100644 --- a/tests/test_forms_single_model.py +++ b/tests/test_forms_single_model.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from fastapi import FastAPI, Form from fastapi.testclient import TestClient @@ -10,7 +10,7 @@ app = FastAPI() class FormModel(BaseModel): username: str lastname: str - age: Optional[int] = None + age: int | None = None tags: list[str] = ["foo", "bar"] alias_with: str = Field(alias="with", default="nothing") diff --git a/tests/test_infer_param_optionality.py b/tests/test_infer_param_optionality.py index bb20a4a1a..2cf74e187 100644 --- a/tests/test_infer_param_optionality.py +++ b/tests/test_infer_param_optionality.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -22,7 +20,7 @@ def get_user(user_id: str): @item_router.get("/") -def get_items(user_id: Optional[str] = None): +def get_items(user_id: str | None = None): if user_id is None: return [{"item_id": "i1", "user_id": "u1"}, {"item_id": "i2", "user_id": "u2"}] else: @@ -30,7 +28,7 @@ def get_items(user_id: Optional[str] = None): @item_router.get("/{item_id}") -def get_item(item_id: str, user_id: Optional[str] = None): +def get_item(item_id: str, user_id: str | None = None): if user_id is None: return {"item_id": item_id} else: diff --git a/tests/test_invalid_sequence_param.py b/tests/test_invalid_sequence_param.py index 3695344f7..d137f6805 100644 --- a/tests/test_invalid_sequence_param.py +++ b/tests/test_invalid_sequence_param.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import FastAPI, Query from pydantic import BaseModel @@ -61,5 +59,5 @@ def test_invalid_simple_dict(): title: str @app.get("/items/") - def read_items(q: Optional[dict] = Query(default=None)): + def read_items(q: dict | None = Query(default=None)): pass # pragma: no cover diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 4528dff44..595202bea 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -6,7 +6,7 @@ from decimal import Decimal from enum import Enum from math import isinf, isnan from pathlib import PurePath, PurePosixPath, PureWindowsPath -from typing import Optional, TypedDict +from typing import TypedDict import pytest from fastapi._compat import Undefined @@ -57,7 +57,7 @@ class RoleEnum(Enum): class ModelWithConfig(BaseModel): - role: Optional[RoleEnum] = None + role: RoleEnum | None = None model_config = {"use_enum_values": True} diff --git a/tests/test_openapi_examples.py b/tests/test_openapi_examples.py index deb74d8a0..e27dd2be0 100644 --- a/tests/test_openapi_examples.py +++ b/tests/test_openapi_examples.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import Body, Cookie, FastAPI, Header, Path, Query from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -57,7 +55,7 @@ def path_examples( @app.get("/query_examples/") def query_examples( - data: Union[str, None] = Query( + data: str | None = Query( default=None, examples=[ "json_schema_query1", @@ -80,7 +78,7 @@ def query_examples( @app.get("/header_examples/") def header_examples( - data: Union[str, None] = Header( + data: str | None = Header( default=None, examples=[ "json_schema_header1", @@ -103,7 +101,7 @@ def header_examples( @app.get("/cookie_examples/") def cookie_examples( - data: Union[str, None] = Cookie( + data: str | None = Cookie( default=None, examples=["json_schema_cookie1", "json_schema_cookie2"], openapi_examples={ diff --git a/tests/test_openapi_query_parameter_extension.py b/tests/test_openapi_query_parameter_extension.py index 836a0a7ee..118d51814 100644 --- a/tests/test_openapi_query_parameter_extension.py +++ b/tests/test_openapi_query_parameter_extension.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -26,7 +24,7 @@ app = FastAPI() ] }, ) -def route_with_extra_query_parameters(standard_query_param: Optional[int] = 50): +def route_with_extra_query_parameters(standard_query_param: int | None = 50): return {} diff --git a/tests/test_openapi_schema_type.py b/tests/test_openapi_schema_type.py index 98d978745..e8166d2fb 100644 --- a/tests/test_openapi_schema_type.py +++ b/tests/test_openapi_schema_type.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - import pytest from fastapi.openapi.models import Schema, SchemaType @@ -13,7 +11,7 @@ from fastapi.openapi.models import Schema, SchemaType ], ) def test_allowed_schema_type( - type_value: Optional[Union[SchemaType, list[SchemaType]]], + type_value: SchemaType | list[SchemaType] | None, ) -> None: """Test that Schema accepts SchemaType, List[SchemaType] and None for type field.""" schema = Schema(type=type_value) diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py index 0efeece01..50255ed09 100644 --- a/tests/test_openapi_separate_input_output_schemas.py +++ b/tests/test_openapi_separate_input_output_schemas.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -8,15 +6,15 @@ from pydantic import BaseModel, computed_field class SubItem(BaseModel): subname: str - sub_description: Optional[str] = None + sub_description: str | None = None tags: list[str] = [] model_config = {"json_schema_serialization_defaults_required": True} class Item(BaseModel): name: str - description: Optional[str] = None - sub: Optional[SubItem] = None + description: str | None = None + sub: SubItem | None = None model_config = {"json_schema_serialization_defaults_required": True} diff --git a/tests/test_optional_file_list.py b/tests/test_optional_file_list.py index 686025864..a57e6358f 100644 --- a/tests/test_optional_file_list.py +++ b/tests/test_optional_file_list.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, File from fastapi.testclient import TestClient @@ -7,7 +5,7 @@ app = FastAPI() @app.post("/files") -async def upload_files(files: Optional[list[bytes]] = File(None)): +async def upload_files(files: list[bytes] | None = File(None)): if files is None: return {"files_count": 0} return {"files_count": len(files), "sizes": [len(f) for f in files]} diff --git a/tests/test_param_class.py b/tests/test_param_class.py index 1fd40dcd2..e6642daea 100644 --- a/tests/test_param_class.py +++ b/tests/test_param_class.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.params import Param from fastapi.testclient import TestClient @@ -8,7 +6,7 @@ app = FastAPI() @app.get("/items/") -def read_items(q: Optional[str] = Param(default=None)): # type: ignore +def read_items(q: str | None = Param(default=None)): # type: ignore return {"q": q} diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py index 463fb51b1..727552b46 100644 --- a/tests/test_param_include_in_schema.py +++ b/tests/test_param_include_in_schema.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import Cookie, FastAPI, Header, Path, Query from fastapi.testclient import TestClient @@ -10,14 +8,14 @@ app = FastAPI() @app.get("/hidden_cookie") async def hidden_cookie( - hidden_cookie: Optional[str] = Cookie(default=None, include_in_schema=False), + hidden_cookie: str | None = Cookie(default=None, include_in_schema=False), ): return {"hidden_cookie": hidden_cookie} @app.get("/hidden_header") async def hidden_header( - hidden_header: Optional[str] = Header(default=None, include_in_schema=False), + hidden_header: str | None = Header(default=None, include_in_schema=False), ): return {"hidden_header": hidden_header} @@ -29,7 +27,7 @@ async def hidden_path(hidden_path: str = Path(include_in_schema=False)): @app.get("/hidden_query") async def hidden_query( - hidden_query: Optional[str] = Query(default=None, include_in_schema=False), + hidden_query: str | None = Query(default=None, include_in_schema=False), ): return {"hidden_query": hidden_query} diff --git a/tests/test_pydantic_v1_error.py b/tests/test_pydantic_v1_error.py index 13229a313..044fdf0d6 100644 --- a/tests/test_pydantic_v1_error.py +++ b/tests/test_pydantic_v1_error.py @@ -1,6 +1,5 @@ import sys import warnings -from typing import Union import pytest @@ -80,7 +79,7 @@ def test_raises_pydantic_v1_model_in_union() -> None: with pytest.raises(PydanticV1NotSupportedError): @app.post("/union") - def endpoint(data: Union[dict, ModelV1A]): # pragma: no cover + def endpoint(data: dict | ModelV1A): # pragma: no cover return data diff --git a/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py index b72b0518a..4f7b0b2a0 100644 --- a/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py +++ b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py @@ -2,7 +2,6 @@ from __future__ import annotations import uuid from dataclasses import dataclass, field -from typing import Union from dirty_equals import IsUUID from fastapi import FastAPI @@ -16,8 +15,8 @@ class Item: name: str price: float tags: list[str] = field(default_factory=list) - description: Union[str, None] = None - tax: Union[float, None] = None + description: str | None = None + tax: float | None = None app = FastAPI() diff --git a/tests/test_request_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py index 970e6a660..aa9745f84 100644 --- a/tests/test_request_params/test_body/test_list.py +++ b/tests/test_request_params/test_body/test_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Union +from typing import Annotated import pytest from dirty_equals import IsOneOf, IsPartialDict @@ -55,7 +55,7 @@ def test_required_list_str_schema(path: str): "path", ["/required-list-str", "/model-required-list-str"], ) -def test_required_list_str_missing(path: str, json: Union[dict, None]): +def test_required_list_str_missing(path: str, json: dict | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -132,7 +132,7 @@ def test_required_list_str_alias_schema(path: str): "path", ["/required-list-alias", "/model-required-list-alias"], ) -def test_required_list_alias_missing(path: str, json: Union[dict, None]): +def test_required_list_alias_missing(path: str, json: dict | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -236,7 +236,7 @@ def test_required_list_validation_alias_schema(path: str): "/model-required-list-validation-alias", ], ) -def test_required_list_validation_alias_missing(path: str, json: Union[dict, None]): +def test_required_list_validation_alias_missing(path: str, json: dict | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 diff --git a/tests/test_request_params/test_body/test_optional_list.py b/tests/test_request_params/test_body/test_optional_list.py index ba8ba9092..2c5c5d61b 100644 --- a/tests/test_request_params/test_body/test_optional_list.py +++ b/tests/test_request_params/test_body/test_optional_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import Body, FastAPI @@ -15,13 +15,13 @@ app = FastAPI() @app.post("/optional-list-str", operation_id="optional_list_str") async def read_optional_list_str( - p: Annotated[Optional[list[str]], Body(embed=True)] = None, + p: Annotated[list[str] | None, Body(embed=True)] = None, ): return {"p": p} class BodyModelOptionalListStr(BaseModel): - p: Optional[list[str]] = None + p: list[str] | None = None @app.post("/model-optional-list-str", operation_id="model_optional_list_str") @@ -103,13 +103,13 @@ def test_optional_list_str(path: str): @app.post("/optional-list-alias", operation_id="optional_list_alias") async def read_optional_list_alias( - p: Annotated[Optional[list[str]], Body(embed=True, alias="p_alias")] = None, + p: Annotated[list[str] | None, Body(embed=True, alias="p_alias")] = None, ): return {"p": p} class BodyModelOptionalListAlias(BaseModel): - p: Optional[list[str]] = Field(None, alias="p_alias") + p: list[str] | None = Field(None, alias="p_alias") @app.post("/model-optional-list-alias", operation_id="model_optional_list_alias") @@ -208,14 +208,14 @@ def test_optional_list_alias_by_alias(path: str): ) def read_optional_list_validation_alias( p: Annotated[ - Optional[list[str]], Body(embed=True, validation_alias="p_val_alias") + list[str] | None, Body(embed=True, validation_alias="p_val_alias") ] = None, ): return {"p": p} class BodyModelOptionalListValidationAlias(BaseModel): - p: Optional[list[str]] = Field(None, validation_alias="p_val_alias") + p: list[str] | None = Field(None, validation_alias="p_val_alias") @app.post( @@ -323,7 +323,7 @@ def test_optional_list_validation_alias_by_validation_alias(path: str): ) def read_optional_list_alias_and_validation_alias( p: Annotated[ - Optional[list[str]], + list[str] | None, Body(embed=True, alias="p_alias", validation_alias="p_val_alias"), ] = None, ): @@ -331,9 +331,7 @@ def read_optional_list_alias_and_validation_alias( class BodyModelOptionalListAliasAndValidationAlias(BaseModel): - p: Optional[list[str]] = Field( - None, alias="p_alias", validation_alias="p_val_alias" - ) + p: list[str] | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.post( diff --git a/tests/test_request_params/test_body/test_optional_str.py b/tests/test_request_params/test_body/test_optional_str.py index b9c18034d..184fc94cb 100644 --- a/tests/test_request_params/test_body/test_optional_str.py +++ b/tests/test_request_params/test_body/test_optional_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import Body, FastAPI @@ -14,12 +14,12 @@ app = FastAPI() @app.post("/optional-str", operation_id="optional_str") -async def read_optional_str(p: Annotated[Optional[str], Body(embed=True)] = None): +async def read_optional_str(p: Annotated[str | None, Body(embed=True)] = None): return {"p": p} class BodyModelOptionalStr(BaseModel): - p: Optional[str] = None + p: str | None = None @app.post("/model-optional-str", operation_id="model_optional_str") @@ -98,13 +98,13 @@ def test_optional_str(path: str): @app.post("/optional-alias", operation_id="optional_alias") async def read_optional_alias( - p: Annotated[Optional[str], Body(embed=True, alias="p_alias")] = None, + p: Annotated[str | None, Body(embed=True, alias="p_alias")] = None, ): return {"p": p} class BodyModelOptionalAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias") + p: str | None = Field(None, alias="p_alias") @app.post("/model-optional-alias", operation_id="model_optional_alias") @@ -197,15 +197,13 @@ def test_optional_alias_by_alias(path: str): @app.post("/optional-validation-alias", operation_id="optional_validation_alias") def read_optional_validation_alias( - p: Annotated[ - Optional[str], Body(embed=True, validation_alias="p_val_alias") - ] = None, + p: Annotated[str | None, Body(embed=True, validation_alias="p_val_alias")] = None, ): return {"p": p} class BodyModelOptionalValidationAlias(BaseModel): - p: Optional[str] = Field(None, validation_alias="p_val_alias") + p: str | None = Field(None, validation_alias="p_val_alias") @app.post( @@ -309,14 +307,14 @@ def test_optional_validation_alias_by_validation_alias(path: str): ) def read_optional_alias_and_validation_alias( p: Annotated[ - Optional[str], Body(embed=True, alias="p_alias", validation_alias="p_val_alias") + str | None, Body(embed=True, alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class BodyModelOptionalAliasAndValidationAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") + p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.post( diff --git a/tests/test_request_params/test_body/test_required_str.py b/tests/test_request_params/test_body/test_required_str.py index 5b434fa1d..2e02f8d20 100644 --- a/tests/test_request_params/test_body/test_required_str.py +++ b/tests/test_request_params/test_body/test_required_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Union +from typing import Annotated, Any import pytest from dirty_equals import IsOneOf @@ -51,7 +51,7 @@ def test_required_str_schema(path: str): "path", ["/required-str", "/model-required-str"], ) -def test_required_str_missing(path: str, json: Union[dict[str, Any], None]): +def test_required_str_missing(path: str, json: dict[str, Any] | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -124,7 +124,7 @@ def test_required_str_alias_schema(path: str): "path", ["/required-alias", "/model-required-alias"], ) -def test_required_alias_missing(path: str, json: Union[dict[str, Any], None]): +def test_required_alias_missing(path: str, json: dict[str, Any] | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -221,9 +221,7 @@ def test_required_validation_alias_schema(path: str): "/model-required-validation-alias", ], ) -def test_required_validation_alias_missing( - path: str, json: Union[dict[str, Any], None] -): +def test_required_validation_alias_missing(path: str, json: dict[str, Any] | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -338,7 +336,7 @@ def test_required_alias_and_validation_alias_schema(path: str): ], ) def test_required_alias_and_validation_alias_missing( - path: str, json: Union[dict[str, Any], None] + path: str, json: dict[str, Any] | None ): client = TestClient(app) response = client.post(path, json=json) diff --git a/tests/test_request_params/test_cookie/test_optional_str.py b/tests/test_request_params/test_cookie/test_optional_str.py index 1b2a18b07..227d2bccc 100644 --- a/tests/test_request_params/test_cookie/test_optional_str.py +++ b/tests/test_request_params/test_cookie/test_optional_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import Cookie, FastAPI @@ -13,12 +13,12 @@ app = FastAPI() @app.get("/optional-str") -async def read_optional_str(p: Annotated[Optional[str], Cookie()] = None): +async def read_optional_str(p: Annotated[str | None, Cookie()] = None): return {"p": p} class CookieModelOptionalStr(BaseModel): - p: Optional[str] = None + p: str | None = None @app.get("/model-optional-str") @@ -75,13 +75,13 @@ def test_optional_str(path: str): @app.get("/optional-alias") async def read_optional_alias( - p: Annotated[Optional[str], Cookie(alias="p_alias")] = None, + p: Annotated[str | None, Cookie(alias="p_alias")] = None, ): return {"p": p} class CookieModelOptionalAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias") + p: str | None = Field(None, alias="p_alias") @app.get("/model-optional-alias") @@ -153,13 +153,13 @@ def test_optional_alias_by_alias(path: str): @app.get("/optional-validation-alias") def read_optional_validation_alias( - p: Annotated[Optional[str], Cookie(validation_alias="p_val_alias")] = None, + p: Annotated[str | None, Cookie(validation_alias="p_val_alias")] = None, ): return {"p": p} class CookieModelOptionalValidationAlias(BaseModel): - p: Optional[str] = Field(None, validation_alias="p_val_alias") + p: str | None = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-validation-alias") @@ -237,14 +237,14 @@ def test_optional_validation_alias_by_validation_alias(path: str): @app.get("/optional-alias-and-validation-alias") def read_optional_alias_and_validation_alias( p: Annotated[ - Optional[str], Cookie(alias="p_alias", validation_alias="p_val_alias") + str | None, Cookie(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class CookieModelOptionalAliasAndValidationAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") + p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.get("/model-optional-alias-and-validation-alias") diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py index 45ef7bdec..b4dc11a06 100644 --- a/tests/test_request_params/test_file/test_optional.py +++ b/tests/test_request_params/test_file/test_optional.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, File, UploadFile @@ -13,12 +13,12 @@ app = FastAPI() @app.post("/optional-bytes", operation_id="optional_bytes") -async def read_optional_bytes(p: Annotated[Optional[bytes], File()] = None): +async def read_optional_bytes(p: Annotated[bytes | None, File()] = None): return {"file_size": len(p) if p else None} @app.post("/optional-uploadfile", operation_id="optional_uploadfile") -async def read_optional_uploadfile(p: Annotated[Optional[UploadFile], File()] = None): +async def read_optional_uploadfile(p: Annotated[UploadFile | None, File()] = None): return {"file_size": p.size if p else None} @@ -82,14 +82,14 @@ def test_optional(path: str): @app.post("/optional-bytes-alias", operation_id="optional_bytes_alias") async def read_optional_bytes_alias( - p: Annotated[Optional[bytes], File(alias="p_alias")] = None, + p: Annotated[bytes | None, File(alias="p_alias")] = None, ): return {"file_size": len(p) if p else None} @app.post("/optional-uploadfile-alias", operation_id="optional_uploadfile_alias") async def read_optional_uploadfile_alias( - p: Annotated[Optional[UploadFile], File(alias="p_alias")] = None, + p: Annotated[UploadFile | None, File(alias="p_alias")] = None, ): return {"file_size": p.size if p else None} @@ -170,7 +170,7 @@ def test_optional_alias_by_alias(path: str): "/optional-bytes-validation-alias", operation_id="optional_bytes_validation_alias" ) def read_optional_bytes_validation_alias( - p: Annotated[Optional[bytes], File(validation_alias="p_val_alias")] = None, + p: Annotated[bytes | None, File(validation_alias="p_val_alias")] = None, ): return {"file_size": len(p) if p else None} @@ -180,7 +180,7 @@ def read_optional_bytes_validation_alias( operation_id="optional_uploadfile_validation_alias", ) def read_optional_uploadfile_validation_alias( - p: Annotated[Optional[UploadFile], File(validation_alias="p_val_alias")] = None, + p: Annotated[UploadFile | None, File(validation_alias="p_val_alias")] = None, ): return {"file_size": p.size if p else None} @@ -263,7 +263,7 @@ def test_optional_validation_alias_by_validation_alias(path: str): ) def read_optional_bytes_alias_and_validation_alias( p: Annotated[ - Optional[bytes], File(alias="p_alias", validation_alias="p_val_alias") + bytes | None, File(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"file_size": len(p) if p else None} @@ -275,7 +275,7 @@ def read_optional_bytes_alias_and_validation_alias( ) def read_optional_uploadfile_alias_and_validation_alias( p: Annotated[ - Optional[UploadFile], File(alias="p_alias", validation_alias="p_val_alias") + UploadFile | None, File(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"file_size": p.size if p else None} diff --git a/tests/test_request_params/test_file/test_optional_list.py b/tests/test_request_params/test_file/test_optional_list.py index 162fbe08a..a506ec991 100644 --- a/tests/test_request_params/test_file/test_optional_list.py +++ b/tests/test_request_params/test_file/test_optional_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, File, UploadFile @@ -13,13 +13,13 @@ app = FastAPI() @app.post("/optional-list-bytes") -async def read_optional_list_bytes(p: Annotated[Optional[list[bytes]], File()] = None): +async def read_optional_list_bytes(p: Annotated[list[bytes] | None, File()] = None): return {"file_size": [len(file) for file in p] if p else None} @app.post("/optional-list-uploadfile") async def read_optional_list_uploadfile( - p: Annotated[Optional[list[UploadFile]], File()] = None, + p: Annotated[list[UploadFile] | None, File()] = None, ): return {"file_size": [file.size for file in p] if p else None} @@ -87,14 +87,14 @@ def test_optional_list(path: str): @app.post("/optional-list-bytes-alias") async def read_optional_list_bytes_alias( - p: Annotated[Optional[list[bytes]], File(alias="p_alias")] = None, + p: Annotated[list[bytes] | None, File(alias="p_alias")] = None, ): return {"file_size": [len(file) for file in p] if p else None} @app.post("/optional-list-uploadfile-alias") async def read_optional_list_uploadfile_alias( - p: Annotated[Optional[list[UploadFile]], File(alias="p_alias")] = None, + p: Annotated[list[UploadFile] | None, File(alias="p_alias")] = None, ): return {"file_size": [file.size for file in p] if p else None} @@ -176,16 +176,14 @@ def test_optional_list_alias_by_alias(path: str): @app.post("/optional-list-bytes-validation-alias") def read_optional_list_bytes_validation_alias( - p: Annotated[Optional[list[bytes]], File(validation_alias="p_val_alias")] = None, + p: Annotated[list[bytes] | None, File(validation_alias="p_val_alias")] = None, ): return {"file_size": [len(file) for file in p] if p else None} @app.post("/optional-list-uploadfile-validation-alias") def read_optional_list_uploadfile_validation_alias( - p: Annotated[ - Optional[list[UploadFile]], File(validation_alias="p_val_alias") - ] = None, + p: Annotated[list[UploadFile] | None, File(validation_alias="p_val_alias")] = None, ): return {"file_size": [file.size for file in p] if p else None} @@ -270,7 +268,7 @@ def test_optional_validation_alias_by_validation_alias(path: str): @app.post("/optional-list-bytes-alias-and-validation-alias") def read_optional_list_bytes_alias_and_validation_alias( p: Annotated[ - Optional[list[bytes]], File(alias="p_alias", validation_alias="p_val_alias") + list[bytes] | None, File(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"file_size": [len(file) for file in p] if p else None} @@ -279,7 +277,7 @@ def read_optional_list_bytes_alias_and_validation_alias( @app.post("/optional-list-uploadfile-alias-and-validation-alias") def read_optional_list_uploadfile_alias_and_validation_alias( p: Annotated[ - Optional[list[UploadFile]], + list[UploadFile] | None, File(alias="p_alias", validation_alias="p_val_alias"), ] = None, ): diff --git a/tests/test_request_params/test_form/test_optional_list.py b/tests/test_request_params/test_form/test_optional_list.py index 6d1957a18..7ecf9c9bf 100644 --- a/tests/test_request_params/test_form/test_optional_list.py +++ b/tests/test_request_params/test_form/test_optional_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Form @@ -15,13 +15,13 @@ app = FastAPI() @app.post("/optional-list-str", operation_id="optional_list_str") async def read_optional_list_str( - p: Annotated[Optional[list[str]], Form()] = None, + p: Annotated[list[str] | None, Form()] = None, ): return {"p": p} class FormModelOptionalListStr(BaseModel): - p: Optional[list[str]] = None + p: list[str] | None = None @app.post("/model-optional-list-str", operation_id="model_optional_list_str") @@ -80,13 +80,13 @@ def test_optional_list_str(path: str): @app.post("/optional-list-alias", operation_id="optional_list_alias") async def read_optional_list_alias( - p: Annotated[Optional[list[str]], Form(alias="p_alias")] = None, + p: Annotated[list[str] | None, Form(alias="p_alias")] = None, ): return {"p": p} class FormModelOptionalListAlias(BaseModel): - p: Optional[list[str]] = Field(None, alias="p_alias") + p: list[str] | None = Field(None, alias="p_alias") @app.post("/model-optional-list-alias", operation_id="model_optional_list_alias") @@ -163,13 +163,13 @@ def test_optional_list_alias_by_alias(path: str): "/optional-list-validation-alias", operation_id="optional_list_validation_alias" ) def read_optional_list_validation_alias( - p: Annotated[Optional[list[str]], Form(validation_alias="p_val_alias")] = None, + p: Annotated[list[str] | None, Form(validation_alias="p_val_alias")] = None, ): return {"p": p} class FormModelOptionalListValidationAlias(BaseModel): - p: Optional[list[str]] = Field(None, validation_alias="p_val_alias") + p: list[str] | None = Field(None, validation_alias="p_val_alias") @app.post( @@ -251,16 +251,14 @@ def test_optional_list_validation_alias_by_validation_alias(path: str): ) def read_optional_list_alias_and_validation_alias( p: Annotated[ - Optional[list[str]], Form(alias="p_alias", validation_alias="p_val_alias") + list[str] | None, Form(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class FormModelOptionalListAliasAndValidationAlias(BaseModel): - p: Optional[list[str]] = Field( - None, alias="p_alias", validation_alias="p_val_alias" - ) + p: list[str] | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.post( diff --git a/tests/test_request_params/test_form/test_optional_str.py b/tests/test_request_params/test_form/test_optional_str.py index 810e83caa..4ef16756e 100644 --- a/tests/test_request_params/test_form/test_optional_str.py +++ b/tests/test_request_params/test_form/test_optional_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Form @@ -14,12 +14,12 @@ app = FastAPI() @app.post("/optional-str", operation_id="optional_str") -async def read_optional_str(p: Annotated[Optional[str], Form()] = None): +async def read_optional_str(p: Annotated[str | None, Form()] = None): return {"p": p} class FormModelOptionalStr(BaseModel): - p: Optional[str] = None + p: str | None = None @app.post("/model-optional-str", operation_id="model_optional_str") @@ -75,13 +75,13 @@ def test_optional_str(path: str): @app.post("/optional-alias", operation_id="optional_alias") async def read_optional_alias( - p: Annotated[Optional[str], Form(alias="p_alias")] = None, + p: Annotated[str | None, Form(alias="p_alias")] = None, ): return {"p": p} class FormModelOptionalAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias") + p: str | None = Field(None, alias="p_alias") @app.post("/model-optional-alias", operation_id="model_optional_alias") @@ -151,13 +151,13 @@ def test_optional_alias_by_alias(path: str): @app.post("/optional-validation-alias", operation_id="optional_validation_alias") def read_optional_validation_alias( - p: Annotated[Optional[str], Form(validation_alias="p_val_alias")] = None, + p: Annotated[str | None, Form(validation_alias="p_val_alias")] = None, ): return {"p": p} class FormModelOptionalValidationAlias(BaseModel): - p: Optional[str] = Field(None, validation_alias="p_val_alias") + p: str | None = Field(None, validation_alias="p_val_alias") @app.post( @@ -238,14 +238,14 @@ def test_optional_validation_alias_by_validation_alias(path: str): ) def read_optional_alias_and_validation_alias( p: Annotated[ - Optional[str], Form(alias="p_alias", validation_alias="p_val_alias") + str | None, Form(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class FormModelOptionalAliasAndValidationAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") + p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.post( diff --git a/tests/test_request_params/test_header/test_optional_list.py b/tests/test_request_params/test_header/test_optional_list.py index 3bbb73d54..9f4eacc23 100644 --- a/tests/test_request_params/test_header/test_optional_list.py +++ b/tests/test_request_params/test_header/test_optional_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Header @@ -14,13 +14,13 @@ app = FastAPI() @app.get("/optional-list-str") async def read_optional_list_str( - p: Annotated[Optional[list[str]], Header()] = None, + p: Annotated[list[str] | None, Header()] = None, ): return {"p": p} class HeaderModelOptionalListStr(BaseModel): - p: Optional[list[str]] = None + p: list[str] | None = None @app.get("/model-optional-list-str") @@ -81,13 +81,13 @@ def test_optional_list_str(path: str): @app.get("/optional-list-alias") async def read_optional_list_alias( - p: Annotated[Optional[list[str]], Header(alias="p_alias")] = None, + p: Annotated[list[str] | None, Header(alias="p_alias")] = None, ): return {"p": p} class HeaderModelOptionalListAlias(BaseModel): - p: Optional[list[str]] = Field(None, alias="p_alias") + p: list[str] | None = Field(None, alias="p_alias") @app.get("/model-optional-list-alias") @@ -162,13 +162,13 @@ def test_optional_list_alias_by_alias(path: str): @app.get("/optional-list-validation-alias") def read_optional_list_validation_alias( - p: Annotated[Optional[list[str]], Header(validation_alias="p_val_alias")] = None, + p: Annotated[list[str] | None, Header(validation_alias="p_val_alias")] = None, ): return {"p": p} class HeaderModelOptionalListValidationAlias(BaseModel): - p: Optional[list[str]] = Field(None, validation_alias="p_val_alias") + p: list[str] | None = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-list-validation-alias") @@ -246,16 +246,14 @@ def test_optional_list_validation_alias_by_validation_alias(path: str): @app.get("/optional-list-alias-and-validation-alias") def read_optional_list_alias_and_validation_alias( p: Annotated[ - Optional[list[str]], Header(alias="p_alias", validation_alias="p_val_alias") + list[str] | None, Header(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class HeaderModelOptionalListAliasAndValidationAlias(BaseModel): - p: Optional[list[str]] = Field( - None, alias="p_alias", validation_alias="p_val_alias" - ) + p: list[str] | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.get("/model-optional-list-alias-and-validation-alias") diff --git a/tests/test_request_params/test_header/test_optional_str.py b/tests/test_request_params/test_header/test_optional_str.py index a5174e59a..04773c83f 100644 --- a/tests/test_request_params/test_header/test_optional_str.py +++ b/tests/test_request_params/test_header/test_optional_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Header @@ -13,12 +13,12 @@ app = FastAPI() @app.get("/optional-str") -async def read_optional_str(p: Annotated[Optional[str], Header()] = None): +async def read_optional_str(p: Annotated[str | None, Header()] = None): return {"p": p} class HeaderModelOptionalStr(BaseModel): - p: Optional[str] = None + p: str | None = None @app.get("/model-optional-str") @@ -74,13 +74,13 @@ def test_optional_str(path: str): @app.get("/optional-alias") async def read_optional_alias( - p: Annotated[Optional[str], Header(alias="p_alias")] = None, + p: Annotated[str | None, Header(alias="p_alias")] = None, ): return {"p": p} class HeaderModelOptionalAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias") + p: str | None = Field(None, alias="p_alias") @app.get("/model-optional-alias") @@ -150,13 +150,13 @@ def test_optional_alias_by_alias(path: str): @app.get("/optional-validation-alias") def read_optional_validation_alias( - p: Annotated[Optional[str], Header(validation_alias="p_val_alias")] = None, + p: Annotated[str | None, Header(validation_alias="p_val_alias")] = None, ): return {"p": p} class HeaderModelOptionalValidationAlias(BaseModel): - p: Optional[str] = Field(None, validation_alias="p_val_alias") + p: str | None = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-validation-alias") @@ -232,14 +232,14 @@ def test_optional_validation_alias_by_validation_alias(path: str): @app.get("/optional-alias-and-validation-alias") def read_optional_alias_and_validation_alias( p: Annotated[ - Optional[str], Header(alias="p_alias", validation_alias="p_val_alias") + str | None, Header(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class HeaderModelOptionalAliasAndValidationAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") + p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.get("/model-optional-alias-and-validation-alias") diff --git a/tests/test_request_params/test_query/test_optional_list.py b/tests/test_request_params/test_query/test_optional_list.py index 5608c6499..6b70b75a4 100644 --- a/tests/test_request_params/test_query/test_optional_list.py +++ b/tests/test_request_params/test_query/test_optional_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Query @@ -14,13 +14,13 @@ app = FastAPI() @app.get("/optional-list-str") async def read_optional_list_str( - p: Annotated[Optional[list[str]], Query()] = None, + p: Annotated[list[str] | None, Query()] = None, ): return {"p": p} class QueryModelOptionalListStr(BaseModel): - p: Optional[list[str]] = None + p: list[str] | None = None @app.get("/model-optional-list-str") @@ -81,13 +81,13 @@ def test_optional_list_str(path: str): @app.get("/optional-list-alias") async def read_optional_list_alias( - p: Annotated[Optional[list[str]], Query(alias="p_alias")] = None, + p: Annotated[list[str] | None, Query(alias="p_alias")] = None, ): return {"p": p} class QueryModelOptionalListAlias(BaseModel): - p: Optional[list[str]] = Field(None, alias="p_alias") + p: list[str] | None = Field(None, alias="p_alias") @app.get("/model-optional-list-alias") @@ -162,13 +162,13 @@ def test_optional_list_alias_by_alias(path: str): @app.get("/optional-list-validation-alias") def read_optional_list_validation_alias( - p: Annotated[Optional[list[str]], Query(validation_alias="p_val_alias")] = None, + p: Annotated[list[str] | None, Query(validation_alias="p_val_alias")] = None, ): return {"p": p} class QueryModelOptionalListValidationAlias(BaseModel): - p: Optional[list[str]] = Field(None, validation_alias="p_val_alias") + p: list[str] | None = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-list-validation-alias") @@ -244,16 +244,14 @@ def test_optional_list_validation_alias_by_validation_alias(path: str): @app.get("/optional-list-alias-and-validation-alias") def read_optional_list_alias_and_validation_alias( p: Annotated[ - Optional[list[str]], Query(alias="p_alias", validation_alias="p_val_alias") + list[str] | None, Query(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class QueryModelOptionalListAliasAndValidationAlias(BaseModel): - p: Optional[list[str]] = Field( - None, alias="p_alias", validation_alias="p_val_alias" - ) + p: list[str] | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.get("/model-optional-list-alias-and-validation-alias") diff --git a/tests/test_request_params/test_query/test_optional_str.py b/tests/test_request_params/test_query/test_optional_str.py index b181686b0..f7f35860b 100644 --- a/tests/test_request_params/test_query/test_optional_str.py +++ b/tests/test_request_params/test_query/test_optional_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Query @@ -13,12 +13,12 @@ app = FastAPI() @app.get("/optional-str") -async def read_optional_str(p: Optional[str] = None): +async def read_optional_str(p: str | None = None): return {"p": p} class QueryModelOptionalStr(BaseModel): - p: Optional[str] = None + p: str | None = None @app.get("/model-optional-str") @@ -74,13 +74,13 @@ def test_optional_str(path: str): @app.get("/optional-alias") async def read_optional_alias( - p: Annotated[Optional[str], Query(alias="p_alias")] = None, + p: Annotated[str | None, Query(alias="p_alias")] = None, ): return {"p": p} class QueryModelOptionalAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias") + p: str | None = Field(None, alias="p_alias") @app.get("/model-optional-alias") @@ -150,13 +150,13 @@ def test_optional_alias_by_alias(path: str): @app.get("/optional-validation-alias") def read_optional_validation_alias( - p: Annotated[Optional[str], Query(validation_alias="p_val_alias")] = None, + p: Annotated[str | None, Query(validation_alias="p_val_alias")] = None, ): return {"p": p} class QueryModelOptionalValidationAlias(BaseModel): - p: Optional[str] = Field(None, validation_alias="p_val_alias") + p: str | None = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-validation-alias") @@ -232,14 +232,14 @@ def test_optional_validation_alias_by_validation_alias(path: str): @app.get("/optional-alias-and-validation-alias") def read_optional_alias_and_validation_alias( p: Annotated[ - Optional[str], Query(alias="p_alias", validation_alias="p_val_alias") + str | None, Query(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class QueryModelOptionalAliasAndValidationAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") + p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.get("/model-optional-alias-and-validation-alias") diff --git a/tests/test_required_noneable.py b/tests/test_required_noneable.py index 5da8cd4d0..c99f20212 100644 --- a/tests/test_required_noneable.py +++ b/tests/test_required_noneable.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import Body, FastAPI, Query from fastapi.testclient import TestClient @@ -7,17 +5,17 @@ app = FastAPI() @app.get("/query") -def read_query(q: Union[str, None]): +def read_query(q: str | None): return q @app.get("/explicit-query") -def read_explicit_query(q: Union[str, None] = Query()): +def read_explicit_query(q: str | None = Query()): return q @app.post("/body-embed") -def send_body_embed(b: Union[str, None] = Body(embed=True)): +def send_body_embed(b: str | None = Body(embed=True)): return b diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py index ded597102..7be7902ad 100644 --- a/tests/test_response_model_as_return_annotation.py +++ b/tests/test_response_model_as_return_annotation.py @@ -1,5 +1,3 @@ -from typing import Union - import pytest from fastapi import FastAPI from fastapi.exceptions import FastAPIError, ResponseValidationError @@ -216,7 +214,7 @@ def no_response_model_annotation_forward_ref_list_of_model() -> "list[User]": @app.get( "/response_model_union-no_annotation-return_model1", - response_model=Union[User, Item], + response_model=User | Item, ) def response_model_union_no_annotation_return_model1(): return DBUser(name="John", surname="Doe", password_hash="secret") @@ -224,19 +222,19 @@ def response_model_union_no_annotation_return_model1(): @app.get( "/response_model_union-no_annotation-return_model2", - response_model=Union[User, Item], + response_model=User | Item, ) def response_model_union_no_annotation_return_model2(): return Item(name="Foo", price=42.0) @app.get("/no_response_model-annotation_union-return_model1") -def no_response_model_annotation_union_return_model1() -> Union[User, Item]: +def no_response_model_annotation_union_return_model1() -> User | Item: return DBUser(name="John", surname="Doe", password_hash="secret") @app.get("/no_response_model-annotation_union-return_model2") -def no_response_model_annotation_union_return_model2() -> Union[User, Item]: +def no_response_model_annotation_union_return_model2() -> User | Item: return Item(name="Foo", price=42.0) @@ -503,7 +501,7 @@ def test_invalid_response_model_field(): with pytest.raises(FastAPIError) as e: @app.get("/") - def read_root() -> Union[Response, None]: + def read_root() -> Response | None: return Response(content="Foo") # pragma: no cover assert "valid Pydantic field type" in e.value.args[0] diff --git a/tests/test_router_events.py b/tests/test_router_events.py index a47d11913..7869a7afc 100644 --- a/tests/test_router_events.py +++ b/tests/test_router_events.py @@ -1,6 +1,5 @@ from collections.abc import AsyncGenerator from contextlib import asynccontextmanager -from typing import Union import pytest from fastapi import APIRouter, FastAPI, Request @@ -176,7 +175,7 @@ def test_router_nested_lifespan_state_overriding_by_parent() -> None: @asynccontextmanager async def lifespan( app: FastAPI, - ) -> AsyncGenerator[dict[str, Union[str, bool]], None]: + ) -> AsyncGenerator[dict[str, str | bool], None]: yield { "app_specific": True, "overridden": "app", @@ -185,7 +184,7 @@ def test_router_nested_lifespan_state_overriding_by_parent() -> None: @asynccontextmanager async def router_lifespan( app: FastAPI, - ) -> AsyncGenerator[dict[str, Union[str, bool]], None]: + ) -> AsyncGenerator[dict[str, str | bool], None]: yield { "router_specific": True, "overridden": "router", # should override parent diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py index 9ec41e7e8..32f5cea47 100644 --- a/tests/test_schema_extra_examples.py +++ b/tests/test_schema_extra_examples.py @@ -1,5 +1,3 @@ -from typing import Union - import pytest from fastapi import Body, Cookie, FastAPI, Header, Path, Query from fastapi.exceptions import FastAPIDeprecationWarning @@ -117,7 +115,7 @@ def create_app(): @app.get("/query_example/") def query_example( - data: Union[str, None] = Query( + data: str | None = Query( default=None, example="query1", ), @@ -126,7 +124,7 @@ def create_app(): @app.get("/query_examples/") def query_examples( - data: Union[str, None] = Query( + data: str | None = Query( default=None, examples=["query1", "query2"], ), @@ -137,7 +135,7 @@ def create_app(): @app.get("/query_example_examples/") def query_example_examples( - data: Union[str, None] = Query( + data: str | None = Query( default=None, example="query_overridden", examples=["query1", "query2"], @@ -149,7 +147,7 @@ def create_app(): @app.get("/header_example/") def header_example( - data: Union[str, None] = Header( + data: str | None = Header( default=None, example="header1", ), @@ -158,7 +156,7 @@ def create_app(): @app.get("/header_examples/") def header_examples( - data: Union[str, None] = Header( + data: str | None = Header( default=None, examples=[ "header1", @@ -172,7 +170,7 @@ def create_app(): @app.get("/header_example_examples/") def header_example_examples( - data: Union[str, None] = Header( + data: str | None = Header( default=None, example="header_overridden", examples=["header1", "header2"], @@ -184,7 +182,7 @@ def create_app(): @app.get("/cookie_example/") def cookie_example( - data: Union[str, None] = Cookie( + data: str | None = Cookie( default=None, example="cookie1", ), @@ -193,7 +191,7 @@ def create_app(): @app.get("/cookie_examples/") def cookie_examples( - data: Union[str, None] = Cookie( + data: str | None = Cookie( default=None, examples=["cookie1", "cookie2"], ), @@ -204,7 +202,7 @@ def create_app(): @app.get("/cookie_example_examples/") def cookie_example_examples( - data: Union[str, None] = Cookie( + data: str | None = Cookie( default=None, example="cookie_overridden", examples=["cookie1", "cookie2"], diff --git a/tests/test_security_api_key_cookie_optional.py b/tests/test_security_api_key_cookie_optional.py index 7988d8044..e911654fa 100644 --- a/tests/test_security_api_key_cookie_optional.py +++ b/tests/test_security_api_key_cookie_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyCookie from fastapi.testclient import TestClient @@ -15,7 +13,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(api_key)): +def get_current_user(oauth_header: str | None = Security(api_key)): if oauth_header is None: return None user = User(username=oauth_header) diff --git a/tests/test_security_api_key_header_optional.py b/tests/test_security_api_key_header_optional.py index 51abd0bb9..0a8cf420e 100644 --- a/tests/test_security_api_key_header_optional.py +++ b/tests/test_security_api_key_header_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyHeader from fastapi.testclient import TestClient @@ -15,7 +13,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(api_key)): +def get_current_user(oauth_header: str | None = Security(api_key)): if oauth_header is None: return None user = User(username=oauth_header) @@ -23,7 +21,7 @@ def get_current_user(oauth_header: Optional[str] = Security(api_key)): @app.get("/users/me") -def read_current_user(current_user: Optional[User] = Depends(get_current_user)): +def read_current_user(current_user: User | None = Depends(get_current_user)): if current_user is None: return {"msg": "Create an account first"} return current_user diff --git a/tests/test_security_api_key_query_optional.py b/tests/test_security_api_key_query_optional.py index 26fbb9ee4..e9fba3043 100644 --- a/tests/test_security_api_key_query_optional.py +++ b/tests/test_security_api_key_query_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyQuery from fastapi.testclient import TestClient @@ -15,7 +13,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(api_key)): +def get_current_user(oauth_header: str | None = Security(api_key)): if oauth_header is None: return None user = User(username=oauth_header) @@ -23,7 +21,7 @@ def get_current_user(oauth_header: Optional[str] = Security(api_key)): @app.get("/users/me") -def read_current_user(current_user: Optional[User] = Depends(get_current_user)): +def read_current_user(current_user: User | None = Depends(get_current_user)): if current_user is None: return {"msg": "Create an account first"} return current_user diff --git a/tests/test_security_http_base_optional.py b/tests/test_security_http_base_optional.py index 612a7909f..1d1944ab0 100644 --- a/tests/test_security_http_base_optional.py +++ b/tests/test_security_http_base_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBase from fastapi.testclient import TestClient @@ -12,7 +10,7 @@ security = HTTPBase(scheme="Other", auto_error=False) @app.get("/users/me") def read_current_user( - credentials: Optional[HTTPAuthorizationCredentials] = Security(security), + credentials: HTTPAuthorizationCredentials | None = Security(security), ): if credentials is None: return {"msg": "Create an account first"} diff --git a/tests/test_security_http_basic_optional.py b/tests/test_security_http_basic_optional.py index e94565c7b..78abf2b68 100644 --- a/tests/test_security_http_basic_optional.py +++ b/tests/test_security_http_basic_optional.py @@ -1,5 +1,4 @@ from base64 import b64encode -from typing import Optional from fastapi import FastAPI, Security from fastapi.security import HTTPBasic, HTTPBasicCredentials @@ -12,7 +11,7 @@ security = HTTPBasic(auto_error=False) @app.get("/users/me") -def read_current_user(credentials: Optional[HTTPBasicCredentials] = Security(security)): +def read_current_user(credentials: HTTPBasicCredentials | None = Security(security)): if credentials is None: return {"msg": "Create an account first"} return {"username": credentials.username, "password": credentials.password} diff --git a/tests/test_security_http_bearer_optional.py b/tests/test_security_http_bearer_optional.py index b49a6593e..06d9d03db 100644 --- a/tests/test_security_http_bearer_optional.py +++ b/tests/test_security_http_bearer_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi.testclient import TestClient @@ -12,7 +10,7 @@ security = HTTPBearer(auto_error=False) @app.get("/users/me") def read_current_user( - credentials: Optional[HTTPAuthorizationCredentials] = Security(security), + credentials: HTTPAuthorizationCredentials | None = Security(security), ): if credentials is None: return {"msg": "Create an account first"} diff --git a/tests/test_security_http_digest_optional.py b/tests/test_security_http_digest_optional.py index 97e62634d..d1056b191 100644 --- a/tests/test_security_http_digest_optional.py +++ b/tests/test_security_http_digest_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest from fastapi.testclient import TestClient @@ -12,7 +10,7 @@ security = HTTPDigest(auto_error=False) @app.get("/users/me") def read_current_user( - credentials: Optional[HTTPAuthorizationCredentials] = Security(security), + credentials: HTTPAuthorizationCredentials | None = Security(security), ): if credentials is None: return {"msg": "Create an account first"} diff --git a/tests/test_security_oauth2_authorization_code_bearer.py b/tests/test_security_oauth2_authorization_code_bearer.py index 1ba577e9f..587486c76 100644 --- a/tests/test_security_oauth2_authorization_code_bearer.py +++ b/tests/test_security_oauth2_authorization_code_bearer.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer from fastapi.testclient import TestClient @@ -13,7 +11,7 @@ oauth2_scheme = OAuth2AuthorizationCodeBearer( @app.get("/items/") -async def read_items(token: Optional[str] = Security(oauth2_scheme)): +async def read_items(token: str | None = Security(oauth2_scheme)): return {"token": token} diff --git a/tests/test_security_oauth2_authorization_code_bearer_description.py b/tests/test_security_oauth2_authorization_code_bearer_description.py index 73807c31a..f878ede64 100644 --- a/tests/test_security_oauth2_authorization_code_bearer_description.py +++ b/tests/test_security_oauth2_authorization_code_bearer_description.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer from fastapi.testclient import TestClient @@ -16,7 +14,7 @@ oauth2_scheme = OAuth2AuthorizationCodeBearer( @app.get("/items/") -async def read_items(token: Optional[str] = Security(oauth2_scheme)): +async def read_items(token: str | None = Security(oauth2_scheme)): return {"token": token} diff --git a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py index 583007c8b..6fcce6fed 100644 --- a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py +++ b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py @@ -1,6 +1,6 @@ # Ref: https://github.com/fastapi/fastapi/issues/14454 -from typing import Annotated, Optional +from typing import Annotated from fastapi import APIRouter, Depends, FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer @@ -46,13 +46,13 @@ router = APIRouter(dependencies=[Security(oauth2_scheme, scopes=["read"])]) @router.get("/items/") -async def read_items(token: Optional[str] = Depends(oauth2_scheme)): +async def read_items(token: str | None = Depends(oauth2_scheme)): return {"token": token} @router.post("/items/") async def create_item( - token: Optional[str] = Security(oauth2_scheme, scopes=["read", "write"]), + token: str | None = Security(oauth2_scheme, scopes=["read", "write"]), ): return {"token": token} diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py index cb79afdb8..a7eaf5944 100644 --- a/tests/test_security_oauth2_optional.py +++ b/tests/test_security_oauth2_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict @@ -24,7 +22,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)): +def get_current_user(oauth_header: str | None = Security(reusable_oauth2)): if oauth_header is None: return None user = User(username=oauth_header) @@ -37,7 +35,7 @@ def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): @app.get("/users/me") -def read_users_me(current_user: Optional[User] = Depends(get_current_user)): +def read_users_me(current_user: User | None = Depends(get_current_user)): if current_user is None: return {"msg": "Create an account first"} return current_user diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py index b3fae37a1..0918d352e 100644 --- a/tests/test_security_oauth2_optional_description.py +++ b/tests/test_security_oauth2_optional_description.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict @@ -25,7 +23,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)): +def get_current_user(oauth_header: str | None = Security(reusable_oauth2)): if oauth_header is None: return None user = User(username=oauth_header) @@ -38,7 +36,7 @@ def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): @app.get("/users/me") -def read_users_me(current_user: Optional[User] = Depends(get_current_user)): +def read_users_me(current_user: User | None = Depends(get_current_user)): if current_user is None: return {"msg": "Create an account first"} return current_user diff --git a/tests/test_security_oauth2_password_bearer_optional.py b/tests/test_security_oauth2_password_bearer_optional.py index 01e2f65ed..263359c95 100644 --- a/tests/test_security_oauth2_password_bearer_optional.py +++ b/tests/test_security_oauth2_password_bearer_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import OAuth2PasswordBearer from fastapi.testclient import TestClient @@ -11,7 +9,7 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token", auto_error=False) @app.get("/items/") -async def read_items(token: Optional[str] = Security(oauth2_scheme)): +async def read_items(token: str | None = Security(oauth2_scheme)): if token is None: return {"msg": "Create an account first"} return {"token": token} diff --git a/tests/test_security_oauth2_password_bearer_optional_description.py b/tests/test_security_oauth2_password_bearer_optional_description.py index fec8d03a7..0deb7e48f 100644 --- a/tests/test_security_oauth2_password_bearer_optional_description.py +++ b/tests/test_security_oauth2_password_bearer_optional_description.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import OAuth2PasswordBearer from fastapi.testclient import TestClient @@ -15,7 +13,7 @@ oauth2_scheme = OAuth2PasswordBearer( @app.get("/items/") -async def read_items(token: Optional[str] = Security(oauth2_scheme)): +async def read_items(token: str | None = Security(oauth2_scheme)): if token is None: return {"msg": "Create an account first"} return {"token": token} diff --git a/tests/test_security_openid_connect_optional.py b/tests/test_security_openid_connect_optional.py index ebaf394dc..44e1a4866 100644 --- a/tests/test_security_openid_connect_optional.py +++ b/tests/test_security_openid_connect_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, FastAPI, Security from fastapi.security.open_id_connect_url import OpenIdConnect from fastapi.testclient import TestClient @@ -15,7 +13,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(oid)): +def get_current_user(oauth_header: str | None = Security(oid)): if oauth_header is None: return None user = User(username=oauth_header) @@ -23,7 +21,7 @@ def get_current_user(oauth_header: Optional[str] = Security(oid)): @app.get("/users/me") -def read_current_user(current_user: Optional[User] = Depends(get_current_user)): +def read_current_user(current_user: User | None = Depends(get_current_user)): if current_user is None: return {"msg": "Create an account first"} return current_user diff --git a/tests/test_serialize_response.py b/tests/test_serialize_response.py index 14f88dd93..114c3c6cb 100644 --- a/tests/test_serialize_response.py +++ b/tests/test_serialize_response.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -9,8 +7,8 @@ app = FastAPI() class Item(BaseModel): name: str - price: Optional[float] = None - owner_ids: Optional[list[int]] = None + price: float | None = None + owner_ids: list[int] | None = None @app.get("/items/valid", response_model=Item) diff --git a/tests/test_serialize_response_dataclass.py b/tests/test_serialize_response_dataclass.py index ee695368b..ae05f14d1 100644 --- a/tests/test_serialize_response_dataclass.py +++ b/tests/test_serialize_response_dataclass.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from datetime import datetime -from typing import Optional from fastapi import FastAPI from fastapi.testclient import TestClient @@ -12,8 +11,8 @@ app = FastAPI() class Item: name: str date: datetime - price: Optional[float] = None - owner_ids: Optional[list[int]] = None + price: float | None = None + owner_ids: list[int] | None = None @app.get("/items/valid", response_model=Item) diff --git a/tests/test_serialize_response_model.py b/tests/test_serialize_response_model.py index 79c90c9c2..bb05f7bc4 100644 --- a/tests/test_serialize_response_model.py +++ b/tests/test_serialize_response_model.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from pydantic import BaseModel, Field from starlette.testclient import TestClient @@ -9,8 +7,8 @@ app = FastAPI() class Item(BaseModel): name: str = Field(alias="aliased_name") - price: Optional[float] = None - owner_ids: Optional[list[int]] = None + price: float | None = None + owner_ids: list[int] | None = None @app.get("/items/valid", response_model=Item) diff --git a/tests/test_skip_defaults.py b/tests/test_skip_defaults.py index 02765291c..238da7392 100644 --- a/tests/test_skip_defaults.py +++ b/tests/test_skip_defaults.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -8,23 +6,23 @@ app = FastAPI() class SubModel(BaseModel): - a: Optional[str] = "foo" + a: str | None = "foo" class Model(BaseModel): - x: Optional[int] = None + x: int | None = None sub: SubModel class ModelSubclass(Model): y: int z: int = 0 - w: Optional[int] = None + w: int | None = None class ModelDefaults(BaseModel): - w: Optional[str] = None - x: Optional[str] = None + w: str | None = None + x: str | None = None y: str = "y" z: str = "z" diff --git a/tests/test_sub_callbacks.py b/tests/test_sub_callbacks.py index 86dc4d00e..b8a9dd292 100644 --- a/tests/test_sub_callbacks.py +++ b/tests/test_sub_callbacks.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -10,7 +8,7 @@ app = FastAPI() class Invoice(BaseModel): id: str - title: Optional[str] = None + title: str | None = None customer: str total: float @@ -51,7 +49,7 @@ subrouter = APIRouter() @subrouter.post("/invoices/", callbacks=invoices_callback_router.routes) -def create_invoice(invoice: Invoice, callback_url: Optional[HttpUrl] = None): +def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None): """ Create an invoice. diff --git a/tests/test_union_body.py b/tests/test_union_body.py index e333e2499..88f9e06cc 100644 --- a/tests/test_union_body.py +++ b/tests/test_union_body.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -9,7 +7,7 @@ app = FastAPI() class Item(BaseModel): - name: Optional[str] = None + name: str | None = None class OtherItem(BaseModel): @@ -17,7 +15,7 @@ class OtherItem(BaseModel): @app.post("/items/") -def save_union_body(item: Union[OtherItem, Item]): +def save_union_body(item: OtherItem | Item): return {"item": item} diff --git a/tests/test_union_body_discriminator.py b/tests/test_union_body_discriminator.py index 4afe7be4b..1b682c775 100644 --- a/tests/test_union_body_discriminator.py +++ b/tests/test_union_body_discriminator.py @@ -1,10 +1,9 @@ -from typing import Annotated, Any, Union +from typing import Annotated, Any, Literal from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot from pydantic import BaseModel, Field -from typing_extensions import Literal def test_discriminator_pydantic_v2() -> None: @@ -21,7 +20,7 @@ def test_discriminator_pydantic_v2() -> None: price: float Item = Annotated[ - Union[Annotated[FirstItem, Tag("first")], Annotated[OtherItem, Tag("other")]], + Annotated[FirstItem, Tag("first")] | Annotated[OtherItem, Tag("other")], Field(discriminator="value"), ] diff --git a/tests/test_union_body_discriminator_annotated.py b/tests/test_union_body_discriminator_annotated.py index 6644d106c..7e64ea75b 100644 --- a/tests/test_union_body_discriminator_annotated.py +++ b/tests/test_union_body_discriminator_annotated.py @@ -1,6 +1,6 @@ # Ref: https://github.com/fastapi/fastapi/discussions/14495 -from typing import Annotated, Union +from typing import Annotated import pytest from fastapi import FastAPI @@ -27,7 +27,7 @@ def client_fixture() -> TestClient: return v.get("pet_type", "") Pet = Annotated[ - Union[Annotated[Cat, Tag("cat")], Annotated[Dog, Tag("dog")]], + Annotated[Cat, Tag("cat")] | Annotated[Dog, Tag("dog")], Discriminator(get_pet_type), ] diff --git a/tests/test_union_forms.py b/tests/test_union_forms.py index f6c2658f9..8cd7b4f01 100644 --- a/tests/test_union_forms.py +++ b/tests/test_union_forms.py @@ -1,4 +1,4 @@ -from typing import Annotated, Union +from typing import Annotated from fastapi import FastAPI, Form from fastapi.testclient import TestClient @@ -19,7 +19,7 @@ class CompanyForm(BaseModel): @app.post("/form-union/") -def post_union_form(data: Annotated[Union[UserForm, CompanyForm], Form()]): +def post_union_form(data: Annotated[UserForm | CompanyForm, Form()]): return {"received": data} diff --git a/tests/test_union_inherited_body.py b/tests/test_union_inherited_body.py index 5378880a4..c997a87a3 100644 --- a/tests/test_union_inherited_body.py +++ b/tests/test_union_inherited_body.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -9,7 +7,7 @@ app = FastAPI() class Item(BaseModel): - name: Optional[str] = None + name: str | None = None class ExtendedItem(Item): @@ -17,7 +15,7 @@ class ExtendedItem(Item): @app.post("/items/") -def save_union_different_body(item: Union[ExtendedItem, Item]): +def save_union_different_body(item: ExtendedItem | Item): return {"item": item} diff --git a/tests/test_validate_response.py b/tests/test_validate_response.py index 938d41956..7288220ea 100644 --- a/tests/test_validate_response.py +++ b/tests/test_validate_response.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - import pytest from fastapi import FastAPI from fastapi.exceptions import ResponseValidationError @@ -11,8 +9,8 @@ app = FastAPI() class Item(BaseModel): name: str - price: Optional[float] = None - owner_ids: Optional[list[int]] = None + price: float | None = None + owner_ids: list[int] | None = None @app.get("/items/invalid", response_model=Item) @@ -25,7 +23,7 @@ def get_invalid_none(): return None -@app.get("/items/validnone", response_model=Union[Item, None]) +@app.get("/items/validnone", response_model=Item | None) def get_valid_none(send_none: bool = False): if send_none: return None diff --git a/tests/test_validate_response_dataclass.py b/tests/test_validate_response_dataclass.py index 67282bcde..03b7d5f33 100644 --- a/tests/test_validate_response_dataclass.py +++ b/tests/test_validate_response_dataclass.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import FastAPI from fastapi.exceptions import ResponseValidationError @@ -12,8 +10,8 @@ app = FastAPI() @dataclass class Item: name: str - price: Optional[float] = None - owner_ids: Optional[list[int]] = None + price: float | None = None + owner_ids: list[int] | None = None @app.get("/items/invalid", response_model=Item) diff --git a/tests/utils.py b/tests/utils.py index 4cbfee79f..09c4e13b0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,7 +2,6 @@ import sys import pytest -needs_py39 = pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9+") needs_py310 = pytest.mark.skipif( sys.version_info < (3, 10), reason="requires python3.10+" ) From da4135ce1eb4df17a4643a21101645951bfb3ab3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Feb 2026 09:59:37 +0000 Subject: [PATCH 143/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1f72f883a..34492bfc5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -29,6 +29,7 @@ hide: ### Internal +* 🎨 Upgrade typing syntax for Python 3.10. PR [#14932](https://github.com/fastapi/fastapi/pull/14932) by [@tiangolo](https://github.com/tiangolo). * ⬆ Bump cryptography from 46.0.4 to 46.0.5. PR [#14892](https://github.com/fastapi/fastapi/pull/14892) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump pillow from 12.1.0 to 12.1.1. PR [#14899](https://github.com/fastapi/fastapi/pull/14899) by [@dependabot[bot]](https://github.com/apps/dependabot). From 339f7028105fdca80296974aeafa7beee59f1f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 18 Feb 2026 07:26:13 -0800 Subject: [PATCH 144/195] =?UTF-8?q?=F0=9F=94=A8=20Add=20Kapa.ai=20widget?= =?UTF-8?q?=20(AI=20chatbot)=20(#14938)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alejandra --- docs/en/docs/css/custom.css | 4 ++++ docs/en/docs/js/init_kapa_widget.js | 29 +++++++++++++++++++++++++++++ docs/en/mkdocs.yml | 1 + 3 files changed, 34 insertions(+) create mode 100644 docs/en/docs/js/init_kapa_widget.js diff --git a/docs/en/docs/css/custom.css b/docs/en/docs/css/custom.css index 7c50dbd9b..dc9c7d63b 100644 --- a/docs/en/docs/css/custom.css +++ b/docs/en/docs/css/custom.css @@ -61,6 +61,10 @@ a.internal-link::after { padding-bottom: 2em; } +.md-footer-meta .md-social { + padding-right: 4rem; +} + .user-list { display: flex; flex-wrap: wrap; diff --git a/docs/en/docs/js/init_kapa_widget.js b/docs/en/docs/js/init_kapa_widget.js new file mode 100644 index 000000000..eaf123bf3 --- /dev/null +++ b/docs/en/docs/js/init_kapa_widget.js @@ -0,0 +1,29 @@ +document.addEventListener("DOMContentLoaded", function () { + var script = document.createElement("script"); + script.src = "https://widget.kapa.ai/kapa-widget.bundle.js"; + script.setAttribute("data-website-id", "91f47f27-b405-4299-bf5f-a1c0ec07b3cc"); + script.setAttribute("data-project-name", "FastAPI"); + script.setAttribute("data-project-color", "#009485"); + script.setAttribute("data-project-logo", "https://fastapi.tiangolo.com/img/favicon.png"); + script.setAttribute("data-bot-protection-mechanism", "hcaptcha"); + script.setAttribute("data-button-height", "3rem"); + script.setAttribute("data-button-width", "3rem"); + script.setAttribute("data-button-border-radius", "50%"); + script.setAttribute("data-button-padding", "0"); + script.setAttribute("data-button-image", "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 8V4H8'/%3E%3Crect width='16' height='12' x='4' y='8' rx='2'/%3E%3Cpath d='M2 14h2'/%3E%3Cpath d='M20 14h2'/%3E%3Cpath d='M15 13v2'/%3E%3Cpath d='M9 13v2'/%3E%3C/svg%3E"); + script.setAttribute("data-button-image-height", "20px"); + script.setAttribute("data-button-image-width", "20px"); + script.setAttribute("data-button-text", "Ask AI"); + script.setAttribute("data-button-text-font-size", "0.5rem"); + script.setAttribute("data-button-text-font-family", "Roboto, sans-serif"); + script.setAttribute("data-button-text-color", "#FFFFFF"); + script.setAttribute("data-modal-border-radius", "0.5rem"); + script.setAttribute("data-modal-header-bg-color", "#009485"); + script.setAttribute("data-modal-title", "FastAPI AI Assistant"); + script.setAttribute("data-modal-title-color", "#FFFFFF"); + script.setAttribute("data-modal-title-font-family", "Roboto, sans-serif"); + script.setAttribute("data-modal-example-questions", "How to define a route?,How to validate models?,How to handle responses?,How to deploy FastAPI?"); + script.setAttribute("data-modal-disclaimer", "AI-generated answers based on FastAPI [documentation](https://fastapi.tiangolo.com/) and [community discussions](https://github.com/fastapi/fastapi/discussions). Always verify important information."); + script.async = true; + document.head.appendChild(script); +}); diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 66ad67e9d..96ed3d586 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -342,5 +342,6 @@ extra_css: extra_javascript: - js/termynal.js - js/custom.js +- js/init_kapa_widget.js hooks: - ../../scripts/mkdocs_hooks.py From d178b95695231e908c5ce398b2655223bcad16cf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 18 Feb 2026 21:19:34 +0000 Subject: [PATCH 145/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 34492bfc5..6242d037d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* 🔨 Add Kapa.ai widget (AI chatbot). PR [#14938](https://github.com/fastapi/fastapi/pull/14938) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove Python 3.9 specific files, no longer needed after updating translations. PR [#14931](https://github.com/fastapi/fastapi/pull/14931) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs for JWT to prevent timing attacks. PR [#14908](https://github.com/fastapi/fastapi/pull/14908) by [@tiangolo](https://github.com/tiangolo). From 3bdf470622e44a7a804225ab5c0976d7f4aba0b0 Mon Sep 17 00:00:00 2001 From: argoarsiks Date: Thu, 19 Feb 2026 00:30:43 +0300 Subject: [PATCH 146/195] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20several=20ty?= =?UTF-8?q?pos=20in=20ru=20translations=20(#14934)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: fix typos * Apply suggestion from @YuriiMotov --------- Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- docs/ru/docs/advanced/middleware.md | 2 +- docs/ru/docs/deployment/docker.md | 2 +- docs/ru/docs/history-design-future.md | 2 +- docs/ru/docs/tutorial/security/oauth2-jwt.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ru/docs/advanced/middleware.md b/docs/ru/docs/advanced/middleware.md index 034feae7e..1f1a16060 100644 --- a/docs/ru/docs/advanced/middleware.md +++ b/docs/ru/docs/advanced/middleware.md @@ -83,7 +83,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") Поддерживаются следующие аргументы: - `minimum_size` — не сжимать GZip‑ом ответы, размер которых меньше этого минимального значения в байтах. По умолчанию — `500`. -- `compresslevel` — уровень GZip‑сжатия. Целое число от 1 до 9. По умолчанию — `9`. Более низкое значение — быстреее сжатие, но больший размер файла; более высокое значение — более медленное сжатие, но меньший размер файла. +- `compresslevel` — уровень GZip‑сжатия. Целое число от 1 до 9. По умолчанию — `9`. Более низкое значение — быстрее сжатие, но больший размер файла; более высокое значение — более медленное сжатие, но меньший размер файла. ## Другие middleware { #other-middlewares } diff --git a/docs/ru/docs/deployment/docker.md b/docs/ru/docs/deployment/docker.md index 791057fe5..5dfa21159 100644 --- a/docs/ru/docs/deployment/docker.md +++ b/docs/ru/docs/deployment/docker.md @@ -214,7 +214,7 @@ CMD ["fastapi", "run", "app/main.py", "--port", "80"] 5. Копируем директорию `./app` внутрь директории `/code`. - Так как здесь весь код, который **меняется чаще всего**, кэш Docker **вряд ли** будет использоваться для этого шагa или **последующих шагов**. + Так как здесь весь код, который **меняется чаще всего**, кэш Docker **вряд ли** будет использоваться для этого шага или **последующих шагов**. Поэтому важно разместить этот шаг **ближе к концу** `Dockerfile`, чтобы оптимизировать время сборки образа контейнера. diff --git a/docs/ru/docs/history-design-future.md b/docs/ru/docs/history-design-future.md index e2395fe8b..501915760 100644 --- a/docs/ru/docs/history-design-future.md +++ b/docs/ru/docs/history-design-future.md @@ -76,4 +76,4 @@ У **FastAPI** великое будущее. -И [ваш вклад в это](help-fastapi.md){.internal-link target=_blank} - очень ценнен. +И [ваш вклад в это](help-fastapi.md){.internal-link target=_blank} - очень ценен. diff --git a/docs/ru/docs/tutorial/security/oauth2-jwt.md b/docs/ru/docs/tutorial/security/oauth2-jwt.md index 7838b07df..f7853d48f 100644 --- a/docs/ru/docs/tutorial/security/oauth2-jwt.md +++ b/docs/ru/docs/tutorial/security/oauth2-jwt.md @@ -20,7 +20,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4 Но он подписан. Следовательно, когда вы получаете токен, который вы эмитировали (выдавали), вы можете убедиться, что это именно вы его эмитировали. -Таким образом, можно создать токен со сроком действия, скажем, 1 неделя. А когда пользователь вернется на следующий день с тем же токеном, вы будете знать, что он все еще авторизирован в вашей системе. +Таким образом, можно создать токен со сроком действия, скажем, 1 неделя. А когда пользователь вернется на следующий день с тем же токеном, вы будете знать, что он все еще авторизован в вашей системе. Через неделю срок действия токена истечет, пользователь не будет авторизован и ему придется заново входить в систему, чтобы получить новый токен. А если пользователь (или третье лицо) попытается модифицировать токен, чтобы изменить срок действия, вы сможете это обнаружить, поскольку подписи не будут совпадать. From c44158384c46c0ceab81dea8d34def67cb318c67 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 18 Feb 2026 21:31:13 +0000 Subject: [PATCH 147/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 6242d037d..4bb2aa990 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Translations +* ✏️ Fix several typos in ru translations. PR [#14934](https://github.com/fastapi/fastapi/pull/14934) by [@argoarsiks](https://github.com/argoarsiks). * 🌐 Update translations for ko (update-all and add-missing). PR [#14923](https://github.com/fastapi/fastapi/pull/14923) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for uk (add-missing). PR [#14922](https://github.com/fastapi/fastapi/pull/14922) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Update translations for zh-hant (update-all and add-missing). PR [#14921](https://github.com/fastapi/fastapi/pull/14921) by [@YuriiMotov](https://github.com/YuriiMotov). From cf058239d14f3957e69848c81bae42afe4270a30 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:40:05 +0100 Subject: [PATCH 148/195] =?UTF-8?q?=F0=9F=91=B7=20Always=20run=20tests=20o?= =?UTF-8?q?n=20push=20to=20`master`=20branch=20and=20when=20run=20by=20sch?= =?UTF-8?q?eduler=20(#14940)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 338f6c390..d61b4add9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,7 +45,7 @@ jobs: test: needs: - changes - if: needs.changes.outputs.src == 'true' + if: needs.changes.outputs.src == 'true' || github.ref == 'refs/heads/master' strategy: matrix: os: [ windows-latest, macos-latest ] From d2c17b603d2f986d5f3800d3bf24acba8e1f485b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 20 Feb 2026 15:40:34 +0000 Subject: [PATCH 149/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4bb2aa990..d579a3cb0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -31,6 +31,7 @@ hide: ### Internal +* 👷 Always run tests on push to `master` branch and when run by scheduler. PR [#14940](https://github.com/fastapi/fastapi/pull/14940) by [@YuriiMotov](https://github.com/YuriiMotov). * 🎨 Upgrade typing syntax for Python 3.10. PR [#14932](https://github.com/fastapi/fastapi/pull/14932) by [@tiangolo](https://github.com/tiangolo). * ⬆ Bump cryptography from 46.0.4 to 46.0.5. PR [#14892](https://github.com/fastapi/fastapi/pull/14892) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Bump pillow from 12.1.0 to 12.1.1. PR [#14899](https://github.com/fastapi/fastapi/pull/14899) by [@dependabot[bot]](https://github.com/apps/dependabot). From e8b98d21871f64234520c1e770f39e6bfea3b0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 21 Feb 2026 05:01:31 -0800 Subject: [PATCH 150/195] =?UTF-8?q?=F0=9F=90=9B=20Fix=20JSON=20Schema=20fo?= =?UTF-8?q?r=20files,=20use=20`contentMediaType`=20instead=20of=20`format:?= =?UTF-8?q?=20binary`=20(#14953)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/json-base64-bytes.md | 63 +++++ .../tutorial/json-base64-bytes/image01.png | Bin 0 -> 72638 bytes docs/en/mkdocs.yml | 1 + docs_src/json_base64_bytes/__init__.py | 0 .../json_base64_bytes/tutorial001_py310.py | 46 ++++ fastapi/_compat/v2.py | 19 +- fastapi/datastructures.py | 2 +- pyproject.toml | 1 + .../playwright/json_base64_bytes/image01.py | 37 +++ .../test_file/test_list.py | 20 +- .../test_file/test_optional.py | 8 +- .../test_file/test_optional_list.py | 20 +- .../test_file/test_required.py | 16 +- .../test_json_base64_bytes/__init__.py | 0 .../test_tutorial001.py | 225 ++++++++++++++++++ .../test_request_files/test_tutorial001.py | 4 +- .../test_request_files/test_tutorial001_02.py | 10 +- .../test_request_files/test_tutorial001_03.py | 4 +- .../test_request_files/test_tutorial002.py | 10 +- .../test_request_files/test_tutorial003.py | 10 +- .../test_tutorial001.py | 4 +- 21 files changed, 470 insertions(+), 30 deletions(-) create mode 100644 docs/en/docs/advanced/json-base64-bytes.md create mode 100644 docs/en/docs/img/tutorial/json-base64-bytes/image01.png create mode 100644 docs_src/json_base64_bytes/__init__.py create mode 100644 docs_src/json_base64_bytes/tutorial001_py310.py create mode 100644 scripts/playwright/json_base64_bytes/image01.py create mode 100644 tests/test_tutorial/test_json_base64_bytes/__init__.py create mode 100644 tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py diff --git a/docs/en/docs/advanced/json-base64-bytes.md b/docs/en/docs/advanced/json-base64-bytes.md new file mode 100644 index 000000000..c0dfec72b --- /dev/null +++ b/docs/en/docs/advanced/json-base64-bytes.md @@ -0,0 +1,63 @@ +# JSON with Bytes as Base64 { #json-with-bytes-as-base64 } + +If your app needs to receive and send JSON data, but you need to include binary data in it, you can encode it as base64. + +## Base64 vs Files { #base64-vs-files } + +Consider first if you can use [Request Files](../tutorial/request-files.md){.internal-link target=_blank} for uploading binary data and [Custom Response - FileResponse](./custom-response.md#fileresponse--fileresponse-){.internal-link target=_blank} for sending binary data, instead of encoding it in JSON. + +JSON can only contain UTF-8 encoded strings, so it can't contain raw bytes. + +Base64 can encode binary data in strings, but to do it, it needs to use more characters than the original binary data, so it would normally be less efficient than regular files. + +Use base64 only if you definitely need to include binary data in JSON, and you can't use files for that. + +## Pydantic `bytes` { #pydantic-bytes } + +You can declare a Pydantic model with `bytes` fields, and then use `val_json_bytes` in the model config to tell it to use base64 to *validate* input JSON data, as part of that validation it will decode the base64 string into bytes. + +{* ../../docs_src/json_base64_bytes/tutorial001_py310.py ln[1:9,29:35] hl[9] *} + +If you check the `/docs`, they will show that the field `data` expects base64 encoded bytes: + +
+ +
+ +You could send a request like: + +```json +{ + "description": "Some data", + "data": "aGVsbG8=" +} +``` + +/// tip + +`aGVsbG8=` is the base64 encoding of `hello`. + +/// + +And then Pydantic will decode the base64 string and give you the original bytes in the `data` field of the model. + +You will receive a response like: + +```json +{ + "description": "Some data", + "content": "hello" +} +``` + +## Pydantic `bytes` for Output Data { #pydantic-bytes-for-output-data } + +You can also use `bytes` fields with `ser_json_bytes` in the model config for output data, and Pydantic will *serialize* the bytes as base64 when generating the JSON response. + +{* ../../docs_src/json_base64_bytes/tutorial001_py310.py ln[1:2,12:16,29,38:41] hl[16] *} + +## Pydantic `bytes` for Input and Output Data { #pydantic-bytes-for-input-and-output-data } + +And of course, you can use the same model configured to use base64 to handle both input (*validate*) with `val_json_bytes` and output (*serialize*) with `ser_json_bytes` when receiving and sending JSON data. + +{* ../../docs_src/json_base64_bytes/tutorial001_py310.py ln[1:2,19:26,29,44:46] hl[23:26] *} diff --git a/docs/en/docs/img/tutorial/json-base64-bytes/image01.png b/docs/en/docs/img/tutorial/json-base64-bytes/image01.png new file mode 100644 index 0000000000000000000000000000000000000000..996732b7499a97cbb7ced1612cd87962ecc3292e GIT binary patch literal 72638 zcmeFZbx@mK)Gtbf(iSVFKq>a(?(XjH#ft`tLxNjNTZ$JaxLY8&1}R<$5ZqmZyW54n z-#7PsbLZMQXXehCxqm&$&a-Xpwbx$1-&#AtYAUjruRpv-K|#Tkmy^;!L3#EY1?4&4 z%cl<|^1~E7C@61Gqf^Kg`SFP;_tLrUcdbM)v)W9{A_{gLa8tv@#YImKTwY!-NA-d2lh}$kI~LdDk5%sB-Csn2 zNHsS%w-s-DdwW{dv{@(I|LCoteKd_s0fAwK#o4k%^Ym{GvDDSo)%*cj?Em;yQBm>m z?e8#A2A}@>P5}P@>rv|eJJbIanoxt0x-(w7>AN|I>(v+M<^2%S6c7;Tk`XenMOWAD zPTQON8NYn&mW!X-#~8_=yE^&?&xZdUT<#NH1@EbQO`=)Pw)lhSGmM5k*KJ-+jvR>!{q}(saoL-Zmx^W`mJF{JWtX-xI4uSmH~uwr(&JDkvT*4W1QhfutKww)--Bg941umNW8ciB5r~bbM7`wTKVfyvyG3ukD^=M*E=m7DN3}>?nKJDRxJ~n z+-RP?d|YPV&_5z?d1*}C0w3NG^Ztr5r(zTd8`Id- z3!D;k#ZaEdORt5T<^wSGweQ@I>>ky1_mw{1bm4KPZgNVg<0gm2FBoJnCEp;*n|8fp zYTImz5}xQz!qK}h6R#QPY;&Bs{r!Z}U`fvt`(%@{f`^x?VrO-6B*E+mPnlFQrhhPD zq}jTWHwFv+1%IQZBl5Q?jz*j=lc4YI9)R7_^PFI)+66~CqNa+1N|;@QscC0uRe@*6Li`3ci{-FMh-cdm6SSA0IpEXdZesQCC>!kELU)9A*cAgs{2 ziIGhLe?@@sS~|eYI1{iblW8lWk55IdzkCX=G}jK` z4y}@o{+VvYESPZpTPXB!q4>H0`&=HveO%A6Xkef>J*f|L-s85bbJT4esil11;IXmX z{8cthDeq+FL@)44=T?@jClOT2$C_YnIcNX7!`reYMLBK`5zXW}@dQ`4!9a7h9Eb@! zBjPB^573h9_->e40!Jp`4c{j0=?FFc)G@s z?k#%eFC&Ic)>@Jv`Fe!AJGYx~bWF>YBV7`3oQ6Z4zn0{q6n+2_9!<_KEvR#X{i7PW zGBRZGE#H|}c=}vPJ^QAgkhkEP-2ItEQEteG=2!k*|3Kpy++4zT=5M9Ltgu0PD`puB ztCY=Am3WWFE8|uSz1v)VIndpHs@@qsU<8P^>?agbrVhsr9q{#V5SRKGy#o$w(xLBH zR*YzVcQ#XV>Zt7LIMr&=BRXeyv|B~9v27q&KUpESbh_Y1cDFW2&91Bj`zb)k{d%#1 z*fo>8-gWb3Oc7Zvh|<L+a=#FMDTSzT%{_<;UbVSXeFbxF-r4b3F>sbPwCU%yNBbZO7zTch4x4H z>82RjOO+d@?7|I0ji@k{hod3j?Aenmc4YCdxt1H3g9}Hr$bErStsF?=weq~DvEVruT0|U&~pLyV;ZL_hzqElwSdgl~XvBmJ)Nov~qWmoAL zISZ~LOd^b13=Ss&QVWx_Dc!LNuAS7cT~sLs%oz}dM6OWd;|Qu!p(0a7!xfwwyY-m% zFJ)oRI;+Rl8LoOytM)k(ZQ8|CJ*o+HCwk|7-UQ_U-tDv)(NJFn3J^|UGUsco_kyYU z0}cDooWbs1%JXwoJ!mV&*br7DTe{wHvhd9&UT0T7yruUiRbvyh+O;kWL@lxBA?TvB z?kjVA;8-E6(e9h0_kV4{v!}ojWaGu4RIUz0?k!~&wenmP0ZCE;h*8niBE|5&|FFvc zO(wiInTc$BlhEa4lw4a~yZQ0EyyA65>}#ZhDxl^r$r2Md9k)4BUg7$T6vg?ecY4IRrm3Wc;rE_x`&@P{>pLGU)lrTOC zabkhwu`Wxio{(ot;QY0YBHNA1ee5?abH^74DwTqSN68{~frPl%j2oV}gN5r7Qg378 z_Jy5l74%O#o{X#kNgqzxfU~6*!QfvL=F4rmv(Hs+9oAodBhhbuk~Co2+Z8umoEa?> z)G>=dJne=SRn36D18h9xqC4_SGo~W9mo^ln;TF`LQ+kIJrO)TA4J))|_UGMe5X(a( z^ur-SmAr8ZF?t|U*V-#%U)(#sJOyS}zEstPn1r+eod{$HnW^e6*khlxFlzlL?LHI9 zhQpG1n{&-tq!TaU()?k}Os$DXlfXlZ7+Kb`iLCx;T#CO9DA(NdHAyK%heZ?N97Z?$t@qDO zuB1T@M`)ENE05Yqdd?%2@exv6rX;&n3WHrTjd>~*#YIJ0x(TOr5oyq?Q6XPp{6c6^ zW4;jtgvShcJ?Z6DO*CY=soCeP1X7&`&L#0j2d%$%w%byrXrzTGY5r<9RN=+Nr-zEZ z7VZVB7GAR}*wCBu13Au@*wSH4ZitFWqn2=SBjsX;g^?4R*_Z^9pLBv5X`>+I^yPu| z3~6ey4De4&)cs()MoP2O$_c6wz&#;j$*R(~_zaL^r2cEYjr!HY+D%8+u!p5GQ43HB zT%M)E2*G+iU{sDIW~4Rk)?}frYH4QRwLqefpK_d#u)wl{gua))j+dLCKqU)qGDh9g zFNN6!`^eu>ehxP>Lz<-J4og`v*zRk?hcIKJW{6w#ZskExbv6^(oT`J5)XA+3pgFlV zIYEJnPg6}dCcfiahvMxE-Z`NROowy!k*;)uLj$XJVoQC)c)QqCopdF;66U1WgGJF2 z^8DqT)#WvVs!af@G9HSYEGk|7+-}Z&>j84S=)Rkf?i0td6T!rpgVWgPHp45i_V2=)$cZeIF_pgFMV@*jt#^s5?Jw*Ur3CmE^yeCEKy zmpH*L-yVbk`U>T$@`UQ>(KWog?{p5Nu8pjK$wdb7pLi<1Asf`OAS&U?ONL{Tv9)IP7GFQ1Xbp$_@U7?q zKrMiGb9JPBxwh|`-h_r;oo!PsnK`sUfQBYtMBv`o&Bd?=l5UUCHLhe~FRK`*{T>zU z4i7GrUC@;gKw273`%tNyjE>ADwXYi#Dt42k+&m3qgu9rVRVew&G%cGZdT$;?loi)m z1baI|$Z@+tC0zJPl|`e;B_6X;LR|TkQw1~9{-5(8d#Mrc(res5n2sD?D50NzqVe*= zq9OLxNPQP`XY@m3Vc4q?HIy!-rP;|_*4e&mY=wF8%+TDCW5D=c%w-eR=Jcg^Xo+L8 zU#z-YsFTl*r&8+2B=rN?)>}S*Rb%IE(xHt+pG$@GS_Q}@$;o974ZWm`q9vbs!F2+U zwtrgPox=;jXR>1l;+jgrW7fPp0B;Z(Q*14VE6)N{t=97;hID)*=_5p$4jT7;OP&fc z>mRnn&fkzt(7T+wya@sTf=Z05@`6up{?WTRluqA`ymQaCC_wOqwX{%vNr)uUd`D1s zdA`Uqt!@n@_r#x+At8OQodk_#tRhB0^Sya-&9iCt>JkXNIQVFEGaVf)`!(^pf=KHV zS!@k`#?}%;XPcU4g$0Qqjz`6_-VNU8K&~||{k(?dv)oErTqg zYFy6l1LSwD+2VP&^_Sa@^2e1l`{DOHHHN)I?t8yMR>PHzcHQ6WGRdVqrAlRKD@nbS z{$UBBD-r!u)H~u1-)A?$1Fi`cJf2;t#PR-_b=+>@jeigx`l>Tid-#E)s!b7rO=}pcuv$BQP`ku=nPqWMT~>>nb*8OMA-bkHz5)ed&ARtx!=Xg!=$vK}u?wX?@_=Sqv?_-wLFAw? zVA+{}nxF>l+VlQW_aOTi^4gK?_VjEBZN2by>HX>HX#>&spc)?GPcy;0>8SdjpQqY7<*aSN!l%Hwosc}$r}7er0bMYo&@F3R~sBol=h`sXgJ?iBFc z^R?$@#9|!@wkXLO&9jshxYdMOptNyU}t#$RC1M(^b|g-A?d5!8O=Q(o9UbA0 zk^K1G->v9GT?NI~Cnb5CE;u=SWrCHcuII#ApK}<7Rf8`x?HEIUyS|EV7#>2&+yP7Q zj+r>5sg_3G93>|1DCdl2f0SVj=D~efaBJ0v*{~>ws@Mv>txSz<;VjJ@WpE;$?q2RV z{1M9(uG*WAHkyv}QBPx*&g)p|c^9^{l51%pXV<%Q=8Wg~Jmc}>HL1jRHqhM470H-= zvV2fvvjgULr!O;Qq&I`dAZehU&qv_=Z-3s^l$*02Uo_J98A(Q6EE23>%0h&T4IKqPhcDv{6MYPg&R#~o4BReiL zt_6t6#x;Fp~&HTVMr6q^V}M>B!}olHe1>Dbd>VZ7GrhXArYX(eLTN3 zKPL6hSQ9KX`&1M%B~rw3tuQ%veM_|NAi{!8-1xh*Tk#FPRzgPHA^9PH`)->4Zv68S zLR_h&R!Vf{Ej+sHoI7hqQ_5w=HTlX_|oM%OB};q9Ij4FOQCX89@)PwZvxc zGyPU;S(txv1}1SgtFh1oqC(rKjp(7}3}NejTAVINqx!qZ*dV}hAG1KB6aP0lDpe-A z$_tT79kNY#hkM6XKxwW#>2bekDoN#b=;{nvu`##GwHl)$dbk|rwE8?2ESl45`I+Qb zE`nr$!{-t8)fG`EBuXi*kw4Dk1k#f4S;WwPu*}!jIaacE$}Wgh-nX9EE%5?c+nlq0 zFt4T=@|zp=#X!p?=)TPStnZ6tViY(rQ~L-6y68A;W`hk#%e^W1E6A>kc`E>XVRa252Xcwz0;pL^&J ztp?N-RKKuppxXFyh5-f1YHqcnI&@n``3EcuoV-e$0W2aZA@eiuOvqZ}`1LfBtVcAM zU_@_+kSHG`_U#OByUe$Gfk#p2>w7Im={1f)(m9c?J6FXrPak#$O2ECEE#@FYlHcNv zXs*PW*!Fj-tBWtT*lC7xyi8(>Z_V3cDqUO^U5<`y7+dN&eaY(Y$ShY@7^iW_=GeX+ zuUu1GgHxJCJAfp5M{$E)6K-xNN1i@tFDZA{NIAQ9U`so?1>#TnxyIb$Rg+-)bHN3<1vRHsBUPeZA zk}E2z!2F-Qg9hH-9`4YEIDOdI>_ZO52FW~)q79PX;AybmU$Z>^7Q%rT=!WmzDQoLU zdFZ%at3Gy|!Tqo<*3aNj6`yDt`$Tfs8Qj3pU#tfWNZQcfi@=pZu23J9rX@_XBUZoTIUT zd(4|{cHaeJ6^me9ilYy6g?ob@3ZgF%LvZ++hm70pZXY8#eN4l-7y;VDP{fEv^bPnD zJ2tuPJJHg<=IyUu^%*t;>#}El;fN>`IMZ)0?cbMq$W7#9)!Hrc`CSsY-jX(^rGJ@2 zPCSE&Ky&q|!~M(r+o$20C!^|MWr0f%J+20$H#lwtM#PVcVXv}{IjHnIWu-#-1zr z;drYjYZ=>JsRX20xr`D(aQjE>9 zynlb%4*Y-0eE*Md$_=ahg;}b^h=uq<3g)j<*X6T#&L=VJk0U^_-mllrumomFl(sC^ zqn^-?WXquGNBSN86)MWEa6XEWrbh9f4Htp!?ZM6p`we(D!%Nf6GZ$XZY?w$-G}EiX zw@0=|@S(6!F}&{-0IcsDmc#v-2;8bsy#<7sia!z~$9 z&at+QD{s1tHrnEjCPTKOSEDgs36wt9zr6~vag@Kz<3lY`e5a6`*l%zXa*D{tuOBYOzMu2ayi`3Ug+Ii6kTr9ob3G!X)Nj{`KWBHf4Q*s zB9un|j<2v?vDiDI5`t_y+Gib^dtq>M^2h`kssCi`;iX$rdJ@Z|GEl9E0clu6=H5UnSBu{0?pkBlj{-++A-L{)aa!A_XPXBh&lp9wjhllr{dW(6 zduGTSjl6M((LYKH?NSNIiedp|bjat1zb8J5Py#+yLs+wRC#ho*q))d82=ZhiIW;69 zA(dVY`J5z6#{+T5OJmUu`h)ze0BwjfJQHVrt9h?BUIQjalz%LDQ!QH{s_5AP@Vk-HI*1)?q;om58I3tqjGg$$@MEojdQqh8rwqx*aiAm} z5$p36X%|k7<&E2cw!arPqse+Mu_$v+@xqpmgyVy>?JW{U2!`W;plZGnjXpDX9xE8% zR*%M7xusGORHaF;f>~u~G{BWRS_2Qa_WPAUjs{LzaV(uJSFWkNskBFwf>C;Nw$>3n zqucEV?OubPBZG<`SI(y;wy27%+P#oho#yWN$Rj!uB0*pHaL5}gP*wvdU&5|nAw1|` zL#6DEh4-L&@mk%vvb*v8SGX^T?9rnd3G|o?2M>|7?>`ku_Kpenf8B}x&dwl@X{#n` z%=?U_D0GupuP0CRxF+fY7|VN$O*lkoI;izsWPNIk0>NI)D-6A+R187cULL6mK-te+ zegmIs>v*kPt;j^vPelg)_~a!*-qm3kgZty<4(M}+VcsUpCa%Rw8qln8jE$3x1B^mX zk)r)T^Z}isqtBD27#nU@FVL*n|08_)KVroH|0e$TF!lfHvd9FX zTFaxM(>!R-zj*%gg>UPG!j(!fm{ok3mj z$DpH_cZKW503oLp8!jOsA)`+5Kf7XZRO`4KH{_P#(!T^DdY=Lw zA>{y~KMbGC3$7a)kux?Hw)r)AVqh*TB9hE*`lJokVpKzW>E|wGssp4ucD*WP5A{zu zuF}C25Cyh}SOQ*u=#NqyUcUcW%SB<@ihb-`;v7x>SDGJ&kH4c643CU}`FE)#Yn1}x zJIQYM5LYL&<@-b{zQi9dhiD^GUovZN%@@__(f=FOvVW6kT@1lMmaE*^9d zIsQ6}$~2%5kC6bEW#INXk%WK-BIOp4drwXXHmF-Ek_*L!q0 zk>De6U6sbodN13ns2`-T=sjHIp!%w1XOnpwja1L9qXqTR)=Hjn^dCp7Ui%c{{zR|} zC4L`<6Wc!>h^YdnCHJAe=;4Dhs-6IU-v;u~?Zx%Z9wFtZX%zcUL)AeE1TssHvLqON z$Un)xwLr$d9E`g>>;c{OekBz##-!|jCDnejb2phj=AkFR%0i*c*|Wd(xtkIb-Vp3z zio+q1-!U5-=2YfuNcN)kx~fxD)!T^_K02KO&jDCz=zPrjly&3p*Y&tyVnn&}2*>8j z^#~F{(uUBU!KB$6GXN6*)gWs9>BbD9p5DP;kZw_6HL7$4QU*T!U5_7Bn@bz7EIQON zel&6%+i`Xn-p6pco6!za>7E@=5x-#57k_9NB||UmR~k{{0bzr*KHp2D^eFxx*w0O{ zBC|e(%XDWjuAOM|$il7HR?ev-=Z~fuDQ!+`r=A)paz4{YON}#-|J%f+goH#|QX2OM zj;i$Z^p+M;&+;m%04p4j612ZGs!Gh zY=CXjXlZH>t5hH@-wTumb)7Uu^|#DD6*0qG4E^|i?Xz1d_*!wR>%u;r zxFMhA`lvg@1%xd;mnr@x69r78l#H#t0Ds5HNXL1WnQT6H;ZSkGIOKT>u4fZLPPeyD zs_QrygC@A5WYZ($O7~$IBVSweD$Ire+bvT#@pl(i7$Kk*>6}c*Zv$ai$~xl+t>|eV zCGf|jF_1an`?rM>BG(2E#?=nIS2k%stQ_=NB-bC)kUYez<94gzH~Vro>QDD+8=G zYd$=?gb(7aDBYPZy9>pX{mQ{C8+lwzRG=dL4rCMO^1k*{=esDHWQ8nM|5~s9ELOJ% zJ9fIr53#B{Bo%#0GB&gyo(U3+Hk{$!@1Jt?Ef* z1b=QVad?aGcYJ)q?#F_kWYOn(lKMc5xclx3SmUeFZ~ll!$6o-96Ua8FA9pz+_G%1I zk6!p(lQz5R-jiG!Ehr&kTz-cMj5PInMAiZ#Lfn>n0M(b>pCi5D}Xgg+QU>N^c(D5AYYy zkB35`D#F7qY0d%TR90~c+S=N=MV%eiUp4+FyxmYH757O2beM&Oh1Yp)hBQtpu?Ll` z^K1l+^l@WN6SayZ|K0e%Tg1zMx&|d>p5bBLd=AM<#JK(X+*1%k93`^h|5N|BO??lR6)|4^`}2;Z0%Fj!3-qu{tqz|`w$|LW z?(@H!UcLI_;?KQ0YLYx6fwKoa^3p@?n$7lC=97o|##;A7Q=v+`^|#i#*!T|x8BLdw z41=t08(osqV}Ym-CDx8kZy%2Xl>dwDz^X~^j!bsXQP%w?p}VG%!z0-}jl6VY9=980 z)V_U0G_S2e+@-qgx#NQ0s-4Dh{3Ceq7-uoUICOIB>yvXFXzMfEZxJ-VPfN`Sx30vq z_FZNiCUlVQ!*~h%@h&c^E-Z-?oKq0;hGcj`bHiwf5~39)x-3_kmSe7I&XpcnFqN#T z|031@a2_tjc<&u)qn)YM5g)qwNM3IJi4l;7;NIOZpWthSuekZut$NIDyu+Uu zHeMCo?AN{7N&9t_D{mUigzz&;Yo&c8v@Z~BUlM}{lmh%*emKY>%H?fxcC7=S=m+tE zD7H?RT58X;PWFAWD7ard4b=0KiP-tz{Y=NobMEU@EnKHVOPi~>0B*WnDY+l7HSwnA z72xotxh}~SBGfXUid#xn6)`9kDXkx%@Bzfc7x#0Xbg8RU6vc8N&VIW#P5IYb#oD24 z61?yP)(}xoCcY!DqmrP`Hxy6&+?15YH(nu?al_^S2f+R zZ4oltZfIYm0O&@-!e@ts<~c4DoT^!hGqOOuWOs979!pDdc{S4B*_6kuzp-I45Wm(D zv@_eV75de3p>YIWwW&?zoPYoG@DOR%#Z7ai0#%9>G#^;AE34w!-GGBfCV%-Dp8)(+f!R!T^ zzTCQLH!~BqD)S|&1CjTx-Z#(S1fA)^AG&fZY6`#S7X2ei%MaZ6P=D88X}Lmf3Nyq_w0h6Xg{aA0j41!$ zWx{!S@Al=u1o~4Z+sLSy-2GTQhdXMi40(j~p})hN=Q1?qjRN1N=^Yzi&sW#}lYZ2v z#={|{)Rm&Ifqr}>klMCt=N-!x`ULv`{`x<1gBP7_%{vjBx$qf$Vuzt z#BrSgouYdk6fyvkA#w8&9y4&EshZ_zMt{vnn?CRrPk%~F9%iD&sC^@ z89_7YVN5#d>{gZEuoHB5UolsoK$jr`?B1=t6Siwbq@!;Z_(E#-g|_cH>!KW0>Rfn> z?w2k5p3yf{oDHE0RL|!TJjm;xaE_Df{Q>JggZ2W#F*Q@qj1nS2AFG++#ajd8#jW_D zbG-4<6hw*1!530wl7tPW(OkoaC$aoEv~92Jn$|I33oD8Q3kvZZ_qO~8abm2Q^n{|+ z|EyMyU~ceM*v|?nK<}EiwTkM*G!)Q z#hMblsXZ;Z%6*2DdKK!U4~mtzRY{md8kXBjda8qs=ZGtcWq}?In;EK2V>4wZ#Jo}$ z{4wi6FMe5>m3zL6l3(j6-p{i4-L`6gx#vV5E5vm)3`Hxmi2Y3#*M|jha1{#Im+j^O zhH0JwwZmhrdixuA8b7#^tLu!Vh$MT*Ld!v$2g)W`g*x0}{ljzy#{bC$oPWZuieRzq z7Si>z+=f1Q&H@9Kxo7z+KJc|_7iIQmGr?p|>p5CAu zsgd4%?eSc7!2};?q3z60Ed{GF+HN%-)!&vw)ExaH^h=Xt{t=dEiJ+s~<2l5aj*Yu^VwX#%L_+J(_`M_g<@Jb`e%5!lCNeWLqPTXJNp2k@Js% zhvR8v6I|7UQbJYEuT6RB#iEi^`t4g>#YZ7VT;JijhV`W*7fs>85;>K=o?|_`30@f( z&mNAI>xu0OQ;Z#V5HxxLyPH}bpb8n`obl|-nxYiiQKI%Qy>hZ`JSx1_SThmyR7p(> zpPM&6SkKt2u{(@X#V3)sS9!thcJihdJ0-MtpYXuCFljl-t<2fm^Rhx6D||nOHp;3e zQUK4Gfq0=^!q0bNn(~s>zrt6WHsqa=9?W_}QceUu0Ab5m)MaDs`}R32V2D1X{oI(;TK-fY};OgYYSG+4spc{RxILARMbZk z;6AaoAoL%!7R?l2xs0`u6=dm&-stFF8%Y(6>Z`lx`;iDK`ZX4xC3tqSJ=Yms5-md= zLZ*UXBF&Ac=v_ABFA-VOsLayPD>RPT9jrA`l@*Z=b)*IGRDM;QIwhMsk1XZ&Jt_g7 zL7vR*Tx!YdTMRCVI^ag)2uUiI`tjr=(t(->poNngK}*Bd*h_9_=xOjeLM{tl^K8N4^L zU48#0D={X2L@vQ5+v|v|p}S(^jsH?y%<}4PsL0Js*lH;H9vRz2dGa$sAr-Kf&o)o{ z7aeeGi&3;lqor>%Zp`n_qv8e8Yqsf#?)7@rq~kM`k%0j>Di&oz_luxZplBe>|CV&& zX5-8L@j!!is@uNM&E@Q^(#s7&KJPl1^5sdm_+DS@u`IJk`h25n5Aiw+SZQJRG?HoZ zTgBVpKl{n^$uW)AW7oHRdyWcv@9x|C4n?gU-cMdC-yfwWjG0fWi%?#n-JkTNFzR#} z!i*(pP8wm?3F!j{`Lk5?1;w=E&agauwsg_g8;`eJe)}utKVFxh92*-`$kkv; zxZls(bZ;lK=D9=man2~FCNXGRf}@*tjY>pP)pR3E;5>ubCm;~`RtLiH8tNm$D7d>hb=9=8pT(jW;Wu4oYt!nP>y-G8WJ}9tK6=*H2Ktf#4lxNw z4##0lR*u)^JdZAQKn>de85Q2fx!vf;q2m2|Yj>;lQ4yeF(v#C4&nzJdM=8+^?KI)8QmX=%?|uAZrSs{L3p=@F<3dF&G9hUlH5k@wy2%C;}1D#^?SU;MggaX#zw z(S>YL6(recWNH)^K_K8uZf@?s!3Z(Mq>N%4*H3JldLwP(`U4N(tj|%|C^dM1yZSF5 z^SdR3p5Io5%x>z4(^F+dj5BezPe>x?A*JU5Nzia*)_0+lYZ2SVu$U2m72Rm&42k;4nex9w*%+I#XP%H=OF^eMqF370Hm=0wcMdG74 z3g>lFqEdk#talm2Pe>Y?OlQ&`JE_u%(lyudtGSyr0R&+m@9)gY<7Q-2EIV|~u`tv7 z&+60U$;{2c&W@aEcV}Ehz14$$*{bs<2D?S45hmo&KME=XZ+mx_-)dH|Ps+|N%W9yW zb16YGJN;K0VP8`pM_+V~?=|a>`#~e0_3%|I z=pUvYCe`V3d?Hco$C2p=r}Q;$G~A%C*Ps8rW+=v}n>)NmC1Ic7-}>+6L@`|AUHk5E z!Zszty7!*KXZT;dtd}0^2GCqwpI#nc)SH|WlW>*sevx^&J%+a$1;a0WN%KWpB=DPsC5;>)hnhJQvTP-VdR7j_r$uADk6E$MNl#z*zv zSkpA$0HIcSTKZ>mk+2@oXmC{d8DuO$vOVHO`j)k$>U41pP6oY0I>^XZV1YFOvKsQ0 zD0*`4vS)c3ix`?|T@*g?5A?TIq;wG;^|=e{yKiK_-t}^~+f_cli}y9=!E+nu&H4P! z^PH;CvK;2BNmuzB{&n%Sfxdw@nSrkHFEy({@dPz5A4cEx!ny?7 zgdy&4&{(;<_HmEP-Ozlf?nf-bq0&OOg(EFgad?7VSRq+*q^0%7l;h5J%n!G*tj3Z) z)iAlW>yt_wV&q!+H35@Is!5BUVi1d8Ouq72t*LE2$-_)NY7zywJo6qxCFL0-5np2CZE0@uuK85s zyPQgM-q0tk33ufNvtuV3L4qwQ?2LE3|xaH=$ZLzKNd%dS@MyCig0J{CMVn z80o$7T?Wcon9c4I+fXkhO#%0WPsRwJ4AAuR$b`uaioUG|Ui)U5BxT5?j z-(&ZsKDUTXbYq~CMD!1)4hv=#`3ss<*i6juYiF;_217fu`JTRCWpxqjURz$wsko=Q z2hw@-wx7&w^7>eNg9V!yE2FP(&B*NA=sMkz>__1#2f0g!{CG+M580vz5t8L^X{w1P zJ(Lr2`*O?-o@8Oa!soQ*tBhuph;vq5NO03 zT{Q`TWIhqFXmCHci?&QwiOjvIOT@e#vU8G`R>>0LEuvbI@FY~$ zPb%Wt)3T24wL1D3`Qsg<@=IZe3c_wfS0qL7r{T?EkR&gW(ez-4&FV73%iDeFfjMAU zuJJkCB;$7`a9j;9!+CwX0X?M6<*o54Q<-~q+j~FyNFC6lpBEng@PfI&EqDgu@)>Dl zrN~iFSPAiYq5pQbjIQVmH|S(*3551qmigYw0uW7Bu{**H6L^_$u|_8R-uA9_%Tq}$ zzKeLqn|$22>yK`b)H*?gQTYkIX(}@i#L16`r^%%u_qApRmL=-e8R7S}?I2y3jGxLy zkE3|VO(ARimRPEVYa^f4ZnL0$!A|haPB~SXD`e-(gDYcgORC(r(uMJ0|M?&8=6T`g z19(&rACfTopCfA$GiS5{tlW(kMcl>Pm9@v&4@;O-Of=dptg`m%tdR!eL+V?jDx;%p z?y)*QSR3Mb{!CAe{r)rGk=asT*5a9d*a^_FpxL;sJzWo?bEQ!If#>4gMB>zvo)}Lx zAN4>zOgXxI!p;l`^X+5>dOG>t&e;(WxXYhRk%+r@N>k@n6;sJJk;t3MQ+q0)acg$( z8o%*d{+&+Gk4o*LuTwCun9EffOA|l6s?zY!mbDy1DV$0%`pUhyVy4OuYimpi^!$NZ z`Zq6t+5768dmhMWv<2v;mM*7Ng*gSCzVUi7aB8tvOlPy+rDsPfZISCD0V1q-S&{2k zGIw{M$rksFQy4CAvmduziZBL^kU}NSQX7XEl-VDXqwlDdQLiMB)#wDJQPU^0BiYF` zV!SOM=6DWL{J~JQwiu0O4wkponfm=QA`8WmY0$5t)I=Yj*s}_U;_;SS{w^j zoc^p35#Rpc&NZmZ>aPm5px{c@Y-hk2=of|*LDk16^_m-}OJIXJwyX?m9ZmR+5h|S} zn&Lxo>y##YBXq4gJq)(n{WRnVlA>r(Arx2(bT>By$vmS~CWI?GX> zvl=)H{G~Fe=fY+Si~b1Icukj2+r($4%C-=q)7B^d`AWq#GybcDoC%=K2ct+NC2F}KKX z-D_H-^E_lz(9)j|F3$lIw@Odai4y6~HlOFxa7^vN=t=RA3@4CCD7dUbA*j`AduzKK z2(r$-NIFTY1+T$*pz53KwTWB>?VC(U&aRJ<^y)!z31#8FIy2rNojJqgw{CjE97f?^ zwZ$Q!>=B zD@Lbub>t8G>+K{Cg>M$j{E)-Q1)kdj_>jZgJeU2v-%<8gXzWzPDVO<%tlh$!UQhF$ zFPQjt4U3~8tbwG48XsIe<}%yV;r=&8oYEm#_eA@n|hKl zncTj^Bqp)}e(N7pD0~9As2D&0>_H*GT>!C=E*CLm!OK^j=r!UJoe!BxinMa6XODNR zcFG@;9!HOegCl--${8)izN4O1tNJt)-R!+&p;Z`s55N8XB78$LkHo%?N;WO#eY&dq zGLExdR^1XCgwe9vR1Z1TqN|HuQ^X~}e!X4ykDFsX`!^>^G$5F;KAJnMBHKPCnjFYf1}+Blo;y%+nj@r);WQgKb34yp~x3sLbB zwC4E(Ti%szE~UHhrsQ12I5~3;-QH*+#xQ94PZ~BhDqv~^q52-A;IlFDU2z~O%Ex)> z_Ge%9{`&8TpW5@nHQTy@emY=sv~~ru`HC;WF@wK;GFD)f8o#k=&#iNaS@44=EEFbV z2KTFVkeG2dxeJ5c)5s++WT(O-a(A-nap~6UeFG z+Q?Ja?hEdFg4HzC30yp`uPZh1{WwRuckb|GrGtq8N~PBu)}J^JPIDd6#v@EO1|4%` zS0lbFe(l5XO!Y7+T z)rvixWP&!Q`PsWdSs+Q2m;)kr=SG$wZO~j3F%Nvv&x?ir%2s0wr)@l6iBI5OIxnx2 z;HyLkhIiB2Nbvce)UTwDC5e*byXXztiP{#TCu0Gl?XB+I!hCRi{e^fq+ zI?1uFZ5G_D6EnfhkiFrO$<2tekutCernggC@a>@9F{ysqzM$*&0w1HT#XMV%dk1GClCLl z@lOnCrP~fz`e7_6`U#?u>DOp0!nM5rm36S*Klxbv2>)LEC5JMaDmSf94IiK%GI}Gj zT|NC?m-70&J+B(`is>(lWIe~Qx&^t^umd@imsca_B}e=4z|`P1`Tvc%w~mVI>9$3a zkc1EdBq4?ecL)+(65QQ2xH~j7jf4b(yEfK1L4r$=h6I-e8h3YVq_IYC!|(gfIrqFf z?t5?CG2VFpF{<|7C0*27HP>9T_)lgqTye;~JqrS-G*auQ71)pNHR0bv9+vGCr5(gK zNC)$l)>d&2i>`g2tE)yix$yL|H!iG|&nl^`=F>sgffYK(Ye&-KS@v`k$c-+6A>7N) zA~)}a??yMqyuWH>U(%nee#*%WLjC3tewe;ir;}J`BGfh2M}I!CTt+`zWL?->$Uu@t+;nX%9jecLYl-iX<`fo`C=JGs?yfRb zTWmDH3;5m*Uo8YZgxK9yXY96q0Nnm@?>~5>Ey$^I7;E=@`z0##mn@vT}TeXIn&m{*LKVr}vmH;(3# zO}rb|@P}0knoslc&|W!8!5a)1M~8p|>>-kveZVvYK`HqNE%ELgLC^&NN zFCYyHxDw+JvGf}sT#vU(Xe-B$6c3W|blRo0sR+ERGXHkfjH}?~91NF-~hT^Tq(yr}}2-DK1)yKmby_`sh^JWxx^P<#_`>263J18koAWrhNhz zGE93D89SNYJH`T_=c%^1fh5|`cV|CRNMD}w@(zilRtML3UuOw|XV;mK%zW1u1+NM( zeik4*wcBI!2TO9FkcoAvS!!AxzE z$IU_(acL`4hK!md(oru$+Vhe$IXP_Il`_9ay1RewOQTE~Bd#-;aoSJihVq*l%rlx3tZ!Sa|6GHPOPd>58i8@?l%B%7|z zUAdiC7pB^EO8*Qh^$M1iMI5yXgAD+t9nE(3X+FOqO`Z|=FzjQ8I`{a0gG9do7i}Q5; zd|GcdnajI+Ph26<$A$RsHa9rDdKRXxpZ^?vEU_4st1sw5#S{soodDG2NtKT$ZQ8f^ zEkAZucIyegzO)2Pkznl>0>#y5zKTNz_FCKYP-#_7!}%xx0iRcfU4Z&j-+)M^y}5>5 z4K?wx^A2J-!G|N&ef0t;s~}@mW5V@|xWA#?YHbJ!q_=9X`YFJj5?=-EM#-%Xb5&R3WDYLB}?q$I_sl^J@AHK%uFT%?YMK1jOMu>?BZ94`*A0Qy7x>Cn~a*!+3&K4EZ$ret?f4J zrKX@$TTfkEMl|{Id%qsc#GORQiO(~G$g2H*{n3_1sa&IP?D*BOmEa=*fTr%tMvL_e ze(n)(gRD4Cj$EK`5|1aJwSj&~b!_g^W%Nf(cV7*6K8BPMwGoHjs%+-SvI&x6Ijm?M zUcN)R7O&_*KMJ5+bV|aiSud7yhi#g$t<4qTV{IV&Q0Cy^U@nR`9;QjYo+!sR|Dlpj z(ZF2|N3w<8?bvxmd`6(&bxj!qTTyuaUUQT8ogG;Srjx8ie4C>8qsxdC-v_5?s^sA2 zvT-v?zlAjER|(UGC77;uJt^Ov8!qSaN|^w#(Y{rc8N!Yi#InpEg@|>D>86V(lBSCW zp4aOrqj%X-jjQx8@T*-jm!wis1-lt<65Mjq?X*g zU5Cm)UZEA?xg!u@ZJP7F|1A^ zSI<1OqU9w)Y{kZ9nA}>?SqC$P%(ul{vXPDwF)8pgR@W$Oh4J$`@5&T-8rECD5L5>M zWt@HwJzy$Mau7VUHx%%d)*$qxhy>@$H9HCZ)gK8PuE#rCGN^O)9(HfA5*|*X#AHbTK$;P;EO15|B zJtouT8loK(NhYGDN~mR%zBrhLaCzMhlqG%n&)+P;X@`#IHS1z~`C$m#FV<~sVJj}v zwK3)Rh6o#7NX@Tv1=8#Bc*^dEW|UBVU;&fRjxqfvkGb3@$q*H{iu@89J^nJI{2BF^ z0QLqSRVJ=>G^`K;^}?CEv%pO!?)Vj+K}zc0|5Bt~xc%kowlEg$H0Vj0?NagGIh9tGty zfrT}w_^W}tF}XJ5!~+$F;19k!?u|F-NywO_yAYZyk?Z;J9B0SmVC9@+w0zI>_-oxG z&3--UC{F8+FHVBU_mxsJ!Fh9~nu)-Vj}ILWT_GqaDbZkvUtL`-ot*?`?MdE7r542h ziqQm!l-u{|n0roIKw@qZsEmhGSJ%xj%pG*Oa^&6pO3n`%*G^m7LwSfJA|h}H=QbGU zcXto-qN87=^o@;rn^%89XM8D|U7a6C>YD%k_9!`+YpmtgYl4@+!J2ra&v5Hq1tZP4 zz4Z)JMMnw$aek`|Dyo9MTa@%ZnRc6k(c9}i_;v4&GW=_g@Oai=-|VZfWCw?3zxkznv|$7=!hs+uH=FyLGg)*A1n z1;#`*=|5pF%QyP^vj$L5{w$Q}*7p_t1d>C6fw+*xvp(hruW$bgzvi2SAMfAG&rffaE|UOs@^6)n;pac$^7$|q(ykb zGdQMm=?f3I+P2W`@1s-umhw@H@L0@!z8bw$x?Xan(!lb&Bg&`kAOGaY{ig}2y-N)b*mDIdK-Py6R{%*QWEeSOP= zs>BaReuhRrlDP{<+Zhx+gIn8D1W$q9D-=@L83vrS3<^_*TK=on#%?j=%)n29nmiD% z!xcO_y^_fvzZ4{xf{WStrbnA5&GWqZ0& zRnD!ChA->+=?aei@|(Kj{+c7`Ink{!TP|@c_@vtyEJGb2N&?^O^bW z)<@Ma9q409P8!=?f(^bDEyMyJ6Zn#n#52qnxP-{Y$OZe!6 z7TcS)(;{C+H(!hPu~|mpcVZ&kmtI>n=@-S+E})c&aD#f^d1BVsp-^F;=H;5c9M-Yj zDNazF)zUywWCt5wl=#G{^O8zmezA6?tN+}p{zVqfrSMdR*-d06rb;w_jUG%Ab$8-* zvxMGHE_*hUudqdnJ>|}q?Q~jpso`N6y?e$j@f%zIOy4_C?)b}mM3^t<7eZHpkHCz| z++ed)zwGmrb;*u3O_dgM3g7rvVUAp%36}XUV8rGEZwZErCZAhFg zaTs{*XL7a6%)IN}p-4o{^rY_c&)?Xrw*8bgo2`k=S>`ib!3SVT^3eJuwZAl;GA%Vk zuRfGjVnK!q=#&}go8rOb;}yN|fO&g;f-k}U+Kl1Jl8XyveKWN30PRMQuhM*g&#{Y{ zc)(L{4*ubKvF_@s*HK*p(yxJK-F|kq=0+;bVPAU_q-7bv-A5bHP6Y#bSYeTMO6Znn z@#F&+d>VrBENbrWrzB4!Wtc}z4Qni}?r)~-AKBD)FFofnvxb$Vnft`~=Sk-Zl$KF2 z0*DdAld8;XW>XPa_q~sm?oBg)P~R0yB}@ zkYcaV^s5CQGOe~hH2amW9f0qIK5(qpoV)9P%V+YQKdYI~xzr6FOcXsSnaDj#uQV^m zgWLgn8ST|eZ*Xxih?>U(b*PDjUorpcON@(-=iM_<){xd8_`yW0+p>iH2+#MWQV(`} zK(blkN`MM=q=SxrTuk;3m-CL2B2m-*==}NaxQ}cVsZ4_U##5jj4~Rm-kDQ7RAJq$O z*QDORnwR->IOM!?+}j%}vx--fQ1+-MA&yg)>DP35p-8gfdDmz7Vb+7X9(V<)G`jM- z);bff&TUr%>(+XI9m^QSv=sTRAK1j_PvXYJu$_KR(=5yCZhzhl{p-Y=i{ci=B@3eN z7eYjyJkc~yopI;W(Mu*HsM^!Z%*_wXWlNNdzKnZDGt#3fikr^5TDB2W5xC44Xd%_( ztb$~GqEG0(WGc`{6OXsPkVlXN#C27YAejZ7L{!BjbdX+{4^Cxsj|z?vb45!(rHXl4 zXSMEw*!e9QsM!9hYcr&Q)<^qta`9&%!%QwcDA60W+9XC_sj6;e?G+JbVPE(!eHlw^ zO$t+jdc~v_U(`j$)IIr!g67_+{&e}kW-#!R0+|1zxyks)O*RT_x4CE$d)5l0R570D z_MO69Zg9l0Y{r6$j@x=J&(Ii8+Ke575pQd*ey@cx%bJ6^bfV{NHhz}e)H+-jd;9MS zY_`@R8xXosU)0l+!xxHzN-{j2a~7sI5zwOfiBD%~`j>M_9FxOQ&qbpl*cQhpb<*Y7 z+%TfEzZ4aqn>lrC-(2~f`qT6($CyY0a^7$*6ZB5d5)5+RgK+bT>PQEHJBzR-d6BnA z*Lziu|Eo{uTz6SrB?y|ah)JLnJiTg!?%<909lX3tV)8JOi=3_*cj8OzHLIMguOBD| zi{ut=|J85yxmr%pF8ktVp2e*8kBYbs&OfT;_m~@#?k5YI&XthgIwAKKz@m4E-~SQm z?LQRK|7QgIe}xU+VV3_pk@bIn!o%Qxecqef)q2lE0xjMp#kjBcz*XK22Vy66TX-*> zo(4TBCp#Eb^@qiU{}9{;2xB>P8~YaP9$J1|yGsD`Al3N{*>~CUlemK`zpgK${h7P> zJ^cP9-u{2%$@~e;$b0bse!jseGR1Q{iC?)Cb5*FR=XuL+q-<>)(PXu+V}Y%i0s=F6 z3Mm^*TP?=rQo46)^6(nAO)p?EyW#R5VZLu3MI^ zXgQq1!a`i)voz@eQH;xZPoz3Nz6rY3IJBO1y^Eoe;}ax6zkJCA?cdf+_h!nXhmg@{ z{D4z_*vW|-Y?WhS*_Xski+p27lT^!y%(ENrJO6p>s8tyyKSl3|(srN7@BKv_gBO@Nj~t0zgUP!4CMIc$aykb~Hxm3~8>o^4Ho8r=wS z!$pkMDa+`kNl(eMn<~2{64eVE&^Y+0Fc-p6C>xS~#!-_vK4CJ-<+tNy5!<9J6o^;R z$HhK<>g#*+t}L|p2J6Xx#;qIWx{jCYB}JadN}ZYAty_4@fvqV-m)$e#hB2i?s!R60 z3(LQ;0AbO5Vqf1ju^Vv1%c_lNrI_i0oB=80^!(PGb%jz*R!~tMofNH}oPFw0ow9gd zlP0A~%u8zE;fGL|ZpID^{jM5EF>M`CkM@T?hr4*6qe&WmzMFF!AFXJR`j@^GQJc8j zhs@~MLq@cV(WJlCeU2IfW^(EmM6}_lZ_e5S1YV;wIj6n!p?$tT=`N9)Ba$&*n*<~; z|Cm&DJmpLSn0Kezv4vxPU)TP9vQNtGZTuBKvFg3{VIB%%cyR7DZ2Kaz>VZ9F(N%qH zR&PDZjLI$>Q8xvqvEsWFpx7`NhN^~FMD|av~}!cy}}#dvkH-KPoxzwPYe9$A<%E z9}9M_8mGMpYoudTRVd!wwcQ2YoJHMB&i+{Ih{o!7`IKP7tU!Cf=a$^&moc(kKT!8I za&o)=1*BV%O!&8!q>Ox|DrG4=sV;#kXS$p{C{T{H5G#z2j0=y4-Mr}kj&!A0X{I(7 ztTJEpC+u7Q{;ih}ww27hAS!V09%sPIlk_ulheJhpofEgpN)7^#f81x^6m-ZM7djlO z{zH(2*`tN!<_O)V+}D_v_yZ`LH#nT7X)MsX&F=$vW`@m4bU0;8!;_k)wnC2Ve6ncs zb{+;6JT+5vp|QkAE1rIwzwu#{?{_&Q9S^*AxpA}J$g(2~Hf{pfvd2BsJGn~yj@n{4 z#O%dg4PLX=VVjKhgyn67!RKrDgx#sdWnd(Nn9qDK`p6{`HTv{G`m;t(rjr}+zw@@q z!7rh*!cn>kt8j|v=$Ue9o%q;3#55QxoA!sr5(}lJN;1HYD3Ac?S@Gz!U;c4{S z%JmC7%+lA-U6lVQ>FUHPp*g*&gRf{I`US+OeI>1 zVAHpoasFE~oW;P&@(!s2{KSmmU-;7fl%r|rq~t0yYGp}ovN|t8`UZcyxnbX(;?3`= zVbanY=iJTVIbJl8xh16GU0)D{A2w%!_UC&bTiQ$`vMrZCmvbEO2kyjSuxT;i1!NlFf$lAo2TjwX zWQG+4I~07~R9NSZ$=lo9%@OQ`P7+ z33MjiNe;JCO3$^F;tBZQFwZ8gp3fZ`g$(-=hxae?;CXIeS$z+Hk*iA$JzZr&vLrml zBYm0Xw7C&6&!X3$D+AjBoe*(-2aas*Rf3z318Ybdy#%3gTpygAkJB&LR{o|vvpSeG zBim`T=LU=x&A0?A>eb7pUUqn&A<7c2o;J3dnVNoR-re0z;k^kM2Y6=KGzVlR&Br9k z%Z?+0qufZ4=c&pO?TNTd+6NaT+Pjwy0FF(t^UkJKjQ63G-Go_r0j({PEhR>O`f9Uq z-o(Ru9gQPd+&f)^q%=>FW)F8enBuwl=cYE&Mk1X*c$nG*Xu6YyMT4ic%h4&K4!SNM zwDS}OntcU)k%L5!U^*C}BpH>GJruhCM9wq>m=2dd1Jw_v^hPw>nt<#(5j3AtLLQuJ zMfI#O9#k`biQ(5oqu`C}pSZ70$O4S=ld=4A&XW4ML2eWMA7=w{*1HI~MSM^qv{iBY z6UlvJzf+=w$2QVgewA};jAy2dxX8)-;**%SFJuvx&V5Wsvyr%nO*P?5QP@n}*_i(8`GL;_@sYFN1s35$11D*Ug=Nlrf`3#1o2TJ7)9=#r zvsACofU|={?IJg)d8de|*L5a+ik%#VyBD~GUkVJ0vQNvpuhYT^#nH_ z)(GfpL%|9n+}p5p_O0e$;uti22pkeS_<>t(Ny2`UeOPGb0? zR+TTB&rUYn(oc(T`qD-t-H~UkhI@@Hnu7$i++Jt91x1@Z5l~N%E~9Ezzt7d^T#>lk z(3c{2r_-GqJ17-*`SYJL_}@?LuzK>O8vqAO?TH4SR-=XLczvz_fRCj)|6m9sJeQ3K z9b?PB*33|QWp2iP6J-a*&^wDc3w>LOHDIxI=^m){!r}AcQv3@xO?FyWI;b;sq_+84 zqK)~<>n;;6mh+8)Z?t=hY$=|=FFJ%s#q!V;hd)X$92^IlOibhIa{qiKUB&l0`Y}@2 znKYiyIf@}4Y+zVtT!Bv=Ji%#!Lh+B6n95bErsBzFU^)ERk?;MXrF`hW+dDT8dF?6$ z3=!{c_uyT0Gi+RojV7U&-uMAFG*E$j>soRr+p%(6gY^Za09%<0R(_`BwppM3LEx!3 z@U|pJHa7@+WyKmEclv}Q5Nb%o(_^UFxLY%{DE%*25ztP&&n6jUI9RL&&GX&8X;Pns z$F;B(fj`umi)1qc<$sBhYjnW8HH0amm5S4A|q9#&uWCuLE# z`9{kI$86-h?t2dFEvsV5j^1;qgXPZ}YT`@%uVLhA*wX1U+AdN%C8}>q2~CRRc#i&N zWrGQ7T~@D(rHq~;Ul)P1M=^4Z>*++VxM#boeg7=5=rxnhWt$RnV6&t-9qD~bEdw>7 zcLl5yHM5gwRv{EGVruhu`CNbXb~&um=d!)1pK5uda8h?Kht|ByuqJoPN%HuQ9|i{p z$LY>ws~oFtO&F?AuK5Dd0K5F9TN`td|MBz_BQ@9xr&Y(5m=8JZ9_ao8?p}dC%0bE1 z%x>#)l}1evGvq9NU-jTM53jpAw}PV`u^s*4%02nF+|YtTT>JDBHRqOev+Pqx{*wbz zL63}DEc}da)uai)tDiTaEtM*@_UrKpuQYp1!E)r_*;!dN5Z?IIV1i@?)#M}Umq0co z)?812PY&RlS%&xIQ(1>TTdIfupNk;IR^xDly$%c(uJb zS$$HPpIM!K9!13)u*%R!e!$P4#D1z|4j9vm(WZ|rN_ybRg`^!HnXGBWSWIVk*`188 zkM;N>zHq-a0(u^Hv^PK9)}p2!rJO^>bU}(3+iJ&=`4W!!V&9=h4O9F9s^y- zYLq5j#9ArW_BHQ*&*jmKObwrPWl-Z_mfm{#_LvSaA7Ihb^Rt9n*MKrP$g>I68knaz zi4j^j&HhyDn6wAJ%Ztu&hGWRP5ePZiN{Y~u&Ev<3;}?GaRrj^Kkkf2$Y<9S)==;~c z#$Mi6W>gCtWKqkdvo-~(}yNp;!?S4=5Vu~ ztO4Jtip|JbB<9l~CT99VU#4^=YaNAw2tqBVXu6j zc}%XV?>zaOV8hccQEGLdPt6LcG9)CifQ6C>d=S2cpzrP~xWcDngnemZ$UPhDh%9I- zPb&Qa7XIBc0~2xcXKtw6D|$`FG7aJt< zSWRP+;`wcd-y18I+R!RCHswq9jfT5=io$!HRyHE>cLc~ph73D~Al^VGDBBFvO=6vW zdo_S`!j#>(=lA^+yyMSkm0kNewH>il@l*?;VRwQBtAS;eYy$*Q6-owP6W`WADc$fc zPTXVGkYG5?LYo~P09u{ulegiXeWMKI9x5Z4E8s zQswkOmxkm`o*ovz1(4%F>s!5#s_VM7-pPkPLr7|`u$SgwtC;7m7Z>^3ToXk}$yx}_ z>+#6TL$^X^X{cvGm?P+9&qy>ni{|vp2H}U|_4$hu$W;A_fnxUMc7EwwXVCGUt(ATI z%Hk#eThZ3u+(d;{d^es5X!Jx8-nf5}5avy-AipWAJi^D9Ux8A>6LPbKlawyvIc9?W2amx&W0e_O;P&C`?*I&YS=rgZW{{ zNy+jB*(Kk~P(P$eVb?g048*4(?MCpiE)k2KdjFx^kFldqwJvrhuW%V5!KFuEVq0XV zBig#G18Y+dCfdYRpuWE;Tqfo>Ri1^6idT`|{gU|SQ!)I5Y*?=O2Xaz&TC;oMC=;;< ze5j4FVX(bPUXYYsBqnk`CXdUkW%pvO5!JfOem!$CIaDeP z0ObjWM5N=FVd?y_tNF=8zdfI8gXV~p{zYL5qzz+>-sut60PVlAfY0~UTB13)*^lio zoB2Bs7g4{6d?PA>+sk$Mv+{>5X##1V+{xIi%f4q`+MgA46(4p?zra0TDRO_H z-`$7Ik4ROdEV;&R)_K0Me@4QXOK*Hk;GY~DjW8kbRD@joRZ0n4<`=%P-Cfsh4Gp-O zOSy`{PE5k}*u4=V-LzA2+&n8V;kL^ETUv7NT8w?YB7)o)85xPP$f>W_DNsvDOl$^N ztOqxdMZbx6mk_k&_;?vRO9nigGQfy6Z;bF7aBsdg520*QcyRA)XF*{h12c0PpBoK_ z_g3k5F%TZkikBhUy`T>DX)?LknqoEoO&C)n%2Dz00Nk(|>!V?5Y;PvfBoKh|Uiia> zkB=`1^&NW3cHZL-YG|}FHElDmbaR7fGi#y2#)n$}6>O|>tYUSYtLA7X!E1OIEnfUM zk|gX1ouZi$5VK9|d$s%{6?Lus<*O{P)dhX&+!UQlNg6Mq@+Z26@;!y04bLgjHi#2j z--~1Ah=h8vK7xcsMDno!EvxO<8p5WXHpijIPJ-+9PryBOju7@6^z8RhpV;=uuKrGqJl*ZWoN|Yt)zi7x1{o=HxF;Ys=MuhEOfN zT?xtUh;K2!KcNflUDWsN{-598xQNzLPJMKnWqWTxw09kbPA4BV+1S`vkqV8Iq5hF| z3&wr5_=W|xK~5Gf+2QZ*CGrUdzZ1w*6@Z2 zr{DKWdgj2;N7+mHE+l1Lxzlz1b7aA`X5=fCNJA#tm0r9W$!R&gW}32CKhGbWXwD<6 z2ryPim!lH<)KA>%fNY*=LS>=Zxo>9f#|k*6BXtVI!+EF?5trn;HP?Ex%0>xgo`__k zX|D4{(X84O&F_1r58P^%e`*-=WQ5xW8i)$^O%_iPG1+rtVhj;#)t~()lrW48A0QYk z;J~k9RLREAWlAyfwyRrdR`Fb3$Wx8^%%RD$!vK%ZOP_AGZv~ue1yGXE-38MJw9%sS z4^j;H! zvN%lV^zZ2)iR8nwYz2Mh6#&`6$wQwrR!It(GYktDh!vjRhA?bA;`~TeR>?!P0tm=G zZ41Db1XwpZsEZE;r|J5H*6a2lR`@r+^WIM*Wcba5?JoQlF*@-xMVyjI?%%3uY;QOAUa=*ZV!!&K7iOIO$Mxp z?V_A+=*_XsS(fKOAm?V9!Y=tJS)}?KQg3~Kb2(!!S2E%C3qP3>&*XkQ;fjOxh2QL= z7W3tm=8u2keWnhdJz2#gcVEe4p_~VT>PsJ`09`u)_9#Yig^7f8llinw%ABX)G)fw} zwp(7$*HI_vq9Ts6Uy7OxA%j}aP%aM|h!|Y#FYC5FHtY&w-T|H_oy;+^c-_T8EFn{H zX@5DD)HifYI8$ys8#4z`qu2ehc@bNAv_eq3-g2=X&{FY>%MoFpNlz|G=_8&kp{RY% zA|KHPA{mEtgHw~Z%}>Ja-&;Z(@B-IAkS7Jluh^^AJ9f}ATGV&7;9Z_|l9*)+t_JSt zLFq;2%vOrQS3kEe#;+3S99&DyPK6LOJtJMyFO$34<>-?qj?u?&ucJ=p%z`zaO7o+< z60(cMM9YFLD?BN{`oL&P#4P&}g){;V{pq;kmEq-?d>HT$4+YcL_XJiX*JPLc9Tcr zi|iXCv#1Zoa}Z3+_SsTn5AXG zQadGfq4frs#(2KSO$y=hrsbfPoNGS$jCBD$KNnlH10D^1S>iva9w%ulZ2K+z6kCSz zNoLZszP;7G@!1~^+j~AdTofk?bFy4+7b@{Ih9U8UzhT}EFr{7@1LGCEXzsSsdLHB! zUI#1}Qe-41V(TnT){sPGY|K$o`U`loAic4D+@eiK)V0RSn{B;Tw( z$K{32gH7I}DFS7%s^+nXcAmUUeqIOvAuDSZpNkr2_wcqjsW4?2)4w#Rz{6>Z=FWO+ zq)akfuSM`2APJHk4zr(yO)&NJcc+SVy=sHoT)XRjY^ZT^V{TlN)${?z!KUnx`m56jKet$9PU)LQx2z0GO2Q*daD66O> zxgvUln@3r{w6Yec2^-mB^f?oUw40B&n{XAFSFlItOAyZhczE*4YF;|9$KB3FmEkY$ zPcFs8GoS{6xX-!DpJ^Q!>jYwsI1SItp zLn?J4`+Mp=EB?DlRFN%ZLuV;$BU6r#70vA8&K;7cKF~ zOP9u)W&>N$5+L0DZp;(4`@N!g$2=PqykgSBTT$rS7S_$(2-gpRFsGB0^Ep{J2)>8L zSfBJjPik9`Ye`}))RVcVP0(>`V#0yNT!xqCM~+I#wf`TAo~&o(VGgK`o_bsl^>q_kv_NrU9vT*_8}a4{QfpH!s0&yh&z+9D^$0Sqtf7K%bOJt9_vEHt9_g~8 zX6jc#XT8UoS%FX$zOc=zWH?wT+4rL9DTwpWWvU}_-}>apAiu&}>#$0n-sBd~7Ye}% ze#+;`F4KY7*|c^O{-hi9`1ihgfE75t2lPnUmxR}ocYRUJersr8_QQ>}*h{-PS&nN7 zJ7bNN**cG%32io2{eEc~=Hf!bRCexK&i4h0q9QFK*F1!bztXHW76wZ>bd!?}oFfzR ze~?`>Q%P%xC;PF;3!sKsL9Na!)uXPPzk))+ww?W4Z)QPSYhnh{rSB`kRJWVvoij6n z`^x3&QLfSYSiy`c4$@wLo6>xa^p`=5*w`|YE&Xe@BI|~D;ct55WyH`C<9n9P=)Fb< zOzOsPbuBL&09|ay_)L9L%5v=(m+_J2%qECx6`8@2Jej z(@#g7jr^(%$}Q@fh0aT^yhEpc*lA=4rm)mCv);HTBp_xEr}J2SDQE2OlQk}1?`Po4 zJu?#lNcpQxOw0ZxU9Ow%x>s6;@s``Jew+UQs=Wi@7AHApZnD{{Z=V#oA>bGq-+eOo zyTg1V$EUycyR4c_@;W>4fVe&K!dIUl(-eFGOWhr>({2@aX$S{jkn$dlpSp~#N~!+< z%!2ShzKc?dqFe+#+u`5B8P~6qOIH9y=v4Gj1BI3BBIJd9iu!Mt-k3-qvz~pX3wms@ zDHCYuI!yoZ37*dB>LDFRlU1!jYa>41b^U-ok4wgLJ2-m2;G~=XKJFwJ$oH%sjt`ye zI6*mkL1*y|<59`YDDwsypuW<*Y}}UECYIiRoDU?qWJCjr5^0Q$T3)o8; zZVeJpgU6PX zd#J$HR_}2fIT3*~ z&9VZ&oI}>y`tt5VIR!ZK^w*y*k>CY}*Z>IiCjo}|YfUqCp+ z`e_D7$`GSv%O-+t1D`BfN&TH6Y?FLt=E>wuv(}^Aw_MNZ!WF*F9u0I6iPR@-`I5O(pS=sbLm@jLMAV! z%O@@m&x6lbx=)iDZTpfPgh&_n+wvlurOyYuh!UT^#=(~xsV+N6ot^G(%icJ)(X zwXZ6PsV60t33H(mJTJ@!vC5k}+a%&N3JB8){NP&7CaR7TgL=T%m6I)T^CmA#;y;#m z;?k%eNE;VqIyyI#+A|qbIbOjvN5HBHIe7@9*6P&hYJ9VjJajZ9O@ExQJcbZOMz2=Sh4LwFC z@Io=`QuGzp&`xhke^cWX95KbmygG4_J4ce_xo^SI^pu5$si!MyhIL`68#M0Kuqn|R zwqzn)8cb|8Hb1?#Xd)}nxEN?R2@RVWf6RC4%6*NTm?K-7nRg-GF^)YtJ#n~^D++}b zSomBaoNuV17yKio-CGNb{C}d=z05}&SO11whXTBW%WuALS0aO}Pv?YcP=SKP1)#n0 zK3k?FQ#}K(V|ZRswR`Y3ZIy~aLWQ6n$K;34M_QiMdv6m&Sgsj)YHFAia+W5-d3pDy zg6aw0rb6F1AN_TdTQ|@rdPUHiGvhxs9kTMvXz7JszEGsA?7Z}oX%BTp2G=BeTid@z z@mM7FWoq255q*($>-hUynkC6ke)W6-(2fa|o!@TgUvJbdEEnM%?Ch&iAyuha&h=Sz zw56#$A6)1uWjV5^kh?6fV%?~kDz-u)m-F#msO{Rb^qSu{RqgG;oi)Cdi%}C@u#q4& zDr%c^QZ-lL@^#?hPD6X2HH*?Kv$ymchnJjr3LL7M2QhL3J+_=4Lq>|!<^XaJvh=LKk&(IIDksDw_7hbVwRe<8(%Y?Wgu}|p1-O~v;d67}n z)7$&X-^A1wM(+7)KyBFTGowsTQU=)Dy7eTq+OCx$i%z;nIqHxI^j1;8bl9eCBDfU# z<~a}j0&Ei!8Y0spt*ING{X&}gx)Za7t@7it(ob^wO)2f+d+pB41z7rQrAJmzzH? zs8|}ly_@DCY_|)NrD{#|;`=6+9@#@*lrie2uk(3hr>mJV6pD$-qGPj?rq<84oU?NU za33DW=XQ2qTD5`L-WLF!Ty?Dq}tK|BIfW5)vu)Lh_ znL&+j-@C2L#8MXF^kG6C~ z#tnc#`?7vlU@Dz-ubpSH4_~*Vs+M(Kgh=uY@_4NGPGY53{aJ)jqk+YTkrrX^1p3o{ zI``|b`pV^G69@V>0?cO@hPm#$XgrQuYEB#w{MDrGv+P{6yzo~E9kwNkhipz)#5ZH2>aLFDk9e}vw}RwHFfVkrgL05QK86}^H$jrr~m z4&!9!_cv)Wb&V|($lZII$2j7My%*(f*~UM6T1*@wUV5ILM;!hl3&;*W*8Z0G_)(MH zukOnZQ7=gx^4AuMi0RRhrkuLLdA$$UZJH~4YHw0>%}x4%*0e!uP(AUebhHs$s2eIk zOD+sZxw%!V6vXwemgeqGE(^t8suG+%l=k-^e6vdKuM2I%c(^kzOmm%~;Q+oq; z=STcB8Mb8C3+m9`mqK&CnJqR`Kjyxj|H%q^GS|%WS zF=OSKAae75tD%dr!C^}?Yx)^WUGXHS1D-EkIBz2XQ z6QW&Q6t!S=?D0w?Grq=eaPR9r&{r?_g*|6{HC6jltZip5UDnKiL z)k%9^w~KRcq5a#Nh%s5^;svcI#2t^d-qDZn{(;XRk%Q0%j%Wqt1jF`Y$FiCr<}SHl z!Hf@iTK>7T8|w^JN?oBuC01EK>Bc!+pV#--EHL1v@hDBT30Y&v&_it4#7ynf$b~@p zWxwT8wZ&m0|AV-tZJYqX-KCL0aQDXDH8|Z^aF^iT zfk5N#KArdderwj6nLBIN-0#l(+g(+s>Zx<;oU@<(>omxvxJm27 z9>*jT`{1i2Asv&cjYdvpierxx>JfT>hc?4dLqaUvCc47!YjmPxcQ(^m#2l>@#>1m! zy-N4DLB8fjUB195I@Ml_UMn`aQfu{64gfF5*R-rm7l*~#H&K{Y~^=qDjr-J z!<&F_yaQIKG48|pTg15D!}OU5Pa$1yejxy*Yqg?RZz3o-$z|s*RKm$FL?Df{9B*m> zJ#nxN)ghKbtV=(hT^W;!=7G7}q|B&_-tEUxvvZP##bq+ZNH3}h)9;4@g{d94A6HK~ zHQ$YmM~BgdJTx=K)_S0BIG@n^5c|79p>Mw8Q7qF~`JI(dZ7Aubwt+c)iRMqcUX0c9 z5XMcdgh>jTv0vcavbO;QSQMX9SBYcDc--^68J#qp7DeUXU2x6`)BO@+(9+P@Atv!~ z*}E15ee#cGwLG(=o1pg-Yv2gdg~JOatFJ9-27V&4Y-15(JAKV8?+ERhG(Tw&Obofc z{$l;3&nkX^-@o;bkJr4QxPzC1hMw0!=Yiu*-BN8Dz<|ro3U(mZ18iL&Vv0!Y!L+~e zbNJeYYdxlc1X`m=DP$DnI?9RG1yIGLdK}P~0Dr&nr>90{Q}10Bfp}OjRR=#jS=myl z*KLrJaHTbhh?+}5yuExkb$_g6mHOZSi05Vcv__EdS#RWP8n$M#q_dF7W{r$$OJd^o+n4o8kT+fc zX>`GmH=4auzZL-!&Nwmc{#5fF&`q~&#ct zxF}wB#Tx&J_Z1$%C|N+Dy&+SZ2dqQfoTpzEfw}nDsjVcPGOM&HT%rP^K#+=HwC~Hj ze8jmQ zI;PrYP6Z^ImH~jo@48RVP97y5t1ECK)nlZC$Vcf6I}-Ul*E%iR?kb|7f2q}qEavRCQaH>SxYIY=(qbFUm{SQHK za2?tc=pGKPQA*_k?{&8h;2h0~5z(CcWt6mR8!SA_9rDA=^iDF{zrECrr}Q}BPgiDT z<|xm|MWG`Bdp;4xt2#Mn=BxV?mREm~T@<=*$g81TbIo+7yk7cEd=6EPdE|S4bh{;_2W4BUpUScC+&Ja7(M&(i?Gu|ln4-rK~nUC@5Eb--dmHM!S*OCE# zI<~_Y!BXN#1LO0~(`;lSQE5S0IDjGIm6_~R^oA*;uX&771oaRUQE_r5g2!a`6kI4K zJwi-=24Ztt%goeF5FKIU7PHbg7anqSVvT)-CQ6kKMx(;;XMw`V_e=@`pdOzz`>7K$ zJi{CCN#*JJ%3$!wEIs~|o;$nxZo1m-Tkxo7rWJUP#?} zKIZx-fLN6?fc6P2sZPVmTiId4evUj!4No#JnvO|0!|-!g?U8*K6?~UwmEMG*sf>6& zOni4m%A;wi&5jLnCWkofv7dc%B7-5ukg&Y#3*P15Btt@KjrnDD+bFyBYZ;_-ZLYnb z>NaN~CV)bw_(?ioWw2|pvAiKwKpXjD3#>$8msWX{N9<0B_u$7S0Dvw*`MsnUiaq2^1ua z&y+IqX`!l}&Q!`U+Sdnm^0+VOU+*lt}MYhOuo(Qxi+g2NsH*h?wK{$=(ng9i9xs!o2lD}!jJ12WF{J22BoLME^iZ-+?_B)`_@E_HF z1fKBvx~;bX4}G}vT*P0-7iXp!EUPNjYI`$JmHqNI$P)61NamanjFcj-$dOBY>Fccx zE9WkF2~xjJEzq3NOko)hLA3j9gtYyHHrECpEy;w%WJ;6!$D5OVc>~DCXN`sQAQNIo zfl-;Gp<`lH-{z=mJemt1@&~9{#2o%m z1_$k`=B}G<12_T|Mz>9}hdb9!AEPx*p-vUEh?DZQg5hCqwl}8YI(EXR5=uB?xUl%W z$LHIvub*OB^8U`)OU&8P zGip7)2`()y{bPj5XJSqyYHo>{5`R8I=^%FF;i;i<^K{#efnsL+P-w3vw7nqv-TQKn z|6Xou8%74EnAeUCmv9Z1SMBPR6{VMfDkz7eG1-y{Y8wa%yP1~R7Zr+mOOoQgL!S-M z{xzvK@<3ix+4_3Hk>a_;0H4$a=5G-Q$iDq^`Dpx$*~GzOEXkW?=2BKvOjac!`2i47 zcok3vm!o_zE-fhmH+g_~yr_KN=bj@ZHcCu36>fx<~73P@ljagv|$n*KFjo*wk z(>05kH8~fRu*qGFO>ctPoN$u!r1^1SJ=iV}_^hU+9%%a%1guudx+aL{wtzAy_o!@* zObt(D5AUD1y6i&$&f|!>%)91bv4OaOBo;JUoUyJ74UE3|h?Ru`6GXFB(}+Z>BFxQ( zyWXbqZo7N`w4B`RLj3Zn;@GD0Cvg0Bl9;P?nY+W1tQ_4?#`oCv#%D#i+OJbw?~D&Gi6oLS<2B zgq%F8^8`RLv_g&7tsxz$RBhFiUZaun$~wcloIV zO6T^M_g7|C7l|3JB@^?Hxc|fVWs}S5sZGK|`YeqcNUcC-ltH+2lVtF;PqCxRs%K-& zK#+)!TF0_kh6B&_&sTa-Q{8!d*!H`G4%KNBe1~YOM_l~I82e`&Nd=|$!E+acD6lRKZdpW$XgVRTU+wE6x3u~*zU9wmzhigf~7p{HH`xyl)s&F zbffl$)8wcSu}WM!epWM_>kpx+Z^rsIkOvt$081y-_kYhgge2bV-`RxNlrQbRN8zW~ zrM2ug$FHrmC|+sX*_ttxq?~{wzEUQMS95EstjYpJhSDgnjE;^3xs*qJqQ4x^92>+_ zWs!C;2{mh88cAcfccl!NHpt>U?2Te2l#BIFhDVM@( zNlI*|-;DMaTLk4st#wgq_}ELFbld7fjo(XuXrl(}ms8_`Zm)8_?vO4!&UnQ*si<~| zw8IDxUNZSOregM0ZKfI^HdOp*t^veS?5^{dZ}y>@SejT@45*=ureVE6G&^$+ZYwKj ze;yUNAmhDPs*r)I`@8e}E)}ntu@9XMkSavIY>>};k`L;e;-3Kn3jIn*^thy9)tL;;e(FE%s zGpN+zUUS%5nJvAa}RYf00c>(_eRM8EZ0hvX`JVw*A zkikWd$uQ@E%+Uo6g7bmMNpO6LiVTSoiShkRjakO>t4Gfe@{jd0tq{RfQtY8)lntN! zurP&BZWk9AsdYnT5)+aV@epTRs0`1~OgsjLk4d6G5Ya;Ya+XoCBKSWBO;`TB z8o~-z-BqlUmKS4tGmBtS1?-&6CkZ3efsL&X0qVh+S!wdyV}*LL8zejr|8ob@D(e04r{U0nq-RI-zd0GJ{@y4Iy8o#t* z``+Rrkmf1*Ys8`Vprgyv%ikJdX!R8u!SH=Pvkk|@w;~v7UgEb;CIvPXr?-fsC3p-* z0Hk~z%C{r490Bq;I3PSm*Goza0) zxJH-Np5@RVo_nQ{(qW5i36sO+5|#370nG$bj%H8b8Kb}E2cHkbcC2J3qqYqE3VDNs z+bNM`#cz}y;~gQKdW@|u1sZgkv=7M}BG9ty=EN%dVI^R@O8^leKDpW6jvZUgJ*D6u zDmm*Th&bG`NHnlZB@iC;PXvCZF@iH2yI}G@&unU8%BKkVzWrFa^wqz*bI;ns#KGT{ zpKN;GY)`;qGtbKu^b1qn8_Ke7uWNuvLY=(kTkGqBPEOK1Ry)w#|6|ew=z5>?2vGJq zyr)T=wx`7}3&ZeQL$C5BPwnj3D46d~H6c0&@ye3%S@?I^h#AskkqMR3V!wY+C_lUH zdihomRc>*&F!KS4|I62--%=WgV3f|#;OEsAHf>F8Y!|j}!EbW3ucp#ToR!^bw7YiK zce?=mCIlg6NMB3I%bQqlD;^U_;Si3a+sOu_T4D`34q%XZs`LI)`$MOnDO~eDX^^s~ zI=f=|OJ6jeQ|+gjJXW^+>Y+wz>WA)^F~_fw@{rV<@CIySKMk2&r8paVeVPpv8>jg= zY8rLRe6z<}Ts(+}iQoG{)v@9oh=#$?yNK9KGnQrf{TAed!6=jTFt{N!!n&VAxqp=Z z68k;2cpsj8h9R7_vg#2bs6a;Q_l|0Qt#AagMse(2)E^qoqLldtMGzk>XN+|nDbk=5 z6D#}kc{X-!a*^gY3)W@sFPG{ew6MO64rNmtN}%gz<8&~yiSM&cM;4yfoPj_Wcw%Rh zRHkupq`%9}cD3Cs=|(y1qcYh=FBpt+jao?2k8f5qipD+`ZAfX}HYv+9n*0e(-95x8 zvN~GQHSI>XdU!rSny}D_qe}RT({M1NKc4cX>!k;d8;uziilS!Y!Af85c!(5(Kv)!N zUm2?L_7eRo-T0@++Qj6)7?+o;vGwLI)jefcr<*9+Wkj=qz#&hrWSwBt}sfpjNh zdy*1{_x_yk6Drhp+-tdKsVCiwZ|Yh2j}0L9j>u{)1q1T$U1#s6eN?hSeNp^ z1#U{*OGdIDk)V%#J~HLy)MR3S$E~FJ#_bK&?9BjG0c}Yn2CA}_L}ydkg)w18ozAj? zSsP}F3cL*|s`a_2wT?MSH$ibK6NSUiE9o!nERQan4Hh9$g|r4X)<&Zig|iGgd#VEZ zV=T10!ruci@yabzM6$g6Fh+3DQ0+57O1#@)ky~5k5!z1PxJdCV9q{^@-^BknSvzdc zm##mq9-ZnKG=V7LCQ}h7WA}FHbFEUtLum}^y@ikEtqsvZ;oVwBq3^LvG z%tebGkh}!0KmOMm@qSc!TYa|qqUwLQMhrkJ`|0x2g!uZc?mVU>t6-AuJGv@F=mI1( zI5(8V<;y5w?abUL@i>^;^~{KIY6JuzMkTv<`lS4UqMrla&g2^e9uXum++Tq|{R}h` z!cO8|nMhZJTV&c(yo8nm+3B%bngo^sv9w zH3Xh4f2;fQ8ozdZ`#P@)BI~{*bv1x z`>INfQ6uPlCNlN!u`fjU?FtbLBH5YP0jf>Z7J5?ukQXPZm(;$otBADhHh(Snrh;W8 z^0b@#zq9e+Hi)6vHPN{7Gv}QYVlM2}zJ{;h0f93SwDnLYWtw7|LR{4*uk2(Ktp2?m z!qP&phCmQwT-M(^%-w3pd!dx3vrJTr=PHQvkG*|EY7^Y1&G`2)c@8EFm%80Ykct%L zuM*x1UHN6?$vqZqzor{>CV2%#`9x~*zl}*mTG7Bw#hjdPZomH9O83%&T+rWBUa+D4 zj~p!jPo+k1DM!kP9;&}*oltwfOfa-TpEUIK{%a=i!V|mwgc8>oml&$_J=%fD)4AlSQB?8(1>BHZ2`}_M__`eEBW`ed_zJny=(#g z>tgVt;oJXX&9DD2A``rgpKJ4!(++%TS$=f?;wcvV-FEZM%bqTxK*rpcs6n|1^x7sU z#3m7-w%{~S#94|JiD4b163zSL2tuA$>a1M|8gIMM3QON1W75A099xKUA?vWnEf&+Q z9?13>ygnCOUwqCyhr;us_u0_foO*(PDYYnGur!OXmp1@}iz=9&Bzx@NK)n!AwH>E@ zhGzSGo72p09Q*fBIa3$d^F3#%KirD~eeLgI-~7M7jKX~a_5YXR?9a}1E3$udcwe00 zN&O>i_G0qYzi;as#lJocFE0KQcclMPeiQvKZ;GA&bZV^s>I>fgiTnSHON0>R?y`uF zr9qx}d{q+5G_kO9=KF(?+_wH1&)=TKL-Ye+>C1~^vb6!azEyR154DsNM&%4FP>eeI zzt8sK*$7=g0?oU#bwkR3B*khL+_IZPtCc;(s_{k#ZFe}+4tgZwS)Lzrl4VGAchvv@ z>eYS7;L#VVb32cG0^Y%3~LhgHOdw^-J(F4f<)}bc|Z@n-#Z=0yI@d1V<#4eG*_y5Fj<|C z8xU`oTXq;+8+Osi#GK&oN_L24eEfUIxno?^c8Idq->0M(rkz~Gp((4Ab~_7YQjiBf z+wII?C?Wq_H8ZU@{NT}-e0Rh}W-Mi4H|Mec{g+vyN@b@JsO56ej`4Qm+WzlmKSjNJw^5mRf*Xh5{ z412WbQ{J;H|D-3v0!!U^NcLVThU}E5r(W%yn>@cG9frn~wH^RCak;WV|rDAA_wx>kS3=UhKuA-(C&=K`g_lijzb z!MFj5d)AoeNoWH58r$} zz6w;i>DYg6^QYW5WeNrYd`5i7ASUxoM&*#Ur`ZU0S{nPQ1hV1t%d#3be!Dj3>I5o! zbCZ{#2Ezt`m+RK{o?+k}ZW(@%_9e=WJu^Kv$Frx&aZ%L>U`TY7#7{WAjM`l)^v$BG z>sAkcP{-Lw;O@Q(nh(7)+0Iur|0lIZd&Mw1mBM5CzY~{4#w=e)_~a7*14e9SOZ~vy z@#XPj)6tdtgm3Hlm%TasB+Z;W2Y(UkTwFbcBYv;bk9E$=*Y8Vo=dfL|fWWC$9^1Va z5h{iM!2;fMrV5dG{76T({Z8K@I`mydpzu1w1(dO8ZwhJv<*~Nj#G>Q~l3KEg$o<4*e2^Ap=7@1#5G{Qo}BmX#ZIw+0Ah`+pkiqd~$K&?&0)*I33v8 z7(=>iQ$d$ntxZlOpeXQ#8g^Rd@`nC>@e<3I!{Hpw!tJ*Q^%KrueVPIWJ`$aZS+S>X zyMlkqiNC!2&-eZ00?G2+@Aydg?B6Ne6X$cl-!hB;zovp`wQqiZSJCik&FlGD@O%hq z$0FM+7SgM$TeGQYK{1nRSeY zi!-L(Qnu=6t%70{ql3-4d2w6q+svOKE#pnR)XUN|w8oHJ$o|p%X-w0b!@Vly4%QVphA;3pPZ0LzfNn zJKYuGP>WSz+CViuF!E`@6FepILTMzSGn*V#W6mHbk{33@7*T(XXFMAIKoS~>@ubuvSkYYmF?P9lW)OI=j3A-`yI;e2j5%;;9 zd583}N&w=oZsKhi9^!DkcWu41^Fi4MvsLSC=_cubOJ-+B$a1qZ?1`YxV8p^s^DYFU zWkDdUNY-y_IIR|dX>!%rfaL-(H^-Yf7YS9#I%QtGeyaN_NF`lcFx(C580z))DM2vM zOKou<=^_Is&^ThE$EM@$e31v$i87SFf;s{oGPN8Hb_9K!(BN_6e3u@1dTw#7 zE+pI6i;hcBncaEwZRvWo`H^|roszlocvI->@B%Vr#r>XHqs5=?-r~VNDzt6s+tk6B zBb9|A8BF}zILIXiiD*jn%feNz-uIKk`P_ti$nmeGF>T}~Sciu__PW`i102ZsE}Fs1 zV!CAhE!tfkmzVYYRczxc7t#kE&LM9vg9{g0qNapQ_K%p$$L^?9+*)NJLM~fV3%8;C`0MRH}&&rQE_M9{A-pXiYUr z9bX7=MEwumsw(x5Z=w^AWw(YK)bXqAQwZ{|WZ= zT^QZWHpdP4v1PP%dlAJY`SB%MN9Ly0ha|y$3${VVPa|x^wsOjw0LyT@9VC{8GdokS zjmldTX^19~^aml*Vm1VNMXYO{5}^9o^+sg(z7R34fF6GCKiE}}^}9=jfwN@# z>gi&13WzC*f>m0OMi^~cIn|KvH@qY*?k>5&q-UctjufF)DF==W-D%P z_Nt6NHy^i^R6y?vq~6N^YT{|Y1?}|OwLuLDd@Putb)xr}3bGw#n)*p$B!oH`yo|wDLH8rn(EP(yH=&3Fkz2vN`s`w^xDjeCDIHv$O1K+5qWE?iMf0@Y2Sa_gGrG2*Uz$P$s&D8KyormLvL+~(N75mzueaC3XFaN ztp|6X?!|YZjt{f#Ntj$`>ZZzz6=wC zr7sqTqoU8x`RX{@JU(l<3Un%uy{#rTx*l#JP_tM?eBGefjG$t2J-Pp;1s0ehPKYaZ z56ZRwJdGus`?Z>7zoBIb>Fff!xW10=gw6x~HKOz}jFo2nb1kG%PBW9}zHdXP-og$tT3i1XC!dw2 z+uXs_A+gvr9gUkkzo-P>gHx@~{6tc{czl5?b3w0yG5lRZq}AgRQqq%sIhg=*tfH%S zo|%~Xn5qO@W(K8mJW#_hmQzgEb>_M&hN7G?u_V_M1e!Ax*ZOzxp>Wbri=0~P2@6`& zrxzDow`{DYpD(rMbV85JzSmg+>G3ID@-pnz-6Res4w6$)6r+Dy~w(_LiV7bY7aqoW?lqgcdj}AN)=W}lb3?|Ow3O$%In>N=z}QdZR@jtZm9SuVp8P!@ zaw~+q0G^tM`F&|~I_}Dh+9a#~yg?#lH52v(X%B>Q+j?t^?FBsqW|}~ajSG}KdMVWQ zWG>XSx_+HUv3b~vClL6@Z2!g;vd|B!X{YKqd|3N4_b76-el8%gZLHo&!grZXKWu!` zg7}?M-*7?LRoQ2!oh`u_?FzH?{O5}4(wyD2t9q4bLAX~AKcQl0vtEG3Ody+TGo?== zjvsKio7ZIB0{dDN-_2p7`)8Uk!rxWcgTHA~o%7bJELnGU^E`({eOfP_fXSD+vmWAQ z|BFcCdw0BcGvHau)(~6$fiHt1hP(Yhb1m@i@_ZA;%HH0bNTy0FwxiB2W0@8qBqrrh z@5#`|?aOF{)mjJ;U<+^ITWddFC3MXUJ!fLu87w2CV#`yfRWV?9i|#?j3IG4ax0jON z>?e*${{_cmU;P&{{NI5#{STMV=8j=1VBk-=9SKID`I$ zg8x_C#CPm}5Y!eaU*lsS4Iu+)vhe62{d5|>DEFI57+s}XCVo{V!F|?7$wyB<$^=kL zvG=2j|MNX_%n~u7u8KZM(SSSvS1pbk*Jlg`LW@q~1MMh4QUrH*z*tg(mJd#JTVl9zma( zc8FqlmhtlEIHG3L#XU?G*qikE;+klsvg@3OSuTd+cUG5s4)eO~QJ8p)F7B*Q-6MiV z$2`JjS})%Nkdrw}z7(z9WxEF1qKLmVpXu?@wfqk~fSRgIOU3 zeBxFB_heJc3rBGk?z4%L_MVc^CNgYnT?3ZRxUA$R-lhgB?;%<&s=wRmpQd1Tjw*{t z_24pwO~aa%>W*EmS_x*a%EF4WPF*QhgDORoyw^COPDc4DO~7U^mqhc| zZ9i;P*;2W=bRVf}N;lBk)JiAETybN-n#k==rRHZ7KQ&WaAS-hB9?3{)URvccxS`QWZc&=qg#D6) zjL)wHRB8tD{R))3D{gtzA+(+eVsO4D6?ZFiu-VG47E5m9KOK08M8){K)#VpB!!Qcp z?OD!Y3(EjA>J3%nlafQ$ScHllj=EbS00h;Rq9MP^>i|A@ENJ@Jq}4{~=Fp95trapQ z-&abmJpz_}zuIm1%=P1Jh}0D>lg7vx_a@<7s)EvSvA3CJMC^ z`K+xv;K)|kVf?4K0h1lxGj4Kh;)Z4sfSqrRr4_CBg5R$0+UV*~sb^d_yk{ssXyKSM zjEhMXIrf!x-n~Db^J||yI4ADf06NQ>+F9XaU)JU~L`Sg5o7P4PZkx-QHP%I?+=lv- ziK}LT-A=2ebe^iI2pYK%vD@Dj>Q0}#b?zXpvo{W%dk}c!h`7jbj1<>Z9-hiE8D!-@ zEL|)tg>qg*BF}EC;c$gRNqxx`(@AC;YRe{XuE_RuUbae=?vkc^r;qR-I^M(K9DGzp z7z84xutY1rb6_{YreH{s;Zv^Ivs2GZqhQqTIl{E@9=2Mh+LJkhv>_HVqHayO%06Kz z>MxZtXXCw+&Q`&2omX!{cqmdt8m0(3(jjeCIC^_}m0p!I=4GJkzuC%hjaNbWYy2_; zBIZ|E&osd=#3$%f+$reA)ewyjC@01ZNS3$;RE;j)p4K%8UB|QF%vGl0{}gX``!#Hf zIA&idayi1mmyL@aVc5d%ceJy%IdkX0zg32uVI+9)l$f1|5oJi;NoQ~UBDvYAFME!V zQTXEUVS#l++-q$7#w}9vBVc-tbtx~SNTEGF66tDNFN96M)7(I@bn$lc<}mo6CUOQ= zWpJ^ILUYW-Vlv;?u{*-CM$js#zEese6wtG0L!0>>&8IQ;|rR-ICwyoOn2^WPTIGk?Ir8ye333s80g8U%-B;K`F zF&6{FJlL{)kiITc;tr}-r#~wk>54sOX++FirRF9$D28a0_94SqAw8j?L1cs1^ro>h}JRnuh7g&YTuMw%@5a=OqY)grJ{&+5eRQ=oh@g zOyWglsybUg+#E998$QQ8&;folBPBU7FIE`Rkt23X$W4`F)3k`}Ql*K6>g9M#!icTu zIa<0u1qMp_xFS0IDkQdl+Hyl^`nguPKiDhAI65ETyG~t$9siPnVB6eC!waHmLBCaD z&<*u2*eXo$8oY4|Mr4^VC#(?3LRm%uka6B&a3Hy-=s>c_89-i$A`q%WxDg2`gNh-W$NlMM2 z+~?1=4A8!Z7!mFWa6V*+;6e8dI;T9o(v8obri?tLN}qx83vvY(WeW<$p}Rv#V~(ya?%GEPhhIIkw|Tb;g%ypnO`L|gpS>=x8vE$P=suGN)MZqd;Lk6hHgGeg z+8aTWC;BCUHFW5-o&=6e3@X%TR1JBv1a|vlcBIGK$;_FX(`v!Npvuoc)%t0K3AsAB z-L$0jQ71xxL^?WM6_7H+MeV?_FDz9zN^U@>k#`W0;53)j%juESgz02la$AfC%>OPr z?*6tFl1eUr>ulwln`1OGlXT$0P}BQPcrCRc7bSLx+gfgBhdk(mw8TUIqm+n;XnbEaRq;pT*cv((BH$ zp3;{0`%1XD{j>$PLj$-?@vRW#7v(Q{=|R9Z}SYQ9g;)Q>4*%Z z5EPRf;o+?VC&)TcG`Hu)8_QoQqb&m+xQ!}pzf}D0G50e|%zp5d?;ehN-Smsj@@^%* zBVV>DkpXUx>9`VC;@3>KEMj><+I(z0z*9aOU1$QWQ@uYj_`W|%A)vbPTLEV> z4p~n2D4!5I$^FyJ&t(te_v=o_w&b9V+7kLbP4sEc#9z)zO3y2{)hMwO#(TT1Sd{Kn z#bDW)Z8Qi&5iR)wajOGE#(?iYr>Ji#Zh;oRrDj@WvDsR4LrdN|{XIQA(e>?_s@M=< zi}}MI#gl$R?8DUe@Uq1tUZpwFg_Y6I`UbQ6SDXOd6rNR2_-=dIXQTHy{cIG*bN1K~6zmJ+CNU_vI9xHGAe^)gPFUANd2XXYr#nLlKT$C9fs{h> z=p>;0Z5eY7{bHCPQ4A@5YfHO*i8bM;{MDclH}o))Z8pd@NII?FALpP`B;06_r!%f} z>8#C29oM6%%FxcV_1z~)(YDpQ8bfTL{my&lnd{Zyo>^r*S1X;fs~!p$DYt9rx|&Z1 z3ic%>A+Lnb@eP@JYf6DRE=%*IlYJgdGF`BaIOUS>mwghOj8uNzCvDPX>pS-tGs|dQ z1w};tYB?nK&@!8w^CbM~!SM~XV*mPiN4CsBhB0)Ciuf$yG_ZsJ#@(e>LNJZujeg8e zkAn=VRnO}uZ|SQL?}-oasn5G?EBTThGP8X5c@zJ4+Kru>kSX71TV>V4l8VKE%A~-b zjFh<2<4-rf$uT|s;8nfM>F5`EV?ezV!E|OmIX!Rfv?)!{mXgP@=DaprB5wW56t-6d zR;$6`^a$sGR|l|Sx}057Gk<#balfO%H1#)fw+#wNFQAlQQH{;2`_l-MJ6EkWL!(m0s9ALm~!Eyvf*D}7wWgELE>I(Pd4 zZ%1;)#lMdlyn0|$$Jrb=V08s|i?VuGnoloXOA>P0`Y!n&AgN0pcOKS2V9w;VhbDbR zq+jb&G8UbZ2pyoa)n)thvebU)0j>6dvr+ztck#|@uavt}#xsv?UDMhv5e#xrl>tl=mwJO)wE+p)j zy{V+E(`2vsM5$LRulE8+dug19t$wf-iCs>*^L?L#o|7n6l2yv>h0mx{@paHA?+SaE zGa_T-K7Q5;D3Ic|I9%y?Oj>3&ikI8|##z%an=yYx9nT>cUtytkeV|lv9%INio)~Xo z8^Q94RK(9VuG!6Ws74McGVF3e*wm9`68|Xiq`S)k33&U-K_PXaPq{gQM|iI zmWw_3+0Ej;f2fCoG*F4s;V&sm*7A<(huJWT_jFbUrpxQ>V?S__+Y$%Q5=WBAA^I&l z*&$BoHlO<%YHJdu-}veeYMsT&sZvsVVYlIvl=Ox{mxtE8489|$+;8*$!2-NEOwjTc zOb7Bllvgn{K%B@Q?5SL|w8<6al_HHa2e4Z)GFwifB@9A<8m*?2EBFLVa#k7UhirD6 zf-Rot8QdZo{I(&4WTId(cbD$EJ`_kKT9M)R;(qjVd^c(=hmxbtu*r{PB5ux_57~{0 z`6i2|8C^Z0NX&mY-s(<}qB)blqLHeH9WT{0{B%ptk>GKhDltz(@>jfdRdLD+U+80V zyt~NDhSV!}sEbt^wt63@??@fUOUllFD{~3ONNSo(0IvpyxV5$cpAMoSI)=FB7!EqY z3Fv;nU}@c{Sm1Un8^9XewD!HA;LZNgah~_)6>_`GSa`lEzuoQ4+xv{|#jA_#AC{Jm zBaAc?rC#q1)Ulpg_S*5~sp~xU?_mQD??mC-5Rt}gu-iU?mt=ZI262dL#cV;1?J|rn z{KbQ~^(}SsP5)#~O5Eeop<_7PXFU2-b0}if zW$`EL$r&-4m7DEx{9j6)A!ks}Z^yALNQVA6B-iN*hON0+hNe$QsG!Rqs`d2{dhw_u zO_Rr}AF7Ca-CUAXlGqueQ1u&G+Qj(PSigA_Lc0&H#n(~o3N#dGU&&B7%7g|y9Bd&u$-nS+XaegZc#SgKLK^PlF6B>Vh9V;1(F% zhw}sTR{~>)aRh^Sn9Xz{8y@v)$wegvaTu#}>=ZG@+s_9*z1<)5Q=43D_(DgLm`Fd8 z$S5FhKW(ogue@pb-5fdIh+1a~^Py9Kx4(70=YI|O%kcXxO9 z?#A7%ad^e|H*@cuJ8#yTwPxK}k3Z?EQ|IhDwa>QCXOkWDMRfV z#fg}llTC?{6#2n!fq&k2V-H~63t0syWy)9bHA*1PU?vhIlUtO>% z8|+{ct!sjfr_o#Vj?d#P5hFPVrwt6bG7xjV(zwgVA3;PT(B&G$HRZ04yRV=(&VN78 zzfwZBE?*L%&Y&?i&Y71Sv+H=6m8g3@i)Lf!4Z(T#6f+zf(Rt&?oAHwNJ}a=Q$|S%q zX#Rkxm#Yz66Vlz_x*I#9B*3F*GXh(9yPnik^XOq7CQbnSUCqrEQ7;4=l-x(Qe%fuji^+x+3OXc57`Q~S)hiLbO=hc zm~&%Li*)4G;+fiON14w~5^x7Xa!Ne5nBRK0VnZ)CF?CC6$!OdwM{z|6BLA>3X z+DM}P=0Kz8_}Y~=*7VnT*K2vB>V;mdPng^GMd@5l-34ydB^X;b0pC0RVevir@}pKS zXJV{d%NMpte4320_0+}$VR`(&>km%^b4xJP3*l3W;UQqwA}kQTH0sQlHW^h&x;gC% z96CM6W3~8z&^cMoFYIBweCuX#QE{mUx3BGi{W1+&4>`6Ot;9#oAJHOKRu;``Q#V#roMi`?=;R$qhs!rRd4YN z_s$4`(vfv!XpA}s+?(Yo|FzuD`wADlws;flUq-BfpECVuO20kfML5KxegAOk)bgCz z@)H|z5%sj-Lc|^8?m}Rp$%KlDDGBp^?>~jJ$8@}&zKawA4p)h=_0+Ud%%tWbvYFzr zs|jq9to7DozoLByno?zKYWaXk5cDhXL{E?R&utSGQe)2#U~7i7N;5VzF0XtALvY5b zG4e;Rc85o;DVO}eQ;}0PCM8c>T&Xc?9q9t zNkeB1;R&@o!x?U5y`zNM!$cRa5L=q4B zHzEhS0@UAuCf}CMS0LVj1oE4xCLC3CJ5i81r#5c~p=JslR#AC}Y@y+DTl9EHtElaD zd6s0OJGV*!G1i2pyH`(TX#(^sU##KRl0~;WiTFSkG~0s}j1z;dAQUcD`>V{I#L+ol znt*k}d7I^*lsmY`3&FLotBWSwqhn($LD_OUdNqK_(B{$cX`3aFWpA+*-J@RQCO8o3 zNGO&43QCS=y;p699523OeT*vXKe{dZTO&R}FLBmrK1rR{iIlhryYyjo zg>EJ%hLz_OXKD*$i&o@nBco?wxG^mDT|NL ziDAPj)rPy}0i4wA^(cCKO&Cvd{GgLxta}4@Zj20m$swf}sz>=@oT|2di6UkWSy!&o zM3%ut9Ot{&L%ZHHuCky41Syn}KWTP{ou;h-C>2$IGY$htl@{G?uGBiso(9#V062Y_uaIwZ*FBf#PO#&z9yq{6CC_mVnDJFRm-zT7JIl;hCd#Ho!uS=)N@lVb zQ{)&~OcO3XwOWo}#ju+o7UrB2@;vgaL@w5LFEfNldVd1B^3S`^sWPm<_o2h;u~SaT zjoU2wQkrLeHFlBSp>TtJ{DF=d##D84Ga1wQ=e8Tu&bpgsXArlQl#1=~!B8q%Nd0$P z^3jn!pYXO8*mG(|b2}#*el|JO+L4BC$sv4FJ~{YKb=L~imhE96)WG?KBC`Cp`I;3rkApm zBOU|xwrY!iR@O?4S**HvWlOBULf*bYx-OOJDel%rS%Dere$=1Et#8O1Cfaqu+Tir- zb_Ykt-R;F!?4CBAS3B)2IyYZmAXTrsCBWlInlx(~2F{zGB`_5{Y|k;Ta<`t~^9@$UxYup|REa3M zo(5wN&>XWGaS|XneNRSP80xcz1NvH>NuA*h4B;^U z5lphwSq)5OwIq2N_B`g+fiREz>>mC!u4{2pq8Oh0#)3dU_jEAvYF}ve&R6HocvMJ_B z<4^A@eKhHgU%B#`SlgQ~P94h$jDl`=IM}+jVvnrd=bNIEV#81M_Q_6MkO(KdEELX; zXLHPp8ssG9l{5q|kK$D&M|xfnepYBe2?;)~_==fZs%fZ~2C(xOhv~RE&`E-7t`oCK zrADbvQ8B+qO@_ardnSu^_1?@^$o4gmU^KME4y_ShIErqEtKaB~?PL2r9((LXQo7q` zIgYld3yKTI9kG1z=V-mmtyJb*Xu6uIdJAsi+?#)i{C6go@L{8=XMtwD)8Tn`80~}{ z3jgMCn5(Q<>fO$9I-?lpQlI`2as}4K8{~`cB+RWvYLzuVxOCuEmaM$fJD1hDK5j~5xjyta6fKdu%fsCJqos##LLPMO{ln63<{qNfZER<;Nn%~`Hy2ut z6ahaK;jzFF44qD3`!~LT2EmjkL&jHU%H(33dap)HqF0J_1bm0c z&1Ffl1!7h@Og>v#&`sKNpLQ!7FFi>a??c+X2&ms#Oox=z`xdeagFGL}WeWEF@c3vB7YSegcbp zLYWF_`##>9Dh^T@`rsZ`#tWq<298w(B*f?Elbu5j3n@{?sPQ_w7%e8kY{qJNC3-y4 zc>KTx07>Bg# z@Apc%MxCHBFn#*=ZhV~49Z6mgx5oHlM(+I zj88o_j4Wih89tf;ZSqQD*L|v=;oSti2v(B{eevnbzeW_+cTl#^6$J2Yo^YTm2Y-9c z2Pr=kSNWDwuhhi8;{1RalWOT>7vg6rZ=j(AE1&S>UF{8-H*B5)2%d1nXA7QX_1+9D z1_0LxeZA9}fsGC0v+rC&LPDQ8Y_&~C&72J!lmWMBYy_~0u~@{LNU|u7%h5xIW9m4;NoojPTLzdPeTo zbFC7(V0{m5sv^=^-XHX6{5cPQi5FD5Bzo(0=aQg+vnXvCwR9JTQ*bYfC*-Cj{qUaE zdh&HkOv;h~@LYtHuYkNW`&AlT6d=QBCm3OmH}DhaZvud&E=f@a?nqr2NNaZeu;3BR zaip7hF#g)mVy!ZN_T&1J4fodVU25sX)AZAd+diOl%4w9}BHW)gJwt<1z-5K0cY?1@ zNED*{Aqx21^bR`~*Hja-zZk)=APXncOW}hMRwpT$5;+`s_=6jkx9J-8`T^Z{B^*3A z_6GPpPW1C}$dN+2`1BUXMGs{9 zOONLx38z7=`Cy8gjW>>mk(!z}4IA7`t&Z|1db@jjdxHv}A;PU!(^Is6$OKpap3~It zzfaFfF_8YKW{~UZb}A&SH?Yv$TO>$#^#-hY^Kg7yt{nA8N{*9+G@{B3E@)qA zN8hYRo*F<$>)2i7+L`hek!?m`@jf{`oA>#XpM88K^P7c&HOg+I>&|EE+JglrF|de2 zPNT-eIN*MjKDUDGDAA~j6K*@$_fk?Z^S(o44aI$P;%Y5FSg0{i6osesrR$lBH09#- zMIr%V$lOVbB1^jcWK_Pi)*hl@3!c_w0H0m0sbH{%vRKH$I72g3 zA{?&&_VFzH(DHo71YtvrRCvqn;1Q{UZkPJaHitvr*}QyuHRX2@$o%y$+pCiz9MXCI z-ud$SR`s2E>zrf!rqn0a;a37ay$p*5g(bptNnOGjp%`k zJKcI#H`;^G&a^prJHZ&F^vOXjf$92cbIW3%{lcbtb0Q_Pk<+<~p^oqz<{H$( z^=xp4cp;9dFscJ5QTU%YqbCfI@OM;DMtyo-A9;ofRASzE{LEgiySq}6dq|}B@SYSp z|B0Fj6J{g_1I^WDM)&oGN@f*^la~hFh!@Rw2dAD{?S-q$T>6RBdX`Vo4;5eS#Z$7= z%{6~KWI2bOG6F>J+eb5WS7OC4pN&R#3u6{MXuMR6-gy^9;i9+%%YLaYFG?zHv*B|oDA7)cFhJ+NH zG8ceZ6#{oFLSxVxu%)L*ZE?c-bkhh56`g+I=y zyhlx5$x>_AnmX2XTcMsZ{aZ(ixt{S+sFTWYU{xw?31-#48IJJ#X3K$kmb-xbxBgUM zMCAh{B~c%5?nl^Dmso_yH+7id<31gThekRR_dZgAO{NVlV5aG5wWJQe$~YmbKbjiK zQAu4H>BVJCIr>HHfH6z)Yx~K4n2e*SGFof?RWVnqYON@1g8Soj@4Hv&bf0Ot#%0%O zO`s1bpF=DP6p*f6Xz14Sio|9Q2yyTxYVUGnV+z3{#q)fpA*a;0(gpM8-?3EYmae$9 zcq^qHPqqQ+=SK>r9Lkl)C{68iHE2N!V9-~?j9ZE{q4y6LcnuNgdZ66{@K6+}t|a1z$@Gbl!LO;|XbY%$ zR)gQIa5Q1^O%IO!!i|q7s+VM9&7Q%hpqF16^_SsV+4ji-a=AE>0uEG-3BW1hujVOi ziUt|j+yD#D`*W>1C6)&vQs{-DoZYHqn;r7dnCaCTa*Dao-WKVc#Jb$lW=BirkSL*O zQ8DA)?4%WB$78(4SEj_n%N`k=S*XLS#MEjz-aYv+ni>#o1mPk~of!$C@e4hyaO9r| zw+Le9vzn z(a2%3S+gFRGa{E_H`w==_voUzj*0t}Su(o9tG6rd`|oimUNnS)gWXD~vj|$+^vg$2 z6p*DOaKQh+#rqHrWuk{%*5Zy^tUdylA>81}m$|#d(!27c4sPJ$aF;)iQz~@3^O5~` znB^Va8~j)T>!&yng7mZhN}&D!XZqpa;Ql1fwZlKo7Ch}5&lO(J*Dn<94sf3wmhW7j z{zV#)22cWYX7Ds`G}jvk-Yz({*eDOZ{TS{c4V`B`>d8?Hy)#kq_jW$l1@MEDtV!s8 z9@WHC?EGK1^hd^ia-KBV7$)3u04Q}K68 z>t3Jn=hKQQ*%kBF{Shz9S0W!Vr{|$oly)!3x&<5&OlC> z9EhPu)VmySQ_Q-^<>vtKhp z%rnXpaoC)0)0B1!gU{Q$I!wyaI0l))Z7NSZ$`;b_Ak+!9uk>o=VgB5ZWaN6 z8DXI)R&|&4Ky?%%OYhMDwW8JM8`M-D=Drp1t604atmT2x{!vb4yj6-#7KP2N*$@5N z(r~F66O)%?oya`$U#AdXUUa9gn0|X8ji^Jpp}QVJljZj@8CY^o3Wj%P=NlADMyQ4iPPDoWI2<^ z!&l57AIrYcUO%U^ysPsEGlPmC3ADNWYv1=a_wu+t)iQ2u&j;yK`F3IizBJ2Ep&Sfe za-4Uv9*U{UPYH~=da=Q37vFFk_~omC%bhYB1Y^l?5I5j-dvh8jk2V-vs>Z$JG@oMU zx85*YqNr6|8>SAQFJOlu?@+$7&(vLw9kMKCPm;^aVty5%Hi>(`S@dvPSns}@dPzX* z)N}w?}}B7-W{F zqc3ltzgwAZaN11`CKI5wK4&Q!aWe&BngF?;|8$A3Dbv=HtQO;c(`{gsuB`n-x_pdm z%v{>t-rZ_ie06a&TTz1}rj*GlnML2IEN{H(`{f>_vO+}JaFP>GZ)JDZE7pzYQSSbI zP44J`|5Y^)OAL2%izrTZ;^I%uM^ho)`RD?n(N=<~^oR|^sP7rdLLr2QVj40ATUiEM z)Lz|6>lX3;UYVfpl5-JNZhJiyxIG<|zt;Q4y|R(FMyR^nWqEXoNJD>~=zD{a&aDQ9 znAwKJiNBZx#7R@*;VK43ZsfR;IR(rGR?D}EPXt$0Dta})@vCrw^)Gl^9%K<$T4Q6s zr}e8iFcGc5OL#MS2Bn*==Bb`*3Hp+$X(xZl`6N+$q-P5s-nt1%IZdZ5aVWG2Y|ktV zv3zfdx%qJ`g}MMlL;m-JvXlyDa$<5 zU$v{{LRNA9ZscyuoB;$6;}uM3 z&|pSpG8iSKVIHit)*Ny1CvWs|ZoqZ1D&pZ;-uB6dYRY+d-4Py?B!vyZBAQmQaVE51 z%_N72+k<8$_GFVkG_#%jrbiJ{*XtOoqRnEwXn+)nueqjEHYU51;A?X2}+F@-2VJsZpQ#^6`FaK&Z#SAO^gaOY6vg zvt?skDF3*64UQrVmM%Etv>9ZetW9@ZgIpj1C!Hr8MK29mR>OT4e)Sn|w)VloLJZ6- zpICd}80?!ug8l7{!wl9T>~FSqW>O3heojl?7kxw3w~|w*8SfP(J(e1kV!Kgux3U4a zj_EQ7{7~oK(6eS2$Qk4n*IQRd)XOGcuEDa_P7yKc;|o*qq$48TB5KY}njeM4b-@Kx zBN9?P-}DLxPlHIAS&zO+si+1@i1eSFpJ(pe{ymMR3E^^a011~=WEDNUE~f2YsHDEv z?UX$IWTd##Zqc&nu22Hb&!MLKWvIDfZ}m~_;`~FujdYZ;2)~oqe!-PiA>qVl;pNr6 zDNEw!G^N?d{o~LOzkx@x53Dl^g>~q{;3uKa|24Hwmemz29WMnK@Mmw;$!FiU{sjxpOAF3KB-Y9u_kWP z1{bbI@0U&l(0Ckd>7<)2FHx$MOMQKb9x4%Ra4BRk9dy+{LVM60z9Vb zcA4oXS*Sj>3@)>$OMPCif2?m9vrteN-n?QzviLQ4jKAY=#Dc?mH+^PjeQ}hC+vw&QJQ@P@c_^iQg z3_pqH1~h!!TGotU&$o-q$(T(Wj;g}7W0Ks&A z?*JvE$D1gTLy4dF)2luAFM79z@IgzkIQ|F49=dZv8{364`pqUzjK@=qr^w5vmq;r{ zOJZl2$bIDhj57!19H>IYFCXN!EKk30$!6_z?4_N4Otr~J?wjC(+j#@wT zA1E0Av^e@1^QPdWI&!)P`gN1uwL1FN>Rx_2vBJ zY{|V2m~>{KRr;Q?M1mhgV=CysSk*WJFbdF!wmBp)Y|Ut$bGyder&;+)B)W%jsV>D`xcQZXPy4HQ7LXW=U+{Ahj5x6VV` zOV9|L`#-^xk`n>^s*O$g6x4HWYX<2J`Ea;*%#(Du?g1&M)Z~*<*5=sV9KM`R_oDZ+ z1lxaMEhPk9OxDauxHOxcjho)HYs?|Iu{;`?smPmVHFLAn=bLp(?*z;q*}Kxv@2RFV zI)MVNej9${@vxPYLd@=Rm&lb;>Pgx|cOegjL&nz~q?eA71MjMYO+6N@RUU9v2G^la zJHs=Vke1jiaeFV8RbPv0Z7~c9x5j2*eVCV9{Q-{NLN;C9i)){5-jF{1H?fM=&4wc! z#US~rCri}*UI^p1QcCb?b9%y&o;4Jk42(V+chxRQ-cw$RB$tb$UjB9f|FNpf-oIOy zqJd21&5PBP*U1a(!E@_9iJw+~fa7afCHb+`)Yj60xY2dDLA8_2;9!{3XC*NKPm-zJ zc4wE%e@bz-e^k@sYV4Oz?#O838E0bow0`KA#Kh>s&5_-7f#0{h;yNYSk^{(@PujJT zWGK|hd-;?X7~W_U*m`8eN*aB=wiAxb1s>}8CgqgG=8k&A-2Nc{XlH*7Mhr^DO zaFk#Th*!(DIqarwoO@f|ftWM-${5OzrKrhSeK#w0cH@A$@67$UU}t5hr%vErZCl%mo8W>NRJA^9bH)F@2*pk1#Xk5gALP5U* zRD@BdbUr+#N2=6--)gIv_t0mH{`q!){P#+ZKmU#R@4u9Q?teqk{kM9O|9^Pr|2Jm= zAd~rtYpRKWw{%?9N$#HLi(vj!)7|c=v9)LJXP%QqCLj>NplkL^WKQNueM-~8D^u>f z$j1zHkgD3_**Q7b);E?$lCq5xfM46>y)*K*T%787mb`wb0b}{kwNl?11A`%aBKt2` zSGzwjG6Di5y`}83IU*=h+(^#Be)!6G5LFpuBD#6__aImY(Wsb;6MjE78=F0#MmRYz zNofjm1KnlQuSA^bIV3AqwP>jhcfU~MAt1!OneeufZxvyqG89>yqL2=P=!L1QiO?Mko)<~>y;}y+ z-fWFajrX|oClI*-%7I44%OlS6Q&;Y(cG&Jwtm{h_Cc02VV!fXFw{P?h=zrD8{5tD` zW6=8TahX{QShTw(_O1l&FD~B#oVnVbC4=I7^M6!zcHh>|Qm@XSs%wy8iI4*8VZKl{ z_0KMhrX-5`u}bWC{?~RIlf4}2pT$^uDq$WjCYbK7fz@mqe;coJAmQUMu*m50`Lmhz z^+S+j*G-B^`M=#01AVFOS8e=%{oM9hv%(gSl8}ahq{1IPC}fX*utwI&_YMlxHJ%=^ z6b@8K^<8tnb_pcZYh^{dMR`y9h!2%f(wug@P4h8b!A0K@OZkE_TNG4iXz}c6e|!9H z*bL0bblKL{7gCM+_8r0-3uX=+WbW!143?BZJ{{6IhJ>>5RaUI@z$iN%+ zHLuPWFq0b3Zj7PhIBc-&migP{I{tR241DS({0>uKACJRYEXyyPLSK2F^kA40s&YOx zM^`iJEI5jsX?^)MXI<-CArUe8=rba;&Su;GgB{(nXR0>zP8P2**+}Bd=%`H1(cvfo zyKaU&#YF|=o3zhq{kDz=>hd^;hb~M6Nk}W!YunEueCxPl;$?RNGu^D}+`|^N`9apw z6faXUT>>Zo{IEqwnA0m#W)ot|?G0>tqM^&*3+6!VZnCxI1CzssBROrlzF)0-J!N54 z#MV9rK9=Q?HR?e@XZHo+D2#+W>v+wdY3qdk%$XpS6*Rb!i8XD0!*$+x^evz`OW`rrZ#AS^ZwOa`+$Hu63s) z&cEVI9<}hjVuRGtH!Zb%vp>~T1qIxSD9kfg11El(>5bH@$f*>6%hZ2ZA?%e_Fa&Ef z?a9Qnn)(YCOYjQNy~&?BcZJe9^VW%*pWY>RL&-;z#s@3%znQwY%84oGQncn4TNLBCg4b=Aki(A z?RhLVx4?yAC0zly)<_YX@oaBM{RQ;p?j&bysYIm8RfxNT!TFSS??TmFG-Wn&*uI1t z_%XJy(kQiGt5}PH##8j3cH?k+SJ+mNU0U_ZIFD2r|J-=BW)v0@U7 zYgRJ4Lo+^%PggMq*(Vj~8RFpCeA?JqCoGu@(}YjQT;X`JIpM9z(Xo8sF2Db<;Zp_8 z&2P&SjnGV})tu^m+TfRfGa9%s&8HZnJXEV+)@t)r(Z?@v)%@ zEa_i}SAv>v2j@SaXBh%kP454IGgeS-ksCDF-JQT4s0qTP6qOzzc?ML5--rql=X?AN zx3N*-RatA^W1QW*{Q5>(Z@$N3JR{Q5kQm zWme89Rj(HQ{QWZ1lGTQ<&}0{9m)*)R-ILrJH83>D>1KkFe(arXCYQOqN%CbD5x0}V zxW`a-p){%4)y;Rt?auy(43~wycah4aOEeCtEpx4ssgxf$Zk<$r>Fa-_c5Cv+=i3dM zl%^iOy<&g=X36d9?^SzOhgcq`%(*oq<)DVfl^V_cu}PtlV)$Q8lU-)Rs#334L|FX7 z%2-%MrDnD$L#=r8-Pg^J zZq-eE#Y(QVR6dTQA;@;%{(jfX^tIdGk`zwzQMxZzq*_ZUO6GPlZCVU%0Ha^%KHJYV zdY%QZkT6u3*dK1;veO_*>@E_X1RUp4{XfB67J5StDmm@r;=azRRVU)Sui($zXyn82 z>GCdg!#+e|OJU{Fytr%JvDVS!?=N-1n0zO`UclLkW5wS&?AIEmYS+xyN#L~rD_NB% zjx8bHwf7`D&unmhX{RqrLvm_g(PCq%d$>)N{!U*xDmXUWtq+r(9riFR-;={tQj?Q@ zLImYHC*~8=&cx%%Hdp!a)voRm9T6k3q&Cct=|Xwk1jB{3mlLsqOz9B%vOjl_3O9iE zhm9i&wEKy8a;#)0a9CZ8XeVy1q;Mv@t1nEIUCjAZ0d&b(? zAZdMryE4lW)E9}lEjL})&V2&Dc1JYX zc_=0$fX1kE4 zJ-`Sol>HE-$)+xk!N+Q{z34%{U)7Lqr@B;PYgPXuwv&nJ&MCpqcq*@2ec)V+htWnl zZZpyHZKlp)O0;&^2L+~`zOA)APvFo~&HInQRngPPi47Wu=`;aujp20_%2!nIOs$AY zkh@m)ns_^S^RlQE?-^gBL;14J97Zd1*92@Js-)qYMa9RZ5;az(!S$}9ApOBMqXo!# z4@6)YZsMt-qI!yhNg^GV%<;I#zhP1go@&dkwAQmyUd%nj!q1X)i>}?=J!zU)NSJWQ zL_e&VM}068UHUVmt1o)eOEP)L@*YvJw|OyU6g%$cIe~Voi>du)>H41;<1X%6j~otI zJ_W1h;6rs>It}6cTR#i^cD;84R3#Vzhv8mI>UBkFK<&3}_v#{>s^$B@z(U+)RX>F( zOw8R-s{dQk1w;Lu4cqtV z{fy}HQg+tpveAkcL!C>)p>5fzh2Obd%!@DsPHUqL!8hf>M3C0K0dIyChH9UA6~p`# z=wXhzF4YC94XBC32}*FHMx=BRFM5)Ax&vNmht55(3FP9bF>}p6o-%HK;bSVFSb~D( zQCZMO&DQr26m^8&;PB32EeanJ75)w5DhzJ$$gsHUAPq>5OEju;F3#?fp! z(6~FaU-?}Soxe?Gkf>&1cXCo-a6!UF!x5{=*%q?BQ&dhBxHEp%qFS244;hSEt0t`Lm zUElVdm$i2T*-h7x=pEjra9F!~xRO6noZd-u+w9HIoO&~{vN9rNDisQA&DRW@qd$47 z2#y*5iN9>0j{=}P3;aR_pj3dr{}VR+|Es|WlIOnOl;ZK9Syq~hyN94L8S9st$;T%= z>}Rv41Cz1RA%jKPbR}9`F7hW@bVao6E(H?&PYe9v0n|16?RLqhj?fqzxn)tZM`#IK z5qg+!iN5E-J35c0Hu@EByyKAabTPvDA0qtMex`?0&~@vXS0RbB;!-aNp{$aQxz{Yvs=7O1`sGH+dyYf&L!Ph=rvRb9*C^jYO|URYT;8mPb-b z!ZC+al~aR8Ea_2RlB)U;I*F87)8BI(xtJEyoEi~ECnCF@NTn49-2#92j> z8x1v{gMCUPjdl>$On*_Unh^K%riJH}8C2LCcJ?$&X+niGWK>eyJ(@b(CYgNF-3&-j zGK5=@W?tFKMEWm-Hh4(cirz+R246jutdI`WL>&ZjZg04HWzqa&7x!q#TdDAsrmPn2 zBbhs|!t5ZTJZ<685E`7|G$D4%p(Coc^X>({X@!j`4vV7J5FPW`X|-ESi{|hF>q+I+ zMrz9lchg>7k!DIcce1xz8_!Ol(d9%kXXVDf0>41z4uX|nvsM|0RmG2$E)WrR^9Lh$ z_Av8l1uV>u8(6LiF?~c?vXqk(n5~J?1>d+o`N3aX%Uol3}5&c2p&+RkEw+ zNX&nk_^!&wb_qfiUS?@yHkK|i)7HN4w=Q(|BK<6$Cj|#Xi{|1tp-BRw`up-9)hSwJ zYPMMe#j9|A{+KFeo?xFn1rL9?_yEBh3Pxp+l}S%&Z#{!}>l>m=Yw^OEBwWJN#TCS@%1sCSD82Qs~X~zCcTSoT2&6)-G4n z^mu(#z5JNC`yGvFqs@NCovj|@8-UO3eXF+ozO}Q7ZJxr!qQ<#fLf2Bt5<{EUZl7L7 z>n)q;d|NpZ{-Uxj+4t1%Stf(N-Jrrf+^ZC{HG!OeZHWH`-JK*{gTOy7k0{2 zR22X*+fJ=xPV1{Dz4iTv-n$u#BL?5mEuh2F6ES=o-#^6g031jUd840N>7dXqnQd0u z8*K!*93c$IHGO7`_N9GbIEP_H90C5rQ-lv8%=h1;vR*cO1TWi;xol9M4%>&PGU#F@ zmutEH7O3dfyOBvrB%l~NioP~iElh``qru+ce?%M7HpqNr^kA1H;qY3~MLm$(O5u+6 zaCaX{0@FgnfsiwR2=rw62+-e8z?$kU92gQ)yu6;1H{nozuq8(D70zyGv9jSaQRQfC zbZ2|y{?Ay$VWF*#CEBt{kx}iwx#UQw;QU*A2Ym-sq!~ou&Hk$%JRGRHuR($RaDM$_ zwjrpZk-Bk`_{R%ie|sUA{{SH9Z+|5INyY-AazrnU_J=HQkuqi4T^#Cwxk*v0A);n_;3txfT zJ;@OYUKxsmVfH>&s`j;LNYE6$|G`k3TI+mhg&%pS3)z;#$Gs ze;+#tD=L<%n>00wBuJ$mZ6lv%|3LF*%v{b*bE4KIEVcfp%4qdI<|iy>gCDa}Sv+fn zKx8M|m82stDQn-_Ua57(V%W9W4@V>Acm*PfIWZ>7U4W9i)rZ5l^~_@Q(?tfiomT3+ zDeQ(R2p*3Q^6+H#o5IGwvp@T-*G-jRF-dn-ePVGXdTnM^cPGe8w@IEiFi=n&R9#9ytUreGYlBcMTR%@t0XiUm$`RB^q2J`+4x59UzUTL zi(mPHKSf-wk#R>`Ozb1yXrx5cfiF)O^{EyF#;X0KyAsjpIF60xSHf~Ec=YgUzW(+2 zHTqjGJIl1qlo5tIRUvk(sHr*f5G9r!Hje_x@A0i(+G5&5kwvT6-3IXvBNamvp4;ZK zL#kO{@oWY2FWT(Eb`U-hPp0rIdwr8obIn?A>111TF?MDu&Tu}WZ6EJ*-$pgC86WyBkz!BDpup}SL%U+5WZ&+&(tmV<(X$t`&K6r1 zQaa=8HW?LP`6-)^OorIy;0;jkijq_3ce`C{CfDkX8;TlAxMlFfVQm;XgC8BlSyN?A zcTT;i4P9gY%Jih98fj0% z$at0=tEpP?tN%sF$jAsP6QCa2GSi+z;bW?i1ay8P!)|<<_N$k=BUoQGY3olp5AN0X z%tVh7Cp*-yamgi5g1ZKik|y4A7YExhI`UXZrJ$P$x=I!rl(5^Mtg8r?MLBOX8U=(1-sgrHX?yEw3)LS5SB`2vIs6SqBvwQe~L(Wlv?h z@60OP5S*QIpUK*+Q0M>F@N-F-D#vuNwCtq8G^~Y}j^pG7m#Z(+C$*m^JEvJBThrc) zrmMZ;nz3>r(Jt7-YHF6JGQ4qNHiORigJttU#L1#g6Mt)GF=H`f3bm@!7f$?5JUku? zl}o{kH6h6l+nHf=sr%<@7R}u?rE0aIPv5uR4^OZQ1tTIS>_oCrkHxJM0@gLU8M$mf z>1Nq%D7n#ml!txqqVnNfrJ4V*O)iLe&UaWLrwb78+(#K^WLzs@Upw4ouV=S4A zvlN@}`K|N&qM@qkjcTM75pD>_9u_OTP1GAvUP(oL5-4sO=e&qu13Ol-`Bm1>Vz3GE zL4pF+XKrUEPBaXR&WEbXN>fPyc6i756qE1uLV&^*?$ICneJg|IMb0_7hfHmhJt8#) zAIM8mmuhr}VGO*j0Y}I&3_dQbWFNn;xANiCVtKK9yW|GQMroL??`E+0QUa|R5#0f_JDpWX0H8Y7*^oHQ{ z&>+i4VVeW7K{C;Agl=ja4K*1Rp~ueiyHt~xuZ<@1ZZh>|{V(r&Y~Ij}s@|+5=;;eq zY$`XZSbF3aQ_{+#lT%Op)FPIz$OfBo5(zFx92yu|8-968xN`Bl+^*?`_)7XQ$4cXK z6LToXxoSEns+(I$@ouEe+);rZE@E*}f1j??0Tfd==SL%~CP2D;IKFv_hNhf|l)bkE z!@abHKsRuT=N!k8`_Jo(8}=26fre3wb7v*1&9Qjg7G*8rh-_xHW5th#2EXppxneT3 zV}A{tjbb@Bw_zW@&F`PDHka=dN8^6n&)r-8kgC-(c&&dXXRXOM5LTYpbwuwfg4!ZG z+^LmVQ@$(4EU99L32ikrVP?m}cD0q+;b3Cu82qb{)Z&~R@>^*$Ig8u(0@}=UKeuTS z6T`+`*auQ*lab;lZh9#oxJ*}ugF6mTC@iP8Yp^X47`H0FUbNW>qAb_Kf9QNHmd?4F zk67itwZ^2u)M;7t{IYuoOC8ZXFgjaRn{7U=mSHb-#)|!!@l9M_qaw#nT`n z&W;q%3OW^}bUV%M^c$f#a3zUtX6;j*B4L{944%5H01q!bKk=TQ?0~*h>_s8qkiNv{ zS{HOlX<(uztz8Xsxs5=IyWCkHBUp#fQM!24^Etm1^0?8=yU$FGlhusbDfv>x*y@3U zGHN(fwbQK~qZcaRfePunmIhSSmTO(j9NFoa;F`;^ z$l|!Oq3J3ojeANe^J{v%xNv3RGSFIW@y`EGu!acc6e&)dIM2sk>&yat5V@lE_V7%D z>zkVzsV7TRTs$ZtVIe6p*H!cQD}rwzR8VyAPz;bu3N3oTuhA6l$9W#ttK-H=z?<#r z>&u&cPvD1|Lvv#*xIOkkULNB597&eni^Iv|9cTMtiS+U7C-N8Hwwalj;Q`cNMm0X> zt+yWl3W~C=c23*v?-aj&Xh@pF`YVf{y=v2TYF-9z+LsCtc%g4ZFM>$1FU2-Bl~TtgTslCf8dQzl;Ifj1_%-QBX zcI=G(UScp?bGjseCM8#?9$?&M*m;uk)n;0=_4Is9OqqXNOMB|@Bu&Y}+Ik6dK}`mH zv>$Wod^mWOAkKuivW=}L@(gXq;NajnVkR{ggGt85#wXfB``6DF%F3(+Vx{UJYBX;= zSDZEp06!z`9 DataOutput: + data = "hello".encode("utf-8") + return DataOutput(description="A plumbus", data=data) + + +@app.post("/data-in-out") +def post_data_in_out(body: DataInputOutput) -> DataInputOutput: + return body diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index b83bc1b55..0535c806f 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -27,7 +27,7 @@ from pydantic._internal._schema_generation_shared import ( # type: ignore[attr- ) from pydantic._internal._typing_extra import eval_type_lenient from pydantic.fields import FieldInfo as FieldInfo -from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema +from pydantic.json_schema import GenerateJsonSchema as _GenerateJsonSchema from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue from pydantic_core import CoreSchema as CoreSchema from pydantic_core import PydanticUndefined @@ -40,6 +40,23 @@ RequiredParam = PydanticUndefined Undefined = PydanticUndefined evaluate_forwardref = eval_type_lenient + +class GenerateJsonSchema(_GenerateJsonSchema): + # TODO: remove when this is merged (or equivalent): https://github.com/pydantic/pydantic/pull/12841 + # and dropping support for any version of Pydantic before that one (so, in a very long time) + def bytes_schema(self, schema: CoreSchema) -> JsonSchemaValue: + json_schema = {"type": "string", "contentMediaType": "application/octet-stream"} + bytes_mode = ( + self._config.ser_json_bytes + if self.mode == "serialization" + else self._config.val_json_bytes + ) + if bytes_mode == "base64": + json_schema["contentEncoding"] = "base64" + self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes) + return json_schema + + # TODO: remove when dropping support for Pydantic < v2.12.3 _Attrs = { "default": ..., diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index c04b5f0f3..479e1a7c3 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -139,7 +139,7 @@ class UploadFile(StarletteUploadFile): def __get_pydantic_json_schema__( cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler ) -> dict[str, Any]: - return {"type": "string", "format": "binary"} + return {"type": "string", "contentMediaType": "application/octet-stream"} @classmethod def __get_pydantic_core_schema__( diff --git a/pyproject.toml b/pyproject.toml index 1e6fda3b1..92b473945 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -548,6 +548,7 @@ ignore = [ "docs_src/security/tutorial005_an_py39.py" = ["B904"] "docs_src/security/tutorial005_py310.py" = ["B904"] "docs_src/security/tutorial005_py39.py" = ["B904"] +"docs_src/json_base64_bytes/tutorial001_py310.py" = ["UP012"] [tool.ruff.lint.isort] known-third-party = ["fastapi", "pydantic", "starlette"] diff --git a/scripts/playwright/json_base64_bytes/image01.py b/scripts/playwright/json_base64_bytes/image01.py new file mode 100644 index 000000000..56c57e1c3 --- /dev/null +++ b/scripts/playwright/json_base64_bytes/image01.py @@ -0,0 +1,37 @@ +import subprocess +import time + +import httpx +from playwright.sync_api import Playwright, sync_playwright + + +# Run playwright codegen to generate the code below, copy paste the sections in run() +def run(playwright: Playwright) -> None: + browser = playwright.chromium.launch(headless=False) + # Update the viewport manually + context = browser.new_context(viewport={"width": 960, "height": 1080}) + page = context.new_page() + page.goto("http://localhost:8000/docs") + page.get_by_role("button", name="POST /data Post Data").click() + # Manually add the screenshot + page.screenshot(path="docs/en/docs/img/tutorial/json-base64-bytes/image01.png") + + # --------------------- + context.close() + browser.close() + + +process = subprocess.Popen( + ["fastapi", "run", "docs_src/json_base64_bytes/tutorial001_py310.py"] +) +try: + for _ in range(3): + try: + response = httpx.get("http://localhost:8000/docs") + except httpx.ConnectError: + time.sleep(1) + break + with sync_playwright() as playwright: + run(playwright) +finally: + process.terminate() diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py index 68280fcf3..5332795f4 100644 --- a/tests/test_request_params/test_file/test_list.py +++ b/tests/test_request_params/test_file/test_list.py @@ -37,7 +37,10 @@ def test_list_schema(path: str): "properties": { "p": { "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, "title": "P", }, }, @@ -115,7 +118,10 @@ def test_list_alias_schema(path: str): "properties": { "p_alias": { "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, "title": "P Alias", }, }, @@ -221,7 +227,10 @@ def test_list_validation_alias_schema(path: str): "properties": { "p_val_alias": { "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, "title": "P Val Alias", }, }, @@ -338,7 +347,10 @@ def test_list_alias_and_validation_alias_schema(path: str): "properties": { "p_val_alias": { "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, "title": "P Val Alias", }, }, diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py index b4dc11a06..3d1aac25e 100644 --- a/tests/test_request_params/test_file/test_optional.py +++ b/tests/test_request_params/test_file/test_optional.py @@ -37,7 +37,7 @@ def test_optional_schema(path: str): "properties": { "p": { "anyOf": [ - {"type": "string", "format": "binary"}, + {"type": "string", "contentMediaType": "application/octet-stream"}, {"type": "null"}, ], "title": "P", @@ -109,7 +109,7 @@ def test_optional_alias_schema(path: str): "properties": { "p_alias": { "anyOf": [ - {"type": "string", "format": "binary"}, + {"type": "string", "contentMediaType": "application/octet-stream"}, {"type": "null"}, ], "title": "P Alias", @@ -200,7 +200,7 @@ def test_optional_validation_alias_schema(path: str): "properties": { "p_val_alias": { "anyOf": [ - {"type": "string", "format": "binary"}, + {"type": "string", "contentMediaType": "application/octet-stream"}, {"type": "null"}, ], "title": "P Val Alias", @@ -296,7 +296,7 @@ def test_optional_alias_and_validation_alias_schema(path: str): "properties": { "p_val_alias": { "anyOf": [ - {"type": "string", "format": "binary"}, + {"type": "string", "contentMediaType": "application/octet-stream"}, {"type": "null"}, ], "title": "P Val Alias", diff --git a/tests/test_request_params/test_file/test_optional_list.py b/tests/test_request_params/test_file/test_optional_list.py index a506ec991..3c211b1e8 100644 --- a/tests/test_request_params/test_file/test_optional_list.py +++ b/tests/test_request_params/test_file/test_optional_list.py @@ -41,7 +41,10 @@ def test_optional_list_schema(path: str): "anyOf": [ { "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, }, {"type": "null"}, ], @@ -116,7 +119,10 @@ def test_optional_list_alias_schema(path: str): "anyOf": [ { "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, }, {"type": "null"}, ], @@ -205,7 +211,10 @@ def test_optional_validation_alias_schema(path: str): "anyOf": [ { "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, }, {"type": "null"}, ], @@ -301,7 +310,10 @@ def test_optional_list_alias_and_validation_alias_schema(path: str): "anyOf": [ { "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, }, {"type": "null"}, ], diff --git a/tests/test_request_params/test_file/test_required.py b/tests/test_request_params/test_file/test_required.py index a0f9d23a6..22d6c0fff 100644 --- a/tests/test_request_params/test_file/test_required.py +++ b/tests/test_request_params/test_file/test_required.py @@ -35,7 +35,11 @@ def test_required_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p": {"title": "P", "type": "string", "format": "binary"}, + "p": { + "title": "P", + "type": "string", + "contentMediaType": "application/octet-stream", + }, }, "required": ["p"], "title": body_model_name, @@ -109,7 +113,11 @@ def test_required_alias_schema(path: str): assert app.openapi()["components"]["schemas"][body_model_name] == { "properties": { - "p_alias": {"title": "P Alias", "type": "string", "format": "binary"}, + "p_alias": { + "title": "P Alias", + "type": "string", + "contentMediaType": "application/octet-stream", + }, }, "required": ["p_alias"], "title": body_model_name, @@ -216,7 +224,7 @@ def test_required_validation_alias_schema(path: str): "p_val_alias": { "title": "P Val Alias", "type": "string", - "format": "binary", + "contentMediaType": "application/octet-stream", }, }, "required": ["p_val_alias"], @@ -329,7 +337,7 @@ def test_required_alias_and_validation_alias_schema(path: str): "p_val_alias": { "title": "P Val Alias", "type": "string", - "format": "binary", + "contentMediaType": "application/octet-stream", }, }, "required": ["p_val_alias"], diff --git a/tests/test_tutorial/test_json_base64_bytes/__init__.py b/tests/test_tutorial/test_json_base64_bytes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py b/tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py new file mode 100644 index 000000000..4d70bca5f --- /dev/null +++ b/tests/test_tutorial/test_json_base64_bytes/test_tutorial001.py @@ -0,0 +1,225 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +from tests.utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[pytest.param("tutorial001_py310", marks=needs_py310)], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.json_base64_bytes.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_post_data(client: TestClient): + response = client.post( + "/data", + json={ + "description": "A file", + "data": "SGVsbG8sIFdvcmxkIQ==", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == {"description": "A file", "content": "Hello, World!"} + + +def test_get_data(client: TestClient): + response = client.get("/data") + assert response.status_code == 200, response.text + assert response.json() == {"description": "A plumbus", "data": "aGVsbG8="} + + +def test_post_data_in_out(client: TestClient): + response = client.post( + "/data-in-out", + json={ + "description": "A plumbus", + "data": "SGVsbG8sIFdvcmxkIQ==", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "description": "A plumbus", + "data": "SGVsbG8sIFdvcmxkIQ==", + } + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/data": { + "get": { + "summary": "Get Data", + "operationId": "get_data_data_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataOutput" + } + } + }, + } + }, + }, + "post": { + "summary": "Post Data", + "operationId": "post_data_data_post", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/DataInput"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, + "/data-in-out": { + "post": { + "summary": "Post Data In Out", + "operationId": "post_data_in_out_data_in_out_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataInputOutput" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DataInputOutput" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "DataInput": { + "properties": { + "description": {"type": "string", "title": "Description"}, + "data": { + "type": "string", + "contentEncoding": "base64", + "contentMediaType": "application/octet-stream", + "title": "Data", + }, + }, + "type": "object", + "required": ["description", "data"], + "title": "DataInput", + }, + "DataInputOutput": { + "properties": { + "description": {"type": "string", "title": "Description"}, + "data": { + "type": "string", + "contentEncoding": "base64", + "contentMediaType": "application/octet-stream", + "title": "Data", + }, + }, + "type": "object", + "required": ["description", "data"], + "title": "DataInputOutput", + }, + "DataOutput": { + "properties": { + "description": {"type": "string", "title": "Description"}, + "data": { + "type": "string", + "contentEncoding": "base64", + "contentMediaType": "application/octet-stream", + "title": "Data", + }, + }, + "type": "object", + "required": ["description", "data"], + "title": "DataOutput", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "ctx": {"title": "Context", "type": "object"}, + "input": {"title": "Input"}, + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py index 4d3c35d65..797225bc2 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001.py @@ -162,8 +162,8 @@ def test_openapi_schema(client: TestClient): "properties": { "file": { "title": "File", + "contentMediaType": "application/octet-stream", "type": "string", - "format": "binary", } }, }, @@ -175,7 +175,7 @@ def test_openapi_schema(client: TestClient): "file": { "title": "File", "type": "string", - "format": "binary", + "contentMediaType": "application/octet-stream", } }, }, diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py index f199b992a..4e3c33818 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py @@ -134,7 +134,10 @@ def test_openapi_schema(client: TestClient): "file": { "title": "File", "anyOf": [ - {"type": "string", "format": "binary"}, + { + "type": "string", + "contentMediaType": "application/octet-stream", + }, {"type": "null"}, ], } @@ -147,7 +150,10 @@ def test_openapi_schema(client: TestClient): "file": { "title": "File", "anyOf": [ - {"type": "string", "format": "binary"}, + { + "type": "string", + "contentMediaType": "application/octet-stream", + }, {"type": "null"}, ], } diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py index ce22c1b5c..bccc61704 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py @@ -123,7 +123,7 @@ def test_openapi_schema(client: TestClient): "title": "File", "type": "string", "description": "A file read as bytes", - "format": "binary", + "contentMediaType": "application/octet-stream", } }, }, @@ -134,9 +134,9 @@ def test_openapi_schema(client: TestClient): "properties": { "file": { "title": "File", + "contentMediaType": "application/octet-stream", "type": "string", "description": "A file read as UploadFile", - "format": "binary", } }, }, diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py index ebf76b3a0..123468d48 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002.py @@ -195,7 +195,10 @@ def test_openapi_schema(client: TestClient): "files": { "title": "Files", "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, } }, }, @@ -207,7 +210,10 @@ def test_openapi_schema(client: TestClient): "files": { "title": "Files", "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, } }, }, diff --git a/tests/test_tutorial/test_request_files/test_tutorial003.py b/tests/test_tutorial/test_request_files/test_tutorial003.py index f11658d27..2f554d948 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial003.py +++ b/tests/test_tutorial/test_request_files/test_tutorial003.py @@ -165,7 +165,10 @@ def test_openapi_schema(client: TestClient): "files": { "title": "Files", "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, "description": "Multiple files as bytes", } }, @@ -178,7 +181,10 @@ def test_openapi_schema(client: TestClient): "files": { "title": "Files", "type": "array", - "items": {"type": "string", "format": "binary"}, + "items": { + "type": "string", + "contentMediaType": "application/octet-stream", + }, "description": "Multiple files as UploadFile", } }, diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py index e2462e040..cc10d8bec 100644 --- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py @@ -198,12 +198,12 @@ def test_openapi_schema(client: TestClient): "file": { "title": "File", "type": "string", - "format": "binary", + "contentMediaType": "application/octet-stream", }, "fileb": { "title": "Fileb", + "contentMediaType": "application/octet-stream", "type": "string", - "format": "binary", }, "token": {"title": "Token", "type": "string"}, }, From 1bf99b9a112031090224bca6727b9b5fa8028d79 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Feb 2026 13:01:58 +0000 Subject: [PATCH 151/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d579a3cb0..fe70b9e87 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix JSON Schema for files, use `contentMediaType` instead of `format: binary`. PR [#14953](https://github.com/fastapi/fastapi/pull/14953) by [@tiangolo](https://github.com/tiangolo). + ### Docs * 🔨 Add Kapa.ai widget (AI chatbot). PR [#14938](https://github.com/fastapi/fastapi/pull/14938) by [@tiangolo](https://github.com/tiangolo). From 04ff07fecd5273f4cd58d1de43a3b445c8e75852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 21 Feb 2026 14:07:09 +0100 Subject: [PATCH 152/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index fe70b9e87..1b0c73dc9 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,7 +9,7 @@ hide: ### Fixes -* 🐛 Fix JSON Schema for files, use `contentMediaType` instead of `format: binary`. PR [#14953](https://github.com/fastapi/fastapi/pull/14953) by [@tiangolo](https://github.com/tiangolo). +* ♻️ Fix JSON Schema for bytes, use `"contentMediaType": "application/octet-stream"` instead of `"format": "binary"`. PR [#14953](https://github.com/fastapi/fastapi/pull/14953) by [@tiangolo](https://github.com/tiangolo). ### Docs From c91fed958e46492be96ab8bc19a16f74c3c2048a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 21 Feb 2026 14:08:06 +0100 Subject: [PATCH 153/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?9.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1b0c73dc9..ad3150489 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.129.1 + ### Fixes * ♻️ Fix JSON Schema for bytes, use `"contentMediaType": "application/octet-stream"` instead of `"format": "binary"`. PR [#14953](https://github.com/fastapi/fastapi/pull/14953) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index de5a0be38..1591a81cf 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.129.0" +__version__ = "0.129.1" from starlette import status as status From f55ab7e020701de7b5e467dad9bafcf1aeab30c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 21 Feb 2026 08:16:03 -0800 Subject: [PATCH 154/195] =?UTF-8?q?=F0=9F=94=A7=20Update=20pyproject.toml,?= =?UTF-8?q?=20remove=20unneeded=20lines=20(#14956)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/labeler.yml | 1 - docs_src/python_types/tutorial005_py39.py | 2 - pyproject.toml | 213 +--------------------- 3 files changed, 3 insertions(+), 213 deletions(-) delete mode 100644 docs_src/python_types/tutorial005_py39.py diff --git a/.github/labeler.yml b/.github/labeler.yml index 57c5e1120..2ee754116 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -30,7 +30,6 @@ internal: - .gitignore - .pre-commit-config.yaml - pdm_build.py - - requirements*.txt - uv.lock - docs/en/data/sponsors.yml - docs/en/overrides/main.html diff --git a/docs_src/python_types/tutorial005_py39.py b/docs_src/python_types/tutorial005_py39.py deleted file mode 100644 index 6c8edb0ec..000000000 --- a/docs_src/python_types/tutorial005_py39.py +++ /dev/null @@ -1,2 +0,0 @@ -def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes): - return item_a, item_b, item_c, item_d, item_e diff --git a/pyproject.toml b/pyproject.toml index 92b473945..d2a025a72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,6 @@ Issues = "https://github.com/fastapi/fastapi/issues" Changelog = "https://fastapi.tiangolo.com/release-notes/" [project.optional-dependencies] - standard = [ "fastapi-cli[standard] >=0.0.8", # For the test client @@ -245,25 +244,16 @@ disallow_incomplete_defs = false disallow_untyped_defs = false disallow_untyped_calls = false -[tool.pytest.ini_options] +[tool.pytest] +minversion = "9.0" addopts = [ "--strict-config", "--strict-markers", "--ignore=docs_src", ] -xfail_strict = true -junit_family = "xunit2" +strict_xfail = true filterwarnings = [ "error", - # 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 - # https://github.com/sqlalchemy/sqlalchemy/commit/59521abcc0676e936b31a523bd968fc157fef0c2 - 'ignore:datetime\.datetime\.utcfromtimestamp\(\) is deprecated and scheduled for removal in a future version\..*:DeprecationWarning:sqlalchemy', - # Trio 24.1.0 raises a warning from attrs - # Ref: https://github.com/python-trio/trio/pull/3054 - # Remove once there's a new version of Trio - 'ignore:The `hash` argument is deprecated*:DeprecationWarning:trio', ] [tool.coverage.run] @@ -280,7 +270,6 @@ dynamic_context = "test_function" omit = [ "docs_src/response_model/tutorial003_04_py39.py", "docs_src/response_model/tutorial003_04_py310.py", - "docs_src/dependencies/tutorial008_an_py39.py", # difficult to mock "docs_src/dependencies/tutorial013_an_py310.py", # temporary code example? "docs_src/dependencies/tutorial014_an_py310.py", # temporary code example? # Pydantic v1 migration, no longer tested @@ -288,202 +277,6 @@ omit = [ "docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py", "docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py", "docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py", - # TODO: remove all the ignores below when all translations use the new Python 3.10 files - "docs_src/additional_responses/tutorial001_py39.py", - "docs_src/additional_responses/tutorial003_py39.py", - "docs_src/advanced_middleware/tutorial001_py39.py", - "docs_src/advanced_middleware/tutorial002_py39.py", - "docs_src/advanced_middleware/tutorial003_py39.py", - "docs_src/app_testing/app_a_py39/main.py", - "docs_src/app_testing/app_a_py39/test_main.py", - "docs_src/app_testing/tutorial001_py39.py", - "docs_src/app_testing/tutorial002_py39.py", - "docs_src/app_testing/tutorial003_py39.py", - "docs_src/app_testing/tutorial004_py39.py", - "docs_src/async_tests/app_a_py39/main.py", - "docs_src/async_tests/app_a_py39/test_main.py", - "docs_src/authentication_error_status_code/tutorial001_an_py39.py", - "docs_src/background_tasks/tutorial001_py39.py", - "docs_src/behind_a_proxy/tutorial001_01_py39.py", - "docs_src/behind_a_proxy/tutorial001_py39.py", - "docs_src/behind_a_proxy/tutorial002_py39.py", - "docs_src/behind_a_proxy/tutorial003_py39.py", - "docs_src/behind_a_proxy/tutorial004_py39.py", - "docs_src/bigger_applications/app_an_py39/dependencies.py", - "docs_src/bigger_applications/app_an_py39/internal/admin.py", - "docs_src/bigger_applications/app_an_py39/main.py", - "docs_src/bigger_applications/app_an_py39/routers/items.py", - "docs_src/bigger_applications/app_an_py39/routers/users.py", - "docs_src/bigger_applications/app_py39/dependencies.py", - "docs_src/bigger_applications/app_py39/main.py", - "docs_src/body_nested_models/tutorial008_py39.py", - "docs_src/body_nested_models/tutorial009_py39.py", - "docs_src/conditional_openapi/tutorial001_py39.py", - "docs_src/configure_swagger_ui/tutorial001_py39.py", - "docs_src/configure_swagger_ui/tutorial002_py39.py", - "docs_src/configure_swagger_ui/tutorial003_py39.py", - "docs_src/cors/tutorial001_py39.py", - "docs_src/custom_docs_ui/tutorial001_py39.py", - "docs_src/custom_docs_ui/tutorial002_py39.py", - "docs_src/custom_response/tutorial001_py39.py", - "docs_src/custom_response/tutorial001b_py39.py", - "docs_src/custom_response/tutorial002_py39.py", - "docs_src/custom_response/tutorial003_py39.py", - "docs_src/custom_response/tutorial004_py39.py", - "docs_src/custom_response/tutorial005_py39.py", - "docs_src/custom_response/tutorial006_py39.py", - "docs_src/custom_response/tutorial006b_py39.py", - "docs_src/custom_response/tutorial006c_py39.py", - "docs_src/custom_response/tutorial007_py39.py", - "docs_src/custom_response/tutorial008_py39.py", - "docs_src/custom_response/tutorial009_py39.py", - "docs_src/custom_response/tutorial009b_py39.py", - "docs_src/custom_response/tutorial009c_py39.py", - "docs_src/custom_response/tutorial010_py39.py", - "docs_src/debugging/tutorial001_py39.py", - "docs_src/dependencies/tutorial006_an_py39.py", - "docs_src/dependencies/tutorial006_py39.py", - "docs_src/dependencies/tutorial007_py39.py", - "docs_src/dependencies/tutorial008_py39.py", - "docs_src/dependencies/tutorial008b_an_py39.py", - "docs_src/dependencies/tutorial008b_py39.py", - "docs_src/dependencies/tutorial008c_an_py39.py", - "docs_src/dependencies/tutorial008c_py39.py", - "docs_src/dependencies/tutorial008d_an_py39.py", - "docs_src/dependencies/tutorial008d_py39.py", - "docs_src/dependencies/tutorial008e_an_py39.py", - "docs_src/dependencies/tutorial008e_py39.py", - "docs_src/dependencies/tutorial010_py39.py", - "docs_src/dependencies/tutorial011_an_py39.py", - "docs_src/dependencies/tutorial011_py39.py", - "docs_src/dependencies/tutorial012_an_py39.py", - "docs_src/dependencies/tutorial012_py39.py", - "docs_src/events/tutorial001_py39.py", - "docs_src/events/tutorial002_py39.py", - "docs_src/events/tutorial003_py39.py", - "docs_src/extending_openapi/tutorial001_py39.py", - "docs_src/extra_models/tutorial004_py39.py", - "docs_src/extra_models/tutorial005_py39.py", - "docs_src/first_steps/tutorial001_py39.py", - "docs_src/first_steps/tutorial003_py39.py", - "docs_src/generate_clients/tutorial001_py39.py", - "docs_src/generate_clients/tutorial002_py39.py", - "docs_src/generate_clients/tutorial003_py39.py", - "docs_src/generate_clients/tutorial004_py39.py", - "docs_src/graphql_/tutorial001_py39.py", - "docs_src/handling_errors/tutorial001_py39.py", - "docs_src/handling_errors/tutorial002_py39.py", - "docs_src/handling_errors/tutorial003_py39.py", - "docs_src/handling_errors/tutorial004_py39.py", - "docs_src/handling_errors/tutorial005_py39.py", - "docs_src/handling_errors/tutorial006_py39.py", - "docs_src/metadata/tutorial001_1_py39.py", - "docs_src/metadata/tutorial001_py39.py", - "docs_src/metadata/tutorial002_py39.py", - "docs_src/metadata/tutorial003_py39.py", - "docs_src/metadata/tutorial004_py39.py", - "docs_src/middleware/tutorial001_py39.py", - "docs_src/openapi_webhooks/tutorial001_py39.py", - "docs_src/path_operation_advanced_configuration/tutorial001_py39.py", - "docs_src/path_operation_advanced_configuration/tutorial002_py39.py", - "docs_src/path_operation_advanced_configuration/tutorial003_py39.py", - "docs_src/path_operation_advanced_configuration/tutorial005_py39.py", - "docs_src/path_operation_advanced_configuration/tutorial006_py39.py", - "docs_src/path_operation_advanced_configuration/tutorial007_py39.py", - "docs_src/path_operation_configuration/tutorial002b_py39.py", - "docs_src/path_operation_configuration/tutorial006_py39.py", - "docs_src/path_params/tutorial001_py39.py", - "docs_src/path_params/tutorial002_py39.py", - "docs_src/path_params/tutorial003_py39.py", - "docs_src/path_params/tutorial003b_py39.py", - "docs_src/path_params/tutorial004_py39.py", - "docs_src/path_params/tutorial005_py39.py", - "docs_src/path_params_numeric_validations/tutorial002_an_py39.py", - "docs_src/path_params_numeric_validations/tutorial002_py39.py", - "docs_src/path_params_numeric_validations/tutorial003_an_py39.py", - "docs_src/path_params_numeric_validations/tutorial003_py39.py", - "docs_src/path_params_numeric_validations/tutorial004_an_py39.py", - "docs_src/path_params_numeric_validations/tutorial004_py39.py", - "docs_src/path_params_numeric_validations/tutorial005_an_py39.py", - "docs_src/path_params_numeric_validations/tutorial005_py39.py", - "docs_src/path_params_numeric_validations/tutorial006_an_py39.py", - "docs_src/path_params_numeric_validations/tutorial006_py39.py", - "docs_src/python_types/tutorial001_py39.py", - "docs_src/python_types/tutorial002_py39.py", - "docs_src/python_types/tutorial003_py39.py", - "docs_src/python_types/tutorial004_py39.py", - "docs_src/python_types/tutorial005_py39.py", - "docs_src/python_types/tutorial006_py39.py", - "docs_src/python_types/tutorial007_py39.py", - "docs_src/python_types/tutorial008_py39.py", - "docs_src/python_types/tutorial008b_py39.py", - "docs_src/python_types/tutorial009_py39.py", - "docs_src/python_types/tutorial009b_py39.py", - "docs_src/python_types/tutorial009c_py39.py", - "docs_src/python_types/tutorial010_py39.py", - "docs_src/python_types/tutorial013_py39.py", - "docs_src/query_params/tutorial001_py39.py", - "docs_src/query_params/tutorial005_py39.py", - "docs_src/query_params_str_validations/tutorial005_an_py39.py", - "docs_src/query_params_str_validations/tutorial005_py39.py", - "docs_src/query_params_str_validations/tutorial006_an_py39.py", - "docs_src/query_params_str_validations/tutorial006_py39.py", - "docs_src/query_params_str_validations/tutorial012_an_py39.py", - "docs_src/query_params_str_validations/tutorial012_py39.py", - "docs_src/query_params_str_validations/tutorial013_an_py39.py", - "docs_src/query_params_str_validations/tutorial013_py39.py", - "docs_src/request_files/tutorial001_03_an_py39.py", - "docs_src/request_files/tutorial001_03_py39.py", - "docs_src/request_files/tutorial001_an_py39.py", - "docs_src/request_files/tutorial001_py39.py", - "docs_src/request_files/tutorial002_an_py39.py", - "docs_src/request_files/tutorial002_py39.py", - "docs_src/request_files/tutorial003_an_py39.py", - "docs_src/request_files/tutorial003_py39.py", - "docs_src/request_form_models/tutorial001_an_py39.py", - "docs_src/request_form_models/tutorial001_py39.py", - "docs_src/request_form_models/tutorial002_an_py39.py", - "docs_src/request_form_models/tutorial002_py39.py", - "docs_src/request_forms/tutorial001_an_py39.py", - "docs_src/request_forms/tutorial001_py39.py", - "docs_src/request_forms_and_files/tutorial001_an_py39.py", - "docs_src/request_forms_and_files/tutorial001_py39.py", - "docs_src/response_change_status_code/tutorial001_py39.py", - "docs_src/response_cookies/tutorial001_py39.py", - "docs_src/response_cookies/tutorial002_py39.py", - "docs_src/response_directly/tutorial002_py39.py", - "docs_src/response_headers/tutorial001_py39.py", - "docs_src/response_headers/tutorial002_py39.py", - "docs_src/response_model/tutorial003_02_py39.py", - "docs_src/response_model/tutorial003_03_py39.py", - "docs_src/response_status_code/tutorial001_py39.py", - "docs_src/response_status_code/tutorial002_py39.py", - "docs_src/security/tutorial001_an_py39.py", - "docs_src/security/tutorial001_py39.py", - "docs_src/security/tutorial006_an_py39.py", - "docs_src/security/tutorial006_py39.py", - "docs_src/security/tutorial007_an_py39.py", - "docs_src/security/tutorial007_py39.py", - "docs_src/settings/app01_py39/config.py", - "docs_src/settings/app01_py39/main.py", - "docs_src/settings/app02_an_py39/config.py", - "docs_src/settings/app02_an_py39/main.py", - "docs_src/settings/app02_an_py39/test_main.py", - "docs_src/settings/app02_py39/config.py", - "docs_src/settings/app02_py39/main.py", - "docs_src/settings/app02_py39/test_main.py", - "docs_src/settings/app03_an_py39/config.py", - "docs_src/settings/app03_an_py39/main.py", - "docs_src/settings/app03_py39/config.py", - "docs_src/settings/app03_py39/main.py", - "docs_src/settings/tutorial001_py39.py", - "docs_src/static_files/tutorial001_py39.py", - "docs_src/sub_applications/tutorial001_py39.py", - "docs_src/templates/tutorial001_py39.py", - "docs_src/using_request_directly/tutorial001_py39.py", - "docs_src/websockets/tutorial001_py39.py", - "docs_src/websockets/tutorial003_py39.py", - "docs_src/wsgi/tutorial001_py39.py", ] [tool.coverage.report] From 1b9a351ee844ff49cdcfa3913f38a12b24b79173 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Feb 2026 16:16:27 +0000 Subject: [PATCH 155/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ad3150489..1f348af99 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* 🔧 Update pyproject.toml, remove unneeded lines. PR [#14956](https://github.com/fastapi/fastapi/pull/14956) by [@tiangolo](https://github.com/tiangolo). + ## 0.129.1 ### Fixes From 083b6ebe9efa76cdee3fe3f74ea686a2ea860f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 21 Feb 2026 08:26:38 -0800 Subject: [PATCH 156/195] =?UTF-8?q?=E2=9E=96=20Drop=20support=20for=20`fas?= =?UTF-8?q?tapi-slim`,=20no=20more=20versions=20will=20be=20released,=20us?= =?UTF-8?q?e=20only=20`"fastapi[standard]"`=20or=20`fastapi`=20(#14957)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/labeler.yml | 1 - pdm_build.py | 40 ---------------------------------------- pyproject.toml | 26 -------------------------- 3 files changed, 67 deletions(-) delete mode 100644 pdm_build.py diff --git a/.github/labeler.yml b/.github/labeler.yml index 2ee754116..3c0bf473e 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -29,7 +29,6 @@ internal: - scripts/** - .gitignore - .pre-commit-config.yaml - - pdm_build.py - uv.lock - docs/en/data/sponsors.yml - docs/en/overrides/main.html diff --git a/pdm_build.py b/pdm_build.py deleted file mode 100644 index b1b662bd3..000000000 --- a/pdm_build.py +++ /dev/null @@ -1,40 +0,0 @@ -import os -from typing import Any - -from pdm.backend.hooks import Context - -TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE") - - -def pdm_build_initialize(context: Context) -> None: - metadata = context.config.metadata - # Get main version - version = metadata["version"] - # Get custom config for the current package, from the env var - all_configs_config: dict[str, Any] = context.config.data["tool"]["tiangolo"][ - "_internal-slim-build" - ]["packages"] - - if TIANGOLO_BUILD_PACKAGE not in all_configs_config: - return - - config = all_configs_config[TIANGOLO_BUILD_PACKAGE] - project_config: dict[str, Any] = config["project"] - # Override main [project] configs with custom configs for this package - for key, value in project_config.items(): - metadata[key] = value - # Get custom build config for the current package - build_config: dict[str, Any] = ( - config.get("tool", {}).get("pdm", {}).get("build", {}) - ) - # Override PDM build config with custom build config for this package - for key, value in build_config.items(): - context.config.build_config[key] = value - # Get main dependencies - dependencies: list[str] = metadata.get("dependencies", []) - # Sync versions in dependencies - new_dependencies = [] - for dep in dependencies: - new_dep = f"{dep}>={version}" - new_dependencies.append(new_dep) - metadata["dependencies"] = new_dependencies diff --git a/pyproject.toml b/pyproject.toml index d2a025a72..199dc2a6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -198,32 +198,6 @@ source-includes = [ "docs/en/docs/img/favicon.png", ] -[tool.tiangolo._internal-slim-build.packages.fastapi-slim.project] -name = "fastapi-slim" -readme = "fastapi-slim/README.md" -dependencies = [ - "fastapi", -] -optional-dependencies = {} -scripts = {} - -[tool.tiangolo._internal-slim-build.packages.fastapi-slim.tool.pdm.build] -# excludes needs to explicitly exclude the top level python packages, -# otherwise PDM includes them by default -# A "*" glob pattern can't be used here because in PDM internals, the patterns are put -# in a set (unordered, order varies) and each excluded file is assigned one of the -# glob patterns that matches, as the set is unordered, the matched pattern could be "*" -# independent of the order here. And then the internal code would give it a lower score -# than the one for a default included file. -# By not using "*" and explicitly excluding the top level packages, they get a higher -# score than the default inclusion -excludes = ["fastapi", "tests", "pdm_build.py"] -# source-includes needs to explicitly define some value because PDM will check the -# truthy value of the list, and if empty, will include some defaults, including "tests", -# an empty string doesn't match anything, but makes the list truthy, so that PDM -# doesn't override it during the build. -source-includes = [""] - [tool.mypy] plugins = ["pydantic.mypy"] strict = true From 69ae1d0f287acea3bf02e968f963ee6d225f3c50 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Feb 2026 16:27:01 +0000 Subject: [PATCH 157/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1f348af99..ec5869516 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* ➖ Drop support for `fastapi-slim`, no more versions will be released, use only `"fastapi[standard]"` or `fastapi`. PR [#14957](https://github.com/fastapi/fastapi/pull/14957) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update pyproject.toml, remove unneeded lines. PR [#14956](https://github.com/fastapi/fastapi/pull/14956) by [@tiangolo](https://github.com/tiangolo). ## 0.129.1 From c9455d54002a1a80dec884e96ada5cffccdc32bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 21 Feb 2026 08:45:15 -0800 Subject: [PATCH 158/195] =?UTF-8?q?=F0=9F=91=B7=20Fix=20CI,=20do=20not=20a?= =?UTF-8?q?ttempt=20to=20publish=20`fastapi-slim`=20(#14958)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2232498cb..58f4f6dd8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,11 +8,6 @@ on: jobs: publish: runs-on: ubuntu-latest - strategy: - matrix: - package: - - fastapi - - fastapi-slim permissions: id-token: write contents: read @@ -26,14 +21,9 @@ jobs: uses: actions/setup-python@v6 with: python-version-file: ".python-version" - # Issue ref: https://github.com/actions/setup-python/issues/436 - # cache: "pip" - # cache-dependency-path: pyproject.toml - name: Install uv uses: astral-sh/setup-uv@v7 - name: Build distribution run: uv build - env: - TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} - name: Publish run: uv publish From 468d5173edf8d16342ee67f3cb433428b58dceac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Feb 2026 16:45:38 +0000 Subject: [PATCH 159/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ec5869516..7f42cd490 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* 👷 Fix CI, do not attempt to publish `fastapi-slim`. PR [#14958](https://github.com/fastapi/fastapi/pull/14958) by [@tiangolo](https://github.com/tiangolo). * ➖ Drop support for `fastapi-slim`, no more versions will be released, use only `"fastapi[standard]"` or `fastapi`. PR [#14957](https://github.com/fastapi/fastapi/pull/14957) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update pyproject.toml, remove unneeded lines. PR [#14956](https://github.com/fastapi/fastapi/pull/14956) by [@tiangolo](https://github.com/tiangolo). From 4ab8138554ed830ba543e57e810fe47f22441e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 21 Feb 2026 09:22:54 -0800 Subject: [PATCH 160/195] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20pytest?= =?UTF-8?q?=20(#14959)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- uv.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 199dc2a6f..c51eb8ce9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -170,7 +170,7 @@ tests = [ "mypy >=1.14.1", "pwdlib[argon2] >=0.2.1", "pyjwt >=2.9.0", - "pytest >=7.1.3,<9.0.0", + "pytest >=9.0.0", "pytest-codspeed >=4.2.0", "pyyaml >=5.3.1,<7.0.0", "sqlmodel >=0.0.31", diff --git a/uv.lock b/uv.lock index aa8c558c7..15ca8714f 100644 --- a/uv.lock +++ b/uv.lock @@ -1276,7 +1276,7 @@ dev = [ { name = "pydantic-ai", specifier = ">=0.4.10" }, { name = "pygithub", specifier = ">=2.8.1" }, { name = "pyjwt", specifier = ">=2.9.0" }, - { name = "pytest", specifier = ">=7.1.3,<9.0.0" }, + { name = "pytest", specifier = ">=9.0.0" }, { name = "pytest-codspeed", specifier = ">=4.2.0" }, { name = "python-slugify", specifier = ">=8.0.4" }, { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" }, @@ -1329,7 +1329,7 @@ tests = [ { name = "mypy", specifier = ">=1.14.1" }, { name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" }, { name = "pyjwt", specifier = ">=2.9.0" }, - { name = "pytest", specifier = ">=7.1.3,<9.0.0" }, + { name = "pytest", specifier = ">=9.0.0" }, { name = "pytest-codspeed", specifier = ">=4.2.0" }, { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" }, { name = "ruff", specifier = ">=0.14.14" }, @@ -4330,7 +4330,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -4341,9 +4341,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] From f921de6495a9d770fafc62f81274bdb6c0ee1e31 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Feb 2026 17:23:22 +0000 Subject: [PATCH 161/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7f42cd490..bf29c0613 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* ⬆️ Upgrade pytest. PR [#14959](https://github.com/fastapi/fastapi/pull/14959) by [@tiangolo](https://github.com/tiangolo). * 👷 Fix CI, do not attempt to publish `fastapi-slim`. PR [#14958](https://github.com/fastapi/fastapi/pull/14958) by [@tiangolo](https://github.com/tiangolo). * ➖ Drop support for `fastapi-slim`, no more versions will be released, use only `"fastapi[standard]"` or `fastapi`. PR [#14957](https://github.com/fastapi/fastapi/pull/14957) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update pyproject.toml, remove unneeded lines. PR [#14956](https://github.com/fastapi/fastapi/pull/14956) by [@tiangolo](https://github.com/tiangolo). From 1e78a36b7310003f0ff634627d8a7bc53c6ccdee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 21 Feb 2026 18:24:59 +0100 Subject: [PATCH 162/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?9.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bf29c0613..edcb3db8d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.129.2 + ### Internal * ⬆️ Upgrade pytest. PR [#14959](https://github.com/fastapi/fastapi/pull/14959) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 1591a81cf..e6798ddc6 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.129.1" +__version__ = "0.129.2" from starlette import status as status From 590a5e535587cc07041ba12d308c748433ccb168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 08:07:19 -0800 Subject: [PATCH 163/195] =?UTF-8?q?=E2=9C=A8=20Serialize=20JSON=20response?= =?UTF-8?q?=20with=20Pydantic=20(in=20Rust),=20when=20there's=20a=20Pydant?= =?UTF-8?q?ic=20return=20type=20or=20response=20model=20(#14962)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/custom-response.md | 80 +++++-------------- docs/en/docs/advanced/response-directly.md | 28 +++++-- docs/en/docs/how-to/general.md | 4 + docs/en/docs/tutorial/response-model.md | 1 + docs_src/custom_response/tutorial010_py310.py | 6 +- fastapi/_compat/v2.py | 26 ++++++ fastapi/routing.py | 23 +++++- tests/test_dump_json_fast_path.py | 51 ++++++++++++ .../test_custom_response/test_tutorial001.py | 1 - .../test_custom_response/test_tutorial010.py | 50 ++++++++++++ 10 files changed, 196 insertions(+), 74 deletions(-) create mode 100644 tests/test_dump_json_fast_path.py create mode 100644 tests/test_tutorial/test_custom_response/test_tutorial010.py diff --git a/docs/en/docs/advanced/custom-response.md b/docs/en/docs/advanced/custom-response.md index 8b4b3da33..e88e95865 100644 --- a/docs/en/docs/advanced/custom-response.md +++ b/docs/en/docs/advanced/custom-response.md @@ -1,6 +1,6 @@ # Custom Response - HTML, Stream, File, others { #custom-response-html-stream-file-others } -By default, **FastAPI** will return the responses using `JSONResponse`. +By default, **FastAPI** will return JSON responses. You can override it by returning a `Response` directly as seen in [Return a Response directly](response-directly.md){.internal-link target=_blank}. @@ -10,43 +10,27 @@ But you can also declare the `Response` that you want to be used (e.g. any `Resp The contents that you return from your *path operation function* will be put inside of that `Response`. -And if that `Response` has a JSON media type (`application/json`), like is the case with the `JSONResponse` and `UJSONResponse`, the data you return will be automatically converted (and filtered) with any Pydantic `response_model` that you declared in the *path operation decorator*. - /// note If you use a response class with no media type, FastAPI will expect your response to have no content, so it will not document the response format in its generated OpenAPI docs. /// -## Use `ORJSONResponse` { #use-orjsonresponse } +## JSON Responses { #json-responses } -For example, if you are squeezing performance, you can install and use `orjson` and set the response to be `ORJSONResponse`. +By default FastAPI returns JSON responses. -Import the `Response` class (sub-class) you want to use and declare it in the *path operation decorator*. +If you declare a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} FastAPI will use it to serialize the data to JSON, using Pydantic. -For large responses, returning a `Response` directly is much faster than returning a dictionary. +If you don't declare a response model, FastAPI will use the `jsonable_encoder` explained in [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} and put it in a `JSONResponse`. -This is because by default, FastAPI will inspect every item inside and make sure it is serializable as JSON, using the same [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} explained in the tutorial. This is what allows you to return **arbitrary objects**, for example database models. +If you declare a `response_class` with a JSON media type (`application/json`), like is the case with the `JSONResponse`, the data you return will be automatically converted (and filtered) with any Pydantic `response_model` that you declared in the *path operation decorator*. But the data won't be serialized to JSON bytes with Pydantic, instead it will be converted with the `jsonable_encoder` and then passed to the `JSONResponse` class, which will serialize it to bytes using the standard JSON library in Python. -But if you are certain that the content that you are returning is **serializable with JSON**, you can pass it directly to the response class and avoid the extra overhead that FastAPI would have by passing your return content through the `jsonable_encoder` before passing it to the response class. +### JSON Performance { #json-performance } -{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} +In short, if you want the maximum performance, use a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} and don't declare a `response_class` in the *path operation decorator*. -/// info - -The parameter `response_class` will also be used to define the "media type" of the response. - -In this case, the HTTP header `Content-Type` will be set to `application/json`. - -And it will be documented as such in OpenAPI. - -/// - -/// tip - -The `ORJSONResponse` is only available in FastAPI, not in Starlette. - -/// +{* ../../docs_src/response_model/tutorial001_01_py310.py ln[15:17] hl[16] *} ## HTML Response { #html-response } @@ -154,40 +138,6 @@ Takes some data and returns an `application/json` encoded response. This is the default response used in **FastAPI**, as you read above. -### `ORJSONResponse` { #orjsonresponse } - -A fast alternative JSON response using `orjson`, as you read above. - -/// info - -This requires installing `orjson` for example with `pip install orjson`. - -/// - -### `UJSONResponse` { #ujsonresponse } - -An alternative JSON response using `ujson`. - -/// info - -This requires installing `ujson` for example with `pip install ujson`. - -/// - -/// warning - -`ujson` is less careful than Python's built-in implementation in how it handles some edge-cases. - -/// - -{* ../../docs_src/custom_response/tutorial001_py310.py hl[2,7] *} - -/// tip - -It's possible that `ORJSONResponse` might be a faster alternative. - -/// - ### `RedirectResponse` { #redirectresponse } Returns an HTTP redirect. Uses a 307 status code (Temporary Redirect) by default. @@ -268,7 +218,7 @@ In this case, you can return the file path directly from your *path operation* f You can create your own custom response class, inheriting from `Response` and using it. -For example, let's say that you want to use `orjson`, but with some custom settings not used in the included `ORJSONResponse` class. +For example, let's say that you want to use `orjson` with some settings. Let's say you want it to return indented and formatted JSON, so you want to use the orjson option `orjson.OPT_INDENT_2`. @@ -292,13 +242,21 @@ Now instead of returning: Of course, you will probably find much better ways to take advantage of this than formatting JSON. 😉 +### `orjson` or Response Model { #orjson-or-response-model } + +If what you are looking for is performance, you are probably better off using a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} than an `orjson` response. + +With a response model, FastAPI will use Pydantic to serialize the data to JSON, without using intermediate steps, like converting it with `jsonable_encoder`, which would happen in any other case. + +And under the hood, Pydantic uses the same underlying Rust mechanisms as `orjson` to serialize to JSON, so you will already get the best performance with a response model. + ## Default response class { #default-response-class } When creating a **FastAPI** class instance or an `APIRouter` you can specify which response class to use by default. The parameter that defines this is `default_response_class`. -In the example below, **FastAPI** will use `ORJSONResponse` by default, in all *path operations*, instead of `JSONResponse`. +In the example below, **FastAPI** will use `HTMLResponse` by default, in all *path operations*, instead of JSON. {* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *} diff --git a/docs/en/docs/advanced/response-directly.md b/docs/en/docs/advanced/response-directly.md index 76cc50d03..9d58490eb 100644 --- a/docs/en/docs/advanced/response-directly.md +++ b/docs/en/docs/advanced/response-directly.md @@ -2,19 +2,23 @@ When you create a **FastAPI** *path operation* you can normally return any data from it: a `dict`, a `list`, a Pydantic model, a database model, etc. -By default, **FastAPI** would automatically convert that return value to JSON using the `jsonable_encoder` explained in [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank}. +If you declare a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} FastAPI will use it to serialize the data to JSON, using Pydantic. -Then, behind the scenes, it would put that JSON-compatible data (e.g. a `dict`) inside of a `JSONResponse` that would be used to send the response to the client. +If you don't declare a response model, FastAPI will use the `jsonable_encoder` explained in [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} and put it in a `JSONResponse`. -But you can return a `JSONResponse` directly from your *path operations*. +You could also create a `JSONResponse` directly and return it. -It might be useful, for example, to return custom headers or cookies. +/// tip + +You will normally have much better performance using a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} than returning a `JSONResponse` directly, as that way it serializes the data using Pydantic, in Rust. + +/// ## Return a `Response` { #return-a-response } -In fact, you can return any `Response` or any sub-class of it. +You can return any `Response` or any sub-class of it. -/// tip +/// info `JSONResponse` itself is a sub-class of `Response`. @@ -56,6 +60,18 @@ You could put your XML content in a string, put that in a `Response`, and return {* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *} +## How a Response Model Works { #how-a-response-model-works } + +When you declare a [Response Model](../tutorial/response-model.md){.internal-link target=_blank} in a path operation, **FastAPI** will use it to serialize the data to JSON, using Pydantic. + +{* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} + +As that will happen on the Rust side, the performance will be much better than if it was done with regular Python and the `JSONResponse` class. + +When using a response model FastAPI won't use the `jsonable_encoder` to convert the data (which would be slower) nor the `JSONResponse` class. + +Instead it takes the JSON bytes generated with Pydantic using the response model and returns a `Response` with the right media type for JSON directly (`application/json`). + ## Notes { #notes } When you return a `Response` directly its data is not validated, converted (serialized), or documented automatically. diff --git a/docs/en/docs/how-to/general.md b/docs/en/docs/how-to/general.md index 934719260..4f611dab0 100644 --- a/docs/en/docs/how-to/general.md +++ b/docs/en/docs/how-to/general.md @@ -6,6 +6,10 @@ Here are several pointers to other places in the docs, for general or frequent q To ensure that you don't return more data than you should, read the docs for [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}. +## Optimize Response Performance - Response Model - Return Type { #optimize-response-performance-response-model-return-type } + +To optimize performance when returning JSON data, use a return type or response model, that way Pydantic will handle the serialization to JSON on the Rust side, without going through Python. Read more in the docs for [Tutorial - Response Model - Return Type](../tutorial/response-model.md){.internal-link target=_blank}. + ## Documentation Tags - OpenAPI { #documentation-tags-openapi } To add tags to your *path operations*, and group them in the docs UI, read the docs for [Tutorial - Path Operation Configurations - Tags](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. diff --git a/docs/en/docs/tutorial/response-model.md b/docs/en/docs/tutorial/response-model.md index 51492722a..c8312d92c 100644 --- a/docs/en/docs/tutorial/response-model.md +++ b/docs/en/docs/tutorial/response-model.md @@ -13,6 +13,7 @@ FastAPI will use this return type to: * Add a **JSON Schema** for the response, in the OpenAPI *path operation*. * This will be used by the **automatic docs**. * It will also be used by automatic client code generation tools. +* **Serialize** the returned data to JSON using Pydantic, which is written in **Rust**, so it will be **much faster**. But most importantly: diff --git a/docs_src/custom_response/tutorial010_py310.py b/docs_src/custom_response/tutorial010_py310.py index 57cb06260..d5bc783aa 100644 --- a/docs_src/custom_response/tutorial010_py310.py +++ b/docs_src/custom_response/tutorial010_py310.py @@ -1,9 +1,9 @@ from fastapi import FastAPI -from fastapi.responses import ORJSONResponse +from fastapi.responses import HTMLResponse -app = FastAPI(default_response_class=ORJSONResponse) +app = FastAPI(default_response_class=HTMLResponse) @app.get("/items/") async def read_items(): - return [{"item_id": "Foo"}] + return "

Items

This is a list of items.

" diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 0535c806f..79fba9318 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -199,6 +199,32 @@ class ModelField: exclude_none=exclude_none, ) + def serialize_json( + self, + value: Any, + *, + include: IncEx | None = None, + exclude: IncEx | None = None, + by_alias: bool = True, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + ) -> bytes: + # What calls this code passes a value that already called + # self._type_adapter.validate_python(value) + # This uses Pydantic's dump_json() which serializes directly to JSON + # bytes in one pass (via Rust), avoiding the intermediate Python dict + # step of dump_python(mode="json") + json.dumps(). + return self._type_adapter.dump_json( + value, + include=include, + exclude=exclude, + by_alias=by_alias, + exclude_unset=exclude_unset, + exclude_defaults=exclude_defaults, + exclude_none=exclude_none, + ) + def __hash__(self) -> int: # Each ModelField is unique for our purposes, to allow making a dict from # ModelField to its JSON Schema. diff --git a/fastapi/routing.py b/fastapi/routing.py index ea82ab14a..528c96296 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -271,6 +271,7 @@ async def serialize_response( exclude_none: bool = False, is_coroutine: bool = True, endpoint_ctx: EndpointContext | None = None, + dump_json: bool = False, ) -> Any: if field: if is_coroutine: @@ -286,8 +287,8 @@ async def serialize_response( body=response_content, endpoint_ctx=ctx, ) - - return field.serialize( + serializer = field.serialize_json if dump_json else field.serialize + return serializer( value, include=include, exclude=exclude, @@ -443,6 +444,14 @@ def get_request_handler( response_args["status_code"] = current_status_code if solved_result.response.status_code: response_args["status_code"] = solved_result.response.status_code + # Use the fast path (dump_json) when no custom response + # class was set and a response field with a TypeAdapter + # exists. Serializes directly to JSON bytes via Pydantic's + # Rust core, skipping the intermediate Python dict + + # json.dumps() step. + use_dump_json = response_field is not None and isinstance( + response_class, DefaultPlaceholder + ) content = await serialize_response( field=response_field, response_content=raw_response, @@ -454,8 +463,16 @@ def get_request_handler( exclude_none=response_model_exclude_none, is_coroutine=is_coroutine, endpoint_ctx=endpoint_ctx, + dump_json=use_dump_json, ) - response = actual_response_class(content, **response_args) + if use_dump_json: + response = Response( + content=content, + media_type="application/json", + **response_args, + ) + else: + response = actual_response_class(content, **response_args) if not is_body_allowed_for_status_code(response.status_code): response.body = b"" response.headers.raw.extend(solved_result.response.headers.raw) diff --git a/tests/test_dump_json_fast_path.py b/tests/test_dump_json_fast_path.py new file mode 100644 index 000000000..d41d5aa66 --- /dev/null +++ b/tests/test_dump_json_fast_path.py @@ -0,0 +1,51 @@ +from unittest.mock import patch + +from fastapi import FastAPI +from fastapi.responses import JSONResponse +from fastapi.testclient import TestClient +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + price: float + + +app = FastAPI() + + +@app.get("/default") +def get_default() -> Item: + return Item(name="widget", price=9.99) + + +@app.get("/explicit", response_class=JSONResponse) +def get_explicit() -> Item: + return Item(name="widget", price=9.99) + + +client = TestClient(app) + + +def test_default_response_class_skips_json_dumps(): + """When no response_class is set, the fast path serializes directly to + JSON bytes via Pydantic's dump_json and never calls json.dumps.""" + with patch( + "starlette.responses.json.dumps", wraps=__import__("json").dumps + ) as mock_dumps: + response = client.get("/default") + assert response.status_code == 200 + assert response.json() == {"name": "widget", "price": 9.99} + mock_dumps.assert_not_called() + + +def test_explicit_response_class_uses_json_dumps(): + """When response_class is explicitly set to JSONResponse, the normal path + is used and json.dumps is called via JSONResponse.render().""" + with patch( + "starlette.responses.json.dumps", wraps=__import__("json").dumps + ) as mock_dumps: + response = client.get("/explicit") + assert response.status_code == 200 + assert response.json() == {"name": "widget", "price": 9.99} + mock_dumps.assert_called_once() diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py index a5fe4c8f4..cec5ebe6c 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py @@ -9,7 +9,6 @@ from inline_snapshot import snapshot name="client", params=[ pytest.param("tutorial001_py310"), - pytest.param("tutorial010_py310"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_custom_response/test_tutorial010.py b/tests/test_tutorial/test_custom_response/test_tutorial010.py new file mode 100644 index 000000000..ffb005cb6 --- /dev/null +++ b/tests/test_tutorial/test_custom_response/test_tutorial010.py @@ -0,0 +1,50 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial010_py310"), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.custom_response.{request.param}") + client = TestClient(mod.app) + return client + + +def test_get_custom_response(client: TestClient): + response = client.get("/items/") + assert response.status_code == 200, response.text + assert response.text == snapshot("

Items

This is a list of items.

") + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": {"schema": {"type": "string"}} + }, + } + }, + "summary": "Read Items", + "operationId": "read_items_items__get", + } + } + }, + } + ) From bc06e4296d588f39f38c7ac2fc96d3913c565962 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Feb 2026 16:07:56 +0000 Subject: [PATCH 164/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index edcb3db8d..ba5ba420b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Features + +* ✨ Serialize JSON response with Pydantic (in Rust), when there's a Pydantic return type or response model. PR [#14962](https://github.com/fastapi/fastapi/pull/14962) by [@tiangolo](https://github.com/tiangolo). + ## 0.129.2 ### Internal From eb544e704c02fd9bad34d23127cbb45255c00706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 17:14:53 +0100 Subject: [PATCH 165/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.13?= =?UTF-8?q?0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ba5ba420b..a5c5241e1 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.130.0 + ### Features * ✨ Serialize JSON response with Pydantic (in Rust), when there's a Pydantic return type or response model. PR [#14962](https://github.com/fastapi/fastapi/pull/14962) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index e6798ddc6..ffa56faaf 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.129.2" +__version__ = "0.130.0" from starlette import status as status From 2e62fb151338c39c1f122d13fe6ad255b8ce1ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 17:18:26 +0100 Subject: [PATCH 166/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a5c5241e1..4b7fba336 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -12,6 +12,8 @@ hide: ### Features * ✨ Serialize JSON response with Pydantic (in Rust), when there's a Pydantic return type or response model. PR [#14962](https://github.com/fastapi/fastapi/pull/14962) by [@tiangolo](https://github.com/tiangolo). + * This results in 2x (or more) performance increase for JSON responses. + * New docs: [Custom Response - JSON Performance](https://fastapi.tiangolo.com/advanced/custom-response/#json-performance). ## 0.129.2 From 48e983573232eea970fb4e0261818d4ab9a481b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 08:34:59 -0800 Subject: [PATCH 167/195] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20Deprecate=20`OR?= =?UTF-8?q?JSONResponse`=20and=20`UJSONResponse`=20(#14964)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/reference/responses.md | 8 +- fastapi/responses.py | 52 +++++++++++-- pyproject.toml | 8 +- tests/test_deprecated_responses.py | 73 +++++++++++++++++++ tests/test_orjson_response_class.py | 13 +++- .../test_custom_response/test_tutorial001.py | 2 + .../test_custom_response/test_tutorial001b.py | 10 ++- uv.lock | 20 ++++- 8 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 tests/test_deprecated_responses.py diff --git a/docs/en/docs/reference/responses.md b/docs/en/docs/reference/responses.md index bd5786129..2df53e970 100644 --- a/docs/en/docs/reference/responses.md +++ b/docs/en/docs/reference/responses.md @@ -22,7 +22,13 @@ from fastapi.responses import ( ## FastAPI Responses -There are a couple of custom FastAPI response classes, you can use them to optimize JSON performance. +There were a couple of custom FastAPI response classes that were intended to optimize JSON performance. + +However, they are now deprecated as you will now get better performance by using a [Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/). + +That way, Pydantic will serialize the data into JSON bytes on the Rust side, which will achieve better performance than these custom JSON responses. + +Read more about it in [Custom Response - HTML, Stream, File, others - `orjson` or Response Model](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model). ::: fastapi.responses.UJSONResponse options: diff --git a/fastapi/responses.py b/fastapi/responses.py index 6c8db6f33..5b1154c04 100644 --- a/fastapi/responses.py +++ b/fastapi/responses.py @@ -1,5 +1,6 @@ from typing import Any +from fastapi.exceptions import FastAPIDeprecationWarning from starlette.responses import FileResponse as FileResponse # noqa from starlette.responses import HTMLResponse as HTMLResponse # noqa from starlette.responses import JSONResponse as JSONResponse # noqa @@ -7,6 +8,7 @@ from starlette.responses import PlainTextResponse as PlainTextResponse # noqa from starlette.responses import RedirectResponse as RedirectResponse # noqa from starlette.responses import Response as Response # noqa from starlette.responses import StreamingResponse as StreamingResponse # noqa +from typing_extensions import deprecated try: import ujson @@ -20,12 +22,29 @@ except ImportError: # pragma: nocover orjson = None # type: ignore +@deprecated( + "UJSONResponse is deprecated, FastAPI now serializes data directly to JSON " + "bytes via Pydantic when a return type or response model is set, which is " + "faster and doesn't need a custom response class. Read more in the FastAPI " + "docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model " + "and https://fastapi.tiangolo.com/tutorial/response-model/", + category=FastAPIDeprecationWarning, + stacklevel=2, +) class UJSONResponse(JSONResponse): - """ - JSON response using the high-performance ujson library to serialize data to JSON. + """JSON response using the ujson library to serialize data to JSON. - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/). + **Deprecated**: `UJSONResponse` is deprecated. FastAPI now serializes data + directly to JSON bytes via Pydantic when a return type or response model is + set, which is faster and doesn't need a custom response class. + + Read more in the + [FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model) + and the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + + **Note**: `ujson` is not included with FastAPI and must be installed + separately, e.g. `pip install ujson`. """ def render(self, content: Any) -> bytes: @@ -33,12 +52,29 @@ class UJSONResponse(JSONResponse): return ujson.dumps(content, ensure_ascii=False).encode("utf-8") +@deprecated( + "ORJSONResponse is deprecated, FastAPI now serializes data directly to JSON " + "bytes via Pydantic when a return type or response model is set, which is " + "faster and doesn't need a custom response class. Read more in the FastAPI " + "docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model " + "and https://fastapi.tiangolo.com/tutorial/response-model/", + category=FastAPIDeprecationWarning, + stacklevel=2, +) class ORJSONResponse(JSONResponse): - """ - JSON response using the high-performance orjson library to serialize data to JSON. + """JSON response using the orjson library to serialize data to JSON. - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/). + **Deprecated**: `ORJSONResponse` is deprecated. FastAPI now serializes data + directly to JSON bytes via Pydantic when a return type or response model is + set, which is faster and doesn't need a custom response class. + + Read more in the + [FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model) + and the + [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). + + **Note**: `orjson` is not included with FastAPI and must be installed + separately, e.g. `pip install orjson`. """ def render(self, content: Any) -> bytes: diff --git a/pyproject.toml b/pyproject.toml index c51eb8ce9..79dfc1fd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,10 +105,6 @@ all = [ "itsdangerous >=1.1.0", # For Starlette's schema generation, would not be used with FastAPI "pyyaml >=5.3.1", - # For UJSONResponse - "ujson >=5.8.0", - # For ORJSONResponse - "orjson >=3.9.3", # To validate email fields "email-validator >=2.0.0", # Uvicorn with uvloop @@ -151,6 +147,10 @@ docs = [ docs-tests = [ "httpx >=0.23.0,<1.0.0", "ruff >=0.14.14", + # For UJSONResponse + "ujson >=5.8.0", + # For ORJSONResponse + "orjson >=3.9.3", ] github-actions = [ "httpx >=0.27.0,<1.0.0", diff --git a/tests/test_deprecated_responses.py b/tests/test_deprecated_responses.py new file mode 100644 index 000000000..eff579271 --- /dev/null +++ b/tests/test_deprecated_responses.py @@ -0,0 +1,73 @@ +import warnings + +import pytest +from fastapi import FastAPI +from fastapi.exceptions import FastAPIDeprecationWarning +from fastapi.responses import ORJSONResponse, UJSONResponse +from fastapi.testclient import TestClient +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + price: float + + +# ORJSON + + +def _make_orjson_app() -> FastAPI: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + app = FastAPI(default_response_class=ORJSONResponse) + + @app.get("/items") + def get_items() -> Item: + return Item(name="widget", price=9.99) + + return app + + +def test_orjson_response_returns_correct_data(): + app = _make_orjson_app() + client = TestClient(app) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + response = client.get("/items") + assert response.status_code == 200 + assert response.json() == {"name": "widget", "price": 9.99} + + +def test_orjson_response_emits_deprecation_warning(): + with pytest.warns(FastAPIDeprecationWarning, match="ORJSONResponse is deprecated"): + ORJSONResponse(content={"hello": "world"}) + + +# UJSON + + +def _make_ujson_app() -> FastAPI: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + app = FastAPI(default_response_class=UJSONResponse) + + @app.get("/items") + def get_items() -> Item: + return Item(name="widget", price=9.99) + + return app + + +def test_ujson_response_returns_correct_data(): + app = _make_ujson_app() + client = TestClient(app) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + response = client.get("/items") + assert response.status_code == 200 + assert response.json() == {"name": "widget", "price": 9.99} + + +def test_ujson_response_emits_deprecation_warning(): + with pytest.warns(FastAPIDeprecationWarning, match="UJSONResponse is deprecated"): + UJSONResponse(content={"hello": "world"}) diff --git a/tests/test_orjson_response_class.py b/tests/test_orjson_response_class.py index 6fe62daf9..63ea054d1 100644 --- a/tests/test_orjson_response_class.py +++ b/tests/test_orjson_response_class.py @@ -1,9 +1,14 @@ +import warnings + from fastapi import FastAPI +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.responses import ORJSONResponse from fastapi.testclient import TestClient from sqlalchemy.sql.elements import quoted_name -app = FastAPI(default_response_class=ORJSONResponse) +with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + app = FastAPI(default_response_class=ORJSONResponse) @app.get("/orjson_non_str_keys") @@ -16,6 +21,8 @@ client = TestClient(app) def test_orjson_non_str_keys(): - with client: - response = client.get("/orjson_non_str_keys") + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + with client: + response = client.get("/orjson_non_str_keys") assert response.json() == {"msg": "Hello World", "1": 1} diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py index cec5ebe6c..a691dd3a8 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py @@ -17,12 +17,14 @@ def get_client(request: pytest.FixtureRequest): return client +@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning") def test_get_custom_response(client: TestClient): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"item_id": "Foo"}] +@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning") def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001b.py b/tests/test_tutorial/test_custom_response/test_tutorial001b.py index 32437db86..11ce813b7 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001b.py @@ -1,17 +1,25 @@ +import warnings + +import pytest +from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.testclient import TestClient from inline_snapshot import snapshot -from docs_src.custom_response.tutorial001b_py310 import app +with warnings.catch_warnings(): + warnings.simplefilter("ignore", FastAPIDeprecationWarning) + from docs_src.custom_response.tutorial001b_py310 import app client = TestClient(app) +@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning") def test_get_custom_response(): response = client.get("/items/") assert response.status_code == 200, response.text assert response.json() == [{"item_id": "Foo"}] +@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning") def test_openapi_schema(): response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/uv.lock b/uv.lock index 15ca8714f..0d16c930b 100644 --- a/uv.lock +++ b/uv.lock @@ -1083,12 +1083,10 @@ all = [ { name = "httpx" }, { name = "itsdangerous" }, { name = "jinja2" }, - { name = "orjson" }, { name = "pydantic-extra-types" }, { name = "pydantic-settings" }, { name = "python-multipart" }, { name = "pyyaml" }, - { name = "ujson" }, { name = "uvicorn", extra = ["standard"] }, ] standard = [ @@ -1134,6 +1132,7 @@ dev = [ { name = "mkdocs-redirects" }, { name = "mkdocstrings", extra = ["python"] }, { name = "mypy" }, + { name = "orjson" }, { name = "pillow" }, { name = "playwright" }, { name = "prek" }, @@ -1151,6 +1150,7 @@ dev = [ { name = "typer" }, { name = "types-orjson" }, { name = "types-ujson" }, + { name = "ujson" }, ] docs = [ { name = "black" }, @@ -1165,15 +1165,19 @@ docs = [ { name = "mkdocs-material" }, { name = "mkdocs-redirects" }, { name = "mkdocstrings", extra = ["python"] }, + { name = "orjson" }, { name = "pillow" }, { name = "python-slugify" }, { name = "pyyaml" }, { name = "ruff" }, { name = "typer" }, + { name = "ujson" }, ] docs-tests = [ { name = "httpx" }, + { name = "orjson" }, { name = "ruff" }, + { name = "ujson" }, ] github-actions = [ { name = "httpx" }, @@ -1192,6 +1196,7 @@ tests = [ { name = "httpx" }, { name = "inline-snapshot" }, { name = "mypy" }, + { name = "orjson" }, { name = "pwdlib", extra = ["argon2"] }, { name = "pyjwt" }, { name = "pytest" }, @@ -1202,6 +1207,7 @@ tests = [ { name = "strawberry-graphql" }, { name = "types-orjson" }, { name = "types-ujson" }, + { name = "ujson" }, ] translations = [ { name = "gitpython" }, @@ -1225,7 +1231,6 @@ requires-dist = [ { name = "jinja2", marker = "extra == 'all'", specifier = ">=3.1.5" }, { name = "jinja2", marker = "extra == 'standard'", specifier = ">=3.1.5" }, { name = "jinja2", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=3.1.5" }, - { name = "orjson", marker = "extra == 'all'", specifier = ">=3.9.3" }, { name = "pydantic", specifier = ">=2.7.0" }, { name = "pydantic-extra-types", marker = "extra == 'all'", specifier = ">=2.0.0" }, { name = "pydantic-extra-types", marker = "extra == 'standard'", specifier = ">=2.0.0" }, @@ -1240,7 +1245,6 @@ requires-dist = [ { name = "starlette", specifier = ">=0.40.0,<1.0.0" }, { name = "typing-extensions", specifier = ">=4.8.0" }, { name = "typing-inspection", specifier = ">=0.4.2" }, - { name = "ujson", marker = "extra == 'all'", specifier = ">=5.8.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.12.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.12.0" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.12.0" }, @@ -1269,6 +1273,7 @@ dev = [ { name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=0.30.1" }, { name = "mypy", specifier = ">=1.14.1" }, + { name = "orjson", specifier = ">=3.9.3" }, { name = "pillow", specifier = ">=11.3.0" }, { name = "playwright", specifier = ">=1.57.0" }, { name = "prek", specifier = ">=0.2.22" }, @@ -1286,6 +1291,7 @@ dev = [ { name = "typer", specifier = ">=0.21.1" }, { name = "types-orjson", specifier = ">=3.6.2" }, { name = "types-ujson", specifier = ">=5.10.0.20240515" }, + { name = "ujson", specifier = ">=5.8.0" }, ] docs = [ { name = "black", specifier = ">=25.1.0" }, @@ -1300,15 +1306,19 @@ docs = [ { 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 = "orjson", specifier = ">=3.9.3" }, { 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.14" }, { name = "typer", specifier = ">=0.21.1" }, + { name = "ujson", specifier = ">=5.8.0" }, ] docs-tests = [ { name = "httpx", specifier = ">=0.23.0,<1.0.0" }, + { name = "orjson", specifier = ">=3.9.3" }, { name = "ruff", specifier = ">=0.14.14" }, + { name = "ujson", specifier = ">=5.8.0" }, ] github-actions = [ { name = "httpx", specifier = ">=0.27.0,<1.0.0" }, @@ -1327,6 +1337,7 @@ tests = [ { name = "httpx", specifier = ">=0.23.0,<1.0.0" }, { name = "inline-snapshot", specifier = ">=0.21.1" }, { name = "mypy", specifier = ">=1.14.1" }, + { name = "orjson", specifier = ">=3.9.3" }, { name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" }, { name = "pyjwt", specifier = ">=2.9.0" }, { name = "pytest", specifier = ">=9.0.0" }, @@ -1337,6 +1348,7 @@ tests = [ { 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" }, + { name = "ujson", specifier = ">=5.8.0" }, ] translations = [ { name = "gitpython", specifier = ">=3.1.46" }, From 70e8558352ef5f60dbd95c95725a427e06e86a24 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Feb 2026 16:35:25 +0000 Subject: [PATCH 168/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4b7fba336..b9848e460 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Breaking Changes + +* 🗑️ Deprecate `ORJSONResponse` and `UJSONResponse`. PR [#14964](https://github.com/fastapi/fastapi/pull/14964) by [@tiangolo](https://github.com/tiangolo). + ## 0.130.0 ### Features From b423b73c355bc401cb49f43cb5ca14515ac088cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 17:36:21 +0100 Subject: [PATCH 169/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.13?= =?UTF-8?q?1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b9848e460..c42133a20 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.131.0 + ### Breaking Changes * 🗑️ Deprecate `ORJSONResponse` and `UJSONResponse`. PR [#14964](https://github.com/fastapi/fastapi/pull/14964) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index ffa56faaf..944fdd58f 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.130.0" +__version__ = "0.131.0" from starlette import status as status From 1cea8f659c5ca6ffef1a62b3ac78b9d7c63c3c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 10:13:49 -0800 Subject: [PATCH 170/195] =?UTF-8?q?=F0=9F=91=B7=20Do=20not=20include=20ben?= =?UTF-8?q?chmark=20tests=20in=20coverage=20to=20speed=20up=20coverage=20p?= =?UTF-8?q?rocessing=20(#14965)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 49 ++++++++++++++++++++++++++++---------- pyproject.toml | 1 + 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d61b4add9..0d3515efb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,10 +68,8 @@ jobs: python-version: "3.13" coverage: coverage uv-resolution: highest - # Ubuntu with 3.13 needs coverage for CodSpeed benchmarks - os: ubuntu-latest python-version: "3.13" - coverage: coverage uv-resolution: highest codspeed: codspeed - os: ubuntu-latest @@ -109,20 +107,10 @@ jobs: run: uv pip install "git+https://github.com/Kludex/starlette@main" - run: mkdir coverage - name: Test - if: matrix.codspeed != 'codspeed' run: uv run --no-sync bash scripts/test.sh env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} - - name: CodSpeed benchmarks - if: matrix.codspeed == 'codspeed' - uses: CodSpeedHQ/action@v4 - env: - COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} - CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} - with: - mode: simulation - run: uv run --no-sync coverage run -m pytest tests/ --codspeed # Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow - name: Store coverage files if: matrix.coverage == 'coverage' @@ -132,6 +120,42 @@ jobs: path: coverage include-hidden-files: true + benchmark: + needs: + - changes + if: needs.changes.outputs.src == 'true' || github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + env: + UV_PYTHON: "3.13" + UV_RESOLUTION: highest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: echo "$GITHUB_CONTEXT" + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + cache-dependency-glob: | + pyproject.toml + uv.lock + - name: Install Dependencies + run: uv sync --no-dev --group tests --extra all + - name: CodSpeed benchmarks + uses: CodSpeedHQ/action@v4 + env: + COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py3.13 + CONTEXT: ${{ runner.os }}-py3.13 + with: + mode: simulation + run: uv run --no-sync coverage run -m pytest tests/ --codspeed + coverage-combine: needs: - test @@ -176,6 +200,7 @@ jobs: if: always() needs: - coverage-combine + - benchmark runs-on: ubuntu-latest steps: - name: Dump GitHub context diff --git a/pyproject.toml b/pyproject.toml index 79dfc1fd3..76b53726a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -242,6 +242,7 @@ relative_files = true context = '${CONTEXT}' dynamic_context = "test_function" omit = [ + "tests/benchmarks/*", "docs_src/response_model/tutorial003_04_py39.py", "docs_src/response_model/tutorial003_04_py310.py", "docs_src/dependencies/tutorial013_an_py310.py", # temporary code example? From c5559a66dd76c229997c673dd48bbf4f46a2cf68 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Feb 2026 18:14:11 +0000 Subject: [PATCH 171/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c42133a20..7ac314280 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* 👷 Do not include benchmark tests in coverage to speed up coverage processing. PR [#14965](https://github.com/fastapi/fastapi/pull/14965) by [@tiangolo](https://github.com/tiangolo). + ## 0.131.0 ### Breaking Changes From 4da264f0f32a1b0eb1c260997d0ab3453f4fdb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 22 Feb 2026 10:21:38 -0800 Subject: [PATCH 172/195] =?UTF-8?q?=F0=9F=91=B7=20Do=20not=20run=20codspee?= =?UTF-8?q?d=20with=20coverage=20as=20it's=20not=20tracked=20(#14966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d3515efb..86e6bfc14 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -149,12 +149,9 @@ jobs: run: uv sync --no-dev --group tests --extra all - name: CodSpeed benchmarks uses: CodSpeedHQ/action@v4 - env: - COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py3.13 - CONTEXT: ${{ runner.os }}-py3.13 with: mode: simulation - run: uv run --no-sync coverage run -m pytest tests/ --codspeed + run: uv run --no-sync pytest tests/benchmarks --codspeed coverage-combine: needs: From 282612437835fd1abbc37cf77c6f4e3bc76de557 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Feb 2026 18:22:03 +0000 Subject: [PATCH 173/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7ac314280..f0fb43062 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* 👷 Do not run codspeed with coverage as it's not tracked. PR [#14966](https://github.com/fastapi/fastapi/pull/14966) by [@tiangolo](https://github.com/tiangolo). * 👷 Do not include benchmark tests in coverage to speed up coverage processing. PR [#14965](https://github.com/fastapi/fastapi/pull/14965) by [@tiangolo](https://github.com/tiangolo). ## 0.131.0 From a3c8c37272e456b331464b33603788ce1d244c94 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:44:47 +0100 Subject: [PATCH 174/195] =?UTF-8?q?=F0=9F=94=A8=20Fix=20`FastAPI=20People`?= =?UTF-8?q?=20workflow=20(#14951)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/people.py | 83 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 10 deletions(-) diff --git a/scripts/people.py b/scripts/people.py index f3254ab60..2e84fcc45 100644 --- a/scripts/people.py +++ b/scripts/people.py @@ -5,6 +5,7 @@ import time from collections import Counter from collections.abc import Container from datetime import datetime, timedelta, timezone +from math import ceil from pathlib import Path from typing import Any @@ -15,12 +16,63 @@ from pydantic import BaseModel, SecretStr from pydantic_settings import BaseSettings github_graphql_url = "https://api.github.com/graphql" -questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0" +questions_category_id = "DIC_kwDOCZduT84B6E2a" + + +POINTS_PER_MINUTE_LIMIT = 84 # 5000 points per hour + + +class RateLimiter: + def __init__(self) -> None: + self.last_query_cost: int = 1 + self.remaining_points: int = 5000 + self.reset_at: datetime = datetime.fromtimestamp(0, timezone.utc) + self.last_request_start_time: datetime = datetime.fromtimestamp(0, timezone.utc) + self.speed_multiplier: float = 1.0 + + def __enter__(self) -> "RateLimiter": + now = datetime.now(tz=timezone.utc) + + # Handle primary rate limits + primary_limit_wait_time = 0.0 + if self.remaining_points <= self.last_query_cost: + primary_limit_wait_time = (self.reset_at - now).total_seconds() + 2 + logging.warning( + f"Approaching GitHub API rate limit, remaining points: {self.remaining_points}, " + f"reset time in {primary_limit_wait_time} seconds" + ) + + # Handle secondary rate limits + secondary_limit_wait_time = 0.0 + points_per_minute = POINTS_PER_MINUTE_LIMIT * self.speed_multiplier + interval = 60 / (points_per_minute / self.last_query_cost) + time_since_last_request = (now - self.last_request_start_time).total_seconds() + if time_since_last_request < interval: + secondary_limit_wait_time = interval - time_since_last_request + + final_wait_time = ceil(max(primary_limit_wait_time, secondary_limit_wait_time)) + logging.info(f"Sleeping for {final_wait_time} seconds to respect rate limit") + time.sleep(max(final_wait_time, 1)) + + self.last_request_start_time = datetime.now(tz=timezone.utc) + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + pass + + def update_request_info(self, cost: int, remaining: int, reset_at: str) -> None: + self.last_query_cost = cost + self.remaining_points = remaining + self.reset_at = datetime.fromisoformat(reset_at.replace("Z", "+00:00")) + + +rate_limiter = RateLimiter() + discussions_query = """ query Q($after: String, $category_id: ID) { repository(name: "fastapi", owner: "fastapi") { - discussions(first: 100, after: $after, categoryId: $category_id) { + discussions(first: 30, after: $after, categoryId: $category_id) { edges { cursor node { @@ -58,6 +110,11 @@ query Q($after: String, $category_id: ID) { } } } + rateLimit { + cost + remaining + resetAt + } } """ @@ -120,7 +177,7 @@ class Settings(BaseSettings): github_token: SecretStr github_repository: str httpx_timeout: int = 30 - sleep_interval: int = 5 + speed_multiplier: float = 1.0 def get_graphql_response( @@ -158,11 +215,18 @@ def get_graphql_question_discussion_edges( settings: Settings, after: str | None = None, ) -> list[DiscussionsEdge]: - data = get_graphql_response( - settings=settings, - query=discussions_query, - after=after, - category_id=questions_category_id, + with rate_limiter: + data = get_graphql_response( + settings=settings, + query=discussions_query, + after=after, + category_id=questions_category_id, + ) + + rate_limiter.update_request_info( + cost=data["data"]["rateLimit"]["cost"], + remaining=data["data"]["rateLimit"]["remaining"], + reset_at=data["data"]["rateLimit"]["resetAt"], ) graphql_response = DiscussionsResponse.model_validate(data) return graphql_response.data.repository.discussions.edges @@ -185,8 +249,6 @@ def get_discussion_nodes(settings: Settings) -> list[DiscussionsNode]: for discussion_edge in discussion_edges: discussion_nodes.append(discussion_edge.node) last_edge = discussion_edges[-1] - # Handle GitHub secondary rate limits, requests per minute - time.sleep(settings.sleep_interval) discussion_edges = get_graphql_question_discussion_edges( settings=settings, after=last_edge.cursor ) @@ -318,6 +380,7 @@ def main() -> None: logging.basicConfig(level=logging.INFO) settings = Settings() logging.info(f"Using config: {settings.model_dump_json()}") + rate_limiter.speed_multiplier = settings.speed_multiplier g = Github(settings.github_token.get_secret_value()) repo = g.get_repo(settings.github_repository) From fef2ce70d928d329af2fa14f9eefe16b716c5219 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Feb 2026 11:45:11 +0000 Subject: [PATCH 175/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f0fb43062..fc3400034 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* 🔨 Fix `FastAPI People` workflow. PR [#14951](https://github.com/fastapi/fastapi/pull/14951) by [@YuriiMotov](https://github.com/YuriiMotov). * 👷 Do not run codspeed with coverage as it's not tracked. PR [#14966](https://github.com/fastapi/fastapi/pull/14966) by [@tiangolo](https://github.com/tiangolo). * 👷 Do not include benchmark tests in coverage to speed up coverage processing. PR [#14965](https://github.com/fastapi/fastapi/pull/14965) by [@tiangolo](https://github.com/tiangolo). From 5161f7b42b131d945e68606cc4ffcd3b49d407a3 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Mon, 23 Feb 2026 16:04:24 +0100 Subject: [PATCH 176/195] =?UTF-8?q?=E2=AC=86=20Update=20all=20dependencies?= =?UTF-8?q?=20to=20use=20`griffelib`=20instead=20of=20`griffe`=20(#14973)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update to griffelib * also update pydantic-ai * move griffelib to get better GH diff * restore accidental edit --- uv.lock | 54 +++++++++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/uv.lock b/uv.lock index 0d16c930b..0b14fde53 100644 --- a/uv.lock +++ b/uv.lock @@ -1923,40 +1923,36 @@ wheels = [ ] [[package]] -name = "griffe" -version = "1.15.0" +name = "griffelib" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" }, ] [[package]] name = "griffe-typingdoc" -version = "0.3.0" +version = "0.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffe" }, + { name = "griffelib" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/77/d5e5fa0a8391bc2890ae45255847197299739833108dd76ee3c9b2ff0bba/griffe_typingdoc-0.3.0.tar.gz", hash = "sha256:59d9ef98d02caa7aed88d8df1119c9e48c02ed049ea50ce4018ace9331d20f8b", size = 33169, upload-time = "2025-10-23T12:01:39.037Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/26/28182e0c8055842bf3da774dee1d5b789c0f236c078dcbdca1937b5214dc/griffe_typingdoc-0.3.1.tar.gz", hash = "sha256:2ff4703115cb7f8a65b9fdcdd1f3c3a15f813b6554621b52eaad094c4782ce96", size = 31218, upload-time = "2026-02-21T09:38:54.409Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/af/aa32c13f753e2625ec895b1f56eee3c9380a2088a88a2c028955e223856e/griffe_typingdoc-0.3.0-py3-none-any.whl", hash = "sha256:4f6483fff7733a679d1dce142fb029f314125f3caaf0d620eb82e7390c8564bb", size = 9923, upload-time = "2025-10-23T12:01:37.601Z" }, + { url = "https://files.pythonhosted.org/packages/b6/c4/cf543fbde49e1ae44830ef0840a4d6ee9f4e4f338138a7766d4e37cf6440/griffe_typingdoc-0.3.1-py3-none-any.whl", hash = "sha256:ecbd457ef6883126b8b6023abf12e08c58e1c152238a2f0e2afdd67a64b07021", size = 10092, upload-time = "2026-02-20T14:53:47.84Z" }, ] [[package]] name = "griffe-warnings-deprecated" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffe" }, + { name = "griffelib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/0e/f034e1714eb2c694d6196c75f77a02f9c69d19f9961c4804a016397bf3e5/griffe_warnings_deprecated-1.1.0.tar.gz", hash = "sha256:7bf21de327d59c66c7ce08d0166aa4292ce0577ff113de5878f428d102b6f7c5", size = 33260, upload-time = "2024-12-10T21:02:18.395Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/9e/fc86f1e9270f143a395a601de81aa42a871722c34d4b3c7763658dc2e04d/griffe_warnings_deprecated-1.1.1.tar.gz", hash = "sha256:9261369bf2acb8b5d24a0dc7895cce788208513d4349031d4ea315b979b2e99f", size = 26262, upload-time = "2026-02-21T09:38:55.858Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/4c/b7241f03ad1f22ec2eed33b0f90c4f8c949e3395c4b7488670b07225a20b/griffe_warnings_deprecated-1.1.0-py3-none-any.whl", hash = "sha256:e7b0e8bfd6e5add3945d4d9805b2a41c72409e456733965be276d55f01e8a7a2", size = 5854, upload-time = "2024-12-10T21:02:16.96Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3c/c2a9eee79bf2c8002d2fa370534bee93fdca39e8b1fc82e83d552d5d2c07/griffe_warnings_deprecated-1.1.1-py3-none-any.whl", hash = "sha256:4b7d765e82ca9139ed44ffe7bdebed0d3a46ce014ad5a35a2c22e9a16288737a", size = 6565, upload-time = "2026-02-20T15:35:23.577Z" }, ] [[package]] @@ -3003,17 +2999,17 @@ python = [ [[package]] name = "mkdocstrings-python" -version = "2.0.1" +version = "2.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffe" }, + { name = "griffelib" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" }, + { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, ] [[package]] @@ -3937,33 +3933,33 @@ email = [ [[package]] name = "pydantic-ai" -version = "1.56.0" +version = "1.62.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "fastmcp", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "temporal", "ui", "vertexai", "xai"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/1a/800a1e02b259152a49d4c11d9103784a7482c7e9b067eeea23e949d3d80f/pydantic_ai-1.56.0.tar.gz", hash = "sha256:643ff71612df52315b3b4c4b41543657f603f567223eb33245dc8098f005bdc4", size = 11795, upload-time = "2026-02-06T01:13:21.122Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/97/e3158fa976a29e9580ba1c59601590424bbb81179c359fd29de0dc23aa09/pydantic_ai-1.62.0.tar.gz", hash = "sha256:d6ae517e365ea3ea162ca8ae643f319e105b71b0b6218b83dcad1d1eb2e38c9b", size = 12130, upload-time = "2026-02-19T05:07:07.853Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/35/f4a7fd2b9962ddb9b021f76f293e74fda71da190bb74b57ed5b343c93022/pydantic_ai-1.56.0-py3-none-any.whl", hash = "sha256:b6b3ac74bdc004693834750da4420ea2cde0d3cbc3f134c0b7544f98f1c00859", size = 7222, upload-time = "2026-02-06T01:13:11.755Z" }, + { url = "https://files.pythonhosted.org/packages/bc/7a/053aebfab576603e95fcfce1139de4a87e12bd5a2ef1ba00007a931c3ff0/pydantic_ai-1.62.0-py3-none-any.whl", hash = "sha256:1eb88f745ae045e63da41ad68966e8876c964d0f023fbf5d6a3f5d243370bd04", size = 7227, upload-time = "2026-02-19T05:06:58.341Z" }, ] [[package]] name = "pydantic-ai-slim" -version = "1.56.0" +version = "1.62.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "genai-prices" }, - { name = "griffe" }, + { name = "griffelib" }, { name = "httpx" }, { name = "opentelemetry-api" }, { name = "pydantic" }, { name = "pydantic-graph" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/5c/3a577825b9c1da8f287be7f2ee6fe9aab48bc8a80e65c8518052c589f51c/pydantic_ai_slim-1.56.0.tar.gz", hash = "sha256:9f9f9c56b1c735837880a515ae5661b465b40207b25f3a3434178098b2137f05", size = 415265, upload-time = "2026-02-06T01:13:23.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/8d/6350a49f2e4b636efbcfc233221420ab576e4ba4edba38254cb84ae4a1e6/pydantic_ai_slim-1.62.0.tar.gz", hash = "sha256:00d84f659107bbbd88823a3d3dbe7348385935a9870b9d7d4ba799256f6b6983", size = 422452, upload-time = "2026-02-19T05:07:10.292Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/4b/34682036528eeb9aaf093c2073540ddf399ab37b99d282a69ca41356f1aa/pydantic_ai_slim-1.56.0-py3-none-any.whl", hash = "sha256:d657e4113485020500b23b7390b0066e2a0277edc7577eaad2290735ca5dd7d5", size = 542270, upload-time = "2026-02-06T01:13:14.918Z" }, + { url = "https://files.pythonhosted.org/packages/3d/67/21e9b3b0944568662e3790c936226bd48a9f27c6b5f27b5916f5857bc4d8/pydantic_ai_slim-1.62.0-py3-none-any.whl", hash = "sha256:5210073fadd46f65859a67da67845093c487f025fa430ed027151f22ec684ab2", size = 549296, upload-time = "2026-02-19T05:07:01.624Z" }, ] [package.optional-dependencies] @@ -4181,7 +4177,7 @@ wheels = [ [[package]] name = "pydantic-graph" -version = "1.56.0" +version = "1.62.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -4189,9 +4185,9 @@ dependencies = [ { name = "pydantic" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/03/f92881cdb12d6f43e60e9bfd602e41c95408f06e2324d3729f7a194e2bcd/pydantic_graph-1.56.0.tar.gz", hash = "sha256:5e22972dbb43dbc379ab9944252ff864019abf3c7d465dcdf572fc8aec9a44a1", size = 58460, upload-time = "2026-02-06T01:13:26.708Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/b6/0b084c847ecd99624f4fbc5c8ecd3f67a2388a282a32612b2a68c3b3595f/pydantic_graph-1.62.0.tar.gz", hash = "sha256:efe56bee3a8ca35b11a3be6a5f7352419fe182ef1e1323a3267ee12dec95f3c7", size = 58529, upload-time = "2026-02-19T05:07:12.947Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/07/8c823eb4d196137c123d4d67434e185901d3cbaea3b0c2b7667da84e72c1/pydantic_graph-1.56.0-py3-none-any.whl", hash = "sha256:ec3f0a1d6fcedd4eb9c59fef45079c2ee4d4185878d70dae26440a9c974c6bb3", size = 72346, upload-time = "2026-02-06T01:13:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/f0/12/1a9cbcd59fd070ba72b0fe544caa6ca97758518643523ec2bf1162084e0d/pydantic_graph-1.62.0-py3-none-any.whl", hash = "sha256:abe0e7b356b4d3202b069ec020d8dd1f647f55e9a0e85cd272dab48250bde87d", size = 72350, upload-time = "2026-02-19T05:07:05.305Z" }, ] [[package]] From da1937443d80fa232ba3a401ddf9117e3cb43555 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Feb 2026 15:04:55 +0000 Subject: [PATCH 177/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index fc3400034..b42368614 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* ⬆ Update all dependencies to use `griffelib` instead of `griffe`. PR [#14973](https://github.com/fastapi/fastapi/pull/14973) by [@svlandeg](https://github.com/svlandeg). * 🔨 Fix `FastAPI People` workflow. PR [#14951](https://github.com/fastapi/fastapi/pull/14951) by [@YuriiMotov](https://github.com/YuriiMotov). * 👷 Do not run codspeed with coverage as it's not tracked. PR [#14966](https://github.com/fastapi/fastapi/pull/14966) by [@tiangolo](https://github.com/tiangolo). * 👷 Do not include benchmark tests in coverage to speed up coverage processing. PR [#14965](https://github.com/fastapi/fastapi/pull/14965) by [@tiangolo](https://github.com/tiangolo). From 248d7fb9f5d3d0ac8202f7a03546441cd0f53c1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:49:53 +0100 Subject: [PATCH 178/195] =?UTF-8?q?=E2=AC=86=20Bump=20flask=20from=203.1.2?= =?UTF-8?q?=20to=203.1.3=20(#14949)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [flask](https://github.com/pallets/flask) from 3.1.2 to 3.1.3. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/3.1.2...3.1.3) --- updated-dependencies: - dependency-name: flask dependency-version: 3.1.3 dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 0b14fde53..6faba9662 100644 --- a/uv.lock +++ b/uv.lock @@ -1607,7 +1607,7 @@ wheels = [ [[package]] name = "flask" -version = "3.1.2" +version = "3.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, @@ -1617,9 +1617,9 @@ dependencies = [ { name = "markupsafe" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, ] [[package]] From 94a1ee749e63591e6e275b726a66e07a25f6c407 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Feb 2026 16:50:41 +0000 Subject: [PATCH 179/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b42368614..51d82313f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* ⬆ Bump flask from 3.1.2 to 3.1.3. PR [#14949](https://github.com/fastapi/fastapi/pull/14949) by [@dependabot[bot]](https://github.com/apps/dependabot). * ⬆ Update all dependencies to use `griffelib` instead of `griffe`. PR [#14973](https://github.com/fastapi/fastapi/pull/14973) by [@svlandeg](https://github.com/svlandeg). * 🔨 Fix `FastAPI People` workflow. PR [#14951](https://github.com/fastapi/fastapi/pull/14951) by [@YuriiMotov](https://github.com/YuriiMotov). * 👷 Do not run codspeed with coverage as it's not tracked. PR [#14966](https://github.com/fastapi/fastapi/pull/14966) by [@tiangolo](https://github.com/tiangolo). From 22354a253037e0fb23e55dabcb8767943e371702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Feb 2026 09:45:20 -0800 Subject: [PATCH 180/195] =?UTF-8?q?=F0=9F=94=92=EF=B8=8F=20Add=20`strict?= =?UTF-8?q?=5Fcontent=5Ftype`=20checking=20for=20JSON=20requests=20(#14978?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/strict-content-type.md | 88 ++++++++++++++++++ docs/en/mkdocs.yml | 1 + docs_src/strict_content_type/__init__.py | 0 .../strict_content_type/tutorial001_py310.py | 14 +++ fastapi/applications.py | 24 +++++ fastapi/routing.py | 44 ++++++++- tests/test_strict_content_type_app_level.py | 44 +++++++++ tests/test_strict_content_type_nested.py | 91 +++++++++++++++++++ .../test_strict_content_type_router_level.py | 61 +++++++++++++ .../test_body/test_tutorial001.py | 10 +- .../test_strict_content_type/__init__.py | 0 .../test_tutorial001.py | 43 +++++++++ 12 files changed, 411 insertions(+), 9 deletions(-) create mode 100644 docs/en/docs/advanced/strict-content-type.md create mode 100644 docs_src/strict_content_type/__init__.py create mode 100644 docs_src/strict_content_type/tutorial001_py310.py create mode 100644 tests/test_strict_content_type_app_level.py create mode 100644 tests/test_strict_content_type_nested.py create mode 100644 tests/test_strict_content_type_router_level.py create mode 100644 tests/test_tutorial/test_strict_content_type/__init__.py create mode 100644 tests/test_tutorial/test_strict_content_type/test_tutorial001.py diff --git a/docs/en/docs/advanced/strict-content-type.md b/docs/en/docs/advanced/strict-content-type.md new file mode 100644 index 000000000..54c099410 --- /dev/null +++ b/docs/en/docs/advanced/strict-content-type.md @@ -0,0 +1,88 @@ +# Strict Content-Type Checking { #strict-content-type-checking } + +By default, **FastAPI** uses strict `Content-Type` header checking for JSON request bodies, this means that JSON requests **must** include a valid `Content-Type` header (e.g. `application/json`) in order for the body to be parsed as JSON. + +## CSRF Risk { #csrf-risk } + +This default behavior provides protection against a class of **Cross-Site Request Forgery (CSRF)** attacks in a very specific scenario. + +These attacks exploit the fact that browsers allow scripts to send requests without doing any CORS preflight check when they: + +* don't have a `Content-Type` header (e.g. using `fetch()` with a `Blob` body) +* and don't send any authentication credentials. + +This type of attack is mainly relevant when: + +* the application is running locally (e.g. on `localhost`) or in an internal network +* and the application doesn't have any authentication, it expects that any request from the same network can be trusted. + +## Example Attack { #example-attack } + +Imagine you build a way to run a local AI agent. + +It provides an API at + +``` +http://localhost:8000/v1/agents/multivac +``` + +There's also a frontend at + +``` +http://localhost:8000 +``` + +/// tip + +Note that both have the same host. + +/// + +Then using the frontend you can make the AI agent do things on your behalf. + +As it's running **locally**, and not in the open internet, you decide to **not have any authentication** set up, just trusting the access to the local network. + +Then one of your users could install it and run it locally. + +Then they could open a malicious website, e.g. something like + +``` +https://evilhackers.example.com +``` + +And that malicious website sends requests using `fetch()` with a `Blob` body to the local API at + +``` +http://localhost:8000/v1/agents/multivac +``` + +Even though the host of the malicious website and the local app is different, the browser won't trigger a CORS preflight request because: + +* It's running without any authentication, it doesn't have to send any credentials. +* The browser thinks it's not sending JSON (because of the missing `Content-Type` header). + +Then the malicious website could make the local AI agent send angry messages to the user's ex-boss... or worse. 😅 + +## Open Internet { #open-internet } + +If your app is in the open internet, you wouldn't "trust the network" and let anyone send privileged requests without authentication. + +Attackers could simply run a script to send requests to your API, no need for browser interaction, so you are probably already securing any privileged endpoints. + +In that case **this attack / risk doesn't apply to you**. + +This risk and attack is mainly relevant when the app runs on the **local network** and that is the **only assumed protection**. + +## Allowing Requests Without Content-Type { #allowing-requests-without-content-type } + +If you need to support clients that don't send a `Content-Type` header, you can disable strict checking by setting `strict_content_type=False`: + +{* ../../docs_src/strict_content_type/tutorial001_py310.py hl[4] *} + +With this setting, requests without a `Content-Type` header will have their body parsed as JSON, which is the same behavior as older versions of FastAPI. + +/// info + +This behavior and configuration was added in FastAPI 0.132.0. + +/// diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index b276e55d9..e86e7b9c4 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -193,6 +193,7 @@ nav: - advanced/generate-clients.md - advanced/advanced-python-types.md - advanced/json-base64-bytes.md + - advanced/strict-content-type.md - fastapi-cli.md - Deployment: - deployment/index.md diff --git a/docs_src/strict_content_type/__init__.py b/docs_src/strict_content_type/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs_src/strict_content_type/tutorial001_py310.py b/docs_src/strict_content_type/tutorial001_py310.py new file mode 100644 index 000000000..a44f4b138 --- /dev/null +++ b/docs_src/strict_content_type/tutorial001_py310.py @@ -0,0 +1,14 @@ +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI(strict_content_type=False) + + +class Item(BaseModel): + name: str + price: float + + +@app.post("/items/") +async def create_item(item: Item): + return item diff --git a/fastapi/applications.py b/fastapi/applications.py index 41d86143e..ed05a1ff9 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -840,6 +840,29 @@ class FastAPI(Starlette): """ ), ] = None, + strict_content_type: Annotated[ + bool, + Doc( + """ + Enable strict checking for request Content-Type headers. + + When `True` (the default), requests with a body that do not include + a `Content-Type` header will **not** be parsed as JSON. + + This prevents potential cross-site request forgery (CSRF) attacks + that exploit the browser's ability to send requests without a + Content-Type header, bypassing CORS preflight checks. In particular + applicable for apps that need to be run locally (in localhost). + + When `False`, requests without a `Content-Type` header will have + their body parsed as JSON, which maintains compatibility with + certain clients that don't send `Content-Type` headers. + + Read more about it in the + [FastAPI docs for Strict Content-Type](https://fastapi.tiangolo.com/advanced/strict-content-type/). + """ + ), + ] = True, **extra: Annotated[ Any, Doc( @@ -974,6 +997,7 @@ class FastAPI(Starlette): include_in_schema=include_in_schema, responses=responses, generate_unique_id_function=generate_unique_id_function, + strict_content_type=strict_content_type, ) self.exception_handlers: dict[ Any, Callable[[Request, Any], Response | Awaitable[Response]] diff --git a/fastapi/routing.py b/fastapi/routing.py index 528c96296..d17650a62 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -329,6 +329,7 @@ def get_request_handler( response_model_exclude_none: bool = False, dependency_overrides_provider: Any | None = None, embed_body_fields: bool = False, + strict_content_type: bool | DefaultPlaceholder = Default(True), ) -> Callable[[Request], Coroutine[Any, Any, Response]]: assert dependant.call is not None, "dependant.call must be a function" is_coroutine = dependant.is_coroutine_callable @@ -337,6 +338,10 @@ def get_request_handler( actual_response_class: type[Response] = response_class.value else: actual_response_class = response_class + if isinstance(strict_content_type, DefaultPlaceholder): + actual_strict_content_type: bool = strict_content_type.value + else: + actual_strict_content_type = strict_content_type async def app(request: Request) -> Response: response: Response | None = None @@ -370,7 +375,8 @@ def get_request_handler( json_body: Any = Undefined content_type_value = request.headers.get("content-type") if not content_type_value: - json_body = await request.json() + if not actual_strict_content_type: + json_body = await request.json() else: message = email.message.Message() message["content-type"] = content_type_value @@ -599,6 +605,7 @@ class APIRoute(routing.Route): openapi_extra: dict[str, Any] | None = None, generate_unique_id_function: Callable[["APIRoute"], str] | DefaultPlaceholder = Default(generate_unique_id), + strict_content_type: bool | DefaultPlaceholder = Default(True), ) -> None: self.path = path self.endpoint = endpoint @@ -625,6 +632,7 @@ class APIRoute(routing.Route): self.callbacks = callbacks self.openapi_extra = openapi_extra self.generate_unique_id_function = generate_unique_id_function + self.strict_content_type = strict_content_type self.tags = tags or [] self.responses = responses or {} self.name = get_name(endpoint) if name is None else name @@ -713,6 +721,7 @@ class APIRoute(routing.Route): response_model_exclude_none=self.response_model_exclude_none, dependency_overrides_provider=self.dependency_overrides_provider, embed_body_fields=self._embed_body_fields, + strict_content_type=self.strict_content_type, ) def matches(self, scope: Scope) -> tuple[Match, Scope]: @@ -963,6 +972,29 @@ class APIRouter(routing.Router): """ ), ] = Default(generate_unique_id), + strict_content_type: Annotated[ + bool, + Doc( + """ + Enable strict checking for request Content-Type headers. + + When `True` (the default), requests with a body that do not include + a `Content-Type` header will **not** be parsed as JSON. + + This prevents potential cross-site request forgery (CSRF) attacks + that exploit the browser's ability to send requests without a + Content-Type header, bypassing CORS preflight checks. In particular + applicable for apps that need to be run locally (in localhost). + + When `False`, requests without a `Content-Type` header will have + their body parsed as JSON, which maintains compatibility with + certain clients that don't send `Content-Type` headers. + + Read more about it in the + [FastAPI docs for Strict Content-Type](https://fastapi.tiangolo.com/advanced/strict-content-type/). + """ + ), + ] = Default(True), ) -> None: # Determine the lifespan context to use if lifespan is None: @@ -1009,6 +1041,7 @@ class APIRouter(routing.Router): self.route_class = route_class self.default_response_class = default_response_class self.generate_unique_id_function = generate_unique_id_function + self.strict_content_type = strict_content_type def route( self, @@ -1059,6 +1092,7 @@ class APIRouter(routing.Router): openapi_extra: dict[str, Any] | None = None, generate_unique_id_function: Callable[[APIRoute], str] | DefaultPlaceholder = Default(generate_unique_id), + strict_content_type: bool | DefaultPlaceholder = Default(True), ) -> None: route_class = route_class_override or self.route_class responses = responses or {} @@ -1105,6 +1139,9 @@ class APIRouter(routing.Router): callbacks=current_callbacks, openapi_extra=openapi_extra, generate_unique_id_function=current_generate_unique_id, + strict_content_type=get_value_or_default( + strict_content_type, self.strict_content_type + ), ) self.routes.append(route) @@ -1480,6 +1517,11 @@ class APIRouter(routing.Router): callbacks=current_callbacks, openapi_extra=route.openapi_extra, generate_unique_id_function=current_generate_unique_id, + strict_content_type=get_value_or_default( + route.strict_content_type, + router.strict_content_type, + self.strict_content_type, + ), ) elif isinstance(route, routing.Route): methods = list(route.methods or []) diff --git a/tests/test_strict_content_type_app_level.py b/tests/test_strict_content_type_app_level.py new file mode 100644 index 000000000..42a0821a4 --- /dev/null +++ b/tests/test_strict_content_type_app_level.py @@ -0,0 +1,44 @@ +from fastapi import FastAPI +from fastapi.testclient import TestClient + +app_default = FastAPI() + + +@app_default.post("/items/") +async def app_default_post(data: dict): + return data + + +app_lax = FastAPI(strict_content_type=False) + + +@app_lax.post("/items/") +async def app_lax_post(data: dict): + return data + + +client_default = TestClient(app_default) +client_lax = TestClient(app_lax) + + +def test_default_strict_rejects_no_content_type(): + response = client_default.post("/items/", content='{"key": "value"}') + assert response.status_code == 422 + + +def test_default_strict_accepts_json_content_type(): + response = client_default.post("/items/", json={"key": "value"}) + assert response.status_code == 200 + assert response.json() == {"key": "value"} + + +def test_lax_accepts_no_content_type(): + response = client_lax.post("/items/", content='{"key": "value"}') + assert response.status_code == 200 + assert response.json() == {"key": "value"} + + +def test_lax_accepts_json_content_type(): + response = client_lax.post("/items/", json={"key": "value"}) + assert response.status_code == 200 + assert response.json() == {"key": "value"} diff --git a/tests/test_strict_content_type_nested.py b/tests/test_strict_content_type_nested.py new file mode 100644 index 000000000..922d01571 --- /dev/null +++ b/tests/test_strict_content_type_nested.py @@ -0,0 +1,91 @@ +from fastapi import APIRouter, FastAPI +from fastapi.testclient import TestClient + +# Lax app with nested routers, inner overrides to strict + +app_nested = FastAPI(strict_content_type=False) # lax app +outer_router = APIRouter(prefix="/outer") # inherits lax from app +inner_strict = APIRouter(prefix="/strict", strict_content_type=True) +inner_default = APIRouter(prefix="/default") + + +@inner_strict.post("/items/") +async def inner_strict_post(data: dict): + return data + + +@inner_default.post("/items/") +async def inner_default_post(data: dict): + return data + + +outer_router.include_router(inner_strict) +outer_router.include_router(inner_default) +app_nested.include_router(outer_router) + +client_nested = TestClient(app_nested) + + +def test_strict_inner_on_lax_app_rejects_no_content_type(): + response = client_nested.post("/outer/strict/items/", content='{"key": "value"}') + assert response.status_code == 422 + + +def test_default_inner_inherits_lax_from_app(): + response = client_nested.post("/outer/default/items/", content='{"key": "value"}') + assert response.status_code == 200 + assert response.json() == {"key": "value"} + + +def test_strict_inner_accepts_json_content_type(): + response = client_nested.post("/outer/strict/items/", json={"key": "value"}) + assert response.status_code == 200 + + +def test_default_inner_accepts_json_content_type(): + response = client_nested.post("/outer/default/items/", json={"key": "value"}) + assert response.status_code == 200 + + +# Strict app -> lax outer router -> strict inner router + +app_mixed = FastAPI(strict_content_type=True) +mixed_outer = APIRouter(prefix="/outer", strict_content_type=False) +mixed_inner = APIRouter(prefix="/inner", strict_content_type=True) + + +@mixed_outer.post("/items/") +async def mixed_outer_post(data: dict): + return data + + +@mixed_inner.post("/items/") +async def mixed_inner_post(data: dict): + return data + + +mixed_outer.include_router(mixed_inner) +app_mixed.include_router(mixed_outer) + +client_mixed = TestClient(app_mixed) + + +def test_lax_outer_on_strict_app_accepts_no_content_type(): + response = client_mixed.post("/outer/items/", content='{"key": "value"}') + assert response.status_code == 200 + assert response.json() == {"key": "value"} + + +def test_strict_inner_on_lax_outer_rejects_no_content_type(): + response = client_mixed.post("/outer/inner/items/", content='{"key": "value"}') + assert response.status_code == 422 + + +def test_lax_outer_accepts_json_content_type(): + response = client_mixed.post("/outer/items/", json={"key": "value"}) + assert response.status_code == 200 + + +def test_strict_inner_on_lax_outer_accepts_json_content_type(): + response = client_mixed.post("/outer/inner/items/", json={"key": "value"}) + assert response.status_code == 200 diff --git a/tests/test_strict_content_type_router_level.py b/tests/test_strict_content_type_router_level.py new file mode 100644 index 000000000..72a02d6c9 --- /dev/null +++ b/tests/test_strict_content_type_router_level.py @@ -0,0 +1,61 @@ +from fastapi import APIRouter, FastAPI +from fastapi.testclient import TestClient + +app = FastAPI() + +router_lax = APIRouter(prefix="/lax", strict_content_type=False) +router_strict = APIRouter(prefix="/strict", strict_content_type=True) +router_default = APIRouter(prefix="/default") + + +@router_lax.post("/items/") +async def router_lax_post(data: dict): + return data + + +@router_strict.post("/items/") +async def router_strict_post(data: dict): + return data + + +@router_default.post("/items/") +async def router_default_post(data: dict): + return data + + +app.include_router(router_lax) +app.include_router(router_strict) +app.include_router(router_default) + +client = TestClient(app) + + +def test_lax_router_on_strict_app_accepts_no_content_type(): + response = client.post("/lax/items/", content='{"key": "value"}') + assert response.status_code == 200 + assert response.json() == {"key": "value"} + + +def test_strict_router_on_strict_app_rejects_no_content_type(): + response = client.post("/strict/items/", content='{"key": "value"}') + assert response.status_code == 422 + + +def test_default_router_inherits_strict_from_app(): + response = client.post("/default/items/", content='{"key": "value"}') + assert response.status_code == 422 + + +def test_lax_router_accepts_json_content_type(): + response = client.post("/lax/items/", json={"key": "value"}) + assert response.status_code == 200 + + +def test_strict_router_accepts_json_content_type(): + response = client.post("/strict/items/", json={"key": "value"}) + assert response.status_code == 200 + + +def test_default_router_accepts_json_content_type(): + response = client.post("/default/items/", json={"key": "value"}) + assert response.status_code == 200 diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py index bdabf8d68..8c883708a 100644 --- a/tests/test_tutorial/test_body/test_tutorial001.py +++ b/tests/test_tutorial/test_body/test_tutorial001.py @@ -189,18 +189,12 @@ def test_geo_json(client: TestClient): assert response.status_code == 200, response.text -def test_no_content_type_is_json(client: TestClient): +def test_no_content_type_json(client: TestClient): response = client.post( "/items/", content='{"name": "Foo", "price": 50.5}', ) - assert response.status_code == 200, response.text - assert response.json() == { - "name": "Foo", - "description": None, - "price": 50.5, - "tax": None, - } + assert response.status_code == 422, response.text def test_wrong_headers(client: TestClient): diff --git a/tests/test_tutorial/test_strict_content_type/__init__.py b/tests/test_tutorial/test_strict_content_type/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_tutorial/test_strict_content_type/test_tutorial001.py b/tests/test_tutorial/test_strict_content_type/test_tutorial001.py new file mode 100644 index 000000000..81e2d3a0b --- /dev/null +++ b/tests/test_tutorial/test_strict_content_type/test_tutorial001.py @@ -0,0 +1,43 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture( + name="client", + params=[ + "tutorial001_py310", + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.strict_content_type.{request.param}") + client = TestClient(mod.app) + return client + + +def test_lax_post_without_content_type_is_parsed_as_json(client: TestClient): + response = client.post( + "/items/", + content='{"name": "Foo", "price": 50.5}', + ) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "price": 50.5} + + +def test_lax_post_with_json_content_type(client: TestClient): + response = client.post( + "/items/", + json={"name": "Foo", "price": 50.5}, + ) + assert response.status_code == 200, response.text + assert response.json() == {"name": "Foo", "price": 50.5} + + +def test_lax_post_with_text_plain_is_still_rejected(client: TestClient): + response = client.post( + "/items/", + content='{"name": "Foo", "price": 50.5}', + headers={"Content-Type": "text/plain"}, + ) + assert response.status_code == 422, response.text From ac8621a76eba48c29cead5cd0dcdd77d446d37ca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Feb 2026 17:46:11 +0000 Subject: [PATCH 181/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 51d82313f..0be7bbc9c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Breaking Changes + +* 🔒️ Add `strict_content_type` checking for JSON requests. PR [#14978](https://github.com/fastapi/fastapi/pull/14978) by [@tiangolo](https://github.com/tiangolo). + ### Internal * ⬆ Bump flask from 3.1.2 to 3.1.3. PR [#14949](https://github.com/fastapi/fastapi/pull/14949) by [@dependabot[bot]](https://github.com/apps/dependabot). From 5c863d0718cea2e0ed812fbbfdc5844a0a34b039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Feb 2026 18:46:57 +0100 Subject: [PATCH 182/195] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.13?= =?UTF-8?q?2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 0be7bbc9c..7ea70eceb 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.132.0 + ### Breaking Changes * 🔒️ Add `strict_content_type` checking for JSON requests. PR [#14978](https://github.com/fastapi/fastapi/pull/14978) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 944fdd58f..d936bb7df 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.131.0" +__version__ = "0.132.0" from starlette import status as status From acdf52e0c89f81952ab17bb7b34d67deb0d533dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Feb 2026 18:54:18 +0100 Subject: [PATCH 183/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7ea70eceb..08826adff 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -12,6 +12,9 @@ hide: ### Breaking Changes * 🔒️ Add `strict_content_type` checking for JSON requests. PR [#14978](https://github.com/fastapi/fastapi/pull/14978) by [@tiangolo](https://github.com/tiangolo). + * Now FastAPI checks, by default, that JSON requests have a `Content-Type` header with a valid JSON value, like `application/json`, and rejects requests that don't. + * If the clients for your app don't send a valid `Content-Type` header you can disable this with `strict_content_type=False`. + * Check the new docs: [Strict Content-Type Checking](https://fastapi.tiangolo.com/advanced/strict-content-type/). ### Internal From 6af38321261afde391dd28f60ec0dab9c15f0629 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:31:54 +0100 Subject: [PATCH 184/195] =?UTF-8?q?=F0=9F=91=B7=20Allow=20skipping=20`benc?= =?UTF-8?q?hmark`=20job=20in=20`test`=20workflow=20(#14974)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86e6bfc14..6046a4560 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -208,4 +208,4 @@ jobs: uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} - allowed-skips: coverage-combine,test + allowed-skips: coverage-combine,test,benchmark From 3f30ca1a5e2ed4cca1f8102b39a40e56151a018e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Feb 2026 18:32:32 +0000 Subject: [PATCH 185/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 08826adff..2b068a572 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* 👷 Allow skipping `benchmark` job in `test` workflow. PR [#14974](https://github.com/fastapi/fastapi/pull/14974) by [@YuriiMotov](https://github.com/YuriiMotov). + ## 0.132.0 ### Breaking Changes From 0cf27ecf8826ab8d1c8c21a4b9883ddae480d2a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 23 Feb 2026 10:47:59 -0800 Subject: [PATCH 186/195] =?UTF-8?q?=F0=9F=91=A5=20Update=20FastAPI=20Peopl?= =?UTF-8?q?e=20-=20Experts=20(#14972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- docs/en/data/people.yml | 932 ++++++++++++++++++++-------------------- 1 file changed, 470 insertions(+), 462 deletions(-) diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml index 2fdb21a05..89269ecd6 100644 --- a/docs/en/data/people.yml +++ b/docs/en/data/people.yml @@ -1,23 +1,23 @@ maintainers: - login: tiangolo - answers: 1900 + answers: 1923 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 url: https://github.com/tiangolo experts: - login: tiangolo - count: 1900 + count: 1923 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 url: https://github.com/tiangolo - login: YuriiMotov - count: 971 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + count: 1107 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 url: https://github.com/YuriiMotov - login: github-actions - count: 769 + count: 770 avatarUrl: https://avatars.githubusercontent.com/in/15368?v=4 url: https://github.com/apps/github-actions - login: Kludex - count: 654 + count: 656 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 url: https://github.com/Kludex - login: jgould22 @@ -37,7 +37,7 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4 url: https://github.com/ycd - login: JarroVGIT - count: 190 + count: 192 avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4 url: https://github.com/JarroVGIT - login: euri10 @@ -53,11 +53,11 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4 url: https://github.com/phy25 - login: JavierSanchezCastro - count: 94 + count: 106 avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 url: https://github.com/JavierSanchezCastro - login: luzzodev - count: 89 + count: 104 avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 url: https://github.com/luzzodev - login: raphaelauv @@ -81,32 +81,32 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4 url: https://github.com/falkben - login: yinziyan1206 - count: 54 + count: 55 avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 url: https://github.com/yinziyan1206 +- login: acidjunk + count: 50 + avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 + url: https://github.com/acidjunk - login: sm-Fifteen count: 49 avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 url: https://github.com/sm-Fifteen -- login: acidjunk - count: 49 - avatarUrl: https://avatars.githubusercontent.com/u/685002?u=b5094ab4527fc84b006c0ac9ff54367bdebb2267&v=4 - url: https://github.com/acidjunk - login: adriangb count: 46 avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 url: https://github.com/adriangb -- login: Dustyposa - count: 45 - avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 - url: https://github.com/Dustyposa - login: insomnes count: 45 avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 url: https://github.com/insomnes +- login: Dustyposa + count: 45 + avatarUrl: https://avatars.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4 + url: https://github.com/Dustyposa - login: frankie567 count: 43 - avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=c159fe047727aedecbbeeaa96a1b03ceb9d39add&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/1144727?u=f3e79acfe4ed207e15c2145161a8a9759925fcd2&v=4 url: https://github.com/frankie567 - login: odiseo0 count: 43 @@ -120,14 +120,14 @@ experts: count: 40 avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 url: https://github.com/includeamin -- login: STeveShary - count: 37 - avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 - url: https://github.com/STeveShary - login: chbndrhnns count: 37 avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 url: https://github.com/chbndrhnns +- login: STeveShary + count: 37 + avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 + url: https://github.com/STeveShary - login: krishnardt count: 35 avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4 @@ -136,18 +136,22 @@ experts: count: 32 avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 url: https://github.com/panla +- login: valentinDruzhinin + count: 30 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin - login: prostomarkeloff count: 28 avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=6918e39a1224194ba636e897461a02a20126d7ad&v=4 url: https://github.com/prostomarkeloff -- login: hasansezertasan - count: 27 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 - url: https://github.com/hasansezertasan - login: alv2017 - count: 26 + count: 27 avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 url: https://github.com/alv2017 +- login: hasansezertasan + count: 27 + avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=d36995e41a00590da64e6204cfd112e0484ac1ca&v=4 + url: https://github.com/hasansezertasan - login: dbanty count: 26 avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9d726785d08e50b1e1cd96505800c8ea8405bce2&v=4 @@ -156,10 +160,6 @@ experts: count: 25 avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 url: https://github.com/wshayes -- login: valentinDruzhinin - count: 24 - avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 - url: https://github.com/valentinDruzhinin - login: SirTelemak count: 23 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 @@ -176,6 +176,10 @@ experts: count: 22 avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 url: https://github.com/chrisK824 +- login: ceb10n + count: 21 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n - login: rafsaf count: 21 avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=5fe59a56e1f2f9ccd8005d71752a8276f133ae1a&v=4 @@ -194,7 +198,7 @@ experts: url: https://github.com/ebottos94 - login: estebanx64 count: 19 - avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=1900887aeed268699e5ea6f3fb7db614f7b77cd3&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=2ca073ee47a625e495a9573bd374ddcd7be5ec91&v=4 url: https://github.com/estebanx64 - login: sehraramiz count: 18 @@ -236,467 +240,471 @@ experts: count: 16 avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=f601c3f111f2148bd9244c2cb3ebbd57b592e674&v=4 url: https://github.com/jonatasoli -- login: ghost - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/10137?u=b1951d34a583cf12ec0d3b0781ba19be97726318&v=4 - url: https://github.com/ghost -- login: abhint - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4 - url: https://github.com/abhint -last_month_experts: -- login: YuriiMotov - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - url: https://github.com/YuriiMotov -- login: valentinDruzhinin - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 - url: https://github.com/valentinDruzhinin -- login: yinziyan1206 - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 - url: https://github.com/yinziyan1206 -- login: tiangolo - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo -- login: luzzodev - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 - url: https://github.com/luzzodev -three_months_experts: -- login: YuriiMotov - count: 397 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - url: https://github.com/YuriiMotov -- login: valentinDruzhinin - count: 24 - avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 - url: https://github.com/valentinDruzhinin -- login: luzzodev - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 - url: https://github.com/luzzodev -- login: raceychan - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4 - url: https://github.com/raceychan -- login: yinziyan1206 - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 - url: https://github.com/yinziyan1206 -- login: DoctorJohn - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=2913e70a6142772847e91e2aaa5b9152391715e9&v=4 - url: https://github.com/DoctorJohn -- login: tiangolo - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo -- login: sachinh35 - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 - url: https://github.com/sachinh35 -- login: eqsdxr - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4 - url: https://github.com/eqsdxr -- login: Jelle-tenB - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 - url: https://github.com/Jelle-tenB -- login: pythonweb2 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 - url: https://github.com/pythonweb2 -- login: WilliamDEdwards - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 - url: https://github.com/WilliamDEdwards -- login: Brikas - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 - url: https://github.com/Brikas -- login: purepani - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 - url: https://github.com/purepani -- login: JavierSanchezCastro - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- login: TaigoFr - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 - url: https://github.com/TaigoFr -- login: Garrett-R - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 - url: https://github.com/Garrett-R -- login: jymchng - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 - url: https://github.com/jymchng -- login: davidhuser - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 - url: https://github.com/davidhuser -six_months_experts: -- login: YuriiMotov - count: 763 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - url: https://github.com/YuriiMotov -- login: luzzodev - count: 45 - avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 - url: https://github.com/luzzodev -- login: valentinDruzhinin - count: 24 - avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 - url: https://github.com/valentinDruzhinin -- login: alv2017 - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 - url: https://github.com/alv2017 -- login: sachinh35 - count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 - url: https://github.com/sachinh35 -- login: yauhen-sobaleu - count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/51629535?u=fc1817060daf2df438bfca86c44f33da5cd667db&v=4 - url: https://github.com/yauhen-sobaleu -- login: tiangolo - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo -- login: JavierSanchezCastro - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- login: raceychan - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4 - url: https://github.com/raceychan -- login: yinziyan1206 - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 - url: https://github.com/yinziyan1206 -- login: DoctorJohn - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=2913e70a6142772847e91e2aaa5b9152391715e9&v=4 - url: https://github.com/DoctorJohn -- login: eqsdxr - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4 - url: https://github.com/eqsdxr -- login: Kludex - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 - url: https://github.com/Kludex -- login: Jelle-tenB - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 - url: https://github.com/Jelle-tenB -- login: adsouza - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/275832?u=f90f110cfafeafed2f14339e840941c2c328c186&v=4 - url: https://github.com/adsouza -- login: pythonweb2 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 - url: https://github.com/pythonweb2 -- login: WilliamDEdwards - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 - url: https://github.com/WilliamDEdwards -- login: Brikas - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 - url: https://github.com/Brikas -- login: purepani - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 - url: https://github.com/purepani -- login: TaigoFr - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 - url: https://github.com/TaigoFr -- login: Garrett-R - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 - url: https://github.com/Garrett-R -- login: EverStarck - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4 - url: https://github.com/EverStarck -- login: henrymcl - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4 - url: https://github.com/henrymcl -- login: jymchng - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 - url: https://github.com/jymchng -- login: davidhuser - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 - url: https://github.com/davidhuser -- login: PidgeyBE - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/19860056?u=47b584eb1c1ab45e31c1b474109a962d7e82be49&v=4 - url: https://github.com/PidgeyBE -- login: KianAnbarestani - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/145364424?u=dcc3d8fb4ca07d36fb52a17f38b6650565de40be&v=4 - url: https://github.com/KianAnbarestani -- login: jgould22 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 - url: https://github.com/jgould22 -- login: marsboy02 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/86903678?u=04cc319d6605f8d1ba3a0bed9f4f55a582719ae6&v=4 - url: https://github.com/marsboy02 -one_year_experts: -- login: YuriiMotov - count: 824 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - url: https://github.com/YuriiMotov -- login: luzzodev - count: 89 - avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 - url: https://github.com/luzzodev -- login: Kludex - count: 50 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 - url: https://github.com/Kludex -- login: sinisaos - count: 33 - avatarUrl: https://avatars.githubusercontent.com/u/30960668?v=4 - url: https://github.com/sinisaos -- login: alv2017 - count: 26 - avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 - url: https://github.com/alv2017 -- login: valentinDruzhinin - count: 24 - avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 - url: https://github.com/valentinDruzhinin -- login: JavierSanchezCastro - count: 24 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- login: jgould22 - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 - url: https://github.com/jgould22 -- login: tiangolo - count: 14 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo -- login: Kfir-G - count: 13 - avatarUrl: https://avatars.githubusercontent.com/u/57500876?u=a3bf923ab27bce3d1b13779a8dd22eb7675017fd&v=4 - url: https://github.com/Kfir-G -- login: sehraramiz - count: 11 - avatarUrl: https://avatars.githubusercontent.com/u/14166324?u=8fac65e84dfff24245d304a5b5b09f7b5bd69dc9&v=4 - url: https://github.com/sehraramiz -- login: sachinh35 - count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 - url: https://github.com/sachinh35 -- login: yauhen-sobaleu - count: 9 - avatarUrl: https://avatars.githubusercontent.com/u/51629535?u=fc1817060daf2df438bfca86c44f33da5cd667db&v=4 - url: https://github.com/yauhen-sobaleu -- login: estebanx64 - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=1900887aeed268699e5ea6f3fb7db614f7b77cd3&v=4 - url: https://github.com/estebanx64 -- login: ceb10n - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 - url: https://github.com/ceb10n -- login: yvallois - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/36999744?v=4 - url: https://github.com/yvallois -- login: raceychan - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4 - url: https://github.com/raceychan -- login: yinziyan1206 - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 - url: https://github.com/yinziyan1206 -- login: DoctorJohn - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=2913e70a6142772847e91e2aaa5b9152391715e9&v=4 - url: https://github.com/DoctorJohn -- login: n8sty - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/2964996?v=4 - url: https://github.com/n8sty -- login: pythonweb2 - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 - url: https://github.com/pythonweb2 -- login: eqsdxr - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4 - url: https://github.com/eqsdxr -- login: yokwejuste - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/71908316?u=4ba43bd63c169b5c015137d8916752a44001445a&v=4 - url: https://github.com/yokwejuste -- login: WilliamDEdwards - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 - url: https://github.com/WilliamDEdwards - login: mattmess1221 - count: 3 + count: 15 avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=d22ea18aa8ea688af25a45df306134d593621a44&v=4 url: https://github.com/mattmess1221 -- login: Jelle-tenB - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 - url: https://github.com/Jelle-tenB -- login: viniciusCalcantara - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/108818737?u=80f3ec7427fa6a41d5896984d0c526432f2299fa&v=4 - url: https://github.com/viniciusCalcantara -- login: davidhuser - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 - url: https://github.com/davidhuser -- login: dbfreem - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/9778569?u=f2f1e9135b5e4f1b0c6821a548b17f97572720fc&v=4 - url: https://github.com/dbfreem -- login: SobikXexe - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/87701130?v=4 - url: https://github.com/SobikXexe -- login: pawelad - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/7062874?u=d27dc220545a8401ad21840590a97d474d7101e6&v=4 - url: https://github.com/pawelad -- login: Isuxiz - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/48672727?u=34d7b4ade252687d22a27cf53037b735b244bfc1&v=4 - url: https://github.com/Isuxiz -- login: Minibrams - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/8108085?u=b028dbc308fa8485e0e2e9402b3d03d8deb22bf9&v=4 - url: https://github.com/Minibrams -- login: adsouza +last_month_experts: +- login: YuriiMotov + count: 20 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov +- login: Toygarmetu + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4 + url: https://github.com/Toygarmetu +- login: JavierSanchezCastro + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +- login: valentinDruzhinin count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/275832?u=f90f110cfafeafed2f14339e840941c2c328c186&v=4 - url: https://github.com/adsouza -- login: Synrom + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +three_months_experts: +- login: YuriiMotov + count: 77 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov +- login: tiangolo + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: Toygarmetu + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4 + url: https://github.com/Toygarmetu +- login: JavierSanchezCastro + count: 7 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +- login: ceb10n + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n +- login: valentinDruzhinin + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +- login: RichieB2B + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4 + url: https://github.com/RichieB2B +- login: sachinh35 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 + url: https://github.com/sachinh35 +- login: EmmanuelNiyonshuti count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/30272537?v=4 - url: https://github.com/Synrom -- login: gaby + avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4 + url: https://github.com/EmmanuelNiyonshuti +- login: luzzodev count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/835733?u=8c72dec16fa560bdc81113354f2ffd79ad062bde&v=4 - url: https://github.com/gaby -- login: Ale-Cas + avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 + url: https://github.com/luzzodev +- login: davidbrochart count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/64859146?u=d52a6ecf8d83d2927e2ae270bdfcc83495dba8c9&v=4 - url: https://github.com/Ale-Cas -- login: CharlesPerrotMinotHCHB + avatarUrl: https://avatars.githubusercontent.com/u/4711805?u=d39696d995a9e02ec3613ffb2f62b20b14f92f26&v=4 + url: https://github.com/davidbrochart +- login: CharlieReitzel count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/112571330?u=e3a666718ff5ad1d1c49d6c31358a9f80c841b30&v=4 - url: https://github.com/CharlesPerrotMinotHCHB -- login: yanggeorge + avatarUrl: https://avatars.githubusercontent.com/u/20848272?v=4 + url: https://github.com/CharlieReitzel +- login: dotmitsu count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/2434407?v=4 - url: https://github.com/yanggeorge -- login: Brikas - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 - url: https://github.com/Brikas + avatarUrl: https://avatars.githubusercontent.com/u/42657211?u=3bccc9a2f386a3f24230ec393080f8904fe2a5b2&v=4 + url: https://github.com/dotmitsu - login: dolfinus count: 2 avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4 url: https://github.com/dolfinus -- login: slafs +- login: garg-khushi count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4 - url: https://github.com/slafs -- login: purepani + avatarUrl: https://avatars.githubusercontent.com/u/139839680?u=7faffa70275f8ab16f163e0c742a11d2662f9c66&v=4 + url: https://github.com/garg-khushi +- login: florentx count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 - url: https://github.com/purepani -- login: ddahan + avatarUrl: https://avatars.githubusercontent.com/u/142113?u=bf10f10080026346b092633c380977b61cee0d9c&v=4 + url: https://github.com/florentx +- login: JunjieAraoXiong count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/1933516?u=1d200a620e8d6841df017e9f2bb7efb58b580f40&v=4 - url: https://github.com/ddahan -- login: TaigoFr + avatarUrl: https://avatars.githubusercontent.com/u/167785867?u=b69afe090c8bf5fd73f2d23fc3a887b28f68f192&v=4 + url: https://github.com/JunjieAraoXiong +six_months_experts: +- login: YuriiMotov + count: 150 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov +- login: tiangolo + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: luzzodev + count: 15 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 + url: https://github.com/luzzodev +- login: engripaye + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/155247530?u=645169bc81856b7f1bd20090ecb0171a56dcbeb4&v=4 + url: https://github.com/engripaye +- login: JavierSanchezCastro + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +- login: Toygarmetu + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4 + url: https://github.com/Toygarmetu +- login: valentinDruzhinin + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +- login: ceb10n + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n +- login: RichieB2B + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4 + url: https://github.com/RichieB2B +- login: JunjieAraoXiong + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/167785867?u=b69afe090c8bf5fd73f2d23fc3a887b28f68f192&v=4 + url: https://github.com/JunjieAraoXiong +- login: CodeKraken-cmd + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/48470371?u=e7c0e7ec8e35ca5fb3ae40a586ed5e788fd0fe6d&v=4 + url: https://github.com/CodeKraken-cmd +- login: svlandeg + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4 + url: https://github.com/svlandeg +- login: ArmanShirzad + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/68951175?u=1f1efae2fa5d0d17c38a1a8413bedca5e538cedb&v=4 + url: https://github.com/ArmanShirzad +- login: krylosov-aa + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/242901957?u=4c9c7b468203b09bca64936fb464620e32cdd252&v=4 + url: https://github.com/krylosov-aa +- login: sachinh35 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 + url: https://github.com/sachinh35 +- login: simone-trubian + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/5606840?u=65703af3c605feca61ce49e4009bb4e26495b425&v=4 + url: https://github.com/simone-trubian +- login: mahimairaja + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/81288263?u=4eef6b4a36b96e84bd666fc1937aa589036ccb9a&v=4 + url: https://github.com/mahimairaja +- login: pankeshpatel + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/1482917?u=666f39197a88cfa38b8bd78d39ef04d95c948b6b&v=4 + url: https://github.com/pankeshpatel +- login: huynguyengl99 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 - url: https://github.com/TaigoFr -- login: Garrett-R + avatarUrl: https://avatars.githubusercontent.com/u/49433085?u=7b626115686c5d97a2a32a03119f5300e425cc9f&v=4 + url: https://github.com/huynguyengl99 +- login: EmmanuelNiyonshuti count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 - url: https://github.com/Garrett-R -- login: jd-solanki + avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4 + url: https://github.com/EmmanuelNiyonshuti +- login: davidbrochart count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/47495003?u=6e225cb42c688d0cd70e65c6baedb9f5922b1178&v=4 - url: https://github.com/jd-solanki -- login: EverStarck + avatarUrl: https://avatars.githubusercontent.com/u/4711805?u=d39696d995a9e02ec3613ffb2f62b20b14f92f26&v=4 + url: https://github.com/davidbrochart +- login: CharlieReitzel count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4 - url: https://github.com/EverStarck -- login: henrymcl + avatarUrl: https://avatars.githubusercontent.com/u/20848272?v=4 + url: https://github.com/CharlieReitzel +- login: dotmitsu count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4 - url: https://github.com/henrymcl + avatarUrl: https://avatars.githubusercontent.com/u/42657211?u=3bccc9a2f386a3f24230ec393080f8904fe2a5b2&v=4 + url: https://github.com/dotmitsu +- login: dolfinus + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4 + url: https://github.com/dolfinus +- login: Kludex + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 + url: https://github.com/Kludex +- login: garg-khushi + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/139839680?u=7faffa70275f8ab16f163e0c742a11d2662f9c66&v=4 + url: https://github.com/garg-khushi +- login: skion + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/532192?v=4 + url: https://github.com/skion +- login: florentx + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/142113?u=bf10f10080026346b092633c380977b61cee0d9c&v=4 + url: https://github.com/florentx +- login: jc-louis + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51329768?v=4 + url: https://github.com/jc-louis +- login: WilliamDEdwards + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 + url: https://github.com/WilliamDEdwards +- login: bughuntr7 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/236391583?u=7f51ff690e3a5711f845a115903c39e21c8af938&v=4 + url: https://github.com/bughuntr7 - login: jymchng count: 2 avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 url: https://github.com/jymchng -- login: christiansicari +- login: XieJiSS count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/29756552?v=4 - url: https://github.com/christiansicari -- login: JacobHayes + avatarUrl: https://avatars.githubusercontent.com/u/24671280?u=7ea0d9bfe46cf762594d62fd2f3c6d3813c3584c&v=4 + url: https://github.com/XieJiSS +- login: profatsky count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/2555532?u=354a525847a276bbb4426b0c95791a8ba5970f9b&v=4 - url: https://github.com/JacobHayes -- login: iloveitaly + avatarUrl: https://avatars.githubusercontent.com/u/92920843?u=81e54bb0b613c171f7cd0ab3cbb58873782c9c9c&v=4 + url: https://github.com/profatsky +one_year_experts: +- login: YuriiMotov + count: 906 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov +- login: luzzodev + count: 62 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 + url: https://github.com/luzzodev +- login: tiangolo + count: 30 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: valentinDruzhinin + count: 30 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +- login: alv2017 + count: 19 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +- login: JavierSanchezCastro + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +- login: engripaye + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/155247530?u=645169bc81856b7f1bd20090ecb0171a56dcbeb4&v=4 + url: https://github.com/engripaye +- login: sachinh35 + count: 12 + avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 + url: https://github.com/sachinh35 +- login: yauhen-sobaleu + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/51629535?u=fc1817060daf2df438bfca86c44f33da5cd667db&v=4 + url: https://github.com/yauhen-sobaleu +- login: Toygarmetu + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4 + url: https://github.com/Toygarmetu +- login: yinziyan1206 + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 + url: https://github.com/yinziyan1206 +- login: Kludex + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 + url: https://github.com/Kludex +- login: raceychan + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4 + url: https://github.com/raceychan +- login: ceb10n + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4 + url: https://github.com/ceb10n +- login: RichieB2B + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4 + url: https://github.com/RichieB2B +- login: JunjieAraoXiong + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/167785867?u=b69afe090c8bf5fd73f2d23fc3a887b28f68f192&v=4 + url: https://github.com/JunjieAraoXiong +- login: CodeKraken-cmd + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/48470371?u=e7c0e7ec8e35ca5fb3ae40a586ed5e788fd0fe6d&v=4 + url: https://github.com/CodeKraken-cmd +- login: svlandeg + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4 + url: https://github.com/svlandeg +- login: DoctorJohn + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=ec43fe79a98dbc864b428afc7220753e25ca3af2&v=4 + url: https://github.com/DoctorJohn +- login: WilliamDEdwards + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 + url: https://github.com/WilliamDEdwards +- login: ArmanShirzad + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/68951175?u=1f1efae2fa5d0d17c38a1a8413bedca5e538cedb&v=4 + url: https://github.com/ArmanShirzad +- login: krylosov-aa + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/242901957?u=4c9c7b468203b09bca64936fb464620e32cdd252&v=4 + url: https://github.com/krylosov-aa +- login: isgin01 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=16d6466476cf7dbc55a4cd575b6ea920ebdd81e1&v=4 + url: https://github.com/isgin01 +- login: sinisaos + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/30960668?v=4 + url: https://github.com/sinisaos +- login: dolfinus + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4 + url: https://github.com/dolfinus +- login: jymchng + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 + url: https://github.com/jymchng +- login: simone-trubian + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/5606840?u=65703af3c605feca61ce49e4009bb4e26495b425&v=4 + url: https://github.com/simone-trubian +- login: mahimairaja + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/81288263?u=4eef6b4a36b96e84bd666fc1937aa589036ccb9a&v=4 + url: https://github.com/mahimairaja +- login: pankeshpatel + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/1482917?u=666f39197a88cfa38b8bd78d39ef04d95c948b6b&v=4 + url: https://github.com/pankeshpatel +- login: Jelle-tenB + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 + url: https://github.com/Jelle-tenB +- login: jgould22 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 + url: https://github.com/jgould22 +- login: stan-dot count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/150855?v=4 - url: https://github.com/iloveitaly -- login: iiotsrc + avatarUrl: https://avatars.githubusercontent.com/u/56644812?u=a7dd773084f1c17c5f05019cc25a984e24873691&v=4 + url: https://github.com/stan-dot +- login: Damon0603 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/131771119?u=bcaf2559ef6266af70b151b7fda31a1ee3dbecb3&v=4 - url: https://github.com/iiotsrc -- login: PidgeyBE + avatarUrl: https://avatars.githubusercontent.com/u/110039208?u=f24bf5c30317bc4959118d1b919587c473a865b6&v=4 + url: https://github.com/Damon0603 +- login: huynguyengl99 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/19860056?u=47b584eb1c1ab45e31c1b474109a962d7e82be49&v=4 - url: https://github.com/PidgeyBE -- login: KianAnbarestani + avatarUrl: https://avatars.githubusercontent.com/u/49433085?u=7b626115686c5d97a2a32a03119f5300e425cc9f&v=4 + url: https://github.com/huynguyengl99 +- login: EmmanuelNiyonshuti count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/145364424?u=dcc3d8fb4ca07d36fb52a17f38b6650565de40be&v=4 - url: https://github.com/KianAnbarestani -- login: ykaiqx + avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4 + url: https://github.com/EmmanuelNiyonshuti +- login: Ale-Cas count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/56395004?u=1eebf5ce25a8067f7bfa6251a24f667be492d9d6&v=4 - url: https://github.com/ykaiqx -- login: AliYmn + avatarUrl: https://avatars.githubusercontent.com/u/64859146?u=d52a6ecf8d83d2927e2ae270bdfcc83495dba8c9&v=4 + url: https://github.com/Ale-Cas +- login: tiborrr count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/18416653?u=a77e2605e3ce6aaf6fef8ad4a7b0d32954fba47a&v=4 - url: https://github.com/AliYmn -- login: gelezo43 + avatarUrl: https://avatars.githubusercontent.com/u/16014746?u=0ce47015e53009e90393582fe86b7b90e809bc28&v=4 + url: https://github.com/tiborrr +- login: davidbrochart count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/40732698?u=611f39d3c1d2f4207a590937a78c1f10eed6232c&v=4 - url: https://github.com/gelezo43 -- login: jfeaver + avatarUrl: https://avatars.githubusercontent.com/u/4711805?u=d39696d995a9e02ec3613ffb2f62b20b14f92f26&v=4 + url: https://github.com/davidbrochart +- login: CharlieReitzel count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/1091338?u=0bcba366447d8fadad63f6705a52d128da4c7ec2&v=4 - url: https://github.com/jfeaver + avatarUrl: https://avatars.githubusercontent.com/u/20848272?v=4 + url: https://github.com/CharlieReitzel +- login: kiranzo + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1070878?u=68f78a891c9751dd87571ac712a6309090c4bc01&v=4 + url: https://github.com/kiranzo +- login: dotmitsu + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/42657211?u=3bccc9a2f386a3f24230ec393080f8904fe2a5b2&v=4 + url: https://github.com/dotmitsu +- login: Brikas + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/80290187?u=2b72e497ca4444ecec1f9dc2d1b8d5437a27b83f&v=4 + url: https://github.com/Brikas +- login: BloodyRain2k + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1014362?v=4 + url: https://github.com/BloodyRain2k +- login: usiqwerty + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/37992525?u=0c6e91d7b3887aa558755f4225ce74a003cbe852&v=4 + url: https://github.com/usiqwerty +- login: garg-khushi + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/139839680?u=7faffa70275f8ab16f163e0c742a11d2662f9c66&v=4 + url: https://github.com/garg-khushi +- login: sk- + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/911768?u=3bfaf87089eb03ef0fa378f316b9c783f431aa9b&v=4 + url: https://github.com/sk- +- login: skion + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/532192?v=4 + url: https://github.com/skion +- login: Danstiv + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/50794055?v=4 + url: https://github.com/Danstiv +- login: florentx + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/142113?u=bf10f10080026346b092633c380977b61cee0d9c&v=4 + url: https://github.com/florentx +- login: jc-louis + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51329768?v=4 + url: https://github.com/jc-louis +- login: bughuntr7 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/236391583?u=7f51ff690e3a5711f845a115903c39e21c8af938&v=4 + url: https://github.com/bughuntr7 +- login: purepani + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 + url: https://github.com/purepani +- login: asmaier + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/3169297?v=4 + url: https://github.com/asmaier +- login: henrymcl + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4 + url: https://github.com/henrymcl +- login: potiuk + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/595491?v=4 + url: https://github.com/potiuk +- login: EverStarck + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4 + url: https://github.com/EverStarck +- login: sanderbollen-clockworks + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/183479560?v=4 + url: https://github.com/sanderbollen-clockworks +- login: davidhuser + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 + url: https://github.com/davidhuser +- login: XieJiSS + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/24671280?u=7ea0d9bfe46cf762594d62fd2f3c6d3813c3584c&v=4 + url: https://github.com/XieJiSS +- login: profatsky + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/92920843?u=81e54bb0b613c171f7cd0ab3cbb58873782c9c9c&v=4 + url: https://github.com/profatsky From 2f9c914d440e99fe8b62646b51e4d24c5bcd858b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 23 Feb 2026 18:48:43 +0000 Subject: [PATCH 187/195] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2b068a572..f56d34b5e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* 👥 Update FastAPI People - Experts. PR [#14972](https://github.com/fastapi/fastapi/pull/14972) by [@tiangolo](https://github.com/tiangolo). * 👷 Allow skipping `benchmark` job in `test` workflow. PR [#14974](https://github.com/fastapi/fastapi/pull/14974) by [@YuriiMotov](https://github.com/YuriiMotov). ## 0.132.0 From 2686c7fbbf9abb17902a2981a9d9fca01f5117b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 24 Feb 2026 01:28:10 -0800 Subject: [PATCH 188/195] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20logic?= =?UTF-8?q?=20to=20handle=20OpenAPI=20and=20Swagger=20UI=20escaping=20data?= =?UTF-8?q?=20(#14986)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/applications.py | 16 +++--- fastapi/openapi/docs.py | 18 ++++++- tests/test_openapi_cache_root_path.py | 75 +++++++++++++++++++++++++++ tests/test_swagger_ui_escape.py | 37 +++++++++++++ 4 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 tests/test_openapi_cache_root_path.py create mode 100644 tests/test_swagger_ui_escape.py diff --git a/fastapi/applications.py b/fastapi/applications.py index ed05a1ff9..e7e816c2e 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -1101,16 +1101,18 @@ class FastAPI(Starlette): def setup(self) -> None: if self.openapi_url: - urls = (server_data.get("url") for server_data in self.servers) - server_urls = {url for url in urls if url} async def openapi(req: Request) -> JSONResponse: root_path = req.scope.get("root_path", "").rstrip("/") - if root_path not in server_urls: - if root_path and self.root_path_in_servers: - self.servers.insert(0, {"url": root_path}) - server_urls.add(root_path) - return JSONResponse(self.openapi()) + schema = self.openapi() + if root_path and self.root_path_in_servers: + server_urls = {s.get("url") for s in schema.get("servers", [])} + if root_path not in server_urls: + schema = dict(schema) + schema["servers"] = [{"url": root_path}] + schema.get( + "servers", [] + ) + return JSONResponse(schema) self.add_route(self.openapi_url, openapi, include_in_schema=False) if self.openapi_url and self.docs_url: diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py index b845f87c1..0d9242f9f 100644 --- a/fastapi/openapi/docs.py +++ b/fastapi/openapi/docs.py @@ -5,6 +5,20 @@ from annotated_doc import Doc from fastapi.encoders import jsonable_encoder from starlette.responses import HTMLResponse + +def _html_safe_json(value: Any) -> str: + """Serialize a value to JSON with HTML special characters escaped. + + This prevents injection when the JSON is embedded inside a " + html = get_swagger_ui_html( + openapi_url="/openapi.json", + title="Test", + init_oauth={"appName": xss_payload}, + ) + body = html.body.decode() + + assert "